From dd44f78fc544d50d792b50e5bfa4651fcfd4e06c Mon Sep 17 00:00:00 2001 From: L Date: Sat, 4 Feb 2023 21:00:01 +0100 Subject: [PATCH] Added gamespy SDK --- code/gamespy/Chat/changelog.txt | 131 + code/gamespy/Chat/chat.h | 998 +++ code/gamespy/Chat/chatASCII.h | 441 ++ code/gamespy/Chat/chatCallbacks.c | 1654 ++++ code/gamespy/Chat/chatCallbacks.h | 329 + code/gamespy/Chat/chatChannel.c | 1265 ++++ code/gamespy/Chat/chatChannel.h | 64 + code/gamespy/Chat/chatCrypt.c | 65 + code/gamespy/Chat/chatCrypt.h | 24 + code/gamespy/Chat/chatHandlers.c | 4720 ++++++++++++ code/gamespy/Chat/chatHandlers.h | 82 + code/gamespy/Chat/chatMain.c | 3075 ++++++++ code/gamespy/Chat/chatMain.h | 133 + code/gamespy/Chat/chatSocket.c | 1032 +++ code/gamespy/Chat/chatSocket.h | 96 + code/gamespy/Chat/chatc/chatc.c | 552 ++ code/gamespy/Chat/chatc/chatnitrocw/Nitro.lcf | 493 ++ .../gamespy/Chat/chatc/chatnitrocw/ROM-TS.rsf | 116 + .../Chat/chatc/chatps2prodg/chatps2prodg.dsp | 384 + .../Chat/chatc/chatps2prodg/chatps2prodg.dsw | 37 + code/gamespy/Chat/chatty/ChannelListDlg.cpp | 164 + code/gamespy/Chat/chatty/ChannelListDlg.h | 49 + code/gamespy/Chat/chatty/ChannelModeDlg.cpp | 56 + code/gamespy/Chat/chatty/ChannelModeDlg.h | 52 + code/gamespy/Chat/chatty/ChildFrm.cpp | 69 + code/gamespy/Chat/chatty/ChildFrm.h | 51 + code/gamespy/Chat/chatty/ConnectDlg.cpp | 47 + code/gamespy/Chat/chatty/ConnectDlg.h | 48 + code/gamespy/Chat/chatty/EnterDlg.cpp | 74 + code/gamespy/Chat/chatty/EnterDlg.h | 52 + code/gamespy/Chat/chatty/GetUserInfoDlg.cpp | 80 + code/gamespy/Chat/chatty/GetUserInfoDlg.h | 50 + code/gamespy/Chat/chatty/KickReasonDlg.cpp | 43 + code/gamespy/Chat/chatty/KickReasonDlg.h | 46 + code/gamespy/Chat/chatty/MainFrm.cpp | 133 + code/gamespy/Chat/chatty/MainFrm.h | 58 + code/gamespy/Chat/chatty/ReadMe.txt | 117 + code/gamespy/Chat/chatty/SendRawDlg.cpp | 43 + code/gamespy/Chat/chatty/SendRawDlg.h | 46 + code/gamespy/Chat/chatty/SetPasswordDlg.cpp | 45 + code/gamespy/Chat/chatty/SetPasswordDlg.h | 47 + code/gamespy/Chat/chatty/SetTopicDlg.cpp | 43 + code/gamespy/Chat/chatty/SetTopicDlg.h | 46 + code/gamespy/Chat/chatty/StdAfx.cpp | 8 + code/gamespy/Chat/chatty/StdAfx.h | 31 + code/gamespy/Chat/chatty/TalkDlg.cpp | 45 + code/gamespy/Chat/chatty/TalkDlg.h | 47 + code/gamespy/Chat/chatty/chatty.cpp | 377 + code/gamespy/Chat/chatty/chatty.h | 59 + code/gamespy/Chat/chatty/chatty.rc | 737 ++ code/gamespy/Chat/chatty/chattyDoc.cpp | 586 ++ code/gamespy/Chat/chatty/chattyDoc.h | 80 + code/gamespy/Chat/chatty/chattyView.cpp | 391 + code/gamespy/Chat/chatty/chattyView.h | 86 + code/gamespy/Chat/chatty/res/Toolbar.bmp | Bin 0 -> 2518 bytes code/gamespy/Chat/chatty/res/chatty.ico | Bin 0 -> 1078 bytes code/gamespy/Chat/chatty/res/chatty.rc2 | 13 + code/gamespy/Chat/chatty/res/chattyDoc.ico | Bin 0 -> 1078 bytes code/gamespy/Chat/chatty/resource.h | 91 + code/gamespy/GP/changelog.txt | 182 + code/gamespy/GP/gp.c | 4318 +++++++++++ code/gamespy/GP/gp.h | 1631 ++++ code/gamespy/GP/gpi.c | 816 ++ code/gamespy/GP/gpi.h | 231 + code/gamespy/GP/gpiBuddy.c | 1064 +++ code/gamespy/GP/gpiBuddy.h | 104 + code/gamespy/GP/gpiBuffer.c | 766 ++ code/gamespy/GP/gpiBuffer.h | 167 + code/gamespy/GP/gpiCallback.c | 277 + code/gamespy/GP/gpiCallback.h | 109 + code/gamespy/GP/gpiConnect.c | 988 +++ code/gamespy/GP/gpiConnect.h | 69 + code/gamespy/GP/gpiInfo.c | 1286 ++++ code/gamespy/GP/gpiInfo.h | 144 + code/gamespy/GP/gpiKeys.c | 196 + code/gamespy/GP/gpiKeys.h | 23 + code/gamespy/GP/gpiOperation.c | 363 + code/gamespy/GP/gpiOperation.h | 120 + code/gamespy/GP/gpiPS3.c | 592 ++ code/gamespy/GP/gpiPS3.h | 54 + code/gamespy/GP/gpiPeer.c | 1308 ++++ code/gamespy/GP/gpiPeer.h | 186 + code/gamespy/GP/gpiProfile.c | 1390 ++++ code/gamespy/GP/gpiProfile.h | 229 + code/gamespy/GP/gpiSearch.c | 1656 ++++ code/gamespy/GP/gpiSearch.h | 172 + code/gamespy/GP/gpiTransfer.c | 2000 +++++ code/gamespy/GP/gpiTransfer.h | 210 + code/gamespy/GP/gpiUnique.c | 234 + code/gamespy/GP/gpiUnique.h | 53 + code/gamespy/GP/gpiUtility.c | 407 + code/gamespy/GP/gpiUtility.h | 127 + code/gamespy/GP/gpstress/gpstress.c | 1707 +++++ code/gamespy/Peer/PeerLobby/ConnectPage.cpp | 762 ++ code/gamespy/Peer/PeerLobby/ConnectPage.h | 57 + code/gamespy/Peer/PeerLobby/CreatePage.cpp | 115 + code/gamespy/Peer/PeerLobby/CreatePage.h | 55 + code/gamespy/Peer/PeerLobby/GroupPage.cpp | 668 ++ code/gamespy/Peer/PeerLobby/GroupPage.h | 88 + code/gamespy/Peer/PeerLobby/LobbyWizard.cpp | 200 + code/gamespy/Peer/PeerLobby/LobbyWizard.h | 84 + code/gamespy/Peer/PeerLobby/PeerLobby.cpp | 101 + code/gamespy/Peer/PeerLobby/PeerLobby.h | 48 + code/gamespy/Peer/PeerLobby/PeerLobby.rc | 308 + code/gamespy/Peer/PeerLobby/ReadMe.txt | 88 + code/gamespy/Peer/PeerLobby/SideBarCtrl.cpp | 217 + code/gamespy/Peer/PeerLobby/SideBarCtrl.h | 51 + code/gamespy/Peer/PeerLobby/StagingPage.cpp | 486 ++ code/gamespy/Peer/PeerLobby/StagingPage.h | 70 + code/gamespy/Peer/PeerLobby/StdAfx.cpp | 8 + code/gamespy/Peer/PeerLobby/StdAfx.h | 31 + code/gamespy/Peer/PeerLobby/TitlePage.cpp | 502 ++ code/gamespy/Peer/PeerLobby/TitlePage.h | 70 + code/gamespy/Peer/PeerLobby/res/PeerLobby.ico | Bin 0 -> 1078 bytes code/gamespy/Peer/PeerLobby/res/PeerLobby.rc2 | 13 + code/gamespy/Peer/PeerLobby/res/ico00001.ico | Bin 0 -> 318 bytes code/gamespy/Peer/PeerLobby/res/ico00002.ico | Bin 0 -> 318 bytes code/gamespy/Peer/PeerLobby/res/ico00003.ico | Bin 0 -> 318 bytes code/gamespy/Peer/PeerLobby/res/ico00004.ico | Bin 0 -> 318 bytes code/gamespy/Peer/PeerLobby/res/icon1.ico | Bin 0 -> 318 bytes code/gamespy/Peer/PeerLobby/resource.h | 45 + code/gamespy/Peer/changelog.txt | 255 + code/gamespy/Peer/peer.h | 1866 +++++ code/gamespy/Peer/peerAscii.h | 734 ++ code/gamespy/Peer/peerAutoMatch.c | 334 + code/gamespy/Peer/peerAutoMatch.h | 39 + code/gamespy/Peer/peerCallbacks.c | 3695 +++++++++ code/gamespy/Peer/peerCallbacks.h | 401 + code/gamespy/Peer/peerGlobalCallbacks.c | 1081 +++ code/gamespy/Peer/peerGlobalCallbacks.h | 56 + code/gamespy/Peer/peerHost.c | 79 + code/gamespy/Peer/peerHost.h | 34 + code/gamespy/Peer/peerKeys.c | 999 +++ code/gamespy/Peer/peerKeys.h | 49 + code/gamespy/Peer/peerMain.c | 4409 +++++++++++ code/gamespy/Peer/peerMain.h | 223 + code/gamespy/Peer/peerMangle.c | 292 + code/gamespy/Peer/peerMangle.h | 46 + code/gamespy/Peer/peerOperations.c | 1844 +++++ code/gamespy/Peer/peerOperations.h | 221 + code/gamespy/Peer/peerPing.c | 1118 +++ code/gamespy/Peer/peerPing.h | 41 + code/gamespy/Peer/peerPlayers.c | 824 ++ code/gamespy/Peer/peerPlayers.h | 92 + code/gamespy/Peer/peerQR.c | 494 ++ code/gamespy/Peer/peerQR.h | 44 + code/gamespy/Peer/peerRooms.c | 416 + code/gamespy/Peer/peerRooms.h | 40 + code/gamespy/Peer/peerSB.c | 1112 +++ code/gamespy/Peer/peerSB.h | 46 + code/gamespy/Peer/peerc/peerc.c | 773 ++ code/gamespy/Peer/peerc/peernitrocw/Nitro.lcf | 493 ++ .../gamespy/Peer/peerc/peernitrocw/ROM-TS.rsf | 116 + .../Peer/peerc/peerps2prodg/peerps2prodg.dsp | 564 ++ .../Peer/peerc/peerps2prodg/peerps2prodg.dsw | 37 + .../Voice2/Voice2BuddyMFC/LoginDlg.cpp | 107 + code/gamespy/Voice2/Voice2BuddyMFC/LoginDlg.h | 50 + .../Voice2/Voice2BuddyMFC/SetupDlg.cpp | 236 + code/gamespy/Voice2/Voice2BuddyMFC/SetupDlg.h | 81 + code/gamespy/Voice2/Voice2BuddyMFC/StdAfx.cpp | 8 + code/gamespy/Voice2/Voice2BuddyMFC/StdAfx.h | 27 + .../Voice2/Voice2BuddyMFC/Voice2BuddyMFC.cpp | 74 + .../Voice2/Voice2BuddyMFC/Voice2BuddyMFC.h | 49 + .../Voice2/Voice2BuddyMFC/Voice2BuddyMFC.rc | 300 + .../Voice2BuddyMFC/Voice2BuddyMFCDlg.cpp | 600 ++ .../Voice2/Voice2BuddyMFC/Voice2BuddyMFCDlg.h | 74 + .../Voice2/Voice2BuddyMFC/VoiceSessionDlg.cpp | 397 + .../Voice2/Voice2BuddyMFC/VoiceSessionDlg.h | 66 + .../Voice2BuddyMFC/res/Voice2BuddyMFC.ico | Bin 0 -> 1078 bytes .../Voice2BuddyMFC/res/Voice2BuddyMFC.rc2 | 13 + .../Voice2/Voice2BuddyMFC/res/gamespyl.bmp | Bin 0 -> 67450 bytes .../Voice2/Voice2BuddyMFC/res/logo270x83.bmp | Bin 0 -> 67450 bytes .../res/microsoft-microphone.bmp | Bin 0 -> 3126 bytes .../Voice2BuddyMFC/res/microsoft-speaker.bmp | Bin 0 -> 3126 bytes .../Voice2/Voice2BuddyMFC/res/speaking.bmp | Bin 0 -> 2454 bytes .../Voice2/Voice2BuddyMFC/res/speaking2.bmp | Bin 0 -> 2454 bytes code/gamespy/Voice2/Voice2BuddyMFC/resource.h | 45 + code/gamespy/Voice2/changelog.txt | 105 + code/gamespy/Voice2/gv.h | 234 + code/gamespy/Voice2/gvCodec.c | 354 + code/gamespy/Voice2/gvCodec.h | 47 + code/gamespy/Voice2/gvCustomDevice.c | 385 + code/gamespy/Voice2/gvCustomDevice.h | 25 + code/gamespy/Voice2/gvDevice.c | 114 + code/gamespy/Voice2/gvDevice.h | 90 + code/gamespy/Voice2/gvDirectSound.c | 1067 +++ code/gamespy/Voice2/gvDirectSound.h | 25 + code/gamespy/Voice2/gvFrame.c | 107 + code/gamespy/Voice2/gvFrame.h | 51 + code/gamespy/Voice2/gvGSM.c | 97 + code/gamespy/Voice2/gvGSM.h | 39 + code/gamespy/Voice2/gvLogitechPS2Codecs.c | 118 + code/gamespy/Voice2/gvLogitechPS2Codecs.h | 41 + code/gamespy/Voice2/gvMain.c | 318 + code/gamespy/Voice2/gvMain.h | 18 + code/gamespy/Voice2/gvOSXAudio.c | 1465 ++++ code/gamespy/Voice2/gvOSXAudio.h | 29 + code/gamespy/Voice2/gvPS2Audio.c | 107 + code/gamespy/Voice2/gvPS2Audio.h | 29 + code/gamespy/Voice2/gvPS2Eyetoy.c | 560 ++ code/gamespy/Voice2/gvPS2Eyetoy.h | 32 + code/gamespy/Voice2/gvPS2Headset.c | 751 ++ code/gamespy/Voice2/gvPS2Headset.h | 29 + code/gamespy/Voice2/gvPS2Spu2.c | 681 ++ code/gamespy/Voice2/gvPS2Spu2.h | 29 + code/gamespy/Voice2/gvPS3Audio.c | 48 + code/gamespy/Voice2/gvPS3Audio.h | 21 + code/gamespy/Voice2/gvPS3Headset.c | 1141 +++ code/gamespy/Voice2/gvPS3Headset.h | 22 + code/gamespy/Voice2/gvPSPAudio.c | 503 ++ code/gamespy/Voice2/gvPSPAudio.h | 29 + code/gamespy/Voice2/gvSource.c | 486 ++ code/gamespy/Voice2/gvSource.h | 40 + code/gamespy/Voice2/gvSpeex.c | 187 + code/gamespy/Voice2/gvSpeex.h | 62 + code/gamespy/Voice2/gvSpeexSpu.c | 226 + code/gamespy/Voice2/gvSpeexSpu.h | 65 + code/gamespy/Voice2/gvUtil.c | 78 + code/gamespy/Voice2/gvUtil.h | 35 + code/gamespy/Voice2/voice2bench/voice2bench.c | 261 + .../voice2bench_prefix_eenet_debug.h | 4 + .../voice2bench_prefix_eenet_release.h | 4 + .../voice2bench_prefix_insock_debug.h | 4 + .../voice2bench_prefix_insock_release.h | 4 + .../voice2bench_prefix_snsystems_debug.h | 4 + .../voice2bench_prefix_snsystems_release.h | 4 + .../voice2benchps2prodg.dsp | 138 + .../voice2benchps2prodg.dsw | 37 + code/gamespy/Voice2/voice2bench/voicesample.h | 6671 +++++++++++++++++ code/gamespy/changelog.txt | 151 + code/gamespy/common/changelog.txt | 321 + code/gamespy/common/gsAssert.c | 182 + code/gamespy/common/gsAssert.h | 86 + code/gamespy/common/gsAvailable.c | 215 + code/gamespy/common/gsAvailable.h | 54 + code/gamespy/common/gsCommon.h | 48 + code/gamespy/common/gsCore.c | 446 ++ code/gamespy/common/gsCore.h | 128 + code/gamespy/common/gsCrypt.c | 509 ++ code/gamespy/common/gsCrypt.h | 90 + code/gamespy/common/gsDebug.c | 388 + code/gamespy/common/gsDebug.h | 198 + code/gamespy/common/gsLargeInt.c | 1904 +++++ code/gamespy/common/gsLargeInt.h | 88 + code/gamespy/common/gsMemory.c | 1774 +++++ code/gamespy/common/gsMemory.h | 177 + code/gamespy/common/gsPlatform.c | 88 + code/gamespy/common/gsPlatform.h | 487 ++ code/gamespy/common/gsPlatformSocket.c | 685 ++ code/gamespy/common/gsPlatformSocket.h | 654 ++ code/gamespy/common/gsPlatformThread.c | 52 + code/gamespy/common/gsPlatformThread.h | 197 + code/gamespy/common/gsPlatformUtil.c | 1897 +++++ code/gamespy/common/gsPlatformUtil.h | 159 + code/gamespy/common/gsRC4.c | 58 + code/gamespy/common/gsRC4.h | 34 + code/gamespy/common/gsSHA1.c | 390 + code/gamespy/common/gsSHA1.h | 93 + code/gamespy/common/gsSSL.c | 37 + code/gamespy/common/gsSSL.h | 186 + code/gamespy/common/gsSoap.c | 280 + code/gamespy/common/gsSoap.h | 73 + code/gamespy/common/gsStringUtil.c | 683 ++ code/gamespy/common/gsStringUtil.h | 83 + code/gamespy/common/gsUdpEngine.c | 1171 +++ code/gamespy/common/gsUdpEngine.h | 180 + code/gamespy/common/gsXML.c | 2036 +++++ code/gamespy/common/gsXML.h | 127 + code/gamespy/common/linux/LinuxCommon.c | 94 + code/gamespy/common/linux/changelog.txt | 11 + code/gamespy/common/linux/gsSocketLinux.c | 0 code/gamespy/common/linux/gsThreadLinux.c | 212 + code/gamespy/common/linux/gsUtilLinux.c | 17 + code/gamespy/common/macosx/MacOSXCommon.c | 47 + code/gamespy/common/macosx/changelog.txt | 15 + code/gamespy/common/macosx/gsThreadMacOSX.c | 213 + code/gamespy/common/macosx/gsUtilMacOSX.c | 17 + code/gamespy/common/nitro/backup.c | 185 + code/gamespy/common/nitro/backup.h | 11 + code/gamespy/common/nitro/changelog.txt | 28 + code/gamespy/common/nitro/font.c | 587 ++ code/gamespy/common/nitro/font.h | 44 + code/gamespy/common/nitro/gsSocketNitro.c | 194 + code/gamespy/common/nitro/gsThreadNitro.c | 155 + code/gamespy/common/nitro/gsTimerNitro.c | 19 + code/gamespy/common/nitro/gsUtilNitro.c | 18 + code/gamespy/common/nitro/key.c | 76 + code/gamespy/common/nitro/key.h | 28 + code/gamespy/common/nitro/main.c | 122 + code/gamespy/common/nitro/menu.c | 410 + code/gamespy/common/nitro/menu.h | 65 + code/gamespy/common/nitro/nitrocommon.cww | 439 ++ .../common/nitro/nitrosample/Nitro.lcf | 509 ++ .../common/nitro/nitrosample/ROM-TS.rsf | 116 + .../common/nitro/nitrosample/nitrosample.c | 606 ++ code/gamespy/common/nitro/screen.c | 237 + code/gamespy/common/nitro/screen.h | 50 + code/gamespy/common/nitro/touch.c | 44 + code/gamespy/common/nitro/touch.h | 15 + code/gamespy/common/nitro/wireless.c | 1541 ++++ code/gamespy/common/nitro/wireless.h | 7 + code/gamespy/common/ps2/changelog.txt | 47 + .../gamespy/common/ps2/cw/LinkSegment_PS2.lcf | 161 + .../common/ps2/cw/PREFIX_PS2_DEBUG_TC296.h | 12 + .../common/ps2/cw/PREFIX_PS2_RELEASE_TC296.h | 11 + code/gamespy/common/ps2/cw/cw.cww | 364 + .../common/ps2/cw/cwprefix_eenet_debug.h | 6 + .../ps2/cw/cwprefix_eenet_debug_uniqueid.h | 5 + .../common/ps2/cw/cwprefix_eenet_release.h | 5 + .../ps2/cw/cwprefix_eenet_release_uniqueid.h | 4 + .../common/ps2/cw/cwprefix_insock_debug.h | 6 + .../ps2/cw/cwprefix_insock_debug_uniqueid.h | 5 + .../common/ps2/cw/cwprefix_insock_release.h | 5 + .../ps2/cw/cwprefix_insock_release_uniqueid.h | 4 + .../common/ps2/cw/cwprefix_snsystems_debug.h | 6 + .../cw/cwprefix_snsystems_debug_uniqueid.h | 5 + .../ps2/cw/cwprefix_snsystems_release.h | 5 + .../cw/cwprefix_snsystems_release_uniqueid.h | 4 + .../ps2/ent_cnf/cw/LinkSegment_PS2IOP.lcf | 78 + .../ps2/ent_cnf/cw/PREFIX_PS2IOP_DEBUG.h | 13 + .../ps2/ent_cnf/cw/PREFIX_PS2IOP_RELEASE.h | 9 + .../common/ps2/ent_cnf/prodg/ent_cnf.dsp | 119 + .../common/ps2/ent_cnf/prodg/ent_cnf.dsw | 37 + code/gamespy/common/ps2/gsSocketPs2.c | 78 + code/gamespy/common/ps2/gsThreadPs2.c | 204 + code/gamespy/common/ps2/gsUtilPs2.c | 15 + code/gamespy/common/ps2/prodg/PS2_in_VC.h | 45 + code/gamespy/common/ps2/prodg/prodg.dsw | 229 + code/gamespy/common/ps2/prodg/ps2.lk | 88 + code/gamespy/common/ps2/ps2common.c | 876 +++ code/gamespy/common/ps2/ps2pad.c | 214 + code/gamespy/common/ps2/ps2pad.h | 30 + .../SpeexSpursTaskManager/CellConfiguration.h | 18 + .../SpeexSpursTaskManager/CellVectorMath.h | 25 + .../ps3/SpeexSpursTaskManager/PS3Types.h | 7 + .../ps3/SpeexSpursTaskManager/SPUAssert.h | 44 + .../SpeexSpursTask/SpuSpeexTaskMain.cpp | 515 ++ .../SpeexSpursTaskManager/SpuDoubleBuffer.h | 106 + .../ps3/SpeexSpursTaskManager/SpuFakeDma.h | 36 + .../SpuSpeexTaskOutput.h | 33 + .../SpursSpeexCInterface.cpp | 102 + .../SpursSpeexCInterface.h | 54 + .../SpursSpeexTaskManager.cpp | 273 + .../SpursSpeexTaskManager.h | 122 + .../spursAlignedAllocator.cpp | 38 + .../spursAlignedAllocator.h | 81 + .../spursAlignedObjectArray.h | 370 + .../spursConfiguration.h | 96 + .../spursPlatformDefinitions.h | 13 + .../ps3/SpeexSpursTaskManager/spursScalar.h | 172 + .../spursSupportInterface.cpp | 553 ++ .../spursSupportInterface.h | 133 + .../spursThreadSupportInterface.cpp | 21 + .../spursUtilityMacros.h | 21 + .../spursthreadsupportinterface.h | 43 + code/gamespy/common/ps3/gsSocketPS3.c | 43 + code/gamespy/common/ps3/gsUtilPS3.c | 76 + code/gamespy/common/ps3/ps3common.c | 248 + code/gamespy/common/psp/gsSocketPSP.c | 350 + code/gamespy/common/psp/gsUtilPSP.c | 275 + code/gamespy/common/psp/pspcommon.c | 416 + .../common/revolution/gsSocketRevolution.c | 197 + .../common/revolution/gsThreadRevoulution.c | 183 + .../common/revolution/gsUtilRevolution.c | 108 + .../common/revolution/revolutionCommon.c | 240 + code/gamespy/common/win32/Win32Common.c | 98 + code/gamespy/common/win32/changelog.txt | 14 + code/gamespy/common/win32/gsSocketWin32.c | 3 + code/gamespy/common/win32/gsThreadWin32.c | 87 + code/gamespy/common/win32/gsUtilWin32.c | 19 + code/gamespy/common/x360/X360Common.c | 93 + code/gamespy/common/x360/gsSocketX360.c | 52 + code/gamespy/common/x360/gsThreadX360.c | 87 + code/gamespy/common/xbox/gsSocketXbox.c | 12 + code/gamespy/darray.c | 377 + code/gamespy/darray.h | 317 + code/gamespy/gcd.cmake | 115 + code/gamespy/gcdkey/CdkeyGen/gcdkeygen.c | 227 + code/gamespy/gcdkey/changelog.txt | 78 + code/gamespy/gcdkey/gcdkeyc.c | 69 + code/gamespy/gcdkey/gcdkeyc.h | 40 + code/gamespy/gcdkey/gcdkeys.c | 961 +++ code/gamespy/gcdkey/gcdkeys.h | 124 + code/gamespy/ghttp/changelog.txt | 156 + code/gamespy/ghttp/ghttp.h | 644 ++ code/gamespy/ghttp/ghttpASCII.h | 270 + code/gamespy/ghttp/ghttpBuffer.c | 561 ++ code/gamespy/ghttp/ghttpBuffer.h | 170 + code/gamespy/ghttp/ghttpCallbacks.c | 114 + code/gamespy/ghttp/ghttpCallbacks.h | 48 + code/gamespy/ghttp/ghttpCommon.c | 597 ++ code/gamespy/ghttp/ghttpCommon.h | 154 + code/gamespy/ghttp/ghttpConnection.c | 424 ++ code/gamespy/ghttp/ghttpConnection.h | 217 + code/gamespy/ghttp/ghttpEncryption.c | 1786 +++++ code/gamespy/ghttp/ghttpEncryption.h | 137 + code/gamespy/ghttp/ghttpMain.c | 1485 ++++ code/gamespy/ghttp/ghttpMain.h | 19 + code/gamespy/ghttp/ghttpPost.c | 1643 ++++ code/gamespy/ghttp/ghttpPost.h | 126 + code/gamespy/ghttp/ghttpProcess.c | 1740 +++++ code/gamespy/ghttp/ghttpProcess.h | 37 + code/gamespy/ghttp/ghttpc/ghttpc.c | 313 + .../ghttp/ghttpc/ghttpnitrocw/Nitro.lcf | 493 ++ .../ghttp/ghttpc/ghttpnitrocw/ROM-TS.rsf | 116 + .../ghttpc/ghttpps2prodg/ghttpps2prodg.dsp | 444 ++ .../ghttpc/ghttpps2prodg/ghttpps2prodg.dsw | 37 + code/gamespy/ghttp/ghttpmfc/ReadMe.txt | 88 + code/gamespy/ghttp/ghttpmfc/StdAfx.cpp | 8 + code/gamespy/ghttp/ghttpmfc/StdAfx.h | 27 + code/gamespy/ghttp/ghttpmfc/ghttpmfc.cpp | 72 + code/gamespy/ghttp/ghttpmfc/ghttpmfc.h | 49 + code/gamespy/ghttp/ghttpmfc/ghttpmfc.rc | 240 + code/gamespy/ghttp/ghttpmfc/ghttpmfcDlg.cpp | 577 ++ code/gamespy/ghttp/ghttpmfc/ghttpmfcDlg.h | 79 + code/gamespy/ghttp/ghttpmfc/res/ghttpmfc.ico | Bin 0 -> 1078 bytes code/gamespy/ghttp/ghttpmfc/res/ghttpmfc.rc2 | 13 + code/gamespy/ghttp/ghttpmfc/resource.h | 54 + code/gamespy/gstats/changelog.txt | 102 + code/gamespy/gstats/gbucket.c | 396 + code/gamespy/gstats/gbucket.h | 52 + code/gamespy/gstats/gp_stats/StdAfx.cpp | 8 + code/gamespy/gstats/gp_stats/StdAfx.h | 27 + code/gamespy/gstats/gp_stats/gp_stats.cpp | 74 + code/gamespy/gstats/gp_stats/gp_stats.h | 49 + code/gamespy/gstats/gp_stats/gp_stats.rc | 201 + code/gamespy/gstats/gp_stats/gp_statsDlg.cpp | 733 ++ code/gamespy/gstats/gp_stats/gp_statsDlg.h | 77 + code/gamespy/gstats/gp_stats/res/gp_stats.ico | Bin 0 -> 1078 bytes code/gamespy/gstats/gp_stats/res/gp_stats.rc2 | 13 + code/gamespy/gstats/gp_stats/resource.h | 33 + code/gamespy/gstats/gpersist.h | 415 + code/gamespy/gstats/gstats.c | 1681 +++++ code/gamespy/gstats/gstats.h | 389 + .../gstats/ladderTrack/HostOrJoinDlg.cpp | 69 + .../gstats/ladderTrack/HostOrJoinDlg.h | 51 + code/gamespy/gstats/ladderTrack/LoginDlg.cpp | 188 + code/gamespy/gstats/ladderTrack/LoginDlg.h | 50 + code/gamespy/gstats/ladderTrack/StdAfx.cpp | 8 + code/gamespy/gstats/ladderTrack/StdAfx.h | 32 + .../gamespy/gstats/ladderTrack/WaitingDlg.cpp | 43 + code/gamespy/gstats/ladderTrack/WaitingDlg.h | 46 + .../gstats/ladderTrack/ladderTrack.cpp | 63 + code/gamespy/gstats/ladderTrack/ladderTrack.h | 47 + .../gamespy/gstats/ladderTrack/ladderTrack.rc | 241 + .../gstats/ladderTrack/ladderTrackDlg.cpp | 989 +++ .../gstats/ladderTrack/ladderTrackDlg.h | 117 + .../gstats/ladderTrack/res/ladderTrack.ico | Bin 0 -> 1078 bytes .../gstats/ladderTrack/res/ladderTrack.rc2 | 13 + code/gamespy/gstats/ladderTrack/resource.h | 34 + .../gstats/multiTrack/HostOrJoinDlg.cpp | 70 + .../gamespy/gstats/multiTrack/HostOrJoinDlg.h | 51 + code/gamespy/gstats/multiTrack/LoginDlg.cpp | 186 + code/gamespy/gstats/multiTrack/LoginDlg.h | 50 + code/gamespy/gstats/multiTrack/StdAfx.cpp | 8 + code/gamespy/gstats/multiTrack/StdAfx.h | 31 + code/gamespy/gstats/multiTrack/WaitingDlg.cpp | 43 + code/gamespy/gstats/multiTrack/WaitingDlg.h | 45 + code/gamespy/gstats/multiTrack/multiTrack.cpp | 61 + code/gamespy/gstats/multiTrack/multiTrack.h | 47 + code/gamespy/gstats/multiTrack/multiTrack.rc | 253 + .../gstats/multiTrack/multiTrackDlg.cpp | 1028 +++ .../gamespy/gstats/multiTrack/multiTrackDlg.h | 135 + .../gstats/multiTrack/res/multiTrack.ico | Bin 0 -> 1078 bytes .../gstats/multiTrack/res/multiTrack.rc2 | 13 + code/gamespy/gstats/multiTrack/resource.h | 41 + code/gamespy/gstats/track/LoginDlg.cpp | 174 + code/gamespy/gstats/track/LoginDlg.h | 50 + code/gamespy/gstats/track/StdAfx.cpp | 8 + code/gamespy/gstats/track/StdAfx.h | 26 + code/gamespy/gstats/track/res/track.ico | Bin 0 -> 1078 bytes code/gamespy/gstats/track/res/track.rc2 | 13 + code/gamespy/gstats/track/resource.h | 34 + code/gamespy/gstats/track/track.cpp | 74 + code/gamespy/gstats/track/track.h | 49 + code/gamespy/gstats/track/track.rc | 214 + code/gamespy/gstats/track/trackDlg.cpp | 441 ++ code/gamespy/gstats/track/trackDlg.h | 90 + code/gamespy/gt2/changelog.txt | 144 + code/gamespy/gt2/gt2.h | 664 ++ code/gamespy/gt2/gt2Auth.c | 102 + code/gamespy/gt2/gt2Auth.h | 42 + code/gamespy/gt2/gt2Buffer.c | 80 + code/gamespy/gt2/gt2Buffer.h | 27 + code/gamespy/gt2/gt2Callback.c | 431 ++ code/gamespy/gt2/gt2Callback.h | 120 + code/gamespy/gt2/gt2Connection.c | 343 + code/gamespy/gt2/gt2Connection.h | 41 + code/gamespy/gt2/gt2Encode.c | 648 ++ code/gamespy/gt2/gt2Encode.h | 180 + code/gamespy/gt2/gt2Filter.c | 191 + code/gamespy/gt2/gt2Filter.h | 24 + code/gamespy/gt2/gt2Main.c | 477 ++ code/gamespy/gt2/gt2Main.h | 255 + code/gamespy/gt2/gt2Message.c | 1802 +++++ code/gamespy/gt2/gt2Message.h | 42 + code/gamespy/gt2/gt2Socket.c | 528 ++ code/gamespy/gt2/gt2Socket.h | 45 + code/gamespy/gt2/gt2Utility.c | 421 ++ code/gamespy/gt2/gt2Utility.h | 28 + code/gamespy/gt2/gt2action/TGAFile.cpp | 1574 ++++ code/gamespy/gt2/gt2action/TGAFile.h | 22 + code/gamespy/gt2/gt2action/gt2aClient.c | 715 ++ code/gamespy/gt2/gt2action/gt2aClient.h | 92 + code/gamespy/gt2/gt2action/gt2aDisplay.c | 1655 ++++ code/gamespy/gt2/gt2action/gt2aDisplay.h | 54 + code/gamespy/gt2/gt2action/gt2aInput.c | 634 ++ code/gamespy/gt2/gt2action/gt2aInput.h | 24 + code/gamespy/gt2/gt2action/gt2aLogic.c | 860 +++ code/gamespy/gt2/gt2action/gt2aLogic.h | 119 + code/gamespy/gt2/gt2action/gt2aMain.c | 276 + code/gamespy/gt2/gt2action/gt2aMain.h | 85 + code/gamespy/gt2/gt2action/gt2aMath.c | 364 + code/gamespy/gt2/gt2action/gt2aMath.h | 161 + code/gamespy/gt2/gt2action/gt2aParse.c | 79 + code/gamespy/gt2/gt2action/gt2aParse.h | 26 + code/gamespy/gt2/gt2action/gt2aServer.c | 746 ++ code/gamespy/gt2/gt2action/gt2aServer.h | 65 + code/gamespy/gt2/gt2action/gt2aSound.c | 543 ++ code/gamespy/gt2/gt2action/gt2aSound.h | 44 + .../gt2/gt2action/images/asteroid0.tga | Bin 0 -> 65580 bytes .../gt2/gt2action/images/asteroid1.tga | Bin 0 -> 65580 bytes .../gt2/gt2action/images/asteroid2.tga | Bin 0 -> 65580 bytes .../gt2/gt2action/images/explosion0.tga | Bin 0 -> 262188 bytes .../gt2/gt2action/images/explosion1.tga | Bin 0 -> 262188 bytes code/gamespy/gt2/gt2action/images/mine0.tga | Bin 0 -> 65580 bytes code/gamespy/gt2/gt2action/images/mine1.tga | Bin 0 -> 65580 bytes code/gamespy/gt2/gt2action/images/mine2.tga | Bin 0 -> 65580 bytes code/gamespy/gt2/gt2action/images/rocket0.tga | Bin 0 -> 16428 bytes code/gamespy/gt2/gt2action/images/rocket1.tga | Bin 0 -> 16428 bytes code/gamespy/gt2/gt2action/images/rocket2.tga | Bin 0 -> 16428 bytes code/gamespy/gt2/gt2action/images/rocket3.tga | Bin 0 -> 16428 bytes code/gamespy/gt2/gt2action/images/ship0.tga | Bin 0 -> 262188 bytes code/gamespy/gt2/gt2action/images/ship1.tga | Bin 0 -> 262188 bytes code/gamespy/gt2/gt2action/images/space.tga | Bin 0 -> 262188 bytes .../gamespy/gt2/gt2action/images/spinner0.tga | Bin 0 -> 65580 bytes .../gamespy/gt2/gt2action/images/spinner1.tga | Bin 0 -> 262188 bytes .../gamespy/gt2/gt2action/images/spinner2.tga | Bin 0 -> 262188 bytes code/gamespy/gt2/gt2action/messages.txt | 49 + code/gamespy/gt2/gt2action/sounds/die.wav | Bin 0 -> 49260 bytes .../gt2/gt2action/sounds/explosion.wav | Bin 0 -> 47720 bytes code/gamespy/gt2/gt2action/sounds/mine.wav | Bin 0 -> 3812 bytes code/gamespy/gt2/gt2action/sounds/pickup.wav | Bin 0 -> 8204 bytes code/gamespy/gt2/gt2action/sounds/rocket.wav | Bin 0 -> 23902 bytes code/gamespy/gt2/gt2action/todo.txt | 23 + code/gamespy/gt2/gt2hostmig/gt2hostmig.c | 674 ++ code/gamespy/gt2/gt2nat/gt2nat.c | 408 + code/gamespy/gt2/gt2proxy/gt2proxy.c | 534 ++ code/gamespy/hashtable.c | 229 + code/gamespy/hashtable.h | 231 + code/gamespy/md5.h | 81 + code/gamespy/md5c.c | 354 + code/gamespy/natneg/NATify.c | 383 + code/gamespy/natneg/NATify.h | 45 + code/gamespy/natneg/changelog.txt | 111 + code/gamespy/natneg/natneg.c | 806 ++ code/gamespy/natneg/natneg.h | 152 + code/gamespy/natneg/nninternal.h | 109 + code/gamespy/nonport.c | 18 + code/gamespy/nonport.h | 17 + code/gamespy/pinger/pinger.h | 117 + code/gamespy/pinger/pingerMain.c | 1003 +++ code/gamespy/pt/changelog.txt | 95 + code/gamespy/pt/fpupdate/fpupdate.exe | Bin 0 -> 224768 bytes code/gamespy/pt/pt.h | 156 + code/gamespy/pt/ptMain.c | 681 ++ code/gamespy/qr2/changelog.txt | 155 + code/gamespy/qr2/qr2.c | 1753 +++++ code/gamespy/qr2/qr2.h | 453 ++ code/gamespy/qr2/qr2csample/ReadMe.txt | 29 + code/gamespy/qr2/qr2csample/qr2csample.c | 536 ++ .../qr2/qr2csample/qr2nitrocw/Nitro.lcf | 493 ++ .../qr2/qr2csample/qr2nitrocw/ROM-TS.rsf | 116 + .../qr2csample/qr2ps2prodg/qr2ps2prodg.dsp | 327 + .../qr2csample/qr2ps2prodg/qr2ps2prodg.dsw | 37 + code/gamespy/qr2/qr2regkeys.c | 157 + code/gamespy/qr2/qr2regkeys.h | 84 + code/gamespy/qr2/querytest.exe | Bin 0 -> 69632 bytes code/gamespy/sake/changelog.txt | 57 + code/gamespy/sake/sake.h | 352 + code/gamespy/sake/sakeMain.c | 428 ++ code/gamespy/sake/sakeMain.h | 51 + code/gamespy/sake/sakeRequest.c | 287 + code/gamespy/sake/sakeRequest.h | 91 + code/gamespy/sake/sakeRequestInternal.h | 65 + code/gamespy/sake/sakeRequestMisc.c | 177 + code/gamespy/sake/sakeRequestModify.c | 290 + code/gamespy/sake/sakeRequestRead.c | 674 ++ code/gamespy/sc/changelog.txt | 50 + code/gamespy/sc/sc.h | 314 + .../gamespy/sc/scRaceSample/HostOrJoinDlg.cpp | 69 + code/gamespy/sc/scRaceSample/HostOrJoinDlg.h | 51 + code/gamespy/sc/scRaceSample/LoginDlg.cpp | 245 + code/gamespy/sc/scRaceSample/LoginDlg.h | 52 + code/gamespy/sc/scRaceSample/ReadMe.txt | 42 + code/gamespy/sc/scRaceSample/ScRaceSample.cpp | 68 + code/gamespy/sc/scRaceSample/ScRaceSample.h | 95 + code/gamespy/sc/scRaceSample/ScRaceSample.rc | 239 + .../sc/scRaceSample/ScRaceSampleDlg.cpp | 1486 ++++ .../gamespy/sc/scRaceSample/ScRaceSampleDlg.h | 135 + code/gamespy/sc/scRaceSample/StdAfx.cpp | 8 + code/gamespy/sc/scRaceSample/StdAfx.h | 32 + code/gamespy/sc/scRaceSample/WaitingDlg.cpp | 43 + code/gamespy/sc/scRaceSample/WaitingDlg.h | 46 + .../atlas_Competition_Race_Sample_App_v1.h | 27 + .../sc/scRaceSample/atlas_sc_race_v1.c | 130 + .../sc/scRaceSample/atlas_sc_race_v1.h | 57 + .../sc/scRaceSample/res/ScRaceSample.ico | Bin 0 -> 1078 bytes .../sc/scRaceSample/res/ScRaceSample.rc2 | 13 + code/gamespy/sc/scRaceSample/resource.h | 34 + code/gamespy/sc/sci.h | 34 + code/gamespy/sc/sciInterface.c | 132 + code/gamespy/sc/sciInterface.h | 52 + code/gamespy/sc/sciMain.c | 706 ++ code/gamespy/sc/sciReport.c | 874 +++ code/gamespy/sc/sciReport.h | 185 + code/gamespy/sc/sciSerialize.c | 171 + code/gamespy/sc/sciSerialize.h | 45 + code/gamespy/sc/sciWebServices.c | 600 ++ code/gamespy/sc/sciWebServices.h | 126 + code/gamespy/serverbrowsing/changelog.txt | 184 + code/gamespy/serverbrowsing/sb_ascii.h | 167 + code/gamespy/serverbrowsing/sb_crypt.c | 212 + code/gamespy/serverbrowsing/sb_crypt.h | 33 + code/gamespy/serverbrowsing/sb_internal.h | 450 ++ code/gamespy/serverbrowsing/sb_queryengine.c | 700 ++ code/gamespy/serverbrowsing/sb_server.c | 892 +++ .../serverbrowsing/sb_serverbrowsing.c | 681 ++ .../serverbrowsing/sb_serverbrowsing.h | 480 ++ code/gamespy/serverbrowsing/sb_serverlist.c | 1816 +++++ .../serverbrowsing/sbmfcsample/StdAfx.cpp | 8 + .../serverbrowsing/sbmfcsample/StdAfx.h | 27 + .../sbmfcsample/res/sbmfcsample.ico | Bin 0 -> 1078 bytes .../sbmfcsample/res/sbmfcsample.rc2 | 13 + .../serverbrowsing/sbmfcsample/resource.h | 31 + .../sbmfcsample/sbmfcsample.cpp | 63 + .../serverbrowsing/sbmfcsample/sbmfcsample.h | 47 + .../serverbrowsing/sbmfcsample/sbmfcsample.rc | 197 + .../sbmfcsample/sbmfcsampleDlg.cpp | 497 ++ .../sbmfcsample/sbmfcsampleDlg.h | 74 + code/gamespy/webservices/AuthService.c | 1093 +++ code/gamespy/webservices/AuthService.h | 199 + 642 files changed, 186078 insertions(+) create mode 100644 code/gamespy/Chat/changelog.txt create mode 100644 code/gamespy/Chat/chat.h create mode 100644 code/gamespy/Chat/chatASCII.h create mode 100644 code/gamespy/Chat/chatCallbacks.c create mode 100644 code/gamespy/Chat/chatCallbacks.h create mode 100644 code/gamespy/Chat/chatChannel.c create mode 100644 code/gamespy/Chat/chatChannel.h create mode 100644 code/gamespy/Chat/chatCrypt.c create mode 100644 code/gamespy/Chat/chatCrypt.h create mode 100644 code/gamespy/Chat/chatHandlers.c create mode 100644 code/gamespy/Chat/chatHandlers.h create mode 100644 code/gamespy/Chat/chatMain.c create mode 100644 code/gamespy/Chat/chatMain.h create mode 100644 code/gamespy/Chat/chatSocket.c create mode 100644 code/gamespy/Chat/chatSocket.h create mode 100644 code/gamespy/Chat/chatc/chatc.c create mode 100644 code/gamespy/Chat/chatc/chatnitrocw/Nitro.lcf create mode 100644 code/gamespy/Chat/chatc/chatnitrocw/ROM-TS.rsf create mode 100644 code/gamespy/Chat/chatc/chatps2prodg/chatps2prodg.dsp create mode 100644 code/gamespy/Chat/chatc/chatps2prodg/chatps2prodg.dsw create mode 100644 code/gamespy/Chat/chatty/ChannelListDlg.cpp create mode 100644 code/gamespy/Chat/chatty/ChannelListDlg.h create mode 100644 code/gamespy/Chat/chatty/ChannelModeDlg.cpp create mode 100644 code/gamespy/Chat/chatty/ChannelModeDlg.h create mode 100644 code/gamespy/Chat/chatty/ChildFrm.cpp create mode 100644 code/gamespy/Chat/chatty/ChildFrm.h create mode 100644 code/gamespy/Chat/chatty/ConnectDlg.cpp create mode 100644 code/gamespy/Chat/chatty/ConnectDlg.h create mode 100644 code/gamespy/Chat/chatty/EnterDlg.cpp create mode 100644 code/gamespy/Chat/chatty/EnterDlg.h create mode 100644 code/gamespy/Chat/chatty/GetUserInfoDlg.cpp create mode 100644 code/gamespy/Chat/chatty/GetUserInfoDlg.h create mode 100644 code/gamespy/Chat/chatty/KickReasonDlg.cpp create mode 100644 code/gamespy/Chat/chatty/KickReasonDlg.h create mode 100644 code/gamespy/Chat/chatty/MainFrm.cpp create mode 100644 code/gamespy/Chat/chatty/MainFrm.h create mode 100644 code/gamespy/Chat/chatty/ReadMe.txt create mode 100644 code/gamespy/Chat/chatty/SendRawDlg.cpp create mode 100644 code/gamespy/Chat/chatty/SendRawDlg.h create mode 100644 code/gamespy/Chat/chatty/SetPasswordDlg.cpp create mode 100644 code/gamespy/Chat/chatty/SetPasswordDlg.h create mode 100644 code/gamespy/Chat/chatty/SetTopicDlg.cpp create mode 100644 code/gamespy/Chat/chatty/SetTopicDlg.h create mode 100644 code/gamespy/Chat/chatty/StdAfx.cpp create mode 100644 code/gamespy/Chat/chatty/StdAfx.h create mode 100644 code/gamespy/Chat/chatty/TalkDlg.cpp create mode 100644 code/gamespy/Chat/chatty/TalkDlg.h create mode 100644 code/gamespy/Chat/chatty/chatty.cpp create mode 100644 code/gamespy/Chat/chatty/chatty.h create mode 100644 code/gamespy/Chat/chatty/chatty.rc create mode 100644 code/gamespy/Chat/chatty/chattyDoc.cpp create mode 100644 code/gamespy/Chat/chatty/chattyDoc.h create mode 100644 code/gamespy/Chat/chatty/chattyView.cpp create mode 100644 code/gamespy/Chat/chatty/chattyView.h create mode 100644 code/gamespy/Chat/chatty/res/Toolbar.bmp create mode 100644 code/gamespy/Chat/chatty/res/chatty.ico create mode 100644 code/gamespy/Chat/chatty/res/chatty.rc2 create mode 100644 code/gamespy/Chat/chatty/res/chattyDoc.ico create mode 100644 code/gamespy/Chat/chatty/resource.h create mode 100644 code/gamespy/GP/changelog.txt create mode 100644 code/gamespy/GP/gp.c create mode 100644 code/gamespy/GP/gp.h create mode 100644 code/gamespy/GP/gpi.c create mode 100644 code/gamespy/GP/gpi.h create mode 100644 code/gamespy/GP/gpiBuddy.c create mode 100644 code/gamespy/GP/gpiBuddy.h create mode 100644 code/gamespy/GP/gpiBuffer.c create mode 100644 code/gamespy/GP/gpiBuffer.h create mode 100644 code/gamespy/GP/gpiCallback.c create mode 100644 code/gamespy/GP/gpiCallback.h create mode 100644 code/gamespy/GP/gpiConnect.c create mode 100644 code/gamespy/GP/gpiConnect.h create mode 100644 code/gamespy/GP/gpiInfo.c create mode 100644 code/gamespy/GP/gpiInfo.h create mode 100644 code/gamespy/GP/gpiKeys.c create mode 100644 code/gamespy/GP/gpiKeys.h create mode 100644 code/gamespy/GP/gpiOperation.c create mode 100644 code/gamespy/GP/gpiOperation.h create mode 100644 code/gamespy/GP/gpiPS3.c create mode 100644 code/gamespy/GP/gpiPS3.h create mode 100644 code/gamespy/GP/gpiPeer.c create mode 100644 code/gamespy/GP/gpiPeer.h create mode 100644 code/gamespy/GP/gpiProfile.c create mode 100644 code/gamespy/GP/gpiProfile.h create mode 100644 code/gamespy/GP/gpiSearch.c create mode 100644 code/gamespy/GP/gpiSearch.h create mode 100644 code/gamespy/GP/gpiTransfer.c create mode 100644 code/gamespy/GP/gpiTransfer.h create mode 100644 code/gamespy/GP/gpiUnique.c create mode 100644 code/gamespy/GP/gpiUnique.h create mode 100644 code/gamespy/GP/gpiUtility.c create mode 100644 code/gamespy/GP/gpiUtility.h create mode 100644 code/gamespy/GP/gpstress/gpstress.c create mode 100644 code/gamespy/Peer/PeerLobby/ConnectPage.cpp create mode 100644 code/gamespy/Peer/PeerLobby/ConnectPage.h create mode 100644 code/gamespy/Peer/PeerLobby/CreatePage.cpp create mode 100644 code/gamespy/Peer/PeerLobby/CreatePage.h create mode 100644 code/gamespy/Peer/PeerLobby/GroupPage.cpp create mode 100644 code/gamespy/Peer/PeerLobby/GroupPage.h create mode 100644 code/gamespy/Peer/PeerLobby/LobbyWizard.cpp create mode 100644 code/gamespy/Peer/PeerLobby/LobbyWizard.h create mode 100644 code/gamespy/Peer/PeerLobby/PeerLobby.cpp create mode 100644 code/gamespy/Peer/PeerLobby/PeerLobby.h create mode 100644 code/gamespy/Peer/PeerLobby/PeerLobby.rc create mode 100644 code/gamespy/Peer/PeerLobby/ReadMe.txt create mode 100644 code/gamespy/Peer/PeerLobby/SideBarCtrl.cpp create mode 100644 code/gamespy/Peer/PeerLobby/SideBarCtrl.h create mode 100644 code/gamespy/Peer/PeerLobby/StagingPage.cpp create mode 100644 code/gamespy/Peer/PeerLobby/StagingPage.h create mode 100644 code/gamespy/Peer/PeerLobby/StdAfx.cpp create mode 100644 code/gamespy/Peer/PeerLobby/StdAfx.h create mode 100644 code/gamespy/Peer/PeerLobby/TitlePage.cpp create mode 100644 code/gamespy/Peer/PeerLobby/TitlePage.h create mode 100644 code/gamespy/Peer/PeerLobby/res/PeerLobby.ico create mode 100644 code/gamespy/Peer/PeerLobby/res/PeerLobby.rc2 create mode 100644 code/gamespy/Peer/PeerLobby/res/ico00001.ico create mode 100644 code/gamespy/Peer/PeerLobby/res/ico00002.ico create mode 100644 code/gamespy/Peer/PeerLobby/res/ico00003.ico create mode 100644 code/gamespy/Peer/PeerLobby/res/ico00004.ico create mode 100644 code/gamespy/Peer/PeerLobby/res/icon1.ico create mode 100644 code/gamespy/Peer/PeerLobby/resource.h create mode 100644 code/gamespy/Peer/changelog.txt create mode 100644 code/gamespy/Peer/peer.h create mode 100644 code/gamespy/Peer/peerAscii.h create mode 100644 code/gamespy/Peer/peerAutoMatch.c create mode 100644 code/gamespy/Peer/peerAutoMatch.h create mode 100644 code/gamespy/Peer/peerCallbacks.c create mode 100644 code/gamespy/Peer/peerCallbacks.h create mode 100644 code/gamespy/Peer/peerGlobalCallbacks.c create mode 100644 code/gamespy/Peer/peerGlobalCallbacks.h create mode 100644 code/gamespy/Peer/peerHost.c create mode 100644 code/gamespy/Peer/peerHost.h create mode 100644 code/gamespy/Peer/peerKeys.c create mode 100644 code/gamespy/Peer/peerKeys.h create mode 100644 code/gamespy/Peer/peerMain.c create mode 100644 code/gamespy/Peer/peerMain.h create mode 100644 code/gamespy/Peer/peerMangle.c create mode 100644 code/gamespy/Peer/peerMangle.h create mode 100644 code/gamespy/Peer/peerOperations.c create mode 100644 code/gamespy/Peer/peerOperations.h create mode 100644 code/gamespy/Peer/peerPing.c create mode 100644 code/gamespy/Peer/peerPing.h create mode 100644 code/gamespy/Peer/peerPlayers.c create mode 100644 code/gamespy/Peer/peerPlayers.h create mode 100644 code/gamespy/Peer/peerQR.c create mode 100644 code/gamespy/Peer/peerQR.h create mode 100644 code/gamespy/Peer/peerRooms.c create mode 100644 code/gamespy/Peer/peerRooms.h create mode 100644 code/gamespy/Peer/peerSB.c create mode 100644 code/gamespy/Peer/peerSB.h create mode 100644 code/gamespy/Peer/peerc/peerc.c create mode 100644 code/gamespy/Peer/peerc/peernitrocw/Nitro.lcf create mode 100644 code/gamespy/Peer/peerc/peernitrocw/ROM-TS.rsf create mode 100644 code/gamespy/Peer/peerc/peerps2prodg/peerps2prodg.dsp create mode 100644 code/gamespy/Peer/peerc/peerps2prodg/peerps2prodg.dsw create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/LoginDlg.cpp create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/LoginDlg.h create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/SetupDlg.cpp create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/SetupDlg.h create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/StdAfx.cpp create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/StdAfx.h create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.cpp create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.h create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.rc create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFCDlg.cpp create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFCDlg.h create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/VoiceSessionDlg.cpp create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/VoiceSessionDlg.h create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/res/Voice2BuddyMFC.ico create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/res/Voice2BuddyMFC.rc2 create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/res/gamespyl.bmp create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/res/logo270x83.bmp create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/res/microsoft-microphone.bmp create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/res/microsoft-speaker.bmp create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/res/speaking.bmp create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/res/speaking2.bmp create mode 100644 code/gamespy/Voice2/Voice2BuddyMFC/resource.h create mode 100644 code/gamespy/Voice2/changelog.txt create mode 100644 code/gamespy/Voice2/gv.h create mode 100644 code/gamespy/Voice2/gvCodec.c create mode 100644 code/gamespy/Voice2/gvCodec.h create mode 100644 code/gamespy/Voice2/gvCustomDevice.c create mode 100644 code/gamespy/Voice2/gvCustomDevice.h create mode 100644 code/gamespy/Voice2/gvDevice.c create mode 100644 code/gamespy/Voice2/gvDevice.h create mode 100644 code/gamespy/Voice2/gvDirectSound.c create mode 100644 code/gamespy/Voice2/gvDirectSound.h create mode 100644 code/gamespy/Voice2/gvFrame.c create mode 100644 code/gamespy/Voice2/gvFrame.h create mode 100644 code/gamespy/Voice2/gvGSM.c create mode 100644 code/gamespy/Voice2/gvGSM.h create mode 100644 code/gamespy/Voice2/gvLogitechPS2Codecs.c create mode 100644 code/gamespy/Voice2/gvLogitechPS2Codecs.h create mode 100644 code/gamespy/Voice2/gvMain.c create mode 100644 code/gamespy/Voice2/gvMain.h create mode 100644 code/gamespy/Voice2/gvOSXAudio.c create mode 100644 code/gamespy/Voice2/gvOSXAudio.h create mode 100644 code/gamespy/Voice2/gvPS2Audio.c create mode 100644 code/gamespy/Voice2/gvPS2Audio.h create mode 100644 code/gamespy/Voice2/gvPS2Eyetoy.c create mode 100644 code/gamespy/Voice2/gvPS2Eyetoy.h create mode 100644 code/gamespy/Voice2/gvPS2Headset.c create mode 100644 code/gamespy/Voice2/gvPS2Headset.h create mode 100644 code/gamespy/Voice2/gvPS2Spu2.c create mode 100644 code/gamespy/Voice2/gvPS2Spu2.h create mode 100644 code/gamespy/Voice2/gvPS3Audio.c create mode 100644 code/gamespy/Voice2/gvPS3Audio.h create mode 100644 code/gamespy/Voice2/gvPS3Headset.c create mode 100644 code/gamespy/Voice2/gvPS3Headset.h create mode 100644 code/gamespy/Voice2/gvPSPAudio.c create mode 100644 code/gamespy/Voice2/gvPSPAudio.h create mode 100644 code/gamespy/Voice2/gvSource.c create mode 100644 code/gamespy/Voice2/gvSource.h create mode 100644 code/gamespy/Voice2/gvSpeex.c create mode 100644 code/gamespy/Voice2/gvSpeex.h create mode 100644 code/gamespy/Voice2/gvSpeexSpu.c create mode 100644 code/gamespy/Voice2/gvSpeexSpu.h create mode 100644 code/gamespy/Voice2/gvUtil.c create mode 100644 code/gamespy/Voice2/gvUtil.h create mode 100644 code/gamespy/Voice2/voice2bench/voice2bench.c create mode 100644 code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_eenet_debug.h create mode 100644 code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_eenet_release.h create mode 100644 code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_insock_debug.h create mode 100644 code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_insock_release.h create mode 100644 code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_snsystems_debug.h create mode 100644 code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_snsystems_release.h create mode 100644 code/gamespy/Voice2/voice2bench/voice2benchps2prodg/voice2benchps2prodg.dsp create mode 100644 code/gamespy/Voice2/voice2bench/voice2benchps2prodg/voice2benchps2prodg.dsw create mode 100644 code/gamespy/Voice2/voice2bench/voicesample.h create mode 100644 code/gamespy/changelog.txt create mode 100644 code/gamespy/common/changelog.txt create mode 100644 code/gamespy/common/gsAssert.c create mode 100644 code/gamespy/common/gsAssert.h create mode 100644 code/gamespy/common/gsAvailable.c create mode 100644 code/gamespy/common/gsAvailable.h create mode 100644 code/gamespy/common/gsCommon.h create mode 100644 code/gamespy/common/gsCore.c create mode 100644 code/gamespy/common/gsCore.h create mode 100644 code/gamespy/common/gsCrypt.c create mode 100644 code/gamespy/common/gsCrypt.h create mode 100644 code/gamespy/common/gsDebug.c create mode 100644 code/gamespy/common/gsDebug.h create mode 100644 code/gamespy/common/gsLargeInt.c create mode 100644 code/gamespy/common/gsLargeInt.h create mode 100644 code/gamespy/common/gsMemory.c create mode 100644 code/gamespy/common/gsMemory.h create mode 100644 code/gamespy/common/gsPlatform.c create mode 100644 code/gamespy/common/gsPlatform.h create mode 100644 code/gamespy/common/gsPlatformSocket.c create mode 100644 code/gamespy/common/gsPlatformSocket.h create mode 100644 code/gamespy/common/gsPlatformThread.c create mode 100644 code/gamespy/common/gsPlatformThread.h create mode 100644 code/gamespy/common/gsPlatformUtil.c create mode 100644 code/gamespy/common/gsPlatformUtil.h create mode 100644 code/gamespy/common/gsRC4.c create mode 100644 code/gamespy/common/gsRC4.h create mode 100644 code/gamespy/common/gsSHA1.c create mode 100644 code/gamespy/common/gsSHA1.h create mode 100644 code/gamespy/common/gsSSL.c create mode 100644 code/gamespy/common/gsSSL.h create mode 100644 code/gamespy/common/gsSoap.c create mode 100644 code/gamespy/common/gsSoap.h create mode 100644 code/gamespy/common/gsStringUtil.c create mode 100644 code/gamespy/common/gsStringUtil.h create mode 100644 code/gamespy/common/gsUdpEngine.c create mode 100644 code/gamespy/common/gsUdpEngine.h create mode 100644 code/gamespy/common/gsXML.c create mode 100644 code/gamespy/common/gsXML.h create mode 100644 code/gamespy/common/linux/LinuxCommon.c create mode 100644 code/gamespy/common/linux/changelog.txt create mode 100644 code/gamespy/common/linux/gsSocketLinux.c create mode 100644 code/gamespy/common/linux/gsThreadLinux.c create mode 100644 code/gamespy/common/linux/gsUtilLinux.c create mode 100644 code/gamespy/common/macosx/MacOSXCommon.c create mode 100644 code/gamespy/common/macosx/changelog.txt create mode 100644 code/gamespy/common/macosx/gsThreadMacOSX.c create mode 100644 code/gamespy/common/macosx/gsUtilMacOSX.c create mode 100644 code/gamespy/common/nitro/backup.c create mode 100644 code/gamespy/common/nitro/backup.h create mode 100644 code/gamespy/common/nitro/changelog.txt create mode 100644 code/gamespy/common/nitro/font.c create mode 100644 code/gamespy/common/nitro/font.h create mode 100644 code/gamespy/common/nitro/gsSocketNitro.c create mode 100644 code/gamespy/common/nitro/gsThreadNitro.c create mode 100644 code/gamespy/common/nitro/gsTimerNitro.c create mode 100644 code/gamespy/common/nitro/gsUtilNitro.c create mode 100644 code/gamespy/common/nitro/key.c create mode 100644 code/gamespy/common/nitro/key.h create mode 100644 code/gamespy/common/nitro/main.c create mode 100644 code/gamespy/common/nitro/menu.c create mode 100644 code/gamespy/common/nitro/menu.h create mode 100644 code/gamespy/common/nitro/nitrocommon.cww create mode 100644 code/gamespy/common/nitro/nitrosample/Nitro.lcf create mode 100644 code/gamespy/common/nitro/nitrosample/ROM-TS.rsf create mode 100644 code/gamespy/common/nitro/nitrosample/nitrosample.c create mode 100644 code/gamespy/common/nitro/screen.c create mode 100644 code/gamespy/common/nitro/screen.h create mode 100644 code/gamespy/common/nitro/touch.c create mode 100644 code/gamespy/common/nitro/touch.h create mode 100644 code/gamespy/common/nitro/wireless.c create mode 100644 code/gamespy/common/nitro/wireless.h create mode 100644 code/gamespy/common/ps2/changelog.txt create mode 100644 code/gamespy/common/ps2/cw/LinkSegment_PS2.lcf create mode 100644 code/gamespy/common/ps2/cw/PREFIX_PS2_DEBUG_TC296.h create mode 100644 code/gamespy/common/ps2/cw/PREFIX_PS2_RELEASE_TC296.h create mode 100644 code/gamespy/common/ps2/cw/cw.cww create mode 100644 code/gamespy/common/ps2/cw/cwprefix_eenet_debug.h create mode 100644 code/gamespy/common/ps2/cw/cwprefix_eenet_debug_uniqueid.h create mode 100644 code/gamespy/common/ps2/cw/cwprefix_eenet_release.h create mode 100644 code/gamespy/common/ps2/cw/cwprefix_eenet_release_uniqueid.h create mode 100644 code/gamespy/common/ps2/cw/cwprefix_insock_debug.h create mode 100644 code/gamespy/common/ps2/cw/cwprefix_insock_debug_uniqueid.h create mode 100644 code/gamespy/common/ps2/cw/cwprefix_insock_release.h create mode 100644 code/gamespy/common/ps2/cw/cwprefix_insock_release_uniqueid.h create mode 100644 code/gamespy/common/ps2/cw/cwprefix_snsystems_debug.h create mode 100644 code/gamespy/common/ps2/cw/cwprefix_snsystems_debug_uniqueid.h create mode 100644 code/gamespy/common/ps2/cw/cwprefix_snsystems_release.h create mode 100644 code/gamespy/common/ps2/cw/cwprefix_snsystems_release_uniqueid.h create mode 100644 code/gamespy/common/ps2/ent_cnf/cw/LinkSegment_PS2IOP.lcf create mode 100644 code/gamespy/common/ps2/ent_cnf/cw/PREFIX_PS2IOP_DEBUG.h create mode 100644 code/gamespy/common/ps2/ent_cnf/cw/PREFIX_PS2IOP_RELEASE.h create mode 100644 code/gamespy/common/ps2/ent_cnf/prodg/ent_cnf.dsp create mode 100644 code/gamespy/common/ps2/ent_cnf/prodg/ent_cnf.dsw create mode 100644 code/gamespy/common/ps2/gsSocketPs2.c create mode 100644 code/gamespy/common/ps2/gsThreadPs2.c create mode 100644 code/gamespy/common/ps2/gsUtilPs2.c create mode 100644 code/gamespy/common/ps2/prodg/PS2_in_VC.h create mode 100644 code/gamespy/common/ps2/prodg/prodg.dsw create mode 100644 code/gamespy/common/ps2/prodg/ps2.lk create mode 100644 code/gamespy/common/ps2/ps2common.c create mode 100644 code/gamespy/common/ps2/ps2pad.c create mode 100644 code/gamespy/common/ps2/ps2pad.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/CellConfiguration.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/CellVectorMath.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/PS3Types.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/SPUAssert.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/SpeexSpursTask/SpuSpeexTaskMain.cpp create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/SpuDoubleBuffer.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/SpuFakeDma.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/SpuSpeexTaskOutput.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/SpursSpeexCInterface.cpp create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/SpursSpeexCInterface.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/SpursSpeexTaskManager.cpp create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/SpursSpeexTaskManager.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/spursAlignedAllocator.cpp create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/spursAlignedAllocator.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/spursAlignedObjectArray.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/spursConfiguration.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/spursPlatformDefinitions.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/spursScalar.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/spursSupportInterface.cpp create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/spursSupportInterface.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/spursThreadSupportInterface.cpp create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/spursUtilityMacros.h create mode 100644 code/gamespy/common/ps3/SpeexSpursTaskManager/spursthreadsupportinterface.h create mode 100644 code/gamespy/common/ps3/gsSocketPS3.c create mode 100644 code/gamespy/common/ps3/gsUtilPS3.c create mode 100644 code/gamespy/common/ps3/ps3common.c create mode 100644 code/gamespy/common/psp/gsSocketPSP.c create mode 100644 code/gamespy/common/psp/gsUtilPSP.c create mode 100644 code/gamespy/common/psp/pspcommon.c create mode 100644 code/gamespy/common/revolution/gsSocketRevolution.c create mode 100644 code/gamespy/common/revolution/gsThreadRevoulution.c create mode 100644 code/gamespy/common/revolution/gsUtilRevolution.c create mode 100644 code/gamespy/common/revolution/revolutionCommon.c create mode 100644 code/gamespy/common/win32/Win32Common.c create mode 100644 code/gamespy/common/win32/changelog.txt create mode 100644 code/gamespy/common/win32/gsSocketWin32.c create mode 100644 code/gamespy/common/win32/gsThreadWin32.c create mode 100644 code/gamespy/common/win32/gsUtilWin32.c create mode 100644 code/gamespy/common/x360/X360Common.c create mode 100644 code/gamespy/common/x360/gsSocketX360.c create mode 100644 code/gamespy/common/x360/gsThreadX360.c create mode 100644 code/gamespy/common/xbox/gsSocketXbox.c create mode 100644 code/gamespy/darray.c create mode 100644 code/gamespy/darray.h create mode 100644 code/gamespy/gcd.cmake create mode 100644 code/gamespy/gcdkey/CdkeyGen/gcdkeygen.c create mode 100644 code/gamespy/gcdkey/changelog.txt create mode 100644 code/gamespy/gcdkey/gcdkeyc.c create mode 100644 code/gamespy/gcdkey/gcdkeyc.h create mode 100644 code/gamespy/gcdkey/gcdkeys.c create mode 100644 code/gamespy/gcdkey/gcdkeys.h create mode 100644 code/gamespy/ghttp/changelog.txt create mode 100644 code/gamespy/ghttp/ghttp.h create mode 100644 code/gamespy/ghttp/ghttpASCII.h create mode 100644 code/gamespy/ghttp/ghttpBuffer.c create mode 100644 code/gamespy/ghttp/ghttpBuffer.h create mode 100644 code/gamespy/ghttp/ghttpCallbacks.c create mode 100644 code/gamespy/ghttp/ghttpCallbacks.h create mode 100644 code/gamespy/ghttp/ghttpCommon.c create mode 100644 code/gamespy/ghttp/ghttpCommon.h create mode 100644 code/gamespy/ghttp/ghttpConnection.c create mode 100644 code/gamespy/ghttp/ghttpConnection.h create mode 100644 code/gamespy/ghttp/ghttpEncryption.c create mode 100644 code/gamespy/ghttp/ghttpEncryption.h create mode 100644 code/gamespy/ghttp/ghttpMain.c create mode 100644 code/gamespy/ghttp/ghttpMain.h create mode 100644 code/gamespy/ghttp/ghttpPost.c create mode 100644 code/gamespy/ghttp/ghttpPost.h create mode 100644 code/gamespy/ghttp/ghttpProcess.c create mode 100644 code/gamespy/ghttp/ghttpProcess.h create mode 100644 code/gamespy/ghttp/ghttpc/ghttpc.c create mode 100644 code/gamespy/ghttp/ghttpc/ghttpnitrocw/Nitro.lcf create mode 100644 code/gamespy/ghttp/ghttpc/ghttpnitrocw/ROM-TS.rsf create mode 100644 code/gamespy/ghttp/ghttpc/ghttpps2prodg/ghttpps2prodg.dsp create mode 100644 code/gamespy/ghttp/ghttpc/ghttpps2prodg/ghttpps2prodg.dsw create mode 100644 code/gamespy/ghttp/ghttpmfc/ReadMe.txt create mode 100644 code/gamespy/ghttp/ghttpmfc/StdAfx.cpp create mode 100644 code/gamespy/ghttp/ghttpmfc/StdAfx.h create mode 100644 code/gamespy/ghttp/ghttpmfc/ghttpmfc.cpp create mode 100644 code/gamespy/ghttp/ghttpmfc/ghttpmfc.h create mode 100644 code/gamespy/ghttp/ghttpmfc/ghttpmfc.rc create mode 100644 code/gamespy/ghttp/ghttpmfc/ghttpmfcDlg.cpp create mode 100644 code/gamespy/ghttp/ghttpmfc/ghttpmfcDlg.h create mode 100644 code/gamespy/ghttp/ghttpmfc/res/ghttpmfc.ico create mode 100644 code/gamespy/ghttp/ghttpmfc/res/ghttpmfc.rc2 create mode 100644 code/gamespy/ghttp/ghttpmfc/resource.h create mode 100644 code/gamespy/gstats/changelog.txt create mode 100644 code/gamespy/gstats/gbucket.c create mode 100644 code/gamespy/gstats/gbucket.h create mode 100644 code/gamespy/gstats/gp_stats/StdAfx.cpp create mode 100644 code/gamespy/gstats/gp_stats/StdAfx.h create mode 100644 code/gamespy/gstats/gp_stats/gp_stats.cpp create mode 100644 code/gamespy/gstats/gp_stats/gp_stats.h create mode 100644 code/gamespy/gstats/gp_stats/gp_stats.rc create mode 100644 code/gamespy/gstats/gp_stats/gp_statsDlg.cpp create mode 100644 code/gamespy/gstats/gp_stats/gp_statsDlg.h create mode 100644 code/gamespy/gstats/gp_stats/res/gp_stats.ico create mode 100644 code/gamespy/gstats/gp_stats/res/gp_stats.rc2 create mode 100644 code/gamespy/gstats/gp_stats/resource.h create mode 100644 code/gamespy/gstats/gpersist.h create mode 100644 code/gamespy/gstats/gstats.c create mode 100644 code/gamespy/gstats/gstats.h create mode 100644 code/gamespy/gstats/ladderTrack/HostOrJoinDlg.cpp create mode 100644 code/gamespy/gstats/ladderTrack/HostOrJoinDlg.h create mode 100644 code/gamespy/gstats/ladderTrack/LoginDlg.cpp create mode 100644 code/gamespy/gstats/ladderTrack/LoginDlg.h create mode 100644 code/gamespy/gstats/ladderTrack/StdAfx.cpp create mode 100644 code/gamespy/gstats/ladderTrack/StdAfx.h create mode 100644 code/gamespy/gstats/ladderTrack/WaitingDlg.cpp create mode 100644 code/gamespy/gstats/ladderTrack/WaitingDlg.h create mode 100644 code/gamespy/gstats/ladderTrack/ladderTrack.cpp create mode 100644 code/gamespy/gstats/ladderTrack/ladderTrack.h create mode 100644 code/gamespy/gstats/ladderTrack/ladderTrack.rc create mode 100644 code/gamespy/gstats/ladderTrack/ladderTrackDlg.cpp create mode 100644 code/gamespy/gstats/ladderTrack/ladderTrackDlg.h create mode 100644 code/gamespy/gstats/ladderTrack/res/ladderTrack.ico create mode 100644 code/gamespy/gstats/ladderTrack/res/ladderTrack.rc2 create mode 100644 code/gamespy/gstats/ladderTrack/resource.h create mode 100644 code/gamespy/gstats/multiTrack/HostOrJoinDlg.cpp create mode 100644 code/gamespy/gstats/multiTrack/HostOrJoinDlg.h create mode 100644 code/gamespy/gstats/multiTrack/LoginDlg.cpp create mode 100644 code/gamespy/gstats/multiTrack/LoginDlg.h create mode 100644 code/gamespy/gstats/multiTrack/StdAfx.cpp create mode 100644 code/gamespy/gstats/multiTrack/StdAfx.h create mode 100644 code/gamespy/gstats/multiTrack/WaitingDlg.cpp create mode 100644 code/gamespy/gstats/multiTrack/WaitingDlg.h create mode 100644 code/gamespy/gstats/multiTrack/multiTrack.cpp create mode 100644 code/gamespy/gstats/multiTrack/multiTrack.h create mode 100644 code/gamespy/gstats/multiTrack/multiTrack.rc create mode 100644 code/gamespy/gstats/multiTrack/multiTrackDlg.cpp create mode 100644 code/gamespy/gstats/multiTrack/multiTrackDlg.h create mode 100644 code/gamespy/gstats/multiTrack/res/multiTrack.ico create mode 100644 code/gamespy/gstats/multiTrack/res/multiTrack.rc2 create mode 100644 code/gamespy/gstats/multiTrack/resource.h create mode 100644 code/gamespy/gstats/track/LoginDlg.cpp create mode 100644 code/gamespy/gstats/track/LoginDlg.h create mode 100644 code/gamespy/gstats/track/StdAfx.cpp create mode 100644 code/gamespy/gstats/track/StdAfx.h create mode 100644 code/gamespy/gstats/track/res/track.ico create mode 100644 code/gamespy/gstats/track/res/track.rc2 create mode 100644 code/gamespy/gstats/track/resource.h create mode 100644 code/gamespy/gstats/track/track.cpp create mode 100644 code/gamespy/gstats/track/track.h create mode 100644 code/gamespy/gstats/track/track.rc create mode 100644 code/gamespy/gstats/track/trackDlg.cpp create mode 100644 code/gamespy/gstats/track/trackDlg.h create mode 100644 code/gamespy/gt2/changelog.txt create mode 100644 code/gamespy/gt2/gt2.h create mode 100644 code/gamespy/gt2/gt2Auth.c create mode 100644 code/gamespy/gt2/gt2Auth.h create mode 100644 code/gamespy/gt2/gt2Buffer.c create mode 100644 code/gamespy/gt2/gt2Buffer.h create mode 100644 code/gamespy/gt2/gt2Callback.c create mode 100644 code/gamespy/gt2/gt2Callback.h create mode 100644 code/gamespy/gt2/gt2Connection.c create mode 100644 code/gamespy/gt2/gt2Connection.h create mode 100644 code/gamespy/gt2/gt2Encode.c create mode 100644 code/gamespy/gt2/gt2Encode.h create mode 100644 code/gamespy/gt2/gt2Filter.c create mode 100644 code/gamespy/gt2/gt2Filter.h create mode 100644 code/gamespy/gt2/gt2Main.c create mode 100644 code/gamespy/gt2/gt2Main.h create mode 100644 code/gamespy/gt2/gt2Message.c create mode 100644 code/gamespy/gt2/gt2Message.h create mode 100644 code/gamespy/gt2/gt2Socket.c create mode 100644 code/gamespy/gt2/gt2Socket.h create mode 100644 code/gamespy/gt2/gt2Utility.c create mode 100644 code/gamespy/gt2/gt2Utility.h create mode 100644 code/gamespy/gt2/gt2action/TGAFile.cpp create mode 100644 code/gamespy/gt2/gt2action/TGAFile.h create mode 100644 code/gamespy/gt2/gt2action/gt2aClient.c create mode 100644 code/gamespy/gt2/gt2action/gt2aClient.h create mode 100644 code/gamespy/gt2/gt2action/gt2aDisplay.c create mode 100644 code/gamespy/gt2/gt2action/gt2aDisplay.h create mode 100644 code/gamespy/gt2/gt2action/gt2aInput.c create mode 100644 code/gamespy/gt2/gt2action/gt2aInput.h create mode 100644 code/gamespy/gt2/gt2action/gt2aLogic.c create mode 100644 code/gamespy/gt2/gt2action/gt2aLogic.h create mode 100644 code/gamespy/gt2/gt2action/gt2aMain.c create mode 100644 code/gamespy/gt2/gt2action/gt2aMain.h create mode 100644 code/gamespy/gt2/gt2action/gt2aMath.c create mode 100644 code/gamespy/gt2/gt2action/gt2aMath.h create mode 100644 code/gamespy/gt2/gt2action/gt2aParse.c create mode 100644 code/gamespy/gt2/gt2action/gt2aParse.h create mode 100644 code/gamespy/gt2/gt2action/gt2aServer.c create mode 100644 code/gamespy/gt2/gt2action/gt2aServer.h create mode 100644 code/gamespy/gt2/gt2action/gt2aSound.c create mode 100644 code/gamespy/gt2/gt2action/gt2aSound.h create mode 100644 code/gamespy/gt2/gt2action/images/asteroid0.tga create mode 100644 code/gamespy/gt2/gt2action/images/asteroid1.tga create mode 100644 code/gamespy/gt2/gt2action/images/asteroid2.tga create mode 100644 code/gamespy/gt2/gt2action/images/explosion0.tga create mode 100644 code/gamespy/gt2/gt2action/images/explosion1.tga create mode 100644 code/gamespy/gt2/gt2action/images/mine0.tga create mode 100644 code/gamespy/gt2/gt2action/images/mine1.tga create mode 100644 code/gamespy/gt2/gt2action/images/mine2.tga create mode 100644 code/gamespy/gt2/gt2action/images/rocket0.tga create mode 100644 code/gamespy/gt2/gt2action/images/rocket1.tga create mode 100644 code/gamespy/gt2/gt2action/images/rocket2.tga create mode 100644 code/gamespy/gt2/gt2action/images/rocket3.tga create mode 100644 code/gamespy/gt2/gt2action/images/ship0.tga create mode 100644 code/gamespy/gt2/gt2action/images/ship1.tga create mode 100644 code/gamespy/gt2/gt2action/images/space.tga create mode 100644 code/gamespy/gt2/gt2action/images/spinner0.tga create mode 100644 code/gamespy/gt2/gt2action/images/spinner1.tga create mode 100644 code/gamespy/gt2/gt2action/images/spinner2.tga create mode 100644 code/gamespy/gt2/gt2action/messages.txt create mode 100644 code/gamespy/gt2/gt2action/sounds/die.wav create mode 100644 code/gamespy/gt2/gt2action/sounds/explosion.wav create mode 100644 code/gamespy/gt2/gt2action/sounds/mine.wav create mode 100644 code/gamespy/gt2/gt2action/sounds/pickup.wav create mode 100644 code/gamespy/gt2/gt2action/sounds/rocket.wav create mode 100644 code/gamespy/gt2/gt2action/todo.txt create mode 100644 code/gamespy/gt2/gt2hostmig/gt2hostmig.c create mode 100644 code/gamespy/gt2/gt2nat/gt2nat.c create mode 100644 code/gamespy/gt2/gt2proxy/gt2proxy.c create mode 100644 code/gamespy/hashtable.c create mode 100644 code/gamespy/hashtable.h create mode 100644 code/gamespy/md5.h create mode 100644 code/gamespy/md5c.c create mode 100644 code/gamespy/natneg/NATify.c create mode 100644 code/gamespy/natneg/NATify.h create mode 100644 code/gamespy/natneg/changelog.txt create mode 100644 code/gamespy/natneg/natneg.c create mode 100644 code/gamespy/natneg/natneg.h create mode 100644 code/gamespy/natneg/nninternal.h create mode 100644 code/gamespy/nonport.c create mode 100644 code/gamespy/nonport.h create mode 100644 code/gamespy/pinger/pinger.h create mode 100644 code/gamespy/pinger/pingerMain.c create mode 100644 code/gamespy/pt/changelog.txt create mode 100644 code/gamespy/pt/fpupdate/fpupdate.exe create mode 100644 code/gamespy/pt/pt.h create mode 100644 code/gamespy/pt/ptMain.c create mode 100644 code/gamespy/qr2/changelog.txt create mode 100644 code/gamespy/qr2/qr2.c create mode 100644 code/gamespy/qr2/qr2.h create mode 100644 code/gamespy/qr2/qr2csample/ReadMe.txt create mode 100644 code/gamespy/qr2/qr2csample/qr2csample.c create mode 100644 code/gamespy/qr2/qr2csample/qr2nitrocw/Nitro.lcf create mode 100644 code/gamespy/qr2/qr2csample/qr2nitrocw/ROM-TS.rsf create mode 100644 code/gamespy/qr2/qr2csample/qr2ps2prodg/qr2ps2prodg.dsp create mode 100644 code/gamespy/qr2/qr2csample/qr2ps2prodg/qr2ps2prodg.dsw create mode 100644 code/gamespy/qr2/qr2regkeys.c create mode 100644 code/gamespy/qr2/qr2regkeys.h create mode 100644 code/gamespy/qr2/querytest.exe create mode 100644 code/gamespy/sake/changelog.txt create mode 100644 code/gamespy/sake/sake.h create mode 100644 code/gamespy/sake/sakeMain.c create mode 100644 code/gamespy/sake/sakeMain.h create mode 100644 code/gamespy/sake/sakeRequest.c create mode 100644 code/gamespy/sake/sakeRequest.h create mode 100644 code/gamespy/sake/sakeRequestInternal.h create mode 100644 code/gamespy/sake/sakeRequestMisc.c create mode 100644 code/gamespy/sake/sakeRequestModify.c create mode 100644 code/gamespy/sake/sakeRequestRead.c create mode 100644 code/gamespy/sc/changelog.txt create mode 100644 code/gamespy/sc/sc.h create mode 100644 code/gamespy/sc/scRaceSample/HostOrJoinDlg.cpp create mode 100644 code/gamespy/sc/scRaceSample/HostOrJoinDlg.h create mode 100644 code/gamespy/sc/scRaceSample/LoginDlg.cpp create mode 100644 code/gamespy/sc/scRaceSample/LoginDlg.h create mode 100644 code/gamespy/sc/scRaceSample/ReadMe.txt create mode 100644 code/gamespy/sc/scRaceSample/ScRaceSample.cpp create mode 100644 code/gamespy/sc/scRaceSample/ScRaceSample.h create mode 100644 code/gamespy/sc/scRaceSample/ScRaceSample.rc create mode 100644 code/gamespy/sc/scRaceSample/ScRaceSampleDlg.cpp create mode 100644 code/gamespy/sc/scRaceSample/ScRaceSampleDlg.h create mode 100644 code/gamespy/sc/scRaceSample/StdAfx.cpp create mode 100644 code/gamespy/sc/scRaceSample/StdAfx.h create mode 100644 code/gamespy/sc/scRaceSample/WaitingDlg.cpp create mode 100644 code/gamespy/sc/scRaceSample/WaitingDlg.h create mode 100644 code/gamespy/sc/scRaceSample/atlas_Competition_Race_Sample_App_v1.h create mode 100644 code/gamespy/sc/scRaceSample/atlas_sc_race_v1.c create mode 100644 code/gamespy/sc/scRaceSample/atlas_sc_race_v1.h create mode 100644 code/gamespy/sc/scRaceSample/res/ScRaceSample.ico create mode 100644 code/gamespy/sc/scRaceSample/res/ScRaceSample.rc2 create mode 100644 code/gamespy/sc/scRaceSample/resource.h create mode 100644 code/gamespy/sc/sci.h create mode 100644 code/gamespy/sc/sciInterface.c create mode 100644 code/gamespy/sc/sciInterface.h create mode 100644 code/gamespy/sc/sciMain.c create mode 100644 code/gamespy/sc/sciReport.c create mode 100644 code/gamespy/sc/sciReport.h create mode 100644 code/gamespy/sc/sciSerialize.c create mode 100644 code/gamespy/sc/sciSerialize.h create mode 100644 code/gamespy/sc/sciWebServices.c create mode 100644 code/gamespy/sc/sciWebServices.h create mode 100644 code/gamespy/serverbrowsing/changelog.txt create mode 100644 code/gamespy/serverbrowsing/sb_ascii.h create mode 100644 code/gamespy/serverbrowsing/sb_crypt.c create mode 100644 code/gamespy/serverbrowsing/sb_crypt.h create mode 100644 code/gamespy/serverbrowsing/sb_internal.h create mode 100644 code/gamespy/serverbrowsing/sb_queryengine.c create mode 100644 code/gamespy/serverbrowsing/sb_server.c create mode 100644 code/gamespy/serverbrowsing/sb_serverbrowsing.c create mode 100644 code/gamespy/serverbrowsing/sb_serverbrowsing.h create mode 100644 code/gamespy/serverbrowsing/sb_serverlist.c create mode 100644 code/gamespy/serverbrowsing/sbmfcsample/StdAfx.cpp create mode 100644 code/gamespy/serverbrowsing/sbmfcsample/StdAfx.h create mode 100644 code/gamespy/serverbrowsing/sbmfcsample/res/sbmfcsample.ico create mode 100644 code/gamespy/serverbrowsing/sbmfcsample/res/sbmfcsample.rc2 create mode 100644 code/gamespy/serverbrowsing/sbmfcsample/resource.h create mode 100644 code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.cpp create mode 100644 code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.h create mode 100644 code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.rc create mode 100644 code/gamespy/serverbrowsing/sbmfcsample/sbmfcsampleDlg.cpp create mode 100644 code/gamespy/serverbrowsing/sbmfcsample/sbmfcsampleDlg.h create mode 100644 code/gamespy/webservices/AuthService.c create mode 100644 code/gamespy/webservices/AuthService.h diff --git a/code/gamespy/Chat/changelog.txt b/code/gamespy/Chat/changelog.txt new file mode 100644 index 00000000..ef872b85 --- /dev/null +++ b/code/gamespy/Chat/changelog.txt @@ -0,0 +1,131 @@ +Changelog for: GameSpy Chat SDK +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 1.15.00 RMV RELEASE Released to Developer Site +08-06-2007 1.14.00 RMV RELEASE Released to Developer Site +07-10-2007 1.13.01 RMV FIX Fixed chatc Project files to get rid of Unicode warnings and fixed other compiler warnings +06-28-2007 1.13.00 DDW FEATURE Added UDP relay support +12-15-2006 1.12.00 MJW RELEASE Released to Developer Site +12-11-2006 1.11.40 SN OTHER Added visual studio 2005 projects for samples + SN OTHER Fixed warnings with visual studio 2005 projects +10-05-2006 1.11.39 SAH FIX Updated MacOSX Makefile +09-28-2006 1.11.38 SAH FIX Fixed PS3 project to work with PS3 095.00x SDK; changed included libaries in linker input. +09-05-2006 1.11.37 SN FIX Updated Revolution support + Fixed bug in the case where null strings were being ignored. +08-02-2006 1.11.36 SAH RELEASE Releasing to developer site +07-31-2006 1.11.36 SAH FIX Fixed PS3 project file - added post-build step to create *.SELF for execution +07-24-2006 1.11.35 SAH FIX Fixed NITRO project, include for crt0.o now above others so it add correct file +07-06-2006 1.11.34 SAH FIX Fixed PSP project file - the linker file was explicitly included in a project +06-30-2006 1.11.33 SAH FIX Fixed NITRO project & linker command file (for Codewarrior 2.0/NitroSDK 3.1) + SAH FIX Fixed Linux makefile +06-02-2006 1.11.32 SAH FIX Added GS_STATIC_CALLBACK on comparator functions for __fastcall support +05-31-2006 1.11.31 SAH RELEASE Releasing to developer site + SAH FIX Fixed Linux makefiles +05-25-2006 1.11.31 SAH FIX Added some GSI_UNUSED calls to get rid of PSP warnings + SAH FIX Changed PS3 project settings to compile with 084_001 SDK +05-19-2006 1.11.30 SAH FIX Added gsTestMain.c to nitro CodeWarrior project +04-25-2006 1.11.29 SAH RELEASE Releasing to developer site +04-24-2006 1.11.29 SAH FIX Fixed the nitro project to work on test machine, removed unncessary source files +04-20-2006 1.11.28 SAH FIX commented out unused variables to get rid of warnings +01-27-2006 1.11.27 SN RELEASE Releasing to developer site +01-26-2006 1.11.27 SN OTHER Added psp prodg solution and project to sgv +01-09-2006 1.11.27 SN FIX Updated code to use default server address and port +12-13-2005 1.11.26 SN OTHER Updated Visual Studio .NET Projects to use new common dir + Created Visual Studio .NET Solution + Removed old Static Library chat.vcproj +12-02-2005 1.11.26 SN FIX Added a check before freeing variable +11-17-2005 1.11.25 DES FIX Removed unneeded assert. + DES FIX Compatibility fix. + DES FIX Updated Nitro Makefile. +11-14-2005 1.11.24 DES FIX Updated the OSX Makefile. + DES FEATURE Adding GSI_DOMAIN_NAME support. +09-21-2005 1.11.23 DES FEATURE Updated DS support +07-28-2005 1.11.22 SN RELEASE Releasing to developer site. +06-03-2005 1.11.22 SN RELEASE Releasing to developer site. +04-29-2005 1.11.22 SN OTHER Created Visual Studio .NET projects for existing projects. +04-28-2005 1.11.22 SN RELEASE Releasing to developer site. +04-27-2005 1.11.22 DES RELEASE Limited release to Nintendo DS developers. +04-04-2005 1.11.22 SN RELEASE Releasing to developer site. +03-31-2005 1.11.22 SN FIX Added some preprocessor code to stop compiler warnings. +03-14-2005 1.11.21 DES FEATURE Nintendo DS support +11-25-2004 1.11.20 SN FIX Added const qualifiers to function parameters not modified +10-15-2004 1.11.19 SN FEATURE Added SDK side nickname checking +09-20-2004 1.11.18 BED FIX ciUserTableCompareFn now treats NULL parameter as "less than" (Arcade helper) +09-16-2004 1.11.17 SN RELEASE Releasing to developer site. +09-15-2004 1.11.17 DDW FEATURE Added global version var to pass on crypt for Arcade blacklisting +09-13-2004 1.11.16 BED FEATURE Added support for channel mode "e". (Ops obey channel limit) +08-27-2004 1.11.15 DES CLEANUP Removed MacOS style includes + DES CLEANUP General Unicode cleanup + DES CLEANUP Updated Win32 project configurations + DES CLEANUP Fixed warnings under OSX + DES CLEANUP Updated OSX Makefile +08-10-2004 1.11.14 BED FIX ciSocketRecv will now process remaining messages after a disconnect. +08-05-2004 1.11.13 SN RELEASE Releasing to developer site. +07-19-2004 1.11.13 SN FIX Updated code with explicit casts to remove implicit cast error when compiling + at highest level and warnings treated as errors. +06-18-2004 1.11.12 BED RELEASE Releasing to developer site. +06-16-2004 1.11.12 BED FEATURE Added PS2 Insock (LibNet) support +04-19-2004 1.11.11 DDW FIX Fixed USRIP handler - handles message correctly after login +12-08-2003 1.11.10 BED FIX Updated PS2 sample for Sony 3.0. UNICODE no longer defined. +11-10-2003 1.11.09 DES RELEASE Releasing to developer site. +11-07-2003 1.11.09 DES FIX Updated the linux makefile to include the MD5 code. +11-06-2003 1.11.08 BED FIX Removed some unnecessary asserts in Unicode layer. + BED FIX Slight correction in sample callback. + BED FIX Sample no longer includes Leftover from memory leak testing. +10-23-2003 1.11.07 BED FIX Changed StringUtil to accept char* instead of unsigned char* + BED FIX Removed additional warnings for various compilers. +10-22-2003 1.11.06 BED RELEASE Releasing to developer site. (UNIQUE NICK AND UNICODE SUPPORT) +10-21-2003 1.11.06 BED FIX Added ChatASCII.h to silence all the CodeWarrior prototype warnings + BED FIX Cleaned up other misc warnings. +10-17-2003 1.11.05 DES FIX It now always picks the correct name to use for the socket log. +10-16-2003 1.11.04 BED FIX Switched from UTF8 to Ascii when dealing with nicknames. + DES FIX Always pass the appropriate nick to the chatNickErrorCallback. + FIX Correctly handle chatConnectLogin with a 0 namespaceID. +10-09-2003 1.11.03 BED FIX Switched to gsi_time type instead of unsinged long for PS2 compatibility +10-06-2003 1.11.02 DES FIX chatGet[User|Profile]ID now return values during the connection attempt. +10-01-2003 1.11.01 DES FEATURE Added a reason code for failed connect attempts. + FEATURE Added suggested nicks for a CHAT_INVALID_UNIQUENICK nick errors. +09-30-2003 1.11.00 DES FEATURE Uniquenick support. +09-30-2003 1.10.27 BED FEATURE Update Chat sample to support GSI_UNICODE mode. +09-15-2003 1.10.26 JED FIX Minor change to CONNECTED macro to be more robust. +09-08-2003 1.10.25 BED FEATURE Added UTF-8 Wrapper for UNICODE support. + FIX Fixed crash when chatRetryWithNick was called with NULL. (As directed in the documentation) +07-24-2003 1.10.24 DES RELEASE Releasing to developer site. +07-24-2003 1.10.24 DES FIX Added GSI_UNUSED around an unused param. +07-18-2003 1.10.23 BED FEATURE Added CodeWarrior (PS2) sample project file. + CLEANUP General cleanup to remove CodeWarrior (PS2) warnings. +07-17-2003 1.10.22 DES CLEANUP Cleaned up the PS2 Makefile, it now uses Makefile.commmon. +07-16-2003 1.10.21 BED FIX Added a newline at end of chatmain.c + DES FIX Changed two __mips64 checks to _PS2 checks. + BED FEATURE Added ProDG sample project files. +07-14-2003 1.10.20 BED FIX Now using ciNickIsValid to validate nicknames on client side. +07-14-2003 1.10.19 DES FIX Correctly handle being disconnected during the connection attempt. +07-11-2003 1.10.18 BED FIX Updated sample to join nonrestricted channel. +06-26-2003 1.10.17 DES CLEANUP Reduced the initial sizes of the channel and user hash tables. +06-11-2003 1.10.16 DES RELEASE Releasing to developer site. +05-09-2003 1.10.16 DES CLEANUP Removed Dreamcast support. + FIX Metrowerks for Win32 is no longer falsely identified as MacOS. +05-07-2003 1.10.15 DES RELEASE Releasing to developer site. +04-23-2003 1.10.15 BGW FIX Now handling the case of localtime() returning NULL. +04-17-2003 1.10.14 DES FIX Fix for simultaneous WHO requests on a single user. +04-16-2003 1.10.13 DDW FEATURE Added chatInChannel function exposing ciInChannel functionality +03-19-2003 1.10.12 DES FEATURE IRC logging (IRC_LOG) now uses the nick as part of the filename. +03-12-2003 1.10.11 DES FIX If requesting 0 keys for a channel, correctly request all keys. +03-11-2003 1.10.10 DES FIX chatGetChannelKeys no longer asserts if keys is NULL. + If keys is NULL and num is 0, all keys are returned. +03-03-2003 1.10.09 DES CLEANUP General cleanup to remove warnings. + FEATURE Added chatSetChannelLimit for directly setting a channel limit. +01-09-2003 1.10.08 DES CLEANUP Removed an unneeded assert. +12-19-2002 1.10.07 DES RELEASE Releasing to developer site. +12-19-2002 1.10.07 DES CLEANUP Removed assert.h includes. +12-13-2002 1.10.06 DES FEATURE Added PS2 eenet stack support. +12-05-2002 1.10.05 DES CLEANUP Added some explicit type casting to eliminate warnings. +11-22-2002 1.10.04 DES RELEASE Releasing to developer site. +11-20-2002 1.10.04 DES CLEANUP Cleaned up to remove compiler warings on the PS2. +11-15-2002 1.10.03 DES OTHER Changed chatc to use chatConnectSecure. +11-07-2002 1.10.02 DES FIX Fixed negative hash due to high-ascii characters in hashed string. +10-17-2002 1.10.01 DES RELEASE Limited release on developer site +10-17-2002 1.10.01 DES FIX Fixed bug where incoming data was not processed when disconnected. +09-25-2002 1.10.00 DDW OTHER Changelog started diff --git a/code/gamespy/Chat/chat.h b/code/gamespy/Chat/chat.h new file mode 100644 index 00000000..246d40d3 --- /dev/null +++ b/code/gamespy/Chat/chat.h @@ -0,0 +1,998 @@ +/* +GameSpy Chat SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _CHAT_H_ +#define _CHAT_H_ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../common/gsCommon.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +extern "C" { +#endif + +/************ +** DEFINES ** +************/ +// User and channel message types. +////////////////////////////////// +#define CHAT_MESSAGE 0 +#define CHAT_ACTION 1 +#define CHAT_NOTICE 2 +#define CHAT_UTM 3 +#define CHAT_ATM 4 + +// User modes. +// PANTS|03.12.01 - These are now bitflags! +// Both CHAT_VOICE and CHAT_OP can be set at the same time. +/////////////////////////////////////////////////////////// +#define CHAT_NORMAL 0 +#define CHAT_VOICE 1 +#define CHAT_OP 2 + +// Part reasons (see the chatUserParted callback). +////////////////////////////////////////////////// +#define CHAT_LEFT 0 // The user left the channel. +#define CHAT_QUIT 1 // The user quit the chat network. +#define CHAT_KICKED 2 // The user was kicked from the channel. +#define CHAT_KILLED 3 // The user was kicked off the chat network. + +// Possible nick errors while connecting. +///////////////////////////////////////// +#define CHAT_NICK_OK 0 +#define CHAT_IN_USE 1 +#define CHAT_INVALID 2 +#define CHAT_UNIQUENICK_EXPIRED 3 +#define CHAT_NO_UNIQUENICK 4 +#define CHAT_INVALID_UNIQUENICK 5 +#define CHAT_NICK_TOO_LONG 6 + +// Reasons why a connect attempt could fail. +//////////////////////////////////////////// +#define CHAT_DISCONNECTED 0 +#define CHAT_NICK_ERROR 1 +#define CHAT_LOGIN_FAILED 2 + +/********** +** TYPES ** +**********/ +// Boolean type. +//////////////// +typedef enum { CHATFalse, CHATTrue } CHATBool; + +// A CHAT object represents a client connection to a chat server. +///////////////////////////////////////////////////////////////// +typedef void * CHAT; + +// Object representing a channel's mode. +//////////////////////////////////////// +typedef struct CHATChannelMode +{ + CHATBool InviteOnly; + CHATBool Private; + CHATBool Secret; + CHATBool Moderated; + CHATBool NoExternalMessages; + CHATBool OnlyOpsChangeTopic; + CHATBool OpsObeyChannelLimit; + int Limit; +} CHATChannelMode; + +// The result of a channel enter attempt, +// passed into the chatEnterChannelCallback(). +////////////////////////////////////////////// +typedef enum +{ + CHATEnterSuccess, // The channel was successfully entered. + CHATBadChannelName, // The channel name was invalid. + CHATChannelIsFull, // The channel is at its user limit. + CHATInviteOnlyChannel, // The channel is invite only. + CHATBannedFromChannel, // The local user is banned from this channel. + CHATBadChannelPassword, // The channel has a password, and a bad password (or none) was given. + CHATTooManyChannels, // The server won't allow this user in any more channels. + CHATEnterTimedOut, // The attempt to enter timed out. + CHATBadChannelMask // Not sure if any servers use this, or what it means! (ERR_BADCHANMASK) +} CHATEnterResult; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef GSI_UNICODE +#define chatConnect chatConnectA +#define chatConnectSpecial chatConnectSpecialA +#define chatConnectSecure chatConnectSecureA +#define chatConnectLogin chatConnectLoginA +#define chatConnectPreAuth chatConnectPreAuthA +#define chatRetryWithNick chatRetryWithNickA +#define chatRegisterUniqueNick chatRegisterUniqueNickA +#define chatSendRaw chatSendRawA +#define chatChangeNick chatChangeNickA +#define chatFixNick chatFixNickA +#define chatTranslateNick chatTranslateNickA +#define chatAuthenticateCDKey chatAuthenticateCDKeyA +#define chatEnumChannels chatEnumChannelsA +#define chatEnterChannel chatEnterChannelA +#define chatLeaveChannel chatLeaveChannelA +#define chatSendChannelMessage chatSendChannelMessageA +#define chatSetChannelTopic chatSetChannelTopicA +#define chatGetChannelTopic chatGetChannelTopicA +#define chatSetChannelMode chatSetChannelModeA +#define chatGetChannelMode chatGetChannelModeA +#define chatSetChannelPassword chatSetChannelPasswordA +#define chatGetChannelPassword chatGetChannelPasswordA +#define chatSetChannelLimit chatSetChannelLimitA +#define chatEnumChannelBans chatEnumChannelBansA +#define chatAddChannelBan chatAddChannelBanA +#define chatRemoveChannelBan chatRemoveChannelBanA +#define chatSetChannelGroup chatSetChannelGroupA +#define chatGetChannelNumUsers chatGetChannelNumUsersA +#define chatInChannel chatInChannelA +#define chatEnumUsers chatEnumUsersA +#define chatSendUserMessage chatSendUserMessageA +#define chatGetUserInfo chatGetUserInfoA +#define chatGetBasicUserInfo chatGetBasicUserInfoA +#define chatGetBasicUserInfoNoWait chatGetBasicUserInfoNoWaitA +#define chatGetChannelBasicUserInfo chatGetChannelBasicUserInfoA +#define chatInviteUser chatInviteUserA +#define chatKickUser chatKickUserA +#define chatBanUser chatBanUserA +#define chatSetUserMode chatSetUserModeA +#define chatGetUserMode chatGetUserModeA +#define chatGetUserModeNoWait chatGetUserModeNoWaitA +#define chatSetGlobalKeys chatSetGlobalKeysA +#define chatSetChannelKeys chatSetChannelKeysA +#define chatGetGlobalKeys chatGetGlobalKeysA +#define chatGetChannelKeys chatGetChannelKeysA +#define chatGetNick chatGetNickA +#define chatGetUdpRelay chatGetUdpRelayA +#else +#define chatConnect chatConnectW +#define chatConnectSpecial chatConnectSpecialW +#define chatConnectSecure chatConnectSecureW +#define chatConnectLogin chatConnectLoginW +#define chatConnectPreAuth chatConnectPreAuthW +#define chatRetryWithNick chatRetryWithNickW +#define chatRegisterUniqueNick chatRegisterUniqueNickW +#define chatSendRaw chatSendRawW +#define chatChangeNick chatChangeNickW +#define chatFixNick chatFixNickW +#define chatTranslateNick chatTranslateNickW +#define chatAuthenticateCDKey chatAuthenticateCDKeyW +#define chatEnumChannels chatEnumChannelsW +#define chatEnterChannel chatEnterChannelW +#define chatLeaveChannel chatLeaveChannelW +#define chatSendChannelMessage chatSendChannelMessageW +#define chatSetChannelTopic chatSetChannelTopicW +#define chatGetChannelTopic chatGetChannelTopicW +#define chatSetChannelMode chatSetChannelModeW +#define chatGetChannelMode chatGetChannelModeW +#define chatSetChannelPassword chatSetChannelPasswordW +#define chatGetChannelPassword chatGetChannelPasswordW +#define chatSetChannelLimit chatSetChannelLimitW +#define chatEnumChannelBans chatEnumChannelBansW +#define chatAddChannelBan chatAddChannelBanW +#define chatRemoveChannelBan chatRemoveChannelBanW +#define chatSetChannelGroup chatSetChannelGroupW +#define chatGetChannelNumUsers chatGetChannelNumUsersW +#define chatInChannel chatInChannelW +#define chatEnumUsers chatEnumUsersW +#define chatSendUserMessage chatSendUserMessageW +#define chatGetUserInfo chatGetUserInfoW +#define chatGetBasicUserInfo chatGetBasicUserInfoW +#define chatGetBasicUserInfoNoWait chatGetBasicUserInfoNoWaitW +#define chatGetChannelBasicUserInfo chatGetChannelBasicUserInfoW +#define chatInviteUser chatInviteUserW +#define chatKickUser chatKickUserW +#define chatBanUser chatBanUserW +#define chatSetUserMode chatSetUserModeW +#define chatGetUserMode chatGetUserModeW +#define chatGetUserModeNoWait chatGetUserModeNoWaitW +#define chatSetGlobalKeys chatSetGlobalKeysW +#define chatSetChannelKeys chatSetChannelKeysW +#define chatGetGlobalKeys chatGetGlobalKeysW +#define chatGetChannelKeys chatGetChannelKeysW +#define chatGetNick chatGetNickW +#define chatGetUdpRelay chatGetUdpRelayW +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/********************** +** GLOBALS CALLBACKS ** +**********************/ +// Gets raw incoming network traffic. +///////////////////////////////////// +typedef void (* chatRaw)(CHAT chat, + const gsi_char * raw, + void * param); + +// Called when the client has been disconnected. +//////////////////////////////////////////////// +typedef void (* chatDisconnected)(CHAT chat, + const gsi_char * reason, + void * param); + +// Called when a private message from another user is received. +// If user==NULL, this is a message from the server. +/////////////////////////////////////////////////////////////// +typedef void (* chatPrivateMessage)(CHAT chat, + const gsi_char * user, + const gsi_char * message, + int type, // See defined message types above. + void * param); + +// Called when invited into a channel. +////////////////////////////////////// +typedef void (* chatInvited)(CHAT chat, + const gsi_char * channel, + const gsi_char * user, + void * param); + +// A connection's global callbacks. +/////////////////////////////////// +typedef struct chatGlobalCallbacks +{ + chatRaw raw; + chatDisconnected disconnected; + chatPrivateMessage privateMessage; + chatInvited invited; + void * param; +} chatGlobalCallbacks; + +/********************** +** CHANNEL CALLBACKS ** +**********************/ +// Called when a message is received in a channel. +////////////////////////////////////////////////// +typedef void (* chatChannelMessage)(CHAT chat, + const gsi_char * channel, + const gsi_char * user, + const gsi_char * message, + int type, // See defined message types above. + void * param); + +// Called when the local client is kicked from a channel. +///////////////////////////////////////////////////////// +typedef void (* chatKicked)(CHAT chat, + const gsi_char * channel, + const gsi_char * user, + const gsi_char * reason, + void * param); + +// Called when a user joins a channel we're in. +/////////////////////////////////////////////// +typedef void (* chatUserJoined)(CHAT chat, + const gsi_char * channel, + const gsi_char * user, + int mode, // See defined user modes above. + void * param); + +// Called when a user parts a channel we're in. +/////////////////////////////////////////////// +typedef void (* chatUserParted)(CHAT chat, + const gsi_char * channel, + const gsi_char * user, + int why, // See defined part reasons above. + const gsi_char * reason, + const gsi_char * kicker, + void * param); + +// Called when a user in a channel we're in changes nicks. +////////////////////////////////////////////////////////// +typedef void (* chatUserChangedNick)(CHAT chat, + const gsi_char * channel, + const gsi_char * oldNick, + const gsi_char * newNick, + void * param); + +// Called when the topic changes in a channel we're in. +/////////////////////////////////////////////////////// +typedef void (* chatTopicChanged)(CHAT chat, + const gsi_char * channel, + const gsi_char * topic, + void * param); + +// Called when the mode changes in a channel we're in. +////////////////////////////////////////////////////// +typedef void (* chatChannelModeChanged)(CHAT chat, + const gsi_char * channel, + CHATChannelMode * mode, + void * param); + +// Called when a user's mode changes in a channel we're in. +/////////////////////////////////////////////////////////// +typedef void (* chatUserModeChanged)(CHAT chat, + const gsi_char * channel, + const gsi_char * user, + int mode, // See defined user modes above. + void * param); + +// Called when the user list changes (due to a join or a part) in a channel we're in. +///////////////////////////////////////////////////////////////////////////////////// +typedef void (* chatUserListUpdated)(CHAT chat, + const gsi_char * channel, + void * param); + +// Called when the chat server sends an entire new user list for a channel we're in. +//////////////////////////////////////////////////////////////////////////////////// +typedef void (* chatNewUserList)(CHAT chat, + const gsi_char * channel, + int num, + const gsi_char ** users, + int * modes, + void * param); + +// Called when a user changes a broadcast key in a channel we're in. +//////////////////////////////////////////////////////////////////// +typedef void (* chatBroadcastKeyChanged)(CHAT chat, + const gsi_char * channel, + const gsi_char * user, + const gsi_char * key, + const gsi_char * value, + void * param); + +// A channel's callbacks. +///////////////////////// +typedef struct chatChannelCallbacks +{ + chatChannelMessage channelMessage; + chatKicked kicked; + chatUserJoined userJoined; + chatUserParted userParted; + chatUserChangedNick userChangedNick; + chatTopicChanged topicChanged; + chatChannelModeChanged channelModeChanged; + chatUserModeChanged userModeChanged; + chatUserListUpdated userListUpdated; + chatNewUserList newUserList; + chatBroadcastKeyChanged broadcastKeyChanged; + void * param; +} chatChannelCallbacks; + +/************ +** GENERAL ** +************/ +// Called when a connect attempt completes. +// failureReason is only set if success is CHATFalse. +///////////////////////////////////////////////////// +typedef void (* chatConnectCallback)(CHAT chat, + CHATBool success, + int failureReason, // CHAT_DISCONNECTED, CHAT_NICK_ERROR, etc. + void * param); +// Called if there is an error with the nick while connecting. +// To retry with a new nick, call chatRetryWithNick. +// Otherwise, call chatDisconnect to stop the connection. +// Suggested nicks are only provided if type is CHAT_INVALID_UNIQUENICK. +//////////////////////////////////////////////////////////////////////// +typedef void (* chatNickErrorCallback)(CHAT chat, + int type, // CHAT_IN_USE, CHAT_INVALID, etc. + const gsi_char * nick, + int numSuggestedNicks, + const gsi_char ** suggestedNicks, + void * param); +typedef void (* chatFillInUserCallback)(CHAT chat, + unsigned int IP, // PANTS|08.21.00 - changed from unsigned long + gsi_char user[128], + void * param); +// Connects you to a chat server and returns a CHAT object. +/////////////////////////////////////////////////////////// +CHAT chatConnect(const gsi_char * serverAddress, + int port, + const gsi_char * nick, + const gsi_char * user, + const gsi_char * name, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking); +CHAT chatConnectSpecial(const gsi_char * serverAddress, + int port, + const gsi_char * nick, + const gsi_char * name, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking); +CHAT chatConnectSecure(const gsi_char * serverAddress, + int port, + const gsi_char * nick, + const gsi_char * name, + const gsi_char * gamename, + const gsi_char * secretKey, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking); +CHAT chatConnectLogin(const gsi_char * serverAddress, + int port, + int namespaceID, + const gsi_char * email, + const gsi_char * profilenick, + const gsi_char * uniquenick, + const gsi_char * password, + const gsi_char * name, + const gsi_char * gamename, + const gsi_char * secretKey, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking); +CHAT chatConnectPreAuth(const gsi_char * serverAddress, + int port, + const gsi_char * authtoken, + const gsi_char * partnerchallenge, + const gsi_char * name, + const gsi_char * gamename, + const gsi_char * secretKey, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking); +// If the chatNickErrorCallback gets called, then this can be called +// with a new nick to retry. If this isn't called, the connection can be +// disconnected with chatDisconnect. If the new nick is successful, then +// the chatConnectCallback will get called. If there's another nick +// error, the chatNickErrorCallback will get called again. +///////////////////////////////////////////////////////////////////////// +void chatRetryWithNick(CHAT chat, + const gsi_char * nick); +// Register a uniquenick. +// Should be called in response to the chatNickErrorCallback being called +// with a type of CHAT_UNIQUENICK_EXPIRED or CHAT_NO_UNIQUENICK. +// If the uniquenick cannot be registered, the chatNickErrorCallback will +// be called again with a type of CHAT_IN_USE or CHAT_INVALID. +///////////////////////////////////////////////////////////////////////// +void chatRegisterUniqueNick(CHAT chat, + int namespaceID, + const gsi_char * uniquenick, + const gsi_char * cdkey); + +// Disconnect the chat connection. +////////////////////////////////// +void chatDisconnect(CHAT chat); + +// Processes the chat connection. +///////////////////////////////// +void chatThink(CHAT chat); + +// Sends raw data, without any interpretation. +////////////////////////////////////////////// +void chatSendRaw(CHAT chat, + const gsi_char * command); + +// Called as a result of a nick change attempt. +/////////////////////////////////////////////// +typedef void (* chatChangeNickCallback)(CHAT chat, + CHATBool success, + const gsi_char * oldNick, + const gsi_char * newNick, + void * param); +// Change the local user's nick. +//////////////////////////////// +void chatChangeNick(CHAT chat, + const gsi_char * newNick, + chatChangeNickCallback callback, + void * param, + CHATBool blocking); + +// Get our local nickname. +////////////////////////// +const gsi_char * chatGetNick(CHAT chat); + +// Copies the oldNick to the newNick, replacing any invalid characters with legal ones. +/////////////////////////////////////////////////////////////////////////////////////// +void chatFixNick(gsi_char * newNick, + const gsi_char * oldNick); + +// Removes the namespace extension from a chat nick. +//////////////////////////////////////////////////// +const gsi_char * chatTranslateNick(gsi_char * nick, + const gsi_char * extension); + +// Gets the local userID. +// Only valid if connected with chatConnectLogin or chatConnectPreAuth. +/////////////////////////////////////////////////////////////////////// +int chatGetUserID(CHAT chat); + +// Gets the local profileID. +// Only valid if connected with chatConnectLogin or chatConnectPreAuth. +/////////////////////////////////////////////////////////////////////// +int chatGetProfileID(CHAT chat); + +// Turn on/off quiet mode. +////////////////////////// +void chatSetQuietMode(CHAT chat, + CHATBool quiet); + +// Called as a result of an authenticate CD key attempt. +//////////////////////////////////////////////////////// +typedef void (* chatAuthenticateCDKeyCallback)(CHAT chat, + int result, + const gsi_char * message, + void * param); + +// Attempts to authenticates a CD key. +////////////////////////////////////// +void chatAuthenticateCDKey(CHAT chat, + const gsi_char * cdkey, + chatAuthenticateCDKeyCallback callback, + void * param, + CHATBool blocking); + +/************* +** CHANNELS ** +*************/ +// Gets called for each channel enumerated. +/////////////////////////////////////////// +typedef void (* chatEnumChannelsCallbackEach)(CHAT chat, + CHATBool success, + int index, + const gsi_char * channel, + const gsi_char * topic, + int numUsers, + void * param); +// Gets called after all channels have been enumerated. +/////////////////////////////////////////////////////// +typedef void (* chatEnumChannelsCallbackAll)(CHAT chat, + CHATBool success, + int numChannels, + const gsi_char ** channels, + const gsi_char ** topics, + int * numUsers, + void * param); +// Enumerates the channels available on a chat server. +////////////////////////////////////////////////////// +void chatEnumChannels(CHAT chat, + const gsi_char * filter, + chatEnumChannelsCallbackEach callbackEach, + chatEnumChannelsCallbackAll callbackAll, + void * param, + CHATBool blocking); + +// Gets called for each channel enumerated. +/////////////////////////////////////////// +typedef void (* chatEnumJoinedChannelsCallback)(CHAT chat, + int index, + const gsi_char * channel, + void * param); + +// Enumerates the channels that we are joined to +////////////////////////////////////////////////////// +void chatEnumJoinedChannels(CHAT chat, + chatEnumJoinedChannelsCallback callback, + void * param); + + + +// Gets called when a channel has been entered. +/////////////////////////////////////////////// +typedef void (* chatEnterChannelCallback)(CHAT chat, + CHATBool success, + CHATEnterResult result, + const gsi_char * channel, + void * param); +// Enters a channel. +//////////////////// +void chatEnterChannel(CHAT chat, + const gsi_char * channel, + const gsi_char * password, + chatChannelCallbacks * callbacks, + chatEnterChannelCallback callback, + void * param, + CHATBool blocking); + +// Leaves a channel. +//////////////////// +void chatLeaveChannel(CHAT chat, + const gsi_char * channel, + const gsi_char * reason); // PANTS|03.13.01 + +// Sends a message to a channel. +//////////////////////////////// +void chatSendChannelMessage(CHAT chat, + const gsi_char * channel, + const gsi_char * message, + int type); + +// Sets the topic in a channel. +/////////////////////////////// +void chatSetChannelTopic(CHAT chat, + const gsi_char * channel, + const gsi_char * topic); + +// Gets called when a channel's topic has been retrieved. +///////////////////////////////////////////////////////// +typedef void (* chatGetChannelTopicCallback)(CHAT chat, + CHATBool success, + const gsi_char * channel, + const gsi_char * topic, + void * param); +// Gets a channel's topic. +////////////////////////// +void chatGetChannelTopic(CHAT chat, + const gsi_char * channel, + chatGetChannelTopicCallback callback, + void * param, + CHATBool blocking); + +// Sets a channel's mode. +///////////////////////// +void chatSetChannelMode(CHAT chat, + const gsi_char * channel, + CHATChannelMode * mode); + +// Gets called when a channel's mode has been retrieved. +//////////////////////////////////////////////////////// +typedef void (* chatGetChannelModeCallback)(CHAT chat, + CHATBool success, + const gsi_char * channel, + CHATChannelMode * mode, + void * param); +// Gets a channel's mode. +///////////////////////// +void chatGetChannelMode(CHAT chat, + const gsi_char * channel, + chatGetChannelModeCallback callback, + void * param, + CHATBool blocking); + +// Sets the password in a channel. +////////////////////////////////// +void chatSetChannelPassword(CHAT chat, + const gsi_char * channel, + CHATBool enable, + const gsi_char * password); + +// Called when the channel's password has been retrieved. +///////////////////////////////////////////////////////// +typedef void (* chatGetChannelPasswordCallback)(CHAT chat, + CHATBool success, + const gsi_char * channel, + CHATBool enabled, + const gsi_char * password, + void * param); +// Gets the password in a channel. +////////////////////////////////// +void chatGetChannelPassword(CHAT chat, + const gsi_char * channel, + chatGetChannelPasswordCallback callback, + void * param, + CHATBool blocking); + +// Set the maximum number of users allowed in a channel. +//////////////////////////////////////////////////////// +void chatSetChannelLimit(CHAT chat, + const gsi_char * channel, + int limit); + +// Called with the list of bans in a channel. +///////////////////////////////////////////// +typedef void (* chatEnumChannelBansCallback)(CHAT chat, + CHATBool success, + const gsi_char * channel, + int numBans, + const gsi_char ** bans, + void * param); +// Enumerate through the bans in a channel. +/////////////////////////////////////////// +void chatEnumChannelBans(CHAT chat, + const gsi_char * channel, + chatEnumChannelBansCallback callback, + void * param, + CHATBool blocking); + +// Adds a channel ban. +////////////////////// +void chatAddChannelBan(CHAT chat, + const gsi_char * channel, + const gsi_char * ban); + +// Removes a ban string from a channel. +/////////////////////////////////////// +void chatRemoveChannelBan(CHAT chat, + const gsi_char * channel, + const gsi_char * ban); + +// Set the group this channel is a part of. +/////////////////////////////////////////// +void chatSetChannelGroup(CHAT chat, + const gsi_char * channel, + const gsi_char * group); + +// Get the number of users in the channel. +// Returns -1 if we are not in the channel. +/////////////////////////////////////////// +int chatGetChannelNumUsers(CHAT chat, + const gsi_char * channel); + + +// Returns CHATTrue if we are in the channel +/////////////////////////////////////////// +CHATBool chatInChannel(CHAT chat, + const gsi_char * channel); + + +/********** +** USERS ** +**********/ +// Called with the list of users in a channel. +////////////////////////////////////////////// +typedef void (* chatEnumUsersCallback)(CHAT chat, + CHATBool success, + const gsi_char * channel, //PANTS|02.11.00|added paramater + int numUsers, + const gsi_char ** users, + int * modes, + void * param); +// Enumerate through the users in a channel. +//////////////////////////////////////////// +void chatEnumUsers(CHAT chat, + const gsi_char * channel, + chatEnumUsersCallback callback, + void * param, + CHATBool blocking); + +// Send a private message to a user. +//////////////////////////////////// +void chatSendUserMessage(CHAT chat, + const gsi_char * user, + const gsi_char * message, + int type); + +// Called with a user's info. +///////////////////////////// +typedef void (* chatGetUserInfoCallback)(CHAT chat, + CHATBool success, + const gsi_char * nick, //PANTS|02.14.2000|added nick and user + const gsi_char * user, + const gsi_char * name, + const gsi_char * address, + int numChannels, + const gsi_char ** channels, + void * param); +// Get a user's info. +///////////////////// +void chatGetUserInfo(CHAT chat, + const gsi_char * user, + chatGetUserInfoCallback callback, + void * param, + CHATBool blocking); + +// Called with a user's basic info. +/////////////////////////////////// +typedef void (* chatGetBasicUserInfoCallback)(CHAT chat, + CHATBool success, + const gsi_char * nick, + const gsi_char * user, + const gsi_char * address, + void * param); + +// Get some basic info on the user. +// PANTS|12.08.2000 +/////////////////////////////////// +void chatGetBasicUserInfo(CHAT chat, + const gsi_char * user, + chatGetBasicUserInfoCallback callback, + void * param, + CHATBool blocking); + +// Get basic info without waiting. +// Returns CHATFalse if the info isn't available. +///////////////////////////////////////////////// +CHATBool chatGetBasicUserInfoNoWait(CHAT chat, + const gsi_char * nick, + const gsi_char ** user, + const gsi_char ** address); + +// Called with a user's basic info for everyone in a channel. +// Called with a NULL nick/user/address at the end. +///////////////////////////////////////////////////////////// +typedef void (* chatGetChannelBasicUserInfoCallback)(CHAT chat, + CHATBool success, + const gsi_char * channel, + const gsi_char * nick, + const gsi_char * user, + const gsi_char * address, + void * param); + +// Get basic info on all the users in a channel. +// PANTS|12.19.00 +//////////////////////////////////////////////// +void chatGetChannelBasicUserInfo(CHAT chat, + const gsi_char * channel, + chatGetChannelBasicUserInfoCallback callback, + void * param, + CHATBool blocking); + +// Invite a user into a channel. +//////////////////////////////// +void chatInviteUser(CHAT chat, + const gsi_char * channel, + const gsi_char * user); + +// Kick a user from a channel. +////////////////////////////// +void chatKickUser(CHAT chat, + const gsi_char * channel, + const gsi_char * user, + const gsi_char * reason); + +// Ban a user from a channel. +///////////////////////////// +void chatBanUser(CHAT chat, + const gsi_char * channel, + const gsi_char * user); + +// Sets a user's mode in a channel. +/////////////////////////////////// +void chatSetUserMode(CHAT chat, + const gsi_char * channel, + const gsi_char * user, + int mode); + +// Called with the user's mode. +/////////////////////////////// +typedef void (* chatGetUserModeCallback)(CHAT chat, + CHATBool success, + const gsi_char * channel, + const gsi_char * user, + int mode, + void * param); +// Gets a user's mode in a channel. +/////////////////////////////////// +void chatGetUserMode(CHAT chat, + const gsi_char * channel, + const gsi_char * user, + chatGetUserModeCallback callback, + void * param, + CHATBool blocking); + +// Gets a user's mode in a channel. +/////////////////////////////////// +CHATBool chatGetUserModeNoWait(CHAT chat, + const gsi_char * channel, + const gsi_char * user, + int * mode); + + +// Called in response to a request for the UDP relay for a channel +//////////////////////////////////////////////////////////////////// +typedef void (* chatGetUdpRelayCallback)(CHAT chat, + const gsi_char * channel, + const gsi_char * udpIp, + unsigned short udpPort, + int udpKey, + void * param); + +// Get the UDP relay address for a channel +/////////////////////////////////// +void chatGetUdpRelay(CHAT chat, + const gsi_char * channel, + chatGetUdpRelayCallback callback, + void * param, + CHATBool blocking); + + +/********* +** KEYS ** +*********/ +// Sets global key/values for the local user. +// Set a value to NULL or "" to clear that key. +/////////////////////////////////////////////// +void chatSetGlobalKeys(CHAT chat, + int num, + const gsi_char ** keys, + const gsi_char ** values); + +// Called with a user's global key/values. +// If used for a set of users, will be +// called with user==NULL when done. +////////////////////////////////////////// +typedef void (* chatGetGlobalKeysCallback)(CHAT chat, + CHATBool success, + const gsi_char * user, + int num, + const gsi_char ** keys, + const gsi_char ** values, + void * param); +// Gets global key/values for a user or users. +// To get the global key/values for one user, pass in that +// user's nick as the target. To get the global key/values +// for every user in a channel, use the channel name as the target. +/////////////////////////////////////////////////////////////////// +void chatGetGlobalKeys(CHAT chat, + const gsi_char * target, + int num, + const gsi_char ** keys, + chatGetGlobalKeysCallback callback, + void * param, + CHATBool blocking); + +// Sets channel key/values. +// If user is NULL or "", the keys will be set on the channel. +// Otherwise, they will be set on the user, +// Only ops can set channel keys on other users. +// Set a value to NULL or "" to clear that key. +////////////////////////////////////////////////////////////// +void chatSetChannelKeys(CHAT chat, + const gsi_char * channel, + const gsi_char * user, + int num, + const gsi_char ** keys, + const gsi_char ** values); + +// Called with a user's channel key/values, or a channel's key/values. +// If used for a set of users, will be called with user==NULL when done. +// If used for a channel, will be called once with user==NULL. +//////////////////////////////////////////////////////////////////////// +typedef void (* chatGetChannelKeysCallback)(CHAT chat, + CHATBool success, + const gsi_char * channel, + const gsi_char * user, + int num, + const gsi_char ** keys, + const gsi_char ** values, + void * param); +// Gets channel key/values for a user or users, or for a channel. +// To get the channel key/values for every user in +// a channel, pass in "*" as the user. To get the keys for a channel, +// pass in NULL or "". +////////////////////////////////////////////////////////////////////// +void chatGetChannelKeys(CHAT chat, + const gsi_char * channel, + const gsi_char * user, + int num, + const gsi_char ** keys, + chatGetChannelKeysCallback callback, + void * param, + CHATBool blocking); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// This ASCII versions must be available even when GSI_UNICODE is defined +#ifdef GSI_UNICODE +CHATBool chatGetBasicUserInfoNoWaitA(CHAT chat, + const char * nick, + const char ** user, + const char ** address); +#endif +/* +void chatGetBasicUserInfoA(CHAT chat, + const char * user, + chatGetBasicUserInfoCallback callback, + void * param, + CHATBool blocking); +*/ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} // extern "C" +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // _CHAT_H_ diff --git a/code/gamespy/Chat/chatASCII.h b/code/gamespy/Chat/chatASCII.h new file mode 100644 index 00000000..176c3f8a --- /dev/null +++ b/code/gamespy/Chat/chatASCII.h @@ -0,0 +1,441 @@ +/* +GameSpy Chat SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _CHATASCII_H_ +#define _CHATASCII_H_ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "chat.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +extern "C" { +#endif + + +// Connects you to a chat server and returns a CHAT object. +/////////////////////////////////////////////////////////// +CHAT chatConnectA(const char * serverAddress, + int port, + const char * nick, + const char * user, + const char * name, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking); +CHAT chatConnectSpecialA(const char * serverAddress, + int port, + const char * nick, + const char * name, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking); +CHAT chatConnectSecureA(const char * serverAddress, + int port, + const char * nick, + const char * name, + const char * gamename, + const char * secretKey, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking); +CHAT chatConnectLoginA(const char * serverAddress, + int port, + int namespaceID, + const char * email, + const char * profilenick, + const char * uniquenick, + const char * password, + const char * name, + const char * gamename, + const char * secretKey, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking); +CHAT chatConnectPreAuthA(const char * serverAddress, + int port, + const char * authtoken, + const char * partnerchallenge, + const char * name, + const char * gamename, + const char * secretKey, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking); + +// If the chatNickErrorCallback gets called, then this can be called +// with a new nick to retry. If this isn't called, the connection can be +// disconnected with chatDisconnect. If the new nick is successful, then +// the chatConnectCallback will get called. If there's another nick +// error, the chatNickErrorCallback will get called again. +///////////////////////////////////////////////////////////////////////// +void chatRetryWithNickA(CHAT chat, + const char * nick); + +// Register a uniquenick. +// Should be called in response to the chatNickErrorCallback being called +// with a type of CHAT_UNIQUENICK_EXPIRED or CHAT_NO_UNIQUENICK. +// If the uniquenick cannot be registered, the chatNickErrorCallback will +// be called again with a type of CHAT_IN_USE or CHAT_INVALID. +///////////////////////////////////////////////////////////////////////// +void chatRegisterUniqueNickA(CHAT chat, + int namespaceID, + const char * uniquenick, + const char * cdkey); + +// Sends raw data, without any interpretation. +////////////////////////////////////////////// +void chatSendRawA(CHAT chat, + const char * command); + +// Change the local user's nick. +//////////////////////////////// +void chatChangeNickA(CHAT chat, + const char * newNick, + chatChangeNickCallback callback, + void * param, + CHATBool blocking); + +// Get our local nickname. +////////////////////////// +const char * chatGetNickA(CHAT chat); + +// Copies the oldNick to the newNick, replacing any invalid characters with legal ones. +/////////////////////////////////////////////////////////////////////////////////////// +void chatFixNickA(char * newNick, + const char * oldNick); + +// Removes the namespace extension from a chat nick. +//////////////////////////////////////////////////// +const char * chatTranslateNickA(char * nick, + const char * extension); + +// Attempts to authenticates a CD key. +////////////////////////////////////// +void chatAuthenticateCDKeyA(CHAT chat, + const char * cdkey, + chatAuthenticateCDKeyCallback callback, + void * param, + CHATBool blocking); + +/************* +** CHANNELS ** +*************/ +// Enumerates the channels available on a chat server. +////////////////////////////////////////////////////// +void chatEnumChannelsA(CHAT chat, + const char * filter, + chatEnumChannelsCallbackEach callbackEach, + chatEnumChannelsCallbackAll callbackAll, + void * param, + CHATBool blocking); + +// Enters a channel. +//////////////////// +void chatEnterChannelA(CHAT chat, + const char * channel, + const char * password, + chatChannelCallbacks * callbacks, + chatEnterChannelCallback callback, + void * param, + CHATBool blocking); + +// Leaves a channel. +//////////////////// +void chatLeaveChannelA(CHAT chat, + const char * channel, + const char * reason); // PANTS|03.13.01 + +// Sends a message to a channel. +//////////////////////////////// +void chatSendChannelMessageA(CHAT chat, + const char * channel, + const char * message, + int type); + +// Sets the topic in a channel. +/////////////////////////////// +void chatSetChannelTopicA(CHAT chat, + const char * channel, + const char * topic); + +// Gets a channel's topic. +////////////////////////// +void chatGetChannelTopicA(CHAT chat, + const char * channel, + chatGetChannelTopicCallback callback, + void * param, + CHATBool blocking); + +// Sets a channel's mode. +///////////////////////// +void chatSetChannelModeA(CHAT chat, + const char * channel, + CHATChannelMode * mode); + +// Gets a channel's mode. +///////////////////////// +void chatGetChannelModeA(CHAT chat, + const char * channel, + chatGetChannelModeCallback callback, + void * param, + CHATBool blocking); + +// Sets the password in a channel. +////////////////////////////////// +void chatSetChannelPasswordA(CHAT chat, + const char * channel, + CHATBool enable, + const char * password); + +// Gets the password in a channel. +////////////////////////////////// +void chatGetChannelPasswordA(CHAT chat, + const char * channel, + chatGetChannelPasswordCallback callback, + void * param, + CHATBool blocking); + +// Set the maximum number of users allowed in a channel. +//////////////////////////////////////////////////////// +void chatSetChannelLimitA(CHAT chat, + const char * channel, + int limit); + +// Enumerate through the bans in a channel. +/////////////////////////////////////////// +void chatEnumChannelBansA(CHAT chat, + const char * channel, + chatEnumChannelBansCallback callback, + void * param, + CHATBool blocking); + +// Adds a channel ban. +////////////////////// +void chatAddChannelBanA(CHAT chat, + const char * channel, + const char * ban); + +// Removes a ban string from a channel. +/////////////////////////////////////// +void chatRemoveChannelBanA(CHAT chat, + const char * channel, + const char * ban); + +// Set the group this channel is a part of. +/////////////////////////////////////////// +void chatSetChannelGroupA(CHAT chat, + const char * channel, + const char * group); + +// Get the number of users in the channel. +// Returns -1 if we are not in the channel. +/////////////////////////////////////////// +int chatGetChannelNumUsersA(CHAT chat, + const char * channel); + + +// Returns CHATTrue if we are in the channel +/////////////////////////////////////////// +CHATBool chatInChannelA(CHAT chat, + const char * channel); + + +/********** +** USERS ** +**********/ +// Enumerate through the users in a channel. +//////////////////////////////////////////// +void chatEnumUsersA(CHAT chat, + const char * channel, + chatEnumUsersCallback callback, + void * param, + CHATBool blocking); + +// Send a private message to a user. +//////////////////////////////////// +void chatSendUserMessageA(CHAT chat, + const char * user, + const char * message, + int type); + +// Get a user's info. +///////////////////// +void chatGetUserInfoA(CHAT chat, + const char * user, + chatGetUserInfoCallback callback, + void * param, + CHATBool blocking); + + +// Get some basic info on the user. +// PANTS|12.08.2000 +/////////////////////////////////// +void chatGetBasicUserInfoA(CHAT chat, + const char * user, + chatGetBasicUserInfoCallback callback, + void * param, + CHATBool blocking); + +// Get basic info without waiting. +// Returns CHATFalse if the info isn't available. +///////////////////////////////////////////////// +CHATBool chatGetBasicUserInfoNoWaitA(CHAT chat, + const char * nick, + const char ** user, + const char ** address); + + +// Get basic info on all the users in a channel. +// PANTS|12.19.00 +//////////////////////////////////////////////// +void chatGetChannelBasicUserInfoA(CHAT chat, + const char * channel, + chatGetChannelBasicUserInfoCallback callback, + void * param, + CHATBool blocking); + +// Invite a user into a channel. +//////////////////////////////// +void chatInviteUserA(CHAT chat, + const char * channel, + const char * user); + +// Kick a user from a channel. +////////////////////////////// +void chatKickUserA(CHAT chat, + const char * channel, + const char * user, + const char * reason); + +// Ban a user from a channel. +///////////////////////////// +void chatBanUserA(CHAT chat, + const char * channel, + const char * user); + +// Sets a user's mode in a channel. +/////////////////////////////////// +void chatSetUserModeA(CHAT chat, + const char * channel, + const char * user, + int mode); + +// Gets a user's mode in a channel. +/////////////////////////////////// +void chatGetUserModeA(CHAT chat, + const char * channel, + const char * user, + chatGetUserModeCallback callback, + void * param, + CHATBool blocking); + +// Gets a user's mode in a channel. +/////////////////////////////////// +CHATBool chatGetUserModeNoWaitA(CHAT chat, + const char * channel, + const char * user, + int * mode); + +// Get the UDP relay address for a channel +/////////////////////////////////// +void chatGetUdpRelayA(CHAT chat, + const char * channel, + chatGetUdpRelayCallback callback, + void * param, + CHATBool blocking); +/********* +** KEYS ** +*********/ +// Sets global key/values for the local user. +// Set a value to NULL or "" to clear that key. +/////////////////////////////////////////////// +void chatSetGlobalKeysA(CHAT chat, + int num, + const char ** keys, + const char ** values); + +// Gets global key/values for a user or users. +// To get the global key/values for one user, pass in that +// user's nick as the target. To get the global key/values +// for every user in a channel, use the channel name as the target. +/////////////////////////////////////////////////////////////////// +void chatGetGlobalKeysA(CHAT chat, + const char * target, + int num, + const char ** keys, + chatGetGlobalKeysCallback callback, + void * param, + CHATBool blocking); + +// Sets channel key/values. +// If user is NULL or "", the keys will be set on the channel. +// Otherwise, they will be set on the user, +// Only ops can set channel keys on other users. +// Set a value to NULL or "" to clear that key. +////////////////////////////////////////////////////////////// +void chatSetChannelKeysA(CHAT chat, + const char * channel, + const char * user, + int num, + const char ** keys, + const char ** values); + +// Gets channel key/values for a user or users, or for a channel. +// To get the channel key/values for every user in +// a channel, pass in "*" as the user. To get the keys for a channel, +// pass in NULL or "". +////////////////////////////////////////////////////////////////////// +void chatGetChannelKeysA(CHAT chat, + const char * channel, + const char * user, + int num, + const char ** keys, + chatGetChannelKeysCallback callback, + void * param, + CHATBool blocking); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// This ASCII versions must be available even when GSI_UNICODE is defined +CHATBool chatGetBasicUserInfoNoWaitA(CHAT chat, + const char * nick, + const char ** user, + const char ** address); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} // extern "C" +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // _CHATASCII_H_ diff --git a/code/gamespy/Chat/chatCallbacks.c b/code/gamespy/Chat/chatCallbacks.c new file mode 100644 index 00000000..87be45a2 --- /dev/null +++ b/code/gamespy/Chat/chatCallbacks.c @@ -0,0 +1,1654 @@ +/* +GameSpy Chat SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include "chatMain.h" +#include "chatCallbacks.h" +#include "chatChannel.h" + +#if defined(_WIN32) +// warning about casting void* to function* +#pragma warning(disable:4055) +#endif + +/************ +** DEFINES ** +************/ +#define ASSERT_DATA(data) assert(data != NULL); assert(data->type >= 0); assert(data->type < CALLBACK_NUM); assert(data->callback != NULL); assert(data->callbackParams != NULL); assert(data->ID >= 0); +#define RAW callbackParams->raw +#define REASON callbackParams->reason +#define USER callbackParams->user +#define MESSAGE callbackParams->message +#define TYPE callbackParams->type +#define CHANNEL callbackParams->channel +#define KICKER callbackParams->kicker +#define KICKEE callbackParams->kickee +#define TOPIC callbackParams->topic +#define MODE callbackParams->mode +#define SUCCESS callbackParams->success +#define INDEX callbackParams->index +#define NUM_USERS callbackParams->numUsers +#define NUM_CHANNELS callbackParams->numChannels +#define CHANNELS callbackParams->channels +#define TOPICS callbackParams->topics +#define ENABLED callbackParams->enabled +#define PASSWORD callbackParams->password +#define USERS callbackParams->users +#define MODES callbackParams->modes +#define ADDRESS callbackParams->address +#define WHY callbackParams->why +#define OLD_NICK callbackParams->oldNick +#define NEW_NICK callbackParams->newNick +#define NUM_BANS callbackParams->numBans +#define BANS callbackParams->bans +#define NICK callbackParams->nick +#define NAME callbackParams->name +#define NUM callbackParams->num +#define KEY callbackParams->key +#define KEYS callbackParams->keys +#define VALUE callbackParams->value +#define VALUES callbackParams->values +#define RESULT callbackParams->result +#define NUM_SUGGESTED_NICKS callbackParams->numSuggestedNicks +#define SUGGESTED_NICKS callbackParams->suggestedNicks +#define COPY(param) if(srcParams->param != NULL)\ + {\ + destParams->param = goastrdup(srcParams->param);\ + if(destParams->param == NULL)\ + {\ + gsifree(destParams);\ + gsifree(data.channel);\ + /*ERRCON*/ return CHATFalse;\ + }\ + } else { destParams->param = srcParams->param; } // remove dangling "if" danger +#define COPY_MODE() if(srcParams->mode != NULL)\ + {\ + destParams->mode = (CHATChannelMode *)gsimalloc(sizeof(CHATChannelMode));\ + if(destParams->mode == NULL)\ + {\ + gsifree(destParams);\ + gsifree(data.channel);\ + /*ERRCON*/ return CHATFalse;\ + }\ + memcpy(destParams->mode, srcParams->mode, sizeof(CHATChannelMode));\ + } else {} // remove dangling "if" danger +#define COPY_STR_ARRAY(array, num) assert(srcParams->num >= 0);\ + if(!srcParams->array)\ + destParams->array = NULL;\ + else\ + {\ + destParams->array = (char **)gsimalloc(sizeof(char *) * srcParams->num);\ + if(destParams->array == NULL)\ + {\ + gsifree(destParams);\ + gsifree(data.channel);\ + /*ERRCON*/ return CHATFalse;\ + }\ + for(i = 0 ; i < srcParams->num ; i++)\ + {\ + if(srcParams->array[i] == NULL)\ + destParams->array[i] = NULL;\ + else\ + {\ + destParams->array[i] = goastrdup(srcParams->array[i]);\ + if(destParams->array[i] == NULL)\ + {\ + for(i-- ; i >= 0 ; i--)\ + gsifree(destParams->array[i]);\ + gsifree(destParams->array);\ + gsifree(destParams);\ + gsifree(data.channel);\ + return CHATFalse;\ + }\ + }\ + }\ + } +#define COPY_INT_ARRAY(array, num) assert(srcParams->num >= 0);\ + if(srcParams->num > 0)\ + {\ + assert(srcParams->array != NULL);\ + len = (int)(sizeof(int) * srcParams->num);\ + destParams->array = (int *)gsimalloc((unsigned int)len);\ + if(destParams->array == NULL)\ + {\ + gsifree(destParams);\ + gsifree(data.channel);\ + /*ERRCON*/ return CHATFalse;\ + }\ + memcpy(destParams->array, srcParams->array, (unsigned int)len);\ + } else {} // remove dangling "if" danger + +#define FREE_STRING_ARRAY(array, num) if (num > 0 && array != NULL)\ + {\ + int i = 0;\ + for (; i < num; i++)\ + gsifree(array[i]);\ + gsifree(array);\ + } else {} // remove dangling "if" danger +/********** +** TYPES ** +**********/ +typedef struct ciCallbackData +{ + int type; + void * callback; + void * callbackParams; + void * param; + int ID; + char * channel; +} ciCallbackData; + +/************** +** FUNCTIONS ** +**************/ +static void ciCallbacksArrayElementFreeFn(void * elem) +{ + ciCallbackData * data = (ciCallbackData *)elem; + GS_ASSERT(data != NULL); + if (data->channel) + gsifree(data->channel); +} + +static void ciFreeCallbackData(ciCallbackData * data) +{ + ASSERT_DATA(data); + + // Find which type of callback it is. + ///////////////////////////////////// + switch(data->type) + { + case CALLBACK_RAW: + { + ciCallbackRawParams * callbackParams = (ciCallbackRawParams *)data->callbackParams; + gsifree(RAW); + break; + } + + case CALLBACK_DISCONNECTED: + { + ciCallbackDisconnectedParams * callbackParams = (ciCallbackDisconnectedParams *)data->callbackParams; + gsifree(REASON); + break; + } + + case CALLBACK_PRIVATE_MESSAGE: + { + ciCallbackPrivateMessageParams * callbackParams = (ciCallbackPrivateMessageParams *)data->callbackParams; + gsifree(USER); + gsifree(MESSAGE); + break; + } + + case CALLBACK_INVITED: + { + ciCallbackInvitedParams * callbackParams = (ciCallbackInvitedParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(USER); + break; + } + + case CALLBACK_CHANNEL_MESSAGE: + { + ciCallbackChannelMessageParams * callbackParams = (ciCallbackChannelMessageParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(USER); + gsifree(MESSAGE); + break; + } + + case CALLBACK_KICKED: + { + ciCallbackKickedParams * callbackParams = (ciCallbackKickedParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(USER); + gsifree(REASON); + break; + } + + case CALLBACK_USER_JOINED: + { + ciCallbackUserJoinedParams * callbackParams = (ciCallbackUserJoinedParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(USER); + break; + } + + case CALLBACK_USER_PARTED: + { + ciCallbackUserPartedParams * callbackParams = (ciCallbackUserPartedParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(USER); + gsifree(REASON); + gsifree(KICKER); + break; + } + + case CALLBACK_USER_CHANGED_NICK: + { + ciCallbackUserChangedNickParams * callbackParams = (ciCallbackUserChangedNickParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(OLD_NICK); + gsifree(NEW_NICK); + break; + } + + case CALLBACK_TOPIC_CHANGED: + { + ciCallbackTopicChangedParams * callbackParams = (ciCallbackTopicChangedParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(TOPIC); + break; + } + + case CALLBACK_CHANNEL_MODE_CHANGED: + { + ciCallbackChannelModeChangedParams * callbackParams = (ciCallbackChannelModeChangedParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(MODE); + break; + } + + case CALLBACK_USER_MODE_CHANGED: + { + ciCallbackUserModeChangedParams * callbackParams = (ciCallbackUserModeChangedParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(USER); + break; + } + + case CALLBACK_USER_LIST_UPDATED: + { + ciCallbackUserListUpdatedParams * callbackParams = (ciCallbackUserListUpdatedParams *)data->callbackParams; + gsifree(CHANNEL); + break; + } + + case CALLBACK_ENUM_CHANNELS_EACH: + { + ciCallbackEnumChannelsEachParams * callbackParams = (ciCallbackEnumChannelsEachParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(TOPIC); + break; + } + + case CALLBACK_ENUM_CHANNELS_ALL: + { + int i; + ciCallbackEnumChannelsAllParams * callbackParams = (ciCallbackEnumChannelsAllParams *)data->callbackParams; + for(i = 0 ; i < NUM_CHANNELS ; i++) + { + gsifree(CHANNELS[i]); + gsifree(TOPICS[i]); + } + gsifree(CHANNELS); + gsifree(TOPICS); + gsifree(NUM_USERS); + break; + } + + case CALLBACK_ENTER_CHANNEL: + { + ciCallbackEnterChannelParams * callbackParams = (ciCallbackEnterChannelParams *)data->callbackParams; + gsifree(CHANNEL); + break; + } + + case CALLBACK_GET_CHANNEL_TOPIC: + { + ciCallbackGetChannelTopicParams * callbackParams = (ciCallbackGetChannelTopicParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(TOPIC); + break; + } + + case CALLBACK_GET_CHANNEL_MODE: + { + ciCallbackGetChannelModeParams * callbackParams = (ciCallbackGetChannelModeParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(MODE); + break; + } + + case CALLBACK_GET_UDPRELAY: + { + ciCallbackGetUdpRelayParams * callbackParams = (ciCallbackGetUdpRelayParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(callbackParams->udpIp); + break; + } + + case CALLBACK_GET_CHANNEL_PASSWORD: + { + ciCallbackGetChannelPasswordParams * callbackParams = (ciCallbackGetChannelPasswordParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(PASSWORD); + break; + } + + case CALLBACK_ENUM_USERS: + { + int i; + ciCallbackEnumUsersParams * callbackParams = (ciCallbackEnumUsersParams *)data->callbackParams; + gsifree(CHANNEL); + for(i = 0 ; i < NUM_USERS ; i++) + gsifree(USERS[i]); + gsifree(USERS); + gsifree(MODES); + break; + } + + case CALLBACK_GET_USER_INFO: + { + int i; + ciCallbackGetUserInfoParams * callbackParams = (ciCallbackGetUserInfoParams *)data->callbackParams; + gsifree(NICK); + gsifree(USER); + gsifree(NAME); + gsifree(ADDRESS); + for(i = 0 ; i < NUM_CHANNELS ; i++) + gsifree(CHANNELS[i]); + gsifree(CHANNELS); + break; + } + + case CALLBACK_GET_BASIC_USER_INFO: + { + ciCallbackGetBasicUserInfoParams * callbackParams = (ciCallbackGetBasicUserInfoParams *)data->callbackParams; + gsifree(NICK); + gsifree(USER); + gsifree(ADDRESS); + break; + } + + case CALLBACK_GET_CHANNEL_BASIC_USER_INFO: + { + ciCallbackGetChannelBasicUserInfoParams * callbackParams = (ciCallbackGetChannelBasicUserInfoParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(NICK); + gsifree(USER); + gsifree(ADDRESS); + break; + } + + case CALLBACK_GET_USER_MODE: + { + ciCallbackGetUserModeParams * callbackParams = (ciCallbackGetUserModeParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(USER); + break; + } + + case CALLBACK_ENUM_CHANNEL_BANS: + { + int i; + ciCallbackEnumChannelBansParams * callbackParams = (ciCallbackEnumChannelBansParams *)data->callbackParams; + gsifree(CHANNEL); + for(i = 0 ; i < NUM_BANS ; i++) + gsifree(BANS[i]); + gsifree(BANS); + break; + } + + case CALLBACK_NICK_ERROR: + { + int i; + ciCallbackNickErrorParams * callbackParams = (ciCallbackNickErrorParams *)data->callbackParams; + gsifree(NICK); + for(i = 0 ; i < NUM_SUGGESTED_NICKS; i++) + gsifree(SUGGESTED_NICKS[i]); + gsifree(SUGGESTED_NICKS); + break; + } + + case CALLBACK_CHANGE_NICK: + { + ciCallbackChangeNickParams * callbackParams = (ciCallbackChangeNickParams *)data->callbackParams; + gsifree(OLD_NICK); + gsifree(NEW_NICK); + break; + } + + case CALLBACK_NEW_USER_LIST: + { + int i; + ciCallbackNewUserListParams * callbackParams = (ciCallbackNewUserListParams *)data->callbackParams; + gsifree(CHANNEL); + for(i = 0 ; i < NUM_USERS ; i++) + gsifree(USERS[i]); + gsifree(USERS); + gsifree(MODES); + break; + } + + case CALLBACK_BROADCAST_KEY_CHANGED: + { + ciCallbackBroadcastKeyChangedParams * callbackParams = (ciCallbackBroadcastKeyChangedParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(USER); + gsifree(KEY); + gsifree(VALUE); + break; + } + + case CALLBACK_GET_GLOBAL_KEYS: + { + int i; + ciCallbackGetGlobalKeysParams * callbackParams = (ciCallbackGetGlobalKeysParams *)data->callbackParams; + gsifree(USER); + for(i = 0 ; i < NUM ; i++) + { + gsifree(KEYS[i]); + if(VALUES) + gsifree(VALUES[i]); + } + gsifree(KEYS); + gsifree(VALUES); + break; + } + + case CALLBACK_GET_CHANNEL_KEYS: + { + int i; + ciCallbackGetChannelKeysParams * callbackParams = (ciCallbackGetChannelKeysParams *)data->callbackParams; + gsifree(CHANNEL); + gsifree(USER); + for(i = 0 ; i < NUM ; i++) + { + gsifree(KEYS[i]); + if(VALUES) + gsifree(VALUES[i]); + } + gsifree(KEYS); + gsifree(VALUES); + break; + } + + case CALLBACK_AUTHENTICATE_CDKEY: + { + ciCallbackAuthenticateCDKeyParams * callbackParams = (ciCallbackAuthenticateCDKeyParams *)data->callbackParams; + gsifree(MESSAGE); + break; + } + + default: + // The type for this callback is messed up. + /////////////////////////////////////////// + assert(0); + } + + // gsifree the params structure. + ///////////////////////////// + gsifree(data->callbackParams); +} + +CHATBool ciInitCallbacks(ciConnection * connection) +{ + // Setup the darray. + //////////////////// + connection->callbackList = ArrayNew(sizeof(ciCallbackData), 128, ciCallbacksArrayElementFreeFn); + if(connection->callbackList == NULL) + return CHATFalse; + + return CHATTrue; +} + +void ciCleanupCallbacks(CHAT chat) +{ + CONNECTION; + + // Cleanup. + /////////// + if(connection->callbackList != NULL) + { + ciCallbackData * data; + int len; + int i; + + // Get the number of callbacks. + /////////////////////////////// + len = ArrayLength(connection->callbackList); + + // gsifree the data. + ///////////////// + for(i = 0 ; i < len ; i++) + { + data = (ciCallbackData *)ArrayNth(connection->callbackList, i); + ASSERT_DATA(data); + + // gsifree the data. + ///////////////// + ciFreeCallbackData(data); + } + + // gsifree the list. + ///////////////// + ArrayFree(connection->callbackList); + } +} + +CHATBool ciAddCallback_(CHAT chat, int type, void * callback, void * callbackParams, void * param, int ID, const char * channel, size_t callbackParamsSize) +{ + ciCallbackData data; + int len; + int i; + CONNECTION; + + assert(type >= 0); + assert(type < CALLBACK_NUM); + assert(connection->callbackList != NULL); + assert(callback != NULL); + assert(callbackParams != NULL); + assert(callbackParamsSize > 0); + assert(ID >= 0); +#ifdef _DEBUG + if(channel != NULL) + assert(channel[0] != '\0'); +#endif + + // Setup the data. + ////////////////// + memset(&data, 0, sizeof(ciCallbackData)); + data.type = type; + data.callback = callback; +#ifndef _DEBUG + data.callbackParams = gsimalloc(callbackParamsSize); +#else + data.callbackParams = gsimalloc(callbackParamsSize + 64); + memset(data.callbackParams, 0xC4, callbackParamsSize + 64); +#endif + if(data.callbackParams == NULL) + return CHATFalse; //ERRCON + memcpy(data.callbackParams, callbackParams, callbackParamsSize); + data.param = param; + data.ID = ID; + if(channel == NULL) + data.channel = NULL; + else + { + len = (int)(strlen(channel) + 1); + data.channel = (char *)gsimalloc((unsigned int)len); + if(data.channel == NULL) + { + gsifree(data.callbackParams); + return CHATFalse; //ERRCON + } + memcpy(data.channel, channel, (unsigned int)len); + } + + // Find which type of callback it is. + ///////////////////////////////////// + switch(data.type) + { + case CALLBACK_RAW: + { + ciCallbackRawParams * destParams = (ciCallbackRawParams *)data.callbackParams; + ciCallbackRawParams * srcParams = (ciCallbackRawParams *)callbackParams; + COPY(raw); + break; + } + + case CALLBACK_DISCONNECTED: + { + ciCallbackDisconnectedParams * destParams = (ciCallbackDisconnectedParams *)data.callbackParams; + ciCallbackDisconnectedParams * srcParams = (ciCallbackDisconnectedParams *)callbackParams; + COPY(reason); + break; + } + + case CALLBACK_PRIVATE_MESSAGE: + { + ciCallbackPrivateMessageParams * destParams = (ciCallbackPrivateMessageParams *)data.callbackParams; + ciCallbackPrivateMessageParams * srcParams = (ciCallbackPrivateMessageParams *)callbackParams; + COPY(user); + COPY(message); + break; + } + + case CALLBACK_INVITED: + { + ciCallbackInvitedParams * destParams = (ciCallbackInvitedParams *)data.callbackParams; + ciCallbackInvitedParams * srcParams = (ciCallbackInvitedParams *)callbackParams; + COPY(channel); + COPY(user); + break; + } + + case CALLBACK_CHANNEL_MESSAGE: + { + ciCallbackChannelMessageParams * destParams = (ciCallbackChannelMessageParams *)data.callbackParams; + ciCallbackChannelMessageParams * srcParams = (ciCallbackChannelMessageParams *)callbackParams; + COPY(channel); + COPY(user); + COPY(message); + break; + } + + case CALLBACK_KICKED: + { + ciCallbackKickedParams * destParams = (ciCallbackKickedParams *)data.callbackParams; + ciCallbackKickedParams * srcParams = (ciCallbackKickedParams *)callbackParams; + COPY(channel); + COPY(user); + COPY(reason); + break; + } + + case CALLBACK_USER_JOINED: + { + ciCallbackUserJoinedParams * destParams = (ciCallbackUserJoinedParams *)data.callbackParams; + ciCallbackUserJoinedParams * srcParams = (ciCallbackUserJoinedParams *)callbackParams; + COPY(channel); + COPY(user); + break; + } + + case CALLBACK_USER_PARTED: + { + ciCallbackUserPartedParams * destParams = (ciCallbackUserPartedParams *)data.callbackParams; + ciCallbackUserPartedParams * srcParams = (ciCallbackUserPartedParams *)callbackParams; + COPY(channel); + COPY(user); + COPY(reason); + COPY(kicker); + break; + } + + case CALLBACK_USER_CHANGED_NICK: + { + ciCallbackUserChangedNickParams * destParams = (ciCallbackUserChangedNickParams *)data.callbackParams; + ciCallbackUserChangedNickParams * srcParams = (ciCallbackUserChangedNickParams *)callbackParams; + COPY(channel); + COPY(oldNick); + COPY(newNick); + break; + } + + case CALLBACK_TOPIC_CHANGED: + { + ciCallbackTopicChangedParams * destParams = (ciCallbackTopicChangedParams *)data.callbackParams; + ciCallbackTopicChangedParams * srcParams = (ciCallbackTopicChangedParams *)callbackParams; + COPY(channel); + COPY(topic); + break; + } + + case CALLBACK_CHANNEL_MODE_CHANGED: + { + ciCallbackChannelModeChangedParams * destParams = (ciCallbackChannelModeChangedParams *)data.callbackParams; + ciCallbackChannelModeChangedParams * srcParams = (ciCallbackChannelModeChangedParams *)callbackParams; + COPY(channel); + COPY_MODE(); + break; + } + + case CALLBACK_USER_MODE_CHANGED: + { + ciCallbackUserModeChangedParams * destParams = (ciCallbackUserModeChangedParams *)data.callbackParams; + ciCallbackUserModeChangedParams * srcParams = (ciCallbackUserModeChangedParams *)callbackParams; + COPY(channel); + COPY(user); + break; + } + + case CALLBACK_USER_LIST_UPDATED: + { + ciCallbackUserListUpdatedParams * destParams = (ciCallbackUserListUpdatedParams *)data.callbackParams; + ciCallbackUserListUpdatedParams * srcParams = (ciCallbackUserListUpdatedParams *)callbackParams; + COPY(channel); + break; + } + + case CALLBACK_ENUM_CHANNELS_EACH: + { + ciCallbackEnumChannelsEachParams * destParams = (ciCallbackEnumChannelsEachParams *)data.callbackParams; + ciCallbackEnumChannelsEachParams * srcParams = (ciCallbackEnumChannelsEachParams *)callbackParams; + COPY(channel); + COPY(topic); + break; + } + + case CALLBACK_ENUM_CHANNELS_ALL: + { + ciCallbackEnumChannelsAllParams * destParams = (ciCallbackEnumChannelsAllParams *)data.callbackParams; + ciCallbackEnumChannelsAllParams * srcParams = (ciCallbackEnumChannelsAllParams *)callbackParams; + COPY_STR_ARRAY(channels, numChannels); + COPY_STR_ARRAY(topics, numChannels); + COPY_INT_ARRAY(numUsers, numChannels); + break; + } + + case CALLBACK_ENTER_CHANNEL: + { + ciCallbackEnterChannelParams * destParams = (ciCallbackEnterChannelParams *)data.callbackParams; + ciCallbackEnterChannelParams * srcParams = (ciCallbackEnterChannelParams *)callbackParams; + COPY(channel); + break; + } + + case CALLBACK_GET_CHANNEL_TOPIC: + { + ciCallbackGetChannelTopicParams * destParams = (ciCallbackGetChannelTopicParams *)data.callbackParams; + ciCallbackGetChannelTopicParams * srcParams = (ciCallbackGetChannelTopicParams *)callbackParams; + COPY(channel); + COPY(topic); + break; + } + + case CALLBACK_GET_CHANNEL_MODE: + { + ciCallbackGetChannelModeParams * destParams = (ciCallbackGetChannelModeParams *)data.callbackParams; + ciCallbackGetChannelModeParams * srcParams = (ciCallbackGetChannelModeParams *)callbackParams; + COPY(channel); + COPY_MODE(); + break; + } + + case CALLBACK_GET_UDPRELAY: + { + ciCallbackGetUdpRelayParams * destParams = (ciCallbackGetUdpRelayParams *)data.callbackParams; + ciCallbackGetUdpRelayParams * srcParams = (ciCallbackGetUdpRelayParams *)callbackParams; + COPY(channel); + COPY(udpIp); + break; + } + + case CALLBACK_GET_CHANNEL_PASSWORD: + { + ciCallbackGetChannelPasswordParams * destParams = (ciCallbackGetChannelPasswordParams *)data.callbackParams; + ciCallbackGetChannelPasswordParams * srcParams = (ciCallbackGetChannelPasswordParams *)callbackParams; + COPY(channel); + COPY(password); + break; + } + + case CALLBACK_ENUM_USERS: + { + ciCallbackEnumUsersParams * destParams = (ciCallbackEnumUsersParams *)data.callbackParams; + ciCallbackEnumUsersParams * srcParams = (ciCallbackEnumUsersParams *)callbackParams; + COPY(channel); + COPY_STR_ARRAY(users, numUsers); + COPY_INT_ARRAY(modes, numUsers); + break; + } + + case CALLBACK_GET_USER_INFO: + { + ciCallbackGetUserInfoParams * destParams = (ciCallbackGetUserInfoParams *)data.callbackParams; + ciCallbackGetUserInfoParams * srcParams = (ciCallbackGetUserInfoParams *)callbackParams; + COPY(nick); + COPY(user); + COPY(name); + COPY(address); + COPY_STR_ARRAY(channels, numChannels); + break; + } + + case CALLBACK_GET_BASIC_USER_INFO: + { + ciCallbackGetBasicUserInfoParams * destParams = (ciCallbackGetBasicUserInfoParams *)data.callbackParams; + ciCallbackGetBasicUserInfoParams * srcParams = (ciCallbackGetBasicUserInfoParams *)callbackParams; + COPY(nick); + COPY(user); + COPY(address); + break; + } + + case CALLBACK_GET_CHANNEL_BASIC_USER_INFO: + { + ciCallbackGetChannelBasicUserInfoParams * destParams = (ciCallbackGetChannelBasicUserInfoParams *)data.callbackParams; + ciCallbackGetChannelBasicUserInfoParams * srcParams = (ciCallbackGetChannelBasicUserInfoParams *)callbackParams; + COPY(channel); + COPY(nick); + COPY(user); + COPY(address); + break; + } + + case CALLBACK_GET_USER_MODE: + { + ciCallbackGetUserModeParams * destParams = (ciCallbackGetUserModeParams *)data.callbackParams; + ciCallbackGetUserModeParams * srcParams = (ciCallbackGetUserModeParams *)callbackParams; + COPY(channel); + COPY(user); + break; + } + + case CALLBACK_ENUM_CHANNEL_BANS: + { + ciCallbackEnumChannelBansParams * destParams = (ciCallbackEnumChannelBansParams *)data.callbackParams; + ciCallbackEnumChannelBansParams * srcParams = (ciCallbackEnumChannelBansParams *)callbackParams; + COPY(channel); + COPY_STR_ARRAY(bans, numBans); + break; + } + + case CALLBACK_NICK_ERROR: + { + ciCallbackNickErrorParams * destParams = (ciCallbackNickErrorParams *)data.callbackParams; + ciCallbackNickErrorParams * srcParams = (ciCallbackNickErrorParams *)callbackParams; + COPY(nick); + COPY_STR_ARRAY(suggestedNicks, numSuggestedNicks); + break; + } + + case CALLBACK_CHANGE_NICK: + { + ciCallbackChangeNickParams * destParams = (ciCallbackChangeNickParams *)data.callbackParams; + ciCallbackChangeNickParams * srcParams = (ciCallbackChangeNickParams *)callbackParams; + COPY(oldNick); + COPY(newNick); + break; + } + + case CALLBACK_NEW_USER_LIST: + { + ciCallbackNewUserListParams * destParams = (ciCallbackNewUserListParams *)data.callbackParams; + ciCallbackNewUserListParams * srcParams = (ciCallbackNewUserListParams *)callbackParams; + COPY(channel); + COPY_STR_ARRAY(users, numUsers); + COPY_INT_ARRAY(modes, numUsers); + break; + } + + case CALLBACK_BROADCAST_KEY_CHANGED: + { + ciCallbackBroadcastKeyChangedParams * destParams = (ciCallbackBroadcastKeyChangedParams *)data.callbackParams; + ciCallbackBroadcastKeyChangedParams * srcParams = (ciCallbackBroadcastKeyChangedParams *)callbackParams; + COPY(channel); + COPY(user); + COPY(key); + COPY(value); + break; + } + + case CALLBACK_GET_GLOBAL_KEYS: + { + ciCallbackGetGlobalKeysParams * destParams = (ciCallbackGetGlobalKeysParams *)data.callbackParams; + ciCallbackGetGlobalKeysParams * srcParams = (ciCallbackGetGlobalKeysParams *)callbackParams; + COPY(user); + COPY_STR_ARRAY(keys, num); + COPY_STR_ARRAY(values, num); + break; + } + + case CALLBACK_GET_CHANNEL_KEYS: + { + ciCallbackGetChannelKeysParams * destParams = (ciCallbackGetChannelKeysParams *)data.callbackParams; + ciCallbackGetChannelKeysParams * srcParams = (ciCallbackGetChannelKeysParams *)callbackParams; + COPY(channel); + COPY(user); + COPY_STR_ARRAY(keys, num); + COPY_STR_ARRAY(values, num); + break; + } + + case CALLBACK_AUTHENTICATE_CDKEY: + { + ciCallbackAuthenticateCDKeyParams * destParams = (ciCallbackAuthenticateCDKeyParams *)data.callbackParams; + ciCallbackAuthenticateCDKeyParams * srcParams = (ciCallbackAuthenticateCDKeyParams *)callbackParams; + COPY(message); + break; + } + + default: + // The type for this callback is messed up. + /////////////////////////////////////////// + assert(0); + } + + // Add it to the array. + /////////////////////// + ArrayAppend(connection->callbackList, &data); + + return CHATTrue; +} + +static void ciCallCallback(CHAT chat, ciCallbackData * data) +{ + void * param; + //CONNECTION; + + ASSERT_DATA(data); + + // Cache the param. + /////////////////// + param = data->param; + + // Find which type of callback it is. + ///////////////////////////////////// + switch(data->type) + { + case CALLBACK_RAW: + { + ciCallbackRawParams * callbackParams = (ciCallbackRawParams *)data->callbackParams; + chatRaw callback = (chatRaw)data->callback; +#ifndef GSI_UNICODE + callback(chat, RAW, param); + break; +#else + // UNICODE: Copy all the params, call the callback, free the params + unsigned short* raw_W = UTF8ToUCS2StringAlloc(RAW); + callback(chat, raw_W, param); + gsifree(raw_W); + break; +#endif + } + + case CALLBACK_DISCONNECTED: + { + ciCallbackDisconnectedParams * callbackParams = (ciCallbackDisconnectedParams *)data->callbackParams; + chatDisconnected callback = (chatDisconnected)data->callback; +#ifndef GSI_UNICODE + callback(chat, REASON, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* reason_W = UTF8ToUCS2StringAlloc(REASON); + callback(chat, reason_W, param); + gsifree(reason_W); + break; +#endif + } + + case CALLBACK_PRIVATE_MESSAGE: + { + ciCallbackPrivateMessageParams * callbackParams = (ciCallbackPrivateMessageParams *)data->callbackParams; + chatPrivateMessage callback = (chatPrivateMessage)data->callback; +#ifndef GSI_UNICODE + callback(chat, USER, MESSAGE, TYPE, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* user_W = UTF8ToUCS2StringAlloc(USER); + unsigned short* message_W = UTF8ToUCS2StringAlloc(MESSAGE); + callback(chat, user_W, message_W, TYPE, param); + gsifree(user_W); + gsifree(message_W); + break; +#endif + } + + case CALLBACK_INVITED: + { + ciCallbackInvitedParams * callbackParams = (ciCallbackInvitedParams *)data->callbackParams; + chatInvited callback = (chatInvited)data->callback; +#ifndef GSI_UNICODE + callback(chat, CHANNEL, USER, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short* user_W = UTF8ToUCS2StringAlloc(USER); + callback(chat, channel_W, user_W, param); + gsifree(channel_W); + gsifree(user_W); + break; +#endif + } + + case CALLBACK_CHANNEL_MESSAGE: + { + ciCallbackChannelMessageParams * callbackParams = (ciCallbackChannelMessageParams *)data->callbackParams; + chatChannelMessage callback = (chatChannelMessage)data->callback; +#ifndef GSI_UNICODE + callback(chat, CHANNEL, USER, MESSAGE, TYPE, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short* user_W = UTF8ToUCS2StringAlloc(USER); + unsigned short* message_W = UTF8ToUCS2StringAlloc(MESSAGE); + callback(chat, channel_W, user_W, message_W, TYPE, param); + gsifree(channel_W); + gsifree(user_W); + gsifree(message_W); + break; +#endif + } + + case CALLBACK_KICKED: + { + ciCallbackKickedParams * callbackParams = (ciCallbackKickedParams *)data->callbackParams; + chatKicked callback = (chatKicked)data->callback; +#ifndef GSI_UNICODE + callback(chat, CHANNEL, USER, REASON, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short* user_W = UTF8ToUCS2StringAlloc(USER); + unsigned short* reason_W = UTF8ToUCS2StringAlloc(REASON); + callback(chat, channel_W, user_W, reason_W, param); + gsifree(channel_W); + gsifree(user_W); + gsifree(reason_W); + break; +#endif + } + + case CALLBACK_USER_JOINED: + { + ciCallbackUserJoinedParams * callbackParams = (ciCallbackUserJoinedParams *)data->callbackParams; + chatUserJoined callback = (chatUserJoined)data->callback; +#ifndef GSI_UNICODE + callback(chat, CHANNEL, USER, MODE, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short* user_W = UTF8ToUCS2StringAlloc(USER); + callback(chat, channel_W, user_W, MODE, param); + gsifree(channel_W); + gsifree(user_W); + break; +#endif + + } + + case CALLBACK_USER_PARTED: + { + ciCallbackUserPartedParams * callbackParams = (ciCallbackUserPartedParams *)data->callbackParams; + chatUserParted callback = (chatUserParted)data->callback; +#ifndef GSI_UNICODE + callback(chat, CHANNEL, USER, WHY, REASON, KICKER, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short* user_W = UTF8ToUCS2StringAlloc(USER); + unsigned short* reason_W = UTF8ToUCS2StringAlloc(REASON); + unsigned short* kicker_W = UTF8ToUCS2StringAlloc(KICKER); + callback(chat, channel_W, user_W, WHY, reason_W, kicker_W, param); + gsifree(channel_W); + gsifree(user_W); + gsifree(reason_W); + gsifree(kicker_W); + break; +#endif + } + + case CALLBACK_USER_CHANGED_NICK: + { + ciCallbackUserChangedNickParams * callbackParams = (ciCallbackUserChangedNickParams *)data->callbackParams; + chatUserChangedNick callback = (chatUserChangedNick)data->callback; +#ifndef GSI_UNICODE + callback(chat, CHANNEL, OLD_NICK, NEW_NICK, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short* oldnick_W = UTF8ToUCS2StringAlloc(OLD_NICK); + unsigned short* newnick_W = UTF8ToUCS2StringAlloc(NEW_NICK); + callback(chat, channel_W, oldnick_W, newnick_W, param); + gsifree(channel_W); + gsifree(oldnick_W); + gsifree(newnick_W); + break; +#endif + } + + case CALLBACK_TOPIC_CHANGED: + { + ciCallbackTopicChangedParams * callbackParams = (ciCallbackTopicChangedParams *)data->callbackParams; + chatTopicChanged callback = (chatTopicChanged)data->callback; +#ifndef GSI_UNICODE + callback(chat, CHANNEL, TOPIC, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short* topic_W = UTF8ToUCS2StringAlloc(TOPIC); + callback(chat, channel_W, topic_W, param); + gsifree(channel_W); + gsifree(topic_W); + break; +#endif + } + + case CALLBACK_CHANNEL_MODE_CHANGED: + { + ciCallbackChannelModeChangedParams * callbackParams = (ciCallbackChannelModeChangedParams *)data->callbackParams; + chatChannelModeChanged callback = (chatChannelModeChanged)data->callback; +#ifndef GSI_UNICODE + callback(chat, CHANNEL, MODE, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + callback(chat, channel_W, MODE, param); + gsifree(channel_W); + break; +#endif + } + + case CALLBACK_USER_MODE_CHANGED: + { + ciCallbackUserModeChangedParams * callbackParams = (ciCallbackUserModeChangedParams *)data->callbackParams; + chatUserModeChanged callback = (chatUserModeChanged)data->callback; +#ifndef GSI_UNICODE + callback(chat, CHANNEL, USER, MODE, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short* user_W = UTF8ToUCS2StringAlloc(USER); + callback(chat, channel_W, user_W, MODE, param); + gsifree(channel_W); + gsifree(user_W); + break; +#endif + } + + case CALLBACK_USER_LIST_UPDATED: + { + ciCallbackUserListUpdatedParams * callbackParams = (ciCallbackUserListUpdatedParams *)data->callbackParams; + chatUserListUpdated callback = (chatUserListUpdated)data->callback; +#ifndef GSI_UNICODE + callback(chat, CHANNEL, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + callback(chat, channel_W, param); + gsifree(channel_W); + break; +#endif + } + + case CALLBACK_ENUM_CHANNELS_EACH: + { + ciCallbackEnumChannelsEachParams * callbackParams = (ciCallbackEnumChannelsEachParams *)data->callbackParams; + chatEnumChannelsCallbackEach callback = (chatEnumChannelsCallbackEach)data->callback; +#ifndef GSI_UNICODE + callback(chat, SUCCESS, INDEX, CHANNEL, TOPIC, NUM_USERS, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short* topic_W = UTF8ToUCS2StringAlloc(TOPIC); + callback(chat, SUCCESS, INDEX, channel_W, topic_W, NUM_USERS, param); + gsifree(channel_W); + gsifree(topic_W); + break; +#endif + } + + case CALLBACK_ENUM_CHANNELS_ALL: + { + ciCallbackEnumChannelsAllParams * callbackParams = (ciCallbackEnumChannelsAllParams *)data->callbackParams; + chatEnumChannelsCallbackAll callback = (chatEnumChannelsCallbackAll)data->callback; +#ifndef GSI_UNICODE + callback(chat, SUCCESS, NUM_CHANNELS, (const char **)CHANNELS, (const char **)TOPICS, NUM_USERS, param); + break; +#else + unsigned short** channels_W = UTF8ToUCS2StringArrayAlloc((const char **)CHANNELS, NUM_CHANNELS); + unsigned short** topics_W = UTF8ToUCS2StringArrayAlloc((const char **)TOPICS, NUM_CHANNELS); + callback(chat, SUCCESS, NUM_CHANNELS, (const unsigned short**)channels_W, (const unsigned short**)topics_W, NUM_USERS, param); + FREE_STRING_ARRAY(channels_W, NUM_CHANNELS); + FREE_STRING_ARRAY(topics_W, NUM_CHANNELS); + break; +#endif + } + + case CALLBACK_ENTER_CHANNEL: + { + ciCallbackEnterChannelParams * callbackParams = (ciCallbackEnterChannelParams *)data->callbackParams; + chatEnterChannelCallback callback = (chatEnterChannelCallback)data->callback; + + // Call this before the callback so funcs called within the callback know. + ////////////////////////////////////////////////////////////////////////// + ciJoinCallbackCalled(chat, CHANNEL); + +#ifndef GSI_UNICODE + callback(chat, SUCCESS, RESULT, CHANNEL, param); + break; +#else + { + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + callback(chat, SUCCESS, RESULT, channel_W, param); + gsifree(channel_W); + break; + } +#endif + } + + case CALLBACK_GET_CHANNEL_TOPIC: + { + ciCallbackGetChannelTopicParams * callbackParams = (ciCallbackGetChannelTopicParams *)data->callbackParams; + chatGetChannelTopicCallback callback = (chatGetChannelTopicCallback)data->callback; +#ifndef GSI_UNICODE + callback(chat, SUCCESS, CHANNEL, TOPIC, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short* topic_W = UTF8ToUCS2StringAlloc(TOPIC); + callback(chat, SUCCESS, channel_W, topic_W, param); + gsifree(channel_W); + gsifree(topic_W); + break; +#endif + } + + case CALLBACK_GET_CHANNEL_MODE: + { + ciCallbackGetChannelModeParams * callbackParams = (ciCallbackGetChannelModeParams *)data->callbackParams; + chatGetChannelModeCallback callback = (chatGetChannelModeCallback)data->callback; +#ifndef GSI_UNICODE + callback(chat, SUCCESS, CHANNEL, MODE, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + callback(chat, SUCCESS, channel_W, MODE, param); + gsifree(channel_W); + break; +#endif + } + + case CALLBACK_GET_UDPRELAY: + { + ciCallbackGetUdpRelayParams * callbackParams = (ciCallbackGetUdpRelayParams *)data->callbackParams; + chatGetUdpRelayCallback callback = (chatGetUdpRelayCallback)data->callback; +#ifndef GSI_UNICODE + callback(chat, CHANNEL, callbackParams->udpIp, callbackParams->udpPort, callbackParams->udpKey, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short* udpIp_W = UTF8ToUCS2StringAlloc(callbackParams->udpIp); + callback(chat, channel_W, udpIp_W, callbackParams->udpPort, callbackParams->udpKey, param); + gsifree(channel_W); + gsifree(udpIp_W); + break; +#endif + } + + case CALLBACK_GET_CHANNEL_PASSWORD: + { + ciCallbackGetChannelPasswordParams * callbackParams = (ciCallbackGetChannelPasswordParams *)data->callbackParams; + chatGetChannelPasswordCallback callback = (chatGetChannelPasswordCallback)data->callback; +#ifndef GSI_UNICODE + callback(chat, SUCCESS, CHANNEL, ENABLED, PASSWORD, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short* password_W = UTF8ToUCS2StringAlloc(PASSWORD); + callback(chat, SUCCESS, channel_W, ENABLED, password_W, param); + gsifree(channel_W); + gsifree(password_W); + break; +#endif + } + + case CALLBACK_ENUM_USERS: + { + ciCallbackEnumUsersParams * callbackParams = (ciCallbackEnumUsersParams *)data->callbackParams; + chatEnumUsersCallback callback = (chatEnumUsersCallback)data->callback; +#ifndef GSI_UNICODE + callback(chat, SUCCESS, CHANNEL, NUM_USERS, (const char **)USERS, MODES, param); + break; +#else + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short** users_W = UTF8ToUCS2StringArrayAlloc((const char **)USERS, NUM_USERS); + callback(chat, SUCCESS, channel_W, NUM_USERS, (const unsigned short**)users_W, MODES, param); + gsifree(channel_W); + FREE_STRING_ARRAY(users_W, NUM_USERS); + break; +#endif + } + + case CALLBACK_GET_USER_INFO: + { + ciCallbackGetUserInfoParams * callbackParams = (ciCallbackGetUserInfoParams *)data->callbackParams; + chatGetUserInfoCallback callback = (chatGetUserInfoCallback)data->callback; +#ifndef GSI_UNICODE + callback(chat, SUCCESS, NICK, USER, NAME, ADDRESS, NUM_CHANNELS, (const char **)CHANNELS, param); + break; +#else + unsigned short* nick_W = UTF8ToUCS2StringAlloc(NICK); + unsigned short* user_W = UTF8ToUCS2StringAlloc(USER); + unsigned short* name_W = UTF8ToUCS2StringAlloc(NAME); + unsigned short* address_W = UTF8ToUCS2StringAlloc(ADDRESS); + unsigned short** channels_W = UTF8ToUCS2StringArrayAlloc((const char **)CHANNELS, NUM_CHANNELS); + callback(chat, SUCCESS, nick_W, user_W, name_W, address_W, NUM_CHANNELS, (const unsigned short**)channels_W, param); + gsifree(nick_W); + gsifree(user_W); + gsifree(name_W); + gsifree(address_W); + FREE_STRING_ARRAY(channels_W, NUM_CHANNELS); + break; +#endif + } + + case CALLBACK_GET_BASIC_USER_INFO: + { + ciCallbackGetBasicUserInfoParams * callbackParams = (ciCallbackGetBasicUserInfoParams *)data->callbackParams; + chatGetBasicUserInfoCallback callback = (chatGetBasicUserInfoCallback)data->callback; +#ifndef GSI_UNICODE + callback(chat, SUCCESS, NICK, USER, ADDRESS, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* nick_W = UTF8ToUCS2StringAlloc(NICK); + unsigned short* user_W = UTF8ToUCS2StringAlloc(USER); + unsigned short* address_W = UTF8ToUCS2StringAlloc(ADDRESS); + callback(chat, SUCCESS, nick_W, user_W, address_W, param); + gsifree(nick_W); + gsifree(user_W); + gsifree(address_W); + break; +#endif + } + + case CALLBACK_GET_CHANNEL_BASIC_USER_INFO: + { + ciCallbackGetChannelBasicUserInfoParams * callbackParams = (ciCallbackGetChannelBasicUserInfoParams *)data->callbackParams; + chatGetChannelBasicUserInfoCallback callback = (chatGetChannelBasicUserInfoCallback)data->callback; +#ifndef GSI_UNICODE + callback(chat, SUCCESS, CHANNEL, NICK, USER, ADDRESS, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short* nick_W = UTF8ToUCS2StringAlloc(NICK); + unsigned short* user_W = UTF8ToUCS2StringAlloc(USER); + unsigned short* address_W = UTF8ToUCS2StringAlloc(ADDRESS); + callback(chat, SUCCESS, channel_W, nick_W, user_W, address_W, param); + gsifree(channel_W); + gsifree(nick_W); + gsifree(user_W); + gsifree(address_W); + break; +#endif + } + + case CALLBACK_GET_USER_MODE: + { + ciCallbackGetUserModeParams * callbackParams = (ciCallbackGetUserModeParams *)data->callbackParams; + chatGetUserModeCallback callback = (chatGetUserModeCallback)data->callback; +#ifndef GSI_UNICODE + callback(chat, SUCCESS, CHANNEL, USER, MODE, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short* user_W = UTF8ToUCS2StringAlloc(USER); + callback(chat, SUCCESS, channel_W, user_W, MODE, param); + gsifree(channel_W); + gsifree(user_W); + break; +#endif + } + + case CALLBACK_ENUM_CHANNEL_BANS: + { + ciCallbackEnumChannelBansParams * callbackParams = (ciCallbackEnumChannelBansParams *)data->callbackParams; + chatEnumChannelBansCallback callback = (chatEnumChannelBansCallback)data->callback; +#ifndef GSI_UNICODE + callback(chat, SUCCESS, CHANNEL, NUM_BANS, (const char **)BANS, param); + break; +#else + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short** bans_W = UTF8ToUCS2StringArrayAlloc((const char **)BANS, NUM_BANS); + callback(chat, SUCCESS, channel_W, NUM_BANS, (const unsigned short**)bans_W, param); + gsifree(channel_W); + FREE_STRING_ARRAY(bans_W, NUM_BANS); + break; +#endif + } + + case CALLBACK_NICK_ERROR: + { + ciCallbackNickErrorParams * callbackParams = (ciCallbackNickErrorParams *)data->callbackParams; + chatNickErrorCallback callback = (chatNickErrorCallback)data->callback; +#ifndef GSI_UNICODE + callback(chat, TYPE, NICK, NUM_SUGGESTED_NICKS, (const char**)SUGGESTED_NICKS, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* nick_W = UTF8ToUCS2StringAlloc(NICK); + unsigned short** suggestedNicks_W = UTF8ToUCS2StringArrayAlloc((const char **)SUGGESTED_NICKS, NUM_SUGGESTED_NICKS); + callback(chat, TYPE, nick_W, NUM_SUGGESTED_NICKS, (const unsigned short**)suggestedNicks_W, param); + gsifree(nick_W); + FREE_STRING_ARRAY(suggestedNicks_W, NUM_SUGGESTED_NICKS); + break; +#endif + } + + case CALLBACK_CHANGE_NICK: + { + ciCallbackChangeNickParams * callbackParams = (ciCallbackChangeNickParams *)data->callbackParams; + chatChangeNickCallback callback = (chatChangeNickCallback)data->callback; +#ifndef GSI_UNICODE + callback(chat, SUCCESS, OLD_NICK, NEW_NICK, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* oldnick_W = UTF8ToUCS2StringAlloc(OLD_NICK); + unsigned short* newnick_W = UTF8ToUCS2StringAlloc(NEW_NICK); + callback(chat, SUCCESS, oldnick_W, newnick_W, param); + gsifree(oldnick_W); + gsifree(newnick_W); + break; +#endif + } + + case CALLBACK_NEW_USER_LIST: + { + ciCallbackNewUserListParams * callbackParams = (ciCallbackNewUserListParams *)data->callbackParams; + chatNewUserList callback = (chatNewUserList)data->callback; +#ifndef GSI_UNICODE + callback(chat, CHANNEL, NUM_USERS, (const char **)USERS, MODES, param); + break; +#else + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short** users_W = UTF8ToUCS2StringArrayAlloc((const char**)USERS, NUM_USERS); + callback(chat, channel_W, NUM_USERS, (const unsigned short**)users_W, MODES, param); + gsifree(channel_W); + FREE_STRING_ARRAY(users_W, NUM_USERS); + break; +#endif + } + + case CALLBACK_BROADCAST_KEY_CHANGED: + { + ciCallbackBroadcastKeyChangedParams * callbackParams = (ciCallbackBroadcastKeyChangedParams *)data->callbackParams; + chatBroadcastKeyChanged callback = (chatBroadcastKeyChanged)data->callback; +#ifndef GSI_UNICODE + callback(chat, CHANNEL, USER, KEY, VALUE, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short* user_W = UTF8ToUCS2StringAlloc(USER); + unsigned short* key_W = UTF8ToUCS2StringAlloc(KEY); + unsigned short* value_W = UTF8ToUCS2StringAlloc(VALUE); + callback(chat, channel_W, user_W, key_W, value_W, param); + gsifree(channel_W); + gsifree(user_W); + gsifree(key_W); + gsifree(value_W); + break; +#endif + } + + case CALLBACK_GET_GLOBAL_KEYS: + { + ciCallbackGetGlobalKeysParams * callbackParams = (ciCallbackGetGlobalKeysParams *)data->callbackParams; + chatGetGlobalKeysCallback callback = (chatGetGlobalKeysCallback)data->callback; +#ifndef GSI_UNICODE + callback(chat, SUCCESS, USER, NUM, (const char **)KEYS, (const char **)VALUES, param); + break; +#else + unsigned short* user_W = UTF8ToUCS2StringAlloc(USER); + unsigned short** keys_W = UTF8ToUCS2StringArrayAlloc((const char**)KEYS, NUM); + unsigned short** values_W = UTF8ToUCS2StringArrayAlloc((const char**)VALUES, NUM); + callback(chat, SUCCESS, user_W, NUM, (const unsigned short**)keys_W, (const unsigned short**)values_W, param); + gsifree(user_W); + FREE_STRING_ARRAY(keys_W, NUM); + FREE_STRING_ARRAY(values_W, NUM); + break; +#endif + } + + case CALLBACK_GET_CHANNEL_KEYS: + { + ciCallbackGetChannelKeysParams * callbackParams = (ciCallbackGetChannelKeysParams *)data->callbackParams; + chatGetChannelKeysCallback callback = (chatGetChannelKeysCallback)data->callback; +#ifndef GSI_UNICODE + callback(chat, SUCCESS, CHANNEL, USER, NUM, (const char **)KEYS, (const char **)VALUES, param); + break; +#else + unsigned short* channel_W = UTF8ToUCS2StringAlloc(CHANNEL); + unsigned short* user_W = UTF8ToUCS2StringAlloc(USER); + unsigned short** keys_W = UTF8ToUCS2StringArrayAlloc((const char**)KEYS, NUM); + unsigned short** values_W = UTF8ToUCS2StringArrayAlloc((const char**)VALUES, NUM); + callback(chat, SUCCESS, channel_W, user_W, NUM, (const unsigned short**)keys_W, (const unsigned short**)values_W, param); + gsifree(channel_W); + gsifree(user_W); + FREE_STRING_ARRAY(keys_W, NUM); + FREE_STRING_ARRAY(values_W, NUM); + break; +#endif + } + + case CALLBACK_AUTHENTICATE_CDKEY: + { + ciCallbackAuthenticateCDKeyParams * callbackParams = (ciCallbackAuthenticateCDKeyParams *)data->callbackParams; + chatAuthenticateCDKeyCallback callback = (chatAuthenticateCDKeyCallback)data->callback; +#ifndef GSI_UNICODE + callback(chat, RESULT, MESSAGE, param); + break; +#else + // Copy all the params, call the callback, free the params + unsigned short* message_W = UTF8ToUCS2StringAlloc(MESSAGE); + callback(chat, RESULT, message_W, param); + gsifree(message_W); + break; +#endif + } + + default: + // The type for this callback is messed up. + /////////////////////////////////////////// + assert(0); + } + + // gsifree the data. + ///////////////// + ciFreeCallbackData(data); +} + +void ciCallCallbacks(CHAT chat, int ID) +{ + ciCallbackData * data; + ciCallbackData dataCopy; + int skip; + CONNECTION; + + // Call the callbacks. + ////////////////////// + for(skip = 0 ; ArrayLength(connection->callbackList) > skip ; ) + { + // Get the callback. + /////////////////// + data = (ciCallbackData *)ArrayNth(connection->callbackList, skip); + ASSERT_DATA(data); + + // Does this depend on a channel we're not in? + ////////////////////////////////////////////// + if((data->channel != NULL) && !ciInChannel(chat, data->channel)) + { + // gsifree the data. + ///////////////// + ciFreeCallbackData(data); + + // Kill it. + /////////// + ArrayDeleteAt(connection->callbackList, skip); + } + else + { + // Check if this callback depends on the join callback having been called. + // Also, if blocking, only call that callback. + ////////////////////////////////////////////////////////////////////////// + if(((data->channel == NULL) || ciWasJoinCallbackCalled(chat, data->channel)) && + ((ID == 0) || (data->ID == ID))) + { + // Copy the data so we can gsifree it before calling the callback. + /////////////////////////////////////////////////////////////// + dataCopy = *data; + + // gsifree it. + /////////// + ArrayDeleteAt(connection->callbackList, skip); + + // Call the callback. + ///////////////////// + ciCallCallback(chat, &dataCopy); + + // Was this the blocking callback? + ////////////////////////////////// + if(ID != 0) + return; + } + else + { + // Increment the skip, because it's still in the array. + /////////////////////////////////////////////////////// + skip++; + } + } + } +} + +static int ciGetCallbackIndexByID(CHAT chat, int ID) +{ + ciCallbackData * data; + int i; + int len; + CONNECTION; + + // Get the array length. + //////////////////////// + len = ArrayLength(connection->callbackList); + + // Loop through the callbacks. + ////////////////////////////// + for(i = 0 ; i < len ; i++) + { + // Get the callback. + //////////////////// + data = (ciCallbackData *)ArrayNth(connection->callbackList, i); + ASSERT_DATA(data); + + // Check for an ID match. + ///////////////////////// + if(data->ID == ID) + return i; + } + + // Didn't find one. + /////////////////// + return -1; +} + +CHATBool ciCheckCallbacksForID(CHAT chat, int ID) +{ + if(ciGetCallbackIndexByID(chat, ID) == -1) + return CHATFalse; + + return CHATTrue; +} diff --git a/code/gamespy/Chat/chatCallbacks.h b/code/gamespy/Chat/chatCallbacks.h new file mode 100644 index 00000000..c84b2855 --- /dev/null +++ b/code/gamespy/Chat/chatCallbacks.h @@ -0,0 +1,329 @@ +/* +GameSpy Chat SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _CHATCALLBACKS_H_ +#define _CHATCALLBACKS_H_ + +/************* +** INCLUDES ** +*************/ +#include "chat.h" +#include "chatMain.h" + +/************ +** DEFINES ** +************/ +enum +{ + CALLBACK_RAW, + CALLBACK_DISCONNECTED, + CALLBACK_PRIVATE_MESSAGE, + CALLBACK_INVITED, + CALLBACK_CHANNEL_MESSAGE, + CALLBACK_KICKED, + CALLBACK_USER_JOINED, + CALLBACK_USER_PARTED, + CALLBACK_USER_CHANGED_NICK, + CALLBACK_TOPIC_CHANGED, + CALLBACK_CHANNEL_MODE_CHANGED, + CALLBACK_USER_MODE_CHANGED, + CALLBACK_USER_LIST_UPDATED, + CALLBACK_ENUM_CHANNELS_EACH, + CALLBACK_ENUM_CHANNELS_ALL, + CALLBACK_ENTER_CHANNEL, + CALLBACK_GET_CHANNEL_TOPIC, + CALLBACK_GET_CHANNEL_MODE, + CALLBACK_GET_CHANNEL_PASSWORD, + CALLBACK_ENUM_USERS, + CALLBACK_GET_USER_INFO, + CALLBACK_GET_BASIC_USER_INFO, + CALLBACK_GET_CHANNEL_BASIC_USER_INFO, + CALLBACK_GET_USER_MODE, + CALLBACK_ENUM_CHANNEL_BANS, + CALLBACK_NICK_ERROR, + CALLBACK_CHANGE_NICK, + CALLBACK_NEW_USER_LIST, + CALLBACK_BROADCAST_KEY_CHANGED, + CALLBACK_GET_GLOBAL_KEYS, + CALLBACK_GET_CHANNEL_KEYS, + CALLBACK_AUTHENTICATE_CDKEY, + CALLBACK_GET_UDPRELAY, + CALLBACK_NUM +}; + +/********** +** TYPES ** +**********/ +typedef struct ciCallbackRawParams +{ + char * raw; +} ciCallbackRawParams; + +typedef struct ciCallbackDisconnectedParams +{ + char * reason; +} ciCallbackDisconnectedParams; + +typedef struct ciCallbackPrivateMessageParams +{ + char * user; + char * message; + int type; +} ciCallbackPrivateMessageParams; + +typedef struct ciCallbackInvitedParams +{ + char * channel; + char * user; +} ciCallbackInvitedParams; + +typedef struct ciCallbackChannelMessageParams +{ + char * channel; + char * user; + char * message; + int type; +} ciCallbackChannelMessageParams; + +typedef struct ciCallbackKickedParams +{ + char * channel; + char * user; + char * reason; +} ciCallbackKickedParams; + +typedef struct ciCallbackUserJoinedParams +{ + char * channel; + char * user; + int mode; +} ciCallbackUserJoinedParams; + +typedef struct ciCallbackUserPartedParams +{ + char * channel; + char * user; + int why; + char * reason; + char * kicker; +} ciCallbackUserPartedParams; + +typedef struct ciCallbackUserChangedNickParams +{ + char * channel; + char * oldNick; + char * newNick; +} ciCallbackUserChangedNickParams; + +typedef struct ciCallbackTopicChangedParams +{ + char * channel; + char * topic; +} ciCallbackTopicChangedParams; + +typedef struct ciCallbackChannelModeChangedParams +{ + char * channel; + CHATChannelMode * mode; +} ciCallbackChannelModeChangedParams; + +typedef struct ciCallbackUserModeChangedParams +{ + char * channel; + char * user; + int mode; +} ciCallbackUserModeChangedParams; + +typedef struct ciCallbackUserListUpdatedParams +{ + char * channel; +} ciCallbackUserListUpdatedParams; + +typedef struct ciCallbackConnectParams +{ + CHATBool success; +} ciCallbackConnectParams; + +typedef struct ciCallbackEnumChannelsEachParams +{ + CHATBool success; + int index; + char * channel; + char * topic; + int numUsers; + void * param; +} ciCallbackEnumChannelsEachParams; + +typedef struct ciCallbackEnumChannelsAllParams +{ + CHATBool success; + int numChannels; + char ** channels; + char ** topics; + int * numUsers; +} ciCallbackEnumChannelsAllParams; + +typedef struct ciCallbackEnterChannelParams +{ + CHATBool success; + CHATEnterResult result; + char * channel; +} ciCallbackEnterChannelParams; + +typedef struct ciCallbackGetChannelTopicParams +{ + CHATBool success; + char * channel; + char * topic; +} ciCallbackGetChannelTopicParams; + +typedef struct ciCallbackGetChannelModeParams +{ + CHATBool success; + char * channel; + CHATChannelMode * mode; +} ciCallbackGetChannelModeParams; + +typedef struct ciCallbackGetChannelPasswordParams +{ + CHATBool success; + char * channel; + CHATBool enabled; + char * password; +} ciCallbackGetChannelPasswordParams; + +typedef struct ciCallbackEnumUsersParams +{ + CHATBool success; + char * channel; + int numUsers; + char ** users; + int * modes; +} ciCallbackEnumUsersParams; + +typedef struct ciCallbackGetUserInfoParams +{ + CHATBool success; + char * nick; + char * user; + char * name; + char * address; + int numChannels; + char ** channels; +} ciCallbackGetUserInfoParams; + +typedef struct ciCallbackGetBasicUserInfoParams +{ + CHATBool success; + char * nick; + char * user; + char * address; +} ciCallbackGetBasicUserInfoParams; + +typedef struct ciCallbackGetChannelBasicUserInfoParams +{ + CHATBool success; + char * channel; + char * nick; + char * user; + char * address; +} ciCallbackGetChannelBasicUserInfoParams; + +typedef struct ciCallbackGetUserModeParams +{ + CHATBool success; + char * channel; + char * user; + int mode; +} ciCallbackGetUserModeParams; + +typedef struct ciCallbackEnumChannelBansParams +{ + CHATBool success; + char * channel; + int numBans; + char ** bans; +} ciCallbackEnumChannelBansParams; + +typedef struct ciCallbackNickErrorParams +{ + int type; + char * nick; + int numSuggestedNicks; + char ** suggestedNicks; +} ciCallbackNickErrorParams; + +typedef struct ciCallbackChangeNickParams +{ + CHATBool success; + char * oldNick; + char * newNick; +} ciCallbackChangeNickParams; + +typedef struct ciCallbackNewUserListParams +{ + char * channel; + int numUsers; + char ** users; + int * modes; +} ciCallbackNewUserListParams; + +typedef struct ciCallbackBroadcastKeyChangedParams +{ + char * channel; + char * user; + char * key; + char * value; +} ciCallbackBroadcastKeyChangedParams; + +typedef struct ciCallbackGetGlobalKeysParams +{ + CHATBool success; + char * user; + int num; + char ** keys; + char ** values; +} ciCallbackGetGlobalKeysParams; + +typedef struct ciCallbackGetChannelKeysParams +{ + CHATBool success; + char * channel; + char * user; + int num; + char ** keys; + char ** values; +} ciCallbackGetChannelKeysParams; + +typedef struct ciCallbackAuthenticateCDKeyParams +{ + int result; + char * message; +} ciCallbackAuthenticateCDKeyParams; + +typedef struct ciCallbackGetUdpRelayParams +{ + char * channel; + char * udpIp; + unsigned short udpPort; + int udpKey; +} ciCallbackGetUdpRelayParams; + +/************** +** FUNCTIONS ** +**************/ +CHATBool ciInitCallbacks(ciConnection * connection); +void ciCleanupCallbacks(CHAT chat); +#define ciAddCallback(chat, type, callback, callbackParams, param, ID, channel) ciAddCallback_(chat, type, callback, callbackParams, param, ID, channel, sizeof(*callbackParams)) +CHATBool ciAddCallback_(CHAT chat, int type, void * callback, void * callbackParams, void * param, int ID, const char * channel, size_t callbackParamsSize); +void ciCallCallbacks(CHAT chat, int ID); +CHATBool ciCheckCallbacksForID(CHAT chat, int ID); + +#endif diff --git a/code/gamespy/Chat/chatChannel.c b/code/gamespy/Chat/chatChannel.c new file mode 100644 index 00000000..bb5ef715 --- /dev/null +++ b/code/gamespy/Chat/chatChannel.c @@ -0,0 +1,1265 @@ +/* +GameSpy Chat SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include +#include +#include "chatMain.h" +#include "chatChannel.h" +#include "chatCallbacks.h" + +#if defined(_WIN32) +// Compiler warns when explicitly casting from function* to void* +#pragma warning(disable:4054) +#endif + +/************ +** DEFINES ** +************/ +#define CHANNEL_HASH_BUCKETS 7 +#define USER_HASH_BUCKETS 61 +#define MAX_CHANNEL 257 +#define MAX_TOPIC 128 +#define MAX_NAME 128 +#define MAX_CACHED_USER 24 +#define MAX_CACHED_ADDRESS 64 + +#define ASSERT_CHANNEL() assert(channel != NULL); assert(channel[0] != '\0'); assert(strlen(channel) < MAX_CHANNEL); +#define ASSERT_USER(user) assert(user != NULL); assert(user[0] != '\0'); assert(strlen(user) < MAX_NAME); +#define ASSERT_STR(str) assert(str != NULL); assert(str[0] != '\0'); +#define ASSERT_MODE(mode) assert((mode >= 0) && (mode <= 3)); + +/********** +** TYPES ** +**********/ +typedef struct ciChatChannel +{ + char name[MAX_CHANNEL]; + chatChannelCallbacks callbacks; + + HashTable users; + + CHATChannelMode mode; + CHATBool gotMode; + + char * password; + + CHATBool joinCallbackCalled; + + char topic[MAX_TOPIC]; +} ciChatChannel; + +typedef struct ciChatUser +{ + char name[MAX_NAME]; + char user[MAX_CACHED_USER]; + char address[MAX_CACHED_ADDRESS]; +#ifdef GSI_UNICODE + // must store a unicode versions since a pointer to them is + // given to the developer application + unsigned short userW[MAX_CACHED_USER]; + unsigned short addressW[MAX_CACHED_ADDRESS]; +#endif + CHATBool gotUserAndAddress; + int mode; +} ciChatUser; + +typedef struct ciUserEnumChannelsData +{ + CHAT chat; + ciChatUser * user; + ciUserEnumChannelsCallback callback; + void * param; +} ciUserEnumChannelsData; + +typedef struct ciUserChangedNickData +{ + CHAT chat; + const char * oldNick; + const char * newNick; +} ciUserChangedNickData; + +typedef struct ciChannelListUsersData +{ + CHAT chat; + int numUsers; + char ** users; + int * modes; +} ciChannelListUsersData; + +typedef struct ciEnumJoinedChannelsData +{ + CHAT chat; + chatEnumJoinedChannelsCallback callback; + void * param; + int index; +} ciEnumJoinedChannelsData; + +typedef struct ciSetUserBasicInfoData +{ + ciChatUser * chatUser; + char * user; + char * address; +} ciSetUserBasicInfoData; + +typedef struct ciGetUserBasicInfoData +{ + CHATBool found; + ciChatUser * chatUser; + char * user; + char * address; +#ifdef GSI_UNICODE + // must store a unicode version since ptr to address is given to dev app + unsigned short * userW; + unsigned short * addressW; +#endif +} ciGetUserBasicInfoData; + +typedef struct ciClearAllUsersData +{ + CHAT chat; + ciChatChannel * channel; +} ciClearAllUsersData; + +/*************** +** PROTOTYPES ** +****************/ +// CodeWarrior will warn if this is not prototyped +int GS_STATIC_CALLBACK ciEnteringChannelComparator(const void *param1, const void *param2); + + +/************** +** FUNCTIONS ** +**************/ +static int ciHashString(const char * str, int numBuckets) +{ + unsigned int hash; + int c; + + ASSERT_STR(str); + + hash = 0; + while((c = *str++) != '\0') + hash += (unsigned int)tolower(c); + + return ((int)hash % numBuckets); +} + +static ciChatChannel * ciGetChannel(ciConnection * connection, const char * channel) +{ + ciChatChannel * chatChannel; + ciChatChannel channelTemp; + + strzcpy(channelTemp.name, channel, MAX_CHANNEL); + chatChannel = (ciChatChannel *)TableLookup(connection->channelTable, &channelTemp); + + return chatChannel; +} + +/********************** +** CHANNEL CALLBACKS ** +**********************/ +static int ciChannelTableHashFn(const void * elem, int numBuckets) +{ + int hash; + + assert(elem != NULL); + assert(numBuckets > 0); + + hash = ciHashString(((ciChatChannel *)elem)->name, numBuckets); + + return hash; +} + +static int GS_STATIC_CALLBACK ciChannelTableCompareFn(const void * elem1, const void * elem2) +{ + const char * str1; + const char * str2; + + assert(elem1 != NULL); + assert(elem2 != NULL); + + str1 = ((ciChatChannel *)elem1)->name; + str2 = ((ciChatChannel *)elem2)->name; + assert(str1 != NULL); + assert(str2 != NULL); + ASSERT_STR(str1); + ASSERT_STR(str2); + + return strcasecmp(str1, str2); +} + +static void ciChannelTableElementFreeFn(void * elem) +{ + ciChatChannel * channel; + + assert(elem != NULL); + + channel = (ciChatChannel *)elem; + gsifree(channel->password); + if(channel->users) + TableFree(channel->users); +} + +/******************* +** USER CALLBACKS ** +*******************/ +static int ciUserTableHashFn(const void * elem, int numBuckets) +{ + int hash; + + assert(elem != NULL); + assert(numBuckets > 0); + + hash = ciHashString(((ciChatUser *)elem)->name, numBuckets); + + return hash; +} + +static int GS_STATIC_CALLBACK ciUserTableCompareFn(const void * elem1, const void * elem2) +{ + const char * str1; + const char * str2; + + assert(elem1 != NULL); + assert(elem2 != NULL); + + // Sept 20, 2004 - Bill Dewey + // A NULL parameter has been causing Arcade to crash. (FogBugz 2825) + // This doesn't solve the real problem (elem is invalid), + // it just tries to keep Arcade going + if (elem1 == NULL || elem2 == NULL) + { + // A NULL user is automatically "less than" + if (elem1 == NULL && elem2 == NULL) + return 0; + else if (elem1 == NULL) + return -1; + else // if (elem2 == NULL) + return 1; + } + + str1 = ((ciChatUser *)elem1)->name; + str2 = ((ciChatUser *)elem2)->name; + ASSERT_STR(str1); + ASSERT_STR(str2); + + return strcasecmp(str1, str2); +} + +static void ciUserTableElementFreeFn(void * elem) +{ + assert(elem != NULL); + + GSI_UNUSED(elem); + + // if anything is done in here that changes the structure + // ciUserChangeNickMap() must be updated +} + +/********************** +** CHANNEL FUNCTIONS ** +**********************/ +CHATBool ciInitChannels(ciConnection * connection) +{ + connection->channelTable = TableNew2(sizeof(ciChatChannel), CHANNEL_HASH_BUCKETS, 2, ciChannelTableHashFn, ciChannelTableCompareFn, ciChannelTableElementFreeFn); + if(connection->channelTable == NULL) + return CHATFalse; + + connection->enteringChannelList = ArrayNew(sizeof(ciChatChannel), 0, NULL); + if(connection->enteringChannelList == NULL) + { + TableFree(connection->channelTable); + return CHATFalse; + } + + return CHATTrue; +} + +void ciCleanupChannels(CHAT chat) +{ + CONNECTION; + + if(connection->channelTable != NULL) + TableFree(connection->channelTable); + + if(connection->enteringChannelList != NULL) + ArrayFree(connection->enteringChannelList); +} + +void ciChannelEntering(CHAT chat, const char * channel) +{ + ciChatChannel chatChannel; + + CONNECTION; + + // Setup the channel. + ///////////////////// + memset(&chatChannel, 0, sizeof(ciChatChannel)); + strzcpy(chatChannel.name, channel, MAX_CHANNEL); + + // Add the channel to the list. + /////////////////////////////// + ArrayAppend(connection->enteringChannelList, &chatChannel); +} + +int GS_STATIC_CALLBACK ciEnteringChannelComparator(const void *param1, const void *param2) +{ + ciChatChannel * channel1 = (ciChatChannel *)param1; + ciChatChannel * channel2 = (ciChatChannel *)param2; + return strcasecmp(channel1->name, channel2->name); +} + +CHATBool ciIsEnteringChannel(CHAT chat, const char * channel) +{ + int i; + int count; + ciChatChannel * chatChannel; + + CONNECTION; + + count = ArrayLength(connection->enteringChannelList); + for(i = 0 ; i < count ; i++) + { + chatChannel = (ciChatChannel *)ArrayNth(connection->enteringChannelList, i); + assert(chatChannel); + if(strcasecmp(chatChannel->name, channel) == 0) + return CHATTrue; + } + + return CHATFalse; +} + +void ciChannelEntered(CHAT chat, const char * channel, chatChannelCallbacks * callbacks) +{ + ciChatChannel chatChannel; + char * password; + int index; + CONNECTION; + + ASSERT_CHANNEL(); + assert(callbacks != NULL); + + // Setup an empty password. + /////////////////////////// + password = (char *)gsimalloc(2); + if(password == NULL) + return; //ERRCON + strcpy(password, ""); + + // Setup the channel. + ///////////////////// + memset(&chatChannel, 0, sizeof(ciChatChannel)); + chatChannel.callbacks = *callbacks; + strzcpy(chatChannel.name, channel, MAX_CHANNEL); + chatChannel.users = TableNew2(sizeof(ciChatUser), USER_HASH_BUCKETS, 2, ciUserTableHashFn, ciUserTableCompareFn, ciUserTableElementFreeFn); + if(chatChannel.users == NULL) + return; //ERRCON + chatChannel.gotMode = CHATFalse; + chatChannel.password = password; + chatChannel.joinCallbackCalled = CHATFalse; + chatChannel.topic[0] = '\0'; + + // Check if this one is in the entering list. + ///////////////////////////////////////////// + index = ArraySearch(connection->enteringChannelList, &chatChannel, ciEnteringChannelComparator, 0, 0); + if(index != NOT_FOUND) + ArrayRemoveAt(connection->enteringChannelList, index); + + // Add the channel to the table. + //////////////////////////////// + TableEnter(connection->channelTable, &chatChannel); +} + +void ciChannelLeft(CHAT chat, const char * channel) +{ + ciChatChannel chatChannel; + int index; + //int rcode; + CONNECTION; + + ASSERT_CHANNEL(); + + // Setup a temp channel with the same name. + /////////////////////////////////////////// + strzcpy(chatChannel.name, channel, MAX_CHANNEL); + + // Check if this one is in the entering list. + ///////////////////////////////////////////// + index = ArraySearch(connection->enteringChannelList, &chatChannel, ciEnteringChannelComparator, 0, 0); + if(index != NOT_FOUND) + { + // Remove it from the entering list. + //////////////////////////////////// + ArrayRemoveAt(connection->enteringChannelList, index); + } + else + { + // Remove based on the name. + //////////////////////////// + //rcode = TableRemove(connection->channelTable, &chatChannel); + TableRemove(connection->channelTable, &chatChannel); + + // This will assert if we don't think we're in this channel. + //////////////////////////////////////////////////////////// + //assert(rcode != 0); + } +} + +chatChannelCallbacks * ciGetChannelCallbacks(CHAT chat, const char * channel) +{ + ciChatChannel * chatChannel; + CONNECTION; + + ASSERT_CHANNEL(); + + // Find this channel. + ///////////////////// + chatChannel = ciGetChannel(connection, channel); + if(chatChannel == NULL) + return NULL; //ERRCON + + return &chatChannel->callbacks; +} + +static void ciChannelListUsersMap(void * elem, void * clientData) +{ + ciChatUser * user; + ciChannelListUsersData * data; + void * tempPtr; + + assert(elem != NULL); + assert(clientData != NULL); + + // Get the user. + //////////////// + user = (ciChatUser *)elem; + ASSERT_USER(user->name); + + // Get the data. + //////////////// + data = (ciChannelListUsersData *)clientData; + assert(data->numUsers >= 0); +#ifdef _DEBUG + { + int i; + for(i = 0 ; i < data->numUsers ; i++) + { + ASSERT_USER(data->users[i]); + ASSERT_MODE(data->modes[i]); + } + } +#endif + + // Resize the arrays. + // TODO: resize in increments. + ////////////////////////////// + tempPtr = gsirealloc(data->users, sizeof(char *) * (data->numUsers + 1)); + if(tempPtr == NULL) + { + assert(0); + return; //ERRCON + } + data->users = (char **)tempPtr; + tempPtr = gsirealloc(data->modes, sizeof(int) * (data->numUsers + 1)); + if(tempPtr == NULL) + { + assert(0); + return; //ERRCON + } + data->modes = (int *)tempPtr; + + // Fill in the data. + //////////////////// + data->users[data->numUsers] = user->name; + data->modes[data->numUsers] = user->mode; + data->numUsers++; +} + +void ciChannelListUsers(CHAT chat, const char * channel, ciChannelListUsersCallback callback, void * param) +{ + ciChatChannel * chatChannel; + ciChannelListUsersData data; + CONNECTION; + + ASSERT_CHANNEL(); + assert(callback != NULL); + + // Find this channel. + ///////////////////// + chatChannel = ciGetChannel(connection, channel); + if(chatChannel == NULL) + return; //ERRCON + + // Enum through the users. + ////////////////////////// + data.chat = chat; + data.numUsers = 0; + data.users = NULL; + data.modes = NULL; + TableMap(chatChannel->users, ciChannelListUsersMap, &data); + + // Call the callback. + ///////////////////// + callback(chat, channel, data.numUsers, (const char **)data.users, data.modes, param); + + // gsifree the memory. + /////////////////// + gsifree(data.users); + gsifree(data.modes); +} + +CHATBool ciInChannel(CHAT chat, const char * channel) +{ + CONNECTION; + + if(ciGetChannel(connection, channel) == NULL) + return CHATFalse; + + return CHATTrue; +} + +CHATBool ciGetChannelMode(CHAT chat, const char * channel, CHATChannelMode * mode) +{ + ciChatChannel * chatChannel; + CONNECTION; + + ASSERT_CHANNEL(); + + // Find this channel. + ///////////////////// + chatChannel = ciGetChannel(connection, channel); + if(chatChannel == NULL) + return CHATFalse; //ERRCON + + // Did we get the mode yet? + /////////////////////////// + if(!chatChannel->gotMode) + return CHATFalse; //ERRCON + + // Copy the mode. + ///////////////// + memcpy(mode, &chatChannel->mode, sizeof(CHATChannelMode)); + + return CHATTrue; +} + +void ciSetChannelMode(CHAT chat, const char * channel, CHATChannelMode * mode) +{ + ciChatChannel * chatChannel; + CONNECTION; + + ASSERT_CHANNEL(); + + // Find this channel. + ///////////////////// + chatChannel = ciGetChannel(connection, channel); + if(chatChannel == NULL) + return; //ERRCON + + // Set the gotMode flag. + //////////////////////// + chatChannel->gotMode = CHATTrue; + + // Copy the mode. + ///////////////// + memcpy(&chatChannel->mode, mode, sizeof(CHATChannelMode)); +} + +void ciSetChannelPassword(CHAT chat, const char * channel, const char * password) +{ + int len; + ciChatChannel * chatChannel; + CONNECTION; + + ASSERT_CHANNEL(); + + // Find this channel. + ///////////////////// + chatChannel = ciGetChannel(connection, channel); + if(chatChannel == NULL) + return; //ERRCON + + // gsifree the old password. + ///////////////////////// + gsifree(chatChannel->password); + + // Set the password. + //////////////////// + if(password == NULL) + password = ""; + len = (int)(strlen(password) + 1); + chatChannel->password = (char *)gsimalloc((unsigned int)len); + if(chatChannel->password == NULL) + return; //ERRCON + memcpy(chatChannel->password, password, (unsigned int)len); +} + +const char * ciGetChannelPassword(CHAT chat, const char * channel) +{ + ciChatChannel * chatChannel; + CONNECTION; + + ASSERT_CHANNEL(); + + // Find this channel. + ///////////////////// + chatChannel = ciGetChannel(connection, channel); + if(chatChannel == NULL) + return NULL; //ERRCON + + // Return the password. + /////////////////////// + return chatChannel->password; +} + +void ciJoinCallbackCalled(CHAT chat, const char * channel) +{ + ciChatChannel * chatChannel; + CONNECTION; + + ASSERT_CHANNEL(); + + // Find this channel. + ///////////////////// + chatChannel = ciGetChannel(connection, channel); + if(chatChannel == NULL) + return; //ERRCON + + // Callback was called. + /////////////////////// + chatChannel->joinCallbackCalled = CHATTrue; +} + +CHATBool ciWasJoinCallbackCalled(CHAT chat, const char * channel) +{ + ciChatChannel * chatChannel; + CONNECTION; + + ASSERT_CHANNEL(); + + // Find this channel. + ///////////////////// + chatChannel = ciGetChannel(connection, channel); + if(chatChannel == NULL) + return CHATFalse; //ERRCON + + return chatChannel->joinCallbackCalled; +} + +void ciSetChannelTopic(CHAT chat, const char * channel, const char * topic) +{ + ciChatChannel * chatChannel; + CONNECTION; + + ASSERT_CHANNEL(); + + // Find this channel. + ///////////////////// + chatChannel = ciGetChannel(connection, channel); + if(chatChannel == NULL) + return; //ERRCON + + // Set the topic. + ///////////////// + strzcpy(chatChannel->topic, topic, MAX_TOPIC); +} + +const char * ciGetChannelTopic(CHAT chat, const char * channel) +{ + ciChatChannel * chatChannel; + CONNECTION; + + ASSERT_CHANNEL(); + + // Find this channel. + ///////////////////// + chatChannel = ciGetChannel(connection, channel); + if(chatChannel == NULL) + return NULL; //ERRCON + + return chatChannel->topic; +} + +int ciGetChannelNumUsers(CHAT chat, const char * channel) +{ + ciChatChannel * chatChannel; + CONNECTION; + + ASSERT_CHANNEL(); + + // Find this channel. + ///////////////////// + chatChannel = ciGetChannel(connection, channel); + if(chatChannel == NULL) + return -1; + + return TableCount(chatChannel->users); +} + +/******************* +** USER FUNCTIONS ** +*******************/ +void ciUserEnteredChannel(CHAT chat, const char * name, const char * channel, int mode, const char * user, const char * address) +{ + ciChatUser chatUser; + ciChatChannel * chatChannel; + CONNECTION; + + ASSERT_USER(name); + ASSERT_STR(channel); + ASSERT_MODE(mode); + + // Get the channel. + /////////////////// + chatChannel = ciGetChannel(connection, channel); + if(!chatChannel) + return; + + // Setup the user. + ////////////////// + memset(&chatUser, 0, sizeof(ciChatUser)); + strzcpy(chatUser.name, name, MAX_NAME); + if(user && address) + { + strzcpy(chatUser.user, user, MAX_CACHED_USER); + strzcpy(chatUser.address, address, MAX_CACHED_ADDRESS); + chatUser.gotUserAndAddress = CHATTrue; + +#ifdef GSI_UNICODE // update the unicode versions + AsciiToUCS2String(chatUser.user, chatUser.userW); + AsciiToUCS2String(chatUser.address, chatUser.addressW); +#endif + } + else + { + chatUser.gotUserAndAddress = CHATFalse; + } + chatUser.mode = mode; + + // Add it to the channel. + ///////////////////////// + TableEnter(chatChannel->users, &chatUser); + + // Check that we're in. + /////////////////////// + assert(TableLookup(chatChannel->users, &chatUser) != NULL); +} + +void ciUserLeftChannel(CHAT chat, const char * user, const char * channel) +{ + ciChatUser chatUser; + ciChatChannel * chatChannel; + CONNECTION; + + ASSERT_USER(user); + ASSERT_STR(channel); + + // Get the channel. + /////////////////// + chatChannel = ciGetChannel(connection, channel); + if(chatChannel == NULL) + return; //ERRCON + + // Setup a temp user with the same name. + //////////////////////////////////////// + strzcpy(chatUser.name, user, MAX_NAME); + + // Remove it. + ///////////// + TableRemove(chatChannel->users, &chatUser); +} + +static void ciUserEnumChannelsMap(void * elem, void * clientData) +{ + ciChatChannel * channel; + ciChatUser * user; + ciUserEnumChannelsData * data; + + assert(elem != NULL); + assert(clientData != NULL); + + // Get the channel. + /////////////////// + channel = (ciChatChannel *)elem; + assert(channel->users != NULL); + + // Get the user and callback. + ///////////////////////////// + data = (ciUserEnumChannelsData *)clientData; + assert(data->user != NULL); + assert(data->user->name[0] != '\0'); + assert(data->callback != NULL); + + // Check for the user. + ////////////////////// + user = (ciChatUser *)TableLookup(channel->users, data->user); + if(user != NULL) + { + // Call the callback. + ///////////////////// + data->callback(data->chat, data->user->name, channel->name, data->param); + } +} + +void ciUserEnumChannels(CHAT chat, const char * user, ciUserEnumChannelsCallback callback, void * param) +{ + ciChatUser chatUser; + ciUserEnumChannelsData data; + CONNECTION; + + ASSERT_USER(user); + assert(callback != NULL); + + strzcpy(chatUser.name, user, MAX_NAME); + data.chat = chat; + data.user = &chatUser; + data.callback = callback; + data.param = param; + + // Enum through channels looking for this user. + /////////////////////////////////////////////// + TableMap(connection->channelTable, ciUserEnumChannelsMap, &data); +} + +static void ciUserChangeNickMap(void * elem, void * clientData) +{ + ciChatChannel * channel; + ciChatUser tempUser; + ciChatUser * user; + ciUserChangedNickData * data; + ciCallbackUserChangedNickParams params; + int rcode; + + assert(elem != NULL); + assert(clientData != NULL); + + // Get the channel. + /////////////////// + channel = (ciChatChannel *)elem; + assert(channel->users != NULL); + + // Get the data. + //////////////// + data = (ciUserChangedNickData *)clientData; + ASSERT_USER(data->newNick); + ASSERT_USER(data->oldNick); + + // Check for the user. + ////////////////////// + user = (ciChatUser *)TableLookup(channel->users, data->oldNick); + if(user != NULL) + { + memcpy(&tempUser, user, sizeof(ciChatUser)); + + // Remove the old user. + /////////////////////// + rcode = TableRemove(channel->users, user); + assert(rcode != 0); + user = &tempUser; + + // Update the nick. + /////////////////// + strzcpy(user->name, data->newNick, MAX_NAME); + + // Add it back. + /////////////// + TableEnter(channel->users, user); + + // Was the join callback called? + //////////////////////////////// + if(ciWasJoinCallbackCalled(data->chat, channel->name)) //PANTS - 03.01.00 - check if the join callback was called + { + // Add the callback. + //////////////////// + if(channel->callbacks.userChangedNick != NULL) + { + params.channel = channel->name; + params.oldNick = (char *)data->oldNick; + params.newNick = (char *)data->newNick; + ciAddCallback(data->chat, CALLBACK_USER_CHANGED_NICK, (void*)channel->callbacks.userChangedNick, ¶ms, channel->callbacks.param, 0, channel->name); + } + } + } + GSI_UNUSED(rcode); +} + +void ciUserChangedNick(CHAT chat, const char * oldNick, const char * newNick) +{ + ciUserChangedNickData data; + CONNECTION; + + ASSERT_USER(oldNick); + ASSERT_USER(newNick); + + data.chat = chat; + data.oldNick = oldNick; + data.newNick = newNick; + + // Enum through channels looking for this user. + /////////////////////////////////////////////// + TableMap(connection->channelTable, ciUserChangeNickMap, &data); +} + +void ciUserChangedMode(CHAT chat, const char * user, const char * channel, int mode, CHATBool enabled) +{ + ciChatChannel channelTemp; + ciChatChannel * chatChannel; + ciChatUser userTemp; + ciChatUser * chatUser; + ciCallbackUserModeChangedParams params; + CONNECTION; + + ASSERT_USER(user); + ASSERT_STR(channel); + ASSERT_MODE(mode); + + // Find the channel. + //////////////////// + strzcpy(channelTemp.name, channel, MAX_CHANNEL); + chatChannel = (ciChatChannel *)TableLookup(connection->channelTable, &channelTemp); + if(chatChannel == NULL) + return; //ERRCON + + // Find the user. + ///////////////// + strzcpy(userTemp.name, user, MAX_NAME); + chatUser = (ciChatUser *)TableLookup(chatChannel->users, &userTemp); + if(chatUser == NULL) + return; //ERRCON + + // Change the mode. + /////////////////// + if(enabled) + chatUser->mode |= mode; + else + chatUser->mode &= ~mode; + + // Add the callback. + //////////////////// + if(chatChannel->callbacks.userModeChanged != NULL) + { + params.channel = (char *)channel; + params.user = (char *)user; + params.mode = chatUser->mode; + ciAddCallback(chat, CALLBACK_USER_MODE_CHANGED, (void*)chatChannel->callbacks.userModeChanged, ¶ms, chatChannel->callbacks.param, 0, channel); + } +} + +CHATBool ciUserInChannel(CHAT chat, const char * channel, const char * user) +{ + ciChatChannel * chatChannel; + ciChatUser chatUser; + CONNECTION; + + chatChannel = ciGetChannel(connection, channel); + if(chatChannel == NULL) + return CHATFalse; + + strzcpy(chatUser.name, user, MAX_NAME); + if(TableLookup(chatChannel->users, &chatUser) == NULL) + return CHATFalse; + + return CHATTrue; +} + +int ciGetUserMode(CHAT chat, const char * channel, const char * user) +{ + ciChatChannel * chatChannel; + ciChatUser userTemp; + ciChatUser * chatUser; + CONNECTION; + + chatChannel = ciGetChannel(connection, channel); + if(chatChannel == NULL) + return -1; //ERRCON + + strzcpy(userTemp.name, user, MAX_NAME); + chatUser = (ciChatUser *)TableLookup(chatChannel->users, &userTemp); + if(chatUser == NULL) + return -1; //ERRCON + + return chatUser->mode; +} + + +static void ciEnumJoinedChannelsMap(void * elem, void * clientData) +{ + ciEnumJoinedChannelsData * data; + ciChatChannel * channel; + assert(elem != NULL); + assert(clientData != NULL); + + // Get the channel. + /////////////////// + channel = (ciChatChannel *)elem; + + // Get the callback & param + ///////////////////////////// + data = (ciEnumJoinedChannelsData *)clientData; + assert(data->callback != NULL); + + // Call the callback. + ///////////////////// +#ifdef GSI_UNICODE + { + unsigned short* name_W = UTF8ToUCS2StringAlloc(channel->name); + data->callback(data->chat, data->index++, name_W, data->param); + gsifree(name_W); + } +#else + data->callback(data->chat, data->index++, channel->name, data->param); +#endif +} + +// Enumerates the channels that we are joined to +////////////////////////////////////////////////////// +void ciEnumJoinedChannels(CHAT chat, + chatEnumJoinedChannelsCallback callback, + void * param) +{ + ciEnumJoinedChannelsData data; + CONNECTION; + CONNECTED; + data.callback = callback; + data.param = param; + data.index = 0; + data.chat = chat; + + TableMap(connection->channelTable,ciEnumJoinedChannelsMap,&data); +} + +static void ciSetUserBasicInfoMap(void * elem, void * clientData) +{ + ciChatChannel * channel; + ciChatUser * user; + ciSetUserBasicInfoData * data; + + assert(elem != NULL); + assert(clientData != NULL); + + // Get the data. + //////////////// + data = (ciSetUserBasicInfoData *)clientData; + + // Get the channel. + /////////////////// + channel = (ciChatChannel *)elem; + assert(channel->users != NULL); + + // Check for the user. + ////////////////////// + user = (ciChatUser *)TableLookup(channel->users, data->chatUser); + if(user != NULL) + { + // Found it. + //////////// + strzcpy(user->user, data->user, MAX_CACHED_USER); + strzcpy(user->address, data->address, MAX_CACHED_ADDRESS); + user->gotUserAndAddress = CHATTrue; + +#ifdef GSI_UNICODE + // Store a unicode version since we pass a raw pointer to the developer + AsciiToUCS2String(user->user, user->userW); + AsciiToUCS2String(user->address, user->addressW); +#endif + } +} + +void ciSetUserBasicInfo(CHAT chat, const char * nick, const char * user, const char * address) +{ + ciChatUser chatUser; + ciSetUserBasicInfoData data; + CONNECTION; + + ASSERT_USER(nick); + + strzcpy(chatUser.name, nick, MAX_NAME); + data.chatUser = &chatUser; + data.user = (char *)user; + data.address = (char *)address; + + // Enum through channels looking for this user. + /////////////////////////////////////////////// + TableMap(connection->channelTable, ciSetUserBasicInfoMap, &data); +} + +static void ciGetUserBasicInfoMap(void * elem, void * clientData) +{ + ciChatChannel * channel; + ciChatUser * user; + ciGetUserBasicInfoData * data; + + assert(elem != NULL); + assert(clientData != NULL); + + // Get the data. + //////////////// + data = (ciGetUserBasicInfoData *)clientData; + + // Did we already find the user? + // Keep looking if we don't have a real address yet. + //////////////////////////////////////////////////// + if(data->found && (strcmp(data->address, "*") != 0)) + return; + + // Get the channel. + /////////////////// + channel = (ciChatChannel *)elem; + assert(channel->users != NULL); + + // Check for the user. + ////////////////////// + user = (ciChatUser *)TableLookup(channel->users, data->chatUser); + if(user != NULL) + { + if(user->gotUserAndAddress) + { + // Found it. + //////////// + data->found = CHATTrue; + data->user = user->user; + data->address = user->address; +#ifdef GSI_UNICODE + data->userW = user->userW; + data->addressW = user->addressW; +#endif + } + } +} + +CHATBool ciGetUserBasicInfoA(CHAT chat, const char * nick, const char ** user, const char ** address) +{ + ciChatUser chatUser; + ciGetUserBasicInfoData data; + CONNECTION; + + ASSERT_USER(nick); + + strzcpy(chatUser.name, nick, MAX_NAME); + data.chatUser = &chatUser; + data.found = CHATFalse; + + // Enum through channels looking for this user. + /////////////////////////////////////////////// + TableMap(connection->channelTable, ciGetUserBasicInfoMap, &data); + + if(!data.found) + return CHATFalse; + + if(user) + *user = data.user; + if(address) + *address = data.address; + + return CHATTrue; +} + +#ifdef GSI_UNICODE +// Because we're returning a pointer to SDK memory, we have to +// duplicate the lookup to return the address of the unsigned short versions +CHATBool ciGetUserBasicInfoW(CHAT chat, const char * nick, const unsigned short ** user, const unsigned short ** address) +{ + ciChatUser chatUser; + ciGetUserBasicInfoData data; + CONNECTION; + + ASSERT_USER(nick); + + strzcpy(chatUser.name, nick, MAX_NAME); + data.chatUser = &chatUser; + data.found = CHATFalse; + + // Enum through channels looking for this user. + /////////////////////////////////////////////// + TableMap(connection->channelTable, ciGetUserBasicInfoMap, &data); + + if(!data.found) + return CHATFalse; + + if(user) + *user = data.userW; + if(address) + *address = data.addressW; + return CHATTrue; +} +#endif + +static void ciClearAllUsersUsersMap(void * elem, void * clientData) +{ + ciClearAllUsersData * data; + ciChatUser * user; + ciChatChannel * channel; + + assert(elem != NULL); + assert(clientData != NULL); + + // Get the user. + //////////////// + user = (ciChatUser *)elem; + + // Get the data. + //////////////// + data = (ciClearAllUsersData *)clientData; + channel = data->channel; + + // Remove the user from the channel. + //////////////////////////////////// + TableRemove(channel->users, user); +} + +static void ciClearAllUsersChannelMap(void * elem, void * clientData) +{ + ciChatChannel * channel; + ciClearAllUsersData data; + CHAT chat; + + assert(elem != NULL); + assert(clientData != NULL); + + // Get the channel. + /////////////////// + channel = (ciChatChannel *)elem; + assert(channel->users != NULL); + + // Get the chat object. + /////////////////////// + chat = (CHAT)clientData; + + // Setup the data. + ////////////////// + data.chat = chat; + data.channel = channel; + + // Remove all the users in this channel. + //////////////////////////////////////// + TableMapSafe(channel->users, ciClearAllUsersUsersMap, &data); + + // Call the user list updated callback. + /////////////////////////////////////// + if(channel->callbacks.userListUpdated != NULL) + { + ciCallbackUserListUpdatedParams params; + params.channel = channel->name; + ciAddCallback(chat, CALLBACK_USER_LIST_UPDATED, (void*)channel->callbacks.userListUpdated, ¶ms, channel->callbacks.param, 0, channel->name); + } +} + +void ciClearAllUsers(CHAT chat) +{ + CONNECTION; + + // Go through all the channels. + /////////////////////////////// + TableMap(connection->channelTable, ciClearAllUsersChannelMap, chat); +} diff --git a/code/gamespy/Chat/chatChannel.h b/code/gamespy/Chat/chatChannel.h new file mode 100644 index 00000000..af38ef02 --- /dev/null +++ b/code/gamespy/Chat/chatChannel.h @@ -0,0 +1,64 @@ +/* +GameSpy Chat SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _CHATCHANNEL_H_ +#define _CHATCHANNEL_H_ + +/************* +** INCLUDES ** +*************/ +#include "chat.h" + +/************ +** DEFINES ** +************/ + +/************** +** FUNCTIONS ** +**************/ +CHATBool ciInitChannels(ciConnection * connection); +void ciCleanupChannels(CHAT chat); +void ciChannelEntering(CHAT chat, const char * channel); +CHATBool ciIsEnteringChannel(CHAT chat, const char * channel); +void ciChannelEntered(CHAT chat, const char * channel, chatChannelCallbacks * callbacks); +void ciChannelLeft(CHAT chat, const char * channel); +chatChannelCallbacks * ciGetChannelCallbacks(CHAT chat, const char * channel); +typedef void (* ciChannelListUsersCallback)(CHAT chat, const char * channel, int numUsers, const char ** users, int * modes, void * param); +void ciChannelListUsers(CHAT chat, const char * channel, ciChannelListUsersCallback callback, void * param); +CHATBool ciInChannel(CHAT chat, const char * channel); +CHATBool ciGetChannelMode(CHAT chat, const char * channel, CHATChannelMode * mode); +void ciSetChannelMode(CHAT chat, const char * channel, CHATChannelMode * mode); +void ciSetChannelPassword(CHAT chat, const char * channel, const char * password); +const char * ciGetChannelPassword(CHAT chat, const char * channel); +void ciJoinCallbackCalled(CHAT chat, const char * channel); +CHATBool ciWasJoinCallbackCalled(CHAT chat, const char * channel); +void ciSetChannelTopic(CHAT chat, const char * channel, const char * topic); +const char * ciGetChannelTopic(CHAT chat, const char * channel); +int ciGetChannelNumUsers(CHAT chat, const char * channel); + +void ciUserEnteredChannel(CHAT chat, const char * nick, const char * channel, int mode, const char * user, const char * address); +void ciUserLeftChannel(CHAT chat, const char * user, const char * channel); +void ciUserChangedNick(CHAT chat, const char * oldNick, const char * newNick); +void ciUserChangedMode(CHAT chat, const char * user, const char * channel, int mode, CHATBool enabled); +typedef void (* ciUserEnumChannelsCallback)(CHAT chat, const char * user, const char * channel, void * param); +void ciUserEnumChannels(CHAT chat, const char * user, ciUserEnumChannelsCallback callback, void * param); +CHATBool ciUserInChannel(CHAT chat, const char * channel, const char * user); +int ciGetUserMode(CHAT chat, const char * channel, const char * user); +void ciEnumJoinedChannels(CHAT chat, chatEnumJoinedChannelsCallback callback, void * param); +void ciSetUserBasicInfo(CHAT chat, const char * nick, const char * user, const char * address); +void ciClearAllUsers(CHAT chat); + +// Because these return pointers to SDK memory, we must have a widestring version +// so we can return a pointer to widestring data +// DO NOT CHANGE nick to an unsigned short*, nicks are internally store as char* +CHATBool ciGetUserBasicInfoA(CHAT chat, const char * nick, const char ** user, const char ** address); +CHATBool ciGetUserBasicInfoW(CHAT chat, const char * nick, const unsigned short ** user, const unsigned short ** address); + +#endif diff --git a/code/gamespy/Chat/chatCrypt.c b/code/gamespy/Chat/chatCrypt.c new file mode 100644 index 00000000..41a95ed6 --- /dev/null +++ b/code/gamespy/Chat/chatCrypt.c @@ -0,0 +1,65 @@ +#include "chatCrypt.h" + +#define swap_byte(x,y) t = *(x); *(x) = *(y); *(y) = t + +void gs_prepare_key(const unsigned char *key_data_ptr, int key_data_len, gs_crypt_key *key) +{ + unsigned char t; + unsigned char index1; + unsigned char index2; + unsigned char* state; + int counter; + + state = &key->state[0]; + for(counter = 0; counter < 256; counter++) + state[255 - counter] = (unsigned char)counter; //crt - we fill reverse of normal + key->x = 0; + key->y = 0; + index1 = 0; + index2 = 0; + for(counter = 0; counter < 256; counter++) + { + index2 = (unsigned char)((key_data_ptr[index1] + state[counter] + index2) % 256); + swap_byte(&state[counter], &state[index2]); + index1 = (unsigned char)((index1 + 1) % key_data_len); + } +} + +void gs_crypt(unsigned char *buffer_ptr, int buffer_len, gs_crypt_key *key) +{ + unsigned char t; + unsigned char x; + unsigned char y; + unsigned char* state; + unsigned char xorIndex; + int counter; + + x = key->x; + y = key->y; + state = &key->state[0]; + for(counter = 0; counter < buffer_len; counter++) + { + x = (unsigned char)((x + 1) % 256); + y = (unsigned char)((state[x] + y) % 256); + swap_byte(&state[x], &state[y]); + xorIndex = (unsigned char)((state[x] + state[y]) % 256); + buffer_ptr[counter] ^= state[xorIndex]; + } + key->x = x; + key->y = y; +} + + +void gs_xcode_buf(char *buf, int len, char *enckey) +{ + int i; + char *pos = enckey; + + for (i = 0 ; i < len ; i++) + { + buf[i] ^= *pos++; + if (*pos == 0) + pos = enckey; + } + +} diff --git a/code/gamespy/Chat/chatCrypt.h b/code/gamespy/Chat/chatCrypt.h new file mode 100644 index 00000000..c32d2c83 --- /dev/null +++ b/code/gamespy/Chat/chatCrypt.h @@ -0,0 +1,24 @@ +#ifndef _CHATCRYPT_H_ +#define _CHATCRYPT_H_ +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct _gs_crypt_key +{ + unsigned char state[256]; + unsigned char x; + unsigned char y; +} gs_crypt_key; + + +void gs_prepare_key(const unsigned char *key_data_ptr, int key_data_len, gs_crypt_key *key); +void gs_crypt(unsigned char *buffer_ptr, int buffer_len, gs_crypt_key *key); +void gs_xcode_buf(char *buf, int len, char *enckey); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Chat/chatHandlers.c b/code/gamespy/Chat/chatHandlers.c new file mode 100644 index 00000000..60dc73d7 --- /dev/null +++ b/code/gamespy/Chat/chatHandlers.c @@ -0,0 +1,4720 @@ +/* +GameSpy Chat SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include +#include +#include +#include "chatMain.h" +#include "chatSocket.h" +#include "chatHandlers.h" +#include "chatChannel.h" +#include "chatCallbacks.h" +#include "chatCrypt.h" + +#if defined(_WIN32) +// Silence the cast function* to void* warning. +// Since we are explicitly casting it, I'm not sure why the compiler warns +#pragma warning(disable:4054) + +// Silence the "conditional expression is constant" on our "while(1)" statements +#pragma warning(disable:4127) +#endif + +/************ +** DEFINES ** +************/ +#define FILTER_TIMEOUT 60000 +#define NAMES_ARRAY_INC 100 + +#define ASSERT_TYPE(type) assert((type >= 0) && (type < NUM_TYPES)); +#define ASSERT_STR(str) assert(str != NULL); assert(str[0] != '\0'); + +#define RPL_WELCOME "001" +#define RPL_USRIP "302" +#define RPL_WHOISUSER "311" +#define RPL_ENDOFWHO "315" +#define RPL_ENDOFWHOIS "318" +#define RPL_WHOISCHANNELS "319" +#define RPL_LISTSTART "321" +#define RPL_LIST "322" +#define RPL_LISTEND "323" +#define RPL_CHANNELMODEIS "324" +#define RPL_NOTOPIC "331" +#define RPL_TOPIC "332" +#define RPL_WHOREPLY "352" +#define RPL_NAMEREPLY "353" +#define RPL_ENDOFNAMES "366" +#define RPL_BANLIST "367" +#define RPL_ENDOFBANLIST "368" +#define RPL_GETKEY "700" +#define RPL_ENDGETKEY "701" +#define RPL_GETCKEY "702" +#define RPL_ENDGETCKEY "703" +#define RPL_GETCHANKEY "704" +#define RPL_SECUREKEY "705" +#define RPL_CDKEY "706" +#define RPL_LOGIN "707" +#define RPL_GETUDPRELAY "712" + +#define ERR_NOSUCHNICK "401" +#define ERR_NOSUCHCHANNEL "403" +#define ERR_TOOMANYCHANNELS "405" +#define ERR_ERRONEUSNICKNAME "432" +#define ERR_NICKNAMEINUSE "433" +#define ERR_CHANNELISFULL "471" +#define ERR_INVITEONLYCHAN "473" +#define ERR_BANNEDFROMCHAN "474" +#define ERR_BADCHANNELKEY "475" +#define ERR_BADCHANMASK "476" +#define ERR_LOGIN_FAILED "708" +#define ERR_NO_UNIQUE_NICK "709" +#define ERR_UNIQUE_NICK_EXPIRED "710" +#define ERR_REGISTER_NICK_FAILED "711" + +#define MODE_END 0 +#define MODE_BAN 1 +#define MODE_INVITE_ONLY 2 +#define MODE_LIMIT 3 +#define MODE_PRIVATE 4 +#define MODE_SECRET 5 +#define MODE_KEY 6 +#define MODE_MODERATED 7 +#define MODE_NO_EXTERNAL_MESSAGES 8 +#define MODE_ONLY_OPS_CHANGE_TOPIC 9 +#define MODE_OP 10 +#define MODE_VOICE 11 +#define MODE_USERS_HIDDEN 12 +#define MODE_RECEIVE_WALLOPS 13 +#define MODE_OPS_OBEY_CHANNEL_LIMIT 14 + + +#define FINISH_FILTER ciFinishFilter(chat, filter, ¶ms) +#define IS_ALPHA(c) ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) + +enum +{ + TYPE_LIST, + TYPE_JOIN, + TYPE_TOPIC, + TYPE_NAMES, + TYPE_WHOIS, + TYPE_CMODE, + TYPE_UMODE, + TYPE_BAN, + TYPE_GETBAN, + TYPE_NICK, + TYPE_WHO, + TYPE_CWHO, + TYPE_GETKEY, + TYPE_GETCKEY, + TYPE_GETCHANKEY, + TYPE_UNQUIET, + TYPE_CDKEY, + TYPE_GETUDPRELAY, + NUM_TYPES +}; + +/********** +** TYPES ** +**********/ +typedef struct ciModeChange +{ + int mode; + CHATBool enable; + char * param; +} ciModeChange; + +typedef struct ciFilterMatch +{ + int type; + const char * name; + const char * name2; +} ciFilterMatch; + +typedef struct LISTData +{ + CHATBool gotStart; + int numChannels; + char ** channels; + int * numUsers; + char ** topics; +} LISTData; + +typedef struct JOINData +{ + chatChannelCallbacks callbacks; + CHATBool joined; + char password[MAX_PASSWORD]; +} JOINData; + +typedef struct NAMESData +{ + int len; + int numUsers; + char ** users; + int * modes; +} NAMESData; + +typedef struct WHOISData +{ + char * user; + char * name; + char * address; + int numChannels; + char ** channels; +} WHOISData; + +typedef struct BANData +{ + char * channel; +} BANData; + +typedef struct GETBANData +{ + int numBans; + char ** bans; +} GETBANData; + +typedef struct GETKEYData +{ + int num; + char ** keys; + char * channel; +} GETKEYData; + +typedef struct GETCKEYData +{ + int num; + char ** keys; + CHATBool channel; + CHATBool allBroadcastKeys; +} GETCKEYData; + +typedef struct GETCHANKEYData +{ + int num; + char ** keys; + CHATBool allBroadcastKeys; +} GETCHANKEYData; + +/***************** +** HANDLER DECS ** +*****************/ +void ciPrivmsgHandler(CHAT chat, const ciServerMessage * message); +void ciNoticeHandler(CHAT chat, const ciServerMessage * message); +void ciUTMHandler(CHAT chat, const ciServerMessage * message); +void ciATMHandler(CHAT chat, const ciServerMessage * message); +void ciPingHandler(CHAT chat, const ciServerMessage * message); +void ciNickHandler(CHAT chat, const ciServerMessage * message); +void ciJoinHandler(CHAT chat, const ciServerMessage * message); +void ciPartHandler(CHAT chat, const ciServerMessage * message); +void ciKickHandler(CHAT chat, const ciServerMessage * message); +void ciQuitHandler(CHAT chat, const ciServerMessage * message); +void ciKillHandler(CHAT chat, const ciServerMessage * message); +void ciTopicHandler(CHAT chat, const ciServerMessage * message); +void ciModeHandler(CHAT chat, const ciServerMessage * message); +void ciErrorHandler(CHAT chat, const ciServerMessage * message); +void ciNameReplyHandler(CHAT chat, const ciServerMessage * message); +void ciEndOfNamesHandler(CHAT chat, const ciServerMessage * message); +void ciInviteHandler(CHAT chat, const ciServerMessage * message); + +void ciRplTopicHandler(CHAT chat, const ciServerMessage * message); +void ciRplNoTopicHandler(CHAT chat, const ciServerMessage * message); +void ciErrNickInUseHandler(CHAT chat, const ciServerMessage * message); +void ciRplWhoReplyHandler(CHAT chat, const ciServerMessage * message); +void ciRplUserIPHandler(CHAT chat, const ciServerMessage * message); +void ciRplListStartHandler(CHAT chat, const ciServerMessage * message); +void ciRplListHandler(CHAT chat, const ciServerMessage * message); +void ciRplListEndHandler(CHAT chat, const ciServerMessage * message); +void ciRplChannelModeIsHandler(CHAT chat, const ciServerMessage * message); +void ciRplWhoisUserHandler(CHAT chat, const ciServerMessage * message); +void ciRplWhoisChannelsHandler(CHAT chat, const ciServerMessage * message); +void ciRplEndOfWhoisHandler(CHAT chat, const ciServerMessage * message); +void ciRplBanListHandler(CHAT chat, const ciServerMessage * message); +void ciRplEndOfBanListHandler(CHAT chat, const ciServerMessage * message); +void ciRplWelcomeHandler(CHAT chat, const ciServerMessage * message); +void ciRplEndOfWhoHandler(CHAT chat, const ciServerMessage * message); +void ciRplGetKeyHandler(CHAT chat, const ciServerMessage * message); +void ciRplEndGetKeyHandler(CHAT chat, const ciServerMessage * message); +void ciRplGetCKeyHandler(CHAT chat, const ciServerMessage * message); +void ciRplEndGetCKeyHandler(CHAT chat, const ciServerMessage * message); +void ciRplGetChanKeyHandler(CHAT chat, const ciServerMessage * message); +void ciRplSecureKeyHandler(CHAT chat, const ciServerMessage * message); +void ciRplCDKeyHandler(CHAT chat, const ciServerMessage * message); +void ciRplLoginHandler(CHAT chat, const ciServerMessage * message); +void ciRplGetUdpRelayHandler(CHAT chat, const ciServerMessage * message); + +void ciErrNoSuchChannelHandler(CHAT chat, const ciServerMessage * message); +void ciErrTooManyChannelsHandler(CHAT chat, const ciServerMessage * message); +void ciErrChannelIsFullHandler(CHAT chat, const ciServerMessage * message); +void ciErrInviteOnlyChanHandler(CHAT chat, const ciServerMessage * message); +void ciErrBannedFromChanHandler(CHAT chat, const ciServerMessage * message); +void ciErrBadChannelKeyHandler(CHAT chat, const ciServerMessage * message); +void ciErrBadChanMaskHandler(CHAT chat, const ciServerMessage * message); +void ciErrNoSuchNickHandler(CHAT chat, const ciServerMessage * message); +void ciErrErroneusNicknameHandler(CHAT chat, const ciServerMessage * message); +void ciErrLoginFailedHandler(CHAT chat, const ciServerMessage * message); +void ciErrNoUniqueNickHandler(CHAT chat, const ciServerMessage * message); +void ciErrUniqueNickExpiredHandler(CHAT chat, const ciServerMessage * message); +void ciErrRegisterNickFailedHandler(CHAT chat, const ciServerMessage * message); + +/************ +** GLOBALS ** +************/ +ciServerMessageType serverMessageTypes[] = +{ + { "PRIVMSG", ciPrivmsgHandler }, + { "NOTICE", ciNoticeHandler }, + { "UTM", ciUTMHandler }, + { "ATM", ciATMHandler }, + { "PING", ciPingHandler }, + { "NICK", ciNickHandler }, + { "JOIN", ciJoinHandler }, + { "PART", ciPartHandler }, + { "KICK", ciKickHandler }, + { "QUIT", ciQuitHandler }, + { "KILL", ciKillHandler }, + { "TOPIC", ciTopicHandler }, + { "MODE", ciModeHandler }, + { "ERROR", ciErrorHandler }, + { "INVITE", ciInviteHandler }, + + { RPL_NAMEREPLY, ciNameReplyHandler }, + { RPL_ENDOFNAMES, ciEndOfNamesHandler }, + { RPL_TOPIC, ciRplTopicHandler }, + { RPL_NOTOPIC, ciRplNoTopicHandler }, + { RPL_WHOREPLY, ciRplWhoReplyHandler }, + { RPL_USRIP, ciRplUserIPHandler }, + { RPL_LISTSTART, ciRplListStartHandler }, + { RPL_LIST, ciRplListHandler }, + { RPL_LISTEND, ciRplListEndHandler }, + { RPL_CHANNELMODEIS, ciRplChannelModeIsHandler }, + { RPL_WHOISUSER, ciRplWhoisUserHandler }, + { RPL_WHOISCHANNELS, ciRplWhoisChannelsHandler }, + { RPL_ENDOFWHOIS, ciRplEndOfWhoisHandler }, + { RPL_BANLIST, ciRplBanListHandler }, + { RPL_ENDOFBANLIST, ciRplEndOfBanListHandler }, + { RPL_WELCOME, ciRplWelcomeHandler }, + { RPL_ENDOFWHO, ciRplEndOfWhoHandler }, + { RPL_GETKEY, ciRplGetKeyHandler }, + { RPL_ENDGETKEY, ciRplEndGetKeyHandler }, + { RPL_GETCKEY, ciRplGetCKeyHandler }, + { RPL_ENDGETCKEY, ciRplEndGetCKeyHandler }, + { RPL_GETCHANKEY, ciRplGetChanKeyHandler }, + { RPL_SECUREKEY, ciRplSecureKeyHandler }, + { RPL_CDKEY, ciRplCDKeyHandler }, + { RPL_LOGIN, ciRplLoginHandler }, + { RPL_GETUDPRELAY, ciRplGetUdpRelayHandler }, + + { ERR_NICKNAMEINUSE, ciErrNickInUseHandler }, + { ERR_NOSUCHCHANNEL, ciErrNoSuchChannelHandler }, + { ERR_TOOMANYCHANNELS, ciErrTooManyChannelsHandler }, + { ERR_CHANNELISFULL, ciErrChannelIsFullHandler }, + { ERR_INVITEONLYCHAN, ciErrInviteOnlyChanHandler }, + { ERR_BANNEDFROMCHAN, ciErrBannedFromChanHandler }, + { ERR_BADCHANNELKEY, ciErrBadChannelKeyHandler }, + { ERR_BADCHANMASK, ciErrBadChanMaskHandler }, + { ERR_NOSUCHNICK, ciErrNoSuchNickHandler }, + { ERR_ERRONEUSNICKNAME, ciErrErroneusNicknameHandler }, + { ERR_LOGIN_FAILED, ciErrLoginFailedHandler }, + { ERR_NO_UNIQUE_NICK, ciErrNoUniqueNickHandler }, + { ERR_UNIQUE_NICK_EXPIRED, ciErrUniqueNickExpiredHandler }, + { ERR_REGISTER_NICK_FAILED, ciErrRegisterNickFailedHandler } +}; +int numServerMessageTypes = (sizeof(serverMessageTypes) / sizeof(ciServerMessageType)); + + +/************** +** FUNCTIONS ** +**************/ +static ciServerMessageFilter * ciFindFilter(CHAT chat, int numMatches, ciFilterMatch * matches) +{ + int i; + const char * name; + const char * name2; + ciServerMessageFilter * filter; + CONNECTION; + + assert(numMatches > 0); + assert(matches); + + for(filter = connection->filterList ; filter != NULL ; filter = filter->pnext) + { + for(i = 0 ; i < numMatches ; i++) + { + ASSERT_TYPE(matches[i].type); + if(filter->type == matches[i].type) + { + name = matches[i].name; + name2 = matches[i].name2; + if((!name && !filter->name) || (name && filter->name && (strcasecmp(name, filter->name) == 0))) + { + if((!name2 && !filter->name2) || (name2 && filter->name2 && (strcasecmp(name2, filter->name2) == 0))) + { + // Someone's interested in this filter, so extend the timeout. + ////////////////////////////////////////////////////////////// + filter->timeout = (current_time() + FILTER_TIMEOUT); + + return filter; + } + } + } + } + } + + return NULL; +} + +static ciServerMessageFilter * ciFindGetKeyFilter(CHAT chat, const char * channel) +{ + GETKEYData * data; + ciServerMessageFilter * filter; + CONNECTION; + + assert(channel); + assert(channel[0]); + + for(filter = connection->filterList ; filter != NULL ; filter = filter->pnext) + { + if(filter->type == TYPE_GETKEY) + { + data = (GETKEYData *)filter->data; + if(strcasecmp(data->channel, channel) == 0) + return filter; + } + } + + return NULL; +} + +static void ciDestroyFilter(ciServerMessageFilter * filter) +{ + assert(filter != NULL); + + gsifree(filter->data); + gsifree(filter->name); + gsifree(filter->name2); + gsifree(filter); +} + +static void ciRemoveFilter(CHAT chat, ciServerMessageFilter * filter) +{ + ciServerMessageFilter * pcurr; + ciServerMessageFilter * pprev = NULL; + CONNECTION; + + assert(filter != NULL); + + for(pcurr = connection->filterList ; pcurr != NULL ; pcurr = pcurr->pnext) + { + if(pcurr == filter) + { + if(connection->filterList == pcurr) + connection->filterList = pcurr->pnext; + + if(connection->lastFilter == pcurr) + connection->lastFilter = pprev; + + if(pprev != NULL) + pprev->pnext = pcurr->pnext; + + ciDestroyFilter(pcurr); + + return; + } + + pprev = pcurr; + } + + //ERRCON +} + +// Calls the callback, if not NULL, then removes the filter. +//////////////////////////////////////////////////////////// +static void ciFinishFilter(CHAT chat, ciServerMessageFilter * filter, void * params) +{ + int i; + + assert(filter); + ASSERT_TYPE(filter->type); + + // Check the type. + ////////////////// + if(filter->type == TYPE_LIST) + { + LISTData * data = (LISTData *)filter->data; + + if(filter->callback2) + ciAddCallback_(chat, CALLBACK_ENUM_CHANNELS_ALL, filter->callback2, params, filter->param, filter->ID, NULL, sizeof(ciCallbackEnumChannelsAllParams)); + + for(i = 0 ; i < data->numChannels ; i++) + { + gsifree(data->channels[i]); + gsifree(data->topics[i]); + } + gsifree(data->channels); + gsifree(data->topics); + gsifree(data->numUsers); + } + else if(filter->type == TYPE_JOIN) + { + if(filter->callback != NULL) + ciAddCallback_(chat, CALLBACK_ENTER_CHANNEL, filter->callback, params, filter->param, filter->ID, NULL, sizeof(ciCallbackEnterChannelParams)); + } + else if(filter->type == TYPE_TOPIC) + { + const char * channel = ((ciCallbackGetChannelTopicParams *)params)->channel; + + if(filter->callback) + ciAddCallback_(chat, CALLBACK_GET_CHANNEL_TOPIC, filter->callback, params, filter->param, filter->ID, channel, sizeof(ciCallbackGetChannelTopicParams)); + } + else if(filter->type == TYPE_NAMES) + { + NAMESData * data = (NAMESData *)filter->data; + + if(filter->callback) + ciAddCallback_(chat, CALLBACK_ENUM_USERS, filter->callback, params, filter->param, filter->ID, NULL, sizeof(ciCallbackEnumUsersParams)); + + for(i = 0 ; i < data->numUsers ; i++) + gsifree(data->users[i]); + gsifree(data->users); + gsifree(data->modes); + } + else if(filter->type == TYPE_WHOIS) + { + WHOISData * data = (WHOISData *)filter->data; + + if(filter->callback) + ciAddCallback_(chat, CALLBACK_GET_USER_INFO, filter->callback, params, filter->param, filter->ID, NULL, sizeof(ciCallbackGetUserInfoParams)); + + for(i = 0 ; i < data->numChannels ; i++) + gsifree(data->channels[i]); + gsifree(data->channels); + gsifree(data->name); + gsifree(data->address); + gsifree(data->user); + } + else if(filter->type == TYPE_WHO) + { + if(filter->callback) + ciAddCallback_(chat, CALLBACK_GET_BASIC_USER_INFO, filter->callback, params, filter->param, filter->ID, NULL, sizeof(ciCallbackGetBasicUserInfoParams)); + } + else if(filter->type == TYPE_CWHO) + { + if(filter->callback) + ciAddCallback_(chat, CALLBACK_GET_CHANNEL_BASIC_USER_INFO, filter->callback, params, filter->param, filter->ID, NULL, sizeof(ciCallbackGetBasicUserInfoParams)); + } + else if(filter->type == TYPE_CMODE) + { + if(filter->callback) + ciAddCallback_(chat, CALLBACK_GET_CHANNEL_MODE, filter->callback, params, filter->param, filter->ID, NULL, sizeof(ciCallbackGetChannelModeParams)); + } + else if(filter->type == TYPE_UMODE) + { + if(filter->callback) + ciAddCallback_(chat, CALLBACK_GET_USER_MODE, filter->callback, params, filter->param, filter->ID, NULL, sizeof(ciCallbackGetUserModeParams)); + } + else if(filter->type == TYPE_GETUDPRELAY) + { + if(filter->callback) + ciAddCallback_(chat, CALLBACK_GET_UDPRELAY, filter->callback, params, filter->param, filter->ID, NULL, sizeof(ciCallbackGetUdpRelayParams)); + } + else if(filter->type == TYPE_BAN) + { + BANData * data = (BANData *)filter->data; + + gsifree(data->channel); + } + else if(filter->type == TYPE_GETBAN) + { + GETBANData * data = (GETBANData *)filter->data; + + if(filter->callback) + ciAddCallback_(chat, CALLBACK_ENUM_CHANNEL_BANS, filter->callback, params, filter->param, filter->ID, NULL, sizeof(ciCallbackEnumChannelBansParams)); + + // gsifree the filter. + /////////////////// + for(i = 0 ; i < data->numBans ; i++) + gsifree(data->bans[i]); + } + else if(filter->type == TYPE_NICK) + { + if(filter->callback) + ciAddCallback_(chat, CALLBACK_CHANGE_NICK, filter->callback, params, filter->param, filter->ID, NULL, sizeof(ciCallbackChangeNickParams)); + } + else if(filter->type == TYPE_GETKEY) + { + GETKEYData * data = (GETKEYData *)filter->data; + + if(filter->callback) + ciAddCallback_(chat, CALLBACK_GET_GLOBAL_KEYS, filter->callback, params, filter->param, filter->ID, NULL, sizeof(ciCallbackGetGlobalKeysParams)); + + gsifree(data->channel); + for(i = 0 ; i < data->num ; i++) + gsifree(data->keys[i]); + gsifree(data->keys); + } + else if(filter->type == TYPE_GETCKEY) + { + GETCKEYData * data = (GETCKEYData *)filter->data; + + if(filter->callback) + ciAddCallback_(chat, CALLBACK_GET_CHANNEL_KEYS, filter->callback, params, filter->param, filter->ID, NULL, sizeof(ciCallbackGetChannelKeysParams)); + + for(i = 0 ; i < data->num ; i++) + gsifree(data->keys[i]); + gsifree(data->keys); + } + else if(filter->type == TYPE_GETCHANKEY) + { + GETCHANKEYData * data = (GETCHANKEYData *)filter->data; + + if(filter->callback) + ciAddCallback_(chat, CALLBACK_GET_CHANNEL_KEYS, filter->callback, params, filter->param, filter->ID, NULL, sizeof(ciCallbackGetChannelKeysParams)); + + for(i = 0 ; i < data->num ; i++) + gsifree(data->keys[i]); + gsifree(data->keys); + } + else if(filter->type == TYPE_UNQUIET) + { + NAMESData * data = (NAMESData *)filter->data; + + if(filter->callback) + ciAddCallback_(chat, CALLBACK_NEW_USER_LIST, filter->callback, params, filter->param, filter->ID, NULL, sizeof(ciCallbackNewUserListParams)); + + for(i = 0 ; i < data->numUsers ; i++) + gsifree(data->users[i]); + gsifree(data->users); + gsifree(data->modes); + } + else if(filter->type == TYPE_CDKEY) + { + if(filter->callback) + ciAddCallback_(chat, CALLBACK_AUTHENTICATE_CDKEY, filter->callback, params, filter->param, filter->ID, NULL, sizeof(ciCallbackAuthenticateCDKeyParams)); + } + else + { + assert(0); + } + + // Remove the filter. + ///////////////////// + ciRemoveFilter(chat, filter); +} + +// Called when a filter times out to deal with calling the failed callback. +/////////////////////////////////////////////////////////////////////////// +static void ciFilterTimedout(CHAT chat, ciServerMessageFilter * filter) +{ + assert(filter); + ASSERT_TYPE(filter->type); + + // Check the type. + ////////////////// + if(filter->type == TYPE_LIST) + { + ciCallbackEnumChannelsAllParams params; + params.success = CHATFalse; + params.numChannels = 0; + params.channels = NULL; + params.topics = NULL; + params.numUsers = NULL; + + FINISH_FILTER; + } + else if(filter->type == TYPE_JOIN) + { + ciCallbackEnterChannelParams params; + params.success = CHATFalse; + params.result = CHATEnterTimedOut; + params.channel = filter->name; + + FINISH_FILTER; + } + else if(filter->type == TYPE_TOPIC) + { + ciCallbackGetChannelTopicParams params; + params.success = CHATFalse; + params.channel = filter->name; + params.topic = NULL; + + FINISH_FILTER; + } + else if(filter->type == TYPE_NAMES) + { + ciCallbackEnumUsersParams params; + params.success = CHATFalse; + params.channel = filter->name; + params.numUsers = 0; + params.users = NULL; + params.modes = NULL; + + FINISH_FILTER; + } + else if(filter->type == TYPE_WHOIS) + { + ciCallbackGetUserInfoParams params; + params.success = CHATFalse; + params.nick = filter->name; + params.user = NULL; + params.name = NULL; + params.address = NULL; + params.numChannels = 0; + params.channels = NULL; + + FINISH_FILTER; + } + else if(filter->type == TYPE_WHO) + { + ciCallbackGetBasicUserInfoParams params; + params.success = CHATFalse; + params.nick = filter->name; + params.user = NULL; + params.address = NULL; + + FINISH_FILTER; + } + else if(filter->type == TYPE_CWHO) + { + ciCallbackGetChannelBasicUserInfoParams params; + params.success = CHATFalse; + params.channel = filter->name; + params.nick = NULL; + params.user = NULL; + params.address = NULL; + + FINISH_FILTER; + } + else if(filter->type == TYPE_CMODE) + { + ciCallbackGetChannelModeParams params; + params.success = CHATFalse; + params.channel = filter->name; + params.mode = NULL; + + FINISH_FILTER; + } + else if(filter->type == TYPE_UMODE) + { + ciCallbackGetUserModeParams params; + params.success = CHATFalse; + params.channel = filter->name2; + params.user = filter->name; + params.mode = 0; + + FINISH_FILTER; + } + else if(filter->type == TYPE_BAN) + { + ciFinishFilter(chat, filter, NULL); + } + else if(filter->type == TYPE_GETBAN) + { + ciCallbackEnumChannelBansParams params; + params.channel = filter->name; + params.numBans = 0; + params.bans = NULL; + + FINISH_FILTER; + } + else if(filter->type == TYPE_NICK) + { + ciCallbackChangeNickParams params; + params.success = CHATFalse; + params.oldNick = filter->name; + params.newNick = filter->name2; + + FINISH_FILTER; + } + else if(filter->type == TYPE_GETKEY) + { + ciCallbackGetGlobalKeysParams params; + params.success = CHATFalse; + params.user = NULL; + params.num = 0; + params.keys = NULL; + params.values = NULL; + + FINISH_FILTER; + } + else if(filter->type == TYPE_GETCKEY) + { + ciCallbackGetChannelKeysParams params; + params.success = CHATFalse; + params.channel = NULL; + params.user = NULL; + params.num = 0; + params.keys = NULL; + params.values = NULL; + + FINISH_FILTER; + } + else if(filter->type == TYPE_GETCHANKEY) + { + ciCallbackGetChannelKeysParams params; + params.success = CHATFalse; + params.channel = NULL; + params.user = NULL; + params.num = 0; + params.keys = NULL; + params.values = NULL; + + FINISH_FILTER; + } + else if(filter->type == TYPE_UNQUIET) + { + ciRemoveFilter(chat, filter); + } + else if(filter->type == TYPE_CDKEY) + { + ciCallbackAuthenticateCDKeyParams params; + params.result = 0; + params.message = "Timed out"; + + FINISH_FILTER; + } + else if(filter->type == TYPE_GETUDPRELAY) + { + ciCallbackGetUdpRelayParams params; + params.channel = filter->name; + params.udpIp = NULL; + params.udpKey = 0; + params.udpPort = 0; + + FINISH_FILTER; + } + else + { + assert(0); + } +} + +void ciFilterThink(CHAT chat) +{ + ciServerMessageFilter * filter; + ciServerMessageFilter * pnext; + gsi_time now; + CONNECTION; + + now = 0; + now = 2; + now = current_time(); + + for(filter = connection->filterList ; filter != NULL ; filter = pnext) + { + pnext = filter->pnext; + if(now > filter->timeout) + { + ciFilterTimedout(chat, filter); + } + } +} + +int ciGetNextID(CHAT chat) +{ + int rcode; + CONNECTION; + + // Store the next. + ////////////////// + rcode = connection->nextID; + + // Increment the ID. + //////////////////// + if(connection->nextID == INT_MAX) + connection->nextID = 1; + else + connection->nextID++; + + return rcode; +} + +CHATBool ciCheckFiltersForID(CHAT chat, int ID) +{ + ciServerMessageFilter * filter; + CONNECTION; + + assert(ID > 0); + + for(filter = connection->filterList ; filter != NULL ; filter = filter->pnext) + { + // Check for a matching ID. + /////////////////////////// + if(filter->ID == ID) + return CHATTrue; + } + + // No matches. + ////////////// + return CHATFalse; +} + +void ciCleanupFilters(CHAT chat) +{ + CONNECTION; + + while(connection->filterList) + ciFilterTimedout(chat, connection->filterList); // PANTS|03.13.01 - changed from ciRemoveFilter +} + +static int ciAddFilter(CHAT chat, int type, const char * name, const char * name2, void * callback, void * callback2, void * param, void * data) +{ + ciServerMessageFilter * filter; + CONNECTION; + + // Create the filter. + ///////////////////// + filter = (ciServerMessageFilter *)gsimalloc(sizeof(ciServerMessageFilter)); + if(filter == NULL) + return 0; //ERRCON + + // Setup the filter. + //////////////////// + memset(filter, 0, sizeof(ciServerMessageFilter)); + filter->type = type; + filter->timeout = (current_time() + FILTER_TIMEOUT); + filter->callback = callback; + filter->callback2 = callback2; + filter->param = param; + filter->data = data; + if(name) + filter->name = goastrdup(name); + else + filter->name = NULL; + if(name2) + filter->name2 = goastrdup(name2); + else + filter->name2 = NULL; + filter->ID = ciGetNextID(chat); + + // Add the filter to the end of the list. + ///////////////////////////////////////// + if(connection->filterList == NULL) + connection->filterList = filter; + else + connection->lastFilter->pnext = filter; + connection->lastFilter = filter; + + return filter->ID; +} + +int ciAddLISTFilter(CHAT chat, chatEnumChannelsCallbackEach callbackEach, chatEnumChannelsCallbackAll callbackAll, void * param) +{ + LISTData * data = (LISTData *)gsimalloc(sizeof(LISTData)); + if(data == NULL) + return 0; //ERRCON + memset(data, 0, sizeof(LISTData)); + + return ciAddFilter(chat, TYPE_LIST, NULL, NULL, (void*)callbackEach, (void*)callbackAll, param, data); +} + +int ciAddJOINFilter(CHAT chat, const char * channel, chatEnterChannelCallback callback, void * param, chatChannelCallbacks * callbacks, const char * password) +{ + int rcode; + JOINData * data; + + assert(password != NULL); + assert(strlen(password) < MAX_PASSWORD); + + data = (JOINData *)gsimalloc(sizeof(JOINData)); + if(data == NULL) + return 0; //ERRCON + memset(data, 0, sizeof(JOINData)); + data->callbacks = *callbacks; + strzcpy(data->password, password, MAX_PASSWORD); + + rcode = ciAddFilter(chat, TYPE_JOIN, channel, NULL, (void*)callback, NULL, param, data); + if(rcode == 0) + gsifree(data); + + return rcode; +} + +int ciAddTOPICFilter(CHAT chat, const char * channel, chatGetChannelTopicCallback callback, void * param) +{ + return ciAddFilter(chat, TYPE_TOPIC, channel, NULL, (void*)callback, NULL, param, NULL); +} + +int ciAddNAMESFilter(CHAT chat, const char * channel, chatEnumUsersCallback callback, void * param) +{ + NAMESData * data = (NAMESData *)gsimalloc(sizeof(NAMESData)); + if(data == NULL) + return 0; //ERRCON + memset(data, 0, sizeof(NAMESData)); + + return ciAddFilter(chat, TYPE_NAMES, channel, NULL, (void*)callback, NULL, param, data); +} + +int ciAddWHOISFilter(CHAT chat, const char * user, chatGetUserInfoCallback callback, void * param) +{ + int rcode; + WHOISData * data = (WHOISData *)gsimalloc(sizeof(WHOISData)); + if(data == NULL) + return 0; //ERRCON + memset(data, 0, sizeof(WHOISData)); + + rcode = ciAddFilter(chat, TYPE_WHOIS, user, NULL, (void*)callback, NULL, param, data); + if(rcode == 0) + gsifree(data); + return rcode; +} + +int ciAddWHOFilter(CHAT chat, const char * user, chatGetBasicUserInfoCallback callback, void * param) +{ + return ciAddFilter(chat, TYPE_WHO, user, NULL, (void*)callback, NULL, param, NULL); +} + +int ciAddCWHOFilter(CHAT chat, const char * channel, chatGetChannelBasicUserInfoCallback callback, void * param) +{ + return ciAddFilter(chat, TYPE_CWHO, channel, NULL, (void*)callback, NULL, param, NULL); +} + +int ciAddCMODEFilter(CHAT chat, const char * channel, chatGetChannelModeCallback callback, void * param) +{ + return ciAddFilter(chat, TYPE_CMODE, channel, NULL, (void*)callback, NULL, param, NULL); +} + +int ciAddUMODEFilter(CHAT chat, const char * user, const char * channel, chatGetUserModeCallback callback, void * param) +{ + return ciAddFilter(chat, TYPE_UMODE, user, channel, (void*)callback, NULL, param, NULL); +} + +int ciAddGETUDPRELAYFilter(CHAT chat, const char * channel, chatGetUdpRelayCallback callback, void * param) +{ + return ciAddFilter(chat, TYPE_GETUDPRELAY, channel, NULL, (void*)callback, NULL, param, NULL); +} + + +int ciAddBANFilter(CHAT chat, const char * user, const char * channel) +{ + BANData * data = (BANData *)gsimalloc(sizeof(BANData)); + if(data == NULL) + return 0; //ERRCON + memset(data, 0, sizeof(BANData)); + data->channel = goastrdup(channel); + if(data->channel == NULL) + { + gsifree(data); + return 0; //ERRCON + } + + return ciAddFilter(chat, TYPE_BAN, user, NULL, NULL, NULL, NULL, data); +} + +int ciAddGETBANFilter(CHAT chat, const char * channel, chatEnumChannelBansCallback callback, void * param) +{ + GETBANData * data = (GETBANData *)gsimalloc(sizeof(GETBANData)); + if(data == NULL) + return 0; //ERRCON + memset(data, 0, sizeof(GETBANData)); + + return ciAddFilter(chat, TYPE_GETBAN, channel, NULL, (void*)callback, NULL, param, data); +} + +int ciAddNICKFilter(CHAT chat, const char * oldNick, const char * newNick, chatChangeNickCallback callback, void * param) +{ + return ciAddFilter(chat, TYPE_NICK, oldNick, newNick, (void*)callback, NULL, param, NULL); +} + +int ciAddGETKEYFilter(CHAT chat, const char * cookie, int num, const char ** keys, const char * channel, chatGetGlobalKeysCallback callback, void * param) +{ + int i; + GETKEYData * data = (GETKEYData *)gsimalloc(sizeof(GETKEYData)); + if(!data) + return 0; + memset(data, 0, sizeof(GETKEYData)); + + data->num = num; + if(channel) + { + data->channel = goastrdup(channel); + if(!data->channel) + { + gsifree(data); + return 0; + } + } + data->keys = (char **)gsimalloc(sizeof(char *) * num); + if(!data->keys) + { + gsifree(data->channel); + gsifree(data); + return 0; + } + for(i = 0 ; i < num ; i++) + { + data->keys[i] = goastrdup(keys[i]); + if(!data->keys[i]) + { + for( i-- ; i >= 0 ; i--) + gsifree(data->keys[i]); + gsifree(data->keys); + gsifree(data->channel); + gsifree(data); + return 0; + } + } + + return ciAddFilter(chat, TYPE_GETKEY, cookie, NULL, (void*)callback, NULL, param, data); +} + +int ciAddGETCKEYFilter(CHAT chat, const char * cookie, int num, const char ** keys, CHATBool channel, CHATBool getBroadcastKeys, chatGetChannelKeysCallback callback, void * param) +{ + int src; + int dest; + GETCKEYData * data = (GETCKEYData *)gsimalloc(sizeof(GETCKEYData)); + if(!data) + return 0; + memset(data, 0, sizeof(GETCKEYData)); + data->allBroadcastKeys = getBroadcastKeys; + + data->num = num; + data->channel = channel; + if(getBroadcastKeys) + data->num--; + data->keys = (char **)gsimalloc(sizeof(char *) * data->num); + if(!data->keys) + { + gsifree(data); + return 0; + } + for(src = 0, dest = 0 ; src < num ; src++) + { + if(strcmp(keys[src], "b_*") != 0) + { + data->keys[dest] = goastrdup(keys[src]); + if(!data->keys[dest]) + { + for( dest-- ; dest >= 0 ; dest--) + gsifree(data->keys[dest]); + gsifree(data->keys); + gsifree(data); + return 0; + } + dest++; + } + } + data->num = dest; + + return ciAddFilter(chat, TYPE_GETCKEY, cookie, NULL, (void*)callback, NULL, param, data); +} + +int ciAddGETCHANKEYFilter(CHAT chat, const char * cookie, int num, const char ** keys, CHATBool getBroadcastKeys, chatGetChannelKeysCallback callback, void * param) +{ + int src; + int dest; + GETCHANKEYData * data = (GETCHANKEYData *)gsimalloc(sizeof(GETCHANKEYData)); + if(!data) + return 0; + memset(data, 0, sizeof(GETCHANKEYData)); + data->allBroadcastKeys = getBroadcastKeys; + + data->num = num; + if(getBroadcastKeys) + data->num--; + if(data->num) + { + data->keys = (char **)gsimalloc(sizeof(char *) * data->num); + if(!data->keys) + { + gsifree(data); + return 0; + } + for(src = 0, dest = 0 ; src < num ; src++) + { + if(strcmp(keys[src], "b_*") != 0) + { + data->keys[dest] = goastrdup(keys[src]); + if(!data->keys[dest]) + { + for( dest-- ; dest >= 0 ; dest--) + gsifree(data->keys[dest]); + gsifree(data->keys); + gsifree(data); + return 0; + } + dest++; + } + } + data->num = dest; + } + + return ciAddFilter(chat, TYPE_GETCHANKEY, cookie, NULL, (void*)callback, NULL, param, data); +} + +int ciAddUNQUIETFilter(CHAT chat, const char * channel) +{ + chatChannelCallbacks * callbacks; + NAMESData * data; + + callbacks = ciGetChannelCallbacks(chat, channel); + if(!callbacks || !callbacks->newUserList) + return 0; + + data = (NAMESData *)gsimalloc(sizeof(NAMESData)); + if(data == NULL) + return 0; //ERRCON + memset(data, 0, sizeof(NAMESData)); + + return ciAddFilter(chat, TYPE_UNQUIET, channel, NULL, (void*)callbacks->newUserList, NULL, callbacks->param, data); +} + +int ciAddCDKEYFilter(CHAT chat, chatAuthenticateCDKeyCallback callback, void * param) +{ + return ciAddFilter(chat, TYPE_CDKEY, NULL, NULL, (void*)callback, NULL, param, NULL); +} + +/***************** +** MODE PARSING ** +*****************/ +static ciModeChange * ciParseMode(char * mode, char ** params, int numParams) +{ + CHATBool enable; + int c; + ciModeChange * changes = NULL; + int numChanges = 0; + ciModeChange * change; + int modeChange; + CHATBool addParam = CHATFalse; + void * tempPtr; + + assert(mode != NULL); + + // Check the initial enable mode. + ///////////////////////////////// + if(*mode == '+') + enable = CHATTrue; + else if(*mode == '-') + enable = CHATFalse; + else + return NULL; //ERRCON + mode++; + + // Go through the mode string. + ////////////////////////////// + do + { + // Get the next character. + ////////////////////////// + c = *mode++; + + // Interpret the char. + ////////////////////// + switch(c) + { + case '+': + enable = CHATTrue; + modeChange = -1; + break; + + case '-': + enable = CHATFalse; + modeChange = -1; + break; + + case '\0': + modeChange = MODE_END; + addParam = CHATFalse; + break; + + case 'i': + modeChange = MODE_INVITE_ONLY; + addParam = CHATFalse; + break; + + case 'l': + modeChange = MODE_LIMIT; + addParam = CHATTrue; + break; + + case 'p': + modeChange = MODE_PRIVATE; + addParam = CHATFalse; + break; + + case 's': + modeChange = MODE_SECRET; + addParam = CHATFalse; + break; + + case 'k': + modeChange = MODE_KEY; + addParam = CHATTrue; + break; + + case 'm': + modeChange = MODE_MODERATED; + addParam = CHATFalse; + break; + + case 'n': + modeChange = MODE_NO_EXTERNAL_MESSAGES; + addParam = CHATFalse; + break; + + case 't': + modeChange = MODE_ONLY_OPS_CHANGE_TOPIC; + addParam = CHATFalse; + break; + + case 'o': + modeChange = MODE_OP; + addParam = CHATTrue; + break; + + case 'v': + modeChange = MODE_VOICE; + addParam = CHATTrue; + break; + + case 'b': + modeChange = MODE_BAN; + addParam = CHATTrue; + break; + + case 'u': + modeChange = MODE_USERS_HIDDEN; + addParam = CHATFalse; + break; + + case 'w': + modeChange = MODE_RECEIVE_WALLOPS; + addParam = CHATFalse; + break; + + case 'e': + modeChange = MODE_OPS_OBEY_CHANNEL_LIMIT; + addParam = CHATFalse; + break; + + default: + // Unknown mode. + //////////////// + //assert(0); + modeChange = -1; + } + + // Make the change. + /////////////////// + if(modeChange != -1) + { + tempPtr = gsirealloc(changes, sizeof(ciModeChange) * (numChanges + 1)); + if(tempPtr == NULL) + { + gsifree(changes); + return NULL; //ERRCON + } + changes = (ciModeChange *)tempPtr; + change = &changes[numChanges++]; + memset(change, 0, sizeof(ciModeChange)); + change->enable = enable; + change->mode = modeChange; + + // Add a param if needed. + ///////////////////////// + if(addParam) + { + // PANTS - 09.27.00 - changed to work even if no param + if(numParams > 0) + { + change->param = *params++; + numParams--; + } + else + change->param = NULL; + } + } + } + while(c != '\0'); + + return changes; +} + +static void ciApplyChangesToMode(CHATChannelMode * mode, ciModeChange * changes) +{ + ciModeChange * change; + + // Go through all the changes. + ////////////////////////////// + for(change = changes ; change->mode != MODE_END ; change++) + { + switch(change->mode) + { + case MODE_BAN: + break; + + case MODE_INVITE_ONLY: + mode->InviteOnly = change->enable; + break; + + case MODE_LIMIT: + if(change->enable && change->param) + mode->Limit = atoi(change->param); + else + mode->Limit = 0; + break; + + case MODE_PRIVATE: + mode->Private = change->enable; + break; + + case MODE_SECRET: + mode->Secret = change->enable; + break; + + case MODE_KEY: + break; + + case MODE_MODERATED: + mode->Moderated = change->enable; + break; + + case MODE_NO_EXTERNAL_MESSAGES: + mode->NoExternalMessages = change->enable; + break; + + case MODE_ONLY_OPS_CHANGE_TOPIC: + mode->OnlyOpsChangeTopic = change->enable; + break; + + case MODE_OP: + break; + + case MODE_VOICE: + break; + + case MODE_USERS_HIDDEN: + break; + + case MODE_RECEIVE_WALLOPS: + break; + + case MODE_OPS_OBEY_CHANNEL_LIMIT: + mode->OpsObeyChannelLimit = change->enable; + break; + + default: + assert(0); + } + } +} + +/************* +** HANDLERS ** +*************/ +void ciPrivmsgHandler(CHAT chat, const ciServerMessage * message) +{ + char * ctcp; + char * target; + char * from; + CHATBool action = CHATFalse; + char * msg; + int len; + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciPrivmsgHandler called\n"); +#endif + + assert(message->numParams == 2); + if(message->numParams != 2) + return; //ERRCON + + target = message->params[0]; + msg = message->params[1]; + from = message->nick; + + // Check for CTCP. + ////////////////// + len = (int)strlen(msg); + ctcp = ""; + if((msg[0] == '\001') && IS_ALPHA(msg[1]) && (msg[len - 1] == '\001')) + { + char * str; + + // Strip the ending \001. + // PANTS|08.14.00 + ///////////////////////// + msg[len - 1] = '\0'; + + // End the command. + /////////////////// + str = strchr(msg, ' '); + if(str != NULL) + { + // The start of the CTCP command. + ///////////////////////////////// + ctcp = &msg[1]; + + // End the ctcp. + //////////////// + *str = '\0'; + msg = (str + 1); + } + } + + // Is it an action? + /////////////////// + if(strcmp(ctcp, "ACTION") == 0) + { + // It's an action. + ////////////////// + action = CHATTrue; + } +#if 0 + // Is it a ping? + //////////////// + else if(strcmp(ctcp, "PING") == 0) + { + // Send back a ping. + //////////////////// + ciSocketSendf(&connection->chatSocket, "NOTICE %s :\001PING %s\001", from, msg); + + return; + } +#endif + else if(ctcp[0] != '\0') + { + // Unsupported action, ignore. + ////////////////////////////// + return; + } + + // Is it a private message? + /////////////////////////// + if(strcasecmp(target, connection->nick) == 0) + { + if(connection->globalCallbacks.privateMessage != NULL) + { + ciCallbackPrivateMessageParams params; + params.user = from; + params.message = msg; + if(action) + params.type = CHAT_ACTION; + else + params.type = CHAT_MESSAGE; + ciAddCallback(chat, CALLBACK_PRIVATE_MESSAGE, (void*)connection->globalCallbacks.privateMessage, ¶ms, connection->globalCallbacks.param, 0, NULL); + } + } + else + { + // Get the channel callbacks. + ///////////////////////////// + chatChannelCallbacks * callbacks = ciGetChannelCallbacks(chat, target); + if((callbacks != NULL) && (callbacks->channelMessage != NULL)) + { + ciCallbackChannelMessageParams params; + params.channel = target; + params.user = from; + params.message = msg; + if(action) + params.type = CHAT_ACTION; + else + params.type = CHAT_MESSAGE; + ciAddCallback(chat, CALLBACK_CHANNEL_MESSAGE, (void*)callbacks->channelMessage, ¶ms, callbacks->param, 0, target); + } + } +} + +void ciNoticeHandler(CHAT chat, const ciServerMessage * message) +{ + char * target; + char * msg; + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciNoticeHandler called\n"); +#endif + + assert(message->numParams == 2); + if(message->numParams != 2) + return; //ERRCON + + target = message->params[0]; + msg = message->params[1]; + + // Is it a private message? + /////////////////////////// + if(strcasecmp(target, connection->nick) == 0) + { + if(connection->globalCallbacks.privateMessage != NULL) + { + ciCallbackPrivateMessageParams params; + if(message->nick != NULL) + params.user = message->nick; + else + params.user = NULL; + params.message = msg; + params.type = CHAT_NOTICE; + ciAddCallback(chat, CALLBACK_PRIVATE_MESSAGE, (void*)connection->globalCallbacks.privateMessage, ¶ms, connection->globalCallbacks.param, 0, NULL); + } + } + else + { + // Get the channel callbacks. + ///////////////////////////// + chatChannelCallbacks * callbacks = ciGetChannelCallbacks(chat, target); + if((callbacks != NULL) && (callbacks->channelMessage != NULL)) + { + ciCallbackChannelMessageParams params; + params.channel = target; + if(message->nick != NULL) + params.user = message->nick; + else + params.user = NULL; + params.message = msg; + params.type = CHAT_NOTICE; + ciAddCallback(chat, CALLBACK_CHANNEL_MESSAGE, (void*)callbacks->channelMessage, ¶ms, callbacks->param, 0, target); + } + } +} + +void ciUTMHandler(CHAT chat, const ciServerMessage * message) +{ + char * target; + char * msg; + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciUTMHandler called\n"); +#endif + + assert(message->numParams == 2); + if(message->numParams != 2) + return; //ERRCON + + target = message->params[0]; + msg = message->params[1]; + + // Is it a private message? + /////////////////////////// + if(strcasecmp(target, connection->nick) == 0) + { + if(connection->globalCallbacks.privateMessage != NULL) + { + ciCallbackPrivateMessageParams params; + if(message->nick != NULL) + params.user = message->nick; + else + params.user = NULL; + params.message = msg; + params.type = CHAT_UTM; + ciAddCallback(chat, CALLBACK_PRIVATE_MESSAGE, (void*)connection->globalCallbacks.privateMessage, ¶ms, connection->globalCallbacks.param, 0, NULL); + } + } + else + { + // Get the channel callbacks. + ///////////////////////////// + chatChannelCallbacks * callbacks = ciGetChannelCallbacks(chat, target); + if((callbacks != NULL) && (callbacks->channelMessage != NULL)) + { + ciCallbackChannelMessageParams params; + params.channel = target; + if(message->nick != NULL) + params.user = message->nick; + else + params.user = NULL; + params.message = msg; + params.type = CHAT_UTM; + ciAddCallback(chat, CALLBACK_CHANNEL_MESSAGE, (void*)callbacks->channelMessage, ¶ms, callbacks->param, 0, target); + } + } +} + +void ciATMHandler(CHAT chat, const ciServerMessage * message) +{ + char * target; + char * msg; + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciATMHandler called\n"); +#endif + + assert(message->numParams == 2); + if(message->numParams != 2) + return; //ERRCON + + target = message->params[0]; + msg = message->params[1]; + + // Is it a private message? + /////////////////////////// + if(strcasecmp(target, connection->nick) == 0) + { + if(connection->globalCallbacks.privateMessage != NULL) + { + ciCallbackPrivateMessageParams params; + if(message->nick != NULL) + params.user = message->nick; + else + params.user = NULL; + params.message = msg; + params.type = CHAT_ATM; + ciAddCallback(chat, CALLBACK_PRIVATE_MESSAGE, (void*)connection->globalCallbacks.privateMessage, ¶ms, connection->globalCallbacks.param, 0, NULL); + } + } + else + { + // Get the channel callbacks. + ///////////////////////////// + chatChannelCallbacks * callbacks = ciGetChannelCallbacks(chat, target); + if((callbacks != NULL) && (callbacks->channelMessage != NULL)) + { + ciCallbackChannelMessageParams params; + params.channel = target; + if(message->nick != NULL) + params.user = message->nick; + else + params.user = NULL; + params.message = msg; + params.type = CHAT_ATM; + ciAddCallback(chat, CALLBACK_CHANNEL_MESSAGE, (void*)callbacks->channelMessage, ¶ms, callbacks->param, 0, target); + } + } +} + +void ciPingHandler(CHAT chat, const ciServerMessage * message) +{ + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciPingHandler called\n"); +#endif + + ciSocketSendf(&connection->chatSocket, "PONG %s", message->param); +} + +void ciNickHandler(CHAT chat, const ciServerMessage * message) +{ + char * oldNick; + char * newNick; + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciNickHandler called\n"); +#endif + + assert(message->numParams == 1); + if(message->numParams != 1) + return; //ERRCON + + oldNick = message->nick; + newNick = message->params[0]; + + // Is this me? + ////////////// + if(strcasecmp(oldNick, connection->nick) == 0) + { + ciServerMessageFilter * filter; + ciFilterMatch match; + + // Copy the new nick. + ///////////////////// + assert(strlen(newNick) < MAX_NICK); + strzcpy(connection->nick, newNick, MAX_NICK); + + // Copy to the unicode version +#ifdef GSI_UNICODE + AsciiToUCS2String(connection->nick, connection->nickW); +#endif + + // Setup the filter match. + ////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_NICK; + match.name = oldNick; + match.name2 = newNick; + + // Find the filter. + /////////////////// + filter = ciFindFilter(chat, 1, &match); + if(filter) + { + // Add the callback. + //////////////////// + ciCallbackChangeNickParams params; + params.success = CHATTrue; + params.oldNick = oldNick; + params.newNick = newNick; + + FINISH_FILTER; + } + } + + // Change the nick. + /////////////////// + ciUserChangedNick(chat, oldNick, newNick); +} + +void ciJoinHandler(CHAT chat, const ciServerMessage * message) +{ + char * channel; + char * nick; + char * user; + char * address; + int mode; + chatChannelCallbacks * callbacks; + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciJoinHandler called\n"); +#endif + + assert(message->numParams == 1); + if(message->numParams != 1) + return; //ERRCON + + channel = message->params[0]; + nick = message->nick; + user = message->user; + address = message->host; + + // Check for mode. + ////////////////// + if(*nick == '@') + { + mode = CHAT_OP; + nick++; + assert(*nick != '\0'); + } + else if(*nick == '+') + { + mode = CHAT_VOICE; + nick++; + assert(*nick != '\0'); + } + else + { + mode = CHAT_NORMAL; + } + + // Me? + ////// + if(strcmp(nick, connection->nick) == 0) + { + ciServerMessageFilter * filter; + ciFilterMatch match; + + // Make sure we're entering this channel. + ///////////////////////////////////////// + if(!ciIsEnteringChannel(chat, channel)) + return; + + // Setup the filter match. + ////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_JOIN; + match.name = channel; + + // Look for a matching filter. + ////////////////////////////// + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + JOINData * data; + + // Get the data. + //////////////// + data = (JOINData *)filter->data; + + // Add the channel. + /////////////////// + ciChannelEntered(chat, channel, &data->callbacks); + + // Set the password. + //////////////////// + ciSetChannelPassword(chat, channel, data->password); + + // Joined. + ////////// + data->joined = CHATTrue; + + // Get the channel's mode. + ////////////////////////// + ciSocketSendf(&connection->chatSocket, "MODE %s", channel); + } + + return; + } + + // Add the user to the channel. + /////////////////////////////// + if(ciInChannel(chat, channel)) + ciUserEnteredChannel(chat, nick, channel, mode, user, address); + + // Was the join callback called? + //////////////////////////////// + if(ciWasJoinCallbackCalled(chat, channel)) //PANTS - 03.01.00 - check if the join callback was called + { + // Call the join callback. + ////////////////////////// + callbacks = ciGetChannelCallbacks(chat, channel); + if(callbacks != NULL) + { + if(callbacks->userJoined != NULL) + { + ciCallbackUserJoinedParams params; + params.channel = channel; + params.user = nick; + params.mode = mode; + ciAddCallback(chat, CALLBACK_USER_JOINED, (void*)callbacks->userJoined, ¶ms, callbacks->param, 0, channel); + } + + // Call the user list updated callback. + /////////////////////////////////////// + if(callbacks->userListUpdated != NULL) + { + ciCallbackUserListUpdatedParams params; + params.channel = channel; + ciAddCallback(chat, CALLBACK_USER_LIST_UPDATED, (void*)callbacks->userListUpdated, ¶ms, callbacks->param, 0, channel); + } + } + } +} + +void ciPartHandler(CHAT chat, const ciServerMessage * message) +{ + char * nick; + char * channel; + char * reason; + chatChannelCallbacks * callbacks; + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciPartHandler called\n"); +#endif + + nick = message->nick; + channel = message->params[0]; + if (message->numParams > 1) //get the reason + reason = message->params[1]; + else + reason = ""; + + // Did we leave the channel? + //////////////////////////// + if(strcmp(nick, connection->nick) == 0) + { + // Ignore it, we already left. + ////////////////////////////// + } + else + { + // Remove the user from the channel. + //////////////////////////////////// + ciUserLeftChannel(chat, nick, channel); + + // Was the join callback called? + //////////////////////////////// + if(ciWasJoinCallbackCalled(chat, channel)) + { + // Call the left callback. + ////////////////////////// + callbacks = ciGetChannelCallbacks(chat, channel); + if(callbacks != NULL) + { + if(callbacks->userParted != NULL) + { + ciCallbackUserPartedParams params; + params.channel = channel; + params.user = nick; + params.why = CHAT_LEFT; + params.reason = reason; + params.kicker = NULL; + ciAddCallback(chat, CALLBACK_USER_PARTED, (void*)callbacks->userParted, ¶ms, callbacks->param, 0, channel); + } + + // Call the user list updated callback. + /////////////////////////////////////// + if(callbacks->userListUpdated != NULL) + { + ciCallbackUserListUpdatedParams params; + params.channel = channel; + ciAddCallback(chat, CALLBACK_USER_LIST_UPDATED, (void*)callbacks->userListUpdated, ¶ms, callbacks->param, 0, channel); + } + } + } + } +} + +void ciKickHandler(CHAT chat, const ciServerMessage * message) +{ + char * channel; + char * kicker; + char * kickee; + char * reason; + chatChannelCallbacks * callbacks; + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciKickHandler called\n"); +#endif + + assert((message->numParams == 2) || (message->numParams == 3)); + if((message->numParams != 2) && (message->numParams != 3)) + return; //ERRCON + + channel = message->params[0]; + kicker = message->nick; + kickee = message->params[1]; + if(message->numParams == 3) + reason = message->params[2]; + else + reason = ""; + + // Remove the user from the channel. + //////////////////////////////////// + ciUserLeftChannel(chat, kickee, channel); + + // Get the callbacks. + ///////////////////// + callbacks = ciGetChannelCallbacks(chat, channel); + if(callbacks != NULL) + { + // Check if we were kicked. + /////////////////////////// + if(strcasecmp(kickee, connection->nick) == 0) + { + // Add the callback. + //////////////////// + if(callbacks->kicked != NULL) + { + ciCallbackKickedParams params; + params.channel = channel; + params.user = kicker; + params.reason = reason; + ciAddCallback(chat, CALLBACK_KICKED, (void*)callbacks->kicked, ¶ms, callbacks->param, 0, NULL); + } + + // Left the channel. + //////////////////// + ciChannelLeft(chat, channel); + } + else + { + // Was the join callback called? + //////////////////////////////// + if(ciWasJoinCallbackCalled(chat, channel)) + { + // Add the callback. + //////////////////// + if(callbacks->userParted != NULL) + { + ciCallbackUserPartedParams params; + params.channel = channel; + params.user = kickee; + params.why = CHAT_KICKED; + params.reason = reason; + params.kicker = kicker; + ciAddCallback(chat, CALLBACK_USER_PARTED, (void*)callbacks->userParted, ¶ms, callbacks->param, 0, channel); + } + + // Call the user list updated callback. + /////////////////////////////////////// + if(callbacks->userListUpdated != NULL) + { + ciCallbackUserListUpdatedParams params; + params.channel = channel; + ciAddCallback(chat, CALLBACK_USER_LIST_UPDATED, (void*)callbacks->userListUpdated, ¶ms, callbacks->param, 0, channel); + } + } + } + } +} + +static void ciQuitEnumChannelsCallback(CHAT chat, const char * user, const char * channel, void * reason) +{ + chatChannelCallbacks * callbacks; + + ASSERT_STR(user); + ASSERT_STR(channel); + assert(reason != NULL); + + // Remove the user from the channel. + //////////////////////////////////// + ciUserLeftChannel(chat, user, channel); + + // Was the join callback called? + //////////////////////////////// + if(ciWasJoinCallbackCalled(chat, channel)) + { + // Get the channel callbacks. + ///////////////////////////// + callbacks = ciGetChannelCallbacks(chat, channel); + if(callbacks != NULL) + { + // Call the quit callback. + ////////////////////////// + if(callbacks->userParted != NULL) + { + ciCallbackUserPartedParams params; + params.channel = (char *)channel; + params.user = (char *)user; + params.why = CHAT_QUIT; + params.reason = (char *)reason; + params.kicker = NULL; + ciAddCallback(chat, CALLBACK_USER_PARTED, (void*)callbacks->userParted, ¶ms, callbacks->param, 0, channel); + } + + // Call the user list updated callback. + /////////////////////////////////////// + if(callbacks->userListUpdated != NULL) + { + ciCallbackUserListUpdatedParams params; + params.channel = (char *)channel; + ciAddCallback(chat, CALLBACK_USER_LIST_UPDATED, (void*)callbacks->userListUpdated, ¶ms, callbacks->param, 0, channel); + } + } + } +} + +void ciQuitHandler(CHAT chat, const ciServerMessage * message) +{ + char * reason; + //CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciQuitHandler called\n"); +#endif + + assert(message->numParams == 1); + if(message->numParams != 1) + return; //ERRCON + + reason = message->params[0]; + + // Enum the channels this user is in. + ///////////////////////////////////// + ciUserEnumChannels(chat, message->nick, ciQuitEnumChannelsCallback, reason); +} + +static void ciKillEnumChannelsCallback(CHAT chat, const char * user, const char * channel, void * param) +{ + chatChannelCallbacks * callbacks; + char *reason = (char *)param; + ASSERT_STR(user); + ASSERT_STR(channel); + assert(reason != NULL); + + // Remove the user from the channel. + //////////////////////////////////// + ciUserLeftChannel(chat, user, channel); + + // Was the join callback called? + //////////////////////////////// + if(ciWasJoinCallbackCalled(chat, channel)) + { + // Get the channel callbacks. + ///////////////////////////// + callbacks = ciGetChannelCallbacks(chat, channel); + if(callbacks != NULL) + { + if(callbacks->userParted != NULL) + { + ciCallbackUserPartedParams params; + params.channel = (char *)channel; + params.user = (char *)user; + params.why = CHAT_KILLED; + params.reason = (char *)reason; + params.kicker = NULL; + ciAddCallback(chat, CALLBACK_USER_PARTED, (void*)callbacks->userParted, ¶ms, callbacks->param, 0, channel); + } + + // Call the user list updated callback. + /////////////////////////////////////// + if(callbacks->userListUpdated != NULL) + { + ciCallbackUserListUpdatedParams params; + params.channel = (char *)channel; + ciAddCallback(chat, CALLBACK_USER_LIST_UPDATED, (void*)callbacks->userListUpdated, ¶ms, callbacks->param, 0, channel); + } + } + } +} + +void ciKillHandler(CHAT chat, const ciServerMessage * message) +{ + char * nick; + char * reason; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciKillHandler called\n"); +#endif + + assert(message->numParams == 2); + if(message->numParams != 2) + return; //ERRCON + + nick = message->params[0]; + reason = message->params[1]; + + // Enum the channels this user is in. + ///////////////////////////////////// + ciUserEnumChannels(chat, nick, ciKillEnumChannelsCallback, reason); +} + +void ciTopicHandler(CHAT chat, const ciServerMessage * message) +{ + char * channel; + char * topic; + chatChannelCallbacks * callbacks; + //CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciTopicHandler called\n"); +#endif + + assert(message->numParams == 2); + if(message->numParams != 2) + return; //ERRCON + + channel = message->params[0]; + topic = message->params[1]; + + // Set the channel's topic. + /////////////////////////// + ciSetChannelTopic(chat, channel, topic); + + // Get the callbacks. + ///////////////////// + callbacks = ciGetChannelCallbacks(chat, channel); + if((callbacks != NULL) && (callbacks->topicChanged != NULL)) + { + ciCallbackTopicChangedParams params; + params.channel = channel; + params.topic = topic; + ciAddCallback(chat, CALLBACK_TOPIC_CHANGED, (void*)callbacks->topicChanged, ¶ms, callbacks->param, 0, channel); + } +} + +void ciModeHandler(CHAT chat, const ciServerMessage * message) +{ + ciModeChange * changes; + ciModeChange * change; + CHATChannelMode channelMode; + char * channel; + char * mode; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciModeHandler called\n"); +#endif + + assert(message->numParams >= 2); + if(message->numParams < 2) + return; //ERRCON + + channel = message->params[0]; + mode = message->params[1]; + + // Check that we're in this channel. + //////////////////////////////////// + if(!ciInChannel(chat, channel)) + return; + + // Parse the changes. + ///////////////////// + changes = ciParseMode(mode, message->params + 2, message->numParams - 2); + if(changes == NULL) + return; //ERRCON + + // Go through all the changes. + ////////////////////////////// + for(change = changes ; change->mode != MODE_END ; change++) + { + switch(change->mode) + { + case MODE_BAN: + break; + + case MODE_INVITE_ONLY: + break; + + case MODE_LIMIT: + break; + + case MODE_PRIVATE: + break; + + case MODE_SECRET: + break; + + case MODE_KEY: + if(change->enable) + ciSetChannelPassword(chat, channel, change->param); + else + ciSetChannelPassword(chat, channel, NULL); + break; + + case MODE_MODERATED: + break; + + case MODE_NO_EXTERNAL_MESSAGES: + break; + + case MODE_ONLY_OPS_CHANGE_TOPIC: + break; + + case MODE_USERS_HIDDEN: + break; + + case MODE_RECEIVE_WALLOPS: + break; + + case MODE_OP: + if(change->param) + ciUserChangedMode(chat, change->param, channel, CHAT_OP, change->enable); + break; + + case MODE_VOICE: + if(change->param) + ciUserChangedMode(chat, change->param, channel, CHAT_VOICE, change->enable); + break; + case MODE_OPS_OBEY_CHANNEL_LIMIT: + break; + default: + assert(0); + } + } + + // Apply the changes to the mode. + ///////////////////////////////// + if(!ciGetChannelMode(chat, channel, &channelMode)) + { + gsifree(changes); + return; //ERRCON + } + ciApplyChangesToMode(&channelMode, changes); + ciSetChannelMode(chat, channel, &channelMode); + + // gsifree the changes. + //////////////////// + gsifree(changes); +} + +void ciErrorHandler(CHAT chat, const ciServerMessage * message) +{ + //CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciErrorHandler called\n"); +#endif + + assert(message->numParams == 1); + if(message->numParams != 1) + return; //ERRCON + + // Handle the disconnection. + //////////////////////////// + ciHandleDisconnect(chat, message->params[0]); +} + +void ciInviteHandler(CHAT chat, const ciServerMessage * message) +{ + char * nick; + char * channel; + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciInviteHandler called\n"); +#endif + + assert(message->numParams == 2); + if(message->numParams != 2) + return; //ERRCON + + // Get info. + //////////// + nick = message->nick; + channel = message->params[1]; + + // Call the invite callback. + //////////////////////////// + if(connection->globalCallbacks.invited != NULL) + { + ciCallbackInvitedParams params; + params.channel = channel; + params.user = nick; + ciAddCallback(chat, CALLBACK_INVITED, (void*)connection->globalCallbacks.invited, ¶ms, connection->globalCallbacks.param, 0, NULL); + } +} + +void ciNameReplyHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch matches[4]; + ciServerMessageFilter * filter; + char * channel; + NAMESData * data = NULL; + char * names; + char * nick; + char * str; + void * tempPtr; + int mode; + int len; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciNameReplyHandler called\n"); +#endif + + assert(message->numParams == 4); + if(message->numParams != 4) + return; //ERRCON + + channel = message->params[2]; + names = message->params[3]; + + // Setup the filter matches. + //////////////////////////// + memset(matches, 0, sizeof(matches)); + matches[0].type = TYPE_JOIN; + matches[0].name = channel; + matches[1].type = TYPE_UNQUIET; + matches[1].name = channel; + matches[2].type = TYPE_NAMES; + matches[2].name = channel; + matches[3].type = TYPE_NAMES; + + // Check for a matching filter. + /////////////////////////////// + filter = ciFindFilter(chat, 4, matches); + if(!filter) + return; + + // Get the data. + //////////////// + if(filter->type != TYPE_JOIN) + data = (NAMESData *)filter->data; + + // Parse out the names. + /////////////////////// + nick = strtok(names, " "); + while(nick != NULL) + { + assert(nick[0] != '\0'); + + // Check the mode. + ////////////////// + if(nick[0] == '@') + { + assert(nick[1] != '\0'); + mode = CHAT_OP; + nick++; + } + else if(nick[0] == '+') + { + assert(nick[1] != '\0'); + mode = CHAT_VOICE; + nick++; + } + else + mode = CHAT_NORMAL; + + if(filter->type != TYPE_JOIN) + { + // Check for resize. + //////////////////// + if(data->numUsers == data->len) + { + tempPtr = (char **)gsirealloc(data->users, sizeof(char *) * (data->len + NAMES_ARRAY_INC)); + if(tempPtr == NULL) + { + assert(0); + return; //ERRCON + } + data->users = (char **)tempPtr; + tempPtr = (char **)gsirealloc(data->modes, sizeof(int) * (data->len + NAMES_ARRAY_INC)); + if(tempPtr == NULL) + { + assert(0); + return; //ERRCON + } + data->modes = (int *)tempPtr; + data->len += NAMES_ARRAY_INC; + } + + // Allocate mem for the nick. + ///////////////////////////// + len = (int)(strlen(nick) + 1); + str = (char *)gsimalloc((unsigned int)len); + if(str == NULL) + { + assert(0); + return; //ERRCON + } + memcpy(str, nick, (unsigned int)len); + + // Set the pointers. + //////////////////// + data->users[data->numUsers] = str; + data->modes[data->numUsers] = mode; + data->numUsers++; + } + + if((filter->type == TYPE_JOIN) || (filter->type == TYPE_UNQUIET)) + { + // Add the nick to the channel. + /////////////////////////////// + ciUserEnteredChannel(chat, nick, channel, mode, NULL, NULL); + } + + // Get the next nick. + ///////////////////// + nick = strtok(NULL, " "); + } +} + +void ciEndOfNamesHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch matches[4]; + ciServerMessageFilter * filter; + char * channel; + //CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciEndOfNamesHandler called\n"); +#endif + + assert(message->numParams == 3); + if(message->numParams != 3) + return; //ERRCON + + // Get the channel. + /////////////////// + channel = message->params[1]; + + // Check for the special case "all channels". + ///////////////////////////////////////////// + if(strcmp(channel, "*") == 0) + channel = NULL; + + // Setup the filter matches. + //////////////////////////// + memset(matches, 0, sizeof(matches)); + matches[0].type = TYPE_JOIN; + matches[0].name = channel; + matches[1].type = TYPE_UNQUIET; + matches[1].name = channel; + matches[2].type = TYPE_NAMES; + matches[2].name = channel; + matches[3].type = TYPE_NAMES; + + // Look for a matching filter. + ////////////////////////////// + filter = ciFindFilter(chat, 4, matches); + if(!filter) + return; + + // Join filter? + /////////////// + if(filter->type == TYPE_JOIN) + { + ciCallbackEnterChannelParams params; + params.success = CHATTrue; + params.result = CHATEnterSuccess; + params.channel = channel; + + if(!filter->callback) + { + // There's no callback, so set the callback called flag now. + //////////////////////////////////////////////////////////// + ciJoinCallbackCalled(chat, channel); + } + + FINISH_FILTER; + + return; + } + + // Unquiet filter? + //////////////////// + if(filter->type == TYPE_UNQUIET) + { + NAMESData * data = (NAMESData *)filter->data; + ciCallbackNewUserListParams params; + params.channel = channel; + params.numUsers = data->numUsers; + params.users = data->users; + params.modes = data->modes; + + FINISH_FILTER; + + return; + } + + // Names filter? + //////////////// + if(filter->type == TYPE_NAMES) + { + NAMESData * data = (NAMESData *)filter->data; + ciCallbackEnumUsersParams params; + params.success = CHATTrue; + params.channel = channel; + params.numUsers = data->numUsers; + params.users = data->users; + params.modes = data->modes; + + FINISH_FILTER; + + return; + } +} + +void ciRplTopicHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + char * topic; + char * channel; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplTopicHandler called\n"); +#endif + + assert(message->numParams == 3); + if(message->numParams != 3) + return; //ERRCON + + channel = message->params[1]; + topic = message->params[2]; + + // Set the channel's topic. + /////////////////////////// + ciSetChannelTopic(chat, channel, topic); + + // Setup a filter match. + //////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_TOPIC; + match.name = channel; + + // Find the filter. + /////////////////// + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + ciCallbackGetChannelTopicParams params; + params.success = CHATTrue; + params.channel = channel; + params.topic = topic; + + FINISH_FILTER; + } + else + { + // No filter, probably checking the topic on join. + ////////////////////////////////////////////////// + chatChannelCallbacks * callbacks; + callbacks = ciGetChannelCallbacks(chat, channel); + if((callbacks != NULL) && (callbacks->topicChanged != NULL)) + { + ciCallbackTopicChangedParams params; + params.channel = channel; + params.topic = topic; + ciAddCallback(chat, CALLBACK_TOPIC_CHANGED, (void*)callbacks->topicChanged, ¶ms, callbacks->param, 0, channel); + } + } +} + +void ciRplNoTopicHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + char * channel; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplNoTopicHandler called\n"); +#endif + + assert(message->numParams >= 2); + if(message->numParams < 2) + return; //ERRCON + + // Get the channel. + /////////////////// + channel = message->params[1]; + + // Setup the filter match. + ////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_TOPIC; + match.name = channel; + + // Find the filter. + /////////////////// + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + ciCallbackGetChannelTopicParams params; + params.success = CHATTrue; + params.channel = channel; + params.topic = ""; + + FINISH_FILTER; + } + else + { + // No filter, probably checking the topic on join. + ////////////////////////////////////////////////// + chatChannelCallbacks * callbacks; + callbacks = ciGetChannelCallbacks(chat, channel); + if((callbacks != NULL) && (callbacks->topicChanged != NULL)) + { + ciCallbackTopicChangedParams params; + params.channel = channel; + params.topic = ""; + ciAddCallback(chat, CALLBACK_TOPIC_CHANGED, (void*)callbacks->topicChanged, ¶ms, callbacks->param, 0, channel); + } + } +} + +void ciErrNickInUseHandler(CHAT chat, const ciServerMessage * message) +{ + char * oldNick; + char * newNick; + ciServerMessageFilter * filter; + ciFilterMatch match; + + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplNickErrorHandler called\n"); +#endif + + assert(message->numParams == 3); + if(message->numParams != 3) + return; //ERRCON + + oldNick = message->params[0]; + newNick = message->params[1]; + + // Setup the filter match. + ////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_NICK; + match.name = oldNick; + match.name2 = newNick; + + // Find the filter. + /////////////////// + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + // Add the callback. + //////////////////// + ciCallbackChangeNickParams params; + params.success = CHATFalse; + params.oldNick = oldNick; + params.newNick = newNick; + + FINISH_FILTER; + } + else + { + // We should be connecting. + ////////////////////////// + assert(connection->connecting); + if(connection->connecting) + ciNickError(chat, CHAT_IN_USE, connection->nick, 0, NULL); + } +} + +void ciRplWhoReplyHandler(CHAT chat, const ciServerMessage * message) +{ + char * channel; + char * nick; + char * user; + char * address; + ciServerMessageFilter * filter; + ciFilterMatch matches[3]; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplWhoReplyHandler called\n"); +#endif + + assert(message->numParams == 8); + if(message->numParams != 8) + return; //ERRCON + + channel = message->params[1]; + user = message->params[2]; + address = message->params[3]; + nick = message->params[5]; + + // Cache the user and address. + ////////////////////////////// + ciSetUserBasicInfo(chat, nick, user, address); + + // Setup the filter matches. + //////////////////////////// + memset(matches, 0, sizeof(matches)); + matches[0].type = TYPE_UMODE; + matches[0].name = nick; + matches[0].name2 = channel; + matches[1].type = TYPE_WHO; + matches[1].name = nick; + matches[2].type = TYPE_CWHO; + matches[2].name = channel; + + // Find the filter. + /////////////////// + filter = ciFindFilter(chat, 3, matches); + if(!filter) + return; + + // User mode filter? + //////////////////// + if(filter->type == TYPE_UMODE) + { + ciCallbackGetUserModeParams params; + int mode; + + // Check the mode. + ////////////////// + mode = 0; + if(strchr(message->params[6], '@')) + mode |= CHAT_OP; + if(strchr(message->params[6], '+')) + mode |= CHAT_VOICE; + + params.success = CHATTrue; + params.channel = channel; + params.user = nick; + params.mode = mode; + + FINISH_FILTER; + + return; + } + + // Who filter? + ////////////// + if(filter->type == TYPE_WHO) + { + ciCallbackGetBasicUserInfoParams params; + + params.success = CHATTrue; + params.nick = nick; + params.user = user; + params.address = address; + + ciAddCallback(chat, CALLBACK_GET_BASIC_USER_INFO, filter->callback, ¶ms, filter->param, filter->ID, NULL); + + // We want to wait until we get the end, but we've already called the callback. + /////////////////////////////////////////////////////////////////////////////// + filter->callback = NULL; + + return; + } + + // Who filter? + ////////////// + if((filter->type == TYPE_CWHO) && filter->callback) + { + ciCallbackGetChannelBasicUserInfoParams params; + + params.success = CHATTrue; + params.channel = filter->name; + params.nick = nick; + params.user = user; + params.address = address; + + ciAddCallback(chat, CALLBACK_GET_CHANNEL_BASIC_USER_INFO, filter->callback, ¶ms, filter->param, filter->ID, NULL); + + return; + } +} + +void ciRplEndOfWhoHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch matches[2]; + ciServerMessageFilter * filter; + char * name; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplEndOfWhoHandler called\n"); +#endif + + assert(message->numParams == 3); + if(message->numParams != 3) + return; //ERRCON + + name = message->params[1]; + + // Setup the filter matches. + //////////////////////////// + memset(matches, 0, sizeof(matches)); + matches[0].type = TYPE_WHO; + matches[0].name = name; + matches[1].type = TYPE_CWHO; + matches[1].name = name; + + // Find the filter. + /////////////////// + filter = ciFindFilter(chat, 2, matches); + if(!filter) + return; + + // Who filter? + ////////////// + if(filter->type == TYPE_WHO) + { + ciCallbackGetBasicUserInfoParams params; + + params.success = CHATFalse; + params.nick = name; + params.user = NULL; + params.address = NULL; + + FINISH_FILTER; + + return; + } + + // Channel who filter? + ////////////////////// + if(filter->type == TYPE_CWHO) + { + ciCallbackGetChannelBasicUserInfoParams params; + + params.success = CHATTrue; + params.channel = name; + params.nick = NULL; + params.user = NULL; + params.address = NULL; + + FINISH_FILTER; + + return; + } +} + +static char * ciParseValue(const char * flags, int * len) +{ + int i; + char * str; + + assert(flags); + assert(len); + + // First should be a '\'. + ///////////////////////// + if(!flags || (flags[0] != '\\')) + return NULL; + + // Skip it. + /////////// + flags++; + + // Find the end of the value. + ///////////////////////////// + for(i = 0 ; flags[i] && (flags[i] != '\\') ; i++) { }; + + // Allocate it. + /////////////// + str = (char *)gsimalloc((unsigned int)i + 1); + if(!str) + return NULL; + + // Copy it in. + ////////////// + memcpy(str, flags, (unsigned int)i); + str[i] = '\0'; + + // Return it. + ///////////// + *len = (i + 1); + return str; +} + +void ciRplGetKeyHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + const char * nick; + const char * cookie; + const char * flags; + int num; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplGetKeyHandler called\n"); +#endif + + assert(message->numParams == 4); + if(message->numParams != 4) + return; //ERRCON + + nick = message->params[1]; + cookie = message->params[2]; + flags = message->params[3]; + + // Setup the filter match. + ////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_GETKEY; + match.name = cookie; + + // Find the filter. + /////////////////// + filter = ciFindFilter(chat, 1, &match); + if(filter) + { + GETKEYData * data; + ciCallbackGetGlobalKeysParams params; + char ** values; + char * str; + int i; + int len; + + data = (GETKEYData *)filter->data; + num = data->num; + + // Allocate the values. + /////////////////////// + values = (char **)gsimalloc(sizeof(char *) * num); + if(!values) + return; + + // Parse them out. + ////////////////// + for(i = 0 ; i < num ; i++) + { + str = ciParseValue(flags, &len); + if(str) + { + values[i] = str; + flags += len; + } + else + { + values[i] = goastrdup(""); + } + } + + // Setup the callback parameters. + ///////////////////////////////// + params.success = CHATTrue; + params.user = (char *)nick; + params.num = num; + params.keys = data->keys; + params.values = values; + + // If this is a 1-user get, we're finished. + /////////////////////////////////////////// + if(!data->channel) + { + FINISH_FILTER; + } + else + { + ciAddCallback(chat, CALLBACK_GET_GLOBAL_KEYS, filter->callback, ¶ms, filter->param, filter->ID, NULL); + } + + for(i = 0 ; i < num ; i++) + gsifree(values[i]); + gsifree(values); + } +} + +void ciRplEndGetKeyHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + const char * cookie; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplEndGetKeyHandler called\n"); +#endif + + assert(message->numParams == 4); + if(message->numParams != 4) + return; //ERRCON + + cookie = message->params[2]; + + // Setup the filter match. + ////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_GETKEY; + match.name = cookie; + + // Find the filter. + /////////////////// + filter = ciFindFilter(chat, 1, &match); + if(filter) + { + ciCallbackGetGlobalKeysParams params; + GETKEYData * data; + + data = (GETKEYData *)filter->data; + + // Setup the callback parameters. + ///////////////////////////////// + params.success = CHATTrue; + params.user = NULL; + params.num = data->num; + params.keys = data->keys; + params.values = NULL; + + FINISH_FILTER; + } +} + +void ciRplGetCKeyHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + const char * nick; + const char * cookie; + char * flags; + const char * channel; + int num; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplGetCKeyHandler called\n"); +#endif + + assert(message->numParams == 5); + if(message->numParams != 5) + return; //ERRCON + + channel = message->params[1]; + nick = message->params[2]; + cookie = message->params[3]; + flags = message->params[4]; + + // Check for a broadcast update. + //////////////////////////////// + if(strcmp(cookie, "BCAST") == 0) + { + chatChannelCallbacks * callbacks; + ciCallbackBroadcastKeyChangedParams params; + char * key; + char * value; + int temp; + + callbacks = ciGetChannelCallbacks(chat, channel); + if(callbacks && callbacks->broadcastKeyChanged) + { + memset(¶ms, 0, sizeof(ciCallbackBroadcastKeyChangedParams)); + params.channel = (char *)channel; + params.user = (char *)nick; + + while(*flags) + { + key = strstr(flags, "b_"); + flags = key; + while(*flags && (*flags != '\\')) + flags++; + if(!*flags) + break; + *flags++ = '\0'; + value = flags; + while(*flags && (*flags != '\\')) + flags++; + temp = *flags; + *flags = '\0'; + + // Call the callback. + ///////////////////// + params.key = key; + params.value = value; + ciAddCallback(chat, CALLBACK_BROADCAST_KEY_CHANGED, (void*)callbacks->broadcastKeyChanged, ¶ms, callbacks->param, 0, channel); + + *flags = (char)temp; + } + } + + return; + } + + // Setup the filter match. + ////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_GETCKEY; + match.name = cookie; + + // Find the filter. + /////////////////// + filter = ciFindFilter(chat, 1, &match); + if(filter) + { + GETCKEYData * data; + ciCallbackGetChannelKeysParams params; + char ** values; + char * key; + char * value; + int i; + int len; + char ** tempPtr; + + data = (GETCKEYData *)filter->data; + num = data->num; + + // Allocate the values. + /////////////////////// + values = (char **)gsimalloc(sizeof(char *) * num); + if(!values) + return; + + // Parse them out. + ////////////////// + for(i = 0 ; i < num ; i++) + { + value = ciParseValue(flags, &len); + values[i] = value; + if(value) + flags += len; + } + + // Do we need to do b_* stuff? + ////////////////////////////// + if(data->allBroadcastKeys) + { + while((key = ciParseValue(flags, &len)) != NULL) + { + flags += len; + value = ciParseValue(flags, &len); + if(value) + { + flags += len; + + // Add this key and value to our list. + ////////////////////////////////////// + tempPtr = (char **)gsirealloc(data->keys, sizeof(char *) * (num + 1)); + if(tempPtr) + { + data->keys = tempPtr; + tempPtr = (char **)gsirealloc(values, sizeof(char *) * (num + 1)); + if(tempPtr) + { + values = tempPtr; + data->keys[num] = key; + values[num] = value; + num++; + } + else + { + gsifree(key); + gsifree(value); + } + } + else + { + gsifree(key); + gsifree(value); + } + } + else + { + gsifree(key); + break; + } + } + + // The new number of keys. + ////////////////////////// + data->num = num; + } + + // Setup the callback parameters. + ///////////////////////////////// + params.success = CHATTrue; + params.channel = (char *)channel; + params.user = (char *)nick; + params.num = num; + params.keys = data->keys; + params.values = values; + + // If this is a 1-user get, we're finished. + /////////////////////////////////////////// + if(!data->channel) + { + FINISH_FILTER; + } + else + { + ciAddCallback(chat, CALLBACK_GET_CHANNEL_KEYS, filter->callback, ¶ms, filter->param, filter->ID, NULL); + } + + for(i = 0 ; i < num ; i++) + gsifree(values[i]); + gsifree(values); + } +} + +void ciRplEndGetCKeyHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + const char * cookie; + const char * channel; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplEndGetCKeyHandler called\n"); +#endif + + assert(message->numParams == 4); + if(message->numParams != 4) + return; //ERRCON + + channel = message->params[1]; + cookie = message->params[2]; + + // Setup the filter match. + ////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_GETCKEY; + match.name = cookie; + + // Find the filter. + /////////////////// + filter = ciFindFilter(chat, 1, &match); + if(filter) + { + GETCKEYData * data; + ciCallbackGetChannelKeysParams params; + + data = (GETCKEYData *)filter->data; + + // Setup the callback parameters. + ///////////////////////////////// + params.success = CHATTrue; + params.channel = (char *)channel; + params.user = NULL; + params.num = data->num; + params.keys = data->keys; + params.values = NULL; + + FINISH_FILTER; + } +} + +void ciRplGetChanKeyHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + const char * cookie; + char * flags; + const char * channel; + int num; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplGetChanKeyHandler called\n"); +#endif + + assert(message->numParams == 4); + if(message->numParams != 4) + return; //ERRCON + + channel = message->params[1]; + cookie = message->params[2]; + flags = message->params[3]; + + // Check for a broadcast update. + //////////////////////////////// + if(strcmp(cookie, "BCAST") == 0) + { + chatChannelCallbacks * callbacks; + ciCallbackBroadcastKeyChangedParams params; + char * key; + char * value; + int temp; + + callbacks = ciGetChannelCallbacks(chat, channel); + if(callbacks && callbacks->broadcastKeyChanged) + { + memset(¶ms, 0, sizeof(ciCallbackBroadcastKeyChangedParams)); + params.channel = (char *)channel; + params.user = NULL; + + while(*flags) + { + key = strstr(flags, "b_"); + flags = key; + while(*flags && (*flags != '\\')) + flags++; + if(!*flags) + break; + *flags++ = '\0'; + value = flags; + while(*flags && (*flags != '\\')) + flags++; + temp = *flags; + *flags = '\0'; + + // Call the callback. + ///////////////////// + params.key = key; + params.value = value; + ciAddCallback(chat, CALLBACK_BROADCAST_KEY_CHANGED, (void*)callbacks->broadcastKeyChanged, ¶ms, callbacks->param, 0, channel); + + *flags = (char)temp; + } + } + + return; + } + + // Setup the filter match. + ////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_GETCHANKEY; + match.name = cookie; + + // Find the filter. + /////////////////// + filter = ciFindFilter(chat, 1, &match); + if(filter) + { + GETCHANKEYData * data; + ciCallbackGetChannelKeysParams params; + char ** values; + char ** keys = NULL; + char * key; + char * value; + int i; + int len; + char ** tempPtr; + + data = (GETCHANKEYData *)filter->data; + num = data->num; + + // Allocate the values. + /////////////////////// + if(num) + { + values = (char **)gsimalloc(sizeof(char *) * num); + if(!values) + return; + + // Parse them out. + ////////////////// + for(i = 0 ; i < num ; i++) + { + value = ciParseValue(flags, &len); + values[i] = value; + if(value) + flags += len; + } + + // Do we need to do b_* stuff? + ////////////////////////////// + if(data->allBroadcastKeys) + { + while((key = ciParseValue(flags, &len)) != NULL) + { + flags += len; + value = ciParseValue(flags, &len); + if(value) + { + flags += len; + + // Add this key and value to our list. + ////////////////////////////////////// + tempPtr = (char **)gsirealloc(data->keys, sizeof(char *) * (num + 1)); + if(tempPtr) + { + data->keys = tempPtr; + tempPtr = (char **)gsirealloc(values, sizeof(char *) * (num + 1)); + if(tempPtr) + { + values = tempPtr; + data->keys[num] = key; + values[num] = value; + num++; + } + else + { + gsifree(key); + gsifree(value); + } + } + else + { + gsifree(key); + gsifree(value); + } + } + else + { + gsifree(key); + break; + } + } + + // The new number of keys. + ////////////////////////// + data->num = num; + } + } + else + { + char ** keysTemp; + char ** valuesTemp; + + keys = NULL; + values = NULL; + num = 0; + while(1) + { + key = ciParseValue(flags, &len); + if(!key) + break; + flags += len; + value = ciParseValue(flags, &len); + if(!value) + { + gsifree(key); + break; + } + flags += len; + + keysTemp = (char **)gsirealloc(keys, sizeof(char *) * (num + 1)); + valuesTemp = (char **)gsirealloc(values, sizeof(char *) * (num + 1)); + if(!keysTemp || !valuesTemp) + { + gsifree(key); + gsifree(value); + while(num--) + { + gsifree(keys[num]); + gsifree(values[num]); + } + if(keysTemp) + gsifree(keysTemp); + else + gsifree(keys); + if(valuesTemp) + gsifree(valuesTemp); + else + gsifree(values); + } + + keys = keysTemp; + keys[num] = key; + values = valuesTemp; + values[num] = value; + + num++; + } + + data->num = num; + data->keys = keys; + } + + // Setup the callback parameters. + ///////////////////////////////// + params.success = CHATTrue; + params.channel = (char *)channel; + params.user = NULL; + params.num = num; + params.keys = data->keys; + params.values = values; + + FINISH_FILTER; + + for(i = 0 ; i < num ; i++) + gsifree(values[i]); + gsifree(values); + } +} + +void ciRplUserIPHandler(CHAT chat, const ciServerMessage * message) +{ + char * IP; + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplUserIPHandler called\n"); +#endif + + assert(message->numParams >= 1); + if(message->numParams < 1) + return; //ERRCON + + IP = strchr(message->params[message->numParams - 1], '@'); + if(IP) + { + IP++; + + if(connection->fillInUserCallback) + { +#ifndef GSI_UNICODE + char user[MAX_USER]; + connection->fillInUserCallback(chat, inet_addr(IP), user, connection->connectParam); + strzcpy(connection->user, user, MAX_USER); +#else + unsigned short user[MAX_USER]; + connection->fillInUserCallback(chat, inet_addr(IP), user, connection->connectParam); + wcszcpy(connection->userW, user, MAX_USER); // copy the unicode version + UCS2ToAsciiString(user, connection->user); // convert the unicode user to ascii (nicknames cannot currently be UTF-8!!) +#endif + } + } + + // Send the nick and user. + ////////////////////////// + ciSendNickAndUser(chat); +} + +void ciRplListStartHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplListStartHandler called\n"); +#endif + + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_LIST; + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + LISTData * data = (LISTData *)filter->data; + assert(data != NULL); + //assert(!data->gotStart); + + data->gotStart = CHATTrue; + } + + GSI_UNUSED(message); +} + +void ciRplListHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplListHandler called\n"); +#endif + + assert(message->numParams == 4); + if(message->numParams != 4) + return; //ERRCON + + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_LIST; + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + if(filter->callback != NULL) + { + ciCallbackEnumChannelsEachParams params; + int index; + char * channel; + int numUsers; + char * topic; + int len; + void * tempPtr; + LISTData * data = (LISTData *)filter->data; + + assert(data != NULL); + //assert(data->gotStart); + + // Get the channel. + /////////////////// + len = (int)(strlen(message->params[1]) + 1); + channel = (char *)gsimalloc((unsigned int)len); + if(channel == NULL) + return; //ERRCON + memcpy(channel, message->params[1], (unsigned int)len); + + // Get the num users. + ///////////////////// + numUsers = atoi(message->params[2]); + + // Get the topic. + ///////////////// + len = (int)(strlen(message->params[3]) + 1); + topic = (char *)gsimalloc((unsigned int)len); + if(topic == NULL) + { + gsifree(channel); + return; //ERRCON + } + memcpy(topic, message->params[3], (unsigned int)len); + + // Get the index. + ///////////////// + index = data->numChannels; + + // Add the callback. + //////////////////// + params.success = CHATTrue; + params.index = index; + params.channel = channel; + params.topic = topic; + params.numUsers = numUsers; + ciAddCallback(chat, CALLBACK_ENUM_CHANNELS_EACH, filter->callback, ¶ms, filter->param, filter->ID, NULL); + + //TODO:only store this stuff if there's an "all" callback + + // Add the channel. + /////////////////// + tempPtr = gsirealloc(data->channels, sizeof(char *) * (data->numChannels + 1)); + if(tempPtr == NULL) + { + gsifree(channel); + gsifree(topic); + return; //ERRCON + } + data->channels = (char **)tempPtr; + data->channels[index] = channel; + + // Add the numUsers. + //////////////////// + tempPtr = gsirealloc(data->numUsers, sizeof(int) * (data->numChannels + 1)); + if(tempPtr == NULL) + { + gsifree(channel); + gsifree(topic); + return; //ERRCON + } + data->numUsers = (int *)tempPtr; + data->numUsers[index] = numUsers; + + // Add the topic. + ///////////////// + tempPtr = gsirealloc(data->topics, sizeof(char *) * (data->numChannels + 1)); + if(tempPtr == NULL) + { + gsifree(channel); + gsifree(topic); + return; //ERRCON + } + data->topics = (char **)tempPtr; + data->topics[index] = topic; + + // One more channel. + //////////////////// + data->numChannels++; + } + } +} + +void ciRplListEndHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplListEndHandler called\n"); +#endif + + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_LIST; + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + LISTData * data = (LISTData *)filter->data; + ciCallbackEnumChannelsAllParams params; + params.success = CHATTrue; + params.numChannels = data->numChannels; + params.channels = data->channels; + params.numUsers = data->numUsers; + params.topics = data->topics; + + FINISH_FILTER; + } + + GSI_UNUSED(message); +} + +void ciRplChannelModeIsHandler(CHAT chat, const ciServerMessage * message) +{ + char * channel; + char * modes; + ciModeChange * changes; + CHATChannelMode mode; + CHATChannelMode dummyMode; + ciServerMessageFilter * filter; + ciFilterMatch match; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplChannelModeIs called\n"); +#endif + + assert(message->numParams >=3); + if(message->numParams < 3) + return; //ERRCON + + channel = message->params[1]; + modes = message->params[2]; + + // Parse the mode. + ////////////////// + changes = ciParseMode(modes, message->params + 3, message->numParams - 3); + if(changes == NULL) + return; //ERRCON + + // Fill the mode struct. + //////////////////////// + memset(&mode, 0, sizeof(CHATChannelMode)); + ciApplyChangesToMode(&mode, changes); + + // Check if we have this channel's mode. + //////////////////////////////////////// + if(!ciGetChannelMode(chat, channel, &dummyMode)) + { + chatChannelCallbacks * callbacks; + + // Set the mode. + //////////////// + ciSetChannelMode(chat, channel, &mode); + + // Mode changed callback? + ///////////////////////// + callbacks = ciGetChannelCallbacks(chat, channel); + if((callbacks != NULL) && (callbacks->channelModeChanged != NULL)) + { + ciCallbackChannelModeChangedParams params; + params.channel = channel; + params.mode = &mode; + ciAddCallback(chat, CALLBACK_CHANNEL_MODE_CHANGED, (void*)callbacks->channelModeChanged, ¶ms, callbacks->param, 0, channel); + } + } + + // Were we waiting for this? + //////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_CMODE; + match.name = channel; + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + ciCallbackGetChannelModeParams params; + params.success = CHATTrue; + params.channel = channel; + params.mode = &mode; + + FINISH_FILTER; + } + + // gsifree the changes. + //////////////////// + gsifree(changes); +} + +void ciRplWhoisUserHandler(CHAT chat, const ciServerMessage * message) +{ + char * nick; + ciFilterMatch matches[2]; + ciServerMessageFilter * filter; + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplWhoisUserHandler called\n"); +#endif + + assert(message->numParams == 6); + if(message->numParams != 6) + return; //ERRCON + + // Get the nick. + //////////////// + nick = message->params[1]; + + // Setup the filter matches. + //////////////////////////// + memset(matches, 0, sizeof(matches)); + matches[0].type = TYPE_WHOIS; + matches[0].name = nick; + matches[1].type = TYPE_BAN; + matches[1].name = nick; + + // Find the filter. + /////////////////// + filter = ciFindFilter(chat, 2, matches); + if(!filter) + return; + + // Whois? + ///////// + if(filter->type == TYPE_WHOIS) + { + char * user; + char * name; + char * address; + + WHOISData * data = (WHOISData *)filter->data; + + // Cache the name and address pointers. + /////////////////////////////////////// + user = message->params[2]; + name = message->params[5]; + address = message->params[3]; + + // Copy the user. + ///////////////// + data->user = goastrdup(user); + if(data->user == NULL) + return; //ERRCON + + // Copy the name. + ///////////////// + data->name = goastrdup(name); + if(data->name == NULL) + return; //ERRCON + + // Copy the address. + //////////////////// + data->address = goastrdup(address); + if(data->address == NULL) + return; //ERRCON + + return; + } + + // Ban? + /////// + if(filter->type == TYPE_BAN) + { + char * host; + + BANData * data = (BANData *)filter->data; + assert(data != NULL); + ASSERT_STR(data->channel); + + host = message->params[3]; + + // Ban this guy. + //////////////// + ciSocketSendf(&connection->chatSocket, "MODE %s +b *!*@%s", data->channel, host); + + ciFinishFilter(chat, filter, NULL); + } +} + +void ciRplWhoisChannelsHandler(CHAT chat, const ciServerMessage * message) +{ + char * nick; + ciServerMessageFilter * filter; + ciFilterMatch match; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplWhoisChannelsHandler called\n"); +#endif + + assert(message->numParams == 3); + if(message->numParams != 3) + return; //ERRCON + + // Get the nick. + //////////////// + nick = message->params[1]; + + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_WHOIS; + match.name = nick; + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + char * channels; + char * str; + char * channel; + char ** tempPtr; + WHOISData * data = (WHOISData *)filter->data; + + channels = message->params[2]; + str = strtok(channels, " "); + while(str != NULL) + { + // Check for deaf mode. + /////////////////////// + if(str[0] == '-') + str++; + + // Check for op or voice. + ///////////////////////// + if((str[0] == '@') || (str[0] == '+')) + str++; + + // Add it to the list. + ////////////////////// + channel = goastrdup(str); + if(channel == NULL) + return; //ERRCON + tempPtr = (char **)gsirealloc(data->channels, sizeof(char *) * (data->numChannels + 1)); + if(tempPtr == NULL) + { + gsifree(channel); + return; //ERRCON + } + data->channels = tempPtr; + data->channels[data->numChannels] = channel; + data->numChannels++; + + // Get the next channel. + //////////////////////// + str = strtok(NULL, " "); + } + } +} + +void ciRplEndOfWhoisHandler(CHAT chat, const ciServerMessage * message) +{ + char * nick; + ciServerMessageFilter * filter; + ciFilterMatch match; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplEndOfWhoisHandler called\n"); +#endif + + assert(message->numParams == 3); + if(message->numParams != 3) + return; //ERRCON + + // Get the nick. + //////////////// + nick = message->params[1]; + + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_WHOIS; + match.name = nick; + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + WHOISData * data = (WHOISData *)filter->data; + ciCallbackGetUserInfoParams params; + params.success = (CHATBool)(data->user != NULL); //PANTS|08.21.00 - false if nothing found + params.nick = nick; + params.user = data->user; + params.name = data->name; + params.address = data->address; + params.numChannels = data->numChannels; + params.channels = data->channels; + + FINISH_FILTER; + } +} + +void ciRplBanListHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + char * channel; + char * ban; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplBanListHandler called\n"); +#endif + + assert(message->numParams >= 3); + if(message->numParams < 3) + return; //ERRCON + + channel = message->params[1]; + ban = message->params[2]; + + // Look for a filter. + ///////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_GETBAN; + match.name = channel; + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + int len; + void * tempPtr; + GETBANData * data = (GETBANData *)filter->data; + assert(data != NULL); + assert(data->numBans >= 0); + + // Increase the ban list. + ///////////////////////// + tempPtr = gsirealloc(data->bans, sizeof(char *) * (data->numBans + 1)); + if(tempPtr == NULL) + return; //ERRCON + data->bans = (char **)tempPtr; + + // Add the new ban. + /////////////////// + len = (int)(strlen(ban) + 1); + tempPtr = gsimalloc((unsigned int)len); + if(tempPtr == NULL) + return; //ERRCON + memcpy(tempPtr, ban, (unsigned int)len); + data->bans[data->numBans] = (char *)tempPtr; + data->numBans++; + } +} + +void ciRplEndOfBanListHandler(CHAT chat, const ciServerMessage * message) +{ + char * channel; + ciServerMessageFilter * filter; + ciFilterMatch match; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplEndOfBanListHandler called\n"); +#endif + + assert(message->numParams == 3); + if(message->numParams != 3) + return; //ERRCON + + channel = message->params[1]; + + // Look for a filter. + ///////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_GETBAN; + match.name = channel; + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + GETBANData * data = (GETBANData *)filter->data; + ciCallbackEnumChannelBansParams params; + params.success = CHATTrue; + params.channel = channel; + params.numBans = data->numBans; + params.bans = data->bans; + + FINISH_FILTER; + } +} + +void ciRplWelcomeHandler(CHAT chat, const ciServerMessage * message) +{ + char * nick; + + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplWelcomeHandler called\n"); +#endif + + assert(message->numParams == 2); + if(message->numParams != 2) + return; + + nick = message->params[0]; + + // Is the nick different? + ///////////////////////// + if(strcmp(connection->nick, nick) != 0) + { + strzcpy(connection->nick, nick, MAX_NICK); +#ifdef GSI_UNICODE + // store a unicode version + AsciiToUCS2String(connection->nick, connection->nickW); +#endif + } + + // Connected. + ///////////// + connection->connecting = CHATFalse; + connection->connected = CHATTrue; + + // Call the callback. + ///////////////////// + if(connection->connectCallback != NULL) + connection->connectCallback(chat, CHATTrue, 0, connection->connectParam); +} + +void ciRplSecureKeyHandler(CHAT chat, const ciServerMessage * message) +{ + char * outKeyRand; + char * inKeyRand; + int outKeyLen; + int inKeyLen; + + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplSecureKeyHandler called\n"); +#endif + + assert(message->numParams == 3); + if(message->numParams != 3) + return; + + outKeyRand = message->params[1]; + inKeyRand = message->params[2]; + + // Take the random keys and the secret key to create the encoding/decoding keys. + //////////////////////////////////////////////////////////////////////////////// + outKeyLen = (int)strlen(outKeyRand); + inKeyLen = (int)strlen(inKeyRand); + gs_xcode_buf(outKeyRand, outKeyLen, connection->secretKey); + gs_xcode_buf(inKeyRand, inKeyLen, connection->secretKey); + gs_prepare_key((unsigned char *)outKeyRand, outKeyLen, &connection->chatSocket.outKey); + gs_prepare_key((unsigned char *)inKeyRand, inKeyLen, &connection->chatSocket.inKey); + + // We now have a secure socket. + /////////////////////////////// + connection->chatSocket.secure = CHATTrue; + + // Login if we need to. + /////////////////////// + if(connection->loginType != CINoLogin) + { + ciSendLogin(chat); + } + // Get the IP if we need to. + //////////////////////////// + else if(connection->fillInUserCallback) + { + ciSocketSend(&connection->chatSocket, "USRIP"); + } + // Otherwise send the nick and user. + //////////////////////////////////// + else + { + ciSendNickAndUser(chat); + } +} + +void ciRplCDKeyHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + int result; + char * msg; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplCDKeyHandler called\n"); +#endif + + assert(message->numParams == 3); + if(message->numParams != 3) + return; + + result = atoi(message->params[1]); + msg = message->params[2]; + + // Setup a filter match. + //////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_CDKEY; + + // Find the filter. + /////////////////// + filter = ciFindFilter(chat, 1, &match); + if(filter) + { + ciCallbackAuthenticateCDKeyParams params; + params.result = result; + params.message = msg; + + FINISH_FILTER; + } +} + +void ciRplLoginHandler(CHAT chat, const ciServerMessage * message) +{ + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplLoginHandler called\n"); +#endif + + assert(message->numParams >= 3); + if(message->numParams < 3) + return; + + // Store the ids. + ///////////////// + connection->userID = atoi(message->params[1]); + connection->profileID = atoi(message->params[2]); + + // Get the IP if we need to. + //////////////////////////// + if(connection->fillInUserCallback) + { + ciSocketSend(&connection->chatSocket, "USRIP"); + } + // Otherwise send the nick and user. + //////////////////////////////////// + else + { + ciSendNickAndUser(chat); + } +} + +void ciRplGetUdpRelayHandler(CHAT chat, const ciServerMessage * message) +{ + char *channel; + int udpKey; + char *udpIp; + unsigned short udpPort; + ciServerMessageFilter * filter; + ciFilterMatch match; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciRplGetUdpRelayHandler called\n"); +#endif + assert(message->numParams >= 5); + if(message->numParams < 5) + return; + + channel = message->params[1]; + udpKey = atoi(message->params[2]); + udpIp = message->params[3]; + udpPort = (unsigned short)atoi(message->params[4]); + + // Were we waiting for this? + //////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_GETUDPRELAY; + match.name = channel; + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + ciCallbackGetUdpRelayParams params; + params.channel = channel; + params.udpIp = udpIp; + params.udpPort = udpPort; + params.udpKey = udpKey; + FINISH_FILTER; + } + + +} + +void ciErrNoSuchChannelHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch matches[2]; + ciServerMessageFilter * filter; + char * channel; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciErrNoSuchChannel called\n"); +#endif + + assert(message->numParams == 3); + if(message->numParams != 3) + return; //ERRCON + + // Get the channel. + /////////////////// + channel = message->params[1]; + + // Setup the filter matches. + //////////////////////////// + memset(&matches, 0, sizeof(ciFilterMatch)); + matches[0].type = TYPE_JOIN; + matches[0].name = channel; + matches[1].type = TYPE_CMODE; + matches[1].name = channel; + + // Look for a matching filter. + ////////////////////////////// + filter = ciFindFilter(chat, 2, matches); + if(filter) + { + // Join? + //////// + if(filter->type == TYPE_JOIN) + { + ciCallbackEnterChannelParams params; + params.success = CHATFalse; + params.result = CHATBadChannelName; + params.channel = channel; + + FINISH_FILTER; + + return; + } + + // Channel mode? + //////////////// + if(filter->type == TYPE_CMODE) + { + ciCallbackGetChannelModeParams params; + params.success = CHATFalse; + params.channel = channel; + params.mode = NULL; + + FINISH_FILTER; + + return; + } + } + + // GetKey? + ////////// + filter = ciFindGetKeyFilter(chat, channel); + if(filter) + { + ciCallbackGetGlobalKeysParams params; + params.success = CHATFalse; + params.user = NULL; + params.num = 0; + params.keys = NULL; + params.values = NULL; + + FINISH_FILTER; + + return; + } +} + +void ciErrTooManyChannelsHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + char * channel; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciErrTooManyChannels called\n"); +#endif + + assert(message->numParams == 3); + if(message->numParams != 3) + return; //ERRCON + + // Get the channel. + /////////////////// + channel = message->params[1]; + + // Look for a matching filter. + ////////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_JOIN; + match.name = channel; + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + ciCallbackEnterChannelParams params; + params.success = CHATFalse; + params.result = CHATTooManyChannels; + params.channel = channel; + + FINISH_FILTER; + } +} + +void ciErrChannelIsFullHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + char * channel; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciErrChannelIsFull called\n"); +#endif + + assert(message->numParams == 3); + if(message->numParams != 3) + return; //ERRCON + + // Get the channel. + /////////////////// + channel = message->params[1]; + + // Look for a matching filter. + ////////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_JOIN; + match.name = channel; + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + ciCallbackEnterChannelParams params; + params.success = CHATFalse; + params.result = CHATChannelIsFull; + params.channel = channel; + + FINISH_FILTER; + } +} + +void ciErrInviteOnlyChanHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + char * channel; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciErrInviteOnlyChan called\n"); +#endif + + assert(message->numParams == 3); + if(message->numParams != 3) + return; //ERRCON + + // Get the channel. + /////////////////// + channel = message->params[1]; + + // Look for a matching filter. + ////////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_JOIN; + match.name = channel; + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + ciCallbackEnterChannelParams params; + params.success = CHATFalse; + params.result = CHATInviteOnlyChannel; + params.channel = channel; + + FINISH_FILTER; + } +} + +void ciErrBannedFromChanHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + char * channel; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciErrBannedFromChan called\n"); +#endif + + assert(message->numParams == 3); + if(message->numParams != 3) + return; //ERRCON + + // Get the channel. + /////////////////// + channel = message->params[1]; + + // Look for a matching filter. + ////////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_JOIN; + match.name = channel; + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + ciCallbackEnterChannelParams params; + params.success = CHATFalse; + params.result = CHATBannedFromChannel; + params.channel = channel; + + FINISH_FILTER; + } +} + +void ciErrBadChannelKeyHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + char * channel; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciErrBadChannelKey called\n"); +#endif + + assert(message->numParams == 3); + if(message->numParams != 3) + return; //ERRCON + + // Get the channel. + /////////////////// + channel = message->params[1]; + + // Look for a matching filter. + ////////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_JOIN; + match.name = channel; + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + ciCallbackEnterChannelParams params; + params.success = CHATFalse; + params.result = CHATBadChannelPassword; + params.channel = channel; + + FINISH_FILTER; + } +} + +void ciErrBadChanMaskHandler(CHAT chat, const ciServerMessage * message) +{ + ciFilterMatch match; + ciServerMessageFilter * filter; + char * channel; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciErrBadChanMask called\n"); +#endif + + assert(message->numParams == 3); + if(message->numParams != 3) + return; //ERRCON + + // Get the channel. + /////////////////// + channel = message->params[1]; + + // Look for a matching filter. + ////////////////////////////// + memset(&match, 0, sizeof(ciFilterMatch)); + match.type = TYPE_JOIN; + match.name = channel; + filter = ciFindFilter(chat, 1, &match); + if(filter != NULL) + { + ciCallbackEnterChannelParams params; + params.success = CHATFalse; + params.result = CHATBadChannelMask; + params.channel = channel; + + FINISH_FILTER; + } +} + +void ciErrNoSuchNickHandler(CHAT chat, const ciServerMessage * message) +{ +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciErrNoSuchNickHandler called\n"); +#endif + + GSI_UNUSED(chat); + GSI_UNUSED(message); +} + +void ciErrErroneusNicknameHandler(CHAT chat, const ciServerMessage * message) +{ + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciErrErroneusNicknameHandler called\n"); +#endif + + // Are we connecting? + ///////////////////// + if(connection->connecting) + ciNickError(chat, CHAT_INVALID, connection->nick, 0, NULL); + + GSI_UNUSED(message); +} + +void ciErrLoginFailedHandler(CHAT chat, const ciServerMessage * message) +{ + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciErrLoginFailedHandler called\n"); +#endif + + // Are we connecting? + ///////////////////// + if(connection->connecting) + { + // No longer connecting. + //////////////////////// + connection->connecting = CHATFalse; + + // Call the connect failed callback. + //////////////////////////////////// + if(connection->connectCallback != NULL) + connection->connectCallback(chat, CHATFalse, CHAT_LOGIN_FAILED, connection->connectParam); + } + + GSI_UNUSED(message); +} + +void ciErrNoUniqueNickHandler(CHAT chat, const ciServerMessage * message) +{ + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciErrNoUniqueNickHandler called\n"); +#endif + + // Are we connecting? + ///////////////////// + if(connection->connecting) + ciNickError(chat, CHAT_NO_UNIQUENICK, "", 0, NULL); + + GSI_UNUSED(message); +} + +void ciErrUniqueNickExpiredHandler(CHAT chat, const ciServerMessage * message) +{ + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciErrUniqueNickExpiredHandler called\n"); +#endif + + // Are we connecting? + ///////////////////// + if(connection->connecting) + ciNickError(chat, CHAT_UNIQUENICK_EXPIRED, "", 0, NULL); + + GSI_UNUSED(message); +} + +void ciErrRegisterNickFailedHandler(CHAT chat, const ciServerMessage * message) +{ + int num; + char * nicks; + char * nick; + char ** nickArray; + int i; + CONNECTION; + +#ifdef FEEDBACK_HANDLERS + OutputDebugString("ciErrRegisterNickFailedHandler called\n"); +#endif + + assert(message->numParams == 4); + if(message->numParams != 4) + return; //ERRCON + + // Get the params we need. + ////////////////////////// + num = atoi(message->params[1]); + nicks = message->params[2]; + + // Are we connecting? + ///////////////////// + if(!connection->connecting) + return; + + // Allocate space for the nicks. + //////////////////////////////// + nickArray = (char **)gsimalloc(sizeof(char *) * num); + if(!nickArray) + return; + + // Parse out the suggested nicks. + ///////////////////////////////// + nick = strtok(nicks, "\\"); + for(i = 0 ; (i < num) && nick ; i++) + { + nickArray[i] = goastrdup(nick); + if(!nickArray[i]) + break; + + nick = strtok(NULL, "\\"); + } + + // Make sure our num is correct. + //////////////////////////////// + num = i; + + // Call the nick error callback. + //////////////////////////////// + ciNickError(chat, CHAT_INVALID_UNIQUENICK, connection->uniquenick, num, nickArray); + + // Free the memory. + /////////////////// + for(i = 0 ; i < num ; i++) + gsifree(nickArray[i]); + gsifree(nickArray); +} diff --git a/code/gamespy/Chat/chatHandlers.h b/code/gamespy/Chat/chatHandlers.h new file mode 100644 index 00000000..af37c718 --- /dev/null +++ b/code/gamespy/Chat/chatHandlers.h @@ -0,0 +1,82 @@ +/* +GameSpy Chat SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _CHATHANDLERS_H_ +#define _CHATHANDLERS_H_ + +/************* +** INCLUDES ** +*************/ +#include "chat.h" +#include "chatSocket.h" + + +/********** +** TYPES ** +**********/ +typedef struct ciServerMessageType +{ + char * command; + void (* handler)(CHAT chat, const ciServerMessage * message); +} ciServerMessageType; + +typedef char ** ciCommands; +typedef struct ciServerMessageFilter +{ + int type; + gsi_time timeout; + + char * name; + char * name2; + + void * callback; + void * callback2; + void * param; + + void * data; + + int ID; + + struct ciServerMessageFilter * pnext; +} ciServerMessageFilter; + +/************ +** GLOBALS ** +************/ +extern ciServerMessageType serverMessageTypes[]; +extern int numServerMessageTypes; + +/************** +** FUNCTIONS ** +**************/ +void ciFilterThink(CHAT chat); +void ciCleanupFilters(CHAT chat); +int ciAddLISTFilter(CHAT chat, chatEnumChannelsCallbackEach callbackEach, chatEnumChannelsCallbackAll callbackAll, void * param); +int ciAddJOINFilter(CHAT chat, const char * channel, chatEnterChannelCallback callback, void * param, chatChannelCallbacks * callbacks, const char * password); +int ciAddTOPICFilter(CHAT chat, const char * channel, chatGetChannelTopicCallback callback, void * param); +int ciAddNAMESFilter(CHAT chat, const char * channel, chatEnumUsersCallback callback, void * param); +int ciAddWHOISFilter(CHAT chat, const char * user, chatGetUserInfoCallback callback, void * param); +int ciAddWHOFilter(CHAT chat, const char * user, chatGetBasicUserInfoCallback callback, void * param); +int ciAddCWHOFilter(CHAT chat, const char * channel, chatGetChannelBasicUserInfoCallback callback, void * param); +int ciAddCMODEFilter(CHAT chat, const char * channel, chatGetChannelModeCallback callback, void * param); +int ciAddUMODEFilter(CHAT chat, const char * user, const char * channel, chatGetUserModeCallback callback, void * param); +int ciAddBANFilter(CHAT chat, const char * user, const char * channel); +int ciAddGETBANFilter(CHAT chat, const char * channel, chatEnumChannelBansCallback callback, void * param); +int ciAddNICKFilter(CHAT chat, const char * oldNick, const char * newNick, chatChangeNickCallback callback, void * param); +int ciAddUNQUIETFilter(CHAT chat, const char * channel); +int ciAddGETKEYFilter(CHAT chat, const char * cookie, int num, const char ** keys, const char * channel, chatGetGlobalKeysCallback callback, void * param); +int ciAddGETCKEYFilter(CHAT chat, const char * cookie, int num, const char ** keys, CHATBool channel, CHATBool getBroadcastKeys, chatGetChannelKeysCallback callback, void * param); +int ciAddGETCHANKEYFilter(CHAT chat, const char * cookie, int num, const char ** keys, CHATBool getBroadcastKeys, chatGetChannelKeysCallback callback, void * param); +int ciAddCDKEYFilter(CHAT chat, chatAuthenticateCDKeyCallback callback, void * param); +int ciAddGETUDPRELAYFilter(CHAT chat, const char * channel, chatGetUdpRelayCallback callback, void * param); +int ciGetNextID(CHAT chat); +CHATBool ciCheckFiltersForID(CHAT chat, int ID); + +#endif diff --git a/code/gamespy/Chat/chatMain.c b/code/gamespy/Chat/chatMain.c new file mode 100644 index 00000000..64c42a32 --- /dev/null +++ b/code/gamespy/Chat/chatMain.c @@ -0,0 +1,3075 @@ +/* +GameSpy Chat SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include "chat.h" +#include "chatMain.h" +#include "chatASCII.h" +#include "chatSocket.h" +#include "chatHandlers.h" +#include "chatChannel.h" +#include "chatCallbacks.h" + + +#if defined(_WIN32) +// Silence the warning about explicitly casting a function* to a void* +#pragma warning(disable:4054) +#endif + +/************ +** GLOBALS ** +************/ +// This can be overridden using an extern to pass a different versionID number to the chat server as part of the crypt negotiation +int ciVersionID = 1; + +/************ +** DEFINES ** +************/ +#define CI_DO_BLOCKING if(blocking)\ + {\ + do{\ + ciThink(chat, ID);\ + msleep(10);\ + }while(ciCheckForID(chat, ID));\ + } + +#define ASSERT_CHANNEL() assert(channel != NULL); assert(channel[0] != '\0'); +#define ASSERT_NICK() assert(nick != NULL); assert(nick[0] != '\0'); assert(strlen(nick) < MAX_NICK); +#define ASSERT_USER(user) assert(user != NULL); assert(user[0] != '\0'); assert(strlen(user) < MAX_USER); +#define ASSERT_MESSAGE() assert(message != NULL); assert(message[0] != '\0'); +#define ASSERT_TYPE(type) assert((type == CHAT_MESSAGE) || (type == CHAT_ACTION) || (type == CHAT_NOTICE) || (type == CHAT_UTM) || (type == CHAT_ATM)); +#define ASSERT_PASSWORD() assert(password != NULL); assert(password[0] != '\0'); +#define ASSERT_BAN() assert(ban != NULL); assert(ban [0] != '\0'); + +#define CI_NUM_TRANSLATED_NICKS 2 + +/********** +** TYPES ** +**********/ + +typedef struct ciEnumUsersData +{ + chatEnumUsersCallback callback; + void * param; +} ciEnumUsersData; + +/************** +** FUNCTIONS ** +**************/ +static CHATBool ciProcessServerMessage(CHAT chat, const ciServerMessage * message) +{ + int i; + + assert(message != NULL); + + // Figure out what type of message this is. + /////////////////////////////////////////// + for(i = 0 ; i < numServerMessageTypes ; i++) + { + // Does the type match? + /////////////////////// + if(strcasecmp(message->command, serverMessageTypes[i].command) == 0) + { + // Is there a handler? + ////////////////////// + if(serverMessageTypes[i].handler != NULL) + { + // Call the handler. + //////////////////// + serverMessageTypes[i].handler(chat, message); + } + + return CHATTrue; + } + } + + // Didn't find a match. + /////////////////////// + return CHATFalse; //ERRCON +} + +static CHATBool ciCheckForID(CHAT chat, int ID) +{ + return (CHATBool)(ciCheckFiltersForID(chat, ID) || ciCheckCallbacksForID(chat, ID)); +} + +void ciHandleDisconnect(CHAT chat, const char * reason) +{ + CHATBool connecting; + CONNECTION; + + // Check if we've already handled this. + /////////////////////////////////////// + if(connection->disconnected) + return; + + // Keep track of if we are trying to connect. + ///////////////////////////////////////////// + connecting = connection->connecting; + + // Not connected anymore. + ///////////////////////// + connection->connected = CHATFalse; + connection->connecting = CHATFalse; + connection->disconnected = CHATTrue; + + // If we're still connecting, let the app know the attempt failed. + ////////////////////////////////////////////////////////////////// + if(connection->connecting) + { + // Call the callback. + ///////////////////// + if(connection->connectCallback != NULL) + connection->connectCallback(chat, CHATFalse, CHAT_DISCONNECTED, connection->connectParam); + } + // Otherwise call the global callback. + ////////////////////////////////////// + else if(connection->globalCallbacks.disconnected != NULL) + { + ciCallbackDisconnectedParams params; + params.reason = (char *)reason; + ciAddCallback(chat, CALLBACK_DISCONNECTED, (void*)connection->globalCallbacks.disconnected, ¶ms, connection->globalCallbacks.param, 0, NULL); + } + GSI_UNUSED(connecting); +} + +static void ciThink(CHAT chat, int ID) +{ + ciServerMessage * message; + CONNECTION; + + // Is the socket connected? + /////////////////////////// + if(connection->chatSocket.connectState == ciConnected) + { + // Do processing. + ///////////////// + ciSocketThink(&connection->chatSocket); + + // Check received messages. + /////////////////////////// + while((message = ciSocketRecv(&connection->chatSocket)) != NULL) + { + // Call the raw callback. + ///////////////////////// + if(connection->globalCallbacks.raw != NULL) + { + ciCallbackRawParams params; + params.raw = message->message; + ciAddCallback(chat, CALLBACK_RAW, (void*)connection->globalCallbacks.raw, ¶ms, connection->globalCallbacks.param, 0, NULL); + } + + // Process the message. + /////////////////////// + ciProcessServerMessage(chat, message); + } + + // Have we lost connection? + /////////////////////////// + if(connection->chatSocket.connectState == ciDisconnected) + { + ciHandleDisconnect(chat, "Disconnected"); + } + } + + // Let the filters think. + ///////////////////////// + ciFilterThink(chat); + + // Call callbacks. + ////////////////// + ciCallCallbacks(chat, ID); + +} + +/************ +** GENERAL ** +************/ +void ciSendNick(CHAT chat) +{ + const char * nick; + CONNECTION; + + // Handle based on login type. + ////////////////////////////// + if(connection->loginType == CINoLogin) + { + + // 10-13-2004: changed by Saad Nader + // check for nick length and for an invalid nick. + ///////////////////////////////////////////////// + + int validateNick = ciNickIsValid(connection->nick); + if (validateNick != CHAT_NICK_OK) + { + ciNickError(chat, validateNick, connection->nick, 0, NULL); + return; + } + + // Use the provided nick. + ///////////////////////// + nick = connection->nick; + } + else if((connection->loginType == CIProfileLogin) && (connection->namespaceID == 0)) + { + + // 10-13-2004: changed by Saad Nader + // check for nick length and for an invalid nick. + ///////////////////////////////////////////////// + + int validateNick = ciNickIsValid(connection->profilenick); + if (validateNick != CHAT_NICK_OK) + { + ciNickError(chat, validateNick, connection->profilenick, 0, NULL); + return; + } + + // Use the profile's nick. + ////////////////////////// + nick = connection->profilenick; + } + else + { + // The server will use the uniquenick. + ////////////////////////////////////// + nick = "*"; + } + + // Send the nick. + ///////////////// + ciSocketSendf(&connection->chatSocket, "NICK %s", nick); +} + +void ciSendUser(CHAT chat) +{ + CONNECTION; + + // Send the user. + ///////////////// + ciSocketSendf(&connection->chatSocket, "USER %s %s %s :%s", + connection->user, + "127.0.0.1", + connection->server, + connection->name); +} + +void ciSendNickAndUser(CHAT chat) +{ + ciSendUser(chat); + ciSendNick(chat); +} + +void ciSendLogin(CHAT chat) +{ + char passwordHash[33]; + CONNECTION; + + // If it's pre-auth, send it. + ///////////////////////////// + if(connection->loginType == CIPreAuthLogin) + { + ciSocketSendf(&connection->chatSocket, "LOGINPREAUTH %s %s", + connection->authtoken, + connection->partnerchallenge); + + return; + } + + // For uniquenick or profile logins, we need to MD5 the password. + ///////////////////////////////////////////////////////////////// + MD5Digest((unsigned char *)connection->password, strlen(connection->password), passwordHash); + + // Send the login message based on type. + //////////////////////////////////////// + if(connection->loginType == CIUniqueNickLogin) + { + ciSocketSendf(&connection->chatSocket, "LOGIN %d %s %s", + connection->namespaceID, + connection->uniquenick, + passwordHash); + } + else if(connection->loginType == CIProfileLogin) + { + ciSocketSendf(&connection->chatSocket, "LOGIN %d * %s :%s@%s", + connection->namespaceID, + passwordHash, + connection->profilenick, + connection->email); + } + else + { + // If we get here, the login type is invalid or isn't being handled properly. + ///////////////////////////////////////////////////////////////////////////// + assert(0); + } +} + +static CHAT chatConnectDoit(CILoginType loginType, + const char * serverAddress, + int port, + const char * nick, + const char * user, + const char * name, + int namespaceID, + const char * email, + const char * profilenick, + const char * uniquenick, + const char * password, + const char * authtoken, + const char * partnerchallenge, + const char * gamename, + const char * secretKey, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking) +{ + ciConnection * connection; + const char * socketNick = ""; + + //Added default server address and port + //assert(serverAddress != NULL); + assert(callbacks != NULL); + assert(connectCallback != NULL); + + // Check the arguments based on the login type. + /////////////////////////////////////////////// + if(loginType == CINoLogin) + { + ASSERT_NICK(); + if(!nick || !nick[0]) + return NULL; + socketNick = nick; + } + else if(loginType == CIUniqueNickLogin) + { + assert(namespaceID > 0); + if(namespaceID <= 0) + return NULL; + assert(uniquenick && uniquenick[0]); + if(!uniquenick || !uniquenick[0]) + return NULL; + assert(password && password[0]); + if(!password || !password[0]) + return NULL; + socketNick = uniquenick; + } + else if(loginType == CIProfileLogin) + { + assert(namespaceID >= 0); + if(namespaceID < 0) + return NULL; + assert(email && email[0]); + if(!email || !email[0]) + return NULL; + assert(profilenick && profilenick[0]); + if(!profilenick || !profilenick[0]) + return NULL; + assert(password && password[0]); + if(!password || !password[0]) + return NULL; + socketNick = profilenick; + } + else if(loginType == CIPreAuthLogin) + { + assert(authtoken && authtoken[0]); + if(!authtoken || !authtoken[0]) + return NULL; + assert(partnerchallenge && partnerchallenge[0]); + if(!partnerchallenge || !partnerchallenge[0]) + return NULL; + socketNick = "preauth"; + } + if(loginType != CINoLogin) + { + assert(gamename && gamename[0]); + if(!gamename || !gamename[0]) + return NULL; + assert(secretKey && secretKey[0]); + if(!secretKey || !secretKey[0]) + return NULL; + } + + // Init sockets. + //////////////// + SocketStartUp(); + + // Create a connection object. + ////////////////////////////// + connection = (ciConnection *)gsimalloc(sizeof(ciConnection)); + if(connection == NULL) + return NULL; //ERRCON + + // Initialize the connection. + ///////////////////////////// + memset(connection, 0, sizeof(ciConnection)); + connection->loginType = loginType; + if(nick) + strzcpy(connection->nick, nick, MAX_NICK); + if(user) + strzcpy(connection->user, user, MAX_USER); +#ifdef GSI_UNICODE // store a unicode version of the nick and user + UTF8ToUCS2String(connection->nick, connection->nickW); + UTF8ToUCS2String(connection->user, connection->userW); +#endif + if(name) + strzcpy(connection->name, name, MAX_NAME); + connection->namespaceID = namespaceID; + if(email) + strzcpy(connection->email, email, MAX_EMAIL); + if(profilenick) + strzcpy(connection->profilenick, profilenick, MAX_PROFILENICK); + if(uniquenick) + strzcpy(connection->uniquenick, uniquenick, MAX_UNIQUENICK); + if(password) + strzcpy(connection->password, password, MAX_PASSWORD); + if(authtoken) + strzcpy(connection->authtoken, authtoken, MAX_AUTHTOKEN); + if(partnerchallenge) + strzcpy(connection->partnerchallenge, partnerchallenge, MAX_PARTNERCHALLENGE); + strzcpy(connection->server, serverAddress?serverAddress:CI_DEFAULT_SERVER_ADDRESS, MAX_SERVER); + connection->port = port?port:CI_DEFUILT_SERVER_PORT; + connection->globalCallbacks = *callbacks; + connection->nextID = 1; + connection->connecting = CHATTrue; + connection->quiet = CHATFalse; + + // Initialize the channel table. + //////////////////////////////// + if(!ciInitChannels(connection)) + { + gsifree(connection); + SocketShutDown(); + return NULL; //ERRCON + } + + // Initialize the callbacks list. + ///////////////////////////////// + if(!ciInitCallbacks(connection)) + { + ciCleanupChannels((CHAT)connection); + gsifree(connection); + SocketShutDown(); + return NULL; //ERRCON + } + + // Initialize the socket. + ///////////////////////// + if(!ciSocketInit(&connection->chatSocket, socketNick)) + { + ciCleanupCallbacks((CHAT)connection); + ciCleanupChannels((CHAT)connection); + gsifree(connection); + SocketShutDown(); + return NULL; //ERRCON + } + + // Connect the socket. + ////////////////////// + if(!ciSocketConnect(&connection->chatSocket, connection->server, connection->port)) + { + ciSocketDisconnect(&connection->chatSocket); + ciCleanupCallbacks((CHAT)connection); + ciCleanupChannels((CHAT)connection); + gsifree(connection); + SocketShutDown(); + return NULL; //ERRCON + } + + // Special stuff for MS Chat server. + //////////////////////////////////// + //ciSocketSend(&connection->chatSocket, "MODE ISIRCX"); + //ciSocketSend(&connection->chatSocket, "IRCX"); + + // Set the callback info. + ///////////////////////// + connection->nickErrorCallback = nickErrorCallback; + connection->fillInUserCallback = fillInUserCallback; + connection->connectCallback = connectCallback; + connection->connectParam = param; + + // Check for a secure connection. + ///////////////////////////////// + if(gamename && gamename[0] && secretKey && secretKey[0]) + { + // Save the game secret key. + //////////////////////////// + strzcpy(connection->secretKey, secretKey, MAX_SECRETKEY); + + // Get the random keys. + /////////////////////// + ciSocketSendf(&connection->chatSocket, "CRYPT des %d %s", ciVersionID, gamename); + } + else if(connection->fillInUserCallback) + { + // Get the IP. + ////////////// + ciSocketSend(&connection->chatSocket, "USRIP"); + } + else + { + // Send the nick and user. + ////////////////////////// + ciSendNickAndUser((CHAT)connection); + } + + // Do blocking. + /////////////// + if(blocking) + { + // While we're connecting. + ////////////////////////// + do + { + ciThink((CHAT)connection, 0); + msleep(10); + } while(connection->connecting); + + // Check if the connect failed. + /////////////////////////////// + if(!connection->connected) + { + // Disconnect the connection. + ///////////////////////////// + chatDisconnect((CHAT)connection); + connection = NULL; + } + } + + return (CHAT)connection; +} + +CHAT chatConnectA(const char * serverAddress, + int port, + const char * nick, + const char * user, + const char * name, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking) +{ + return chatConnectDoit(CINoLogin, + serverAddress, + port, + nick, + user, + name, + 0, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + callbacks, + nickErrorCallback, + NULL, + connectCallback, + param, + blocking); +} +#ifdef GSI_UNICODE +CHAT chatConnectW(const unsigned short * serverAddress, + int port, + const unsigned short * nick, + const unsigned short * user, + const unsigned short * name, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking) +{ + char* serverAddress_A = (char*)UCS2ToUTF8StringAlloc(serverAddress); + char* nick_A = (char*)UCS2ToUTF8StringAlloc(nick); + char* user_A = (char*)UCS2ToUTF8StringAlloc(user); + char* name_A = (char*)UCS2ToUTF8StringAlloc(name); + + CHAT aChat = chatConnectA(serverAddress_A, + port, + nick_A, + user_A, + name_A, + callbacks, + nickErrorCallback, + connectCallback, + param, + blocking); + + gsifree(serverAddress_A); + gsifree(nick_A); + gsifree(user_A); + gsifree(name_A); + + return aChat; +} +#endif + +CHAT chatConnectSpecialA(const char * serverAddress, + int port, + const char * nick, + const char * name, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking) +{ + return chatConnectDoit(CINoLogin, + serverAddress, + port, + nick, + NULL, + name, + 0, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + callbacks, + nickErrorCallback, + fillInUserCallback, + connectCallback, + param, + blocking); +} +#ifdef GSI_UNICODE +CHAT chatConnectSpecialW(const unsigned short * serverAddress, + int port, + const unsigned short * nick, + const unsigned short * name, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking) +{ + char* serverAddress_A = (char*)UCS2ToUTF8StringAlloc(serverAddress); + char* nick_A = (char*)UCS2ToUTF8StringAlloc(nick); + char* name_A = (char*)UCS2ToUTF8StringAlloc(name); + CHAT aChat = chatConnectSpecialA(serverAddress_A, port, nick_A, name_A, callbacks, nickErrorCallback, fillInUserCallback, connectCallback, param, blocking); + gsifree(serverAddress_A); + gsifree(nick_A); + gsifree(name_A); + + return aChat; +} +#endif + +CHAT chatConnectSecureA(const char * serverAddress, + int port, + const char * nick, + const char * name, + const char * gamename, + const char * secretKey, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking) +{ + return chatConnectDoit(CINoLogin, + serverAddress, + port, + nick, + NULL, + name, + 0, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + gamename, + secretKey, + callbacks, + nickErrorCallback, + fillInUserCallback, + connectCallback, + param, + blocking); +} +#ifdef GSI_UNICODE +CHAT chatConnectSecureW(const unsigned short * serverAddress, + int port, + const unsigned short * nick, + const unsigned short * name, + const unsigned short * gamename, + const unsigned short * secretKey, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking) +{ + char* serverAddress_A = (char*)UCS2ToUTF8StringAlloc(serverAddress); + char* nick_A = (char*)UCS2ToUTF8StringAlloc(nick); + char* name_A = (char*)UCS2ToUTF8StringAlloc(name); + char* gamename_A = (char*)UCS2ToUTF8StringAlloc(gamename); + char* secretKey_A = (char*)UCS2ToUTF8StringAlloc(secretKey); + CHAT aChat = chatConnectSecureA(serverAddress_A, port, nick_A, name_A, gamename_A, secretKey_A, callbacks, nickErrorCallback, fillInUserCallback, connectCallback, param, blocking); + gsifree(serverAddress_A); + gsifree(nick_A); + gsifree(name_A); + gsifree(gamename_A); + gsifree(secretKey_A); + + return aChat; +} +#endif + +CHAT chatConnectLoginA(const char * serverAddress, + int port, + int namespaceID, + const char * email, + const char * profilenick, + const char * uniquenick, + const char * password, + const char * name, + const char * gamename, + const char * secretKey, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking) +{ + return chatConnectDoit((uniquenick && uniquenick[0])?CIUniqueNickLogin:CIProfileLogin, + serverAddress, + port, + NULL, + NULL, + name, + namespaceID, + email, + profilenick, + uniquenick, + password, + NULL, + NULL, + gamename, + secretKey, + callbacks, + nickErrorCallback, + fillInUserCallback, + connectCallback, + param, + blocking); +} +#ifdef GSI_UNICODE +CHAT chatConnectLoginW(const unsigned short * serverAddress, + int port, + int namespaceID, + const unsigned short * email, + const unsigned short * profilenick, + const unsigned short * uniquenick, + const unsigned short * password, + const unsigned short * name, + const unsigned short * gamename, + const unsigned short * secretKey, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking) +{ + char* serverAddress_A = (char*)UCS2ToUTF8StringAlloc(serverAddress); + char* email_A = (char*)UCS2ToUTF8StringAlloc(email); + char* profilenick_A = (char*)UCS2ToUTF8StringAlloc(profilenick); + char* uniquenick_A = (char*)UCS2ToUTF8StringAlloc(uniquenick); + char* password_A = (char*)UCS2ToUTF8StringAlloc(password); + char* name_A = (char*)UCS2ToUTF8StringAlloc(name); + char* gamename_A = (char*)UCS2ToUTF8StringAlloc(gamename); + char* secretKey_A = (char*)UCS2ToUTF8StringAlloc(secretKey); + CHAT aChat= chatConnectLoginA(serverAddress_A, port, namespaceID, email_A, profilenick_A, uniquenick_A, password_A, name_A, gamename_A, secretKey_A, callbacks, nickErrorCallback, fillInUserCallback, connectCallback, param, blocking); + gsifree(serverAddress_A); + gsifree(email_A); + gsifree(profilenick_A); + gsifree(uniquenick_A); + gsifree(name_A); + gsifree(gamename_A); + gsifree(secretKey_A); + + return aChat; +} +#endif + +CHAT chatConnectPreAuthA(const char * serverAddress, + int port, + const char * authtoken, + const char * partnerchallenge, + const char * name, + const char * gamename, + const char * secretKey, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking) +{ + return chatConnectDoit(CIPreAuthLogin, + serverAddress, + port, + NULL, + NULL, + name, + 0, + NULL, + NULL, + NULL, + NULL, + authtoken, + partnerchallenge, + gamename, + secretKey, + callbacks, + nickErrorCallback, + fillInUserCallback, + connectCallback, + param, + blocking); +} +#ifdef GSI_UNICODE +CHAT chatConnectPreAuthW(const unsigned short * serverAddress, + int port, + const unsigned short * authtoken, + const unsigned short * partnerchallenge, + const unsigned short * name, + const unsigned short * gamename, + const unsigned short * secretKey, + chatGlobalCallbacks * callbacks, + chatNickErrorCallback nickErrorCallback, + chatFillInUserCallback fillInUserCallback, + chatConnectCallback connectCallback, + void * param, + CHATBool blocking) +{ + char* serverAddress_A = (char*)UCS2ToUTF8StringAlloc(serverAddress); + char* authtoken_A = (char*)UCS2ToUTF8StringAlloc(authtoken); + char* partnerchallenge_A = (char*)UCS2ToUTF8StringAlloc(partnerchallenge); + char* name_A = (char*)UCS2ToUTF8StringAlloc(name); + char* gamename_A = (char*)UCS2ToUTF8StringAlloc(gamename); + char* secretKey_A = (char*)UCS2ToUTF8StringAlloc(secretKey); + CHAT aChat = chatConnectPreAuthA(serverAddress_A, port, authtoken_A, partnerchallenge_A, name_A, gamename_A, secretKey_A, callbacks, nickErrorCallback, fillInUserCallback, connectCallback, param, blocking); + gsifree(serverAddress_A); + gsifree(authtoken_A); + gsifree(partnerchallenge_A); + gsifree(name_A); + gsifree(gamename_A); + gsifree(secretKey_A); + + return aChat; +} +#endif + +void chatRetryWithNickA(CHAT chat, + const char * nick) +{ + int validateNick; + CONNECTION; + + // Are we already connected? + //////////////////////////// + if(connection->connected) + return; + + // A NULL nick means stop retrying and disconnect + if (nick == NULL) + { + connection->connecting = CHATFalse; + + // Call the callback. (Failed to connect) + ///////////////////// + if(connection->connectCallback != NULL) + connection->connectCallback(chat, CHATFalse, CHAT_NICK_ERROR, connection->connectParam); + + return; + } + + // Copy the new nick. + ///////////////////// + strzcpy(connection->nick, nick, MAX_NICK); +#ifdef GSI_UNICODE // store a unicode version of the nick + AsciiToUCS2String(connection->nick, connection->nickW); +#endif + + // Check for a bad nick. + //////////////////////// + validateNick = ciNickIsValid(nick); + if (validateNick != CHAT_NICK_OK) + { + ciNickError(chat, validateNick, nick, 0, NULL); + return; + } + + // Send the new nick. + ///////////////////// + ciSocketSendf(&connection->chatSocket, "NICK :%s", nick); + +} +#ifdef GSI_UNICODE +void chatRetryWithNickW(CHAT chat, + const unsigned short * nick) +{ + char* nick_A = (char*)UCS2ToUTF8StringAlloc(nick); + chatRetryWithNickA(chat, nick_A); + gsifree(nick_A); +} +#endif + +void chatRegisterUniqueNickA(CHAT chat, + int namespaceID, + const char * uniquenick, + const char * cdkey) +{ + CONNECTION; + + // Are we already connected? + //////////////////////////// + if(connection->connected) + return; + + // A NULL nick means stop trying and disconnect. + //////////////////////////////////////////////// + if(uniquenick == NULL) + { + connection->connecting = CHATFalse; + + // Call the callback. + ///////////////////// + if(connection->connectCallback != NULL) + connection->connectCallback(chat, CHATFalse, CHAT_NICK_ERROR, connection->connectParam); + + return; + } + + // CDKey is optional. + ///////////////////// + if(!cdkey) + cdkey = ""; + + // Send the message. + //////////////////// + ciSocketSendf(&connection->chatSocket, "REGISTERNICK %d %s %s", namespaceID, uniquenick, cdkey); + + // Save the uniquenick we're trying to use. + /////////////////////////////////////////// + strzcpy(connection->uniquenick, uniquenick, MAX_UNIQUENICK); +} +#ifdef GSI_UNICODE +void chatRegisterUniqueNickW(CHAT chat, + int namespaceID, + const unsigned short * uniquenick, + const unsigned short * cdkey) +{ + char* uniquenick_A = (char*)UCS2ToUTF8StringAlloc(uniquenick); + char* cdkey_A = (char*)UCS2ToUTF8StringAlloc(cdkey); + chatRegisterUniqueNickA(chat, namespaceID, uniquenick_A, cdkey_A); + gsifree(uniquenick_A); + gsifree(cdkey_A); +} +#endif + +void chatDisconnect(CHAT chat) +{ + CONNECTION; + + // Cleanup all the filters first. + ///////////////////////////////// + ciCleanupFilters(chat); + + // Call the disconnected callback if we haven't already. + //////////////////////////////////////////////////////// + if(!connection->disconnected && connection->globalCallbacks.disconnected) +#ifdef GSI_UNICODE + connection->globalCallbacks.disconnected(chat, L"", connection->globalCallbacks.param); +#else + connection->globalCallbacks.disconnected(chat, "", connection->globalCallbacks.param); +#endif + + // Are we connected. + //////////////////// + if(connection->connected) + { + ciSocketSend(&connection->chatSocket, "QUIT :Later!"); + ciSocketThink(&connection->chatSocket); + } + + // gsifree the channel table. + ////////////////////////// + ciCleanupChannels(chat); + + // Cleanup the callbacks list. + ////////////////////////////// + ciCleanupCallbacks(chat); + + // Shutdown the chat socket. + //////////////////////////// + ciSocketDisconnect(&connection->chatSocket); + + // gsifree the memory. + /////////////////// + gsifree(chat); + + // Shutdown sockets. + //////////////////// + SocketShutDown(); +} + +void chatThink(CHAT chat) +{ + ciThink(chat, 0); +} + +void chatSendRawA(CHAT chat, + const char * command) +{ + CONNECTION; + if(!connection || (!connection->connected && !connection->connecting)) + return; + + ciSocketSend(&connection->chatSocket, command); +} +#ifdef GSI_UNICODE +void chatSendRawW(CHAT chat, + const unsigned short* command) +{ + char* command_A = (char*)UCS2ToUTF8StringAlloc(command); + chatSendRawA(chat, command_A); + gsifree(command_A); +} +#endif + +void chatChangeNickA(CHAT chat, + const char * newNick, + chatChangeNickCallback callback, + void * param, + CHATBool blocking) +{ + int ID; + CHATBool success = CHATTrue; + + CONNECTION; + CONNECTED; + + assert(newNick); + assert(newNick[0]); + assert(strlen(newNick) < MAX_NICK); + assert(callback); + assert(connection->connected); + + // chatRetryWithNick should be called while connecting. + /////////////////////////////////////////////////////// + if(!connection->connected) + return; + + // No nick. + /////////// + if(!newNick || !newNick[0]) + success = CHATFalse; + + // 10-13-2004: Added By Saad Nader + // check for long or invalid chars in new nick. + /////////////////////////////////////////////// + if (ciNickIsValid(newNick) != CHAT_NICK_OK) + { + success = CHATFalse; + } + + // Check for same nick. + /////////////////////// + if(success && (strcasecmp(newNick, connection->nick) == 0)) + success = CHATFalse; + + // Call the callback? + ///////////////////// + if(!success) + { + if(callback) + { + ciCallbackChangeNickParams params; + params.success = success; + params.oldNick = connection->nick; + params.newNick = (char *)newNick; + ID = ciGetNextID(chat); + ciAddCallback(chat, CALLBACK_CHANGE_NICK, (void*)callback, ¶ms, param, ID, NULL); + + CI_DO_BLOCKING; + } + + return; + } + + // Send the request. + //////////////////// + ciSocketSendf(&connection->chatSocket, "NICK :%s", newNick); + + ID = ciAddNICKFilter(chat, connection->nick, newNick, callback, param); + + CI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void chatChangeNickW(CHAT chat, + const unsigned short * newNick, + chatChangeNickCallback callback, + void * param, + CHATBool blocking) +{ + char* newNick_A = (char*)UCS2ToUTF8StringAlloc(newNick); + chatChangeNickA(chat, newNick_A, callback, param, blocking); + gsifree(newNick_A); +} +#endif + +const char * chatGetNickA(CHAT chat) +{ + CONNECTION; + + if(!connection->connected) + return ""; + + return connection->nick; +} +#ifdef GSI_UNICODE +const unsigned short * chatGetNickW(CHAT chat) +{ + CONNECTION; + + if(!connection->connected) + return L""; + + return connection->nickW; +} +#endif + +void chatFixNickA(char * newNick, + const char * oldNick) +{ + int c; + char oldNickCopy[MAX_CHAT_NICK]; + char *pOldNick = oldNickCopy; + assert(oldNick); + assert(newNick); + strzcpy(oldNickCopy, oldNick, MAX_CHAT_NICK); + //if(isdigit(*oldNick) || (*oldNick == '-')) + // 10-14-2004 Changed by Saad Nader + // Using the nickname rules for unique nicks + // commented out the previous rules + //////////////////////////////////////////////// + if(*pOldNick == '@' || *pOldNick== '#' || *pOldNick== '+' || *pOldNick == ':') + *newNick++ = '_'; + + while((c = *pOldNick++) != '\0') + { + if(!strchr(VALID_NICK_CHARS, c)) + c = '_'; + + *newNick++ = (char)c; + } + *newNick = '\0'; + +} +#ifdef GSI_UNICODE +void chatFixNickW(unsigned short* newNick, + const unsigned short* oldNick) +{ + char* oldNick_A = (char*)UCS2ToUTF8StringAlloc(newNick); + char newNick_A[MAX_NICK]; + + chatFixNickA(newNick_A, oldNick_A); + + UTF8ToUCS2String(newNick_A, newNick); + + gsifree(oldNick_A); + + GSI_UNUSED(oldNick); +} +#endif + +const char * chatTranslateNickA(char * nick, + const char * extension) +{ + int nickLen; + int extensionLen; + + assert(nick); + assert(extension); + + nickLen = (int)strlen(nick); + extensionLen = (int)strlen(extension); + + if((extensionLen < nickLen) && (strcasecmp(nick + nickLen - extensionLen, extension) == 0)) + { + nick[nickLen - extensionLen] = '\0'; + return nick; + } + + return NULL; +} +#ifdef GSI_UNICODE +const unsigned short * chatTranslateNickW(unsigned short * nick, + const unsigned short * extension) +{ + char nick_A[MAX_NICK]; + char extension_A[MAX_NICK]; + const char * translatedNick_A; + + assert(nick); + assert(extension); + + UCS2ToAsciiString(nick, nick_A); + UCS2ToAsciiString(extension, extension_A); + + translatedNick_A = chatTranslateNickA(nick_A, extension_A); + + if(translatedNick_A) + { + AsciiToUCS2String(translatedNick_A, nick); + return nick; + } + + return NULL; +} +#endif + +int chatGetUserID(CHAT chat) +{ + CONNECTION; + + return connection->userID; +} + +int chatGetProfileID(CHAT chat) +{ + CONNECTION; + + return connection->profileID; +} + +static void ciSetQuietModeEnumJoinedChannelsA(CHAT chat, + int index, + const char * channel, + void * param) +{ + // Setup a filter. + ////////////////// + ciAddUNQUIETFilter(chat, channel); + + GSI_UNUSED(index); + GSI_UNUSED(param); +} +#ifdef GSI_UNICODE +static void ciSetQuietModeEnumJoinedChannelsW(CHAT chat, + int index, + const unsigned short * channel, + void * param) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + ciSetQuietModeEnumJoinedChannelsA(chat, index, channel_A, param); + gsifree(channel_A); +} +#endif + +void chatSetQuietMode(CHAT chat, + CHATBool quiet) +{ + CONNECTION; + CONNECTED; + + // Check if its the current mode. + ///////////////////////////////// + if(connection->quiet == quiet) + return; + + // Send the message. + //////////////////// + if(quiet) + ciSocketSendf(&connection->chatSocket, "MODE %s +q", connection->nick); + else + ciSocketSendf(&connection->chatSocket, "MODE %s -q", connection->nick); + + // Set the mode. + //////////////// + connection->quiet = quiet; + + // Are we disabling it? + /////////////////////// + if(!quiet) + { + // Clear all the player lists. + ////////////////////////////// + ciClearAllUsers(chat); + + // Setup a filter for each joined channel. + ////////////////////////////////////////// +#ifdef GSI_UNICODE + ciEnumJoinedChannels(chat, ciSetQuietModeEnumJoinedChannelsW, NULL); +#else + ciEnumJoinedChannels(chat, ciSetQuietModeEnumJoinedChannelsA, NULL); +#endif + } +} + +void chatAuthenticateCDKeyA(CHAT chat, + const char * cdkey, + chatAuthenticateCDKeyCallback callback, + void * param, + CHATBool blocking) +{ + int ID; + CHATBool success = CHATTrue; + + CONNECTION; + CONNECTED; + + assert(cdkey); + assert(cdkey[0]); + assert(callback); + assert(connection->connected); + + // Check we're connected. + ///////////////////////// + if(!connection->connected) + return; + + // No key. + ////////// + if(!cdkey || !cdkey[0]) + success = CHATFalse; + + // Call the callback? + ///////////////////// + if(!success) + { + if(callback) + { + ciCallbackAuthenticateCDKeyParams params; + params.result = 0; + params.message = ""; + ID = ciGetNextID(chat); + ciAddCallback(chat, CALLBACK_AUTHENTICATE_CDKEY, (void*)callback, ¶ms, param, ID, NULL); + + CI_DO_BLOCKING; + } + + return; + } + + // Send the request. + //////////////////// + ciSocketSendf(&connection->chatSocket, "CDKEY %s", cdkey); + + ID = ciAddCDKEYFilter(chat, callback, param); + + CI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void chatAuthenticateCDKeyW(CHAT chat, + const unsigned short* cdkey, + chatAuthenticateCDKeyCallback callback, + void * param, + CHATBool blocking) +{ + char* cdkey_A = (char*)UCS2ToUTF8StringAlloc(cdkey); + chatAuthenticateCDKeyA(chat, cdkey_A, callback, param, blocking); + gsifree(cdkey_A); +} +#endif + +/************* +** CHANNELS ** +*************/ +void chatEnumChannelsA(CHAT chat, + const char * filter, + chatEnumChannelsCallbackEach callbackEach, + chatEnumChannelsCallbackAll callbackAll, + void * param, + CHATBool blocking) +{ + int ID; + CONNECTION; + CONNECTED; + + assert((callbackAll != NULL) || (callbackEach != NULL)); + + if(!filter) + filter = ""; + + ciSocketSendf(&connection->chatSocket, "LIST %s", filter); + + ID = ciAddLISTFilter(chat, callbackEach, callbackAll, param); + + CI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void chatEnumChannelsW(CHAT chat, + const unsigned short * filter, + chatEnumChannelsCallbackEach callbackEach, + chatEnumChannelsCallbackAll callbackAll, + void * param, + CHATBool blocking) +{ + char* filter_A = (char*)UCS2ToUTF8StringAlloc(filter); + chatEnumChannelsA(chat, filter_A, callbackEach, callbackAll, param, blocking); + gsifree(filter_A); +} +#endif + +void chatEnterChannelA(CHAT chat, + const char * channel, + const char * password, + chatChannelCallbacks * callbacks, + chatEnterChannelCallback callback, + void * param, + CHATBool blocking) +{ + int ID; + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + assert(callbacks != NULL); + + if(password == NULL) + password = ""; + + ciSocketSendf(&connection->chatSocket, "JOIN %s %s", channel, password); + + ID = ciAddJOINFilter(chat, channel, callback, param, callbacks, password); + + // Entering. + //////////// + ciChannelEntering(chat, channel); + + CI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void chatEnterChannelW(CHAT chat, + const unsigned short * channel, + const unsigned short * password, + chatChannelCallbacks * callbacks, + chatEnterChannelCallback callback, + void * param, + CHATBool blocking) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + char* password_A = (char*)UCS2ToUTF8StringAlloc(password); + chatEnterChannelA(chat, channel_A, password_A, callbacks, callback, param, blocking); + gsifree(channel_A); + gsifree(password_A); +} +#endif + +void chatLeaveChannelA(CHAT chat, + const char * channel, + const char * reason) +{ + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + + if(!reason) + reason = ""; + + ciSocketSendf(&connection->chatSocket, "PART %s :%s", channel, reason); + + // Left the channel. + //////////////////// + ciChannelLeft(chat, channel); +} +#ifdef GSI_UNICODE +void chatLeaveChannelW(CHAT chat, + const unsigned short * channel, + const unsigned short* reason) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + char* reason_A = (char*)UCS2ToUTF8StringAlloc(reason); + chatLeaveChannelA(chat, channel_A, reason_A); + gsifree(channel_A); + gsifree(reason_A); +} +#endif + +void chatSendChannelMessageA(CHAT chat, + const char * channel, + const char * message, + int type) +{ + chatChannelCallbacks * callbacks; + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + ASSERT_TYPE(type); + + if (!message || !message[0]) + return; + if(type == CHAT_MESSAGE) + ciSocketSendf(&connection->chatSocket, "PRIVMSG %s :%s", channel, message); + else if(type == CHAT_ACTION) + ciSocketSendf(&connection->chatSocket, "PRIVMSG %s :\001ACTION %s\001", channel, message); + else if(type == CHAT_NOTICE) + ciSocketSendf(&connection->chatSocket, "NOTICE %s :%s", channel, message); + else if(type == CHAT_UTM) + ciSocketSendf(&connection->chatSocket, "UTM %s :%s", channel, message); + else if(type == CHAT_ATM) + ciSocketSendf(&connection->chatSocket, "ATM %s :%s", channel, message); + else + return; + + // We don't get these back, so call the callbacks. + ////////////////////////////////////////////////// + callbacks = ciGetChannelCallbacks(chat, channel); + if(callbacks != NULL) + { + ciCallbackChannelMessageParams params; + params.channel = (char *)channel; + params.user = connection->nick; + params.message = (char *)message; + params.type = type; + ciAddCallback(chat, CALLBACK_CHANNEL_MESSAGE, (void*)callbacks->channelMessage, ¶ms, callbacks->param, 0, channel); + } +} +#ifdef GSI_UNICODE +void chatSendChannelMessageW(CHAT chat, + const unsigned short * channel, + const unsigned short * message, + int type) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + char* message_A = (char*)UCS2ToUTF8StringAlloc(message); + chatSendChannelMessageA(chat, channel_A, message_A, type); + gsifree(channel_A); + gsifree(message_A); +} +#endif + +void chatSetChannelTopicA(CHAT chat, + const char * channel, + const char * topic) +{ + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + + if(topic == NULL) + topic = ""; + + ciSocketSendf(&connection->chatSocket, "TOPIC %s :%s", channel, topic); +} +#ifdef GSI_UNICODE +void chatSetChannelTopicW(CHAT chat, + const unsigned short * channel, + const unsigned short * topic) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + char* topic_A = (char*)UCS2ToUTF8StringAlloc(topic); + chatSetChannelTopicA(chat, channel_A, topic_A); + gsifree(channel_A); + gsifree(topic_A); +} +#endif + +void chatGetChannelTopicA(CHAT chat, + const char * channel, + chatGetChannelTopicCallback callback, + void * param, + CHATBool blocking) +{ + int ID; + const char * topic; + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + assert(callback != NULL); + + // Check if we already have the topic. + ////////////////////////////////////// + topic = ciGetChannelTopic(chat, channel); + if(topic) + { + ciCallbackGetChannelTopicParams params; + + ID = ciGetNextID(chat); + + params.success = CHATTrue; + params.channel = (char *)channel; + params.topic = (char *)topic; + ciAddCallback(chat, CALLBACK_GET_CHANNEL_TOPIC, (void*)callback, ¶ms, param, ID, channel); + } + else + { + ciSocketSendf(&connection->chatSocket, "TOPIC %s", channel); + + ID = ciAddTOPICFilter(chat, channel, callback, param); + } + + CI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void chatGetChannelTopicW(CHAT chat, + const unsigned short * channel, + chatGetChannelTopicCallback callback, + void * param, + CHATBool blocking) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + chatGetChannelTopicA(chat, channel_A, callback, param, blocking); + gsifree(channel_A); +} +#endif + +void chatSetChannelModeA(CHAT chat, + const char * channel, + CHATChannelMode * mode) +{ + char buffer[64]; + + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + assert(mode != NULL); + + // Build the mode string. + ///////////////////////// + strcpy(buffer, "XiXpXsXmXnXtXlXe"); + if(mode->InviteOnly) + buffer[0] = '+'; + else + buffer[0] = '-'; + if(mode->Private) + buffer[2] = '+'; + else + buffer[2] = '-'; + if(mode->Secret) + buffer[4] = '+'; + else + buffer[4] = '-'; + if(mode->Moderated) + buffer[6] = '+'; + else + buffer[6] = '-'; + if(mode->NoExternalMessages) + buffer[8] = '+'; + else + buffer[8] = '-'; + if(mode->OnlyOpsChangeTopic) + buffer[10] = '+'; + else + buffer[10] = '-'; + if(mode->Limit > 0) + buffer[12] = '+'; + else + buffer[12] = '-'; + if(mode->OpsObeyChannelLimit) + buffer[14] = '+'; + else + buffer[14] = '-'; + + // Add limit if needed. + /////////////////////// + if(mode->Limit > 0) + sprintf(&buffer[strlen(buffer)], " %d", mode->Limit); + + ciSocketSendf(&connection->chatSocket, "MODE %s %s", channel, buffer); +} +#ifdef GSI_UNICODE +void chatSetChannelModeW(CHAT chat, + const unsigned short * channel, + CHATChannelMode * mode) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + chatSetChannelModeA(chat, channel_A, mode); + gsifree(channel_A); +} +#endif + +void chatGetChannelModeA(CHAT chat, + const char * channel, + chatGetChannelModeCallback callback, + void * param, + CHATBool blocking) +{ + int ID; + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + assert(callback != NULL); + + // Are we in this channel? + ////////////////////////// + if(ciInChannel(chat, channel)) + { + CHATChannelMode mode; + + // Get the mode locally. + //////////////////////// + if(ciGetChannelMode(chat, channel, &mode)) + { + ciCallbackGetChannelModeParams params; + + // Get an ID. + ///////////// + ID = ciGetNextID(chat); + + // Add the callback. + //////////////////// + params.success = CHATTrue; + params.channel = (char *)channel; + params.mode = &mode; + ciAddCallback(chat, CALLBACK_GET_CHANNEL_MODE, (void*)callback, ¶ms, param, ID, NULL); + + CI_DO_BLOCKING; + + return; + } + } + + ciSocketSendf(&connection->chatSocket, "MODE %s", channel); + + ID = ciAddCMODEFilter(chat, channel, callback, param); + + CI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void chatGetChannelModeW(CHAT chat, + const unsigned short * channel, + chatGetChannelModeCallback callback, + void * param, + CHATBool blocking) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + chatGetChannelModeA(chat, channel_A, callback, param, blocking); + gsifree(channel_A); +} +#endif + +void chatSetChannelPasswordA(CHAT chat, + const char * channel, + CHATBool enable, + const char * password) +{ + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + ASSERT_PASSWORD(); + + if(enable) + ciSocketSendf(&connection->chatSocket, "MODE %s +k %s", channel, password); + else + ciSocketSendf(&connection->chatSocket, "MODE %s -k %s", channel, password); +} +#ifdef GSI_UNICODE +void chatSetChannelPasswordW(CHAT chat, + const unsigned short * channel, + CHATBool enable, + const unsigned short * password) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + char* password_A = (char*)UCS2ToUTF8StringAlloc(password); + chatSetChannelPasswordA(chat, channel_A, enable, password_A); + gsifree(channel_A); + gsifree(password_A); +} +#endif + +void chatGetChannelPasswordA(CHAT chat, + const char * channel, + chatGetChannelPasswordCallback callback, + void * param, + CHATBool blocking) +{ + ciCallbackGetChannelPasswordParams params; + const char * password; + int ID; + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + assert(callback != NULL); + + // Check that we're in the channel. + /////////////////////////////////// + if(!ciInChannel(chat, channel)) + return; //ERRCON + + // Get the password. + //////////////////// + password = ciGetChannelPassword(chat, channel); + assert(password != NULL); + + // Get an ID. + ///////////// + ID = ciGetNextID(chat); + + // Add the callback. + //////////////////// + params.success = CHATTrue; + params.channel = (char *)channel; + params.enabled = CHATTrue; + params.password = (char *)password; + ciAddCallback(chat, CALLBACK_GET_CHANNEL_PASSWORD, (void*)callback, ¶ms, param, ID, NULL); + + CI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void chatGetChannelPasswordW(CHAT chat, + const unsigned short * channel, + chatGetChannelPasswordCallback callback, + void * param, + CHATBool blocking) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + chatGetChannelPasswordA(chat, channel_A, callback, param, blocking); + gsifree(channel_A); +} +#endif + +void chatSetChannelLimitA(CHAT chat, + const char * channel, + int limit) +{ + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + assert(limit >= 0); + + if(limit) + ciSocketSendf(&connection->chatSocket, "MODE %s +l %d", channel, limit); + else + ciSocketSendf(&connection->chatSocket, "MODE %s -l", channel); +} +#ifdef GSI_UNICODE +void chatSetChannelLimitW(CHAT chat, + const unsigned short * channel, + int limit) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + chatSetChannelLimitA(chat, channel_A, limit); + gsifree(channel_A); +} +#endif + +void chatEnumChannelBansA(CHAT chat, + const char * channel, + chatEnumChannelBansCallback callback, + void * param, + CHATBool blocking) +{ + int ID; + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + assert(callback != NULL); + + ciSocketSendf(&connection->chatSocket, "MODE %s +b", channel); + + ID = ciAddGETBANFilter(chat, channel, callback, param); + + CI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void chatEnumChannelBansW(CHAT chat, + const unsigned short * channel, + chatEnumChannelBansCallback callback, + void * param, + CHATBool blocking) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + chatEnumChannelBansA(chat, channel_A, callback, param, blocking); + gsifree(channel_A); +} +#endif + +void chatAddChannelBanA(CHAT chat, + const char * channel, + const char * ban) +{ + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + ASSERT_BAN(); + + ciSocketSendf(&connection->chatSocket, "MODE %s +b %s", channel, ban); +} +#ifdef GSI_UNICODE +void chatAddChannelBanW(CHAT chat, + const unsigned short * channel, + const unsigned short * ban) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + char* ban_A = (char*)UCS2ToUTF8StringAlloc(ban); + chatAddChannelBanA(chat, channel_A, ban_A); + gsifree(channel_A); + gsifree(ban_A); +} +#endif + +void chatRemoveChannelBanA(CHAT chat, + const char * channel, + const char * ban) +{ + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + ASSERT_BAN(); + + ciSocketSendf(&connection->chatSocket, "MODE %s -b %s", channel, ban); +} +#ifdef GSI_UNICODE +void chatRemoveChannelBanW(CHAT chat, + const unsigned short * channel, + const unsigned short * ban) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + char* ban_A = (char*)UCS2ToUTF8StringAlloc(ban); + chatRemoveChannelBanA(chat, channel_A, ban_A); + gsifree(channel_A); + gsifree(ban_A); +} +#endif + +void chatSetChannelGroupA(CHAT chat, + const char * channel, + const char * group) +{ + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + + // No way to clear the group. + ///////////////////////////// + if(!group || !group[0]) + return; + + ciSocketSendf(&connection->chatSocket, "SETGROUP %s %s", channel, group); +} +#ifdef GSI_UNICODE +void chatSetChannelGroupW(CHAT chat, + const unsigned short * channel, + const unsigned short* group) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + char* group_A = (char*)UCS2ToUTF8StringAlloc(group); + chatSetChannelGroupA(chat, channel_A, group_A); + gsifree(channel_A); + gsifree(group_A); +} +#endif + +int chatGetChannelNumUsersA(CHAT chat, + const char * channel) +{ + CONNECTION; + if(!connection->connected) + return -1; + + ASSERT_CHANNEL(); + + if(!channel || !channel[0]) + return -1; + + if(!ciInChannel(chat, channel)) + return -1; + + return ciGetChannelNumUsers(chat, channel); +} +#ifdef GSI_UNICODE +int chatGetChannelNumUsersW(CHAT chat, + const unsigned short * channel) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + int result = chatGetChannelNumUsersA(chat, channel_A); + gsifree(channel_A); + + return result; +} +#endif + +CHATBool chatInChannelA(CHAT chat, + const char * channel) + +{ + CONNECTION; + if(!connection->connected) + return CHATFalse; + + ASSERT_CHANNEL(); + + if(!channel || !channel[0]) + return CHATFalse; + + return ciInChannel(chat, channel); +} +#ifdef GSI_UNICODE +CHATBool chatInChannelW(CHAT chat, + const unsigned short * channel) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + CHATBool result = chatInChannelA(chat, channel_A); + gsifree(channel_A); + + return result; +} +#endif + + +/********** +** USERS ** +**********/ +static void ciEnumUsersCallback(CHAT chat, const char * channel, int numUsers, const char ** users, int * modes, void * param) +{ + ciEnumUsersData * data; + CONNECTION; + + // Check the args. + ////////////////// + ASSERT_CHANNEL(); + assert(numUsers >= 0); +#ifdef _DEBUG + { + int i; + if(numUsers > 0) + { + assert(users != NULL); + assert(modes != NULL); + } + for(i = 0 ; i < numUsers ; i++) + { + ASSERT_USER(users[i]); + ASSERT_TYPE(modes[i]); + } + } +#endif + assert(param != NULL); + + // Get the data. + //////////////// + data = (ciEnumUsersData *)param; + assert(data->callback != NULL); + + // Call the callback directly. + ////////////////////////////// +#ifdef GSI_UNICODE + { + unsigned short* channel_W = UTF8ToUCS2StringAlloc(channel); + unsigned short** users_W = UTF8ToUCS2StringArrayAlloc(users, numUsers); + data->callback(chat, CHATTrue, channel_W, numUsers, (const unsigned short**)users_W, modes, data->param); + gsifree(channel_W); + while(numUsers-- > 0) + gsifree(users_W[numUsers]); + gsifree(users_W); + } +#else + data->callback(chat, CHATTrue, channel, numUsers, users, modes, data->param); +#endif +} + +void chatEnumUsersA(CHAT chat, + const char * channel, + chatEnumUsersCallback callback, + void * param, + CHATBool blocking) +{ + int ID; + ciEnumUsersData data; + CONNECTION; + CONNECTED; + + //ASSERT_CHANNEL(); + assert(callback != NULL); + + if(channel == NULL) + channel = ""; + + // Is there a channel specified? + //////////////////////////////// + if(channel[0] != '\0') + { + // Check if we have this one locally. + ///////////////////////////////////// + if(ciInChannel(chat, channel)) + { + // Get the users in the channel. + //////////////////////////////// + data.callback = callback; + data.param = param; + ciChannelListUsers(chat, channel, ciEnumUsersCallback, &data); + + return; + } + } + + ciSocketSendf(&connection->chatSocket, "NAMES %s", channel); + + // Channel needs to be empty, not NULL, for the filter. + /////////////////////////////////////////////////////// + if(!channel[0]) + channel = NULL; + + ID = ciAddNAMESFilter(chat, channel, callback, param); + + CI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void chatEnumUsersW(CHAT chat, + const unsigned short * channel, + chatEnumUsersCallback callback, + void * param, + CHATBool blocking) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + chatEnumUsersA(chat, channel_A, callback, param, blocking); + gsifree(channel_A); +} +#endif + + +// Enumerates the channels that we are joined to +////////////////////////////////////////////////////// +void chatEnumJoinedChannels(CHAT chat, + chatEnumJoinedChannelsCallback callback, + void * param) +{ + ciEnumJoinedChannels(chat, callback, param); +} + +void chatSendUserMessageA(CHAT chat, + const char * user, + const char * message, + int type) +{ + CONNECTION; + CONNECTED; + + ASSERT_USER(user); + ASSERT_TYPE(type); + + if (!message || message[0] == 0) + return; + + if(type == CHAT_MESSAGE) + ciSocketSendf(&connection->chatSocket, "PRIVMSG %s :%s", user, message); + else if(type == CHAT_ACTION) + ciSocketSendf(&connection->chatSocket, "PRIVMSG %s :\001ACTION %s\001", user, message); + else if(type == CHAT_NOTICE) + ciSocketSendf(&connection->chatSocket, "NOTICE %s :%s", user, message); + else if(type == CHAT_UTM) + ciSocketSendf(&connection->chatSocket, "UTM %s :%s", user, message); + else if(type == CHAT_ATM) + ciSocketSendf(&connection->chatSocket, "ATM %s :%s", user, message); +} +#ifdef GSI_UNICODE +void chatSendUserMessageW(CHAT chat, + const unsigned short * user, + const unsigned short * message, + int type) +{ + char* user_A = (char*)UCS2ToUTF8StringAlloc(user); + char* message_A = (char*)UCS2ToUTF8StringAlloc(message); + chatSendUserMessageA(chat, user_A, message_A, type); + gsifree(user_A); + gsifree(message_A); +} +#endif + +void chatGetUserInfoA(CHAT chat, + const char * user, + chatGetUserInfoCallback callback, + void * param, + CHATBool blocking) +{ + int ID; + CONNECTION; + CONNECTED; + + ASSERT_USER(user); + assert(callback != NULL); + + ciSocketSendf(&connection->chatSocket, "WHOIS %s", user); + + ID = ciAddWHOISFilter(chat, user, callback, param); + + CI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void chatGetUserInfoW(CHAT chat, + const unsigned short * user, + chatGetUserInfoCallback callback, + void * param, + CHATBool blocking) +{ + char* user_A = (char*)UCS2ToUTF8StringAlloc(user); + chatGetUserInfoA(chat, user_A, callback, param, blocking); + gsifree(user_A); +} +#endif + +void chatGetBasicUserInfoA(CHAT chat, + const char * nick, + chatGetBasicUserInfoCallback callback, + void * param, + CHATBool blocking) +{ + int ID; + const char * user; + const char * address; + CONNECTION; + CONNECTED; + + ASSERT_USER(nick); + assert(callback != NULL); + + // Check if we already have it. + /////////////////////////////// + if(ciGetUserBasicInfoA(chat, nick, &user, &address)) + { + ciCallbackGetBasicUserInfoParams params; + + params.success = CHATTrue; + params.nick = (char *)nick; + params.user = (char *)user; + params.address = (char *)address; + + ID = ciGetNextID(chat); + + ciAddCallback(chat, CALLBACK_GET_BASIC_USER_INFO, (void*)callback, ¶ms, param, ID, NULL); + } + else + { + ciSocketSendf(&connection->chatSocket, "WHO %s", nick); + + ID = ciAddWHOFilter(chat, nick, callback, param); + } + + CI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void chatGetBasicUserInfoW(CHAT chat, + const unsigned short * nick, + chatGetBasicUserInfoCallback callback, + void * param, + CHATBool blocking) +{ + char* nick_A = (char*)UCS2ToUTF8StringAlloc(nick); + chatGetBasicUserInfoA(chat, nick_A, callback, param, blocking); + gsifree(nick_A); +} +#endif + +CHATBool chatGetBasicUserInfoNoWaitA(CHAT chat, + const char * nick, + const char ** user, + const char ** address) +{ + CONNECTION; + // 2002.Feb.28.JED - added additional check, was blowing up in GSA + if(!connection) + return CHATFalse; + if(!connection->connected) + return CHATFalse; + + ASSERT_USER(nick); + + return ciGetUserBasicInfoA(chat, nick, user, address); +} +#ifdef GSI_UNICODE +CHATBool chatGetBasicUserInfoNoWaitW(CHAT chat, + const unsigned short * nick, + const unsigned short ** user, + const unsigned short ** address) +{ + char nick_A[MAX_NICK]; + + CONNECTION; + // 2002.Feb.28.JED - added additional check, was blowing up in GSA + if(!connection) + return CHATFalse; + if(!connection->connected) + return CHATFalse; + + assert(nick); + + UCS2ToAsciiString(nick, nick_A); + return ciGetUserBasicInfoW(chat, nick_A, user, address); +} +#endif + +void chatGetChannelBasicUserInfoA(CHAT chat, + const char * channel, + chatGetChannelBasicUserInfoCallback callback, + void * param, + CHATBool blocking) +{ + int ID; + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + assert(callback != NULL); + + ciSocketSendf(&connection->chatSocket, "WHO %s", channel); + + ID = ciAddCWHOFilter(chat, channel, callback, param); + + CI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void chatGetChannelBasicUserInfoW(CHAT chat, + const unsigned short * channel, + chatGetChannelBasicUserInfoCallback callback, + void * param, + CHATBool blocking) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + chatGetChannelBasicUserInfoA(chat, channel_A, callback, param, blocking); + gsifree(channel_A); +} +#endif + +void chatInviteUserA(CHAT chat, + const char * channel, + const char * user) +{ + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + ASSERT_USER(user); + + ciSocketSendf(&connection->chatSocket, "INVITE %s %s", user, channel); +} +#ifdef GSI_UNICODE +void chatInviteUserW(CHAT chat, + const unsigned short * channel, + const unsigned short * user) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + char* user_A = (char*)UCS2ToUTF8StringAlloc(user); + chatInviteUserA(chat, channel_A, user_A); + gsifree(channel_A); + gsifree(user_A); +} +#endif + +void chatKickUserA(CHAT chat, + const char * channel, + const char * user, + const char * reason) +{ + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + ASSERT_USER(user); + + if(reason == NULL) + reason = ""; + + ciSocketSendf(&connection->chatSocket, "KICK %s %s :%s", channel, user, reason); +} +#ifdef GSI_UNICODE +void chatKickUserW(CHAT chat, + const unsigned short * channel, + const unsigned short * user, + const unsigned short * reason) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + char* user_A = (char*)UCS2ToUTF8StringAlloc(user); + char* reason_A = (char*)UCS2ToUTF8StringAlloc(reason); + chatKickUserA(chat, channel_A, user_A, reason_A); + gsifree(channel_A); + gsifree(user_A); + gsifree(reason_A); +} +#endif + +void chatBanUserA(CHAT chat, + const char * channel, + const char * user) +{ + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + ASSERT_USER(user); + + ciSocketSendf(&connection->chatSocket, "WHOIS %s", user); + + ciAddBANFilter(chat, user, channel); +} +#ifdef GSI_UNICODE +void chatBanUserW(CHAT chat, + const unsigned short * channel, + const unsigned short * user) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + char* user_A = (char*)UCS2ToUTF8StringAlloc(user); + chatBanUserA(chat, channel_A, user_A); + gsifree(channel_A); + gsifree(user_A); +} +#endif + +void chatSetUserModeA(CHAT chat, + const char * channel, + const char * user, + int mode) +{ + int sign; + + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + ASSERT_USER(user); + ASSERT_TYPE(mode); + + sign = (mode & CHAT_OP)?'+':'-'; + ciSocketSendf(&connection->chatSocket, "MODE %s %co %s", channel, sign, user); + + sign = (mode & CHAT_VOICE)?'+':'-'; + ciSocketSendf(&connection->chatSocket, "MODE %s %cv %s", channel, sign, user); +} +#ifdef GSI_UNICODE +void chatSetUserModeW(CHAT chat, + const unsigned short * channel, + const unsigned short * user, + int mode) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + char* user_A = (char*)UCS2ToUTF8StringAlloc(user); + chatSetUserModeA(chat, channel_A, user_A, mode); + gsifree(channel_A); + gsifree(user_A); +} +#endif + +void chatGetUserModeA(CHAT chat, + const char * channel, + const char * user, + chatGetUserModeCallback callback, + void * param, + CHATBool blocking) +{ + int ID; + int mode; + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + ASSERT_USER(user); + assert(callback != NULL); + + // Get the mode. + //////////////// + mode = ciGetUserMode(chat, channel, user); + if(mode != -1) + { + ciCallbackGetUserModeParams params; + params.success = CHATTrue; + params.channel = (char *)channel; + params.user = (char *)user; + params.mode = mode; + + ID = ciGetNextID(chat); + ciAddCallback(chat, CALLBACK_GET_USER_MODE, (void*)callback, ¶ms, param, ID, NULL); + + CI_DO_BLOCKING; + } + + ciSocketSendf(&connection->chatSocket, "WHO %s", user); + + ID = ciAddUMODEFilter(chat, user, channel, callback, param); + + CI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void chatGetUserModeW(CHAT chat, + const unsigned short * channel, + const unsigned short * user, + chatGetUserModeCallback callback, + void * param, + CHATBool blocking) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + char* user_A = (char*)UCS2ToUTF8StringAlloc(user); + chatGetUserModeA(chat, channel_A, user_A, callback, param, blocking); + gsifree(channel_A); + gsifree(user_A); +} +#endif + +CHATBool chatGetUserModeNoWaitA(CHAT chat, + const char * channel, + const char * user, + int * mode) +{ + CONNECTION; + if(!connection->connected) + return CHATFalse; + + ASSERT_CHANNEL(); + ASSERT_USER(user); + assert(mode); + + // Get the mode. + //////////////// + *mode = ciGetUserMode(chat, channel, user); + + return (CHATBool)(*mode != -1); +} +#ifdef GSI_UNICODE +CHATBool chatGetUserModeNoWaitW(CHAT chat, + const unsigned short * channel, + const unsigned short * user, + int * mode) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + char* user_A = (char*)UCS2ToUTF8StringAlloc(user); + CHATBool result = chatGetUserModeNoWaitA(chat, channel_A, user_A, mode); + gsifree(channel_A); + gsifree(user_A); + return result; +} +#endif + + +void chatGetUdpRelayA(CHAT chat, + const char * channel, + chatGetUdpRelayCallback callback, + void * param, + CHATBool blocking) +{ + int ID; + CONNECTION; + CONNECTED; + + ASSERT_CHANNEL(); + assert(callback != NULL); + + ciSocketSendf(&connection->chatSocket, "GETUDPRELAY %s", channel); + + ID = ciAddGETUDPRELAYFilter(chat, channel, callback, param); + + CI_DO_BLOCKING; +} + +#ifdef GSI_UNICODE +void chatGetUdpRelayW(CHAT chat, + const unsigned short * channel, + chatGetUdpRelayCallback callback, + void * param, + CHATBool blocking) +{ + char* channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + chatGetUdpRelayA(chat, channel_A, callback, param, blocking); + gsifree(channel_A); +} +#endif + +/********* +** KEYS ** +*********/ +void chatSetGlobalKeysA(CHAT chat, + int num, + const char ** keys, + const char ** values) +{ + char buffer[512]; + const char * key; + const char * value; + int i; + CONNECTION; + CONNECTED; + + if(!keys || !values) + return; + + strcpy(buffer, "SETKEY :"); + for(i = 0 ; i < num ; i++) + { + key = keys[i]; + if(!key || !key[0]) + return; + value = values[i]; + if(!value) + value = ""; + sprintf(buffer + strlen(buffer), "\\%s\\%s", key, value); + } + + ciSocketSend(&connection->chatSocket, buffer); +} +#ifdef GSI_UNICODE +void chatSetGlobalKeysW(CHAT chat, + int num, + const unsigned short ** keys, + const unsigned short ** values) +{ + char** keys_A = (char**)UCS2ToUTF8StringArrayAlloc(keys, num); + char** values_A = (char**)UCS2ToUTF8StringArrayAlloc(values, num); + int i = 0; + chatSetGlobalKeysA(chat, num, (const char**)keys_A, (const char**)values_A); + for (; i < num; i++) + { + gsifree(keys_A[i]); + gsifree(values_A[i]); + } + gsifree(keys_A); + gsifree(values_A); +} +#endif + +static char * ciRandomCookie() +{ + static char cookie[4]; + static int nextCookie = 0; + + sprintf(cookie, "%03d", nextCookie++); + nextCookie %= 1000; + + return cookie; +} + +static void ciSendGetKey(CHAT chat, + const char * target, + const char * cookie, + int num, + const char ** keys) +{ + char buffer[512]; + int len; + int i; + int j; + int keyLen; + + CONNECTION; + + assert(target && target[0]); + assert(cookie && cookie[0]); + assert(num >= 1); + assert(keys); + + // Start off the buffer. + //////////////////////// + sprintf(buffer, "GETKEY %s %s 0 :", target, cookie); + len = (int)strlen(buffer); + + // Add the keys. + //////////////// + for(i = 0 ; i < num ; i++) + { + // Check for a blank. + ///////////////////// + if(!keys[i] || !keys[i][0]) + continue; + + // Check lengths. + ///////////////// + keyLen = (int)strlen(keys[i]); + if((len + keyLen + 1) >= (int)sizeof(buffer)) + return; + + // Add the key. + /////////////// + buffer[len++] = '\\'; + memcpy(buffer + len, keys[i], (unsigned int)keyLen); + for(j = len ; j < (len + keyLen) ; j++) + if(buffer[j] == '\\') + buffer[j] = '/'; + len += keyLen; + buffer[len] = '\0'; + } + + // Send it. + /////////// + ciSocketSend(&connection->chatSocket, buffer); +} + +void chatGetGlobalKeysA(CHAT chat, + const char * target, + int num, + const char ** keys, + chatGetGlobalKeysCallback callback, + void * param, + CHATBool blocking) +{ + char * cookie; + const char * channel; + int ID; + CONNECTION; + CONNECTED; + + assert(num >= 0); + assert(keys); + + if(!target || !target[0]) + target = connection->nick; + + // Get a cookie. + //////////////// + cookie = ciRandomCookie(); + + // Send the request. + //////////////////// + ciSendGetKey(chat, target, cookie, num, keys); + + // Check if this is a channel or a user. + //////////////////////////////////////// + if(target[0] == '#') + channel = target; + else + channel = NULL; + + ID = ciAddGETKEYFilter(chat, cookie, num, keys, channel, callback, param); + + CI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void chatGetGlobalKeysW(CHAT chat, + const unsigned short * target, + int num, + const unsigned short ** keys, + chatGetGlobalKeysCallback callback, + void * param, + CHATBool blocking) +{ + char* target_A; + char** keys_A; + int i = 0; + + assert(target); + assert(keys); + + target_A = (char*)UCS2ToUTF8StringAlloc(target); + keys_A = (char**)UCS2ToUTF8StringArrayAlloc(keys, num); + + chatGetGlobalKeysA(chat, target_A, num, (const char**)keys_A, callback, param, blocking); + gsifree(target_A); + + for (; i < num; i++) + gsifree(keys_A[i]); + gsifree(keys_A); +} +#endif + +void chatSetChannelKeysA(CHAT chat, + const char * channel, + const char * user, + int num, + const char ** keys, + const char ** values) +{ + char buffer[512]; + const char * value; + int i; + CONNECTION; + CONNECTED; + + if(!user || !user[0]) + sprintf(buffer, "SETCHANKEY %s :", channel); + else + sprintf(buffer, "SETCKEY %s %s :", channel, user); + for(i = 0 ; i < num ; i++) + { + value = values[i]; + if(!value) + value = ""; + sprintf(buffer + strlen(buffer), "\\%s\\%s", keys[i], value); + } + + ciSocketSend(&connection->chatSocket, buffer); +} +#ifdef GSI_UNICODE +void chatSetChannelKeysW(CHAT chat, + const unsigned short * channel, + const unsigned short * user, + int num, + const unsigned short ** keys, + const unsigned short ** values) +{ + char* channel_A; + char* user_A; + char** keys_A; + char** values_A; + int i = 0; + + channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + user_A = (char*)UCS2ToUTF8StringAlloc(user); + keys_A = (char**)UCS2ToUTF8StringArrayAlloc(keys, num); + values_A = (char**)UCS2ToUTF8StringArrayAlloc(values, num); + + chatSetChannelKeysA(chat, channel_A, user_A, num, (const char**)keys_A, (const char**)values_A); + + gsifree(channel_A); + gsifree(user_A); + + for (; i < num; i++) + { + gsifree(keys_A[i]); + gsifree(values_A[i]); + } + + gsifree(keys_A); + gsifree(values_A); +} +#endif + +static CHATBool ciSendGetChannelKey(CHAT chat, + const char * channel, + const char * nick, + const char * cookie, + int num, + const char ** keys) +{ + char buffer[512]; + int len; + int i; + int j; + int keyLen; + CHATBool getBrocastKeys = CHATFalse; + + CONNECTION; + + assert(channel && channel[0]); + assert(cookie && cookie[0]); + assert(!num || keys); + + // Start off the buffer. + //////////////////////// + if(!nick || !nick[0]) + sprintf(buffer, "GETCHANKEY %s %s 0 :", channel, cookie); + else + sprintf(buffer, "GETCKEY %s %s %s 0 :", channel, nick, cookie); + len = (int)strlen(buffer); + + // Add the keys. + //////////////// + for(i = 0 ; i < num ; i++) + { + // Check for a blank. + ///////////////////// + if(!keys[i] || !keys[i][0]) + continue; + + // Check for b_*. + ///////////////// + if(strcmp(keys[i], "b_*") == 0) + { + getBrocastKeys = CHATTrue; + continue; + } + + // Check lengths. + ///////////////// + keyLen = (int)strlen(keys[i]); + if((len + keyLen + 1) >= (int)sizeof(buffer)) + continue; + + // Add the key. + /////////////// + buffer[len++] = '\\'; + memcpy(buffer + len, keys[i], (unsigned int)keyLen); + for(j = len ; j < (len + keyLen) ; j++) + if(buffer[j] == '\\') + buffer[j] = '/'; + len += keyLen; + buffer[len] = '\0'; + } + + // Check for broadcast keys. + //////////////////////////// + if(getBrocastKeys) + { + if((len + 4) < (int)sizeof(buffer)) + { + strcpy(buffer + len, "\\b_*"); + len += 4; + } + } + + // Check for requesting all keys on a room. + /////////////////////////////////////////// + if(!num && (!nick || !nick[0])) + { + strcpy(buffer + len, "*"); + len++; + } + + // Send it. + /////////// + ciSocketSend(&connection->chatSocket, buffer); + + return getBrocastKeys; +} + +void chatGetChannelKeysA(CHAT chat, + const char * channel, + const char * user, + int num, + const char ** keys, + chatGetChannelKeysCallback callback, + void * param, + CHATBool blocking) +{ + char * cookie; + int ID; + CHATBool getBroadcastKeys; + CONNECTION; + CONNECTED; + + assert(num >= 0); + assert(!num || keys); + + // Get a cookie. + //////////////// + cookie = ciRandomCookie(); + + // Send the request. + //////////////////// + getBroadcastKeys = ciSendGetChannelKey(chat, channel, user, cookie, num, keys); + + if(!user || !user[0]) + ID = ciAddGETCHANKEYFilter(chat, cookie, num, keys, getBroadcastKeys, callback, param); + else + ID = ciAddGETCKEYFilter(chat, cookie, num, keys, (CHATBool)(strcmp(user, "*") == 0), getBroadcastKeys, callback, param); + + CI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void chatGetChannelKeysW(CHAT chat, + const unsigned short * channel, + const unsigned short * user, + int num, + const unsigned short ** keys, + chatGetChannelKeysCallback callback, + void * param, + CHATBool blocking) +{ + char* channel_A; + char* user_A; + char** keys_A; + int i = 0; + + channel_A = (char*)UCS2ToUTF8StringAlloc(channel); + user_A = (char*)UCS2ToUTF8StringAlloc(user); + keys_A = (char**)UCS2ToUTF8StringArrayAlloc(keys, num); + + chatGetChannelKeysA(chat, channel_A, user_A, num, (const char**)keys_A, callback, param, blocking); + + gsifree(channel_A); + gsifree(user_A); + + for (; i < num; i++) + gsifree(keys_A[i]); + + gsifree(keys_A); +} +#endif + +// Check if a given nickname is valid. Looks for illegal IRC characters. +// [in] nick - The nickname to validate +int ciNickIsValid(const char* nick) +{ + if (strlen(nick) >= MAX_CHAT_NICK) + return CHAT_NICK_TOO_LONG; + + // Empty nick is invalid + if ((NULL == nick) || ('\0' == *nick)) + return CHAT_INVALID; + + + // 10-14-2004 Changed by Saad Nader + // Using the nickname rules for unique nicks + // commented out the previous rules + //////////////////////////////////////////////// + // Nick can't start with a number or a '+', '@', '#' + //if(isdigit(*oldNick) || (*oldNick == '-')) + + if(*nick == '@' || *nick == '#' || *nick == '+' || *nick == ':') + return CHAT_INVALID; + + // Make sure each character is valid + while(*nick != '\0') + { + // If the character isn't in the valid set, the nick is not valid + if (NULL == strchr(VALID_NICK_CHARS,*nick++)) + return CHAT_INVALID; + } + + return CHAT_NICK_OK; +} + +/**************** +** NICK ERRORS ** +****************/ +void ciNickError(CHAT chat, int type, const char * nick, int numSuggestedNicks, char ** suggestedNicks) +{ + CONNECTION; + + // Check if there's a nick-in-use callback. + /////////////////////////////////////////// + if(connection->nickErrorCallback) + { + ciCallbackNickErrorParams params; + + // Add the callback. + //////////////////// + memset(¶ms, 0, sizeof(ciCallbackNickErrorParams)); + params.type = type; + params.nick = (char *)nick; + params.numSuggestedNicks = numSuggestedNicks; + params.suggestedNicks = suggestedNicks; + ciAddCallback(chat, CALLBACK_NICK_ERROR, (void*)connection->nickErrorCallback, ¶ms, connection->connectParam, 0, NULL); + } + else + { + // There's no callback, disconnect. + /////////////////////////////////// + connection->connecting = CHATFalse; + + // Call the callback. + ///////////////////// + if(connection->connectCallback != NULL) + connection->connectCallback(chat, CHATFalse, CHAT_NICK_ERROR, connection->connectParam); + } +} + diff --git a/code/gamespy/Chat/chatMain.h b/code/gamespy/Chat/chatMain.h new file mode 100644 index 00000000..417e6a87 --- /dev/null +++ b/code/gamespy/Chat/chatMain.h @@ -0,0 +1,133 @@ +/* +GameSpy Chat SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _CHATMAIN_H_ +#define _CHATMAIN_H_ + +/************* +** INCLUDES ** +*************/ +#include "chat.h" +#include "chatSocket.h" +#include "chatHandlers.h" +#include "../hashtable.h" +#include "../darray.h" +#include "../md5.h" + +/************ +** DEFINES ** +************/ +#define MAX_NICK 64 +#define MAX_CHAT_NICK 21 +#define MAX_NAME 128 +#define MAX_USER 128 +#define MAX_SERVER 128 +#define MAX_PARAM 512 +#define MAX_SECRETKEY 128 +#define MAX_EMAIL 64 +#define MAX_PROFILENICK 32 +#define MAX_UNIQUENICK 64 +#define MAX_PASSWORD 32 +#define MAX_AUTHTOKEN 256 +#define MAX_PARTNERCHALLENGE 256 + +#define CONNECTION ciConnection * connection;\ + assert(chat != NULL);\ + connection = (ciConnection *)chat;\ + GSI_UNUSED(connection); +#define CONNECTED if(!connection || !connection->connected) return; //ERRCON +#if 0 +ciConnection * connection; // for visual assist +#endif + +#define VALID_NICK_CHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\"#$%&'()*+,-./:;<=>?@[]^_`{|}~" + +#define CI_DEFAULT_SERVER_ADDRESS "peerchat." GSI_DOMAIN_NAME +#define CI_DEFUILT_SERVER_PORT 6667 +/********** +** TYPES ** +**********/ +typedef enum +{ + CINoLogin, + CIUniqueNickLogin, + CIProfileLogin, + CIPreAuthLogin +} CILoginType; + +typedef struct ciConnection +{ + CHATBool connected; + CHATBool connecting; + CHATBool disconnected; + chatNickErrorCallback nickErrorCallback; + chatFillInUserCallback fillInUserCallback; + chatConnectCallback connectCallback; + void * connectParam; + + ciSocket chatSocket; + + char nick[MAX_NICK]; + char name[MAX_NAME]; + char user[MAX_USER]; + + int namespaceID; + char email[MAX_EMAIL]; + char profilenick[MAX_PROFILENICK]; + char uniquenick[MAX_UNIQUENICK]; + char password[MAX_PASSWORD]; + + char authtoken[MAX_AUTHTOKEN]; + char partnerchallenge[MAX_PARTNERCHALLENGE]; + +#ifdef GSI_UNICODE + unsigned short nickW[MAX_NICK]; + unsigned short userW[MAX_NAME]; +#endif + + unsigned int IP; + + char server[MAX_SERVER]; + int port; + + chatGlobalCallbacks globalCallbacks; + + HashTable channelTable; + DArray enteringChannelList; + + ciServerMessageFilter * filterList; + ciServerMessageFilter * lastFilter; + + int nextID; + + DArray callbackList; + + CHATBool quiet; + + char secretKey[MAX_SECRETKEY]; + + CILoginType loginType; + + int userID; + int profileID; +} ciConnection; + +void ciSendNickAndUser(CHAT chat); +void ciSendNick(CHAT chat); +void ciSendUser(CHAT chat); +void ciSendLogin(CHAT chat); +void ciHandleDisconnect(CHAT chat, const char * reason); +int ciNickIsValid(const char* nick); +void ciNickError(CHAT chat, int type, const char * nick, int numSuggestedNicks, char ** suggestedNicks); + +#define strzcpy(dest, src, len) { strncpy(dest, src, (len)); (dest)[(len) - 1] = '\0'; } +#define wcszcpy(dest, src, len) { wcsncpy(dest, src, (len)); (dest)[(len) - 1] = 0; } + +#endif diff --git a/code/gamespy/Chat/chatSocket.c b/code/gamespy/Chat/chatSocket.c new file mode 100644 index 00000000..9bb7227d --- /dev/null +++ b/code/gamespy/Chat/chatSocket.c @@ -0,0 +1,1032 @@ +/* +GameSpy Chat SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include +#include +#include +#include +#include "chatMain.h" +#include "chatSocket.h" + + +#if defined(_WIN32) +// Silence the "conditional expression is constant" on the FD_SET macros +#pragma warning(disable:4127) +#endif + +/************ +** DEFINES ** +************/ +#define BUFFER_INC 8192 +#define RECV_LEN 4096 + +/*********** +** MACROS ** +***********/ +#define ASSERT_SOCK(sock) {\ + assert((sock) != NULL);\ + assert(((sock)->connectState == ciNotConnected) ||\ + ((sock)->connectState == ciConnected) ||\ + ((sock)->connectState == ciDisconnected));\ + ASSERT_BUFFER(&(sock)->inputQueue);\ + ASSERT_BUFFER(&(sock)->outputQueue);\ + } + +#define ASSERT_CONNECTED(sock) assert((sock)->connectState == ciConnected) + +#define ASSERT_BUFFER(buffer) {\ + assert((buffer) != NULL);\ + assert((buffer)->size >= 0);\ + assert(((buffer)->size % BUFFER_INC) == 0);\ + assert((buffer)->length >= 0);\ + assert((buffer)->length <= (buffer)->size);\ + } +#define RESET(ptr) {if (ptr) {gsifree(ptr); ptr = NULL;} } + +/********* +** TIME ** +*********/ +#ifdef IRC_LOG +static const char * ciGetTime(void) +{ +#if defined(UNDER_CE) || defined(_PS2) || defined(_NITRO) + return ""; +#else + static char buffer[256]; + time_t timer; + struct tm * now; + + timer = time(NULL); + now = localtime(&timer); + if(now) // fixes the date > 2060 crash (23apr03/bgw) + { + if(now->tm_year > 99) + now->tm_year -= 100; + sprintf(buffer, "%02d.%02d.%02d %02d:%02d.%02d", now->tm_mon + 1, now->tm_mday, now->tm_year, now->tm_hour, now->tm_min, now->tm_sec); + } + else + strcpy(buffer, "00.00.00 00:00.00"); + + return buffer; +#endif +} +#endif + +/*********** +** BUFFER ** +***********/ +static CHATBool ciBufferInit(ciBuffer * buffer) +{ + assert(buffer != NULL); + + buffer->length = 0; + buffer->size = BUFFER_INC; + buffer->buffer = (char *)gsimalloc((unsigned int)buffer->size + 1); + if(buffer->buffer == NULL) + return CHATFalse; + + // Just for fun. + //////////////// + buffer->buffer[0] = '\0'; + + return CHATTrue; +} + +static void ciBufferFree(ciBuffer * buffer) +{ + gsifree(buffer->buffer); +} + +static CHATBool ciBufferPreAppend(ciBuffer * buffer, int len) +{ + int total; + char * tempPtr; + + ASSERT_BUFFER(buffer); + assert(len >= 0); + assert(len <= SHRT_MAX); // sanity check + + // Check if the buffer is big enough. + ///////////////////////////////////// + total = (buffer->length + len); + if(total <= buffer->size) + return CHATTrue; + + // Figure out the new size. + /////////////////////////// + total += BUFFER_INC; + total -= (total % BUFFER_INC); + + // Allocate the memory. + /////////////////////// + tempPtr = (char *)gsirealloc(buffer->buffer, (unsigned int)total + 1); + if(tempPtr == NULL) + return CHATFalse; + + // Update the buffer. + ///////////////////// + buffer->buffer = tempPtr; + buffer->size = total; + + return CHATTrue; +} + +static void ciBufferClipFront(ciBuffer * buffer, int len) +{ + ASSERT_BUFFER(buffer); + assert(len >= 0); + assert(len <= buffer->length); + + buffer->length -= len; + memmove(buffer->buffer, &buffer->buffer[len], (unsigned int)buffer->length); + buffer->buffer[buffer->length] = '\0'; +} + + +#ifdef __MWERKS__ // CodeWarrior will warn if not prototyped +/*************** +** PROTOTYPES ** +****************/ +CHATBool ciParseParam(const char *pText, ciServerMessage * message); +#endif + +/************** +** FUNCTIONS ** +**************/ +CHATBool ciSocketInit(ciSocket * sock, const char * nick) +{ +#ifdef IRC_LOG + FILE * log; +#endif + + assert(sock != NULL); + + memset(sock, 0, sizeof(ciSocket)); + + sock->sock = INVALID_SOCKET; + if(!ciBufferInit(&sock->inputQueue)) + return CHATFalse; + if(!ciBufferInit(&sock->outputQueue)) + { + ciBufferFree(&sock->inputQueue); + return CHATFalse; + } +#ifdef IRC_LOG + sprintf(sock->filename, "%s_irc.log", nick); + log = fopen(sock->filename, "at"); + if(log != NULL) + { + fprintf(log, "\n\n\n\n\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n"); + fclose(log); + } +#endif + + GSI_UNUSED(nick); + return CHATTrue; +} + +CHATBool ciSocketConnect(ciSocket * sock, + const char * serverAddress, + int port) +{ + unsigned int ip; + HOSTENT * host; + SOCKADDR_IN address; + int rcode; + +#if !defined(INSOCK) && !defined(_NITRO) && !defined(_REVOLUTION) + int keepalive; +#endif + + ASSERT_SOCK(sock); + assert(serverAddress != NULL); + assert(port >= 0); + assert(port <= USHRT_MAX); + assert(sock->connectState == ciNotConnected); + + // Copy off the address. + //////////////////////// + strzcpy(sock->serverAddress, serverAddress, 255); + + // Try resolving the string as an IP a.b.c.d number. + //////////////////////////////////////////////////// + ip = inet_addr(serverAddress); + if(ip == INADDR_NONE) + { + // Try resolving with DNS. + ////////////////////////// + host = gethostbyname((char *)serverAddress); + if(host == NULL) + return CHATFalse; + + // Get the ip. + ////////////// + ip = *(unsigned int *)host->h_addr_list[0]; + } + + // Setup the address. + ///////////////////// + memset(&address, 0, sizeof(SOCKADDR_IN)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = ip; + address.sin_port = htons((unsigned short)port); + + // Create the socket. + ///////////////////// + sock->sock = socket(AF_INET, SOCK_STREAM, 0); + if(sock->sock == INVALID_SOCKET) + return CHATFalse; + + // Enable keep-alive. + ///////////////////// +#if !defined(INSOCK) && !defined(_NITRO) && !defined(_REVOLUTION) + keepalive = 1; + rcode = setsockopt(sock->sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&keepalive, sizeof(int)); + //assert(gsiSocketIsNotError(rcode)); +#endif + + // Try and connect. + /////////////////// + rcode = connect(sock->sock, (SOCKADDR *)&address, sizeof(SOCKADDR_IN)); + if(gsiSocketIsError(rcode)) + { + closesocket(sock->sock); + return CHATFalse; + } + + // We're connected. + /////////////////// + sock->connectState = ciConnected; + + return CHATTrue; +} + +void ciSocketDisconnect(ciSocket * sock) +{ + int i; + + ASSERT_SOCK(sock); + + // Shutdown the socket. + /////////////////////// + if(sock->sock != INVALID_SOCKET) + { + shutdown(sock->sock, 2); + closesocket(sock->sock); + } + + // We're disconnected. + ////////////////////// + sock->connectState = ciDisconnected; + + // gsifree the buffers. + //////////////////// + ciBufferFree(&sock->inputQueue); + ciBufferFree(&sock->outputQueue); + + // gsifree the last-message pointers. + ////////////////////////////////// + gsifree(sock->lastMessage.message); + gsifree(sock->lastMessage.server); + gsifree(sock->lastMessage.nick); + gsifree(sock->lastMessage.user); + gsifree(sock->lastMessage.host); + gsifree(sock->lastMessage.command); + gsifree(sock->lastMessage.middle); + gsifree(sock->lastMessage.param); + for(i = 0 ; i < sock->lastMessage.numParams ; i++) + gsifree(sock->lastMessage.params[i]); + gsifree(sock->lastMessage.params); +} + +static void ciSocketSelect(SOCKET sock, CHATBool * readFlag, CHATBool * writeFlag, CHATBool * exceptFlag) +{ + int aReadFlag = 0; + int aWriteFlag = 0; + int aExceptFlag = 0; + + // Call generic select GSISocketSelect + GSISocketSelect(sock, &aReadFlag, &aWriteFlag, &aExceptFlag); + + // Translate the int flags to ChatBool flags + if (readFlag) + *readFlag = (CHATBool)aReadFlag; + if (writeFlag) + *writeFlag = (CHATBool)aWriteFlag; + if (exceptFlag) + *exceptFlag = (CHATBool)aExceptFlag; +} + +static void ciSocketThinkSend(ciSocket * sock) +{ + int rcode; + int len; + + ASSERT_SOCK(sock); + ASSERT_CONNECTED(sock); + + // While there's data to send... + //////////////////////////////// + while(sock->outputQueue.length > 0) + { + CHATBool writeFlag; + + // Can we send? + /////////////// + ciSocketSelect(sock->sock, NULL, &writeFlag, NULL); + if(!writeFlag) + return; + + // Try and send some. + ///////////////////// + len = min(sock->outputQueue.length, 1024); + rcode = send(sock->sock, sock->outputQueue.buffer, len, 0); + + if(rcode == 0) + return; + if(gsiSocketIsError(rcode)) + return; //ERRCON + + // Update the output queue. + /////////////////////////// + ciBufferClipFront(&sock->outputQueue, rcode); + } +} + +static void ciSocketThinkRecv(ciSocket * sock) +{ + CHATBool readFlag; + int len; + int rcode; + char * pos; + + ASSERT_SOCK(sock); + ASSERT_CONNECTED(sock); + + while(CHATTrue) + { + // Check the read flag. + /////////////////////// + ciSocketSelect(sock->sock, &readFlag, NULL, NULL); + + // Nothing to read? + /////////////////// + if(!readFlag) + return; + + // Make sure the buffer is ready. + ///////////////////////////////// + if(!ciBufferPreAppend(&sock->inputQueue, RECV_LEN)) + return; //ERRCON + + // Recv into the buffer. + //////////////////////// + pos = &sock->inputQueue.buffer[sock->inputQueue.length]; + rcode = recv(sock->sock, pos, RECV_LEN, 0); + + // Connection closed? + ///////////////////// + if(rcode <= 0) //crt -- handle remote disconnections + { + sock->connectState = ciDisconnected; + return; + } + + // Set the number of bytes read. + //////////////////////////////// + len = rcode; + +#if 0 +{ + char * buffer; + int i; + buffer = (char *)gsimalloc(len + 2); + assert(buffer != NULL); + memcpy(buffer, &sock->inputQueue.buffer[sock->inputQueue.length], len); + buffer[len] = '\0'; + OutputDebugString("--->>>XXXRECV\n"); + for(i = 0 ; i < len ; i += 1023) + OutputDebugString(&buffer[i]); + OutputDebugString("<<<---XXXRECV\n"); +} +#endif + + // Decrypt data if the socket is secure. + //////////////////////////////////////// + if(sock->secure) + gs_crypt((unsigned char *)pos, len, &sock->inKey); + + // Update the buffer length. + //////////////////////////// + sock->inputQueue.length += len; + + // NUL terminate the buffer. + //////////////////////////// + sock->inputQueue.buffer[sock->inputQueue.length] = '\0'; + } +} + +void ciSocketThink(ciSocket * sock) +{ + ASSERT_SOCK(sock); + ASSERT_CONNECTED(sock); + + // Disconnected? + //////////////// + if(sock->connectState == ciDisconnected) + return; + + // Send any waiting data. + ///////////////////////// + ciSocketThinkSend(sock); + + // Recv any waiting data. + ///////////////////////// + ciSocketThinkRecv(sock); +} + +CHATBool ciSocketSend(ciSocket * sock, + const char * buffer) +{ +#ifdef IRC_LOG + FILE * log; +#endif + int len; + char * pos; + + ASSERT_SOCK(sock); + ASSERT_CONNECTED(sock); + assert(buffer != NULL); + + // Disconnected? + //////////////// + if(sock->connectState == ciDisconnected) + return CHATTrue; + + // Get the buffer length. + ///////////////////////// + len = (int)strlen(buffer); + + // Make sure the buffer is big enough. + ////////////////////////////////////// + if(!ciBufferPreAppend(&sock->outputQueue, len + 2)) + return CHATFalse; + + // Append to the output buffer. + /////////////////////////////// + pos = &sock->outputQueue.buffer[sock->outputQueue.length]; + memcpy(pos, buffer, (unsigned int)len); + + // Update the buffer length. + //////////////////////////// + sock->outputQueue.length += len; + + // Add the CRLF. + //////////////// + sock->outputQueue.buffer[sock->outputQueue.length++] = 0x0D; + sock->outputQueue.buffer[sock->outputQueue.length++] = 0x0A; + + // Encrypt data if the socket is secure. + //////////////////////////////////////// + if(sock->secure) + gs_crypt((unsigned char *)pos, len + 2, &sock->outKey); + +#ifdef IRC_LOG + // Write it to the log. + /////////////////////// + log = fopen(sock->filename, "at"); + if(log != NULL) + { + fprintf(log, "%s | OUT | %s\n", ciGetTime(), buffer); + fclose(log); + } +#endif + + return CHATTrue; +} + +CHATBool ciSocketSendf(ciSocket * sock, + const char * format, + ...) +{ + static char buffer[4096]; + int num; + va_list args; + + ASSERT_SOCK(sock); + ASSERT_CONNECTED(sock); + assert(buffer != NULL); + + // Disconnected? + //////////////// + if(sock->connectState == ciDisconnected) + return CHATTrue; + + // Do the formatting. + ///////////////////// + va_start(args, format); + num = vsprintf(buffer, format, args); + if(num != -1) + buffer[num] = '\0'; + else + buffer[sizeof(buffer) - 1] = '\0'; //ERRCON + + // Send. + //////// + return ciSocketSend(sock, buffer); +} + +static CHATBool ciParseUser(const char *pText, ciServerMessage * message) +{ + char *pTmpNick = NULL, *pTmpUsername = NULL, *pTmpHost = NULL; + int nNick = 0, nUsername = 0, nHost = 0; + char *p; + + if(pText == NULL || pText[0] == '\0') + { + assert(0); + return CHATFalse; //ERRCON + } + + p = pTmpNick = (char *)pText; + + while(*p != '\0') + { + if(*p != '!') + { + ++p; + ++nNick; + } + else + { + pTmpUsername = ++p; + + while(*p != '\0') + { + if(*p != '@') + { + ++p; + ++nUsername; + } + else + { + pTmpHost = ++p; + + while(*p != '\0') + { + ++p; + ++nHost; + } + } + } + } + } + + if(nNick) + { + message->nick = (char *)gsimalloc((unsigned int)nNick + 1); + + if(message->nick) + { + memcpy(message->nick, pTmpNick, (unsigned int)nNick); + message->nick[nNick] = '\0'; + } + } + else + message->nick = NULL; + + if(nUsername) + { + message->user = (char *)gsimalloc((unsigned int)nUsername + 1); + + if(message->user) + { + memcpy(message->user, pTmpUsername, (unsigned int)nUsername); + message->user[nUsername] = '\0'; + } + } + else + message->user = NULL; + + if(nHost) + { + message->host = (char *)gsimalloc((unsigned int)nHost + 1); + + if(message->host) + { + memcpy(message->host, pTmpHost, (unsigned int)nHost); + message->host[nHost] = '\0'; + } + } + else + message->host = NULL; + + return CHATTrue; +} + +static CHATBool ciAddParam(const char *param, ciServerMessage * message) +{ + void * tempPtr; + + // Reallocate the parameter array. + ////////////////////////////////// + tempPtr = gsirealloc(message->params, sizeof(char *) * (message->numParams + 1)); + if(tempPtr == NULL) + return CHATFalse; //ERRCON + message->params = (char **)tempPtr; + + // Allocate mem for the param. + ////////////////////////////// + tempPtr = gsimalloc(strlen(param) + 1); + if(tempPtr == NULL) + return CHATFalse; //ERRCON + + // Copy the param. + ////////////////// + strcpy((char *)tempPtr, param); + message->params[message->numParams++] = (char *)tempPtr; + + return CHATTrue; +} + +CHATBool ciParseParam(const char *pText, ciServerMessage * message) +{ + char * colon; + char * str; + char * p; + + assert(pText != NULL); + assert(message != NULL); + + // Copy off the text. + ///////////////////// + p = (char *)gsimalloc(strlen(pText) + 1); + if(p == NULL) + return CHATFalse; //ERRCON + strcpy(p, pText); + + // Find the colon. + ////////////////// + if(p[0] == ':') + { + p[0] = '\0'; + colon = &p[1]; + } + else + { + colon = strstr(p, " :"); + if(colon != NULL) + { + *colon = '\0'; + colon += 2; + } + } + + str = strtok(p, " "); + while(str != NULL) + { + // Add the param. + ///////////////// + if(!ciAddParam(str, message)) + { + gsifree(p); + return CHATFalse; //ERRCON + } + + // Get the next one. + //////////////////// + str = strtok(NULL, " "); + } + + if(colon != NULL) + { + // Add the last param. + ////////////////////// + if(!ciAddParam(colon, message)) + { + gsifree(p); + return CHATFalse; //ERRCON + } + } + + gsifree(p); + return CHATTrue; +} + +static CHATBool ciParseMessage(ciSocket * sock, const char *sText) +{ + // TRACE("ServM: Parse Data=%s\n", sText); + int nMessage = 0, nServer = 0, nCommand = 0, nMiddle = 0, nParam = 0; + char *p, *temp; + ciServerMessage * message = &sock->lastMessage; + + if(sText == NULL || sText[0] == '\0') + { + assert(0); + return CHATFalse; //ERRCON + } + + nMessage = (int)strlen(sText); + message->message = (char *)gsimalloc((unsigned int)nMessage + 1); + if(message->message == NULL) + return CHATFalse; //ERRCON + memcpy(message->message, sText, (unsigned int)nMessage); + message->message[nMessage] = '\0'; + + p = (char *)sText; + + while(*p == '\n' || *p == '\r') + ++p; + + if(*p == ':') + { // server + message->server = ++p; // ?? BUGBUG + + if(*p != '\0') + { + while(*p != ' ' && *p != '\0') + { + ++nServer; + ++p; + } + } + } + + while(*p == ' ') + // skip spaces + ++p; + + if(*p != '\0') + { // command + message->command = p; + + while(*p != ' ' && *p != '\0') + { + ++nCommand; + ++p; + } + } + + while(*p == ' ') + // skip spaces + ++p; + + if(*p != ':' && *p != '\0') + { // middle + message->middle = p; + + while(*p != ' ' && *p != '\0') + { + ++nMiddle; + ++p; + } + } + + while(*p == ' ') + // skip spaces + ++p; + + //if(*p == ':') + // params delimiter + // ++p; + + if(*p != '\0') + { // params + message->param = p; + + while(*p != '\0') + { + ++nParam; + ++p; + } + } + + if(nServer) + { + temp = message->server; + message->server = (char *)gsimalloc((unsigned int)nServer + 1); + + if(message->server) + { + memcpy(message->server, temp, (unsigned int)nServer); + message->server[nServer] = '\0'; + } + + if(!ciParseUser(message->server, message)) + { + RESET(message->message); + RESET(message->server); + return CHATFalse; //ERRCON + } + } + else + { + message->server = NULL; + message->nick = NULL; + message->user = NULL; + message->host = NULL; + } + + if(nMiddle) + { + if(!ciParseParam(message->middle, message)) + { + RESET(message->message); + RESET(message->server); + RESET(message->nick); + RESET(message->user); + RESET(message->host); + return CHATFalse; //ERRCON + } + } + else if(nParam) + { + if(!ciParseParam(message->param, message)) + { + RESET(message->message); + RESET(message->server); + RESET(message->nick); + RESET(message->user); + RESET(message->host); + return CHATFalse; //ERRCON + } + } + else + { + message->params = NULL; + message->numParams = 0; + } + + if(nParam) + { + temp = message->param; + message->param = (char *)gsimalloc((unsigned int)nParam + 1); + + if(message->param) + { + memcpy(message->param, temp, (unsigned int)nParam); + message->param[nParam] = '\0'; + } + } + else + { + message->param = NULL; + } + + if(nCommand) + { + temp = message->command; + message->command = (char *)gsimalloc((unsigned int)nCommand + 1); + + if(message->command) + { + memcpy(message->command, temp, (unsigned int)nCommand); + message->command[nCommand] = '\0'; + } + } + else + message->command = NULL; + + if(nMiddle) + { + temp = message->middle; + message->middle = (char *)gsimalloc((unsigned int)nMiddle + 1); + + if(message->middle) + { + memcpy(message->middle, temp, (unsigned int)nMiddle); + message->middle[nMiddle] = '\0'; + } + } + else + message->middle = NULL; + + return CHATTrue; +} + + +static CHATBool ciParseInput(ciSocket * sock) +{ + char *p, *q, *r; + char temp; + int i; + + p = sock->inputQueue.buffer; // start + + if(*p != '\0') + { + // eat all CRs & LFs + while(*p == 13 || *p == 10) + ++p; + + // end of string ? + if(*p != '\0') + { + r = q = p; + + // everything between last-nonspace char and CRLF should be discarded + while(*q != 10 && *q != 13 && *q != '\0') + { + if(*q != ' ') + r = q; + ++q; + } + + if(*q != '\0') + { + ++r; + temp = *r; + *r = '\0'; + + // gsifree the old message stuff. + ////////////////////////////// + RESET(sock->lastMessage.message); + RESET(sock->lastMessage.server); + RESET(sock->lastMessage.nick); + RESET(sock->lastMessage.user); + RESET(sock->lastMessage.host); + RESET(sock->lastMessage.command); + RESET(sock->lastMessage.middle); + RESET(sock->lastMessage.param); + for(i = 0 ; i < sock->lastMessage.numParams ; i++) + RESET(sock->lastMessage.params[i]); + RESET(sock->lastMessage.params); + sock->lastMessage.numParams = 0; + + // Parse the message. + ///////////////////// + memset(&sock->lastMessage,0,sizeof(sock->lastMessage)); + if(!ciParseMessage(sock, p)) + { + memset(&sock->lastMessage,0,sizeof(sock->lastMessage)); + return CHATFalse; //ERRCON + } + + // Restore the temp character. + ////////////////////////////// + *r = temp; + + // Take the message out of the buffer. + ////////////////////////////////////// + ciBufferClipFront(&sock->inputQueue, (q - sock->inputQueue.buffer)); + + return CHATTrue; + } + } + } + + return CHATFalse; +} + +ciServerMessage * ciSocketRecv(ciSocket * sock) +{ +#ifdef IRC_LOG + FILE * log; +#endif + ASSERT_SOCK(sock); + + // Bill: 08-10-04 + // There may be unprocessed messages in the queue + //if(sock->connectState == ciDisconnected) + // return NULL; + + // Check for an empty buffer. + ///////////////////////////// + if(sock->inputQueue.length == 0) + return NULL; + + // Check for a message. + /////////////////////// + if(!ciParseInput(sock)) + { + // No message. + ////////////// + return NULL; + } + +#ifdef IRC_LOG + // Write it to the log. + /////////////////////// + log = fopen(sock->filename, "at"); + if(log != NULL) + { + fprintf(log, "%s | IN | %s\n", ciGetTime(), sock->lastMessage.message); + fclose(log); + } +#endif + + // Got a message. + ///////////////// + return &sock->lastMessage; +} diff --git a/code/gamespy/Chat/chatSocket.h b/code/gamespy/Chat/chatSocket.h new file mode 100644 index 00000000..fa955753 --- /dev/null +++ b/code/gamespy/Chat/chatSocket.h @@ -0,0 +1,96 @@ +/* +GameSpy Chat SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _CHATSOCKET_H_ +#define _CHATSOCKET_H_ + +/************* +** INCLUDES ** +*************/ +#include "chat.h" +#include "chatCrypt.h" + +/********** +** ENUMS ** +**********/ +typedef enum ciConnectState +{ + ciNotConnected, + ciConnected, + ciDisconnected +} ciConnectState; + +/********** +** TYPES ** +**********/ +typedef struct ciBuffer +{ + char * buffer; + int length; + int size; +} ciBuffer; + +typedef struct ciServerMessage +{ + char * message; + char * server; + char * nick; + char * user; + char * host; + char * command; + char * middle; + char * param; + char ** params; + int numParams; +} ciServerMessage; + +typedef struct ciSocket +{ + SOCKET sock; + ciConnectState connectState; + char serverAddress[256]; + + ciBuffer inputQueue; + ciBuffer outputQueue; + + CHATBool secure; + gs_crypt_key inKey; + gs_crypt_key outKey; + + ciServerMessage lastMessage; + +#ifdef IRC_LOG + char filename[FILENAME_MAX]; +#endif +} ciSocket; + +/************** +** FUNCTIONS ** +**************/ +CHATBool ciSocketInit(ciSocket * sock, const char * nick); + +CHATBool ciSocketConnect(ciSocket * sock, + const char * serverAddress, + int port); + +void ciSocketDisconnect(ciSocket * sock); + +void ciSocketThink(ciSocket * sock); + +CHATBool ciSocketSend(ciSocket * sock, + const char * buffer); + +CHATBool ciSocketSendf(ciSocket * sock, + const char * format, + ...); + +ciServerMessage * ciSocketRecv(ciSocket * sock); + +#endif diff --git a/code/gamespy/Chat/chatc/chatc.c b/code/gamespy/Chat/chatc/chatc.c new file mode 100644 index 00000000..d95a6d60 --- /dev/null +++ b/code/gamespy/Chat/chatc/chatc.c @@ -0,0 +1,552 @@ +// GameSpy Chat SDK C Test App +// Dan "Mr. Pants" Schoenblum +// dan@gamespy.com + +/************* +** INCLUDES ** +*************/ +#include "../chat.h" +#include "../../common/gsStringUtil.h" + +#ifdef UNDER_CE + void RetailOutputA(CHAR *tszErr, ...); + #define printf RetailOutputA +#elif defined(_NITRO) + #include "../../common/nitro/screen.h" + #define printf Printf + #define vprintf VPrintf +#endif + +#define MAX_MESSAGE_SIZE 200 +#define CHAT_NICK_SIZE 128 + +/************ +** GLOBALS ** +************/ +// mj Nov 7th, zero out to known state globals. +int port = 0; +CHAT chat = {0}; +gsi_char serverAddress[128] = {0}; +gsi_char chatNick[128] = {0}; +gsi_char chatUser[128] = {0}; +gsi_char chatName[128] = {0}; +gsi_char chatChannel[128] = {0}; +gsi_char gamename[128] = {0}; +gsi_char secretKey[128] = {0}; +CHATBool quit = CHATFalse; + +#ifdef __MWERKS__ // CodeWarrior will warn if functions not prototyped +/*************** +** PROTOTYPES ** +***************/ +int test_main(int argc, char **argv); +#endif + +/************** +** FUNCTIONS ** +**************/ +#ifdef GSI_UNICODE + #define _tstrcasecmp WideCaseCompare + #define _tstrncasecmp WideCaseNCompare +#else + #define _tstrcasecmp strcasecmp + #define _tstrncasecmp strncasecmp +#endif + +// Simulate case insensitive compare functions +#if defined(GSI_UNICODE) +int WideCaseCompare(const unsigned short* s1, const unsigned short* s2); +int WideCaseNCompare(const unsigned short* s1, const unsigned short* s2, size_t count); + +int WideCaseCompare(const unsigned short* s1, const unsigned short* s2) +{ + char s1_A[512]; + char s2_A[512]; + UCS2ToAsciiString(s1, s1_A); + UCS2ToAsciiString(s2, s2_A); + return strcasecmp(s1_A, s2_A); +} + +int WideCaseNCompare(const unsigned short* s1, const unsigned short* s2, size_t count) +{ + char s1_A[512]; + char s2_A[512]; + unsigned short temp[512]; + + // null terminate + temp[count+1] = 0; + + // Copy to temp buffer, then convert to ascii + memcpy(temp, s1, count * sizeof(unsigned short)); + UCS2ToAsciiString(temp, s1_A); + + memcpy(temp, s2, count * sizeof(unsigned short)); + UCS2ToAsciiString(temp, s2_A); + + return strncasecmp(s1_A, s2_A, count); +} + +#endif + + +static void Raw(CHAT chat, const gsi_char * raw, void * param) +{ + _tprintf(_T("RAW: %s\n"), raw); + + GSI_UNUSED(chat); + GSI_UNUSED(param); +} + +static void Disconnected(CHAT chat, const gsi_char * reason, void * param) +{ + _tprintf(_T("Disconnected: %s\n"), reason); + + GSI_UNUSED(chat); + GSI_UNUSED(param); +} + +static void ChangedNickCallback(CHAT chat, CHATBool success, const gsi_char * oldNick, const gsi_char * newNick, void * param) +{ + if(success) + { + _tprintf(_T("Successfully changed")); + _tcscpy(chatNick, newNick); + } + else + _tprintf(_T("Failed to change")); + _tprintf(_T(" nick from %s to %s\n"), oldNick, newNick); + + GSI_UNUSED(chat); + GSI_UNUSED(param); +} + +static void PrivateMessage(CHAT chat, const gsi_char * user, const gsi_char * message, int type, void * param) +{ + _tprintf(_T("Private message from %s: %s\n"), user, message); + + // Nick change? + /////////////// + if(_tstrncasecmp(_T("nick"), message, 4) == 0) + { + chatChangeNick(chat, &message[5], ChangedNickCallback, NULL, CHATFalse); + } + + GSI_UNUSED(type); + GSI_UNUSED(param); +} + +static void Invited(CHAT chat, const gsi_char * channel, const gsi_char * user, void * param) +{ + _tprintf(_T("Invited by %s to %s\n"), user, channel); + + GSI_UNUSED(chat); + GSI_UNUSED(param); +} + +static void ChannelMessage(CHAT chat, const gsi_char * channel, const gsi_char * user, const gsi_char * message, int type, void * param) +{ + gsi_char buffer[MAX_MESSAGE_SIZE]; + + _tprintf(_T("%s, in %s, said \"%s\"\n"), user, channel, message); + + // Is this from us? + /////////////////// + if(_tstrcasecmp(user, chatNick) == 0) + return; + + // Is it a command? + /////////////////// + if(message[0] == '!') + { + message++; + if(!_tstrcasecmp(message, _T("quit")) || !_tstrcasecmp(message, _T("exit"))) + quit = CHATTrue; + return; + } + _tsnprintf(buffer, MAX_MESSAGE_SIZE, _T("%s: I agree"), user); + buffer[MAX_MESSAGE_SIZE - 1] = '\0'; + chatSendChannelMessage(chat, channel, buffer, CHAT_MESSAGE); + + GSI_UNUSED(type); + GSI_UNUSED(param); +} + +static void Kicked(CHAT chat, const gsi_char * channel, const gsi_char * user, const gsi_char * reason, void * param) +{ + _tprintf(_T("Kicked from %s by %s: %s\n"), channel, user, reason); + + GSI_UNUSED(chat); + GSI_UNUSED(param); +} + +static void UserJoined(CHAT chat, const gsi_char * channel, const gsi_char * user, int mode, void * param) +{ + _tprintf(_T("%s joined %s"), user, channel); + + GSI_UNUSED(chat); + GSI_UNUSED(param); + GSI_UNUSED(mode); +} + +static void UserParted(CHAT chat, const gsi_char * channel, const gsi_char * user, int why, const gsi_char * reason, const gsi_char * kicker, void * param) +{ + if(why == CHAT_LEFT) + _tprintf(_T("%s left %s\n"), user, channel); + else if(why == CHAT_QUIT) + _tprintf(_T("%s quit: %s\n"), user, reason); + else if(why == CHAT_KICKED) + _tprintf(_T("%s was kicked from %s by %s: %s"), user, channel, kicker, reason); + else if(why == CHAT_KILLED) + _tprintf(_T("%s was killed: %s\n"), user, reason); + else + _tprintf(_T("UserParted() called with unknown part-type\n")); + + GSI_UNUSED(chat); + GSI_UNUSED(param); +} + +static void UserChangedNick(CHAT chat, const gsi_char * channel, const gsi_char * oldNick, const gsi_char * newNick, void * param) +{ + _tprintf(_T("%s changed nicks to %s\n"), oldNick, newNick); + + GSI_UNUSED(chat); + GSI_UNUSED(channel); + GSI_UNUSED(param); +} + +static void UserModeChanged(CHAT chat, const gsi_char * channel, const gsi_char * user, int mode, void * param) +{ + _tprintf(_T("%s's new mode in %s is "), user, channel);; + if(mode == CHAT_VOICE) + _tprintf(_T("voice\n")); + else if(mode == CHAT_OP) + _tprintf(_T("ops\n")); + else if(mode == (CHAT_VOICE | CHAT_OP)) + _tprintf(_T("voice+ops\n")); + + GSI_UNUSED(chat); + GSI_UNUSED(param); +} + +static void TopicChanged(CHAT chat, const gsi_char * channel, const gsi_char * topic, void * param) +{ + _tprintf(_T("The topic in %s changed to %s\n"), channel, topic); + + GSI_UNUSED(chat); + GSI_UNUSED(param); +} + +static void ChannelModeChanged(CHAT chat, const gsi_char * channel, CHATChannelMode * mode, void * param) +{ + _tprintf(_T("The mode in %s has changed:\n"), channel); + _tprintf(_T(" InviteOnly: %d\n"), mode->InviteOnly); + _tprintf(_T(" Private: %d\n"), mode->Private); + _tprintf(_T(" Secret: %d\n"), mode->Secret); + _tprintf(_T(" Moderated: %d\n"), mode->Moderated); + _tprintf(_T(" NoExternalMessages: %d\n"), mode->NoExternalMessages); + _tprintf(_T(" OnlyOpsChangeTopic: %d\n"), mode->OnlyOpsChangeTopic); + _tprintf(_T(" Limit: ")); + if(mode->Limit == 0) + _tprintf(_T("N/A\n")); + else + _tprintf(_T("%d\n"), mode->Limit); + + GSI_UNUSED(chat); + GSI_UNUSED(param); +} + +static void UserListUpdated(CHAT chat, const gsi_char * channel, void * param) +{ + _tprintf(_T("User list updated\n")); + + GSI_UNUSED(chat); + GSI_UNUSED(channel); + GSI_UNUSED(param); +} + +static void ConnectCallback(CHAT chat, CHATBool success, int failureReason, void * param) +{ + if (success == CHATFalse) + _tprintf(_T("Failed to connect (%d)\n"), failureReason); + else + _tprintf(_T("Connected\n")); + GSI_UNUSED(chat); + GSI_UNUSED(success); + GSI_UNUSED(param); +} + + +static void FillInUserCallback(CHAT chat, unsigned int IP, gsi_char user[128], void * param) +{ + _tcscpy(user, chatUser); + + GSI_UNUSED(chat); + GSI_UNUSED(IP); + GSI_UNUSED(param); +} + +static void NickErrorCallback(CHAT chat, int type, const gsi_char * nick, int numSuggestedNicks, const gsi_char ** suggestedNicks, void * param) +{ + if(type == CHAT_IN_USE) + { + _tprintf(_T("The nick %s is already being used.\n"), nick); + _tsnprintf(chatNick,CHAT_NICK_SIZE,_T("ChatC%lu"),(unsigned long)current_time()); + chatNick[CHAT_NICK_SIZE - 1] = '\0'; + chatRetryWithNick(chat, chatNick); + } + else if(type == CHAT_INVALID) + { + _tprintf(_T("The nick %s is invalid!\n"), nick); + // chatDisconnect(chat); THIS CRASHES + + // 10-14-2004: Added By Saad Nader + // this is necessary as the function will fail if a new nick is not retries. + //////////////////////////////////////////////////////////////////////////// + _tsnprintf(chatNick,CHAT_NICK_SIZE,_T("ChatC%lu"),(unsigned long)current_time()); + chatNick[CHAT_NICK_SIZE - 1] = '\0'; + chatRetryWithNick(chat, chatNick); + } + else if((type == CHAT_UNIQUENICK_EXPIRED) || (type == CHAT_NO_UNIQUENICK)) + { + _tprintf(_T("This account has no uniquenick or an expired uniquenick!\n")); + + chatRegisterUniqueNick(chat, 2, _T("MrPants"), _T("")); + } + else if(type == CHAT_INVALID_UNIQUENICK) + { + int i; + + _tprintf(_T("The uniquenick %s is invalid or in use\n"), nick); + _tprintf(_T("There are %d suggested nicks:\n"), numSuggestedNicks); + + for(i = 0 ; i < numSuggestedNicks ; i++) + _tprintf(_T(" %s\n"), suggestedNicks[i]); + } + + // 10-14-2004: Added By Saad Nader + // added for the addition of a new error code. + //////////////////////////////////////////////////////////////////////////// + else if(type == CHAT_NICK_TOO_LONG) + { + _tprintf(_T("The nick %s is too long.\n"), nick); + _tsnprintf(chatNick,CHAT_NICK_SIZE,_T("ChatC%lu"),(unsigned long)current_time()); + chatNick[CHAT_NICK_SIZE - 1] = '\0'; + chatRetryWithNick(chat, chatNick); + } + GSI_UNUSED(param); +} + +CHATBool enterChannelSuccess; +static void EnterChannelCallback(CHAT chat, CHATBool success, CHATEnterResult result, const gsi_char * channel, void * param) +{ + enterChannelSuccess = success; + + GSI_UNUSED(chat); + GSI_UNUSED(result); + GSI_UNUSED(channel); + GSI_UNUSED(param); +} + +static void GetUserInfoCallback(CHAT chat, CHATBool success, const gsi_char * nick, const gsi_char * user, const gsi_char * name, const gsi_char * address, int numChannels, const gsi_char ** channels, void * param) +{ + int i; + + if(!success) + { + _tprintf(_T("GetUserInfo failed\n")); + return; + } + + _tprintf(_T("%s's Info:\n"), nick); + _tprintf(_T(" User: %s\n"), user); + _tprintf(_T(" Name: %s\n"), name); + _tprintf(_T(" Address: %s\n"), address); + _tprintf(_T(" Channels (%d):\n"), numChannels); + for(i = 0 ; i < numChannels ; i++) + _tprintf(_T(" %s\n"), channels[i]); + + GSI_UNUSED(chat); + GSI_UNUSED(param); +} +/* +static void EnumChannelsAllCallback(CHAT chat, CHATBool success, int numChannels, const gsi_char ** channel, const gsi_char ** topic, int* numUsers, void * param) +{ + GSI_UNUSED(chat); + GSI_UNUSED(success); + GSI_UNUSED(numChannels); + GSI_UNUSED(channel); + GSI_UNUSED(topic); + GSI_UNUSED(numUsers); + GSI_UNUSED(param); +} +*/ +static void EnumUsersCallback(CHAT chat, CHATBool success, const gsi_char * channel, int numUsers, const gsi_char ** users, int * modes, void * param) +{ + int i; + + if(!success) + { + _tprintf(_T("EnumUsers failed\n")); + return; + } + + for(i = 0 ; i < numUsers ; i++) + chatGetUserInfo(chat, users[i], GetUserInfoCallback, NULL, CHATFalse); + + GSI_UNUSED(channel); + GSI_UNUSED(modes); + GSI_UNUSED(param); +} + +int test_main(int argc, char **argv) +{ + int i; + chatGlobalCallbacks globalCallbacks; + chatChannelCallbacks channelCallbacks; + unsigned long stopTime; + + + // Set default options. + /////////////////////// + // SDK takes care of default server address and port now + //_tcscpy(serverAddress, _T("peerchat." GSI_DOMAIN_NAME)); + //port = 6667; + _tsnprintf(chatNick,CHAT_NICK_SIZE,_T("ChatC%lu"),(unsigned long)current_time() % 1000); + chatNick[CHAT_NICK_SIZE - 1] = '\0'; + _tcscpy(chatUser, _T("ChatCUser")); + _tcscpy(chatName, _T("ChatCName")); + _tcscpy(chatChannel, _T("#GSP!gmtest")); + _tcscpy(gamename, _T("gmtest")); + secretKey[0] = 'H'; + secretKey[1] = 'A'; + secretKey[2] = '6'; + secretKey[3] = 'z'; + secretKey[4] = 'k'; + secretKey[5] = 'S'; + secretKey[6] = '\0'; + + // Go through command-line options. + /////////////////////////////////// + for(i = 1 ; i < argc ; i++) + { + if((argv[i][0] == '-') && ((i + 1) < argc)) + { + switch(argv[i][1]) + { + case 's': +#ifndef GSI_UNICODE + strcpy(serverAddress, argv[++i]); +#else + AsciiToUCS2String(argv[++i], serverAddress); +#endif + break; + case 'p': + port = atoi(argv[++i]); + break; + case 'n': +#ifndef GSI_UNICODE + strcpy(chatNick, argv[++i]); +#else + AsciiToUCS2String(argv[++i], chatNick); +#endif + break; + case 'u': +#ifndef GSI_UNICODE + strcpy(chatUser, argv[++i]); +#else + AsciiToUCS2String(argv[++i], chatUser); +#endif + break; + case 'c': +#ifndef GSI_UNICODE + strcpy(chatChannel, argv[++i]); +#else + AsciiToUCS2String(argv[++i], chatChannel); +#endif + break; + default: + _tprintf(_T("Error parsing command-line: %s\n"), argv[i]); + return 1; + } + } + else + { + _tprintf(_T("Error parsing command-line: %s\n"), argv[i]); + return 1; + } + } + + // Set global callbacks. + //////////////////////// + memset(&globalCallbacks, 0, sizeof(chatGlobalCallbacks)); + globalCallbacks.raw = Raw; + globalCallbacks.disconnected = Disconnected; + globalCallbacks.privateMessage = PrivateMessage; + globalCallbacks.invited = Invited; + globalCallbacks.param = NULL; + + // Connect. + /////////// + chat = chatConnectSecure(serverAddress[0]?serverAddress:NULL, port, chatNick, chatName, gamename, secretKey, &globalCallbacks, NickErrorCallback, FillInUserCallback, ConnectCallback, NULL, CHATTrue); + if(!chat) + { + _tprintf(_T("Connect failed\n")); + return 1; + } + + // Set channel callbacks. + ///////////////////////// + memset(&channelCallbacks, 0, sizeof(chatChannelCallbacks)); + channelCallbacks.channelMessage = ChannelMessage; + channelCallbacks.channelModeChanged = ChannelModeChanged; + channelCallbacks.kicked = Kicked; + channelCallbacks.topicChanged = TopicChanged; + channelCallbacks.userParted = UserParted; + channelCallbacks.userJoined = UserJoined; + channelCallbacks.userListUpdated = UserListUpdated; + channelCallbacks.userModeChanged = UserModeChanged; + channelCallbacks.userChangedNick = UserChangedNick; + channelCallbacks.param = NULL; + + // Join. + //////// + chatEnterChannel(chat, chatChannel, NULL, &channelCallbacks, EnterChannelCallback, NULL, CHATTrue); + if(!enterChannelSuccess) + { + _tprintf(_T("Enter Channel failed\n")); + return 1; + } + + // Say hi. + ////////// + chatSendChannelMessage(chat, chatChannel, _T("Hi"), CHAT_MESSAGE); + + + // Enum through the players. + //////////////////////////// + chatEnumUsers(chat, chatChannel, EnumUsersCallback, NULL, CHATFalse); + + // Stay for a while. + //////////////////// + stopTime = (current_time() + 60000); + do + { + chatThink(chat); + msleep(50); + } + while(!quit && (current_time() < stopTime)); + + // Say bye. + /////////// + chatSendChannelMessage(chat, chatChannel, _T("Bye"), CHAT_MESSAGE); + + // Leave. + ///////// + chatLeaveChannel(chat, chatChannel, NULL); + + // Disconnect. + ////////////// + chatDisconnect(chat); + _tprintf(_T("All Done!\n")); + return 0; +} diff --git a/code/gamespy/Chat/chatc/chatnitrocw/Nitro.lcf b/code/gamespy/Chat/chatc/chatnitrocw/Nitro.lcf new file mode 100644 index 00000000..998f6b00 --- /dev/null +++ b/code/gamespy/Chat/chatc/chatnitrocw/Nitro.lcf @@ -0,0 +1,493 @@ +#--------------------------------------------------------------------------- +# Project: NitroSDK - tools - makelcf +# File: ARM9-TS.lcf.template +# +# Copyright 2003-2006 Nintendo. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# proprietary information of Nintendo of America Inc. and/or Nintendo +# Company Ltd., and are protected by Federal copyright law. They may +# not be disclosed to third parties or copied or duplicated in any form, +# in whole or in part, without the prior written consent of Nintendo. +# +# $Log: ARM9-TS.lcf.template,v $ +# Revision 1.34 04/06/2006 09:02:36 kitase_hirotake +# support for .itcm.bss and .dtcm.bss +# +# Revision 1.33 03/30/2006 23:59:22 AM yasu +# changed creation year +# +# Revision 1.32 03/29/2006 13:14:22 AM yasu +# support for overlays in CWVER 2.x +# +# Revision 1.31 11/24/2005 01:16:47 yada +# change start address of mainEX arena from 0x2400000 to 0x23e0000 +# +# Revision 1.30 09/02/2005 04:14:22 AM yasu +# Old symbols were redefined so they can be used even under SDK2.2 +# +# Revision 1.29 08/31/2005 09:34:57 AM yasu +# Corrected a problem where code would not function normally when using section names such as section_BSS +# +# Revision 1.28 08/26/2005 11:22:16 AM yasu +# overlay support for ITCM/DTCM +# +# Revision 1.27 06/20/2005 12:29:20 AM yasu +# Changed Surffix to Suffix +# +# Revision 1.26 06/14/2005 09:03:42 yada +# fix around minus value of SDK_STACKSIZE +# +# Revision 1.25 04/13/2005 12:51:00 terui +# Change SDK_AUTOLOAD.DTCM.START 0x027c0000 -> 0x027e0000 +# +# Revision 1.24 03/30/2005 00:02:14 yosizaki +# fix copyright header. +# +# Revision 1.23 03/25/2005 12:54:59 AM yasu +# Include .version section +# +# Revision 1.22 10/03/2004 02:00:56 AM yasu +# Output component file list for compstatic tool +# +# Revision 1.21 09/27/2004 05:28:21 AM yasu +# Support .sinit +# +# Revision 1.20 09/09/2004 11:49:20 AM yasu +# Support compstatic in default +# +# Revision 1.19 09/06/2004 06:40:00 AM yasu +# Add labels for digest +# +# Revision 1.18 08/20/2004 06:19:59 AM yasu +# DTCM moves to 0x027c0000 at default +# +# Revision 1.17 08/02/2004 10:38:53 AM yasu +# Add autoload-done callback address in overlaydefs +# +# Revision 1.16 07/26/2004 02:22:32 AM yasu +# Change DTCM address to 0x023c0000 +# +# Revision 1.15 07/26/2004 00:08:27 AM yasu +# Fix label of exception table +# +# Revision 1.14 07/24/2004 05:42:25 AM yasu +# Set default values for SDK_AUTOGEN_xTCM_START +# +# Revision 1.13 07/23/2004 11:32:14 AM yasu +# Define labels for __exception_table_start__ and _end__ +# +# Revision 1.12 07/12/2004 12:21:08 AM yasu +# Check size of ITCM/DTCM +# +# Revision 1.11 07/10/2004 04:10:26 AM yasu +# Support command 'Library' +# +# Revision 1.10 07/02/2004 08:13:02 AM yasu +# Support OBJECT( ) +# +# Revision 1.9 07/01/2004 12:54:38 yasu +# support ITCM/DTCM/WRAM autoload +# +# Revision 1.8 07/01/2004 10:41:46 yasu +# support autoload +# +# Revision 1.7 06/02/2004 07:35:37 yasu +# Set libsyscall.a in FORCE_ACTIVE +# Put NitroMain at the top of ROM image +# +# Revision 1.6 06/02/2004 04:56:28 yasu +# Change to fit to new ROM map of TS +# +# Revision 1.5 2004/06/01 06:12:00 miya +# add padding at top of ROM image. +# +# Revision 1.4 04/26/2004 12:16:48 yasu +# add KEEP_SECTIONS +# +# Revision 1.3 04/20/2004 07:41:32 yasu +# Set STATICINIT instead of .ctor temporarily +# +# Revision 1.2 04/14/2004 07:16:42 yasu +# add ALIGN(32) for convenience to handle cache line +# +# Revision 1.1 04/06/2004 01:59:54 yasu +# newly added +# +# $NoKeywords: $ +#--------------------------------------------------------------------------- +MEMORY +{ + main (RWX) : ORIGIN = 0x02000000, LENGTH = 0x0 > main.sbin + ITCM (RWX) : ORIGIN = 0x01ff8000, LENGTH = 0x0 >> main.sbin + DTCM (RWX) : ORIGIN = 0x027e0000, LENGTH = 0x0 >> main.sbin + binary.AUTOLOAD_INFO (RWX) : ORIGIN = 0, LENGTH = 0x0 >> main.sbin + binary.STATIC_FOOTER (RWX) : ORIGIN = 0, LENGTH = 0x0 >> main.sbin + + main_defs (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 > main_defs.sbin + main_table (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 > main_table.sbin + dummy.MAIN_EX (RW) : ORIGIN = 0x023e0000, LENGTH = 0x0 + arena.MAIN (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 + arena.MAIN_EX (RW) : ORIGIN = AFTER(dummy.MAIN_EX), LENGTH = 0x0 + arena.ITCM (RW) : ORIGIN = AFTER(ITCM), LENGTH = 0x0 + arena.DTCM (RW) : ORIGIN = AFTER(DTCM), LENGTH = 0x0 + binary.MODULE_FILES (RW) : ORIGIN = 0x0, LENGTH = 0x0 > component.files + check.ITCM (RWX) : ORIGIN = 0x0, LENGTH = 0x08000 > itcm.check + check.DTCM (RW) : ORIGIN = 0x0, LENGTH = 0x04000 > dtcm.check +} + +FORCE_ACTIVE +{ + SVC_SoftReset +} + +KEEP_SECTION +{ + .sinit +} + +SECTIONS +{ + ############################ STATIC ################################# + .main: + { + ALIGNALL(4); . = ALIGN(32); # Fit to cache line + + # + # TEXT BLOCK: READ ONLY + # + SDK_STATIC_START =.; + SDK_STATIC_TEXT_START =.; + #:::::::::: text/rodata + libsyscall.a (.text) + crt0.o (.text) + crt0.o (.rodata) + * (.version) + OBJECT(NitroMain,*) + GROUP(ROOT) (.text) + . = ALIGN(4); + * (.exception) + . = ALIGN(4); + SDK_STATIC_ETABLE_START =.; + EXCEPTION + SDK_STATIC_ETABLE_END =.; + . = ALIGN(4); + GROUP(ROOT) (.init) + . = ALIGN(4); + GROUP(ROOT) (.rodata) + . = ALIGN(4); + + SDK_STATIC_SINIT_START =.; + #:::::::::: ctor + GROUP(ROOT) (.ctor) + GROUP(ROOT) (.sinit) + WRITEW 0; + #:::::::::: ctor + SDK_STATIC_SINIT_END =.; + + #:::::::::: text/rodata + . = ALIGN(32); + SDK_STATIC_TEXT_END =.; + + # + # DATA BLOCK: READ WRITE + # + SDK_STATIC_DATA_START =.; + #:::::::::: data + GROUP(ROOT) (.sdata) + . = ALIGN(4); + GROUP(ROOT) (.data) + . = ALIGN(4); + SDK_OVERLAY_DIGEST =.; + # NO DIGEST + SDK_OVERLAY_DIGEST_END =.; + #:::::::::: data + . = ALIGN(32); + SDK_STATIC_DATA_END =.; + SDK_STATIC_END =.; + + SDK_STATIC_TEXT_SIZE = SDK_STATIC_TEXT_END - SDK_STATIC_TEXT_START; + SDK_STATIC_DATA_SIZE = SDK_STATIC_DATA_END - SDK_STATIC_DATA_START; + SDK_STATIC_SIZE = SDK_STATIC_END - SDK_STATIC_START; + __sinit__ = SDK_STATIC_SINIT_START; # for static initializer + __exception_table_start__ = SDK_STATIC_ETABLE_START; # for exception table + __exception_table_end__ = SDK_STATIC_ETABLE_END; # for exception table + } > main + + .main.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_STATIC_BSS_START =.; + #:::::::::: bss + GROUP(ROOT) (.sbss) + . = ALIGN(4); + GROUP(ROOT) (.bss) + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_STATIC_BSS_END = .; + SDK_STATIC_BSS_SIZE = SDK_STATIC_BSS_END - SDK_STATIC_BSS_START; + + } >> main + + + ############################ AUTOLOADS ############################## + SDK_AUTOLOAD.ITCM.START = 0x01ff8000; + SDK_AUTOLOAD.ITCM.END = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD.ITCM.BSS_END = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD.ITCM.SIZE = 0; + SDK_AUTOLOAD.ITCM.BSS_SIZE = 0; + SDK_AUTOLOAD.DTCM.START = 0x027e0000; + SDK_AUTOLOAD.DTCM.END = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD.DTCM.BSS_END = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD.DTCM.SIZE = 0; + SDK_AUTOLOAD.DTCM.BSS_SIZE = 0; + SDK_AUTOLOAD_START = SDK_STATIC_END; + SDK_AUTOLOAD_SIZE = 0; + SDK_AUTOLOAD_NUMBER = 2; + + .ITCM: + { + ALIGNALL(4); . = ALIGN(32); + + # + # TEXT BLOCK: READ ONLY + # + SDK_AUTOLOAD_ITCM_ID =0; + SDK_AUTOLOAD.ITCM.ID =0; + SDK_AUTOLOAD.ITCM.START =.; + SDK_AUTOLOAD.ITCM.TEXT_START =.; + #:::::::::: text/rodata + . = ALIGN(4); + * (.itcm) + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: text/rodata + SDK_AUTOLOAD.ITCM.TEXT_END =.; + + # + # DATA BLOCK: READ WRITE BLOCK + # + SDK_AUTOLOAD.ITCM.DATA_START =.; + #:::::::::: data + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: data + . = ALIGN(32); + SDK_AUTOLOAD.ITCM.DATA_END =.; + SDK_AUTOLOAD.ITCM.END =.; + + SDK_AUTOLOAD.ITCM.TEXT_SIZE = SDK_AUTOLOAD.ITCM.TEXT_END - SDK_AUTOLOAD.ITCM.TEXT_START; + SDK_AUTOLOAD.ITCM.DATA_SIZE = SDK_AUTOLOAD.ITCM.DATA_END - SDK_AUTOLOAD.ITCM.DATA_START; + SDK_AUTOLOAD.ITCM.SIZE = SDK_AUTOLOAD.ITCM.END - SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SDK_AUTOLOAD.ITCM.SIZE; + + } > ITCM + + .ITCM.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_AUTOLOAD.ITCM.BSS_START = .; + #:::::::::: bss + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + * (.itcm.bss) + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_AUTOLOAD.ITCM.BSS_END = .; + + SDK_AUTOLOAD.ITCM.BSS_SIZE = SDK_AUTOLOAD.ITCM.BSS_END - SDK_AUTOLOAD.ITCM.BSS_START; + + } >> ITCM + + .DTCM: + { + ALIGNALL(4); . = ALIGN(32); + + # + # TEXT BLOCK: READ ONLY + # + SDK_AUTOLOAD_DTCM_ID =1; + SDK_AUTOLOAD.DTCM.ID =1; + SDK_AUTOLOAD.DTCM.START =.; + SDK_AUTOLOAD.DTCM.TEXT_START =.; + #:::::::::: text/rodata + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: text/rodata + SDK_AUTOLOAD.DTCM.TEXT_END =.; + + # + # DATA BLOCK: READ WRITE BLOCK + # + SDK_AUTOLOAD.DTCM.DATA_START =.; + #:::::::::: data + . = ALIGN(4); + . = ALIGN(4); + * (.dtcm) + . = ALIGN(4); + #:::::::::: data + . = ALIGN(32); + SDK_AUTOLOAD.DTCM.DATA_END =.; + SDK_AUTOLOAD.DTCM.END =.; + + SDK_AUTOLOAD.DTCM.TEXT_SIZE = SDK_AUTOLOAD.DTCM.TEXT_END - SDK_AUTOLOAD.DTCM.TEXT_START; + SDK_AUTOLOAD.DTCM.DATA_SIZE = SDK_AUTOLOAD.DTCM.DATA_END - SDK_AUTOLOAD.DTCM.DATA_START; + SDK_AUTOLOAD.DTCM.SIZE = SDK_AUTOLOAD.DTCM.END - SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SDK_AUTOLOAD.DTCM.SIZE; + + } > DTCM + + .DTCM.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_AUTOLOAD.DTCM.BSS_START = .; + #:::::::::: bss + . = ALIGN(4); + . = ALIGN(4); + * (.dtcm.bss) + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_AUTOLOAD.DTCM.BSS_END = .; + + SDK_AUTOLOAD.DTCM.BSS_SIZE = SDK_AUTOLOAD.DTCM.BSS_END - SDK_AUTOLOAD.DTCM.BSS_START; + + } >> DTCM + + + SDK_AUTOLOAD_ITCM_START = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD_ITCM_END = SDK_AUTOLOAD.ITCM.END; + SDK_AUTOLOAD_ITCM_BSS_END = SDK_AUTOLOAD.ITCM.BSS_END; + SDK_AUTOLOAD_ITCM_SIZE = SDK_AUTOLOAD.ITCM.SIZE; + SDK_AUTOLOAD_ITCM_BSS_SIZE = SDK_AUTOLOAD.ITCM.BSS_SIZE; + SDK_AUTOLOAD_DTCM_START = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD_DTCM_END = SDK_AUTOLOAD.DTCM.END; + SDK_AUTOLOAD_DTCM_BSS_END = SDK_AUTOLOAD.DTCM.BSS_END; + SDK_AUTOLOAD_DTCM_SIZE = SDK_AUTOLOAD.DTCM.SIZE; + SDK_AUTOLOAD_DTCM_BSS_SIZE = SDK_AUTOLOAD.DTCM.BSS_SIZE; + + ############################ AUTOLOAD_INFO ########################## + .binary.AUTOLOAD_INFO: + { + WRITEW ADDR(.ITCM); + WRITEW SDK_AUTOLOAD.ITCM.SIZE; + WRITEW SDK_AUTOLOAD.ITCM.BSS_SIZE; + WRITEW ADDR(.DTCM); + WRITEW SDK_AUTOLOAD.DTCM.SIZE; + WRITEW SDK_AUTOLOAD.DTCM.BSS_SIZE; + } > binary.AUTOLOAD_INFO + + SDK_AUTOLOAD_LIST = SDK_AUTOLOAD_START + SDK_AUTOLOAD_SIZE; + SDK_AUTOLOAD_LIST_END = SDK_AUTOLOAD_START + SDK_AUTOLOAD_SIZE + SIZEOF(.binary.AUTOLOAD_INFO); + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SIZEOF(.binary.AUTOLOAD_INFO); + + ############################ STATIC_FOOTER ########################## + .binary.STATIC_FOOTER: + { + WRITEW 0xdec00621; # LE(0x2106C0DE) = NITRO CODE + WRITEW _start_ModuleParams - ADDR(.main); + WRITEW 0; # NO DIGEST + } > binary.STATIC_FOOTER + + ############################ OVERLAYS ############################### + SDK_OVERLAY_NUMBER = 0; + + + ############################ MAIN EX ################################## + # MAIN EX Area + .dummy.MAIN_EX: + { + . = ALIGN(32); + } > dummy.MAIN_EX + + ############################ ARENA ################################## + .arena.MAIN: + { + . = ALIGN(32); + SDK_SECTION_ARENA_START =.; + } > arena.MAIN + + .arena.MAIN_EX: + { + . = ALIGN(32); + SDK_SECTION_ARENA_EX_START =.; + } > arena.MAIN_EX + + .arena.ITCM: + { + . = ALIGN(32); + SDK_SECTION_ARENA_ITCM_START =.; + } > arena.ITCM + + .arena.DTCM: + { + . = ALIGN(32); + SDK_SECTION_ARENA_DTCM_START =.; + } > arena.DTCM + + ############################ OVERLAYDEFS ############################ + .main_defs: + { + ### main module information + WRITEW ADDR(.main); # load address + WRITEW _start; # entry address + WRITEW SDK_STATIC_SIZE + SDK_AUTOLOAD_SIZE; # size of module + WRITEW _start_AutoloadDoneCallback; # callback autoload done + + ### overlay filename + + } > main_defs + + + ############################ OVERLAYTABLE ########################### + .main_table: + { + + } > main_table + + + ############################ OTHERS ################################# + SDK_MAIN_ARENA_LO = SDK_SECTION_ARENA_START; + SDK_IRQ_STACKSIZE = 4096; # allocated in DTCM + SDK_SYS_STACKSIZE = 0; # when 0 means all remains of DTCM + + # Module filelist + .binary.MODULE_FILES: + { + WRITES ("main.sbin"); + WRITES ("main_defs.sbin"); + WRITES ("main_table.sbin"); + } > binary.MODULE_FILES + + # ITCM/DTCM size checker => check AUTOLOAD_ITCM/DTCM + .check.ITCM: + { + . = . + SDK_AUTOLOAD_ITCM_SIZE + SDK_AUTOLOAD_ITCM_BSS_SIZE; + } > check.ITCM + + SDK_SYS_STACKSIZE_SIGN = (SDK_SYS_STACKSIZE < 0x80000000) * 2 - 1; + .check.DTCM: + { + . = . + SDK_AUTOLOAD_DTCM_SIZE + SDK_AUTOLOAD_DTCM_BSS_SIZE; + . = . + SDK_IRQ_STACKSIZE + SDK_SYS_STACKSIZE * SDK_SYS_STACKSIZE_SIGN; + } > check.DTCM + +} diff --git a/code/gamespy/Chat/chatc/chatnitrocw/ROM-TS.rsf b/code/gamespy/Chat/chatc/chatnitrocw/ROM-TS.rsf new file mode 100644 index 00000000..cec9e1db --- /dev/null +++ b/code/gamespy/Chat/chatc/chatnitrocw/ROM-TS.rsf @@ -0,0 +1,116 @@ +#---------------------------------------------------------------------------- +# Project: NitroSDK - include +# File: ROM-TS.lsf +# +# Copyright 2003-2005 Nintendo. All rights reserved. +# +# These coded insructions, statements, and computer programs contain +# proprietary information of Nintendo of America Inc. and/or Nintendo +# Company Ltd., and are protected by Federal copyright law. They may +# not be disclosed to third parties or copied or duplicated in any form, +# in whole or in part, without the prior written consent of Nintendo. +# +# $Log: ROM-TS.rsf,v $ +# Revision 1.6 2005/04/05 23:52:58 yosizaki +# fix copyright date. +# +# Revision 1.5 2005/04/05 12:16:10 yosizaki +# support RomSpeedType parameter. +# +# Revision 1.4 2004/09/21 02:18:49 yasu +# Add default banner +# +# Revision 1.3 2004/09/09 11:39:09 yasu +# Unified ROM-TS and ROM-TS-C, also ROM-TEG and ROM-TEG-C +# +# Revision 1.2 2004/05/26 12:03:38 yasu +# add :r option to get basename for supporting IDE with makerom +# +# Revision 1.1 2004/04/06 01:59:59 yasu +# newly added +# +# $NoKeywords: $ +#---------------------------------------------------------------------------- +# +# Nitro ROM SPEC FILE +# + +Arm9 +{ + Static "$(MAKEROM_ARM9:r).sbin$(COMPSUFFIX9)" + OverlayDefs "$(MAKEROM_ARM9:r)_defs.sbin$(COMPSUFFIX9)" + OverlayTable "$(MAKEROM_ARM9:r)_table.sbin$(COMPSUFFIX9)" + Elf "$(MAKEROM_ARM9:r).nef" +} + +Arm7 +{ + Static "$(MAKEROM_ARM7:r).sbin$(COMPSUFFIX7)" + OverlayDefs "$(MAKEROM_ARM7:r)_defs.sbin$(COMPSUFFIX7)" + OverlayTable "$(MAKEROM_ARM7:r)_table.sbin$(COMPSUFFIX7)" + Elf "$(MAKEROM_ARM7:r).nef" +} + +Property +{ + ### + ### Settings for FinalROM + ### + #### BEGIN + # + # TITLE NAME: Your product name within 12bytes + # + #TitleName "YourAppName" + + # + # MAKER CODE: Your company ID# in 2 ascii words + # issued by NINTENDO + # + #MakerCode "00" + + # + # REMASTER VERSION: Mastering version + # + #RomVersion 0 + + # + # ROM SPEED TYPE: [MROM/1TROM/UNDEFINED] + # + RomSpeedType $(MAKEROM_ROMSPEED) + + # + # ROM SIZE: in bit [64M/128M/256M/512M/1G/2G] + # + #RomSize 128M + #RomSize 256M + + # + # ROM PADDING: TRUE if finalrom + # + #RomFootPadding TRUE + + # + # ROM HEADER TEMPLATE: Provided to every product by NINTENDO + # + #RomHeaderTemplate ./etc/rom_header.template.sbin + + # + # BANNER FILE: generated from Banner Spec File + # + #BannerFile ./etc/myGameBanner.bnr + BannerFile $(NITROSDK_ROOT)/include/nitro/specfiles/default.bnr + + ### + ### + ### + #### END +} + +RomSpec +{ + Offset 0x00000000 + Segment ALL + HostRoot $(MAKEROM_ROMROOT) + Root / + File $(MAKEROM_ROMFILES) +} diff --git a/code/gamespy/Chat/chatc/chatps2prodg/chatps2prodg.dsp b/code/gamespy/Chat/chatc/chatps2prodg/chatps2prodg.dsp new file mode 100644 index 00000000..45caf5bf --- /dev/null +++ b/code/gamespy/Chat/chatc/chatps2prodg/chatps2prodg.dsp @@ -0,0 +1,384 @@ +# Microsoft Developer Studio Project File - Name="chatps2prodg" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=chatps2prodg - Win32 PS2 EE Release Insock +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "chatps2prodg.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "chatps2prodg.mak" CFG="chatps2prodg - Win32 PS2 EE Release Insock" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "chatps2prodg - Win32 PS2 EE Debug EENet" (based on "Win32 (x86) Console Application") +!MESSAGE "chatps2prodg - Win32 PS2 EE Debug SNSystems" (based on "Win32 (x86) Console Application") +!MESSAGE "chatps2prodg - Win32 PS2 EE Debug Insock" (based on "Win32 (x86) Console Application") +!MESSAGE "chatps2prodg - Win32 PS2 EE Release EENet" (based on "Win32 (x86) Console Application") +!MESSAGE "chatps2prodg - Win32 PS2 EE Release SNSystems" (based on "Win32 (x86) Console Application") +!MESSAGE "chatps2prodg - Win32 PS2 EE Release Insock" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/Gamespy/GOA/Chat/chatc/chatps2prodg", DSEDAAAA" +# PROP Scc_LocalPath "." +CPP=snCl.exe +RSC=rc.exe + +!IF "$(CFG)" == "chatps2prodg - Win32 PS2 EE Debug EENet" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "PS2 EE Debug EENet" +# PROP BASE Intermediate_Dir "PS2 EE Debug EENet" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_EENet" +# PROP Intermediate_Dir "Debug_EENet" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Debug/" /FD /debug /c +# ADD CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /I "C:\usr\local\sce\ee\include\libeenet" /D "SN_TARGET_PS2" /D "_DEBUG" /D "EENET" /FD /debug /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"PS2_EE_Debug\chatps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 ent_smap.a ent_eth.a ent_ppp.a eenetctl.a libeenet.a libcdvd.a libscf.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"Debug_EENet\chatps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "chatps2prodg - Win32 PS2 EE Debug SNSystems" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "PS2 EE Debug SNSystems" +# PROP BASE Intermediate_Dir "PS2 EE Debug SNSystems" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_SNSystems" +# PROP Intermediate_Dir "Debug_SNSystems" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Debug/" /FD /debug /c +# ADD CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /D "_DEBUG" /D "SN_SYSTEMS" /FD /debug /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"PS2_EE_Debug\chatps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 sneetcp.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"Debug_SNSystems\chatps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "chatps2prodg - Win32 PS2 EE Debug Insock" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "chatps2prodg___Win32_PS2_EE_Debug_Insock" +# PROP BASE Intermediate_Dir "chatps2prodg___Win32_PS2_EE_Debug_Insock" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_Insock" +# PROP Intermediate_Dir "Debug_Insock" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /I "C:\usr\local\sce\ee\include\libeenet" /D "SN_TARGET_PS2" /D "_DEBUG" /D "EENET" /FD /debug /c +# ADD CPP /nologo /W3 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /D "_DEBUG" /D "INSOCK" /FD /debug /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 ent_smap.a ent_eth.a ent_ppp.a eenetctl.a libeenet.a libcdvd.a libscf.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"Debug_EENet\chatps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 libinsck.a libnet.a libmrpc.a libkernl.a libcdvd.a libnetif.a netcnfif.a /nologo /pdb:none /debug /machine:IX86 /out:"Debug_Insock\chatps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "chatps2prodg - Win32 PS2 EE Release EENet" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "PS2 EE Release EENet" +# PROP BASE Intermediate_Dir "PS2 EE Release EENet" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Release_EENet" +# PROP Intermediate_Dir "Release_EENet" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /O2 /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Release/" /FD /c +# ADD CPP /nologo /W4 /WX /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /I "C:\usr\local\sce\ee\include\libeenet" /D "SN_TARGET_PS2" /D "EENET" /FD /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"PS2_EE_Release\chatps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 ent_smap.a ent_eth.a ent_ppp.a eenetctl.a libeenet.a libcdvd.a libscf.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"Release_EENet\chatps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "chatps2prodg - Win32 PS2 EE Release SNSystems" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "PS2 EE Release SNSystems" +# PROP BASE Intermediate_Dir "PS2 EE Release SNSystems" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Release_SNSystems" +# PROP Intermediate_Dir "Release_SNSystems" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /O2 /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Release/" /FD /c +# ADD CPP /nologo /W4 /WX /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /D "SN_SYSTEMS" /FD /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"PS2_EE_Release\chatps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 sneetcp.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"Release_SNSystems\chatps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "chatps2prodg - Win32 PS2 EE Release Insock" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "chatps2prodg___Win32_PS2_EE_Release_Insock" +# PROP BASE Intermediate_Dir "chatps2prodg___Win32_PS2_EE_Release_Insock" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "chatps2prodg___Win32_PS2_EE_Release_Insock" +# PROP Intermediate_Dir "chatps2prodg___Win32_PS2_EE_Release_Insock" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /D "_DEBUG" /D "INSOCK" /Fo"Debug_Insock/" /Fd"Debug_Insock/" /FD /debug /c +# ADD CPP /nologo /W4 /WX /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /D "INSOCK" /Fo"Release_Insock/" /Fd"Release_Insock/" /FD /debug /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libinsck.a libnet.a libmrpc.a libkernl.a libcdvd.a libnetif.a netcnfif.a /nologo /pdb:none /debug /machine:IX86 /out:"Debug_Insock\chatps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 libinsck.a libnet.a libmrpc.a libkernl.a libcdvd.a libnetif.a netcnfif.a /nologo /pdb:none /debug /machine:IX86 /out:"Release_Insock\chatps2prodg.elf" /D:SN_TARGET_PS2 + +!ENDIF + +# Begin Target + +# Name "chatps2prodg - Win32 PS2 EE Debug EENet" +# Name "chatps2prodg - Win32 PS2 EE Debug SNSystems" +# Name "chatps2prodg - Win32 PS2 EE Debug Insock" +# Name "chatps2prodg - Win32 PS2 EE Release EENet" +# Name "chatps2prodg - Win32 PS2 EE Release SNSystems" +# Name "chatps2prodg - Win32 PS2 EE Release Insock" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\chatc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\ps2\ps2common.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\..\common\ps2\prodg\PS2_in_VC.h +# End Source File +# End Group +# Begin Group "ChatSDK" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\chat.h +# End Source File +# Begin Source File + +SOURCE=..\..\chatCallbacks.c +# End Source File +# Begin Source File + +SOURCE=..\..\chatCallbacks.h +# End Source File +# Begin Source File + +SOURCE=..\..\chatChannel.c +# End Source File +# Begin Source File + +SOURCE=..\..\chatChannel.h +# End Source File +# Begin Source File + +SOURCE=..\..\chatCrypt.c +# End Source File +# Begin Source File + +SOURCE=..\..\chatCrypt.h +# End Source File +# Begin Source File + +SOURCE=..\..\chatHandlers.c +# End Source File +# Begin Source File + +SOURCE=..\..\chatHandlers.h +# End Source File +# Begin Source File + +SOURCE=..\..\chatMain.c +# End Source File +# Begin Source File + +SOURCE=..\..\chatMain.h +# End Source File +# Begin Source File + +SOURCE=..\..\chatSocket.c +# End Source File +# Begin Source File + +SOURCE=..\..\chatSocket.h +# End Source File +# End Group +# Begin Group "GsCommon" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\darray.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\darray.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAssert.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAssert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAvailable.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAvailable.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsCommon.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsDebug.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsDebug.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsMemory.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsMemory.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatform.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatform.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformSocket.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformSocket.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformThread.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformThread.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformUtil.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformUtil.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsStringUtil.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsStringUtil.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\hashtable.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\hashtable.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\md5.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\md5c.c +# End Source File +# End Group +# Begin Source File + +SOURCE=c:\usr\local\sce\ee\lib\app.cmd +# End Source File +# Begin Source File + +SOURCE=c:\usr\local\sce\ee\lib\crt0.s +# End Source File +# Begin Source File + +SOURCE=..\..\..\ps2common\prodg\ps2.lk +# End Source File +# End Target +# End Project diff --git a/code/gamespy/Chat/chatc/chatps2prodg/chatps2prodg.dsw b/code/gamespy/Chat/chatc/chatps2prodg/chatps2prodg.dsw new file mode 100644 index 00000000..ec4a5ce5 --- /dev/null +++ b/code/gamespy/Chat/chatc/chatps2prodg/chatps2prodg.dsw @@ -0,0 +1,37 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "chatps2prodg"=.\chatps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/Chat/chatc/chatps2prodg", DSEDAAAA + . + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/Chat/chatc/chatps2prodg", DSEDAAAA + . + end source code control +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/code/gamespy/Chat/chatty/ChannelListDlg.cpp b/code/gamespy/Chat/chatty/ChannelListDlg.cpp new file mode 100644 index 00000000..4cb385d6 --- /dev/null +++ b/code/gamespy/Chat/chatty/ChannelListDlg.cpp @@ -0,0 +1,164 @@ +// ChannelListDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "chatty.h" +#include "ChannelListDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CChannelListDlg dialog + + +CChannelListDlg::CChannelListDlg(CWnd* pParent /*=NULL*/) + : CDialog(CChannelListDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CChannelListDlg) + m_filter = _T(""); + //}}AFX_DATA_INIT +} + + +void CChannelListDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CChannelListDlg) + DDX_Control(pDX, IDC_NUM, m_num); + DDX_Control(pDX, IDC_LIST, m_list); + DDX_Text(pDX, IDC_FILTER, m_filter); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CChannelListDlg, CDialog) + //{{AFX_MSG_MAP(CChannelListDlg) + ON_BN_CLICKED(ID_CHANNELS, OnChannels) + ON_BN_CLICKED(ID_USERS, OnUsers) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CChannelListDlg message handlers + +void EnumChannelsCallbackEach(CHAT chat, CHATBool success, int index, const char * channel, const char * topic, int numUsers, void * param) +{ + if(success) + { + CChannelListDlg * dlg = (CChannelListDlg *)param; + + CListBox * list = &dlg->m_list; + + CString str; + str = channel; + str += " ("; + char buffer[16]; + itoa(numUsers, buffer, 10); + str += buffer; + str += "): "; + str += topic; + + list->AddString(str); + + char buf[16]; + itoa(index + 1, buf, 10); + dlg->m_num.SetWindowText(buf); + } + + GSI_UNUSED(chat); +} + +void EnumJoinedChannelsCallback(CHAT chat, + int index, + const char * channel, + void * param) +{ + CChannelListDlg * dlg = (CChannelListDlg *)param; + + CListBox * list = &dlg->m_list; + + CString str; + str = "IN: "; + str += channel; + str += " ("; + char buffer[16]; + itoa(index, buffer, 10); + str += buffer; + str += "): "; + list->AddString(str); + + GSI_UNUSED(chat); +} + +void EnumChannelsCallbackAll(CHAT chat, CHATBool success, int numChannels, const char ** channels, const char ** topics, int * numUsers, void * param) +{ + if(success) + { + CChannelListDlg * dlg = (CChannelListDlg *)param; + dlg->MessageBox("Search Complete"); + } + + GSI_UNUSED(numUsers); + GSI_UNUSED(topics); + GSI_UNUSED(channels); + GSI_UNUSED(numChannels); + GSI_UNUSED(chat); +} + +void ListUsers(CHAT chat, CHATBool success, const char * channel, int numUsers, const char ** users, int * modes, void * param) +{ + if(success) + { + CChannelListDlg * dlg = (CChannelListDlg *)param; + + CListBox * list = &dlg->m_list; + + CString str; + for(int i = 0 ; i < numUsers ; i++) + { + str = users[i]; + if(modes[i] & CHAT_OP) + str.Insert(0, '@'); + else if(modes[i] & CHAT_VOICE) + str.Insert(0, '?'); + + list->AddString(str); + } + + char buf[16]; + itoa(numUsers, buf, 10); + dlg->m_num.SetWindowText(buf); + } + + GSI_UNUSED(channel); + GSI_UNUSED(chat); +} + +void CChannelListDlg::OnChannels() +{ + // Clear the list. + ////////////////// + m_list.ResetContent(); + + // Get the list. + //////////////// + UpdateData(); + chatEnumJoinedChannels(theApp.m_chat, EnumJoinedChannelsCallback, this); + chatEnumChannels(theApp.m_chat, m_filter, EnumChannelsCallbackEach, EnumChannelsCallbackAll, this, CHATFalse); +} + +void CChannelListDlg::OnUsers() +{ + // Clear the list. + ////////////////// + m_list.ResetContent(); + + // Get the list. + //////////////// + UpdateData(); + chatEnumUsers(theApp.m_chat, m_filter, ListUsers, this, CHATFalse); +} diff --git a/code/gamespy/Chat/chatty/ChannelListDlg.h b/code/gamespy/Chat/chatty/ChannelListDlg.h new file mode 100644 index 00000000..1322ca8f --- /dev/null +++ b/code/gamespy/Chat/chatty/ChannelListDlg.h @@ -0,0 +1,49 @@ +#if !defined(AFX_CHANNELLISTDLG_H__2D417F5B_EB62_4913_BB7A_6B20477ED21D__INCLUDED_) +#define AFX_CHANNELLISTDLG_H__2D417F5B_EB62_4913_BB7A_6B20477ED21D__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// ChannelListDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CChannelListDlg dialog + +class CChannelListDlg : public CDialog +{ +// Construction +public: + CChannelListDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CChannelListDlg) + enum { IDD = IDD_CHANNEL_LIST }; + CEdit m_num; + CListBox m_list; + CString m_filter; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CChannelListDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CChannelListDlg) + afx_msg void OnChannels(); + afx_msg void OnUsers(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_CHANNELLISTDLG_H__2D417F5B_EB62_4913_BB7A_6B20477ED21D__INCLUDED_) diff --git a/code/gamespy/Chat/chatty/ChannelModeDlg.cpp b/code/gamespy/Chat/chatty/ChannelModeDlg.cpp new file mode 100644 index 00000000..66d32f01 --- /dev/null +++ b/code/gamespy/Chat/chatty/ChannelModeDlg.cpp @@ -0,0 +1,56 @@ +// ChannelModeDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "chatty.h" +#include "ChannelModeDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CChannelModeDlg dialog + + +CChannelModeDlg::CChannelModeDlg(CWnd* pParent /*=NULL*/) + : CDialog(CChannelModeDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CChannelModeDlg) + m_inviteOnly = FALSE; + m_limit = 0; + m_moderated = FALSE; + m_noExternalMessages = FALSE; + m_onlyOpsChangeTopic = FALSE; + m_private = FALSE; + m_secret = FALSE; + //}}AFX_DATA_INIT +} + + +void CChannelModeDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CChannelModeDlg) + DDX_Check(pDX, IDC_INVITE_ONLY, m_inviteOnly); + DDX_Text(pDX, IDC_LIMIT, m_limit); + DDV_MinMaxInt(pDX, m_limit, 0, 9999); + DDX_Check(pDX, IDC_MODERATED, m_moderated); + DDX_Check(pDX, IDC_NO_EXTERNAL_MESSAGES, m_noExternalMessages); + DDX_Check(pDX, IDC_ONLY_OPS_CHANGE_TOPIC, m_onlyOpsChangeTopic); + DDX_Check(pDX, IDC_PRIVATE, m_private); + DDX_Check(pDX, IDC_SECRET, m_secret); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CChannelModeDlg, CDialog) + //{{AFX_MSG_MAP(CChannelModeDlg) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CChannelModeDlg message handlers diff --git a/code/gamespy/Chat/chatty/ChannelModeDlg.h b/code/gamespy/Chat/chatty/ChannelModeDlg.h new file mode 100644 index 00000000..16d001b1 --- /dev/null +++ b/code/gamespy/Chat/chatty/ChannelModeDlg.h @@ -0,0 +1,52 @@ +#if !defined(AFX_CHANNELMODEDLG_H__87449420_C42C_11D3_BD38_00C0F056BC39__INCLUDED_) +#define AFX_CHANNELMODEDLG_H__87449420_C42C_11D3_BD38_00C0F056BC39__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// ChannelModeDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CChannelModeDlg dialog + +class CChannelModeDlg : public CDialog +{ +// Construction +public: + CChannelModeDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CChannelModeDlg) + enum { IDD = IDD_CHANNEL_MODE }; + BOOL m_inviteOnly; + int m_limit; + BOOL m_moderated; + BOOL m_noExternalMessages; + BOOL m_onlyOpsChangeTopic; + BOOL m_private; + BOOL m_secret; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CChannelModeDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CChannelModeDlg) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_CHANNELMODEDLG_H__87449420_C42C_11D3_BD38_00C0F056BC39__INCLUDED_) diff --git a/code/gamespy/Chat/chatty/ChildFrm.cpp b/code/gamespy/Chat/chatty/ChildFrm.cpp new file mode 100644 index 00000000..4db2c1fb --- /dev/null +++ b/code/gamespy/Chat/chatty/ChildFrm.cpp @@ -0,0 +1,69 @@ +// ChildFrm.cpp : implementation of the CChildFrame class +// + +#include "stdafx.h" +#include "chatty.h" + +#include "ChildFrm.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CChildFrame + +IMPLEMENT_DYNCREATE(CChildFrame, CMDIChildWnd) + +BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWnd) + //{{AFX_MSG_MAP(CChildFrame) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CChildFrame construction/destruction + +CChildFrame::CChildFrame() +{ + // TODO: add member initialization code here + +} + +CChildFrame::~CChildFrame() +{ +} + +BOOL CChildFrame::PreCreateWindow(CREATESTRUCT& cs) +{ + // TODO: Modify the Window class or styles here by modifying + // the CREATESTRUCT cs + + if( !CMDIChildWnd::PreCreateWindow(cs) ) + return FALSE; + + return TRUE; +} + + + +///////////////////////////////////////////////////////////////////////////// +// CChildFrame diagnostics + +#ifdef _DEBUG +void CChildFrame::AssertValid() const +{ + CMDIChildWnd::AssertValid(); +} + +void CChildFrame::Dump(CDumpContext& dc) const +{ + CMDIChildWnd::Dump(dc); +} + +#endif //_DEBUG + +///////////////////////////////////////////////////////////////////////////// +// CChildFrame message handlers + diff --git a/code/gamespy/Chat/chatty/ChildFrm.h b/code/gamespy/Chat/chatty/ChildFrm.h new file mode 100644 index 00000000..0ef4e246 --- /dev/null +++ b/code/gamespy/Chat/chatty/ChildFrm.h @@ -0,0 +1,51 @@ +// ChildFrm.h : interface of the CChildFrame class +// +///////////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_CHILDFRM_H__CECBEADE_E41E_4FB6_93DD_C3735CFC4679__INCLUDED_) +#define AFX_CHILDFRM_H__CECBEADE_E41E_4FB6_93DD_C3735CFC4679__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + + +class CChildFrame : public CMDIChildWnd +{ + DECLARE_DYNCREATE(CChildFrame) +public: + CChildFrame(); + +// Attributes +public: + +// Operations +public: + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CChildFrame) + virtual BOOL PreCreateWindow(CREATESTRUCT& cs); + //}}AFX_VIRTUAL + +// Implementation +public: + virtual ~CChildFrame(); +#ifdef _DEBUG + virtual void AssertValid() const; + virtual void Dump(CDumpContext& dc) const; +#endif + +// Generated message map functions +protected: + //{{AFX_MSG(CChildFrame) + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_CHILDFRM_H__CECBEADE_E41E_4FB6_93DD_C3735CFC4679__INCLUDED_) diff --git a/code/gamespy/Chat/chatty/ConnectDlg.cpp b/code/gamespy/Chat/chatty/ConnectDlg.cpp new file mode 100644 index 00000000..e4ec8944 --- /dev/null +++ b/code/gamespy/Chat/chatty/ConnectDlg.cpp @@ -0,0 +1,47 @@ +// ConnectDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "chatty.h" +#include "ConnectDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CConnectDlg dialog + + +CConnectDlg::CConnectDlg(CWnd* pParent /*=NULL*/) + : CDialog(CConnectDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CConnectDlg) + m_address = _T(""); + m_nick = _T(""); + m_port = 0; + //}}AFX_DATA_INIT +} + + +void CConnectDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CConnectDlg) + DDX_Text(pDX, IDC_ADDRESS, m_address); + DDX_Text(pDX, IDC_NICK, m_nick); + DDX_Text(pDX, IDC_PORT, m_port); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CConnectDlg, CDialog) + //{{AFX_MSG_MAP(CConnectDlg) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CConnectDlg message handlers diff --git a/code/gamespy/Chat/chatty/ConnectDlg.h b/code/gamespy/Chat/chatty/ConnectDlg.h new file mode 100644 index 00000000..427675b5 --- /dev/null +++ b/code/gamespy/Chat/chatty/ConnectDlg.h @@ -0,0 +1,48 @@ +#if !defined(AFX_CONNECTDLG_H__999D9DD8_12B0_4F46_BF46_F607FB3341A0__INCLUDED_) +#define AFX_CONNECTDLG_H__999D9DD8_12B0_4F46_BF46_F607FB3341A0__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// ConnectDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CConnectDlg dialog + +class CConnectDlg : public CDialog +{ +// Construction +public: + CConnectDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CConnectDlg) + enum { IDD = IDD_CONNECT }; + CString m_address; + CString m_nick; + int m_port; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CConnectDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CConnectDlg) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_CONNECTDLG_H__999D9DD8_12B0_4F46_BF46_F607FB3341A0__INCLUDED_) diff --git a/code/gamespy/Chat/chatty/EnterDlg.cpp b/code/gamespy/Chat/chatty/EnterDlg.cpp new file mode 100644 index 00000000..24e1f215 --- /dev/null +++ b/code/gamespy/Chat/chatty/EnterDlg.cpp @@ -0,0 +1,74 @@ +// EnterDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "chatty.h" +#include "EnterDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CEnterDlg dialog + + +CEnterDlg::CEnterDlg(CWnd* pParent /*=NULL*/) + : CDialog(CEnterDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CEnterDlg) + m_channel = _T(""); + m_password = _T(""); + //}}AFX_DATA_INIT + + m_quickChannel = ""; +} + + +void CEnterDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CEnterDlg) + DDX_Text(pDX, IDC_CHANNEL, m_channel); + DDX_Text(pDX, IDC_PASSWORD, m_password); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CEnterDlg, CDialog) + //{{AFX_MSG_MAP(CEnterDlg) + ON_BN_CLICKED(IDC_QUICK1, OnQuick1) + ON_BN_CLICKED(IDC_QUICK2, OnQuick2) + ON_BN_CLICKED(IDC_QUICK3, OnQuick3) + ON_BN_CLICKED(IDC_QUICK4, OnQuick4) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CEnterDlg message handlers + +void CEnterDlg::OnQuick1() +{ + m_quickChannel = "#istanbul"; + OnOK(); +} + +void CEnterDlg::OnQuick2() +{ + m_quickChannel = "#montreal"; + OnOK(); +} + +void CEnterDlg::OnQuick3() +{ + m_quickChannel = "#pants-test"; + OnOK(); +} + +void CEnterDlg::OnQuick4() +{ + m_quickChannel = "#zurna"; + OnOK(); +} diff --git a/code/gamespy/Chat/chatty/EnterDlg.h b/code/gamespy/Chat/chatty/EnterDlg.h new file mode 100644 index 00000000..0dd126a8 --- /dev/null +++ b/code/gamespy/Chat/chatty/EnterDlg.h @@ -0,0 +1,52 @@ +#if !defined(AFX_ENTERDLG_H__56D121BA_9750_4FF9_AEA2_0CDAC1CB1DC2__INCLUDED_) +#define AFX_ENTERDLG_H__56D121BA_9750_4FF9_AEA2_0CDAC1CB1DC2__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// EnterDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CEnterDlg dialog + +class CEnterDlg : public CDialog +{ +// Construction +public: + CEnterDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CEnterDlg) + enum { IDD = IDD_ENTER }; + CString m_channel; + CString m_password; + //}}AFX_DATA + + CString m_quickChannel; + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CEnterDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CEnterDlg) + afx_msg void OnQuick1(); + afx_msg void OnQuick2(); + afx_msg void OnQuick3(); + afx_msg void OnQuick4(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_ENTERDLG_H__56D121BA_9750_4FF9_AEA2_0CDAC1CB1DC2__INCLUDED_) diff --git a/code/gamespy/Chat/chatty/GetUserInfoDlg.cpp b/code/gamespy/Chat/chatty/GetUserInfoDlg.cpp new file mode 100644 index 00000000..be0276b1 --- /dev/null +++ b/code/gamespy/Chat/chatty/GetUserInfoDlg.cpp @@ -0,0 +1,80 @@ +// GetUserInfoDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "chatty.h" +#include "GetUserInfoDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CGetUserInfoDlg dialog + + +CGetUserInfoDlg::CGetUserInfoDlg(CWnd* pParent /*=NULL*/) + : CDialog(CGetUserInfoDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CGetUserInfoDlg) + m_address = _T(""); + m_name = _T(""); + m_user = _T(""); + m_nick = _T(""); + //}}AFX_DATA_INIT +} + + +void CGetUserInfoDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CGetUserInfoDlg) + DDX_Control(pDX, IDC_CHANNELS, m_channels); + DDX_Text(pDX, IDC_ADDRESS, m_address); + DDX_Text(pDX, IDC_NAME, m_name); + DDX_Text(pDX, IDC_USER, m_user); + DDX_Text(pDX, IDC_NICK, m_nick); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CGetUserInfoDlg, CDialog) + //{{AFX_MSG_MAP(CGetUserInfoDlg) + ON_BN_CLICKED(ID_GET_INFO, OnGetInfo) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CGetUserInfoDlg message handlers + +void GetUserInfoCallback(CHAT chat, CHATBool success, const char * nick, const char * user, const char * name, const char * address, int numChannels, const char ** channels, void * param) +{ + CGetUserInfoDlg * dlg = (CGetUserInfoDlg *)param; + + if(success) + { + dlg->m_user = user; + dlg->m_name = name; + dlg->m_address = address; + dlg->UpdateData(FALSE); + dlg->m_channels.ResetContent(); + for(int i = 0 ; i < numChannels ; i++) + dlg->m_channels.AddString(channels[i]); + } + + dlg->MessageBox("done"); + + GSI_UNUSED(nick); + GSI_UNUSED(chat); +} + +void CGetUserInfoDlg::OnGetInfo() +{ + if(theApp.m_chat != NULL) + { + UpdateData(); + chatGetUserInfo(theApp.m_chat, m_nick, GetUserInfoCallback, this, CHATTrue); + } +} diff --git a/code/gamespy/Chat/chatty/GetUserInfoDlg.h b/code/gamespy/Chat/chatty/GetUserInfoDlg.h new file mode 100644 index 00000000..1452927c --- /dev/null +++ b/code/gamespy/Chat/chatty/GetUserInfoDlg.h @@ -0,0 +1,50 @@ +#if !defined(AFX_GETUSERINFODLG_H__600E8857_A5B5_41DF_973E_0A3DCCF0DCEB__INCLUDED_) +#define AFX_GETUSERINFODLG_H__600E8857_A5B5_41DF_973E_0A3DCCF0DCEB__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// GetUserInfoDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CGetUserInfoDlg dialog + +class CGetUserInfoDlg : public CDialog +{ +// Construction +public: + CGetUserInfoDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CGetUserInfoDlg) + enum { IDD = IDD_GET_USER_INFO }; + CListBox m_channels; + CString m_address; + CString m_name; + CString m_user; + CString m_nick; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CGetUserInfoDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CGetUserInfoDlg) + afx_msg void OnGetInfo(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_GETUSERINFODLG_H__600E8857_A5B5_41DF_973E_0A3DCCF0DCEB__INCLUDED_) diff --git a/code/gamespy/Chat/chatty/KickReasonDlg.cpp b/code/gamespy/Chat/chatty/KickReasonDlg.cpp new file mode 100644 index 00000000..7d9f6cc9 --- /dev/null +++ b/code/gamespy/Chat/chatty/KickReasonDlg.cpp @@ -0,0 +1,43 @@ +// KickReasonDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "chatty.h" +#include "KickReasonDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CKickReasonDlg dialog + + +CKickReasonDlg::CKickReasonDlg(CWnd* pParent /*=NULL*/) + : CDialog(CKickReasonDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CKickReasonDlg) + m_reason = _T(""); + //}}AFX_DATA_INIT +} + + +void CKickReasonDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CKickReasonDlg) + DDX_Text(pDX, IDC_REASON, m_reason); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CKickReasonDlg, CDialog) + //{{AFX_MSG_MAP(CKickReasonDlg) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CKickReasonDlg message handlers diff --git a/code/gamespy/Chat/chatty/KickReasonDlg.h b/code/gamespy/Chat/chatty/KickReasonDlg.h new file mode 100644 index 00000000..07739d72 --- /dev/null +++ b/code/gamespy/Chat/chatty/KickReasonDlg.h @@ -0,0 +1,46 @@ +#if !defined(AFX_KICKREASONDLG_H__25C49432_E9A9_4BEF_8C47_31F8DF889DF0__INCLUDED_) +#define AFX_KICKREASONDLG_H__25C49432_E9A9_4BEF_8C47_31F8DF889DF0__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// KickReasonDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CKickReasonDlg dialog + +class CKickReasonDlg : public CDialog +{ +// Construction +public: + CKickReasonDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CKickReasonDlg) + enum { IDD = IDD_KICK_REASON }; + CString m_reason; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CKickReasonDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CKickReasonDlg) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_KICKREASONDLG_H__25C49432_E9A9_4BEF_8C47_31F8DF889DF0__INCLUDED_) diff --git a/code/gamespy/Chat/chatty/MainFrm.cpp b/code/gamespy/Chat/chatty/MainFrm.cpp new file mode 100644 index 00000000..3365da4e --- /dev/null +++ b/code/gamespy/Chat/chatty/MainFrm.cpp @@ -0,0 +1,133 @@ +// MainFrm.cpp : implementation of the CMainFrame class +// + +#include "stdafx.h" +#include "chatty.h" + +#include "MainFrm.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CMainFrame + +IMPLEMENT_DYNAMIC(CMainFrame, CMDIFrameWnd) + +BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd) + //{{AFX_MSG_MAP(CMainFrame) + ON_WM_CREATE() + ON_WM_TIMER() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +static UINT indicators[] = +{ + ID_SEPARATOR, // status line indicator + ID_INDICATOR_CAPS, + ID_INDICATOR_NUM, + ID_INDICATOR_SCRL, +}; + +///////////////////////////////////////////////////////////////////////////// +// CMainFrame construction/destruction + +CMainFrame::CMainFrame() +{ + // TODO: add member initialization code here + +} + +CMainFrame::~CMainFrame() +{ +} + +int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) +{ + if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1) + return -1; + + if (!m_wndToolBar.CreateEx(this) || + !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) + { + TRACE0("Failed to create toolbar\n"); + return -1; // fail to create + } + if (!m_wndDlgBar.Create(this, IDR_MAINFRAME, + CBRS_ALIGN_TOP, AFX_IDW_DIALOGBAR)) + { + TRACE0("Failed to create dialogbar\n"); + return -1; // fail to create + } + + if (!m_wndReBar.Create(this) || + !m_wndReBar.AddBar(&m_wndToolBar) || + !m_wndReBar.AddBar(&m_wndDlgBar)) + { + TRACE0("Failed to create rebar\n"); + return -1; // fail to create + } + + if (!m_wndStatusBar.Create(this) || + !m_wndStatusBar.SetIndicators(indicators, + sizeof(indicators)/sizeof(UINT))) + { + TRACE0("Failed to create status bar\n"); + return -1; // fail to create + } + + // TODO: Remove this if you don't want tool tips + m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() | + CBRS_TOOLTIPS | CBRS_FLYBY); + +/**************** +** TIMER SETUP ** +****************/ + SetTimer(50, 50, NULL); + + return 0; +} + +BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) +{ + if( !CMDIFrameWnd::PreCreateWindow(cs) ) + return FALSE; + // TODO: Modify the Window class or styles here by modifying + // the CREATESTRUCT cs + + return TRUE; +} + +///////////////////////////////////////////////////////////////////////////// +// CMainFrame diagnostics + +#ifdef _DEBUG +void CMainFrame::AssertValid() const +{ + CMDIFrameWnd::AssertValid(); +} + +void CMainFrame::Dump(CDumpContext& dc) const +{ + CMDIFrameWnd::Dump(dc); +} + +#endif //_DEBUG + +///////////////////////////////////////////////////////////////////////////// +// CMainFrame message handlers + + +void CMainFrame::OnTimer(UINT nIDEvent) +{ + if(nIDEvent == 50) + { + if(theApp.m_chat != NULL) + chatThink(theApp.m_chat); + } + + CMDIFrameWnd::OnTimer(nIDEvent); +} diff --git a/code/gamespy/Chat/chatty/MainFrm.h b/code/gamespy/Chat/chatty/MainFrm.h new file mode 100644 index 00000000..2b869f7d --- /dev/null +++ b/code/gamespy/Chat/chatty/MainFrm.h @@ -0,0 +1,58 @@ +// MainFrm.h : interface of the CMainFrame class +// +///////////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_MAINFRM_H__501D21BC_112E_496A_A17B_FB30CD931AF1__INCLUDED_) +#define AFX_MAINFRM_H__501D21BC_112E_496A_A17B_FB30CD931AF1__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +class CMainFrame : public CMDIFrameWnd +{ + DECLARE_DYNAMIC(CMainFrame) +public: + CMainFrame(); + +// Attributes +public: + +// Operations +public: + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CMainFrame) + virtual BOOL PreCreateWindow(CREATESTRUCT& cs); + //}}AFX_VIRTUAL + +// Implementation +public: + virtual ~CMainFrame(); +#ifdef _DEBUG + virtual void AssertValid() const; + virtual void Dump(CDumpContext& dc) const; +#endif + +protected: // control bar embedded members + CStatusBar m_wndStatusBar; + CToolBar m_wndToolBar; + CReBar m_wndReBar; + CDialogBar m_wndDlgBar; + +// Generated message map functions +protected: + //{{AFX_MSG(CMainFrame) + afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); + afx_msg void OnTimer(UINT nIDEvent); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_MAINFRM_H__501D21BC_112E_496A_A17B_FB30CD931AF1__INCLUDED_) diff --git a/code/gamespy/Chat/chatty/ReadMe.txt b/code/gamespy/Chat/chatty/ReadMe.txt new file mode 100644 index 00000000..beade7a9 --- /dev/null +++ b/code/gamespy/Chat/chatty/ReadMe.txt @@ -0,0 +1,117 @@ +======================================================================== + MICROSOFT FOUNDATION CLASS LIBRARY : chatty +======================================================================== + + +AppWizard has created this chatty application for you. This application +not only demonstrates the basics of using the Microsoft Foundation classes +but is also a starting point for writing your application. + +This file contains a summary of what you will find in each of the files that +make up your chatty application. + +chatty.dsp + This file (the project file) contains information at the project level and + is used to build a single project or subproject. Other users can share the + project (.dsp) file, but they should export the makefiles locally. + +chatty.h + This is the main header file for the application. It includes other + project specific headers (including Resource.h) and declares the + CChattyApp application class. + +chatty.cpp + This is the main application source file that contains the application + class CChattyApp. + +chatty.rc + This is a listing of all of the Microsoft Windows resources that the + program uses. It includes the icons, bitmaps, and cursors that are stored + in the RES subdirectory. This file can be directly edited in Microsoft + Visual C++. + +chatty.clw + This file contains information used by ClassWizard to edit existing + classes or add new classes. ClassWizard also uses this file to store + information needed to create and edit message maps and dialog data + maps and to create prototype member functions. + +res\chatty.ico + This is an icon file, which is used as the application's icon. This + icon is included by the main resource file chatty.rc. + +res\chatty.rc2 + This file contains resources that are not edited by Microsoft + Visual C++. You should place all resources not editable by + the resource editor in this file. + + + +///////////////////////////////////////////////////////////////////////////// + +For the main frame window: + +MainFrm.h, MainFrm.cpp + These files contain the frame class CMainFrame, which is derived from + CMDIFrameWnd and controls all MDI frame features. + +res\Toolbar.bmp + This bitmap file is used to create tiled images for the toolbar. + The initial toolbar and status bar are constructed in the CMainFrame + class. Edit this toolbar bitmap using the resource editor, and + update the IDR_MAINFRAME TOOLBAR array in chatty.rc to add + toolbar buttons. +///////////////////////////////////////////////////////////////////////////// + +For the child frame window: + +ChildFrm.h, ChildFrm.cpp + These files define and implement the CChildFrame class, which + supports the child windows in an MDI application. + +///////////////////////////////////////////////////////////////////////////// + +AppWizard creates one document type and one view: + +chattyDoc.h, chattyDoc.cpp - the document + These files contain your CChattyDoc class. Edit these files to + add your special document data and to implement file saving and loading + (via CChattyDoc::Serialize). + +chattyView.h, chattyView.cpp - the view of the document + These files contain your CChattyView class. + CChattyView objects are used to view CChattyDoc objects. + +res\chattyDoc.ico + This is an icon file, which is used as the icon for MDI child windows + for the CChattyDoc class. This icon is included by the main + resource file chatty.rc. + + +///////////////////////////////////////////////////////////////////////////// +Other standard files: + +StdAfx.h, StdAfx.cpp + These files are used to build a precompiled header (PCH) file + named chatty.pch and a precompiled types file named StdAfx.obj. + +Resource.h + This is the standard header file, which defines new resource IDs. + Microsoft Visual C++ reads and updates this file. + +///////////////////////////////////////////////////////////////////////////// +Other notes: + +AppWizard uses "TODO:" to indicate parts of the source code you +should add to or customize. + +If your application uses MFC in a shared DLL, and your application is +in a language other than the operating system's current language, you +will need to copy the corresponding localized resources MFC42XXX.DLL +from the Microsoft Visual C++ CD-ROM onto the system or system32 directory, +and rename it to be MFCLOC.DLL. ("XXX" stands for the language abbreviation. +For example, MFC42DEU.DLL contains resources translated to German.) If you +don't do this, some of the UI elements of your application will remain in the +language of the operating system. + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/Chat/chatty/SendRawDlg.cpp b/code/gamespy/Chat/chatty/SendRawDlg.cpp new file mode 100644 index 00000000..7646f14e --- /dev/null +++ b/code/gamespy/Chat/chatty/SendRawDlg.cpp @@ -0,0 +1,43 @@ +// SendRawDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "chatty.h" +#include "SendRawDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CSendRawDlg dialog + + +CSendRawDlg::CSendRawDlg(CWnd* pParent /*=NULL*/) + : CDialog(CSendRawDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CSendRawDlg) + m_raw = _T(""); + //}}AFX_DATA_INIT +} + + +void CSendRawDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CSendRawDlg) + DDX_Text(pDX, IDC_RAW, m_raw); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CSendRawDlg, CDialog) + //{{AFX_MSG_MAP(CSendRawDlg) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CSendRawDlg message handlers diff --git a/code/gamespy/Chat/chatty/SendRawDlg.h b/code/gamespy/Chat/chatty/SendRawDlg.h new file mode 100644 index 00000000..7723f3cf --- /dev/null +++ b/code/gamespy/Chat/chatty/SendRawDlg.h @@ -0,0 +1,46 @@ +#if !defined(AFX_SENDRAWDLG_H__543BE7A8_4BE8_4ED0_972D_2339364077F6__INCLUDED_) +#define AFX_SENDRAWDLG_H__543BE7A8_4BE8_4ED0_972D_2339364077F6__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// SendRawDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CSendRawDlg dialog + +class CSendRawDlg : public CDialog +{ +// Construction +public: + CSendRawDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CSendRawDlg) + enum { IDD = IDD_SEND_RAW }; + CString m_raw; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CSendRawDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CSendRawDlg) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_SENDRAWDLG_H__543BE7A8_4BE8_4ED0_972D_2339364077F6__INCLUDED_) diff --git a/code/gamespy/Chat/chatty/SetPasswordDlg.cpp b/code/gamespy/Chat/chatty/SetPasswordDlg.cpp new file mode 100644 index 00000000..32630d1b --- /dev/null +++ b/code/gamespy/Chat/chatty/SetPasswordDlg.cpp @@ -0,0 +1,45 @@ +// SetPasswordDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "chatty.h" +#include "SetPasswordDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CSetPasswordDlg dialog + + +CSetPasswordDlg::CSetPasswordDlg(CWnd* pParent /*=NULL*/) + : CDialog(CSetPasswordDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CSetPasswordDlg) + m_password = _T(""); + m_enable = FALSE; + //}}AFX_DATA_INIT +} + + +void CSetPasswordDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CSetPasswordDlg) + DDX_Text(pDX, IDC_PASSWORD, m_password); + DDX_Check(pDX, IDC_ENABLE, m_enable); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CSetPasswordDlg, CDialog) + //{{AFX_MSG_MAP(CSetPasswordDlg) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CSetPasswordDlg message handlers diff --git a/code/gamespy/Chat/chatty/SetPasswordDlg.h b/code/gamespy/Chat/chatty/SetPasswordDlg.h new file mode 100644 index 00000000..95736f82 --- /dev/null +++ b/code/gamespy/Chat/chatty/SetPasswordDlg.h @@ -0,0 +1,47 @@ +#if !defined(AFX_SETPASSWORDDLG_H__666C66DA_E10E_420D_954E_FB2BCE01D798__INCLUDED_) +#define AFX_SETPASSWORDDLG_H__666C66DA_E10E_420D_954E_FB2BCE01D798__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// SetPasswordDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CSetPasswordDlg dialog + +class CSetPasswordDlg : public CDialog +{ +// Construction +public: + CSetPasswordDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CSetPasswordDlg) + enum { IDD = IDD_SET_PASSWORD }; + CString m_password; + BOOL m_enable; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CSetPasswordDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CSetPasswordDlg) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_SETPASSWORDDLG_H__666C66DA_E10E_420D_954E_FB2BCE01D798__INCLUDED_) diff --git a/code/gamespy/Chat/chatty/SetTopicDlg.cpp b/code/gamespy/Chat/chatty/SetTopicDlg.cpp new file mode 100644 index 00000000..496c2ef4 --- /dev/null +++ b/code/gamespy/Chat/chatty/SetTopicDlg.cpp @@ -0,0 +1,43 @@ +// SetTopicDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "chatty.h" +#include "SetTopicDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CSetTopicDlg dialog + + +CSetTopicDlg::CSetTopicDlg(CWnd* pParent /*=NULL*/) + : CDialog(CSetTopicDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CSetTopicDlg) + m_topic = _T(""); + //}}AFX_DATA_INIT +} + + +void CSetTopicDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CSetTopicDlg) + DDX_Text(pDX, IDC_TOPIC, m_topic); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CSetTopicDlg, CDialog) + //{{AFX_MSG_MAP(CSetTopicDlg) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CSetTopicDlg message handlers diff --git a/code/gamespy/Chat/chatty/SetTopicDlg.h b/code/gamespy/Chat/chatty/SetTopicDlg.h new file mode 100644 index 00000000..26a91465 --- /dev/null +++ b/code/gamespy/Chat/chatty/SetTopicDlg.h @@ -0,0 +1,46 @@ +#if !defined(AFX_SETTOPICDLG_H__15C72001_C3D8_11D3_BD38_00C0F056BC39__INCLUDED_) +#define AFX_SETTOPICDLG_H__15C72001_C3D8_11D3_BD38_00C0F056BC39__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// SetTopicDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CSetTopicDlg dialog + +class CSetTopicDlg : public CDialog +{ +// Construction +public: + CSetTopicDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CSetTopicDlg) + enum { IDD = IDD_SET_TOPIC }; + CString m_topic; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CSetTopicDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CSetTopicDlg) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_SETTOPICDLG_H__15C72001_C3D8_11D3_BD38_00C0F056BC39__INCLUDED_) diff --git a/code/gamespy/Chat/chatty/StdAfx.cpp b/code/gamespy/Chat/chatty/StdAfx.cpp new file mode 100644 index 00000000..b92833be --- /dev/null +++ b/code/gamespy/Chat/chatty/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// chatty.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/code/gamespy/Chat/chatty/StdAfx.h b/code/gamespy/Chat/chatty/StdAfx.h new file mode 100644 index 00000000..11ae4416 --- /dev/null +++ b/code/gamespy/Chat/chatty/StdAfx.h @@ -0,0 +1,31 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__4CAB521F_F4DE_4629_A857_08E96E0C89E8__INCLUDED_) +#define AFX_STDAFX_H__4CAB521F_F4DE_4629_A857_08E96E0C89E8__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +#include // MFC socket extensions +#include + +#include "../chat.h" + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__4CAB521F_F4DE_4629_A857_08E96E0C89E8__INCLUDED_) diff --git a/code/gamespy/Chat/chatty/TalkDlg.cpp b/code/gamespy/Chat/chatty/TalkDlg.cpp new file mode 100644 index 00000000..95462974 --- /dev/null +++ b/code/gamespy/Chat/chatty/TalkDlg.cpp @@ -0,0 +1,45 @@ +// TalkDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "chatty.h" +#include "TalkDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CTalkDlg dialog + + +CTalkDlg::CTalkDlg(CWnd* pParent /*=NULL*/) + : CDialog(CTalkDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CTalkDlg) + m_message = _T(""); + m_type = -1; + //}}AFX_DATA_INIT +} + + +void CTalkDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CTalkDlg) + DDX_Text(pDX, IDC_MESSAGE, m_message); + DDX_Radio(pDX, IDC_NORMAL, m_type); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CTalkDlg, CDialog) + //{{AFX_MSG_MAP(CTalkDlg) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CTalkDlg message handlers diff --git a/code/gamespy/Chat/chatty/TalkDlg.h b/code/gamespy/Chat/chatty/TalkDlg.h new file mode 100644 index 00000000..e7af85f2 --- /dev/null +++ b/code/gamespy/Chat/chatty/TalkDlg.h @@ -0,0 +1,47 @@ +#if !defined(AFX_TALKDLG_H__87449421_C42C_11D3_BD38_00C0F056BC39__INCLUDED_) +#define AFX_TALKDLG_H__87449421_C42C_11D3_BD38_00C0F056BC39__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// TalkDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CTalkDlg dialog + +class CTalkDlg : public CDialog +{ +// Construction +public: + CTalkDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CTalkDlg) + enum { IDD = IDD_TALK }; + CString m_message; + int m_type; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CTalkDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CTalkDlg) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_TALKDLG_H__87449421_C42C_11D3_BD38_00C0F056BC39__INCLUDED_) diff --git a/code/gamespy/Chat/chatty/chatty.cpp b/code/gamespy/Chat/chatty/chatty.cpp new file mode 100644 index 00000000..a76f8823 --- /dev/null +++ b/code/gamespy/Chat/chatty/chatty.cpp @@ -0,0 +1,377 @@ +// chatty.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "chatty.h" + +#include "MainFrm.h" +#include "ChildFrm.h" +#include "chattyDoc.h" +#include "chattyView.h" +#include "ConnectDlg.h" +#include "ChannelListDlg.h" +#include "SendRawDlg.h" +#include "GetUserInfoDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CChattyApp + +BEGIN_MESSAGE_MAP(CChattyApp, CWinApp) + //{{AFX_MSG_MAP(CChattyApp) + ON_COMMAND(ID_APP_ABOUT, OnAppAbout) + ON_COMMAND(ID_FILE_CONNECT, OnFileConnect) + ON_COMMAND(ID_FILE_DISCONNECT, OnFileDisconnect) + ON_COMMAND(ID_FILE_LISTCHANNELS, OnFileListchannels) + ON_COMMAND(ID_FILE_SENDRAW, OnFileSendraw) + ON_COMMAND(ID_FILE_GETUSERINFO, OnFileGetuserinfo) + ON_COMMAND(ID_FILE_SILENCE, OnFileSilence) + ON_COMMAND(ID_FILE_UNSILENCE, OnFileUnsilence) + //}}AFX_MSG_MAP + // Standard file based document commands + ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew) + ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CChattyApp construction + +CChattyApp::CChattyApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance + m_chat = NULL; + m_connected = false; +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CChattyApp object + +CChattyApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CChattyApp initialization + +BOOL CChattyApp::InitInstance() +{ + if (!AfxSocketInit()) + { + AfxMessageBox(IDP_SOCKETS_INIT_FAILED); + return FALSE; + } + + AfxEnableControlContainer(); + + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. +/* +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif +*/ + // Change the registry key under which our settings are stored. + // TODO: You should modify this string to be something appropriate + // such as the name of your company or organization. + SetRegistryKey(_T("GameSpy")); + + LoadStdProfileSettings(0); // Load standard INI file options (including MRU) + + // Register the application's document templates. Document templates + // serve as the connection between documents, frame windows and views. + + CMultiDocTemplate* pDocTemplate; + pDocTemplate = new CMultiDocTemplate( + IDR_CHATTYTYPE, + RUNTIME_CLASS(CChattyDoc), + RUNTIME_CLASS(CChildFrame), // custom MDI child frame + RUNTIME_CLASS(CChattyView)); + AddDocTemplate(pDocTemplate); + + // create main MDI Frame window + CMainFrame* pMainFrame = new CMainFrame; + if (!pMainFrame->LoadFrame(IDR_MAINFRAME)) + return FALSE; + m_pMainWnd = pMainFrame; + + // Parse command line for standard shell commands, DDE, file open + // Removed for VS .NET + /* + CCommandLineInfo cmdInfo; + cmdInfo.m_nShellCommand = CCommandLineInfo.FileNothing; + ParseCommandLine(cmdInfo); + + // Dispatch commands specified on the command line + if (!ProcessShellCommand(cmdInfo)) + return FALSE; + */ + // The main window has been initialized, so show and update it. + pMainFrame->ShowWindow(m_nCmdShow); + pMainFrame->UpdateWindow(); + + return TRUE; +} + + +///////////////////////////////////////////////////////////////////////////// +// CAboutDlg dialog used for App About + +class CAboutDlg : public CDialog +{ +public: + CAboutDlg(); + +// Dialog Data + //{{AFX_DATA(CAboutDlg) + enum { IDD = IDD_ABOUTBOX }; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CAboutDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + //{{AFX_MSG(CAboutDlg) + // No message handlers + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) +{ + //{{AFX_DATA_INIT(CAboutDlg) + //}}AFX_DATA_INIT +} + +void CAboutDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CAboutDlg) + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) + //{{AFX_MSG_MAP(CAboutDlg) + // No message handlers + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +// App command to run the dialog +void CChattyApp::OnAppAbout() +{ + CAboutDlg aboutDlg; + aboutDlg.DoModal(); +} + +///////////////////////////////////////////////////////////////////////////// +// CChattyApp message handlers + +/********************* +** GLOBAL CALLBACKS ** +*********************/ +void Raw(CHAT chat, const char * raw, void * param) +{ + GSI_UNUSED(chat); + GSI_UNUSED(raw); + GSI_UNUSED(param); +} + +void Disconnected(CHAT chat, const char * reason, void * param) +{ + OutputDebugString("Disconnected called\n"); + char buffer[256]; + + sprintf(buffer, "You have been disconnected: %s", reason); + theApp.m_pActiveWnd->MessageBox(buffer); + + //chatDisconnect(theApp.m_chat); + theApp.m_chat = NULL; + + GSI_UNUSED(chat); + GSI_UNUSED(param); +} + +void PrivateMessage(CHAT chat, const char * user, const char * message, int type, void * param) +{ + OutputDebugString("PrivateMessage called\n"); +#ifdef FEEDBACK + CString str = user; + str.MakeLower(); + + // Ignore of this is from a server. + /////////////////////////////////// + if(strstr(str, ".org") || strstr(str, ".net") || strstr(str, ".com")) + return; + + char buffer[256]; + + sprintf(buffer, "%s: %s", user, message); + if(type == CHAT_ACTION) + strcat(buffer, ""); + else if(type == CHAT_NOTICE) + strcat(buffer, ""); + theApp.m_pActiveWnd->MessageBox(buffer); +#endif + + GSI_UNUSED(param); + GSI_UNUSED(type); + GSI_UNUSED(message); + GSI_UNUSED(user); + GSI_UNUSED(chat); +} + +void Invited(CHAT chat, const char * channel, const char * user, void * param) +{ + OutputDebugString("Invited called\n"); +#ifdef FEEDBACK + char buffer[256]; + + sprintf(buffer, "%s has invited you to join %s", user, channel); + theApp.m_pActiveWnd->MessageBox(buffer); +#endif + + GSI_UNUSED(param); + GSI_UNUSED(user); + GSI_UNUSED(channel); + GSI_UNUSED(chat); +} + +void NickErrorCallback(CHAT chat, int type, const char * nick, int numSuggestedNicks, const char ** suggestedNicks, void * param) +{ + OutputDebugString("NickErrorCallback called\n"); + + // Try a new nick. + ////////////////// + chatRetryWithNick(chat, "testnick"); + + GSI_UNUSED(param); + GSI_UNUSED(suggestedNicks); + GSI_UNUSED(numSuggestedNicks); + GSI_UNUSED(nick); + GSI_UNUSED(type); +} + +void ConnectCallback(CHAT chat, CHATBool success, int failureReason, void * param) +{ + OutputDebugString("ConnectCallback called\n"); + if(success) + { + theApp.m_pMainWnd->MessageBox("Connected!"); + theApp.m_connected = true; + } + else + { + theApp.m_pMainWnd->MessageBox("Connect Failed!"); + theApp.m_connected = false; + } + + GSI_UNUSED(param); + GSI_UNUSED(failureReason); + GSI_UNUSED(chat); +} + +void CChattyApp::OnFileConnect() +{ + if(m_chat == NULL) + { + CConnectDlg dlg; + dlg.m_nick = "Pants"; + dlg.m_address = "peerchat." GSI_DOMAIN_NAME; + //dlg.m_address = "baltimore.md.us.undernet.org"; + //dlg.m_address = "saltlake.ut.us.undernet.org"; + dlg.m_port = 6667; + + if(dlg.DoModal() == IDOK) + { + chatGlobalCallbacks callbacks; + memset(&callbacks, 0, sizeof(chatGlobalCallbacks)); + callbacks.raw = Raw; + callbacks.disconnected = Disconnected; + callbacks.privateMessage = PrivateMessage; + callbacks.invited = Invited; + + m_chat = chatConnect(dlg.m_address, dlg.m_port, dlg.m_nick, "pants", "pants", &callbacks, NickErrorCallback, ConnectCallback, this, CHATTrue); + + // Check for a NULL chat object. + // PANTS|05.15.2000 + //////////////////////////////// + if(!m_chat) + MessageBox(NULL, "chatConnect() failed", NULL, MB_OK); + } + } +} + +void CChattyApp::OnFileDisconnect() +{ + if(m_chat != NULL) + { + chatDisconnect(m_chat); + + m_chat = NULL; + } +} + +int CChattyApp::ExitInstance() +{ + if(m_chat != NULL) + { + chatDisconnect(m_chat); + + m_chat = NULL; + } + + return CWinApp::ExitInstance(); +} + +void CChattyApp::OnFileListchannels() +{ + if(theApp.m_chat != NULL) + { + CChannelListDlg dlg; + dlg.DoModal(); + } +} + +void CChattyApp::OnFileSendraw() +{ + if(theApp.m_chat != NULL) + { + CSendRawDlg dlg; + if(dlg.DoModal() == IDOK) + { + chatSendRaw(theApp.m_chat, dlg.m_raw); + } + } +} + +void CChattyApp::OnFileGetuserinfo() +{ + if(theApp.m_chat != NULL) + { + CGetUserInfoDlg dlg; + dlg.DoModal(); + } +} + +void CChattyApp::OnFileSilence() +{ + if(theApp.m_chat != NULL) + chatSetQuietMode(theApp.m_chat, CHATTrue); +} + +void CChattyApp::OnFileUnsilence() +{ + if(theApp.m_chat != NULL) + chatSetQuietMode(theApp.m_chat, CHATFalse); +} diff --git a/code/gamespy/Chat/chatty/chatty.h b/code/gamespy/Chat/chatty/chatty.h new file mode 100644 index 00000000..3d74cd6c --- /dev/null +++ b/code/gamespy/Chat/chatty/chatty.h @@ -0,0 +1,59 @@ +// chatty.h : main header file for the CHATTY application +// + +#if !defined(AFX_CHATTY_H__D2C88C24_CA2E_4ED3_B08E_BF48F6C0A36B__INCLUDED_) +#define AFX_CHATTY_H__D2C88C24_CA2E_4ED3_B08E_BF48F6C0A36B__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CChattyApp: +// See chatty.cpp for the implementation of this class +// + +class CChattyApp : public CWinApp +{ +public: + CChattyApp(); + + CHAT m_chat; + bool m_connected; + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CChattyApp) + public: + virtual BOOL InitInstance(); + virtual int ExitInstance(); + //}}AFX_VIRTUAL + +// Implementation + //{{AFX_MSG(CChattyApp) + afx_msg void OnAppAbout(); + afx_msg void OnFileConnect(); + afx_msg void OnFileDisconnect(); + afx_msg void OnFileListchannels(); + afx_msg void OnFileSendraw(); + afx_msg void OnFileGetuserinfo(); + afx_msg void OnFileSilence(); + afx_msg void OnFileUnsilence(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +extern CChattyApp theApp; + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_CHATTY_H__D2C88C24_CA2E_4ED3_B08E_BF48F6C0A36B__INCLUDED_) diff --git a/code/gamespy/Chat/chatty/chatty.rc b/code/gamespy/Chat/chatty/chatty.rc new file mode 100644 index 00000000..a71806fa --- /dev/null +++ b/code/gamespy/Chat/chatty/chatty.rc @@ -0,0 +1,737 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\chatty.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\chatty.ico" +IDR_CHATTYTYPE ICON DISCARDABLE "res\\chattyDoc.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +IDR_MAINFRAME BITMAP MOVEABLE PURE "res\\Toolbar.bmp" + +///////////////////////////////////////////////////////////////////////////// +// +// Toolbar +// + +IDR_MAINFRAME TOOLBAR DISCARDABLE 16, 15 +BEGIN + BUTTON ID_FILE_CONNECT + BUTTON ID_FILE_DISCONNECT + SEPARATOR + BUTTON ID_FILE_LISTCHANNELS + BUTTON ID_FILE_SENDRAW + SEPARATOR + BUTTON ID_FILE_NEW + BUTTON ID_FILE_CLOSE + SEPARATOR + BUTTON ID_CHANNEL_SET_TOPIC + BUTTON ID_CHANNEL_MODE + BUTTON ID_CHANNEL_TALK + BUTTON ID_CHANNEL_PASSWORD + SEPARATOR + BUTTON ID_FILE_GETUSERINFO + SEPARATOR + BUTTON ID_FILE_SILENCE + BUTTON ID_FILE_UNSILENCE + SEPARATOR + BUTTON ID_BUTTON32802 + BUTTON ID_BUTTON32803 + SEPARATOR + BUTTON ID_FILE_OPEN + BUTTON ID_FILE_SAVE + SEPARATOR + BUTTON ID_EDIT_CUT + BUTTON ID_EDIT_COPY + BUTTON ID_EDIT_PASTE +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_MAINFRAME MENU PRELOAD DISCARDABLE +BEGIN + POPUP "&File" + BEGIN + MENUITEM "&Connect...", ID_FILE_CONNECT + MENUITEM "&Disconnect", ID_FILE_DISCONNECT + MENUITEM SEPARATOR + MENUITEM "&Enter...\tCtrl+N", ID_FILE_NEW + MENUITEM "&Open...\tCtrl+O", ID_FILE_OPEN + MENUITEM SEPARATOR + MENUITEM "&List Channels...", ID_FILE_LISTCHANNELS + MENUITEM "&Send Raw...", ID_FILE_SENDRAW + MENUITEM "&Get User Info...", ID_FILE_GETUSERINFO + MENUITEM "&Silence", ID_FILE_SILENCE + MENUITEM "&Unsilence", ID_FILE_UNSILENCE + MENUITEM SEPARATOR + MENUITEM "E&xit", ID_APP_EXIT + END + POPUP "&View" + BEGIN + MENUITEM "&Toolbar", ID_VIEW_TOOLBAR + MENUITEM "&Status Bar", ID_VIEW_STATUS_BAR + END + POPUP "&Help" + BEGIN + MENUITEM "&About chatty...", ID_APP_ABOUT + END +END + +IDR_CHATTYTYPE MENU PRELOAD DISCARDABLE +BEGIN + POPUP "&File" + BEGIN + MENUITEM "&Enter\tCtrl+N", ID_FILE_NEW + MENUITEM "&Open...\tCtrl+O", ID_FILE_OPEN + MENUITEM "&Leave", ID_FILE_CLOSE + MENUITEM "&Save\tCtrl+S", ID_FILE_SAVE + MENUITEM "Save &As...", ID_FILE_SAVE_AS + MENUITEM SEPARATOR + MENUITEM "E&xit", ID_APP_EXIT + END + POPUP "&Edit" + BEGIN + MENUITEM "&Undo\tCtrl+Z", ID_EDIT_UNDO + MENUITEM SEPARATOR + MENUITEM "Cu&t\tCtrl+X", ID_EDIT_CUT + MENUITEM "&Copy\tCtrl+C", ID_EDIT_COPY + MENUITEM "&Paste\tCtrl+V", ID_EDIT_PASTE + END + POPUP "&View" + BEGIN + MENUITEM "&Toolbar", ID_VIEW_TOOLBAR + MENUITEM "&Status Bar", ID_VIEW_STATUS_BAR + END + POPUP "&Window" + BEGIN + MENUITEM "&New Window", ID_WINDOW_NEW + MENUITEM "&Cascade", ID_WINDOW_CASCADE + MENUITEM "&Tile", ID_WINDOW_TILE_HORZ + MENUITEM "&Arrange Icons", ID_WINDOW_ARRANGE + END + POPUP "&Help" + BEGIN + MENUITEM "&About chatty...", ID_APP_ABOUT + END + POPUP "&Channel" + BEGIN + MENUITEM "Set &Topic", ID_CHANNEL_SET_TOPIC + MENUITEM "&Mode", ID_CHANNEL_MODE + MENUITEM "&Talk", ID_CHANNEL_TALK + MENUITEM "&Password", ID_CHANNEL_PASSWORD + MENUITEM "&Get Ban List", ID_CHANNEL_GETBANLIST + END + POPUP "User" + BEGIN + MENUITEM "Get Info", ID_USER_GETINFO + MENUITEM "Kick", ID_USER_KICK + MENUITEM "Ban", ID_USER_BAN + POPUP "Mode" + BEGIN + MENUITEM "Op", ID_USER_MODE_OP + MENUITEM "Voice", ID_USER_MODE_VOICE + MENUITEM "Normal", ID_USER_MODE_NORMAL + END + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Accelerator +// + +IDR_MAINFRAME ACCELERATORS PRELOAD MOVEABLE PURE +BEGIN + "N", ID_FILE_NEW, VIRTKEY, CONTROL + "O", ID_FILE_OPEN, VIRTKEY, CONTROL + "S", ID_FILE_SAVE, VIRTKEY, CONTROL + "Z", ID_EDIT_UNDO, VIRTKEY, CONTROL + "X", ID_EDIT_CUT, VIRTKEY, CONTROL + "C", ID_EDIT_COPY, VIRTKEY, CONTROL + "V", ID_EDIT_PASTE, VIRTKEY, CONTROL + VK_BACK, ID_EDIT_UNDO, VIRTKEY, ALT + VK_DELETE, ID_EDIT_CUT, VIRTKEY, SHIFT + VK_INSERT, ID_EDIT_COPY, VIRTKEY, CONTROL + VK_INSERT, ID_EDIT_PASTE, VIRTKEY, SHIFT + VK_F6, ID_NEXT_PANE, VIRTKEY + VK_F6, ID_PREV_PANE, VIRTKEY, SHIFT +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ABOUTBOX DIALOG DISCARDABLE 0, 0, 235, 55 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About chatty" +FONT 8, "MS Sans Serif" +BEGIN + ICON IDR_MAINFRAME,IDC_STATIC,11,17,20,20 + LTEXT "chatty Version 1.0",IDC_STATIC,40,10,119,8,SS_NOPREFIX + LTEXT "Copyright (C) 2000",IDC_STATIC,40,25,119,8 + DEFPUSHBUTTON "OK",IDOK,178,7,50,14,WS_GROUP +END + +IDD_CHATTY_FORM DIALOG DISCARDABLE 0, 0, 427, 304 +STYLE WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + LISTBOX IDC_LIST,7,7,343,173,NOT LBS_NOTIFY | + LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | + WS_TABSTOP + EDITTEXT IDC_EDIT,7,185,343,13,ES_AUTOHSCROLL | ES_WANTRETURN + LISTBOX IDC_USERS,352,20,68,146,LBS_SORT | LBS_NOINTEGRALHEIGHT | + LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP + LISTBOX IDC_CALLBACKS,7,202,415,95,NOT LBS_NOTIFY | + LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | + WS_HSCROLL | WS_TABSTOP + LTEXT "Num Users:",IDC_STATIC,353,7,38,8 + EDITTEXT IDC_NUM_USERS,394,7,26,12,ES_AUTOHSCROLL | ES_READONLY | + ES_NUMBER | NOT WS_BORDER + CONTROL "Hide Stuff",IDC_HIDE,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,353,170,47,10 + DEFPUSHBUTTON "Send",IDC_SENDBUTT,353,184,50,14 +END + +IDR_MAINFRAME DIALOG DISCARDABLE 0, 0, 330, 16 +STYLE WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "TODO: layout dialog bar ",IDC_STATIC,20,8,300,8 +END + +IDD_CONNECT DIALOG DISCARDABLE 0, 0, 187, 55 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Connect" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,130,5,50,14 + PUSHBUTTON "Cancel",IDCANCEL,130,25,50,14 + LTEXT "Nick:",IDC_STATIC,5,9,18,8 + LTEXT "Address:",IDC_STATIC,5,24,28,8 + LTEXT "Port:",IDC_STATIC,5,39,16,8 + EDITTEXT IDC_NICK,45,5,80,12,ES_AUTOHSCROLL + EDITTEXT IDC_ADDRESS,45,20,80,12,ES_AUTOHSCROLL + EDITTEXT IDC_PORT,45,35,80,12,ES_AUTOHSCROLL +END + +IDD_ENTER DIALOG DISCARDABLE 0, 0, 186, 66 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Enter" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,130,5,50,14 + PUSHBUTTON "Cancel",IDCANCEL,129,24,50,14 + LTEXT "Channel:",IDC_STATIC,5,9,29,8 + EDITTEXT IDC_CHANNEL,45,5,80,12,ES_AUTOHSCROLL + LTEXT "Password:",IDC_STATIC,5,30,34,8 + EDITTEXT IDC_PASSWORD,45,26,80,12,ES_PASSWORD | ES_AUTOHSCROLL + PUSHBUTTON "istanbul",IDC_QUICK1,7,45,35,14 + PUSHBUTTON "montreal",IDC_QUICK2,52,45,35,14 + PUSHBUTTON "pants-test",IDC_QUICK3,97,45,35,14 + PUSHBUTTON "zurna",IDC_QUICK4,142,45,35,14 +END + +IDD_SET_TOPIC DIALOG DISCARDABLE 0, 0, 186, 46 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Set Topic" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,129,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,129,24,50,14 + LTEXT "Topic:",IDC_STATIC,5,19,21,8 + EDITTEXT IDC_TOPIC,30,16,90,12,ES_AUTOHSCROLL +END + +IDD_CHANNEL_MODE DIALOG DISCARDABLE 0, 0, 128, 114 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Channel Mode" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,71,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,71,24,50,14 + LTEXT "Limit:",IDC_STATIC,7,97,17,8 + CONTROL "Invite Only",IDC_INVITE_ONLY,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,7,7,49,10 + CONTROL "Private",IDC_PRIVATE,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,7,22,38,10 + CONTROL "Secret",IDC_SECRET,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,7,37,37,10 + CONTROL "Moderated",IDC_MODERATED,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,7,52,50,10 + CONTROL "No External Messages",IDC_NO_EXTERNAL_MESSAGES,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,7,67,87,10 + CONTROL "Only Ops Change Topic",IDC_ONLY_OPS_CHANGE_TOPIC,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,7,82,91,10 + EDITTEXT IDC_LIMIT,30,96,26,12,ES_AUTOHSCROLL +END + +IDD_TALK DIALOG DISCARDABLE 0, 0, 186, 47 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Talk" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,129,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,129,24,50,14 + LTEXT "Talk:",IDC_STATIC,7,10,17,8 + EDITTEXT IDC_MESSAGE,32,7,90,12,ES_AUTOHSCROLL + CONTROL "Normal",IDC_NORMAL,"Button",BS_AUTORADIOBUTTON | + WS_GROUP,7,30,38,10 + CONTROL "Action",IDC_ACTION,"Button",BS_AUTORADIOBUTTON,47,30,36, + 10 + CONTROL "Notice",IDC_NOTICE,"Button",BS_AUTORADIOBUTTON,85,30,37, + 10 +END + +IDD_CHANNEL_LIST DIALOG DISCARDABLE 0, 0, 288, 257 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Channel List" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,231,7,50,14 + LISTBOX IDC_LIST,7,24,274,226,LBS_SORT | LBS_NOINTEGRALHEIGHT | + WS_VSCROLL | WS_TABSTOP + DEFPUSHBUTTON "Channels",ID_CHANNELS,7,7,41,14 + EDITTEXT IDC_FILTER,107,7,96,12,ES_AUTOHSCROLL + EDITTEXT IDC_NUM,209,7,17,12,ES_AUTOHSCROLL | ES_READONLY | NOT + WS_BORDER + DEFPUSHBUTTON "Users",ID_USERS,57,7,44,14 +END + +IDD_SEND_RAW DIALOG DISCARDABLE 0, 0, 186, 46 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Send Raw" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,129,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,129,24,50,14 + LTEXT "Raw:",IDC_STATIC,7,19,18,8 + EDITTEXT IDC_RAW,30,16,90,12,ES_AUTOHSCROLL +END + +IDD_SET_PASSWORD DIALOG DISCARDABLE 0, 0, 220, 47 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Set Password" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,163,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,163,24,50,14 + LTEXT "Password:",IDC_STATIC,7,20,34,8 + EDITTEXT IDC_PASSWORD,46,17,76,12,ES_AUTOHSCROLL + CONTROL "Enable",IDC_ENABLE,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,125,18,38,10 +END + +IDD_GET_USER_INFO DIALOG DISCARDABLE 0, 0, 186, 202 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Get User Info" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,129,23,50,14 + LTEXT "Nick:",IDC_STATIC,7,7,18,8 + EDITTEXT IDC_NICK,42,7,80,12,ES_AUTOHSCROLL + LTEXT "Address:",IDC_STATIC,7,41,28,8 + EDITTEXT IDC_ADDRESS,42,39,80,12,ES_AUTOHSCROLL | ES_READONLY + LISTBOX IDC_CHANNELS,7,85,172,110,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + LTEXT "Channels:",IDC_STATIC,7,73,32,8 + DEFPUSHBUTTON "Get Info",ID_GET_INFO,129,7,50,14 + LTEXT "Name:",IDC_STATIC,7,23,22,8 + EDITTEXT IDC_NAME,42,23,80,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "User:",IDC_STATIC,7,57,18,8 + EDITTEXT IDC_USER,42,54,80,12,ES_AUTOHSCROLL | ES_READONLY +END + +IDD_KICK_REASON DIALOG DISCARDABLE 0, 0, 186, 46 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Kick Reason" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,129,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,129,24,50,14 + LTEXT "Reason:",IDC_STATIC,7,18,28,8 + EDITTEXT IDC_REASON,37,16,83,12,ES_AUTOHSCROLL +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "chatty MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "chatty\0" + VALUE "LegalCopyright", "Copyright (C) 2000\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "chatty.EXE\0" + VALUE "ProductName", "chatty Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_ABOUTBOX, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 228 + TOPMARGIN, 7 + BOTTOMMARGIN, 48 + END + + IDD_CHATTY_FORM, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 420 + TOPMARGIN, 7 + BOTTOMMARGIN, 297 + END + + IDD_CONNECT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 180 + TOPMARGIN, 7 + BOTTOMMARGIN, 48 + END + + IDD_ENTER, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 59 + END + + IDD_SET_TOPIC, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 39 + END + + IDD_CHANNEL_MODE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 121 + TOPMARGIN, 7 + BOTTOMMARGIN, 107 + END + + IDD_TALK, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 40 + END + + IDD_CHANNEL_LIST, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 281 + TOPMARGIN, 7 + BOTTOMMARGIN, 250 + END + + IDD_SEND_RAW, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 39 + END + + IDD_SET_PASSWORD, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 213 + TOPMARGIN, 7 + BOTTOMMARGIN, 40 + END + + IDD_GET_USER_INFO, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 195 + END + + IDD_KICK_REASON, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 39 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDP_SOCKETS_INIT_FAILED "Windows sockets initialization failed." +END + +STRINGTABLE PRELOAD DISCARDABLE +BEGIN + IDR_MAINFRAME "chatty" + IDR_CHATTYTYPE "\nChatty\nChatty\n\n\nChatty.Document\nChatty Document" +END + +STRINGTABLE PRELOAD DISCARDABLE +BEGIN + AFX_IDS_APP_TITLE "chatty" + AFX_IDS_IDLEMESSAGE "Ready" +END + +STRINGTABLE DISCARDABLE +BEGIN + ID_INDICATOR_EXT "EXT" + ID_INDICATOR_CAPS "CAP" + ID_INDICATOR_NUM "NUM" + ID_INDICATOR_SCRL "SCRL" + ID_INDICATOR_OVR "OVR" + ID_INDICATOR_REC "REC" +END + +STRINGTABLE DISCARDABLE +BEGIN + ID_FILE_NEW "Create a new document\nNew" + ID_FILE_OPEN "Open an existing document\nOpen" + ID_FILE_CLOSE "Close the active document\nClose" + ID_FILE_SAVE "Save the active document\nSave" + ID_FILE_SAVE_AS "Save the active document with a new name\nSave As" +END + +STRINGTABLE DISCARDABLE +BEGIN + ID_APP_EXIT "Quit the application; prompts to save documents\nExit" +END + +STRINGTABLE DISCARDABLE +BEGIN + ID_FILE_MRU_FILE1 "Open this document" + ID_FILE_MRU_FILE2 "Open this document" + ID_FILE_MRU_FILE3 "Open this document" + ID_FILE_MRU_FILE4 "Open this document" + ID_FILE_MRU_FILE5 "Open this document" + ID_FILE_MRU_FILE6 "Open this document" + ID_FILE_MRU_FILE7 "Open this document" + ID_FILE_MRU_FILE8 "Open this document" + ID_FILE_MRU_FILE9 "Open this document" + ID_FILE_MRU_FILE10 "Open this document" + ID_FILE_MRU_FILE11 "Open this document" + ID_FILE_MRU_FILE12 "Open this document" + ID_FILE_MRU_FILE13 "Open this document" + ID_FILE_MRU_FILE14 "Open this document" + ID_FILE_MRU_FILE15 "Open this document" + ID_FILE_MRU_FILE16 "Open this document" +END + +STRINGTABLE DISCARDABLE +BEGIN + ID_NEXT_PANE "Switch to the next window pane\nNext Pane" + ID_PREV_PANE "Switch back to the previous window pane\nPrevious Pane" +END + +STRINGTABLE DISCARDABLE +BEGIN + ID_WINDOW_NEW "Open another window for the active document\nNew Window" + ID_WINDOW_ARRANGE "Arrange icons at the bottom of the window\nArrange Icons" + ID_WINDOW_CASCADE "Arrange windows so they overlap\nCascade Windows" + ID_WINDOW_TILE_HORZ "Arrange windows as non-overlapping tiles\nTile Windows" + ID_WINDOW_TILE_VERT "Arrange windows as non-overlapping tiles\nTile Windows" + ID_WINDOW_SPLIT "Split the active window into panes\nSplit" +END + +STRINGTABLE DISCARDABLE +BEGIN + ID_EDIT_CLEAR "Erase the selection\nErase" + ID_EDIT_CLEAR_ALL "Erase everything\nErase All" + ID_EDIT_COPY "Copy the selection and put it on the Clipboard\nCopy" + ID_EDIT_CUT "Cut the selection and put it on the Clipboard\nCut" + ID_EDIT_FIND "Find the specified text\nFind" + ID_EDIT_PASTE "Insert Clipboard contents\nPaste" + ID_EDIT_REPEAT "Repeat the last action\nRepeat" + ID_EDIT_REPLACE "Replace specific text with different text\nReplace" + ID_EDIT_SELECT_ALL "Select the entire document\nSelect All" + ID_EDIT_UNDO "Undo the last action\nUndo" + ID_EDIT_REDO "Redo the previously undone action\nRedo" +END + +STRINGTABLE DISCARDABLE +BEGIN + ID_VIEW_TOOLBAR "Show or hide the toolbar\nToggle ToolBar" + ID_VIEW_STATUS_BAR "Show or hide the status bar\nToggle StatusBar" +END + +STRINGTABLE DISCARDABLE +BEGIN + AFX_IDS_SCSIZE "Change the window size" + AFX_IDS_SCMOVE "Change the window position" + AFX_IDS_SCMINIMIZE "Reduce the window to an icon" + AFX_IDS_SCMAXIMIZE "Enlarge the window to full size" + AFX_IDS_SCNEXTWINDOW "Switch to the next document window" + AFX_IDS_SCPREVWINDOW "Switch to the previous document window" + AFX_IDS_SCCLOSE "Close the active window and prompts to save the documents" +END + +STRINGTABLE DISCARDABLE +BEGIN + AFX_IDS_SCRESTORE "Restore the window to normal size" + AFX_IDS_SCTASKLIST "Activate Task List" + AFX_IDS_MDICHILD "Activate this window" +END + +STRINGTABLE DISCARDABLE +BEGIN + ID_FILE_CONNECT "Connect to the chat server\nConnect" + ID_FILE_DISCONNECT "Disconnect from the chat server\nDisconnect" + ID_CHANNEL_SET_TOPIC "Set the channel topic\nTopic" + ID_CHANNEL_MODE "View/Set the channel mode\nMode" + ID_CHANNEL_TALK "Send a message\nTalk" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\chatty.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/gamespy/Chat/chatty/chattyDoc.cpp b/code/gamespy/Chat/chatty/chattyDoc.cpp new file mode 100644 index 00000000..e72dfdaa --- /dev/null +++ b/code/gamespy/Chat/chatty/chattyDoc.cpp @@ -0,0 +1,586 @@ +// chattyDoc.cpp : implementation of the CChattyDoc class +// + +#include "stdafx.h" +#include "chatty.h" + +#include "chattyDoc.h" +#include "EnterDlg.h" +#include "SetTopicDlg.h" +#include "ChannelModeDlg.h" +#include "TalkDlg.h" +#include "SetPasswordDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CChattyDoc + +IMPLEMENT_DYNCREATE(CChattyDoc, CDocument) + +BEGIN_MESSAGE_MAP(CChattyDoc, CDocument) + //{{AFX_MSG_MAP(CChattyDoc) + ON_COMMAND(ID_CHANNEL_SET_TOPIC, OnChannelSetTopic) + ON_COMMAND(ID_CHANNEL_MODE, OnChannelMode) + ON_COMMAND(ID_CHANNEL_TALK, OnChannelTalk) + ON_COMMAND(ID_CHANNEL_PASSWORD, OnChannelPassword) + ON_COMMAND(ID_CHANNEL_GETBANLIST, OnChannelGetbanlist) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CChattyDoc construction/destruction + +CChattyDoc::CChattyDoc() +{ + m_inChannel = FALSE; + m_hide = FALSE; +} + +CChattyDoc::~CChattyDoc() +{ +} + +/********************** +** CHANNEL CALLBACKS ** +**********************/ +#if 0 +CChattyDoc * pDoc; // Get rid of VA complaints. +#endif +#define DOC CChattyDoc * pDoc;\ + pDoc = (CChattyDoc *)param;\ + ASSERT(pDoc != NULL);\ + if(!pDoc->m_inChannel) { OutputDebugString("Not in channel anymore!\n"); return; } +#define ADD(s) *str += " | ";\ + *str += #s;\ + *str += "--";\ + *str += s; +char __i__[32]; +#define ADD_INT(i) *str += " | ";\ + *str += #i;\ + *str += "--";\ + itoa(i, __i__, 10);\ + *str += __i__; +void ChannelMessage(CHAT chat, const char * channel, const char * user, const char * message, int type, void * param) +{ + OutputDebugString("ChannelMessage called\n"); + DOC; + CString * str; + + str = new CString("ChannelMessage"); + ADD(channel); + ADD(user); + ADD(message); + ADD_INT(type); + pDoc->m_addCallbacks.AddTail(str); // MORE DESCRIPTIVE (SHOW PARAMS) + + str = new CString; + + if(type == CHAT_MESSAGE) + { + *str = user; + *str += ": "; + *str += message; + } + else if(type == CHAT_ACTION) + { + *str += user; + *str += " "; + *str += message; + } + else + { + *str += user; + *str += "* "; + *str += message; + } + + pDoc->m_newStuff.AddTail(str); + + GSI_UNUSED(chat); +} + +void Kicked(CHAT chat, const char * channel, const char * user, const char * reason, void * param) +{ + OutputDebugString("Kicked called\n"); + DOC; + + // Not in the channel anymore. + ////////////////////////////// + pDoc->m_inChannel = FALSE; + + POSITION pos = pDoc->GetFirstViewPosition(); + pDoc->GetNextView(pos)->GetParentFrame()->DestroyWindow(); + + GSI_UNUSED(reason); + GSI_UNUSED(user); + GSI_UNUSED(channel); + GSI_UNUSED(chat); +} + +void UserJoined(CHAT chat, const char * channel, const char * user, int mode, void * param) +{ + OutputDebugString("UserJoined called\n"); + DOC; + CString * str; + + if(!pDoc->m_hide) + { + str = new CString("UserJoined"); + ADD(channel); + ADD(user); + ADD_INT(mode); + pDoc->m_addCallbacks.AddTail(str); + } + + CModUsers * mod = new CModUsers; + mod->type = NEW; + mod->nick = user; + mod->mode = mode; + pDoc->m_modUsers.AddTail(mod); + + GSI_UNUSED(chat); +} + +void UserParted(CHAT chat, const char * channel, const char * user, int why, const char * reason, const char * kicker, void * param) +{ + OutputDebugString("UserParted called\n"); + DOC; + CString * str; + + if(!pDoc->m_hide) + { + str = new CString("UserParted"); + ADD(channel); + ADD(user); + ADD_INT(why); + ADD(reason); + ADD(kicker); + pDoc->m_addCallbacks.AddTail(str); + } + + CModUsers * mod = new CModUsers; + mod->type = DEL; + mod->nick = user; + pDoc->m_modUsers.AddTail(mod); + + GSI_UNUSED(chat); +} + +void UserChangedNick(CHAT chat, const char * channel, const char * oldNick, const char * newNick, void * param) +{ + OutputDebugString("UserChangedNick called\n"); + DOC; + CString * str; + + if(!pDoc->m_hide) + { + str = new CString("UserChangedNick"); + ADD(channel); + ADD(oldNick); + ADD(newNick); + pDoc->m_addCallbacks.AddTail(str); + } + + CModUsers * mod = new CModUsers; + mod->type = RENAME; + mod->nick = oldNick; + mod->newNick = newNick; + pDoc->m_modUsers.AddTail(mod); + + GSI_UNUSED(chat); +} + +void UserModeChanged(CHAT chat, const char * channel, const char * user, int mode, void * param) +{ + OutputDebugString("UserModeChanged called\n"); + DOC; + CString *str; + + str = new CString("UserModeChanged"); + ADD(channel); + ADD(user); + ADD_INT(mode); + pDoc->m_addCallbacks.AddTail(str); + + CModUsers * mod = new CModUsers; + mod->type = MODE; + mod->nick = user; + mod->mode = mode; + pDoc->m_modUsers.AddTail(mod); + + GSI_UNUSED(chat); +} + +void TopicChanged(CHAT chat, const char * channel, const char * topic, void * param) +{ + OutputDebugString("TopicChanged called\n"); + DOC; + CString * str; + + str = new CString("TopicChanged"); + ADD(channel); + ADD(topic); + pDoc->m_addCallbacks.AddTail(str); + + CString title = channel; + title += " - "; + title += topic; + pDoc->SetTitle(title); + + pDoc->m_topic = topic; + + GSI_UNUSED(chat); +} + +void ChannelModeChanged(CHAT chat, const char * channel, CHATChannelMode * mode, void * param) +{ + OutputDebugString("ChannelModeChanged called\n"); + DOC; + CString * str; + + str = new CString("ChannelModeChanged"); + ADD(channel); + ADD_INT(mode->InviteOnly); + ADD_INT(mode->Limit); + ADD_INT(mode->Moderated); + ADD_INT(mode->NoExternalMessages); + ADD_INT(mode->OnlyOpsChangeTopic); + ADD_INT(mode->Private); + ADD_INT(mode->Secret); + pDoc->m_addCallbacks.AddTail(str); + + str = new CString("Channel Mode Changed"); + pDoc->m_newStuff.AddTail(str); + + GSI_UNUSED(chat); +} + +void UserListUpdated(CHAT chat, const char * channel, void * param) +{ + OutputDebugString("UserListUpdated called\n"); + DOC; + CString * str; + + if(!pDoc->m_hide) + { + str = new CString("UserListUpdated"); + ADD(channel); + pDoc->m_addCallbacks.AddTail(str); + } + + GSI_UNUSED(chat); +} + +void EnumUsersCallback(CHAT chat, CHATBool success, const char * channel, int numUsers, const char ** users, int * modes, void * param) +{ + OutputDebugString("EnumUsersCallback called\n"); + CChattyDoc * pDoc = (CChattyDoc *)param; + + if(success) + { + pDoc->m_numUsers = numUsers; + + int i; + for(i = 0 ; i < numUsers ; i++) + { + CModUsers * mod = new CModUsers; + ASSERT(mod != NULL); + mod->type = NEW; + mod->nick = users[i]; + mod->mode = modes[i]; + pDoc->m_modUsers.AddTail(mod); + } + } + + GSI_UNUSED(chat); + GSI_UNUSED(channel); +} + +void EnterChannelCallback(CHAT chat, CHATBool success, CHATEnterResult result, const char * channel, void * param) +{ + OutputDebugString("EnterChannelCallback called\n"); + CChattyDoc * pDoc = (CChattyDoc *)param; + + // Check for success or failure. + //////////////////////////////// + if(success) + { + pDoc->m_inChannel = TRUE; + + // Get the inital list of users. + //////////////////////////////// + DWORD before = GetTickCount(); + chatEnumUsers(theApp.m_chat, channel, EnumUsersCallback, pDoc, CHATTrue); + DWORD after = GetTickCount(); + char buffer[128]; + sprintf(buffer, "%dms to enum %d users in %s\n", after - before, pDoc->m_numUsers, pDoc->m_channelName); + OutputDebugString(buffer); + } + + GSI_UNUSED(chat); + GSI_UNUSED(result); +} + +BOOL CChattyDoc::OnNewDocument() +{ + if (!CDocument::OnNewDocument()) + return FALSE; + + // Check for no connection. + /////////////////////////// + if(theApp.m_chat == NULL) + return FALSE; + + // Set the dialog defaults. + /////////////////////////// + CEnterDlg dlg; + //dlg.m_channel = "#test"; + //dlg.m_channel = "#montreal"; + dlg.m_channel = "#istanbul"; + dlg.m_password = ""; + + // Go modal. + //////////// + int rcode = dlg.DoModal(); + + // Check for a cancel. + ////////////////////// + if(rcode != IDOK) + return FALSE; + + // Setup the callbacks. + /////////////////////// + chatChannelCallbacks callbacks; + memset(&callbacks, 0, sizeof(chatChannelCallbacks)); + callbacks.channelMessage = ChannelMessage; + callbacks.channelModeChanged = ChannelModeChanged; + callbacks.kicked = Kicked; + callbacks.topicChanged = TopicChanged; + callbacks.userParted = UserParted; + callbacks.userJoined = UserJoined; + callbacks.userListUpdated = UserListUpdated; + callbacks.userModeChanged = UserModeChanged; + callbacks.userChangedNick = UserChangedNick; + callbacks.param = this; + + // Join the group. + ////////////////// + if(dlg.m_quickChannel != "") + m_channelName = dlg.m_quickChannel; + else + m_channelName = dlg.m_channel; + chatEnterChannel(theApp.m_chat, m_channelName, dlg.m_password, &callbacks, EnterChannelCallback, this, CHATTrue); + + // Check for failure. + ///////////////////// + if(!m_inChannel) + return FALSE; + + // Set the name. + //////////////// + SetTitle(m_channelName); + + return TRUE; +} + + + +///////////////////////////////////////////////////////////////////////////// +// CChattyDoc serialization + +void CChattyDoc::Serialize(CArchive& ar) +{ + if (ar.IsStoring()) + { + // TODO: add storing code here + } + else + { + // TODO: add loading code here + } +} + +///////////////////////////////////////////////////////////////////////////// +// CChattyDoc diagnostics + +#ifdef _DEBUG +void CChattyDoc::AssertValid() const +{ + CDocument::AssertValid(); +} + +void CChattyDoc::Dump(CDumpContext& dc) const +{ + CDocument::Dump(dc); +} +#endif //_DEBUG + +///////////////////////////////////////////////////////////////////////////// +// CChattyDoc commands + +void CChattyDoc::OnCloseDocument() +{ + // Did we ever get in the channel? + ////////////////////////////////// + if(m_inChannel) + { + if(theApp.m_chat != NULL) + { + // Leave the channel. + ///////////////////// + chatLeaveChannel(theApp.m_chat, m_channelName, NULL); + + OutputDebugString("LEFT channel\n"); + } + + m_inChannel = FALSE; + } + + CDocument::OnCloseDocument(); +} + +void CChattyDoc::OnChannelSetTopic() +{ + if(theApp.m_chat != NULL) + { + CSetTopicDlg dlg; + dlg.m_topic = m_topic; + + if(dlg.DoModal() == IDOK) + { + chatSetChannelTopic(theApp.m_chat, m_channelName, dlg.m_topic); + } + } +} + +void GetChannelModeCallback(CHAT chat, CHATBool success, const char * channel, CHATChannelMode * mode, void * param) +{ + CHATChannelMode * modeParam = (CHATChannelMode *)param; + *modeParam = *mode; + + GSI_UNUSED(chat); + GSI_UNUSED(channel); + GSI_UNUSED(success); +} + +void CChattyDoc::OnChannelMode() +{ + if(theApp.m_chat != NULL) + { + CHATChannelMode mode; + + // Get the channel mode. + //////////////////////// + chatGetChannelMode(theApp.m_chat, m_channelName, GetChannelModeCallback, &mode, CHATTrue); + + // Setup the dlg. + ///////////////// + CChannelModeDlg dlg; + dlg.m_inviteOnly = mode.InviteOnly; + dlg.m_private = mode.Private; + dlg.m_secret = mode.Secret; + dlg.m_moderated = mode.Moderated; + dlg.m_noExternalMessages = mode.NoExternalMessages; + dlg.m_onlyOpsChangeTopic = mode.OnlyOpsChangeTopic; + dlg.m_limit = mode.Limit; + + // Show the dlg. + //////////////// + if(dlg.DoModal() == IDOK) + { + // Copy back the new mode. + ////////////////////////// + mode.InviteOnly = (CHATBool)dlg.m_inviteOnly; + mode.Private = (CHATBool)dlg.m_private; + mode.Secret = (CHATBool)dlg.m_secret; + mode.Moderated = (CHATBool)dlg.m_moderated; + mode.NoExternalMessages = (CHATBool)dlg.m_noExternalMessages; + mode.OnlyOpsChangeTopic = (CHATBool)dlg.m_onlyOpsChangeTopic; + mode.Limit = (CHATBool)dlg.m_limit; + + // Set the mode. + //////////////// + chatSetChannelMode(theApp.m_chat, m_channelName, &mode); + } + } +} + +void CChattyDoc::OnChannelTalk() +{ + if(theApp.m_chat != NULL) + { + CTalkDlg dlg; + dlg.m_type = 0; + if(dlg.DoModal() == IDOK) + { + int type; + if(dlg.m_type == 0) + type = CHAT_NORMAL; + else if(dlg.m_type == 1) + type = CHAT_ACTION; + else + type = CHAT_NOTICE; + chatSendChannelMessage(theApp.m_chat, m_channelName, dlg.m_message, type); + } + } +} + +void GetChannelPasswordCallback(CHAT chat, CHATBool success, const char * channel, CHATBool enabled, const char * password, void * param) +{ + CSetPasswordDlg * dlg = (CSetPasswordDlg *)param; + if(success) + { + dlg->m_password = password; + dlg->m_enable = enabled; + } + else + { + dlg->m_password = ""; + dlg->m_enable = FALSE; + } + + GSI_UNUSED(channel); + GSI_UNUSED(chat); +} + +void CChattyDoc::OnChannelPassword() +{ + if(theApp.m_chat != NULL) + { + CSetPasswordDlg dlg; + chatGetChannelPassword(theApp.m_chat, m_channelName, GetChannelPasswordCallback, &dlg, CHATTrue); + if(dlg.DoModal() == IDOK) + { + chatSetChannelPassword(theApp.m_chat, m_channelName, (CHATBool)dlg.m_enable, dlg.m_password); + } + } +} + +void EnumChannelBansCallback(CHAT chat, CHATBool success, const char * channel, int numBans, const char ** bans, void * param) +{ + DOC; + int i; + + pDoc->m_newStuff.AddTail(new CString("Bans:")); + for(i = 0 ; i < numBans ; i++) + { + pDoc->m_newStuff.AddTail(new CString(bans[i])); + } + + GSI_UNUSED(channel); + GSI_UNUSED(success); + GSI_UNUSED(chat); +} + +void CChattyDoc::OnChannelGetbanlist() +{ + if(theApp.m_chat != NULL) + { + chatEnumChannelBans(theApp.m_chat, m_channelName, EnumChannelBansCallback, this, CHATFalse); + } +} diff --git a/code/gamespy/Chat/chatty/chattyDoc.h b/code/gamespy/Chat/chatty/chattyDoc.h new file mode 100644 index 00000000..e2812702 --- /dev/null +++ b/code/gamespy/Chat/chatty/chattyDoc.h @@ -0,0 +1,80 @@ +// chattyDoc.h : interface of the CChattyDoc class +// +///////////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_CHATTYDOC_H__A9A7E462_6E3D_4AE0_9DF8_355AE2D28FBC__INCLUDED_) +#define AFX_CHATTYDOC_H__A9A7E462_6E3D_4AE0_9DF8_355AE2D28FBC__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define NEW 1 +#define DEL 2 +#define RENAME 3 +#define MODE 4 +struct CModUsers +{ + int type; + CString nick; + CString newNick; + int mode; +}; + +class CChattyDoc : public CDocument +{ +protected: // create from serialization only + CChattyDoc(); + DECLARE_DYNCREATE(CChattyDoc) + +// Attributes +public: + BOOL m_inChannel; + CString m_channelName; + CList m_newStuff; + CString m_topic; + CList m_addCallbacks; + CList m_modUsers; + BOOL m_hide; + int m_numUsers; + +// Operations +public: + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CChattyDoc) + public: + virtual BOOL OnNewDocument(); + virtual void Serialize(CArchive& ar); + virtual void OnCloseDocument(); + //}}AFX_VIRTUAL + +// Implementation +public: + virtual ~CChattyDoc(); +#ifdef _DEBUG + virtual void AssertValid() const; + virtual void Dump(CDumpContext& dc) const; +#endif + +protected: + +// Generated message map functions +protected: + //{{AFX_MSG(CChattyDoc) + afx_msg void OnChannelSetTopic(); + afx_msg void OnChannelMode(); + afx_msg void OnChannelTalk(); + afx_msg void OnChannelPassword(); + afx_msg void OnChannelGetbanlist(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_CHATTYDOC_H__A9A7E462_6E3D_4AE0_9DF8_355AE2D28FBC__INCLUDED_) diff --git a/code/gamespy/Chat/chatty/chattyView.cpp b/code/gamespy/Chat/chatty/chattyView.cpp new file mode 100644 index 00000000..b8499899 --- /dev/null +++ b/code/gamespy/Chat/chatty/chattyView.cpp @@ -0,0 +1,391 @@ +// chattyView.cpp : implementation of the CChattyView class +// + +#include "stdafx.h" +#include "chatty.h" + +#include "chattyDoc.h" +#include "chattyView.h" +#include "TalkDlg.h" +#include "KickReasonDlg.h" +#include "GetUserInfoDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CChattyView + +IMPLEMENT_DYNCREATE(CChattyView, CFormView) + +BEGIN_MESSAGE_MAP(CChattyView, CFormView) + //{{AFX_MSG_MAP(CChattyView) + ON_WM_CREATE() + ON_WM_TIMER() + ON_BN_CLICKED(IDC_HIDE, OnHide) + ON_LBN_SELCHANGE(IDC_CALLBACKS, OnSelchangeCallbacks) + ON_LBN_DBLCLK(IDC_USERS, OnDblclkUsers) + ON_COMMAND(ID_USER_BAN, OnUserBan) + ON_COMMAND(ID_USER_GETINFO, OnUserGetinfo) + ON_COMMAND(ID_USER_KICK, OnUserKick) + ON_COMMAND(ID_USER_MODE_OP, OnUserModeOp) + ON_COMMAND(ID_USER_MODE_VOICE, OnUserModeVoice) + ON_COMMAND(ID_USER_MODE_NORMAL, OnUserModeNormal) + ON_BN_CLICKED(IDC_SENDBUTT, OnSendbutt) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CChattyView construction/destruction + +CChattyView::CChattyView() + : CFormView(CChattyView::IDD) +{ + //{{AFX_DATA_INIT(CChattyView) + m_edit = _T(""); + m_hide = FALSE; + //}}AFX_DATA_INIT + // TODO: add construction code here +} + +CChattyView::~CChattyView() +{ +} + +void CChattyView::DoDataExchange(CDataExchange* pDX) +{ + CFormView::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CChattyView) + DDX_Control(pDX, IDC_NUM_USERS, m_numUsers); + DDX_Control(pDX, IDC_CALLBACKS, m_callbacks); + DDX_Control(pDX, IDC_USERS, m_users); + DDX_Control(pDX, IDC_LIST, m_list); + DDX_Text(pDX, IDC_EDIT, m_edit); + DDX_Check(pDX, IDC_HIDE, m_hide); + //}}AFX_DATA_MAP +} + +BOOL CChattyView::PreCreateWindow(CREATESTRUCT& cs) +{ + // TODO: Modify the Window class or styles here by modifying + // the CREATESTRUCT cs + + return CFormView::PreCreateWindow(cs); +} + +void CChattyView::OnInitialUpdate() +{ + CFormView::OnInitialUpdate(); + ResizeParentToFit(); + + SetTimer(100, 50, NULL); +} + +///////////////////////////////////////////////////////////////////////////// +// CChattyView diagnostics + +#ifdef _DEBUG +void CChattyView::AssertValid() const +{ + CFormView::AssertValid(); +} + +void CChattyView::Dump(CDumpContext& dc) const +{ + CFormView::Dump(dc); +} + +CChattyDoc* CChattyView::GetDocument() // non-debug version is inline +{ + ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CChattyDoc))); + return (CChattyDoc*)m_pDocument; +} +#endif //_DEBUG + +///////////////////////////////////////////////////////////////////////////// +// CChattyView message handlers + +int CChattyView::OnCreate(LPCREATESTRUCT lpCreateStruct) +{ + if (CFormView::OnCreate(lpCreateStruct) == -1) + return -1; + + return 0; +} + +void CChattyView::OnTimer(UINT nIDEvent) +{ + if(nIDEvent == 100) + { + CChattyDoc * pDoc = GetDocument(); + if(pDoc != NULL) + { + CString * str; + while(!pDoc->m_newStuff.IsEmpty()) + { + str = pDoc->m_newStuff.GetHead(); + pDoc->m_newStuff.RemoveHead(); + + m_list.InsertString(0, *str); + + delete str; + } + + while(!pDoc->m_addCallbacks.IsEmpty()) + { + str = pDoc->m_addCallbacks.GetHead(); + pDoc->m_addCallbacks.RemoveHead(); + + m_callbacks.InsertString(0, *str); + + delete str; + } + + CModUsers * mod; + CString name; + CString newName; + UINT index; + while(!pDoc->m_modUsers.IsEmpty()) + { + mod = pDoc->m_modUsers.GetHead(); + pDoc->m_modUsers.RemoveHead(); + + if(mod->type == NEW) + { + name = mod->nick; + if(mod->mode & CHAT_OP) + name.Insert(0, '@'); + else if(mod->mode & CHAT_VOICE) + name.Insert(0, '?'); + + m_users.AddString(name); + } + else if(mod->type == DEL) + { + name = mod->nick; + index = m_users.FindStringExact(-1, name); + if(index == LB_ERR) + index = m_users.FindStringExact(-1, '@' + name); + if(index == LB_ERR) + index = m_users.FindStringExact(-1, '?' + name); + if(index == LB_ERR) + ASSERT(0); + + m_users.DeleteString(index); + } + else if(mod->type == RENAME) + { + name = mod->nick; + newName = ""; + index = m_users.FindStringExact(-1, name); + if(index == LB_ERR) + { + newName = '@'; + index = m_users.FindStringExact(-1, '@' + name); + } + if(index == LB_ERR) + { + newName = '?'; + index = m_users.FindStringExact(-1, '?' + name); + } + if(index == LB_ERR) + ASSERT(0); + + m_users.DeleteString(index); + + newName += mod->newNick; + m_users.AddString(newName); + } + else if(mod->type == MODE) + { + name = mod->nick; + index = m_users.FindStringExact(-1, name); + if(index == LB_ERR) + index = m_users.FindStringExact(-1, '@' + name); + if(index == LB_ERR) + index = m_users.FindStringExact(-1, '?' + name); + if(index == LB_ERR) + ASSERT(0); + + m_users.DeleteString(index); + + if(mod->mode & CHAT_OP) + name.Insert(0, '@'); + else if(mod->mode & CHAT_VOICE) + name.Insert(0, '?'); + + m_users.AddString(name); + } + else + ASSERT(0); + + delete mod; + } + + char buf[16]; + itoa(m_users.GetCount(), buf, 10); + m_numUsers.SetWindowText(buf); + } + + SetTimer(100, 50, NULL); + } + + CFormView::OnTimer(nIDEvent); +} + +void CChattyView::OnHide() +{ + UpdateData(); + GetDocument()->m_hide = m_hide; +} + +void CChattyView::OnSelchangeCallbacks() +{ + UpdateData(); + m_callbacks.GetText(m_callbacks.GetCurSel(), m_edit); + UpdateData(FALSE); +} + +void CChattyView::OnDblclkUsers() +{ + if(theApp.m_chat != NULL) + { + int index = m_users.GetCurSel(); + if(index != LB_ERR) + { + CString user; + m_users.GetText(index, user); + if((user.Left(1) == "@") || (user.Left(1) == "?")) + user = user.Mid(1); + CTalkDlg dlg; + dlg.m_type = 0; + if(dlg.DoModal() == IDOK) + chatSendUserMessage(theApp.m_chat, user, dlg.m_message, dlg.m_type); + } + } +} + +void CChattyView::OnUserBan() +{ + if(theApp.m_chat != NULL) + { + int index = m_users.GetCurSel(); + if(index != LB_ERR) + { + CString user; + m_users.GetText(index, user); + if((user.Left(1) == "@") || (user.Left(1) == "?")) + user = user.Mid(1); + + chatBanUser(theApp.m_chat, GetDocument()->m_channelName, user); + } + } +} + +void CChattyView::OnUserGetinfo() +{ + if(theApp.m_chat != NULL) + { + int index = m_users.GetCurSel(); + if(index != LB_ERR) + { + CString user; + m_users.GetText(index, user); + if((user.Left(1) == "@") || (user.Left(1) == "?")) + user = user.Mid(1); + + CGetUserInfoDlg dlg; + dlg.m_user = user; + dlg.DoModal(); + } + } +} + +void CChattyView::OnUserKick() +{ + if(theApp.m_chat != NULL) + { + int index = m_users.GetCurSel(); + if(index != LB_ERR) + { + CString user; + m_users.GetText(index, user); + if((user.Left(1) == "@") || (user.Left(1) == "?")) + user = user.Mid(1); + + CKickReasonDlg dlg; + if(dlg.DoModal() == IDOK) + { + chatKickUser(theApp.m_chat, GetDocument()->m_channelName, user, dlg.m_reason); + } + } + } +} + +void CChattyView::OnUserModeOp() +{ + if(theApp.m_chat != NULL) + { + int index = m_users.GetCurSel(); + if(index != LB_ERR) + { + CString user; + m_users.GetText(index, user); + if((user.Left(1) == "@") || (user.Left(1) == "?")) + user = user.Mid(1); + + chatSetUserMode(theApp.m_chat, GetDocument()->m_channelName, user, CHAT_OP); + } + } +} + +void CChattyView::OnUserModeVoice() +{ + if(theApp.m_chat != NULL) + { + int index = m_users.GetCurSel(); + if(index != LB_ERR) + { + CString user; + m_users.GetText(index, user); + if((user.Left(1) == "@") || (user.Left(1) == "?")) + user = user.Mid(1); + + chatSetUserMode(theApp.m_chat, GetDocument()->m_channelName, user, CHAT_VOICE); + } + } +} + +void CChattyView::OnUserModeNormal() +{ + if(theApp.m_chat != NULL) + { + int index = m_users.GetCurSel(); + if(index != LB_ERR) + { + CString user; + m_users.GetText(index, user); + if((user.Left(1) == "@") || (user.Left(1) == "?")) + user = user.Mid(1); + + chatSetUserMode(theApp.m_chat, GetDocument()->m_channelName, user, CHAT_NORMAL); + } + } +} + + +void CChattyView::OnSendbutt() +{ + // TODO: Add your control notification handler code here + if(theApp.m_chat != NULL) + { + UpdateData(); + chatSendChannelMessage(theApp.m_chat, GetDocument()->m_channelName, (LPCSTR)m_edit,CHAT_MESSAGE); + m_edit.Empty(); + UpdateData(FALSE); + } + +} diff --git a/code/gamespy/Chat/chatty/chattyView.h b/code/gamespy/Chat/chatty/chattyView.h new file mode 100644 index 00000000..c7e53468 --- /dev/null +++ b/code/gamespy/Chat/chatty/chattyView.h @@ -0,0 +1,86 @@ +// chattyView.h : interface of the CChattyView class +// +///////////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_CHATTYVIEW_H__E1B25967_4DA7_4ABD_A404_9C60D59E1C60__INCLUDED_) +#define AFX_CHATTYVIEW_H__E1B25967_4DA7_4ABD_A404_9C60D59E1C60__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + + +class CChattyView : public CFormView +{ +protected: // create from serialization only + CChattyView(); + DECLARE_DYNCREATE(CChattyView) + +public: + //{{AFX_DATA(CChattyView) + enum { IDD = IDD_CHATTY_FORM }; + CEdit m_numUsers; + CListBox m_callbacks; + CListBox m_users; + CListBox m_list; + CString m_edit; + BOOL m_hide; + //}}AFX_DATA + +// Attributes +public: + CChattyDoc* GetDocument(); + +// Operations +public: + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CChattyView) + public: + virtual BOOL PreCreateWindow(CREATESTRUCT& cs); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual void OnInitialUpdate(); // called first time after construct + //}}AFX_VIRTUAL + +// Implementation +public: + virtual ~CChattyView(); +#ifdef _DEBUG + virtual void AssertValid() const; + virtual void Dump(CDumpContext& dc) const; +#endif + +protected: + +// Generated message map functions +protected: + //{{AFX_MSG(CChattyView) + afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnHide(); + afx_msg void OnSelchangeCallbacks(); + afx_msg void OnDblclkUsers(); + afx_msg void OnUserBan(); + afx_msg void OnUserGetinfo(); + afx_msg void OnUserKick(); + afx_msg void OnUserModeOp(); + afx_msg void OnUserModeVoice(); + afx_msg void OnUserModeNormal(); + afx_msg void OnSendbutt(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +#ifndef _DEBUG // debug version in chattyView.cpp +inline CChattyDoc* CChattyView::GetDocument() + { return (CChattyDoc*)m_pDocument; } +#endif + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_CHATTYVIEW_H__E1B25967_4DA7_4ABD_A404_9C60D59E1C60__INCLUDED_) diff --git a/code/gamespy/Chat/chatty/res/Toolbar.bmp b/code/gamespy/Chat/chatty/res/Toolbar.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2d40a91fcd5059b2356b94e665b6c421e03f50e4 GIT binary patch literal 2518 zcmd6pJ&xNj5QS%919ITP*GRd*rf?Z72)GJzg&cu}5E)1#zya*zsCQwXBIi({&V};c z8j%Kjy_t1^4FX82j#dWJWO zcD-JavtiWF#*rmF_@fE0=KoJ-6(A$PXRNT=5f<3hz?>b?lW&$)D(z}SOKT@~IN6F> zv$qx9~Tlysjw1_AoT!kqgX^-w-y{7o=>BCftXy#M+Kqb`uQ61rn z?&9JF3I6Z+>SjkzwYaw14?_q8X9EN}bUs0$MrW9r_%w5?TK1aas+M}v+^#f6eWred z5udwszu@Z0_xvVbZ#Klk;X64IX$ zQh&_>d>0o``We@^4%82N__|8JAu#-@asA*aO>)lX;EFj7V?GyrStzX;7H{f4~0+<@zceDtDopaEU)bIX23 zzl(O3>*st+HusZ$K;0=w)B$LW*>L|9rn>KEa5`6~nTM5c6T4`EJ-{h zl76%xB|b%}xB-E);plj1yY&M4HTaK%7OZaifN0=fzY5=D4-p?O*H8Cb=JQ1`;AkPW j0pVb>!3}jLb9s)T}@Kod(893@u8ajANzT`op9^o+)S?=nU(FD@%0s)Sg^oyC8{H z9myetc;MEP)59v(LMa~xK8Yu^jIR*H22uCFiq5%C{s7(PJi>o15i^bmX4(vPxWAio z9ryY#AU_jfnd047-@`)XzL?%iS$gQyFP{44kS9X)fN{{QoL~hO-&=q&20Zr*cxFAt PkaNE{wR~2C$NfnjhSXWT literal 0 HcmV?d00001 diff --git a/code/gamespy/Chat/chatty/res/chatty.rc2 b/code/gamespy/Chat/chatty/res/chatty.rc2 new file mode 100644 index 00000000..399a219f --- /dev/null +++ b/code/gamespy/Chat/chatty/res/chatty.rc2 @@ -0,0 +1,13 @@ +// +// CHATTY.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/Chat/chatty/res/chattyDoc.ico b/code/gamespy/Chat/chatty/res/chattyDoc.ico new file mode 100644 index 0000000000000000000000000000000000000000..2a1f1ae6ef15e51df8c39bc028bbfb2171822ba5 GIT binary patch literal 1078 zcmcJNF;c@Y5JlGsgIpoERJdY%i*S@2!JS&si6f-)RXoFGDAfg5;z_dQBoi_)1DpI^ z{oS?KlD%25H@>BZ{KJV|_dD9_G1MV<{5a&-}7^W%4AM)k- zx&P*V(j}a@*Y~UhksXTAK!NRyiYN-8NMyKz<)2v9@tUao7A!g+SzkAcsHvdq6!0vQ z#-rA6>0BAr)4*u6Y57EdkamnXf Uh-a7VEuQ2KJb_2>o71HC3-%7w@Bjb+ literal 0 HcmV?d00001 diff --git a/code/gamespy/Chat/chatty/resource.h b/code/gamespy/Chat/chatty/resource.h new file mode 100644 index 00000000..3b28d341 --- /dev/null +++ b/code/gamespy/Chat/chatty/resource.h @@ -0,0 +1,91 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by chatty.rc +// +#define ID_CHANNELS 2 +#define ID_GET_INFO 2 +#define ID_USERS 3 +#define IDD_ABOUTBOX 100 +#define IDD_CHATTY_FORM 101 +#define IDP_SOCKETS_INIT_FAILED 104 +#define IDR_MAINFRAME 128 +#define IDR_CHATTYTYPE 129 +#define IDD_SET_TOPIC 130 +#define IDD_CHANNEL_MODE 131 +#define IDD_CONNECT 132 +#define IDD_ENTER 133 +#define IDD_TALK 134 +#define IDD_CHANNEL_LIST 136 +#define IDD_SEND_RAW 137 +#define IDD_SET_PASSWORD 138 +#define IDD_GET_USER_INFO 139 +#define IDD_KICK_REASON 140 +#define IDC_LIST 1000 +#define IDC_EDIT 1001 +#define IDC_TOPIC 1002 +#define IDC_USERS 1002 +#define IDC_NICK 1003 +#define IDC_INVITE_ONLY 1003 +#define IDC_CALLBACKS 1003 +#define IDC_ADDRESS 1004 +#define IDC_PASSWORD 1004 +#define IDC_PRIVATE 1004 +#define IDC_PORT 1005 +#define IDC_CHANNEL 1005 +#define IDC_SECRET 1005 +#define IDC_USER 1005 +#define IDC_MODERATED 1006 +#define IDC_NO_EXTERNAL_MESSAGES 1007 +#define IDC_ONLY_OPS_CHANGE_TOPIC 1008 +#define IDC_LIMIT 1009 +#define IDC_MESSAGE 1010 +#define IDC_NORMAL 1012 +#define IDC_ACTION 1013 +#define IDC_NOTICE 1014 +#define IDC_NUM_USERS 1015 +#define IDC_HIDE 1016 +#define IDC_FILTER 1018 +#define IDC_RAW 1019 +#define IDC_ENABLE 1020 +#define IDC_NUM 1021 +#define IDC_NAME 1023 +#define IDC_CHANNELS 1025 +#define IDC_QUICK1 1026 +#define IDC_QUICK2 1027 +#define IDC_REASON 1027 +#define IDC_QUICK3 1028 +#define IDC_SENDBUTT 1028 +#define IDC_QUICK4 1029 +#define ID_FILE_CONNECT 32771 +#define ID_FILE_DISCONNECT 32772 +#define ID_CHANNEL_SET_TOPIC 32776 +#define ID_CHANNEL_MODE 32778 +#define ID_CHANNEL_TALK 32781 +#define ID_BUTTON32782 32782 +#define ID_FILE_LISTCHANNELS 32783 +#define ID_FILE_SENDRAW 32784 +#define ID_CHANNEL_PASSWORD 32786 +#define ID_FILE_GETUSERINFO 32789 +#define ID_USER_GETINFO 32790 +#define ID_USER_KICK 32792 +#define ID_USER_BAN 32793 +#define ID_USER_MODE_OP 32796 +#define ID_USER_MODE_VOICE 32798 +#define ID_USER_MODE_NORMAL 32800 +#define ID_CHANNEL_GETBANLIST 32801 +#define ID_BUTTON32802 32802 +#define ID_BUTTON32803 32803 +#define ID_FILE_SILENCE 32804 +#define ID_FILE_UNSILENCE 32805 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 141 +#define _APS_NEXT_COMMAND_VALUE 32806 +#define _APS_NEXT_CONTROL_VALUE 1029 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/gamespy/GP/changelog.txt b/code/gamespy/GP/changelog.txt new file mode 100644 index 00000000..d9553492 --- /dev/null +++ b/code/gamespy/GP/changelog.txt @@ -0,0 +1,182 @@ +Changelog for: GameSpy Presence & Messaging SDK +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 1.13.00 RMV RELEASE Released to Developer Site +12-12-2007 1.12.15 RMV OTHER Updated gptest app to include block list functions and cleaned up buddy messaging +12-05-2007 1.12.14 SN OTHER Added test for searching unique nickes across namespaces +11-27-2007 1.12.13 SAH CLEANUP Moved extern "c" block below includes to prevent linker errors +11-19-2007 1.12.12 SAH OTHER Marked gpFindPlayers and gpSetInvitableGames as DEPRECATED + SAH CLEANUP Removed gpFindPlayers and gpSetInvitableGames from MFC app +11-13-2007 1.12.11 SAH OTHER Modified GP MFC App to support Blocked Lists + SAH FIX Fixed bug where blocked list was deleted from disabling info cache +11-12-2007 1.12.10 MWJ FEATURE Added/updated Add/Remove Block List error messages +11-09-2007 1.12.09 SAH CLEANUP Switched to using a darray for NP Id lookup transactions + SAH FIX Fixed NP Block List Add to keep trying if NP returns busy +11-08-2007 1.12.08 SAH CLEANUP Removed gpGetBlockedList to replace with more consistent functions + SAH FEATURE Added gpGetNumBlocked, gpGetBlockedProfile, gpIsBlocked +11-05-2007 1.12.07 SAH FEATURE Added NP Block List mirroring for gpAddtoBlockedList + SAH FEATURE Added initial support for PS3 NP Block List Syncing +10-30-2007 1.12.06 SAH FEATURE Added BlockedList support and buddy/block list retrieval upon login + SAH FIX Fixed bug causing infoCache to not save when disconnect called before destroy + SAH FIX Fixed memory leak from infoCacheBuddyOnly not freeing up status data +10-23-2007 1.12.05 SAH FIX Fixed NP Sync so it only destroys NP if GP was the one that initialized it +10-11-2007 1.12.04 SAH FIX Fixed gpiPS3.c to use gpSendBuddyRequest to remove errors using GSI_UNICODE +09-14-2007 1.12.03 SAH FEATURE Added initial support for PS3 NP Buddy Syncing + SAH FIX Fixed two bugs in gpSearch.c - searches causing assert and missing unicode support +09-11-2007 1.12.02 DES FEATURE Added support for a search by uniquenick across multiple namespaces +08-23-2007 1.12.01 SAH OTHER Added gpRegisterCdKey to gptest MFC sample +08-06-2007 1.12.00 RMV RELEASE Released to Developer Site +07-27-2007 1.11.03 BED FEATURE Added gpRegisterCdKey +07-19-2007 1.11.02 SAH FIX Added explicit typecasts to remove compiler warnings, Fixed DS project +07-10-2007 1.11.01 RMV FIX Fixed gptest/gptestc Project files to get rid of Unicode warnings + RMV FIX Fixed memory allocation to prevent possible use of uninitialized pointer in gp.c +06-07-2007 1.11.00 SN RELEASE Releasing to developer site +06-07-2007 1.11.00 SN FEATURE Added newer style buddy status info + SN FEATURE Replaced old TCP peer to peer code with UDP Layer + SN FEATURE Added keys/values pair feature +05-22-2007 1.10.05 DES FEATURE Added gpGetInfoNoWait +05-17-2007 1.10.04 DES FIX Added a few explicit casts to avoid warnings +02-21-2007 1.10.03 SN FEATURE Added a version of get reverse buddies that allows a specified list of profiles to be passed in. +01-16-2007 1.10.02 DES FEATURE Added X360 support +12-21-2006 1.10.01 DES FEATURE Added initial support for quiet mode +12-15-2006 1.10.00 MJW RELEASE Released to Developer Site +12-13-2006 1.09.69 SN FIX Removed connection state change to allow the internal disconnect function to change the state +10-05-2006 1.09.68 SAH FIX Updated MacOSX Makefile +09-28-2006 1.09.67 SAH FIX Fixed PS3 project to work with PS3 095.00x SDK; changed included libaries in linker input. +08-07-2006 1.09.66 SAH FIX fixed gpGetBuddyStatus to set 'status' output to '\0' if it encounters NULL data +08-04-2006 1.09.65 SAH OTHER Changed ctime/gmtime calls to gsi time wrapper calls +08-03-2006 1.09.64 DDW FEATURE Added support for automatic login ticket renewal +08-02-2006 1.09.63 DDW OTHER Added productID to GUI for gptest sample +08-02-2006 1.09.62 SAH RELEASE Releasing to developer site +07-31-2006 1.09.62 SAH FIX Fixed PS3 project file - added post-build step to create *.SELF for execution +07-25-2006 1.09.61 SAH FIX Fixed NITRO project, include for crt0.o now above others so it add correct file +07-24-2006 1.09.60 SAH FIX Removed #ifdef _PS3 for socket calls (changed platformSocket.h to typecast calls) +07-06-2006 1.09.59 SAH FIX Fixed PSP project file to not explicitly include the PSP linker file +06-30-2006 1.09.58 SAH FIX Fixed NITRO project & linker command file (to work with CW 2.0/NitroSDK 3.1) + SAH FIX Fixed Linux makefile +06-27-2006 1.09.57 SAH FIX Removed unused variables in gpiInfo.c and gpiSearch.c +06-26-2006 1.09.56 BMS FEATURE Passwords no longer go over the wire in cleartext +05-31-2006 1.09.55 SAH RELEASE Releasing to developer site + SAH FIX Fixed Linux makefile +05-30-2006 1.09.54 SAH FIX Fixed PS3 project to work with newest PS3(084_001 SDK) release +05-25-2006 1.09.53 SAH FIX Added PS3 required typecasts + SAH FIX Changed PSP project warning levels + SAH FIX Fixed PS3 project to compile with 084_001 SDK +05-22-2006 1.09.52 SAH FIX Added GSI_UNUSED calls to get rid of codewarrior warnings +05-19-2006 1.09.51 SAH FIX Added gsTestMain.c to nitro CodeWarrior project +05-15-2006 1.09.50 SAH FIX Added "PS3 Release" configuration to project +05-08-2006 1.09.49 SAH FIX Changed some sprintf calls on empty string to strcpy +04-27-2006 1.09.48 BMS FEATURE Updates to Fedreg implementation: login and profile update also needed partner ID. +04-25-2006 1.09.47 SAH RELEASE Releasing to developer site +04-24-2006 1.09.47 SAH FIX Fixed Nitro project files to work on build machine +04-13-2006 1.09.46 SAH FIX Replaced all (rcode == SOCKET_ERROR) w/ (gsiSocketIsError(rcode)) +04-12-2006 1.09.45 BMS FEATURE Updates for Fedreg. Includes new partner ID parameter on gpInitialize. +02-27-2006 1.09.44 SN FIX Added check to determine if hard coded IP was used for connection manager +02-03-2006 1.09.43 SN FIX Added checks for character string limits within gpConnectNewUser +01-26-2006 1.09.42 SN FIX Added psp prodg solution and project to sgv +01-19-2006 1.09.42 RH FIX Changed gpSendBuddyUTM message type from char * to gsi_char *. +12-16-2005 1.09.41 SN OTHER Cleaned up projects to missing common code if any +12-12-2005 1.09.40 SN FIX Removed connection closed check, eliminating false timeouts +11-18-2005 1.09.40 SN FEATURE Added timeout errors to searches which are non-fatal. +11-17-2005 1.09.39 DES FIX Updated Nitro Makefile. + DES FIX Fixed Unicode incompatibility in gptestc. +11-14-2005 1.09.38 DES FIX Updated the OSX Makefile. + DES FEATURE Added support for GSI_DOMAIN_NAME +11-03-2005 1.09.37 BED FEATURE Added ps3 makefile. +09-21-2005 1.09.36 DES FEATURE Updated DS support + DES FEATURE Updated to use the common debug code + DES FIX All connection operation errors are now fatal errors + DES CLEANUP Cleaned up gptestc and added a firewall option +09-16-2005 1.09.35 BED FEATURE Added a callback for buddy revoke notifications. +08-19-2005 1.09.34 BED FIX Update linux makefile +08-11-2005 1.09.33 BED FEATURE Added support for Buddy UTMs (part one, text messages on separate channel) +07-28-2005 1.09.32 SN RELEASE Releasing to developer site. +07-27-2005 1.09.32 SN FEATURE Added support for new style responses for both gpdeleteprofile and buddy authorizations. +06-03-2005 1.09.31 SN RELEASE Releasing to developer site. +05-26-2005 1.09.31 SN FIX Fixed the setinfo function that sent passwords in clear text. Now passwords are encrypted for security purposes. +05-05-2005 1.09.30 BED FIX Updated projects to use new common folder +04-29-2005 1.09.29 SN OTHER Created a Visual Studio .NET project +04-28-2005 1.09.29 SN RELEASE Releasing to developer site. +04-27-2005 1.09.29 DES RELEASE Limited release to Nintendo DS developers. +04-27-2005 1.09.29 DES CLEANUP Extra printfs in gptestc. + DES FEATURE Always assume a firewall on the DS. +04-25-2005 1.09.28 DES FIX Cleaned up a Nitro warning. + DES CLEANUP General cleanup of gptestc. + DES CLEANUP Disable Win32 linker warning. +04-04-2005 1.09.27 SN RELEASE Releasing to developer site. +04-01-2005 1.09.27 DDW FIX Use gsi_time in transfer file structure to match nonport definition +03-30-2005 1.09.26 SN FIX Changed size of unique nick to match DB size plus null character +03-30-2005 1.09.25 SN FIX Fixed case where time was of type long instead of 32 bit int +03-28-2005 1.09.24 SN FIX Fixed bug with all transfers being deleted when deleting only those associated with a single peer +03-18-2005 1.09.24 DES FIX Fixed typo in parameter explanation. +03-14-2005 1.09.23 DES FEATURE Nintendo DS support +12-28-2004 1.09.22 SN FIX Added const qualifiers to function parameters not modified +09-24-2004 1.09.21 BED FIX SDK now allows you to delete buddies that it doesn't have a status for. +09-16-2004 1.09.20 SN RELEASE Releasing to developer site. +08-31-2004 1.09.20 SN FIX Added call to reset the GP internal connection structure fixing memory leaks +08-27-2004 1.09.19 DES CLEANUP Removed MacOS style includes + DES CLEANUP Removed headers already included in nonport.h + XGD bug in caching profile marketing info + DES CLEANUP Updated Win32 project configurations + DES CLEANUP Fixed warnings under OSX + DES CLEANUP Updated OSX Makefile +08-05-2004 1.09.18 SN RELEASE Releasing to developer site. +07-19-2004 1.09.18 SN FIX Updated code with explicit casts to remove implicit cast error + when compiling at highest level and warnings treated as errors. +06-25-2004 1.09.18 BED FEATURE Added location string to gpInvitePlayer +06-24-2004 1.09.17 BED FEATURE Now encrypting password and cdkey for gpNewUser +06-18-2004 1.09.16 BED RELEASE Releasing to developer site. +06-17-2004 1.09.16 DDW FEATURE Added login ticket support +06-16-2004 1.09.15 BED FEATURE Added PS2 Insock support +05-20-2004 1.09.14 BED RELEASE Releasing to developer site. +05-20-2004 1.09.14 BED FEATURE Added state GP_INFO_CACHING_BUDDY_ONLY. +04-05-2004 1.09.13 BED FIX Fixed small allocation bug in Unicode interface. +03-30-2004 1.09.12 BED FIX Removed misc compiler warnings for VC7 strict compiling. +01-10-2004 1.09.11 BED FIX Fixed typo'd length for email addresses in gpNewUser. +01-08-2004 1.09.10 BED FIX ProfileID was not initialized when receiving a buddy message in UNICODE mode. +01-03-2004 1.09.09 DES FIX Receive and send buffer sizes are now set on incoming Peer sockets. +11-10-2003 1.09.08 DES RELEASE Releasing to developer site. +11-07-2003 1.09.08 BED FIX Removed CodeWarrior strictest warnings. +11-07-2003 1.09.07 DES FIX Updated the linux and PS2 makefiles. +11-04-2003 1.09.06 DES FEATURE Added availability check code. +11-03-2003 1.09.05 BED FIX Converting email and passwords to Ascii instead of UTF8 now. + FIX Removed misc. strict warnings in sample. +10-29-2003 1.09.04 DES FEATURE Pass the gamename to the backend when connecting or searching. +10-21-2003 1.09.03 BED RELEASE Releasing to developer site. (UNIQUE NICK AND UNICODE SUPPORT) +10-21-2003 1.09.03 DES FEATURE Updated gptest to handle new connect methods. +10-17-2003 1.09.02 DES FIX Changed gptest to use the default GameSpy namespace. +10-16-2003 1.09.01 DES FIX The desirednick pased to gpSuggestUniqueNick now has a + length of GP_UNIQUENICK_LEN instead of GP_NICK_LEN. +09-22-2003 1.09.00 DES FEATURE Added support for unique nicks. + FEATURE Added support for associating a cdkey with a unique nick. + FEATURE Added a namespaceID parameter to gpInitialize for setting the current namespace. + FEATURE Added gpConnectPreAuthenticated for logging in from a partner system. + FIX Minor internal cleanup and fixes. +09-14-2003 1.08.17 DES FIX Removed Unicode defines for gptest debug project. +09-08-2003 1.08.16 BED FEATURE Added wrapper for UNICODE support. +07-24-2003 1.08.15 DES RELEASE Releasing to developer site. +07-24-2003 1.08.15 DES CLEANUP Removed unused gpiSendInfo(). + CLEANUP Fixed up NOFILE usage to prevent warnings. +07-18-2003 1.08.14 BED FEATURE Added CodeWarrior (PS2) sample project file. + BED CLEANUP General cleanup to remove CodeWarrior warnings. +07-17-2003 1.08.13 DES CLEANUP Cleaned up the PS2 Makefile, it now uses Makefile.commmon. +07-16-2003 1.08.12 BED FEATURE Added ProDG sample project files. +07-14-2003 1.08.11 DES FIX Changed check for __mips64 to check for _PS2. +07-10-2003 1.08.10 BED CLEANUP Changed GP to use GSI_UNUSED for silencing unused variable warnings. + CLEANUP Added newline to end of gpiUtility.c to silence compiler warning. +05-09-2003 1.08.09 DES CLEANUP Removed Dreamcast support. + FIX Metrowerks for Win32 is no longer falsely identified as MacOS. +04-08-2003 1.08.08 JED FIX Cleanup up code to remove multiple DevStudio level4 compiler warnings +03-26-2003 1.08.07 DES FIX gpiDisconnect now checks if sockets are valid before attempting to close them. +03-20-2003 1.08.06 DES FEATURE The productID is now reported to the backend on connect. +03-03-2003 1.08.05 DES CLEANUP General cleanup to remove warnings. +12-19-2002 1.08.04 DES RELEASE Releasing to developer site. +12-19-2002 1.08.04 DES CLEANUP Removed assert.h includes. +12-16-2002 1.08.03 DES FIX Set listen call to use SOMAXCONN for the backlog paramter. + CLEANUP Removed call to GOAClearSocketError. +12-13-2002 1.08.02 DES FEATURE Added PS2 eenet stack support. + CLEANUP Cleaned up code to remove PS2 compiler warnings. +12-11-2002 1.08.01 DES OTHER Moved SetSendBufferSize(), GetSendBufferSize(), and GetReceiveBufferSize() to nonport. +09-25-2002 1.08.00 DDW OTHER Changelog started diff --git a/code/gamespy/GP/gp.c b/code/gamespy/GP/gp.c new file mode 100644 index 00000000..90e0dafd --- /dev/null +++ b/code/gamespy/GP/gp.c @@ -0,0 +1,4318 @@ +/* +gp.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +//INCLUDES +////////// +#include +#include +#include "gpi.h" + +//FUNCTIONS +/////////// +GPResult gpInitialize( + GPConnection * connection, + int productID, + int namespaceID, + int partnerID +) +{ + // Check if the backend is available. + ///////////////////////////////////// + if(__GSIACResult != GSIACAvailable) + return GP_PARAMETER_ERROR; + + // Error check. + /////////////// + if(connection == NULL) + return GP_PARAMETER_ERROR; + + return gpiInitialize(connection, productID, namespaceID, partnerID); +} + +void gpDestroy( + GPConnection * connection +) +{ + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return; + + gpiDestroy(connection); +} + +GPResult gpEnable( + GPConnection * connection, + GPEnum state +) +{ + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + return gpiEnable(connection, state); +} + +GPResult gpDisable( + GPConnection * connection, + GPEnum state +) +{ + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + return gpiDisable(connection, state); +} + +GPResult gpProcess( + GPConnection * connection +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return GP_NO_ERROR; + + return gpiProcess(connection, 0); +} + +GPResult gpSetCallback( + GPConnection * connection, + GPEnum func, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + int index; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Find which callback. + /////////////////////// + index = func; + if((index < 0) || (index >= GPI_NUM_CALLBACKS)) + Error(connection, GP_PARAMETER_ERROR, "Invalid func."); + + // Set the info. + //////////////// + iconnection->callbacks[index].callback = callback; + iconnection->callbacks[index].param = param; + + return GP_NO_ERROR; +} + +GPResult gpConnectA( + GPConnection * connection, + const char nick[GP_NICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + GPEnum firewall, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + if((nick == NULL) || (nick[0] == '\0')) + return GP_PARAMETER_ERROR; + if((email == NULL) || (email[0] == '\0')) + return GP_PARAMETER_ERROR; + if((password == NULL) || (password[0] == '\0')) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if(iconnection->simulation) + { + GPConnectResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do it. + ///////// + return gpiConnect(connection, nick, "", email, password, "", "", NULL, firewall, GPIFalse, blocking, callback, param); +} +#ifdef GSI_UNICODE +GPResult gpConnectW( + GPConnection * connection, + const unsigned short nick[GP_NICK_LEN], + const unsigned short email[GP_EMAIL_LEN], + const unsigned short password[GP_PASSWORD_LEN], + GPEnum firewall, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + char nick_A[GP_NICK_LEN]; + char email_A[GP_EMAIL_LEN]; + char password_A[GP_PASSWORD_LEN]; + + UCS2ToAsciiString(nick, nick_A); + UCS2ToAsciiString(email, email_A); + UCS2ToAsciiString(password, password_A); + + return gpConnectA(connection, nick_A, email_A, password_A, firewall, blocking, callback, param); +} +#endif + +GPResult gpConnectNewUserA( + GPConnection * connection, + const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + const char cdkey[GP_CDKEY_LEN], + GPEnum firewall, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + if((nick == NULL) || (nick[0] == '\0')) + return GP_PARAMETER_ERROR; + if(uniquenick == NULL) + uniquenick = ""; + if((email == NULL) || (email[0] == '\0')) + return GP_PARAMETER_ERROR; + if((password == NULL) || (password[0] == '\0')) + return GP_PARAMETER_ERROR; + if(cdkey && (cdkey[0] == '\0')) + cdkey = NULL; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check the length of the nick. + //////////////////////////////// + if(strlen(nick) >= GP_NICK_LEN) + Error(connection, GP_PARAMETER_ERROR, "Nick too long."); + + // Check the length of the uniquenick. + ////////////////////////////////////// + if(strlen(uniquenick) >= GP_UNIQUENICK_LEN) + Error(connection, GP_PARAMETER_ERROR, "Uniquenick too long."); + + // Check the length of the email. + ///////////////////////////////// + if(strlen(email) >= GP_EMAIL_LEN) + Error(connection, GP_PARAMETER_ERROR, "Email too long."); + + // Check the length of the password. + //////////////////////////////////// + if(strlen(password) >= GP_PASSWORD_LEN) + Error(connection, GP_PARAMETER_ERROR, "Password too long."); + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if(iconnection->simulation) + { + GPConnectResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do it. + ///////// + return gpiConnect(connection, nick, uniquenick, email, password, "", "", cdkey, firewall, GPITrue, blocking, callback, param); +} +#ifdef GSI_UNICODE +GPResult gpConnectNewUserW( + GPConnection * connection, + const unsigned short nick[GP_NICK_LEN], + const unsigned short uniquenick[GP_UNIQUENICK_LEN], + const unsigned short email[GP_EMAIL_LEN], + const unsigned short password[GP_PASSWORD_LEN], + const unsigned short cdkey[GP_CDKEY_LEN], + GPEnum firewall, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + char nick_A[GP_NICK_LEN]; + char uniquenick_A[GP_UNIQUENICK_LEN]; + char email_A[GP_NICK_LEN]; + char password_A[GP_NICK_LEN]; + char cdkey_A[GP_CDKEY_LEN]; + + UCS2ToAsciiString(nick, nick_A); + UCS2ToAsciiString(uniquenick, uniquenick_A); + UCS2ToAsciiString(email, email_A); + UCS2ToAsciiString(password, password_A); + UCS2ToAsciiString(cdkey, cdkey_A); + + return gpConnectNewUserA(connection, nick_A, uniquenick_A, email_A, password_A, cdkey_A, firewall, blocking, callback, param); +} +#endif + +GPResult gpConnectUniqueNickA( + GPConnection * connection, + const char uniquenick[GP_UNIQUENICK_LEN], + const char password[GP_PASSWORD_LEN], + GPEnum firewall, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + if((uniquenick == NULL) || (uniquenick[0] == '\0')) + return GP_PARAMETER_ERROR; + if((password == NULL) || (password[0] == '\0')) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if(iconnection->simulation) + { + GPConnectResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do it. + ///////// + return gpiConnect(connection, "", uniquenick, "", password, "", "", NULL, firewall, GPIFalse, blocking, callback, param); +} +#ifdef GSI_UNICODE +GPResult gpConnectUniqueNickW( + GPConnection * connection, + const unsigned short uniquenick[GP_UNIQUENICK_LEN], + const unsigned short password[GP_PASSWORD_LEN], + GPEnum firewall, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + char uniquenick_A[GP_UNIQUENICK_LEN]; + char password_A[GP_NICK_LEN]; + + UCS2ToAsciiString(uniquenick, uniquenick_A); + UCS2ToAsciiString(password, password_A); + + return gpConnectUniqueNickA(connection, uniquenick_A, password_A, firewall, blocking, callback, param); +} +#endif + +GPResult gpConnectPreAuthenticatedA +( + GPConnection * connection, + const char authtoken[GP_AUTHTOKEN_LEN], + const char partnerchallenge[GP_PARTNERCHALLENGE_LEN], + GPEnum firewall, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + if((authtoken == NULL) || (authtoken[0] == '\0')) + return GP_PARAMETER_ERROR; + if((partnerchallenge == NULL) || (partnerchallenge[0] == '\0')) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if(iconnection->simulation) + { + GPConnectResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do it. + ///////// + return gpiConnect(connection, "", "", "", "", authtoken, partnerchallenge, NULL, firewall, GPIFalse, blocking, callback, param); +} +#ifdef GSI_UNICODE +GPResult gpConnectPreAuthenticatedW +( + GPConnection * connection, + const unsigned short authtoken[GP_AUTHTOKEN_LEN], + const unsigned short partnerchallenge[GP_PARTNERCHALLENGE_LEN], + GPEnum firewall, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + char authtoken_A[GP_AUTHTOKEN_LEN]; + char partnerchallenge_A[GP_PARTNERCHALLENGE_LEN]; + + UCS2ToAsciiString(authtoken, authtoken_A); + UCS2ToAsciiString(partnerchallenge, partnerchallenge_A); + + return gpConnectPreAuthenticatedA(connection, authtoken_A, partnerchallenge_A, firewall, blocking, callback, param); +} +#endif + +void gpDisconnect( + GPConnection * connection +) +{ + GPIConnection * iconnection; + int oldState; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return; + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if(iconnection->simulation) + return; + + // Make a note of connection state prior to reset + ///////////////////////////////////////////////// + oldState = iconnection->connectState; + + gpiDisconnect(connection, GPITrue); + //Added by Saad Nader + //08-28-2004; fix for memory leaks after being disconnected abruptly + //////////////////////////////////////////////// + gpiReset(connection); + + // If we were connected prior, set to disconnected to save off info cache + ////////////////////////////////////////////////////////////////////////// + if (oldState == GPI_CONNECTED) + iconnection->connectState = GPI_DISCONNECTED; + +} + +GPResult gpIsConnected +( + GPConnection * connection, + GPEnum * connected +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Set the flag. + //////////////// + if(iconnection->connectState == GPI_CONNECTED) + *connected = GP_CONNECTED; + else + *connected = GP_NOT_CONNECTED; + + return GP_NO_ERROR; +} + +GPResult gpCheckUserA( + GPConnection * connection, + const char nick[GP_NICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check the length of the nick. + //////////////////////////////// + if(strlen(nick) >= GP_NICK_LEN) + Error(connection, GP_PARAMETER_ERROR, "Nick too long."); + + // Check the length of the email. + ///////////////////////////////// + if(strlen(email) >= GP_EMAIL_LEN) + Error(connection, GP_PARAMETER_ERROR, "Email too long."); + + // Check the length of the password. + //////////////////////////////////// + if(password && (strlen(password) >= GP_PASSWORD_LEN)) + Error(connection, GP_PARAMETER_ERROR, "Password too long."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + GPCheckResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do the check. + //////////////// + return gpiCheckUser(connection, nick, email, password, blocking, callback, param); +} +#ifdef GSI_UNICODE +GPResult gpCheckUserW( + GPConnection * connection, + const unsigned short nick[GP_NICK_LEN], + const unsigned short email[GP_EMAIL_LEN], + const unsigned short password[GP_PASSWORD_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + char nick_A[GP_NICK_LEN]; + char email_A[GP_NICK_LEN]; + char password_A[GP_NICK_LEN]; + + UCS2ToAsciiString(nick, nick_A); + UCS2ToAsciiString(email, email_A); + UCS2ToAsciiString(password, password_A); + + return gpCheckUserA(connection, nick_A, email_A, password_A, blocking, callback, param); +} +#endif + +GPResult gpNewUserA( + GPConnection * connection, + const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + const char cdkey[GP_CDKEY_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + if((nick == NULL) || (nick[0] == '\0')) + return GP_PARAMETER_ERROR; + if(uniquenick == NULL) + uniquenick = ""; + if((email == NULL) || (email[0] == '\0')) + return GP_PARAMETER_ERROR; + if((password == NULL) || (password[0] == '\0')) + return GP_PARAMETER_ERROR; + if(cdkey && (cdkey[0] == '\0')) + cdkey = NULL; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check the length of the nick. + //////////////////////////////// + if(strlen(nick) >= GP_NICK_LEN) + Error(connection, GP_PARAMETER_ERROR, "Nick too long."); + + // Check the length of the uniquenick. + ////////////////////////////////////// + if(strlen(uniquenick) >= GP_UNIQUENICK_LEN) + Error(connection, GP_PARAMETER_ERROR, "Uniquenick too long."); + + // Check the length of the email. + ///////////////////////////////// + if(strlen(email) >= GP_EMAIL_LEN) + Error(connection, GP_PARAMETER_ERROR, "Email too long."); + + // Check the length of the password. + //////////////////////////////////// + if(strlen(password) >= GP_PASSWORD_LEN) + Error(connection, GP_PARAMETER_ERROR, "Password too long."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + GPNewUserResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Creat the new user. + ////////////////////// + return gpiNewUser(connection, nick, uniquenick, email, password, cdkey, blocking, callback, param); +} +#ifdef GSI_UNICODE +GPResult gpNewUserW( + GPConnection * connection, + const unsigned short nick[GP_NICK_LEN], + const unsigned short uniquenick[GP_UNIQUENICK_LEN], + const unsigned short email[GP_EMAIL_LEN], + const unsigned short password[GP_PASSWORD_LEN], + const unsigned short cdkey[GP_CDKEY_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + char nick_A[GP_NICK_LEN]; + char uniquenick_A[GP_UNIQUENICK_LEN]; + char email_A[GP_EMAIL_LEN]; + char password_A[GP_PASSWORD_LEN]; + char cdkey_A[GP_CDKEY_LEN]; + + UCS2ToAsciiString(nick, nick_A); + UCS2ToAsciiString(uniquenick, uniquenick_A); + UCS2ToAsciiString(email, email_A); + UCS2ToAsciiString(password, password_A); + UCS2ToAsciiString(cdkey, cdkey_A); + + return gpNewUserA(connection, nick_A, uniquenick_A, email_A, password_A, cdkey_A, blocking, callback, param); +} +#endif + +GPResult gpSuggestUniqueNickA( + GPConnection * connection, + const char desirednick[GP_NICK_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check the length of the desirednick. + /////////////////////////////////////// + if(strlen(desirednick) >= GP_UNIQUENICK_LEN) + Error(connection, GP_PARAMETER_ERROR, "Desirednick too long."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + GPSuggestUniqueNickResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Creat the new user. + ////////////////////// + return gpiSuggestUniqueNick(connection, desirednick, blocking, callback, param); +} +#ifdef GSI_UNICODE +GPResult gpSuggestUniqueNickW( + GPConnection * connection, + const unsigned short desirednick[GP_NICK_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + char desirednick_A[GP_UNIQUENICK_LEN]; + + UCS2ToAsciiString(desirednick, desirednick_A); + + return gpSuggestUniqueNickA(connection, desirednick_A, blocking, callback, param); +} +#endif + +GPResult gpRegisterUniqueNickA( + GPConnection * connection, + const char uniquenick[GP_UNIQUENICK_LEN], + const char cdkey[GP_CDKEY_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + if((uniquenick == NULL) || (uniquenick[0] == '\0')) + return GP_PARAMETER_ERROR; + if(cdkey && (cdkey[0] == '\0')) + cdkey = NULL; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + GPRegisterUniqueNickResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + return gpiRegisterUniqueNick(connection, uniquenick, cdkey, blocking, callback, param); +} +#ifdef GSI_UNICODE +GPResult gpRegisterUniqueNickW( + GPConnection * connection, + const unsigned short uniquenick[GP_UNIQUENICK_LEN], + const unsigned short cdkey[GP_CDKEY_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + char uniquenick_A[GP_UNIQUENICK_LEN]; + char cdkey_A[GP_CDKEY_LEN]; + + UCS2ToAsciiString(uniquenick, uniquenick_A); + UCS2ToAsciiString(cdkey, cdkey_A); + + return gpRegisterUniqueNickA(connection, uniquenick_A, cdkey_A, blocking, callback, param); +} +#endif + + +GPResult gpRegisterCdKeyA( + GPConnection * connection, + const char cdkey[GP_CDKEY_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + if((cdkey == NULL) || (cdkey[0] == '\0')) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + GPRegisterCdKeyResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + return gpiRegisterCdKey(connection, cdkey, blocking, callback, param); +} +#ifdef GSI_UNICODE +GPResult gpRegisterCdKeyW( + GPConnection * connection, + const gsi_char cdkey[GP_CDKEY_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + char cdkey_A[GP_CDKEY_LEN]; + UCS2ToAsciiString(cdkey, cdkey_A); + return gpRegisterCdKeyA(connection, cdkey_A, blocking, callback, param); +} +#endif + +GPResult gpGetErrorCode( + GPConnection * connection, + GPErrorCode * errorCode +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Error check. + /////////////// + if(errorCode == NULL) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + *errorCode = (GPErrorCode)0; + return GP_NO_ERROR; + } + + // Set the code. + //////////////// + *errorCode = iconnection->errorCode; + + return GP_NO_ERROR; +} + +GPResult gpGetErrorStringA( + GPConnection * connection, + char errorString[GP_ERROR_STRING_LEN] +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Error check. + /////////////// + if(errorString == NULL) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + errorString[0] = '\0'; + return GP_NO_ERROR; + } + + // Copy the error string. + ///////////////////////// + strzcpy(errorString, iconnection->errorString, GP_ERROR_STRING_LEN); + return GP_NO_ERROR; +} +#ifdef GSI_UNICODE +GPResult gpGetErrorStringW( + GPConnection * connection, + unsigned short errorString[GP_ERROR_STRING_LEN] +) +{ + char errorString_A[GP_ERROR_STRING_LEN]; + GPResult result; + + result = gpGetErrorStringA(connection, errorString_A); + AsciiToUCS2String(errorString_A, errorString); + return result; +} +#endif + +GPResult gpNewProfileA( + GPConnection * connection, + const char nick[GP_NICK_LEN], + GPEnum replace, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check for no nick. + // PANTS|05.18.00 + ///////////////////// + if((nick == NULL) || (nick[0] == '\0')) + Error(connection, GP_PARAMETER_ERROR, "Invalid nick."); + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if(iconnection->simulation) + { + GPNewProfileResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + return gpiNewProfile(connection, nick, replace, blocking, callback, param); +} +#ifdef GSI_UNICODE +GPResult gpNewProfileW( + GPConnection * connection, + const unsigned short nick[GP_NICK_LEN], + GPEnum replace, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + char nick_A[GP_NICK_LEN]; + + // Since we don't currently allow UNICODE nicknames, make sure the first byte is empty + // (We make this check as an early alert to devlopers that the parameter is invalid) + // (Even if it's bypassed, the server will still reject the name.) + int i = 0; + for (; (i < GP_NICK_LEN) && (nick[i] != 0); i++) + { + if ((nick[i] & 0xFF00) != 0) + return GP_PARAMETER_ERROR; + } + + // Convert to ascii and call "A" version + UCS2ToAsciiString(nick, nick_A); + return gpNewProfileA(connection, nick_A, replace, blocking, callback, param); +} +#endif + +GPResult gpDeleteProfile( + GPConnection * connection, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if(iconnection->simulation) + { + GPDeleteProfileResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + return gpiDeleteProfile(connection, callback, param); +} + +GPResult gpProfileFromID( + GPConnection * connection, + GPProfile * profile, + int id +) +{ + GSI_UNUSED(connection); + + // Set the profile. + // This function is depreciated & may be removed from future versions. + ////////////////////////////////////////////////////////////////////// + *profile = id; + + return GP_NO_ERROR; +} + +// gpIDFromProfile +////////////////// +GPResult gpIDFromProfile( + GPConnection * connection, + GPProfile profile, + int * id +) +{ + GSI_UNUSED(connection); + + // ID is the same as GPProfile + // This function is depreciated & may be removed from future versions. + ////////////////////////////////////////////////////////////////////// + *id = profile; + + return GP_NO_ERROR; +} + +// gpUserIDFromProfile +////////////////// +GPResult gpUserIDFromProfile( + GPConnection * connection, + GPProfile profile, + int * userid +) +{ + GPIConnection * iconnection; + GPIProfile * pProfile; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + *userid = 0; + return GP_NO_ERROR; + } + + // Get the profile object. + ////////////////////////// + if(!gpiGetProfile(connection, profile, &pProfile)) + Error(connection, GP_PARAMETER_ERROR, "Invalid profile."); + + // Set the id. + ////////////// + *userid = pProfile->userId; + + return GP_NO_ERROR; +} + + +GPResult gpProfileSearchA( + GPConnection * connection, + const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char firstname[GP_FIRSTNAME_LEN], + const char lastname[GP_LASTNAME_LEN], + int icquin, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + GPProfileSearchResponseArg arg; + memset(&arg, 0, sizeof(arg)); + arg.more = GP_DONE; + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do the search. + ///////////////// + return gpiProfileSearch(connection, nick, uniquenick, email, firstname, lastname, icquin, 0, blocking, callback, param); +} +#ifdef GSI_UNICODE +GPResult gpProfileSearchW( + GPConnection * connection, + const unsigned short nick[GP_NICK_LEN], + const unsigned short uniquenick[GP_UNIQUENICK_LEN], + const unsigned short email[GP_EMAIL_LEN], + const unsigned short firstname[GP_FIRSTNAME_LEN], + const unsigned short lastname[GP_LASTNAME_LEN], + int icquin, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + char nick_A[GP_NICK_LEN]; + char uniquenick_A[GP_UNIQUENICK_LEN]; + char email_A[GP_NICK_LEN]; + char firstname_A[GP_NICK_LEN]; + char lastname_A[GP_NICK_LEN]; + + UCS2ToAsciiString(nick, nick_A); // nicknames are ascii + UCS2ToAsciiString(uniquenick, uniquenick_A); + UCS2ToAsciiString(email, email_A); + UCS2ToAsciiString(firstname, firstname_A); + UCS2ToAsciiString(lastname, lastname_A); + + return gpProfileSearchA(connection, nick_A, uniquenick_A, email_A, firstname_A, lastname_A, icquin, blocking, callback, param); +} +#endif + +GPResult gpProfileSearchUniquenickA( + GPConnection * connection, + const char uniquenick[GP_UNIQUENICK_LEN], + const int namespaceIDs[GP_MAX_NAMESPACEIDS], + int numNamespaces, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL) || (namespaceIDs == NULL) || (numNamespaces < 1)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + GPProfileSearchResponseArg arg; + memset(&arg, 0, sizeof(arg)); + arg.more = GP_DONE; + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do the search. + ///////////////// + return gpiProfileSearchUniquenick(connection, uniquenick, namespaceIDs, numNamespaces, blocking, callback, param); +} +#ifdef GSI_UNICODE +GPResult gpProfileSearchUniquenickW( + GPConnection * connection, + const unsigned short uniquenick[GP_UNIQUENICK_LEN], + const int namespaceIDs[GP_MAX_NAMESPACEIDS], + int numNamespaces, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + char uniquenick_A[GP_UNIQUENICK_LEN]; + + UCS2ToAsciiString(uniquenick, uniquenick_A); + + return gpProfileSearchUniquenickA(connection, uniquenick_A, namespaceIDs, numNamespaces, blocking, callback, param); +} +#endif + +GPResult gpGetInfo( + GPConnection * connection, + GPProfile profile, + GPEnum checkCache, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL) || (profile == 0)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + GPGetInfoResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + return gpiGetInfo(connection, profile, checkCache, blocking, callback, param); +} + +GPResult gpGetInfoNoWait( + GPConnection * connection, + GPProfile profile, + GPGetInfoResponseArg * arg +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL) || (profile == 0) || (arg == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if(iconnection->simulation) + { + memset(arg, 0, sizeof(arg)); + return GP_NO_ERROR; + } + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + return gpiGetInfoNoWait(connection, profile, arg); +} + +GPResult gpSetInfoi( + GPConnection * connection, + GPEnum info, + int value +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + return gpiSetInfoi(connection, info, value); +} + +GPResult gpSetInfosA( + GPConnection * connection, + GPEnum info, + const char * value +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + return gpiSetInfos(connection, info, value); +} +#ifdef GSI_UNICODE +GPResult gpSetInfosW( + GPConnection * connection, + GPEnum info, + const unsigned short* value +) +{ + char* value_A = UCS2ToUTF8StringAlloc(value); + GPResult result = gpSetInfosA(connection, info, value_A); + gsifree(value_A); + return result; +} +#endif + +GPResult gpSetInfod( + GPConnection * connection, + GPEnum info, + int day, + int month, + int year +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + return gpiSetInfod(connection, info, day, month, year); +} + +GPResult gpSetInfoMask( + GPConnection * connection, + GPEnum mask +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + return gpiSetInfoMask(connection, mask); +} + +GPResult gpSendBuddyRequestA( + GPConnection * connection, + GPProfile profile, + const char reason[GP_REASON_LEN] +) +{ + GPIConnection * iconnection; + char reasonFixed[GP_REASON_LEN]; + int i; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + // Error check. + /////////////// + if(reason == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid reason."); + + // Replace backslashes in reason. + ///////////////////////////////// + strzcpy(reasonFixed, reason, GP_REASON_LEN); + for(i = 0 ; reasonFixed[i] ; i++) + if(reasonFixed[i] == '\\') + reasonFixed[i] = '/'; + + // Send the request. + //////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\addbuddy\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\newprofileid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profile); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\reason\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, reasonFixed); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} +#ifdef GSI_UNICODE +GPResult gpSendBuddyRequestW( + GPConnection * connection, + GPProfile profile, + const unsigned short reason[GP_REASON_LEN] +) +{ + char reason_A[GP_REASON_LEN]; + UCS2ToUTF8String(reason, reason_A); + return gpSendBuddyRequestA(connection, profile, reason_A); +} +#endif + +GPResult gpAuthBuddyRequest( + GPConnection * connection, + GPProfile profile +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + return gpiAuthBuddyRequest(connection, profile); +} + +GPResult gpDenyBuddyRequest( + GPConnection * connection, + GPProfile profile +) +{ + GPIConnection * iconnection; + GPIProfile * pProfile; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + // Get the profile. + /////////////////// + if(!gpiGetProfile(connection, profile, &pProfile)) + return GP_NO_ERROR; + + // freeclear the sig if no more requests. + //////////////////////////////////// + pProfile->requestCount--; + if(!iconnection->infoCaching && (pProfile->requestCount <= 0)) + { + freeclear(pProfile->authSig); + if(gpiCanFreeProfile(pProfile)) + gpiRemoveProfile(connection, pProfile); + } + + return GP_NO_ERROR; +} + +GPResult gpGetNumBuddies( + GPConnection * connection, + int * numBuddies +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + *numBuddies = 0; + return GP_NO_ERROR; + } + + // Set the number of buddies. + ///////////////////////////// + *numBuddies = iconnection->profileList.numBuddies; + + return GP_NO_ERROR; +} + +#ifndef GP_NEW_STATUS_INFO +GPResult gpGetBuddyStatus( + GPConnection * connection, + int index, + GPBuddyStatus * status +) +{ + GPIConnection * iconnection; + int num; + GPIProfile * profile; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + memset(status, 0, sizeof(GPBuddyStatus)); + return GP_NO_ERROR; + } + + // Check for a NULL status. + /////////////////////////// + if(status == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid status."); + + // Check the buddy index. + ///////////////////////// + num = iconnection->profileList.numBuddies; + if((index < 0) || (index >= num)) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + + // Find the buddy with this index. + ////////////////////////////////// + profile = gpiFindBuddy(connection, index); + if(!profile) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + + + assert(profile->buddyStatus); + status->profile = (GPProfile)profile->profileId; + status->status = profile->buddyStatus->status; +#ifndef GSI_UNICODE + if(profile->buddyStatus->statusString) + strzcpy(status->statusString, profile->buddyStatus->statusString, GP_STATUS_STRING_LEN); + else + status->statusString[0] = '\0'; + + + if(profile->buddyStatus->locationString) + strzcpy(status->locationString, profile->buddyStatus->locationString, GP_LOCATION_STRING_LEN); + else + status->locationString[0] = '\0'; + +#else + if(profile->buddyStatus->statusString) + UTF8ToUCS2String(profile->buddyStatus->statusString, status->statusString); + else + status->statusString[0] = '\0'; + + if(profile->buddyStatus->locationString) + UTF8ToUCS2String(profile->buddyStatus->locationString, status->locationString); + else + status->locationString[0] = '\0'; + +#endif + status->ip = profile->buddyStatus->ip; + status->port = profile->buddyStatus->port; + status->quietModeFlags = profile->buddyStatus->quietModeFlags; + + return GP_NO_ERROR; +} +#endif + +#ifdef GP_NEW_STATUS_INFO +GPResult gpGetBuddyStatusInfo( + GPConnection * connection, + int index, + GPBuddyStatusInfo * statusInfo +) +{ + GPIConnection * iconnection; + int num; + GPIProfile * profile; + GPIBuddyStatus *buddyStatus; + GPIBuddyStatusInfo * buddyStatusInfo; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + memset(statusInfo, 0, sizeof(GPBuddyStatusInfo)); + return GP_NO_ERROR; + } + + // Check for a NULL status. + /////////////////////////// + if(statusInfo == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid status."); + + // Check the buddy index. + ///////////////////////// + num = iconnection->profileList.numBuddies; + if((index < 0) || (index >= num)) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + + // Find the buddy with this index. + ////////////////////////////////// + profile = gpiFindBuddy(connection, index); + if(!profile) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + + + buddyStatus = profile->buddyStatus; + buddyStatusInfo = profile->buddyStatusInfo; + assert(buddyStatus || buddyStatusInfo); + + statusInfo->profile = (GPProfile)profile->profileId; + if (buddyStatus) + { + statusInfo->statusState = buddyStatus->status; +#ifndef GSI_UNICODE + if(buddyStatus->statusString) + strzcpy(statusInfo->richStatus, buddyStatus->statusString, GP_RICH_STATUS_LEN); + else + statusInfo->richStatus[0] = '\0'; + statusInfo->gameType[0] = '\0'; + statusInfo->gameVariant[0] = '\0'; + statusInfo->gameMapName[0] = '\0'; +#else + if(buddyStatus->statusString) + UTF8ToUCS2String(buddyStatus->statusString, statusInfo->richStatus); + else + statusInfo->richStatus[0] = '\0'; + statusInfo->gameType[0] = '\0'; + statusInfo->gameVariant[0] = '\0'; + statusInfo->gameMapName[0] = '\0'; +#endif + statusInfo->buddyIp = buddyStatus->ip; + statusInfo->buddyPort = buddyStatus->port; + statusInfo->quietModeFlags = buddyStatus->quietModeFlags; + statusInfo->newStatusInfoFlag = GP_NEW_STATUS_INFO_NOT_SUPPORTED; + } + else if (buddyStatusInfo) + { + statusInfo->statusState = buddyStatusInfo->statusState; + statusInfo->buddyIp = buddyStatusInfo->buddyIp; + statusInfo->buddyPort = buddyStatusInfo->buddyPort; + statusInfo->hostIp = buddyStatusInfo->hostIp; + statusInfo->hostPrivateIp = buddyStatusInfo->hostPrivateIp; + statusInfo->queryPort = buddyStatusInfo->queryPort; + statusInfo->hostPort = buddyStatusInfo->hostPort; + statusInfo->sessionFlags = buddyStatusInfo->sessionFlags; + statusInfo->quietModeFlags = buddyStatusInfo->quietModeFlags; + statusInfo->newStatusInfoFlag = GP_NEW_STATUS_INFO_SUPPORTED; +#ifndef GSI_UNICODE + strzcpy(statusInfo->richStatus, buddyStatusInfo->richStatus, GP_RICH_STATUS_LEN); + strzcpy(statusInfo->gameType, buddyStatusInfo->gameType, GP_STATUS_BASIC_STR_LEN); + strzcpy(statusInfo->gameVariant, buddyStatusInfo->gameVariant, GP_STATUS_BASIC_STR_LEN); + strzcpy(statusInfo->gameMapName, buddyStatusInfo->gameMapName, GP_STATUS_BASIC_STR_LEN); +#else + UTF8ToUCS2String(buddyStatusInfo->richStatus, statusInfo->richStatus); + UTF8ToUCS2String(buddyStatusInfo->gameType, statusInfo->gameType); + UTF8ToUCS2String(buddyStatusInfo->gameVariant, statusInfo->gameVariant); + UTF8ToUCS2String(buddyStatusInfo->gameMapName, statusInfo->gameMapName); +#endif + } + + return GP_NO_ERROR; +} + +GPResult gpSetBuddyAddr( + GPConnection *connection, + int index, + unsigned int buddyIp, + unsigned short buddyPort +) +{ + GPIConnection * iconnection; + int num; + GPIProfile * profile; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check the buddy index. + ///////////////////////// + num = iconnection->profileList.numBuddies; + if((index < 0) || (index >= num)) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + + // Find the buddy with this index. + ////////////////////////////////// + profile = gpiFindBuddy(connection, index); + if(!profile) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + + if (buddyIp == 0 || buddyPort == 0) + Error(connection, GP_PARAMETER_ERROR, "Invalid IP and port"); + if (profile->buddyStatusInfo) + { + profile->buddyStatusInfo->buddyIp = buddyIp; + profile->buddyStatusInfo->buddyPort = buddyPort; + } + return GP_NO_ERROR; +} +#endif + +GPResult gpGetBuddyIndex( + GPConnection * connection, + GPProfile profile, + int * index +) +{ + GPIProfile * pProfile; + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + *index = 0; + return GP_NO_ERROR; + } + + // Get the index. + ///////////////// + if(gpiGetProfile(connection, profile, &pProfile) && pProfile->buddyStatus) + *index = pProfile->buddyStatus->buddyIndex; + else if (gpiGetProfile(connection, profile, &pProfile) && pProfile->buddyStatusInfo) + *index = pProfile->buddyStatusInfo->buddyIndex; + else + *index = -1; + + return GP_NO_ERROR; +} + +int gpIsBuddy( + GPConnection * connection, + GPProfile profile +) +{ + GPIProfile * pProfile; + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return 0; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return 0; + + // Get the index. + ///////////////// + if(gpiGetProfile(connection, profile, &pProfile) && pProfile->buddyStatus) + return 1; + else if (gpiGetProfile(connection, profile, &pProfile) &&pProfile->buddyStatusInfo) + return 1; + + return 0; +} + +int gpIsBuddyConnectionOpen( + GPConnection * connection, + GPProfile profile +) +{ + GPIConnection * iconnection; + GPIPeer *aPeer; + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return 0; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return 0; + + aPeer = gpiGetPeerByProfile(connection, profile); + + if (aPeer == NULL || !gpiIsPeerConnected(aPeer)) + return 0; // not connected + else + return 1; // connected +} + +GPResult gpDeleteBuddy( + GPConnection * connection, + GPProfile profile +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + // Delete the buddy. + //////////////////// + CHECK_RESULT(gpiDeleteBuddy(connection, profile, GPITrue)); + + return GP_NO_ERROR; +} + +GPResult gpAddToBlockedList( + GPConnection * connection, + GPProfile profile +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return GP_NO_ERROR; + + // Add em to the internal list - remove buddy status if already a buddy + /////////////////////////////////////////////////////////////////////// + return gpiAddToBlockedList(connection, profile); +} + +GPResult gpRemoveFromBlockedList( + GPConnection * connection, + GPProfile profile +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return GP_NO_ERROR; + + // Remove blocked association from internal list if it's there + ////////////////////////////////////////////////////////////// + return gpiRemoveFromBlockedList(connection, profile); +} + +GPResult gpGetNumBlocked( + GPConnection * connection, + int * numBlocked +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + *numBlocked = 0; + return GP_NO_ERROR; + } + + // Set the number of blocked profiles + ///////////////////////////////////// + *numBlocked = iconnection->profileList.numBlocked; + + return GP_NO_ERROR; +} + +GPResult gpGetBlockedProfile( + GPConnection * connection, + int index, + GPProfile * profile +) +{ + GPIConnection * iconnection; + int num; + GPIProfile * pProfile; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for a NULL profile. + //////////////////////////// + if(profile == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid profile container"); + + // Check the block index. + ///////////////////////// + num = iconnection->profileList.numBlocked; + if((index < 0) || (index >= num)) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + + // Find the blocked profile with this index. + //////////////////////////////////////////// + pProfile = gpiFindBlockedProfile(connection, index); + if(!pProfile) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + + *profile = (GPProfile)pProfile->profileId; + + return GP_NO_ERROR; +} + +gsi_bool gpIsBlocked( + GPConnection * connection, + GPProfile profile +) +{ + GPIProfile * pProfile; + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return gsi_false; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return gsi_false; + + // Get the index. + ///////////////// + if(gpiGetProfile(connection, profile, &pProfile) && pProfile->blocked) + return gsi_true; + + return gsi_false; +} + +#ifndef GP_NEW_STATUS_INFO +GPResult gpSetStatusA( + GPConnection * connection, + GPEnum status, + const char statusString[GP_STATUS_STRING_LEN], + const char locationString[GP_LOCATION_STRING_LEN] +) +{ + char statusStringFixed[GP_STATUS_STRING_LEN]; + char locationStringFixed[GP_LOCATION_STRING_LEN]; + GPIConnection * iconnection; + int i; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + // Error check. + /////////////// + if(statusString == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid statusString."); + if(locationString == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid locationString."); + + // Replace backslashes with slashes. + //////////////////////////////////// + strzcpy(statusStringFixed, statusString, GP_STATUS_STRING_LEN); + for(i = 0 ; statusStringFixed[i] ; i++) + if(statusStringFixed[i] == '\\') + statusStringFixed[i] = '/'; + strzcpy(locationStringFixed, locationString, GP_LOCATION_STRING_LEN); + for(i = 0 ; locationStringFixed[i] ; i++) + if(locationStringFixed[i] == '\\') + locationStringFixed[i] = '/'; + + // Don't send it if its the same as the previous. + ///////////////////////////////////////////////// + if((status == iconnection->lastStatusState) && + (strcmp(statusStringFixed, iconnection->lastStatusString) == 0) && + (strcmp(locationStringFixed, iconnection->lastLocationString) == 0)) + { + return GP_NO_ERROR; + } + + // Copy off the new status. + /////////////////////////// + iconnection->lastStatusState = status; +#ifndef GSI_UNICODE + strzcpy(iconnection->lastStatusString, statusStringFixed, GP_STATUS_STRING_LEN); + strzcpy(iconnection->lastLocationString, locationStringFixed, GP_LOCATION_STRING_LEN); +#else + UTF8ToUCS2StringLen(iconnection->lastStatusString, iconnection->lastStatusString_W, GP_STATUS_STRING_LEN); + UTF8ToUCS2StringLen(iconnection->lastStatusString, iconnection->lastLocationString_W, GP_LOCATION_STRING_LEN); +#endif + + // Send the new status. + /////////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\status\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, status); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\statstring\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, statusStringFixed); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\locstring\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, locationStringFixed); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} +#ifdef GSI_UNICODE +GPResult gpSetStatusW( + GPConnection * connection, + GPEnum status, + const unsigned short statusString[GP_STATUS_STRING_LEN], + const unsigned short locationString[GP_LOCATION_STRING_LEN] +) +{ + char statusString_A[GP_STATUS_STRING_LEN]; + char locationString_A[GP_LOCATION_STRING_LEN]; + UCS2ToUTF8String(statusString, statusString_A); + UCS2ToUTF8String(locationString, locationString_A); + return gpSetStatusA(connection, status, statusString_A, locationString_A); +} +#endif +#endif + +#ifdef GP_NEW_STATUS_INFO +GPResult gpSetStatusInfoA( + GPConnection *connection, + GPEnum statusState, + unsigned int hostIp, + unsigned int hostPrivateIp, + unsigned short queryPort, + unsigned short hostPort, + unsigned int sessionFlags, + const char *richStatus, + int richStatusLen, + const char *gameType, + int gameTypeLen, + const char *gameVariant, + int gameVariantLen, + const char *gameMapName, + int gameMapNameLen +) +{ + GPIConnection * iconnection; + +#ifndef GSI_UNICODE + char gameTypeFixed[GP_STATUS_BASIC_STR_LEN]; + char gameVariantFixed[GP_STATUS_BASIC_STR_LEN]; + char gameMapNameFixed[GP_STATUS_BASIC_STR_LEN]; +#else + char *gameTypeFixed; + char *gameVariantFixed; + char *gameMapNameFixed; +#endif + + GS_ASSERT(connection != NULL); + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + // Error check. + /////////////// + GS_ASSERT(richStatus != NULL); + if (richStatus == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid richStatus."); + +#ifndef GSI_UNICODE + GS_ASSERT(richStatusLen <= GP_RICH_STATUS_LEN); + GS_ASSERT(gameTypeLen <= GP_STATUS_BASIC_STR_LEN); + GS_ASSERT(gameVariantLen <= GP_STATUS_BASIC_STR_LEN); + GS_ASSERT(gameMapNameLen <= GP_STATUS_BASIC_STR_LEN); + + if (gameType == NULL) + strncpy(gameTypeFixed, "", GP_STATUS_BASIC_STR_LEN); + else + strncpy(gameTypeFixed, gameType, GP_STATUS_BASIC_STR_LEN); + if (gameVariant == NULL) + strncpy(gameVariantFixed, "", GP_STATUS_BASIC_STR_LEN); + else + strncpy(gameVariantFixed, gameVariant, GP_STATUS_BASIC_STR_LEN); + if (gameMapName == NULL) + strncpy(gameMapNameFixed, "", GP_STATUS_BASIC_STR_LEN); + else + strncpy(gameMapNameFixed, gameMapName, GP_STATUS_BASIC_STR_LEN); + + // Don't send it if its the same as the previous. + ///////////////////////////////////////////////// + if((statusState == iconnection->lastStatusState) && + (strcmp(richStatus, iconnection->richStatus) == 0) && + (strcmp(gameTypeFixed, iconnection->gameType) == 0) && + (strcmp(gameVariantFixed, iconnection->gameVariant) == 0) && + (strcmp(gameMapNameFixed, iconnection->gameMapName) == 0) && + (sessionFlags == iconnection->sessionFlags) && + (hostIp == iconnection->hostIp) && + (hostPrivateIp == iconnection->hostPrivateIp) && + (queryPort == iconnection->queryPort) && + (hostPort == iconnection->hostPort)) + { + return GP_NO_ERROR; + } +#else + gameTypeFixed = goastrdup(gameType); + gameVariantFixed = goastrdup(gameVariant); + gameMapNameFixed = goastrdup(gameMapName); +#endif + + iconnection->lastStatusState = statusState; + iconnection->hostIp = hostIp; + iconnection->hostPrivateIp = hostPrivateIp; + iconnection->queryPort = queryPort; + iconnection->hostPort = hostPort; + iconnection->sessionFlags = sessionFlags; + +#ifndef GSI_UNICODE + strzcpy(iconnection->gameType, gameTypeFixed, GP_STATUS_BASIC_STR_LEN); + strzcpy(iconnection->gameVariant, gameVariantFixed, GP_STATUS_BASIC_STR_LEN); + strzcpy(iconnection->gameMapName, gameMapNameFixed, GP_STATUS_BASIC_STR_LEN); + strzcpy(iconnection->richStatus, richStatus, GP_RICH_STATUS_LEN); +#endif + + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\statusinfo\\\\state\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, statusState); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\hostIp\\"); + gpiAppendUIntToBuffer(connection, &iconnection->outputBuffer, ntohl(hostIp)); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\hprivIp\\"); + gpiAppendUIntToBuffer(connection, &iconnection->outputBuffer, ntohl(hostPrivateIp)); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\qport\\"); + gpiAppendUShortToBuffer(connection, &iconnection->outputBuffer, queryPort); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\hport\\"); + gpiAppendUShortToBuffer(connection, &iconnection->outputBuffer, hostPort); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sessflags\\"); + gpiAppendUIntToBuffer(connection, &iconnection->outputBuffer, sessionFlags); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\richStatus\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, richStatus); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\gameType\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, gameTypeFixed); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\gameVariant\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, gameVariantFixed); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\gameMapName\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, gameMapNameFixed); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + GSI_UNUSED(richStatusLen); + GSI_UNUSED(gameTypeLen); + GSI_UNUSED(gameVariantLen); + GSI_UNUSED(gameMapNameLen); + return GP_NO_ERROR; +} + +#ifdef GSI_UNICODE +GPResult gpSetStatusInfoW( + GPConnection *connection, + GPEnum statusState, + unsigned int hostIp, + unsigned int hostPrivateIp, + unsigned short queryPort, + unsigned short hostPort, + unsigned int sessionFlags, + const unsigned short *richStatus, + int richStatusLen, + const unsigned short *gameType, + int gameTypeLen, + const unsigned short *gameVariant, + int gameVariantLen, + const unsigned short *gameMapName, + int gameMapNameLen + ) +{ + char *richStatus_A, *gameType_A, *gameVariant_A, *gameMapName_A; + GPResult aResult; + GPIConnection * iconnection; + GS_ASSERT(connection != NULL); + GS_ASSERT(richStatusLen <= GP_RICH_STATUS_LEN); + GS_ASSERT(gameTypeLen <= GP_STATUS_BASIC_STR_LEN); + GS_ASSERT(gameVariantLen <= GP_STATUS_BASIC_STR_LEN); + GS_ASSERT(gameMapNameLen <= GP_STATUS_BASIC_STR_LEN); + GS_ASSERT(richStatus != NULL); + + if (connection != NULL && (*connection != NULL)) + iconnection = (GPIConnection *)*connection; + else return GP_PARAMETER_ERROR; + + if (richStatus == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid richStatus."); + + if (richStatusLen <= GP_RICH_STATUS_LEN) + richStatus_A = UCS2ToUTF8StringAlloc(richStatus); + else + richStatus_A = UCS2ToUTF8StringAlloc((UCS2String)_T("")); + if (gameType && (gameTypeLen <= GP_STATUS_BASIC_STR_LEN)) + gameType_A = UCS2ToUTF8StringAlloc(gameType); + else + gameType_A = UCS2ToUTF8StringAlloc((UCS2String)_T("")); + if (gameVariant && (gameVariantLen <= GP_STATUS_BASIC_STR_LEN)) + gameVariant_A = UCS2ToUTF8StringAlloc(gameVariant); + else + gameVariant_A = UCS2ToUTF8StringAlloc((UCS2String)_T("")); + if (gameMapName && (gameMapNameLen <= GP_STATUS_BASIC_STR_LEN)) + gameMapName_A = UCS2ToUTF8StringAlloc(gameMapName); + else + gameMapName_A = UCS2ToUTF8StringAlloc((UCS2String)_T("")); + + if ((statusState == iconnection->lastStatusState) && + (sessionFlags == iconnection->sessionFlags) && + (hostIp == iconnection->hostIp) && + (hostPrivateIp == iconnection->hostPrivateIp) && + (queryPort == iconnection->queryPort) && + (hostPort == iconnection->hostPort) && + (_tcscmp(richStatus, iconnection->richStatus_W) == 0) && + (_tcscmp(gameType, iconnection->gameType_W) == 0) && + (_tcscmp(gameVariant, iconnection->gameVariant_W) == 0) && + (_tcscmp(gameMapName, iconnection->gameMapName_W) == 0)) + { + return GP_NO_ERROR; + } + _tcsncpy(iconnection->richStatus_W, richStatus, GP_RICH_STATUS_LEN); + _tcsncpy(iconnection->gameType_W, gameType, GP_STATUS_BASIC_STR_LEN); + _tcsncpy(iconnection->gameVariant_W, gameVariant, GP_STATUS_BASIC_STR_LEN); + _tcsncpy(iconnection->gameMapName_W, gameMapName, GP_STATUS_BASIC_STR_LEN); + + aResult = gpSetStatusInfoA(connection, statusState, hostIp, hostPrivateIp, queryPort, hostPort, + sessionFlags, richStatus_A, (int)strlen(richStatus_A), gameType_A, (int)strlen(gameType_A), + gameVariant_A, (int)strlen(gameVariant_A), gameMapName_A, (int)strlen(gameMapName_A)); + gsifree(richStatus_A); + gsifree(gameType_A); + gsifree(gameVariant_A); + gsifree(gameMapName_A); + return aResult; +} +#endif + +GPResult gpAddStatusInfoKeyA(GPConnection *connection, const char *keyName, const char *keyValue) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if ( iconnection->simulation ) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + return gpiStatusInfoAddKey(connection, iconnection->extendedInfoKeys, keyName, keyValue); +} + +#ifdef GSI_UNICODE +GPResult gpAddStatusInfoKeyW(GPConnection *connection, const unsigned short *keyName, const unsigned short *keyValue) +{ + GPResult aResult; + char *keyName_A = UCS2ToUTF8StringAlloc(keyName); + char *keyValue_A = UCS2ToUTF8StringAlloc(keyValue); + aResult = gpAddStatusInfoKeyA(connection, keyName_A, keyValue_A); + gsifree(keyName_A); + gsifree(keyValue_A); + return aResult; +} +#endif + +GPResult gpSetStatusInfoKeyA(GPConnection *connection, const char *keyName, const char *keyValue) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if ( iconnection->simulation ) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + return gpiStatusInfoSetKey(connection, iconnection->extendedInfoKeys, keyName, keyValue); +} + +#ifdef GSI_UNICODE +GPResult gpSetStatusInfoKeyW(GPConnection *connection, const unsigned short *keyName, const unsigned short *keyValue) +{ + GPResult aResult; + char *keyName_A = UCS2ToUTF8StringAlloc(keyName); + char *keyValue_A = UCS2ToUTF8StringAlloc(keyValue); + aResult = gpSetStatusInfoKeyA(connection, keyName_A, keyValue_A); + gsifree(keyName_A); + gsifree(keyValue_A); + return aResult; +} +#endif + +GPResult gpDelStatusInfoKeyA(GPConnection *connection, const char *keyName) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if ( iconnection->simulation ) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + return gpiStatusInfoDelKey(connection, iconnection->extendedInfoKeys, keyName); +} + +#ifdef GSI_UNICODE +GPResult gpDelStatusInfoKeyW(GPConnection *connection, const unsigned short *keyName) +{ + GPResult aResult; + char *keyName_A = UCS2ToUTF8StringAlloc(keyName); + aResult = gpDelStatusInfoKeyA(connection, keyName_A); + gsifree(keyName_A); + return aResult; +} +#endif + +GPResult gpGetStatusInfoKeyValA(GPConnection *connection, const char *keyName, char **keyValue) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if ( iconnection->simulation ) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + return gpiStatusInfoGetKey(connection, iconnection->extendedInfoKeys, keyName, keyValue); +} +#ifdef GSI_UNICODE +GPResult gpGetStatusInfoKeyValW(GPConnection *connection, const unsigned short *keyName, unsigned short **keyValue) +{ + GPResult aResult; + char *keyValue_A; + + char *keyName_A = UCS2ToUTF8StringAlloc(keyName); + + aResult = gpGetStatusInfoKeyValA(connection, keyName_A, &keyValue_A); + *keyValue = UTF8ToUCS2StringAlloc(keyValue_A); + + gsifree(keyName_A); + gsifree(keyValue_A); + return aResult; +} +#endif + + + +GPResult gpGetBuddyStatusInfoKeys(GPConnection *connection, int index, GPCallback callback, void *userData) +{ + GPIConnection *iconnection; + GPIProfile * pProfile; + GPResult aResult; + GPIPeerOp *aPeerOp; + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + pProfile = gpiFindBuddy(connection, index); + if (!pProfile) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + + if (pProfile->buddyStatus) + CallbackError(connection, GP_PARAMETER_ERROR, GP_BM_EXT_INFO_NOT_SUPPORTED, "The profile does not support extended info keys.") + + if (!pProfile->buddyStatusInfo && !pProfile->buddyStatus) + CallbackError(connection, GP_PARAMETER_ERROR, GP_BM_NOT_BUDDY, "The profile used to get extended info keys is not a buddy.") + + if (pProfile->buddyStatusInfo && pProfile->buddyStatusInfo->statusState == GP_OFFLINE) + CallbackError(connection, GP_NETWORK_ERROR, GP_BM_BUDDY_OFFLINE, "The profile used to get extended info keys is offline."); + + aPeerOp = (GPIPeerOp *)gsimalloc(sizeof(GPIPeerOp)); + aPeerOp->callback = callback; + aPeerOp->next = NULL; + aPeerOp->state = GPI_PEER_OP_STATE_REQUESTED; + aPeerOp->type = GPI_BM_KEYS_REQUEST; + aPeerOp->userData = userData; + aPeerOp->timeout = current_time() + GPI_PEER_OP_TIMEOUT; + aResult = gpiSendBuddyMessage(connection, pProfile->profileId, GPI_BM_KEYS_REQUEST, "Keys?", GP_DONT_ROUTE, aPeerOp); + return aResult; +} +#endif + +GPResult gpSendBuddyMessageA( + GPConnection * connection, + GPProfile profile, + const char * message +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + // Error check. + /////////////// + if(message == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid message."); + + return gpiSendBuddyMessage(connection, profile, GPI_BM_MESSAGE, message, 0, NULL); +} +#ifdef GSI_UNICODE +GPResult gpSendBuddyMessageW( + GPConnection * connection, + GPProfile profile, + const unsigned short* message +) +{ + char* message_A; + GPResult result; + + assert(message != NULL); + message_A = UCS2ToUTF8StringAlloc(message); // convert to UTF8 + result = gpSendBuddyMessageA(connection, profile, message_A); // send + gsifree(message_A); // free the converted string + return result; +} +#endif + +GPResult gpSendBuddyUTMA( + GPConnection * connection, + GPProfile profile, + const char * message, + int sendOption +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + // Error check. + /////////////// + if(message == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid message."); + + return gpiSendBuddyMessage(connection, profile, GPI_BM_UTM, message, sendOption, NULL); +} + +#ifdef GSI_UNICODE +GPResult gpSendBuddyUTMW( + GPConnection * connection, + GPProfile profile, + const unsigned short* message, + int sendOption +) +{ + char* message_A; + GPResult result; + + assert(message != NULL); + message_A = UCS2ToUTF8StringAlloc(message); // convert to UTF8 + result = gpSendBuddyUTMA(connection, profile, message_A, sendOption); // send + gsifree(message_A); // free the converted string + return result; +} +#endif + +GPResult gpIsValidEmailA( + GPConnection * connection, + const char email[GP_EMAIL_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check the length of the email. + ///////////////////////////////// + if(strlen(email) >= GP_EMAIL_LEN) + Error(connection, GP_PARAMETER_ERROR, "Email too long."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + GPIsValidEmailResponseArg arg; + memset(&arg, 0, sizeof(arg)); +#ifndef GSI_UNICODE + strzcpy(arg.email, email, GP_EMAIL_LEN); +#else + UTF8ToUCS2String(email, arg.email); +#endif + arg.isValid = GP_INVALID; + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do the validation. + ///////////////////// + return gpiIsValidEmail(connection, email, blocking, callback, param); +} +#ifdef GSI_UNICODE +GPResult gpIsValidEmailW( + GPConnection * connection, + const unsigned short email[GP_EMAIL_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + char email_A[GP_EMAIL_LEN]; + UCS2ToAsciiString(email, email_A); + return gpIsValidEmailA(connection, email_A, blocking,callback, param); +} +#endif + +GPResult gpGetUserNicksA( + GPConnection * connection, + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check the length of the email. + ///////////////////////////////// + if(strlen(email) >= GP_EMAIL_LEN) + Error(connection, GP_PARAMETER_ERROR, "Email too long."); + + // Check the length of the password. + //////////////////////////////////// + if(strlen(password) >= GP_PASSWORD_LEN) + Error(connection, GP_PARAMETER_ERROR, "Password too long."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + GPGetUserNicksResponseArg arg; + memset(&arg, 0, sizeof(arg)); +#ifndef GSI_UNICODE + strzcpy(arg.email, email, GP_EMAIL_LEN); +#else + AsciiToUCS2String(email, arg.email); +#endif + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do the validation. + ///////////////////// + return gpiGetUserNicks(connection, email, password, blocking, callback, param); +} +#ifdef GSI_UNICODE +GPResult gpGetUserNicksW( + GPConnection * connection, + const unsigned short email[GP_EMAIL_LEN], + const unsigned short password[GP_PASSWORD_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + char email_A[GP_EMAIL_LEN]; + char password_A[GP_PASSWORD_LEN]; + UCS2ToAsciiString(email, email_A); + UCS2ToAsciiString(password, password_A); + return gpGetUserNicksA(connection, email_A, password_A, blocking, callback, param); +} +#endif + +GPResult gpSetInvitableGames( + GPConnection * connection, + int numProductIDs, + const int * productIDs +) +{ + GPIConnection * iconnection; + int i; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + // Error check. + /////////////// + if(numProductIDs < 0) + Error(connection, GP_PARAMETER_ERROR, "Invalid numProductIDs."); + if((numProductIDs > 0) && (productIDs == NULL)) + Error(connection, GP_PARAMETER_ERROR, "Invalid productIDs."); + + // Send the list. + ///////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\inviteto\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\products\\"); + for(i = 0 ; i < numProductIDs ; i++) + { + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, productIDs[i]); + if(i < (numProductIDs - 1)) + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, ","); + } + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult gpFindPlayers( + GPConnection * connection, + int productID, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + GPFindPlayersResponseArg arg; + memset(&arg, 0, sizeof(arg)); + arg.productID = productID; + arg.numMatches = 0; + arg.matches = NULL; + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Start the find. + ////////////////// + return gpiFindPlayers(connection, productID, blocking, callback, param); +} + +GPResult gpInvitePlayerA( + GPConnection * connection, + GPProfile profile, + int productID, + const char location[GP_LOCATION_STRING_LEN] +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + // Send the invite. + /////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\pinvite\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\profileid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profile); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\productid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, productID); + + if (location && location[0]) + { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\location\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, location); + } + + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} +#ifdef GSI_UNICODE +GPResult gpInvitePlayerW( + GPConnection * connection, + GPProfile profile, + int productID, + const gsi_char location[GP_LOCATION_STRING_LEN] +) +{ + char location_A[GP_LOCATION_STRING_LEN]; + UCS2ToAsciiString(location, location_A); + return gpInvitePlayerA(connection, profile, productID, location_A); +} +#endif + +GPResult gpGetReverseBuddies( + GPConnection * connection, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + GPGetReverseBuddiesResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Start the search. + //////////////////// + return gpiOthersBuddy(connection, blocking, callback, param); +} + +GPResult gpGetReversBuddiesList( GPConnection * connection, + GPProfile *targets, int numOfTargets, + GPEnum blocking, + GPCallback callback, + void * param) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if(callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + { + GPGetReverseBuddiesListResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Start the search. + //////////////////// + return gpiOthersBuddyList(connection, targets, numOfTargets, blocking, callback, param); + +} + + +GPResult gpRevokeBuddyAuthorization( + GPConnection * connection, + GPProfile profile +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + // Send the invite. + /////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\revoke\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\profileid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profile); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + + +GPResult gpGetLoginTicket( + GPConnection * connection, + char loginTicket[GP_LOGIN_TICKET_LEN] +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + memcpy(loginTicket, iconnection->loginTicket, GP_LOGIN_TICKET_LEN); + return GP_NO_ERROR; +} + +GPResult gpSetQuietMode( + GPConnection * connection, + GPEnum flags +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Store the flags locally. + /////////////////////////// + iconnection->quietModeFlags = flags; + + // Check for a connection. + ////////////////////////// + if(iconnection->connectState == GPI_CONNECTED) + { + // Send the flags. + ////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\quiet\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->quietModeFlags); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + } + + return GP_NO_ERROR; +} + +#ifndef NOFILE +void gpSetInfoCacheFilenameA( + const char * filename +) +{ + gpiSetInfoCacheFilename(filename); +} +void gpSetInfoCacheFilenameW( + const unsigned short * filename +) +{ + char* filename_A = UCS2ToUTF8StringAlloc(filename); + gpiSetInfoCacheFilename(filename_A); + gsifree(filename_A); +} + +static GPResult gpiAddSendingFileA( + GPConnection * connection, + GPITransfer * transfer, + const char * path, + const char * name +) +{ + GPIFile * file = NULL; + int size = 0; + gsi_time modTime = 0; + + // Check for a bad path or name. + //////////////////////////////// + if(!path && !name) + Error(connection, GP_PARAMETER_ERROR, "File missing path and name."); + if(path && !path[0]) + Error(connection, GP_PARAMETER_ERROR, "Empty path."); + if(name && !name[0]) + Error(connection, GP_PARAMETER_ERROR, "Empty name."); + + // Check that the file exists and is readable. + ////////////////////////////////////////////// + if(path) + { + FILE * fileVerify; + + fileVerify = fopen(path, "r"); + if(!fileVerify) + Error(connection, GP_PARAMETER_ERROR, "Can't find file."); + + if(!gpiGetTransferFileInfo(fileVerify, &size, &modTime)) + { + fclose(fileVerify); + Error(connection, GP_PARAMETER_ERROR, "Can't get info on file."); + } + + fclose(fileVerify); + } + + // Validate the name. + ///////////////////// + if(name) + { + size_t len; + + len = strlen(name); + + if(strstr(name, "//") || strstr(name, "\\\\")) + Error(connection, GP_PARAMETER_ERROR, "Empty directory in filename."); + if(strstr(name, "./") || strstr(name, ".\\") || (name[len - 1] == '.')) + Error(connection, GP_PARAMETER_ERROR, "Directory level in filename."); + if((name[0] == '/') || (name[0] == '\\')) + Error(connection, GP_PARAMETER_ERROR, "Filename can't start with a slash."); + if(strcspn(name, ":*?\"<>|\n") != len) + Error(connection, GP_PARAMETER_ERROR, "Invalid character in filename."); + } + // The name is the path's title. + //////////////////////////////// + else + { + const char * str; + + // Find the end of the path. + //////////////////////////// + name = strrchr(path, '/'); + str = strrchr(path, '\\'); + if(str > name) + name = str; + + // Point the name at the title. + /////////////////////////////// + if(name) + name++; + else + name = path; + } + + // Add this to the list. + //////////////////////// + file = gpiAddFileToTransfer(transfer, path, name); + if(!file) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Set the size and time. + ///////////////////////// + file->size = size; + file->modTime = modTime; + + // Update the total size. + ///////////////////////// + transfer->totalSize += size; + + return GP_NO_ERROR; +} +#ifdef GSI_UNICODE +static GPResult gpiAddSendingFileW( + GPConnection * connection, + GPITransfer * transfer, + const unsigned short * path, + const unsigned short * name +) +{ + char* path_A = UCS2ToUTF8StringAlloc(path); + char* name_A = UCS2ToUTF8StringAlloc(name); + GPResult result = gpiAddSendingFileA(connection, transfer, path_A, name_A); + gsifree(path_A); + gsifree(name_A); + return result; +} +#endif + + +GPResult gpSendFilesA( + GPConnection * connection, + GPTransfer * transfer, + GPProfile profile, + const char * message, + gpSendFilesCallback callback, + void * param +) +{ + GPIConnection * iconnection; + GPITransfer * pTransfer; + GPResult result; + const gsi_char * path; + const gsi_char * name; + int numFiles; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Check for simulation mode. + ///////////////////////////// + if(iconnection->simulation) + Error(connection, GP_PARAMETER_ERROR, "Cannot send files in simulation mode."); + + // Check for disconnected. + ////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, "The connection has already been disconnected."); + + // Check other stuff. + ///////////////////// + if(!callback) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + if(!iconnection->callbacks[GPI_TRANSFER_CALLBACK].callback) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // No message is an empty message. + ////////////////////////////////// + if(!message) + message = ""; + + // Create the transfer object. + ////////////////////////////// + CHECK_RESULT(gpiNewSenderTransfer(connection, &pTransfer, profile)); + + // Fill in the message. + /////////////////////// + pTransfer->message = goastrdup(message); + if(!pTransfer->message) + { + gpiFreeTransfer(connection, pTransfer); + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + } + + // Add all the files. + ///////////////////// + numFiles = 0; + do + { + path = NULL; + name = NULL; + callback(connection, numFiles++, &path, &name, param); + if(path && !path[0]) + path = NULL; + if(name && !name[0]) + name = NULL; + + if(name || path) + { +#ifndef GSI_UNICODE + result = gpiAddSendingFileA(connection, pTransfer, path, name); +#else + result = gpiAddSendingFileW(connection, pTransfer, path, name); +#endif + if(result != GP_NO_ERROR) + { + gpiFreeTransfer(connection, pTransfer); + return result; + } + } + } + while(name || path); + + // Check that we got at least 1 file. + ///////////////////////////////////// + if(!ArrayLength(pTransfer->files)) + { + gpiFreeTransfer(connection, pTransfer); + Error(connection, GP_PARAMETER_ERROR, "No files to send."); + } + + // Ping the receiver. + ///////////////////// + result = gpiSendBuddyMessage(connection, profile, GPI_BM_PING, "1", 0, NULL); + if(result != GP_NO_ERROR) + { + gpiFreeTransfer(connection, pTransfer); + return result; + } + + // Successful so far. + ///////////////////// + if(transfer) + *transfer = pTransfer->localID; + + return GP_NO_ERROR; +} +GPResult gpSendFilesW( + GPConnection * connection, + GPTransfer * transfer, + GPProfile profile, + const unsigned short* message, + gpSendFilesCallback callback, + void * param +) +{ + char* message_A = NULL; + GPResult result; + + if (message == NULL) + return gpSendFilesA(connection, transfer, profile, NULL, callback, param); + + message_A = UCS2ToUTF8StringAlloc(message); + result = gpSendFilesA(connection, transfer, profile, message_A, callback, param); + gsifree(message_A); + return result; +} + +GPResult gpAcceptTransferA( + GPConnection * connection, + GPTransfer transfer, + const char * message +) +{ + GPITransfer * pTransfer; + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Check that we have a directory set. + ////////////////////////////////////// + if(!pTransfer->baseDirectory) + Error(connection, GP_PARAMETER_ERROR, "No transfer directory set."); + + // Check if this transfer has been cancelled. + ///////////////////////////////////////////// + if(pTransfer->state & GPITransferCancelled) + Error(connection, GP_PARAMETER_ERROR, "Transfer already cancelled."); + + // Send a reply. + //////////////// + CHECK_RESULT(gpiSendTransferReply(connection, &pTransfer->transferID, pTransfer->peer, GPI_ACCEPTED, message)); + + // We're now transferring. + ////////////////////////// + pTransfer->state = GPITransferTransferring; + + // Set the current file index to the first file. + //////////////////////////////////////////////// + pTransfer->currentFile = 0; + + return GP_NO_ERROR; +} +GPResult gpAcceptTransferW( + GPConnection * connection, + GPTransfer transfer, + const unsigned short * message +) +{ + char* message_A = NULL; + GPResult result; + + if (message == NULL) + return gpAcceptTransferA(connection, transfer, NULL); + + message_A = UCS2ToUTF8StringAlloc(message); + result = gpAcceptTransferA(connection, transfer, message_A); + gsifree(message_A); + return result; +} + + +GPResult gpRejectTransferA( + GPConnection * connection, + GPTransfer transfer, + const char * message +) +{ + GPITransfer * pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + return GP_NO_ERROR; + + // Check if this transfer has been cancelled. + ///////////////////////////////////////////// + if(pTransfer->state & GPITransferCancelled) + return GP_NO_ERROR; + + // Send the reply. + ////////////////// + gpiSendTransferReply(connection, &pTransfer->transferID, pTransfer->peer, GPI_REJECTED, message); + + // Free the transfer. + ///////////////////// + gpiFreeTransfer(connection, pTransfer); + + return GP_NO_ERROR; +} +GPResult gpRejectTransferW( + GPConnection * connection, + GPTransfer transfer, + const unsigned short* message +) +{ + char* message_A = NULL; + GPResult result; + + if (message == NULL) + return gpRejectTransferA(connection, transfer, NULL); + + message_A = UCS2ToUTF8StringAlloc(message); + result = gpRejectTransferA(connection, transfer, message_A); + gsifree(message_A); + return result; +} + +GPResult gpFreeTransfer( + GPConnection * connection, + GPTransfer transfer +) +{ + GPITransfer * pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + return GP_NO_ERROR; + + // Check if this should be a reject. + //////////////////////////////////// + if(!pTransfer->sender && (pTransfer->state == GPITransferWaiting)) + return gpRejectTransfer(connection, transfer, NULL); + + // Check for cancelling. + //////////////////////// + if(pTransfer->state < GPITransferComplete) + gpiCancelTransfer(connection, pTransfer); + + // Free the transfer. + ///////////////////// + gpiFreeTransfer(connection, pTransfer); + + return GP_NO_ERROR; +} + +GPResult gpSetTransferData( + GPConnection * connection, + GPTransfer transfer, + void * userData +) +{ + GPITransfer * pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Set the data. + //////////////// + pTransfer->userData = userData; + + return GP_NO_ERROR; +} + +void * gpGetTransferData( + GPConnection * connection, + GPTransfer transfer +) +{ + GPITransfer * pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + return NULL; + + // Return the data. + /////////////////// + return pTransfer->userData; +} + +GPResult gpSetTransferDirectoryA( + GPConnection * connection, + GPTransfer transfer, + const char * directory +) +{ + GPITransfer * pTransfer; + char lastChar; + + if(!directory || !directory[0]) + Error(connection, GP_PARAMETER_ERROR, "Invalid directory."); + lastChar = directory[strlen(directory) - 1]; + if((lastChar != '\\') && (lastChar != '/')) + Error(connection, GP_PARAMETER_ERROR, "Invalid directory."); + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // This has to be set before transferring. + ////////////////////////////////////////// + if(pTransfer->sender) + Error(connection, GP_PARAMETER_ERROR, "Sender has no transfer directory."); + if(pTransfer->state != GPITransferWaiting) + Error(connection, GP_PARAMETER_ERROR, "Can only set transfer directory before transferring."); + + // Free any existing directory. + /////////////////////////////// + if(pTransfer->baseDirectory) + gsifree(pTransfer->baseDirectory); + pTransfer->baseDirectory = NULL; + + // Set the directory. + ///////////////////// + pTransfer->baseDirectory = goastrdup(directory); + if(!pTransfer->baseDirectory) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + return GP_NO_ERROR; +} +GPResult gpSetTransferDirectoryW( + GPConnection * connection, + GPTransfer transfer, + const unsigned short * directory +) +{ + char* directory_A = UCS2ToUTF8StringAlloc(directory); + GPResult result = gpSetTransferDirectoryA(connection, transfer, directory_A); + gsifree(directory_A); + return result; +} + +GPResult gpSetTransferThrottle( + GPConnection * connection, + GPTransfer transfer, + int throttle +) +{ + GPITransfer * pTransfer; + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Negative means no throttle. + ////////////////////////////// + if(throttle < 0) + throttle = -1; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Store the throttle setting. + ////////////////////////////// + pTransfer->throttle = throttle; + + // Send the rate. + ///////////////// + CHECK_RESULT(gpiPeerStartTransferMessage(connection, pTransfer->peer, GPI_BM_FILE_TRANSFER_THROTTLE, (GPITransferID_st)&pTransfer->transferID)); + gpiSendOrBufferString(connection, pTransfer->peer, "\\rate\\"); + gpiSendOrBufferInt(connection, pTransfer->peer, throttle); + gpiPeerFinishTransferMessage(connection, pTransfer->peer, NULL, 0); + + // If we're the sender, call the callback. + ////////////////////////////////////////// + if(pTransfer->sender) + { + GPTransferCallbackArg * arg; + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = pTransfer->localID; + arg->type = GP_TRANSFER_THROTTLE; + arg->num = throttle; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + } + + return GP_NO_ERROR; +} + +GPResult gpGetTransferThrottle( + GPConnection * connection, + GPTransfer transfer, + int * throttle +) +{ + GPITransfer * pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the throttle. + //////////////////// + *throttle = pTransfer->throttle; + + return GP_NO_ERROR; +} + +GPResult gpGetTransferProfile( + GPConnection * connection, + GPTransfer transfer, + GPProfile * profile +) +{ + GPITransfer * pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the profile. + /////////////////// + *profile = pTransfer->profile; + + return GP_NO_ERROR; +} + +GPResult gpGetTransferSide( + GPConnection * connection, + GPTransfer transfer, + GPEnum * side +) +{ + GPITransfer * pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the side. + //////////////// + if(pTransfer->sender) + *side = GP_TRANSFER_SENDER; + else + *side = GP_TRANSFER_RECEIVER; + + return GP_NO_ERROR; +} + +GPResult gpGetTransferSize( + GPConnection * connection, + GPTransfer transfer, + int * size +) +{ + GPITransfer * pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the size. + //////////////// + *size = pTransfer->totalSize; + + return GP_NO_ERROR; +} + +GPResult gpGetTransferProgress( + GPConnection * connection, + GPTransfer transfer, + int * progress +) +{ + GPITransfer * pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the progress. + //////////////////// + *progress = pTransfer->progress; + + return GP_NO_ERROR; +} + +GPResult gpGetNumFiles( + GPConnection * connection, + GPTransfer transfer, + int * num +) +{ + GPITransfer * pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the progress. + //////////////////// + *num = ArrayLength(pTransfer->files); + + return GP_NO_ERROR; +} + +GPResult gpGetCurrentFile( + GPConnection * connection, + GPTransfer transfer, + int * index +) +{ + GPITransfer * pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the current file. + //////////////////////// + *index = pTransfer->currentFile; + + return GP_NO_ERROR; +} + +GPResult gpSkipFile( + GPConnection * connection, + GPTransfer transfer, + int index +) +{ + GPIFile * file; + GPITransfer * pTransfer; + GPTransferCallbackArg * arg; + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the file. + //////////////// + if((index < 0) || (index >= ArrayLength(pTransfer->files))) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + file = (GPIFile *)ArrayNth(pTransfer->files, index); + + // Are we already past this file? + ///////////////////////////////// + if(index < pTransfer->currentFile) + return GP_NO_ERROR; + + // Did we not get to this file yet? + /////////////////////////////////// + if(pTransfer->currentFile != index) + { + // Mark it. + /////////// + file->flags |= GPI_FILE_SKIP; + + // If we're receiving, let the sender know we want to skip it. + ////////////////////////////////////////////////////////////// + if(!pTransfer->sender) + gpiSkipFile(connection, pTransfer, index, GPI_SKIP_USER_SKIP); + + return GP_NO_ERROR; + } + + // If we're receiving, delete our temp file. + //////////////////////////////////////////// + if(!pTransfer->sender && (index == pTransfer->currentFile) && file->file) + { + fclose(file->file); + file->file = NULL; + remove(file->path); + } + + // Skip the current file. + ///////////////////////// + gpiSkipCurrentFile(connection, pTransfer, GPI_SKIP_USER_SKIP); + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = pTransfer->localID; + arg->index = index; + arg->type = GP_FILE_SKIP; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + return GP_NO_ERROR; +} + +GPResult gpGetFileName( + GPConnection * connection, + GPTransfer transfer, + int index, + gsi_char ** name +) +{ + GPIFile * file; + GPITransfer * pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the file. + //////////////// + if((index < 0) || (index >= ArrayLength(pTransfer->files))) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + file = (GPIFile *)ArrayNth(pTransfer->files, index); + + // Get the name. + //////////////// +#ifndef GSI_UNICODE + *name = file->name; +#else + *name = file->name_W; +#endif + + return GP_NO_ERROR; +} + + +GPResult gpGetFilePath( + GPConnection * connection, + GPTransfer transfer, + int index, + gsi_char ** path +) +{ + GPIFile * file; + GPITransfer * pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the file. + //////////////// + if((index < 0) || (index >= ArrayLength(pTransfer->files))) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + file = (GPIFile *)ArrayNth(pTransfer->files, index); + + // Get the path. + //////////////// +#ifndef GSI_UNICODE + *path = file->path; +#else + *path = file->path_W; +#endif + + return GP_NO_ERROR; +} + +GPResult gpGetFileSize( + GPConnection * connection, + GPTransfer transfer, + int index, + int * size +) +{ + GPIFile * file; + GPITransfer * pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the file. + //////////////// + if((index < 0) || (index >= ArrayLength(pTransfer->files))) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + file = (GPIFile *)ArrayNth(pTransfer->files, index); + + // Get the size. + //////////////// + *size = file->size; + + return GP_NO_ERROR; +} + +GPResult gpGetFileProgress( + GPConnection * connection, + GPTransfer transfer, + int index, + int * progress +) +{ + GPIFile * file; + GPITransfer * pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the file. + //////////////// + if((index < 0) || (index >= ArrayLength(pTransfer->files))) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + file = (GPIFile *)ArrayNth(pTransfer->files, index); + + // Get the progress. + //////////////////// + *progress = file->progress; + + return GP_NO_ERROR; +} + +GPResult gpGetFileModificationTime( + GPConnection * connection, + GPTransfer transfer, + int index, + gsi_time * modTime +) +{ + GPIFile * file; + GPITransfer * pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if(!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the file. + //////////////// + if((index < 0) || (index >= ArrayLength(pTransfer->files))) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + file = (GPIFile *)ArrayNth(pTransfer->files, index); + + // Get the modTime. + /////////////////// + *modTime = file->modTime; + + return GP_NO_ERROR; +} + +GPResult gpGetNumTransfers( + GPConnection * connection, + int * num +) +{ + GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for NULL. + ////////////////// + if(num == NULL) + Error(connection, GP_PARAMETER_ERROR, "NULL pointer."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Set num. + /////////// + *num = ArrayLength(iconnection->transfers); + + return GP_NO_ERROR; +} + +GPResult gpGetTransfer( + GPConnection * connection, + int index, + GPTransfer * transfer +) +{ + GPIConnection * iconnection; + int localID; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for NULL. + ////////////////// + if(transfer == NULL) + Error(connection, GP_PARAMETER_ERROR, "NULL pointer."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection *)*connection; + + // Get the local ID. + //////////////////// + localID = gpiGetTransferLocalIDByIndex(connection, index); + + // Check if it was a bad index. + /////////////////////////////// + if(localID == -1) + Error(connection, GP_PARAMETER_ERROR, "Index out of range."); + + // Set the transfer they want. + ////////////////////////////// + *transfer = localID; + + return GP_NO_ERROR; +} +#endif + +#ifdef _DEBUG +void gpProfilesReport( + GPConnection * connection, + void (* report)(const char * output) +) +{ + //GPIConnection * iconnection; + + // Error check. + /////////////// + if((connection == NULL) || (*connection == NULL)) + return; + + // Get the connection object. + ///////////////////////////// + //iconnection = (GPIConnection *)*connection; + + gpiReport(connection, report); +} +#endif diff --git a/code/gamespy/GP/gp.h b/code/gamespy/GP/gp.h new file mode 100644 index 00000000..b0ea430a --- /dev/null +++ b/code/gamespy/GP/gp.h @@ -0,0 +1,1631 @@ +/* +gp.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ +#ifndef _GP_H_ +#define _GP_H_ + +// necessary for gsi_char and UNICODE support +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//ENUMS +//////// +typedef enum _GPEnum +{ + // Callbacks + //////////// + GP_ERROR = 0, + GP_RECV_BUDDY_REQUEST, + GP_RECV_BUDDY_STATUS, + GP_RECV_BUDDY_MESSAGE, + GP_RECV_BUDDY_UTM, + GP_RECV_GAME_INVITE, + GP_TRANSFER_CALLBACK, + GP_RECV_BUDDY_AUTH, + GP_RECV_BUDDY_REVOKE, + + // Global States. + ///////////////// + GP_INFO_CACHING = 0x0100, + GP_SIMULATION, + GP_INFO_CACHING_BUDDY_AND_BLOCK_ONLY, + + // Blocking + /////////// + GP_BLOCKING = 1, + GP_NON_BLOCKING = 0, + + // Firewall + /////////// + GP_FIREWALL = 1, + GP_NO_FIREWALL = 0, + + // Check Cache + ////////////// + GP_CHECK_CACHE = 1, + GP_DONT_CHECK_CACHE = 0, + + // Is Valid Email. + // PANTS|02.15.00 + ////////////////// + GP_VALID = 1, + GP_INVALID = 0, + + // Fatal Error. + /////////////// + GP_FATAL = 1, + GP_NON_FATAL = 0, + + // Sex + ////// + GP_MALE = 0x0500, + GP_FEMALE, + GP_PAT, + + // Profile Search. + ////////////////// + GP_MORE = 0x0600, + GP_DONE, + + // Set Info + /////////// + GP_NICK = 0x0700, + GP_UNIQUENICK, + GP_EMAIL, + GP_PASSWORD, + GP_FIRSTNAME, + GP_LASTNAME, + GP_ICQUIN, + GP_HOMEPAGE, + GP_ZIPCODE, + GP_COUNTRYCODE, + GP_BIRTHDAY, + GP_SEX, + GP_CPUBRANDID, + GP_CPUSPEED, + GP_MEMORY, + GP_VIDEOCARD1STRING, + GP_VIDEOCARD1RAM, + GP_VIDEOCARD2STRING, + GP_VIDEOCARD2RAM, + GP_CONNECTIONID, + GP_CONNECTIONSPEED, + GP_HASNETWORK, + GP_OSSTRING, + GP_AIMNAME, // PANTS|03.20.01 + GP_PIC, + GP_OCCUPATIONID, + GP_INDUSTRYID, + GP_INCOMEID, + GP_MARRIEDID, + GP_CHILDCOUNT, + GP_INTERESTS1, + + // New Profile. + /////////////// + GP_REPLACE = 1, + GP_DONT_REPLACE = 0, + + // Is Connected. + //////////////// + GP_CONNECTED = 1, + GP_NOT_CONNECTED = 0, + + // Public mask. + /////////////// + GP_MASK_NONE = 0x00000000, + GP_MASK_HOMEPAGE = 0x00000001, + GP_MASK_ZIPCODE = 0x00000002, + GP_MASK_COUNTRYCODE = 0x00000004, + GP_MASK_BIRTHDAY = 0x00000008, + GP_MASK_SEX = 0x00000010, + GP_MASK_EMAIL = 0x00000020, + GP_MASK_ALL = 0xFFFFFFFF, + + // Status + ///////// + GP_OFFLINE = 0, + GP_ONLINE = 1, + GP_PLAYING = 2, + GP_STAGING = 3, + GP_CHATTING = 4, + GP_AWAY = 5, + + // Session flags + ///////////////// + GP_SESS_IS_CLOSED = 0x00000001, + GP_SESS_IS_OPEN = 0x00000002, + GP_SESS_HAS_PASSWORD = 0x00000004, + GP_SESS_IS_BEHIND_NAT = 0x00000008, + GP_SESS_IS_RANKED = 0x000000010, + + + // CPU Brand ID + /////////////// + GP_INTEL = 1, + GP_AMD, + GP_CYRIX, + GP_MOTOROLA, + GP_ALPHA, + + // Connection ID. + ///////////////// + GP_MODEM = 1, + GP_ISDN, + GP_CABLEMODEM, + GP_DSL, + GP_SATELLITE, + GP_ETHERNET, + GP_WIRELESS, + + // Transfer callback type. + // *** the transfer is ended when these types are received + ////////////////////////// + GP_TRANSFER_SEND_REQUEST = 0x800, // arg->num == numFiles + GP_TRANSFER_ACCEPTED, + GP_TRANSFER_REJECTED, // *** + GP_TRANSFER_NOT_ACCEPTING, // *** + GP_TRANSFER_NO_CONNECTION, // *** + GP_TRANSFER_DONE, // *** + GP_TRANSFER_CANCELLED, // *** + GP_TRANSFER_LOST_CONNECTION, // *** + GP_TRANSFER_ERROR, // *** + GP_TRANSFER_THROTTLE, // arg->num == Bps + GP_FILE_BEGIN, + GP_FILE_PROGRESS, // arg->num == numBytes + GP_FILE_END, + GP_FILE_DIRECTORY, + GP_FILE_SKIP, + GP_FILE_FAILED, // arg->num == error + + // GP_FILE_FAILED error + /////////////////////// + GP_FILE_READ_ERROR = 0x900, + GP_FILE_WRITE_ERROR, + GP_FILE_DATA_ERROR, + + // Transfer Side. + ///////////////// + GP_TRANSFER_SENDER = 0xA00, + GP_TRANSFER_RECEIVER, + + // UTM send options. + //////////////////// + GP_DONT_ROUTE = 0xB00, // only send direct + + // Quiet mode flags. + //////////////////// + GP_SILENCE_NONE = 0x00000000, + GP_SILENCE_MESSAGES = 0x00000001, + GP_SILENCE_UTMS = 0x00000002, + GP_SILENCE_LIST = 0x00000004, // includes requests, auths, and revokes + GP_SILENCE_ALL = 0xFFFFFFFF, + + // Flags for checking if newer version of status info is supported + GP_NEW_STATUS_INFO_SUPPORTED = 0xC00, + GP_NEW_STATUS_INFO_NOT_SUPPORTED = 0xC01 +} GPEnum; + +//RESULTS +////////// +typedef enum _GPResult +{ + GP_NO_ERROR, + GP_MEMORY_ERROR, + GP_PARAMETER_ERROR, + GP_NETWORK_ERROR, + GP_SERVER_ERROR, + GP_MISC_ERROR, + GP_COUNT +} GPResult; + +//ERROR CODES +///////////// +//#define GP_ERROR_TYPE(errorCode) ((errorCode) >> 8) +typedef enum _GPErrorCode +{ + // General. + /////////// + GP_GENERAL = 0x0000, + GP_PARSE, + GP_NOT_LOGGED_IN, + GP_BAD_SESSKEY, + GP_DATABASE, + GP_NETWORK, + GP_FORCED_DISCONNECT, + GP_CONNECTION_CLOSED, + GP_UDP_LAYER, + + // Login. + ///////// + GP_LOGIN = 0x0100, + GP_LOGIN_TIMEOUT, + + GP_LOGIN_BAD_NICK, + GP_LOGIN_BAD_EMAIL, + GP_LOGIN_BAD_PASSWORD, + GP_LOGIN_BAD_PROFILE, + GP_LOGIN_PROFILE_DELETED, + GP_LOGIN_CONNECTION_FAILED, + GP_LOGIN_SERVER_AUTH_FAILED, + GP_LOGIN_BAD_UNIQUENICK, + GP_LOGIN_BAD_PREAUTH, + + // Newuser. + /////////// + GP_NEWUSER = 0x0200, + GP_NEWUSER_BAD_NICK, + GP_NEWUSER_BAD_PASSWORD, + GP_NEWUSER_UNIQUENICK_INVALID, + GP_NEWUSER_UNIQUENICK_INUSE, + + // Updateui. + //////////// + GP_UPDATEUI = 0x0300, + GP_UPDATEUI_BAD_EMAIL, + + // Newprofile. + ////////////// + GP_NEWPROFILE = 0x0400, + GP_NEWPROFILE_BAD_NICK, + GP_NEWPROFILE_BAD_OLD_NICK, + + // Updatepro. + ///////////// + GP_UPDATEPRO = 0x0500, + GP_UPDATEPRO_BAD_NICK, + + // Addbuddy. + //////////// + GP_ADDBUDDY = 0x0600, + GP_ADDBUDDY_BAD_FROM, + GP_ADDBUDDY_BAD_NEW, + GP_ADDBUDDY_ALREADY_BUDDY, + + // Authadd. + /////////// + GP_AUTHADD = 0x0700, + GP_AUTHADD_BAD_FROM, + GP_AUTHADD_BAD_SIG, + + // Status. + ////////// + GP_STATUS = 0x0800, + + // Bm. + ////// + GP_BM = 0x0900, + GP_BM_NOT_BUDDY, + GP_BM_EXT_INFO_NOT_SUPPORTED, + GP_BM_BUDDY_OFFLINE, + + // Getprofile. + ////////////// + GP_GETPROFILE = 0x0A00, + GP_GETPROFILE_BAD_PROFILE, + + // Delbuddy. + //////////// + GP_DELBUDDY = 0x0B00, + GP_DELBUDDY_NOT_BUDDY, + + // Delprofile. + ///////////// + GP_DELPROFILE = 0x0C00, + GP_DELPROFILE_LAST_PROFILE, + + // Search. + ////////// + GP_SEARCH = 0x0D00, + GP_SEARCH_CONNECTION_FAILED, + GP_SEARCH_TIMED_OUT, + + // Check. + ///////// + GP_CHECK = 0x0E00, + GP_CHECK_BAD_EMAIL, + GP_CHECK_BAD_NICK, + GP_CHECK_BAD_PASSWORD, + + // Revoke. + ////////// + GP_REVOKE = 0x0F00, + GP_REVOKE_NOT_BUDDY, + + // Registeruniquenick. + ////////////////////// + GP_REGISTERUNIQUENICK = 0x1000, + GP_REGISTERUNIQUENICK_TAKEN, + GP_REGISTERUNIQUENICK_RESERVED, + GP_REGISTERUNIQUENICK_BAD_NAMESPACE, + + // Register cdkey. + ////////////////// + GP_REGISTERCDKEY = 0x1100, + GP_REGISTERCDKEY_BAD_KEY, + GP_REGISTERCDKEY_ALREADY_SET, + GP_REGISTERCDKEY_ALREADY_TAKEN, + + // AddBlock. + //////////// + GP_ADDBLOCK = 0x1200, + GP_ADDBLOCK_ALREADY_BLOCKED, + + // RemoveBlock. + /////////////// + GP_REMOVEBLOCK = 0x1300, + GP_REMOVEBLOCK_NOT_BLOCKED + +} GPErrorCode; + +//STRING LENGTHS +//////////////// +#define GP_NICK_LEN 31 +#define GP_UNIQUENICK_LEN 21 +#define GP_FIRSTNAME_LEN 31 +#define GP_LASTNAME_LEN 31 +#define GP_EMAIL_LEN 51 +#define GP_PASSWORD_LEN 31 +#define GP_PASSWORDENC_LEN ((((GP_PASSWORD_LEN+2)*4)/3)+1) +#define GP_HOMEPAGE_LEN 76 +#define GP_ZIPCODE_LEN 11 +#define GP_COUNTRYCODE_LEN 3 +#define GP_PLACE_LEN 128 +#define GP_AIMNAME_LEN 51 +#define GP_REASON_LEN 1025 +#define GP_STATUS_STRING_LEN 256 +#define GP_LOCATION_STRING_LEN 256 +#define GP_ERROR_STRING_LEN 256 +#define GP_AUTHTOKEN_LEN 256 +#define GP_PARTNERCHALLENGE_LEN 256 +#define GP_CDKEY_LEN 65 +#define GP_CDKEYENC_LEN ((((GP_CDKEY_LEN+2)*4)/3)+1) +#define GP_LOGIN_TICKET_LEN 25 + +#define GP_RICH_STATUS_LEN 256 +#define GP_STATUS_BASIC_STR_LEN 33 + +// Random number seed for PASSWORDENC and CDKEYENC +// MUST MATCH SERVER - If you change this, you'll have to +// release an updated server +#define GP_XOR_SEED 0x79707367 // "gspy" + +// Well known values for partner ID. +#define GP_PARTNERID_GAMESPY 0 +#define GP_PARTNERID_IGN 10 + +// Maximum number of namespaces that can be searched for a uniquenick +#define GP_MAX_NAMESPACEIDS 16 + +//TYPES +//////// +// GPConnection +/////////////// +typedef void * GPConnection; + +// GPProfile +//////////// +typedef int GPProfile; + +// GPTransfer +///////////// +typedef int GPTransfer; + +// GPCallback +///////////// +typedef void (* GPCallback)( + GPConnection * connection, + void * arg, + void * param +); + +//STRUCTURES +///////////// +// GPErrorArg +///////////// +typedef struct +{ + GPResult result; + GPErrorCode errorCode; + gsi_char * errorString; + GPEnum fatal; +} GPErrorArg; + +// GPConnectResponseArg +//////////////////////// +typedef struct +{ + GPResult result; + GPProfile profile; + gsi_char uniquenick[GP_UNIQUENICK_LEN]; +} GPConnectResponseArg; + +// GPNewUserResponseArg +/////////////////////// +typedef struct +{ + GPResult result; + GPProfile profile; +} GPNewUserResponseArg; + +// GPCheckResponseArg +///////////////////// +typedef struct +{ + GPResult result; + GPProfile profile; +} GPCheckResponseArg; + +// GPSuggestUniqueNickResponseArg +///////////////////////////////// +typedef struct +{ + GPResult result; + int numSuggestedNicks; + gsi_char ** suggestedNicks; +} GPSuggestUniqueNickResponseArg; + +// GPRegisterUniqueNickResponseArg +////////////////////////////////// +typedef struct +{ + GPResult result; +} GPRegisterUniqueNickResponseArg; + +// GPRegisterCdKeyResponseArg +////////////////////////////////// +typedef struct +{ + GPResult result; +} GPRegisterCdKeyResponseArg; + +// GPNewProfileResponseArg +////////////////////////// +typedef struct +{ + GPResult result; + GPProfile profile; +} GPNewProfileResponseArg; + +// GPDeleteProfileResponseArg +///////////////////////////// + +typedef struct +{ + GPResult result; + GPProfile profile; +} GPDeleteProfileResponseArg; + +// GPProfileSearchMatch +/////////////////////// +typedef struct +{ + GPProfile profile; + gsi_char nick[GP_NICK_LEN]; + gsi_char uniquenick[GP_UNIQUENICK_LEN]; + int namespaceID; + gsi_char firstname[GP_FIRSTNAME_LEN]; + gsi_char lastname[GP_LASTNAME_LEN]; + gsi_char email[GP_EMAIL_LEN]; +} GPProfileSearchMatch; + +// GPProfileSearchResponseArg +///////////////////////////// +typedef struct +{ + GPResult result; + int numMatches; + GPEnum more; + GPProfileSearchMatch * matches; +} GPProfileSearchResponseArg; + +// GPGetInfoResponseArg +/////////////////////// +typedef struct +{ + GPResult result; + GPProfile profile; + gsi_char nick[GP_NICK_LEN]; + gsi_char uniquenick[GP_UNIQUENICK_LEN]; + gsi_char email[GP_EMAIL_LEN]; + gsi_char firstname[GP_FIRSTNAME_LEN]; + gsi_char lastname[GP_LASTNAME_LEN]; + gsi_char homepage[GP_HOMEPAGE_LEN]; + int icquin; + gsi_char zipcode[GP_ZIPCODE_LEN]; + gsi_char countrycode[GP_COUNTRYCODE_LEN]; + float longitude; // negative is west, positive is east. (0, 0) means unknown. + float latitude; // negative is south, positive is north. (0, 0) means unknown. + gsi_char place[GP_PLACE_LEN]; // e.g., "USA|California|Irvine", "South Korea|Seoul", "Turkey" + int birthday; + int birthmonth; + int birthyear; + GPEnum sex; + GPEnum publicmask; + gsi_char aimname[GP_AIMNAME_LEN]; + int pic; + int occupationid; + int industryid; + int incomeid; + int marriedid; + int childcount; + int interests1; + int ownership1; + int conntypeid; +} GPGetInfoResponseArg; + +// GPRecvBuddyRequestArg +//////////////////////// +typedef struct +{ + GPProfile profile; + unsigned int date; + gsi_char reason[GP_REASON_LEN]; +} GPRecvBuddyRequestArg; + +// GPBuddyStatus +//////////////// +typedef struct +{ + GPProfile profile; + GPEnum status; + gsi_char statusString[GP_STATUS_STRING_LEN]; + gsi_char locationString[GP_LOCATION_STRING_LEN]; + unsigned int ip; + int port; + GPEnum quietModeFlags; +} GPBuddyStatus; + + +// BETA +//GPBuddyStatusInfo +/////////////////// +typedef struct +{ + GPProfile profile; + GPEnum statusState; + unsigned int buddyIp; + unsigned short buddyPort; + unsigned int hostIp; + unsigned int hostPrivateIp; + unsigned short queryPort; + unsigned short hostPort; + unsigned int sessionFlags; + gsi_char richStatus[GP_RICH_STATUS_LEN]; + gsi_char gameType[GP_STATUS_BASIC_STR_LEN]; + gsi_char gameVariant[GP_STATUS_BASIC_STR_LEN]; + gsi_char gameMapName[GP_STATUS_BASIC_STR_LEN]; + GPEnum quietModeFlags; + GPEnum newStatusInfoFlag; +} GPBuddyStatusInfo; + +//GPGetBuddyStatusInfoKeysArg +///////////////////////////// +typedef struct +{ + GPProfile profile; + gsi_char **keys; + gsi_char **values; + int numKeys; + +} GPGetBuddyStatusInfoKeysArg; + + +// GPRecvBuddyStatusArg +/////////////////////// +typedef struct +{ + GPProfile profile; + unsigned int date; + int index; +} GPRecvBuddyStatusArg; + +// GPRecvBuddyMessageArg +//////////////////////// +typedef struct +{ + GPProfile profile; + unsigned int date; + gsi_char * message; +} GPRecvBuddyMessageArg; + +typedef struct +{ + GPProfile profile; + unsigned int date; + gsi_char * message; +} GPRecvBuddyUTMArg; + +typedef struct +{ + GPProfile profile; + unsigned int date; +} GPRecvBuddyAuthArg; + +typedef struct +{ + GPProfile profile; + unsigned int date; +} GPRecvBuddyRevokeArg; + +// GPTransferCallbackArg; +///////////////////////// +typedef struct +{ + GPTransfer transfer; + GPEnum type; + int index; + int num; + gsi_char * message; +} GPTransferCallbackArg; + +// GPIsValidEmailResponseArg +//////////////////////////// +typedef struct +{ + GPResult result; + gsi_char email[GP_EMAIL_LEN]; + GPEnum isValid; +} GPIsValidEmailResponseArg; + +// GPGetUserNicksResponseArg +//////////////////////////// +typedef struct +{ + GPResult result; + gsi_char email[GP_EMAIL_LEN]; + int numNicks; // If 0, then the email/password did not match. + gsi_char ** nicks; + gsi_char ** uniquenicks; +} GPGetUserNicksResponseArg; + +// GPRecvGameInviteArg +////////////////////// +typedef struct +{ + GPProfile profile; + int productID; + gsi_char location[GP_LOCATION_STRING_LEN]; +} GPRecvGameInviteArg; + +// GPFindPlayerMatch +//////////////////// +typedef struct +{ + GPProfile profile; + gsi_char nick[GP_NICK_LEN]; + GPEnum status; + gsi_char statusString[GP_STATUS_STRING_LEN]; +} GPFindPlayerMatch; + +// GPFindPlayersResponseArg +/////////////////////////// +typedef struct +{ + GPResult result; + int productID; //PANTS|06.06.00 - added by request for JED + int numMatches; + GPFindPlayerMatch * matches; +} GPFindPlayersResponseArg; + +// GPGetReverseBuddiesResponseArg +///////////////////////////////// +typedef struct +{ + GPResult result; + int numProfiles; + GPProfileSearchMatch * profiles; +} GPGetReverseBuddiesResponseArg; + +typedef struct +{ + GPProfile profile; + gsi_char uniqueNick[GP_UNIQUENICK_LEN]; +} GPUniqueMatch; + +typedef struct +{ + GPResult result; + int numOfUniqueMatchs; + GPUniqueMatch *matches; +} GPGetReverseBuddiesListResponseArg; + + +//GLOBALS +///////// +/* The hostnames of the connection manager +server and the search manager server. +If the app resolves either or both hostnames, +the IP(s) can be stored in the string(s) before +calling gpInitialize */ +extern char GPConnectionManagerHostname[64]; +extern char GPSearchManagerHostname[64]; + + +//FUNCTIONS +//////////// +#ifndef GSI_UNICODE +#define gpConnect gpConnectA +#define gpConnectNewUser gpConnectNewUserA +#define gpConnectUniqueNick gpConnectUniqueNickA +#define gpConnectPreAuthenticated gpConnectPreAuthenticatedA +#define gpCheckUser gpCheckUserA +#define gpNewUser gpNewUserA +#define gpSuggestUniqueNick gpSuggestUniqueNickA +#define gpRegisterUniqueNick gpRegisterUniqueNickA +#define gpRegisterCdKey gpRegisterCdKeyA +#define gpGetErrorString gpGetErrorStringA +#define gpNewProfile gpNewProfileA +#define gpProfileSearch gpProfileSearchA +#define gpProfileSearchUniquenick gpProfileSearchUniquenickA +#define gpSetInfos gpSetInfosA +#define gpSendBuddyRequest gpSendBuddyRequestA +#ifndef GP_NEW_STATUS_INFO +#define gpSetStatus gpSetStatusA +#endif +#ifdef GP_NEW_STATUS_INFO +// BETA +#define gpSetStatusInfo gpSetStatusInfoA +#endif +#define gpSendBuddyMessage gpSendBuddyMessageA +#define gpSendBuddyUTM gpSendBuddyUTMA +#define gpIsValidEmail gpIsValidEmailA +#define gpGetUserNicks gpGetUserNicksA +#define gpSetInfoCacheFilename gpSetInfoCacheFilenameA +#define gpSendFiles gpSendFilesA +#define gpAcceptTransfer gpAcceptTransferA +#define gpRejectTransfer gpRejectTransferA +#define gpSetTransferDirectory gpSetTransferDirectoryA +#define gpGetFileName gpGetFileNameA +#define gpGetFilePath gpGetFilePathA +#define gpInvitePlayer gpInvitePlayerA +#ifdef GP_NEW_STATUS_INFO +// BETA +#define gpAddStatusInfoKey gpAddStatusInfoKeyA +#define gpSetStatusInfoKey gpSetStatusInfoKeyA +#define gpGetStatusInfoKeyVal gpGetStatusInfoKeyValA +#define gpDelStatusInfoKey gpDelStatusInfoKeyA +#endif +#else +#define gpConnect gpConnectW +#define gpConnectNewUser gpConnectNewUserW +#define gpConnectUniqueNick gpConnectUniqueNickW +#define gpConnectPreAuthenticated gpConnectPreAuthenticatedW +#define gpCheckUser gpCheckUserW +#define gpNewUser gpNewUserW +#define gpSuggestUniqueNick gpSuggestUniqueNickW +#define gpRegisterUniqueNick gpRegisterUniqueNickW +#define gpRegisterCdKey gpRegisterCdKeyW +#define gpGetErrorString gpGetErrorStringW +#define gpNewProfile gpNewProfileW +#define gpProfileSearch gpProfileSearchW +#define gpProfileSearchUniquenick gpProfileSearchUniquenickW +#define gpSetInfos gpSetInfosW +#define gpSendBuddyRequest gpSendBuddyRequestW +#ifndef GP_NEW_STATUS_INFO +#define gpSetStatus gpSetStatusW +#endif +#ifdef GP_NEW_STATUS_INFO +// BETA +#define gpSetStatusInfo gpSetStatusInfoW +#endif +#define gpSendBuddyMessage gpSendBuddyMessageW +#define gpSendBuddyUTM gpSendBuddyUTMW +#define gpIsValidEmail gpIsValidEmailW +#define gpGetUserNicks gpGetUserNicksW +#define gpSetInfoCacheFilename gpSetInfoCacheFilenameW +#define gpSendFiles gpSendFilesW +#define gpAcceptTransfer gpAcceptTransferW +#define gpRejectTransfer gpRejectTransferW +#define gpSetTransferDirectory gpSetTransferDirectoryW +#define gpGetFileName gpGetFileNameW +#define gpGetFilePath gpGetFilePathW +#define gpInvitePlayer gpInvitePlayerW +// #ifdef GP_NEW_STATUS_INFO +// BETA +// #define gpAddStatusInfoKey gpAddStatusInfoKeyW +// #define gpSetStatusInfoKey gpSetStatusInfoKeyW +// #define gpGetStatusInfoKeyVal gpGetStatusInfoKeyValW +// #define gpDelStatusInfoKey gpDelStatusInfoKeyW +// #endif +#endif + +// gpInitialize +/////////////// +GPResult gpInitialize +( + GPConnection * connection, + int productID, // The productID is a unique ID that identifies your product + int namespaceID, // The namespaceID identified which namespace to login under. A namespaceID of 0 indicates that no + // namespace should be used. A namespaceID of 1 represents the default GameSpy namespace + int partnerID // The partnerID identifies the account system being used. + // Use GP_PARTNERID_GAMESPY for GSID accounts. + // Use GP_PARTNERID_IGN for IGN accounts. +); + +// gpDestroy +//////////// +void gpDestroy( + GPConnection * connection +); + +// gpEnable +/////////// +GPResult gpEnable +( + GPConnection * connection, + GPEnum state +); + +// gpDisable +//////////// +GPResult gpDisable +( + GPConnection * connection, + GPEnum state +); + +// gpProcess +//////////// +GPResult gpProcess +( + GPConnection * connection +); + +// gpSetCallback +//////////////// +GPResult gpSetCallback +( + GPConnection * connection, + GPEnum func, + GPCallback callback, + void * param +); + +// gpConnect +//////////// +GPResult gpConnect +( + GPConnection * connection, + const gsi_char nick[GP_NICK_LEN], + const gsi_char email[GP_EMAIL_LEN], + const gsi_char password[GP_PASSWORD_LEN], + GPEnum firewall, + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpConnectNewUser +/////////////////// +GPResult gpConnectNewUser +( + GPConnection * connection, + const gsi_char nick[GP_NICK_LEN], + const gsi_char uniquenick[GP_UNIQUENICK_LEN], + const gsi_char email[GP_EMAIL_LEN], + const gsi_char password[GP_PASSWORD_LEN], + const gsi_char cdkey[GP_CDKEY_LEN], + GPEnum firewall, + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpConnectUniqueNick +////////////////////// +GPResult gpConnectUniqueNick +( + GPConnection * connection, + const gsi_char uniquenick[GP_UNIQUENICK_LEN], + const gsi_char password[GP_PASSWORD_LEN], + GPEnum firewall, + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpConnectPreAuthenticated +//////////////////////////// +GPResult gpConnectPreAuthenticated +( + GPConnection * connection, + const gsi_char authtoken[GP_AUTHTOKEN_LEN], + const gsi_char partnerchallenge[GP_PARTNERCHALLENGE_LEN], + GPEnum firewall, + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpDisconnect +/////////////// +void gpDisconnect +( + GPConnection * connection +); + +// gpIsConnected +//////////////// +GPResult gpIsConnected +( + GPConnection * connection, + GPEnum * connected +); + +// gpCheckUser +////////////// +GPResult gpCheckUser +( + GPConnection * connection, + const gsi_char nick[GP_NICK_LEN], + const gsi_char email[GP_EMAIL_LEN], + const gsi_char password[GP_PASSWORD_LEN], + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpNewUser +//////////// +GPResult gpNewUser +( + GPConnection * connection, + const gsi_char nick[GP_NICK_LEN], + const gsi_char uniquenick[GP_UNIQUENICK_LEN], + const gsi_char email[GP_EMAIL_LEN], + const gsi_char password[GP_PASSWORD_LEN], + const gsi_char cdkey[GP_CDKEY_LEN], + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpSuggestUniqueNick +////////////////////// +GPResult gpSuggestUniqueNick +( + GPConnection * connection, + const gsi_char desirednick[GP_UNIQUENICK_LEN], + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpRegisterUniqueNick +/////////////////////// +GPResult gpRegisterUniqueNick +( + GPConnection * connection, + const gsi_char uniquenick[GP_UNIQUENICK_LEN], + const gsi_char cdkey[GP_CDKEY_LEN], + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpRegisterCdKey +/////////////////////// +GPResult gpRegisterCdKey +( + GPConnection * connection, + const gsi_char cdkey[GP_CDKEY_LEN], + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpGetErrorCode +///////////////// +GPResult gpGetErrorCode( + GPConnection * connection, + GPErrorCode * errorCode +); + +// gpGetErrorString +/////////////////// +GPResult gpGetErrorString( + GPConnection * connection, + gsi_char errorString[GP_ERROR_STRING_LEN] +); + +// gpNewProfile +/////////////// +GPResult gpNewProfile( + GPConnection * connection, + const gsi_char nick[GP_NICK_LEN], + GPEnum replace, + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpDeleteProfile +////////////////// +GPResult gpDeleteProfile( + GPConnection * connection, + GPCallback callback, + void * param +); + +// gpProfileFromID +// PANTS|09.11.00 - A GPProfile is now the same +// as a profileid. This function is no longer needed +// and will be removed in a future version of GP. +///////////////////////////////////////////////////// +GPResult gpProfileFromID( + GPConnection * connection, + GPProfile * profile, + int id +); + +// gpIDFromProfile +// PANTS|09.11.00 - A GPProfile is now the same +// as a profileid. This function is no longer needed +// and will be removed in a future version of GP. +///////////////////////////////////////////////////// +GPResult gpIDFromProfile( + GPConnection * connection, + GPProfile profile, + int * id +); + +// gpUserIDFromProfile +////////////////// +GPResult gpUserIDFromProfile( + GPConnection * connection, + GPProfile profile, + int * userid +); + +// gpProfileSearch +////////////////// +GPResult gpProfileSearch( + GPConnection * connection, + const gsi_char nick[GP_NICK_LEN], + const gsi_char uniquenick[GP_UNIQUENICK_LEN], + const gsi_char email[GP_EMAIL_LEN], + const gsi_char firstname[GP_FIRSTNAME_LEN], + const gsi_char lastname[GP_LASTNAME_LEN], + int icquin, + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpProfileSearchUniquenick +//////////////////////////// +GPResult gpProfileSearchUniquenick( + GPConnection * connection, + const gsi_char uniquenick[GP_UNIQUENICK_LEN], + const int namespaceIDs[GP_MAX_NAMESPACEIDS], + int numNamespaces, + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpGetInfo +//////////// +GPResult gpGetInfo( + GPConnection * connection, + GPProfile profile, + GPEnum checkCache, + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpGetInfoNoWait +////////////////// +GPResult gpGetInfoNoWait( + GPConnection * connection, + GPProfile profile, + GPGetInfoResponseArg * arg +); + +// gpSetInfoi +///////////// +GPResult gpSetInfoi( + GPConnection * connection, + GPEnum info, + int value +); + +// gpSetInfos +///////////// +GPResult gpSetInfos( + GPConnection * connection, + GPEnum info, + const gsi_char * value +); + +// gpSetInfod +///////////// +GPResult gpSetInfod( + GPConnection * connection, + GPEnum info, + int day, + int month, + int year +); + +// gpSetInfoMask +//////////////// +GPResult gpSetInfoMask( + GPConnection * connection, + GPEnum mask +); + +// gpSendBuddyRequest +///////////////////// +GPResult gpSendBuddyRequest( + GPConnection * connection, + GPProfile profile, + const gsi_char reason[GP_REASON_LEN] +); + +// gpAuthBuddyRequest +///////////////////// +GPResult gpAuthBuddyRequest( + GPConnection * connection, + GPProfile profile +); + +// gpDenyBuddyRequest +// PANTS|09.11.00 +///////////////////// +GPResult gpDenyBuddyRequest( + GPConnection * connection, + GPProfile profile +); + +// gpDeleteBuddy +//////////////// +GPResult gpDeleteBuddy( + GPConnection * connection, + GPProfile profile +); + +// gpAddToBlockedList +///////////////////// +GPResult gpAddToBlockedList( + GPConnection * connection, + GPProfile profile +); + +// gpRemoveFromBlockedList +///////////////////// +GPResult gpRemoveFromBlockedList( + GPConnection * connection, + GPProfile profile +); + +// gpGetNumBlocked +////////////////// +GPResult gpGetNumBlocked( + GPConnection * connection, + int * numBlocked +); + +// gpGetBlockedProfile +///////////////////// +GPResult gpGetBlockedProfile( + GPConnection * connection, + int index, + GPProfile * profile +); + +// gpIsBlocked +// returns gsi_true if blocked, gsi_false if not blocked +//////////////////////////////////////////////////////// +gsi_bool gpIsBlocked( + GPConnection * connection, + GPProfile profile +); + +// gpGetNumBuddies +////////////////// +GPResult gpGetNumBuddies( + GPConnection * connection, + int * numBuddies +); + +// gpGetBuddyStatus +/////////////////// +#ifndef GP_NEW_STATUS_INFO +GPResult gpGetBuddyStatus( + GPConnection * connection, + int index, + GPBuddyStatus * status +); +#endif + +#ifdef GP_NEW_STATUS_INFO +// +////////////////////////////// +GPResult gpGetBuddyStatusInfo( + GPConnection * connection, + int index, + GPBuddyStatusInfo * statusInfo +); + +GPResult gpSetBuddyAddr( + GPConnection *connection, + int index, + unsigned int buddyIp, + unsigned short buddyPort +); +#endif +// gpGetBuddyIndex +////////////////// +GPResult gpGetBuddyIndex( + GPConnection * connection, + GPProfile profile, + int * index +); + +// gpIsBuddy +// returns 1 if a buddy, 0 if not a buddy +//////////// +int gpIsBuddy( + GPConnection * connection, + GPProfile profile +); + +int gpIsBuddyConnectionOpen( + GPConnection * connection, + GPProfile profile +); + +// gpSetStatus +////////////// +#ifndef GP_NEW_STATUS_INFO +GPResult gpSetStatus( + GPConnection * connection, + GPEnum status, + const gsi_char statusString[GP_STATUS_STRING_LEN], + const gsi_char locationString[GP_LOCATION_STRING_LEN] +); +#endif + +#ifdef GP_NEW_STATUS_INFO +GPResult gpSetStatusInfo( + GPConnection *connection, + GPEnum statusState, + unsigned int hostIp, + unsigned int hostPrivateIp, + unsigned short queryPort, + unsigned short hostPort, + unsigned int sessionFlags, + const gsi_char *richStatus, + int richStatusLen, + const gsi_char *gameType, + int gameTypeLen, + const gsi_char *gameVariant, + int gameVariantLen, + const gsi_char *gameMapName, + int gameMapNameLen +); + +GPResult gpAddStatusInfoKey(GPConnection *connection, const gsi_char *keyName, const gsi_char *keyValue); +GPResult gpSetStatusInfoKey(GPConnection *connection, const gsi_char *keyName, const gsi_char *keyValue); +GPResult gpGetStatusInfoKeyVal(GPConnection *connection, const gsi_char *keyName, gsi_char **keyValue); +GPResult gpDelStatusInfoKey(GPConnection *connection, const gsi_char *keyName); + +GPResult gpGetBuddyStatusInfoKeys(GPConnection *connection, int index, GPCallback callback, void *userData); +#endif +// gpSendBuddyMessage +///////////////////// +GPResult gpSendBuddyMessage( + GPConnection * connection, + GPProfile profile, + const gsi_char * message +); + +// gpSendBuddyUTM +///////////////////// +GPResult gpSendBuddyUTM( + GPConnection * connection, + GPProfile profile, + const gsi_char * message, + int sendOption // GP_DONT_ROUTE +); + + +// PANTS|02.15.00 +// Added gpIsValidEmail and gpGetUserNicks for login wizard. +//////////////////////////////////////////////////////////// + +// gpIsValidEmail +///////////////// +GPResult gpIsValidEmail( + GPConnection * connection, + const gsi_char email[GP_EMAIL_LEN], + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpGetUserNicks +///////////////// +GPResult gpGetUserNicks( + GPConnection * connection, + const gsi_char email[GP_EMAIL_LEN], + const gsi_char password[GP_PASSWORD_LEN], + GPEnum blocking, + GPCallback callback, + void * param +); + +// *DEPRECATED* +// gpSetInvitableGames +////////////////////// +GPResult gpSetInvitableGames( + GPConnection * connection, + int numProductIDs, + const int * productIDs +); + +// *DEPRECATED* +// gpFindPlayers +//////////////// +GPResult gpFindPlayers( + GPConnection * connection, + int productID, + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpInvitePlayer +///////////////// +GPResult gpInvitePlayer( + GPConnection * connection, + GPProfile profile, + int productID, + const gsi_char location[GP_LOCATION_STRING_LEN] +); + +// gpGetReverseBuddies +// Get profiles that have you on their buddy list. +////////////////////////////////////////////////// +GPResult gpGetReverseBuddies( + GPConnection * connection, + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpGetReverseBuddiesList +// Get profile ids and unique nicks for profiles +// that have you on their buddy list. +////////////////////////////////////////////////// +GPResult gpGetReversBuddiesList( GPConnection * connection, + GPProfile *targets, int numOfTargets, + GPEnum blocking, + GPCallback callback, + void * param +); + +// gpRevokeBuddyAuthorization +///////////////////////////// +GPResult gpRevokeBuddyAuthorization( + GPConnection * connection, + GPProfile profile +); + +// gpSetCdKey +GPResult gpSetCdKey( + GPConnection * connection, + const gsi_char cdkeyhash, + GPCallback callback +); + +// gpGetLoginTicket +///////////////////////////// +GPResult gpGetLoginTicket( + GPConnection * connection, + char loginTicket[GP_LOGIN_TICKET_LEN] +); + +// gpSetQuietMode +///////////////// +GPResult gpSetQuietMode( + GPConnection * connection, + GPEnum flags +); + +#ifndef NOFILE + +// gpiSetInfoCacheFilename +// Should be called before gpIntialize. +/////////////////////////////////////// +void gpSetInfoCacheFilename( + const gsi_char * filename +); + +/////////////////// +// FILE TRANSFER // +/////////////////// +typedef void (* gpSendFilesCallback)( + GPConnection * connection, + int index, + const gsi_char ** path, + const gsi_char ** name, + void * param +); + +GPResult gpSendFiles( + GPConnection * connection, + GPTransfer * transfer, + GPProfile profile, + const gsi_char * message, + gpSendFilesCallback callback, + void * param +); + +GPResult gpAcceptTransfer( + GPConnection * connection, + GPTransfer transfer, + const gsi_char * message +); + +GPResult gpRejectTransfer( + GPConnection * connection, + GPTransfer transfer, + const gsi_char * message +); + +GPResult gpFreeTransfer( + GPConnection * connection, + GPTransfer transfer +); + +GPResult gpSetTransferData( + GPConnection * connection, + GPTransfer transfer, + void * userData +); + +void * gpGetTransferData( + GPConnection * connection, + GPTransfer transfer +); + +GPResult gpSetTransferDirectory( + GPConnection * connection, + GPTransfer transfer, + const gsi_char * directory +); + +// NOTE: THROTTLING IS NOT CURRENTLY IMPLEMENTED +GPResult gpSetTransferThrottle( + GPConnection * connection, + GPTransfer transfer, + int throttle +); + +// NOTE: THROTTLING IS NOT CURRENTLY IMPLEMENTED +GPResult gpGetTransferThrottle( + GPConnection * connection, + GPTransfer transfer, + int * throttle +); + +GPResult gpGetTransferProfile( + GPConnection * connection, + GPTransfer transfer, + GPProfile * profile +); + +GPResult gpGetTransferSide( + GPConnection * connection, + GPTransfer transfer, + GPEnum * side +); + +GPResult gpGetTransferSize( + GPConnection * connection, + GPTransfer transfer, + int * size +); + +GPResult gpGetTransferProgress( + GPConnection * connection, + GPTransfer transfer, + int * progress +); + +GPResult gpGetNumFiles( + GPConnection * connection, + GPTransfer transfer, + int * num +); + +GPResult gpGetCurrentFile( + GPConnection * connection, + GPTransfer transfer, + int * index +); + +GPResult gpSkipFile( + GPConnection * connection, + GPTransfer transfer, + int index +); + +GPResult gpGetFileName( + GPConnection * connection, + GPTransfer transfer, + int index, + gsi_char ** name +); + +GPResult gpGetFilePath( + GPConnection * connection, + GPTransfer transfer, + int index, + gsi_char ** path +); + +GPResult gpGetFileSize( + GPConnection * connection, + GPTransfer transfer, + int index, + int * size +); + +GPResult gpGetFileProgress( + GPConnection * connection, + GPTransfer transfer, + int index, + int * progress +); + +GPResult gpGetFileModificationTime( + GPConnection * connection, + GPTransfer transfer, + int index, + gsi_time * modTime +); + +GPResult gpGetNumTransfers( + GPConnection * connection, + int * num +); + +GPResult gpGetTransfer( + GPConnection * connection, + int index, + GPTransfer * transfer +); +#endif + +#ifdef _DEBUG +// gpProfilesReport +// PANTS|09.11.00 +/////////////////// +void gpProfilesReport( + GPConnection * connection, + void (* report)(const char * output) +); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/GP/gpi.c b/code/gamespy/GP/gpi.c new file mode 100644 index 00000000..f9535e04 --- /dev/null +++ b/code/gamespy/GP/gpi.c @@ -0,0 +1,816 @@ +/* +gpi.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +//INCLUDES +////////// +#include +#include +#include "gpi.h" + +// DEFINES +////////// +#define KEEPALIVE_TIMEOUT (60 * 2000) + +// This is so VisualAssist will know about these functions. +/////////////////////////////////////////////////////////// +#if 0 +void MD5Init(MD5_CTX *); +void MD5Update(MD5_CTX *, unsigned char *, unsigned int); +void MD5Final(unsigned char [16], MD5_CTX *); +void MD5Print(unsigned char [16], char[33]); +void MD5Digest(unsigned char *, unsigned int, char[33]); +#endif + +//FUNCTIONS +/////////// +GPResult +gpiInitialize( + GPConnection * connection, + int productID, + int namespaceID, + int partnerID +) +{ + GPIConnection * iconnection; + int i; + GPResult result; + + // Set the connection to NULL in case of error. + /////////////////////////////////////////////// + *connection = NULL; + + // Allocate the connection. + /////////////////////////// + iconnection = (GPIConnection *)gsimalloc(sizeof(GPIConnection)); + if(iconnection == NULL) + return GP_MEMORY_ERROR; + + // Initialize connection-specific variables. + //////////////////////////////////////////// + memset(iconnection, 0, sizeof(GPIConnection)); + iconnection->errorString[0] = '\0'; + iconnection->errorCode = (GPErrorCode)0; + iconnection->infoCaching = GPITrue; + iconnection->infoCachingBuddyAndBlockOnly = GPIFalse; + iconnection->simulation = GPIFalse; + iconnection->firewall = GPIFalse; + iconnection->productID = productID; + iconnection->namespaceID = namespaceID; + iconnection->partnerID = partnerID; + +#ifdef GSI_UNICODE + iconnection->errorString_W[0] = '\0'; +#endif + + if(!gpiInitProfiles((GPConnection *)&iconnection)) + { + freeclear(iconnection); + return GP_MEMORY_ERROR; + } + iconnection->diskCache = NULL; + for(i = 0 ; i < GPI_NUM_CALLBACKS ; i++) + { + iconnection->callbacks[i].callback = NULL; + iconnection->callbacks[i].param = NULL; + } + + // Reset connection-specific stuff. + /////////////////////////////////// + result = gpiReset((GPConnection *)&iconnection); + if(result != GP_NO_ERROR) + { + gpiDestroy((GPConnection *)&iconnection); + return result; + } + + // Initialize the sockets library. + ////////////////////////////////// + SocketStartUp(); + + // Seed the random number generator. + //////////////////////////////////// + srand((unsigned int)current_time()); + +#ifndef NOFILE + // Load profiles cached on disk. + //////////////////////////////// + result = gpiLoadDiskProfiles((GPConnection *)&iconnection); + if(result != GP_NO_ERROR) + { + gpiDestroy((GPConnection *)&iconnection); + return result; + } +#endif + +#ifndef NOFILE + result = gpiInitTransfers((GPConnection *)&iconnection); + if(result != GP_NO_ERROR) + { + gpiDestroy((GPConnection *)&iconnection); + return result; + } +#endif + + // Set the connection. + ////////////////////// + *connection = (GPConnection)iconnection; + + return GP_NO_ERROR; +} + +void +gpiDestroy( + GPConnection * connection +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Cleanup connection-specific stuff. + ///////////////////////////////////// + gpiDisconnect(connection, GPITrue); + gpiStatusInfoKeysDestroy(connection); + +#ifdef _PS3 + // Destroy NP + ///////////// + if (iconnection->npInitialized) + gpiDestroyNpBasic(connection); +#endif + +#ifndef NOFILE + // Write the profile info to disk. + // BD - Don't update if we never connected. + ////////////////////////////////// + if(iconnection->infoCaching && iconnection->connectState != GPI_NOT_CONNECTED) + { + if(gpiSaveDiskProfiles(connection) != GP_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_HotError, + "Error saving profiles to disk."); + } + } +#endif + + // Free the profile list. + ///////////////////////// + TableFree(iconnection->profileList.profileTable); + +#ifndef NOFILE + // Free the transfers. + ////////////////////// + gpiCleanupTransfers(connection); +#endif + + // Free the memory. + /////////////////// + freeclear(iconnection); + + // Set the connection pointer to NULL. + ////////////////////////////////////// + *connection = NULL; +} + +static GPIBool +gpiResetProfile( + GPConnection * connection, + GPIProfile * profile, + void * data +) +{ + GSI_UNUSED(connection); + GSI_UNUSED(data); + + profile->buddyStatus = NULL; + profile->buddyStatusInfo = NULL; + profile->authSig = NULL; + profile->requestCount = 0; + profile->peerSig = NULL; + profile->blocked = gsi_false; + profile->buddyOrBlockCache = gsi_false; + + return GPITrue; +} + +GPResult +gpiReset( + GPConnection * connection +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPResult result; + iconnection->nick[0] = '\0'; + iconnection->uniquenick[0] = '\0'; + iconnection->email[0] = '\0'; + iconnection->cmSocket = INVALID_SOCKET; + iconnection->connectState = GPI_NOT_CONNECTED; + + iconnection->socketBuffer.len = 0; + iconnection->socketBuffer.pos = 0; + iconnection->socketBuffer.size = 0; + freeclear(iconnection->socketBuffer.buffer); + iconnection->socketBuffer.buffer = NULL; + + iconnection->inputBufferSize = 0; + freeclear(iconnection->inputBuffer); + iconnection->inputBuffer = NULL; + + iconnection->outputBuffer.len = 0; + iconnection->outputBuffer.pos = 0; + iconnection->outputBuffer.size = 0; + freeclear(iconnection->outputBuffer.buffer); + iconnection->outputBuffer.buffer = NULL; + + iconnection->updateproBuffer.len = 0; + iconnection->updateproBuffer.pos = 0; + iconnection->updateproBuffer.size = 0; + freeclear(iconnection->updateproBuffer.buffer); + iconnection->updateproBuffer.buffer = NULL; + + iconnection->updateuiBuffer.len = 0; + iconnection->updateuiBuffer.pos = 0; + iconnection->updateuiBuffer.size = 0; + freeclear(iconnection->updateuiBuffer.buffer); + iconnection->updateuiBuffer.buffer = NULL; + gpiStatusInfoKeysDestroy(connection); + result = gpiStatusInfoKeysInit((GPConnection *)&iconnection); + if (result != GP_NO_ERROR) + { + gpiDestroy((GPConnection *)&iconnection); + return result; + } + //iconnection->peerSocket = INVALID_SOCKET; + iconnection->nextOperationID = 2; + while(iconnection->operationList != NULL) + gpiRemoveOperation(connection, iconnection->operationList); + iconnection->operationList = NULL; + iconnection->profileList.numBuddies = 0; + iconnection->profileList.numBlocked = 0; + gpiProfileMap(connection, gpiResetProfile, NULL); + iconnection->userid = 0; + iconnection->profileid = 0; + iconnection->sessKey = 0; + iconnection->numSearches = 0; + iconnection->fatalError = GPIFalse; + iconnection->peerList = NULL; + iconnection->lastStatusState = (GPEnum)-1; + iconnection->lastStatusString[0] = '\0'; + iconnection->lastLocationString[0] = '\0'; + iconnection->kaTransmit = 0; + +#ifdef GSI_UNICODE + iconnection->nick_W[0] = '\0'; + iconnection->uniquenick_W[0] = '\0'; + iconnection->email_W[0] = '\0'; + iconnection->lastStatusString_W[0] = '\0'; + iconnection->lastLocationString_W[0] = '\0'; +#endif + + return GP_NO_ERROR; +} + +GPResult +gpiProcessConnectionManager( + GPConnection * connection +) +{ + char * next; + char * str; + int id; + GPIOperation * operation; + char * tempPtr; + int len; + GPIBool connClosed = GPIFalse; + GPIConnection * iconnection = (GPIConnection*)*connection; + GPResult result; + GPIBool loop; + gsi_time now = current_time(); + + // Loop through the rest while waiting for any blocking operations. + /////////////////////////////////////////////////////////////////// + do + { + // Add any waiting info to the output buffer. + ///////////////////////////////////////////// + gpiAddLocalInfo(connection, &iconnection->outputBuffer); + + // Send anything that needs to be sent. + /////////////////////////////////////// + if ( iconnection->outputBuffer.len > 0 ) + iconnection->kaTransmit = now; // data already being transmitted. We don't need to send keep alives + CHECK_RESULT(gpiSendFromBuffer(connection, iconnection->cmSocket, &iconnection->outputBuffer, &connClosed, GPITrue, "CM")); + + // Read everything the connection manager sent. + /////////////////////////////////////////////// + result = gpiRecvToBuffer(connection, iconnection->cmSocket, &iconnection->socketBuffer, &len, &connClosed, "CM"); + if(result != GP_NO_ERROR) + { + if(result == GP_NETWORK_ERROR) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error reading from the server."); + + return result; + } + + // Check if we have a completed command. + //////////////////////////////////////// + while((next = strstr(iconnection->socketBuffer.buffer, "\\final\\")) != NULL) + { + // Received command. Connection is still valid + ////////////////////////////////////////////// + iconnection->kaTransmit = now; + + // NUL terminate the command. + ///////////////////////////// + next[0] = '\0'; + + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_RawDump, + "CMD: %s\n", iconnection->socketBuffer.buffer); + + // Copy the command to the input buffer. + //////////////////////////////////////// + len = (next - iconnection->socketBuffer.buffer); + if(len > iconnection->inputBufferSize) + { + iconnection->inputBufferSize += max(GPI_READ_SIZE, len); + tempPtr = (char*)gsirealloc(iconnection->inputBuffer, (unsigned int)iconnection->inputBufferSize + 1); + if(tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + iconnection->inputBuffer = tempPtr; + } + memcpy(iconnection->inputBuffer, iconnection->socketBuffer.buffer, (unsigned int)len + 1); + + // Point to the start of the next one. + ////////////////////////////////////// + next += 7; + + // Move the rest of the connect buffer up to the front. + /////////////////////////////////////////////////////// + iconnection->socketBuffer.len -= (next - iconnection->socketBuffer.buffer); + memmove(iconnection->socketBuffer.buffer, next, (unsigned int)iconnection->socketBuffer.len + 1); + + // Check for an id. + /////////////////// + str = strstr(iconnection->inputBuffer, "\\id\\"); + if(str != NULL) + { + // Get the id. + ////////////// + id = atoi(str + 4); + + // Try and match the id with an operation. + ////////////////////////////////////////// + if(!gpiFindOperationByID(connection, &operation, id)) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_HotError, + "No matching operation found for id %d\n", id); + } + else + { + // Process the operation. + ///////////////////////// + CHECK_RESULT(gpiProcessOperation(connection, operation, iconnection->inputBuffer)); + } + } + // This is an unsolicited message. + ////////////////////////////////// + else + { + // Check for an error. + ////////////////////// + if(gpiCheckForError(connection, iconnection->inputBuffer, GPITrue)) + { + return GP_SERVER_ERROR; + } + else if(strncmp(iconnection->inputBuffer, "\\bm\\", 4) == 0) + { + CHECK_RESULT(gpiProcessRecvBuddyMessage(connection, iconnection->inputBuffer)); + } + else if(strncmp(iconnection->inputBuffer, "\\ka\\", 4) == 0) + { + // Ignore the keep-alive. + ///////////////////////// + } + else if(strncmp(iconnection->inputBuffer, "\\lt\\", 4) == 0) + { + // Process the login ticket + ///////////////////////// + gpiValueForKey(iconnection->inputBuffer, "\\lt\\", iconnection->loginTicket, sizeof(iconnection->loginTicket)); + } + else if(strncmp(iconnection->inputBuffer, "\\bsi\\", 5) == 0) + { + CHECK_RESULT(gpiProcessRecvBuddyStatusInfo(connection, iconnection->inputBuffer)); + } + else if(strncmp(iconnection->inputBuffer, "\\bdy\\", 5) == 0) + { + // Process the buddy list - retrieved upon login before final login response + // * Note: this only gets the list of your buddies so at least you'll know who + // is a buddy while the status of each is asynchronously updated. + ////////////////////////////////////////////////////////////////////////////// + CHECK_RESULT(gpiProcessRecvBuddyList(connection, iconnection->inputBuffer)); + } + else if(strncmp(iconnection->inputBuffer, "\\blk\\", 5) == 0) + { + // Process the block list - retrieved upon login before final login response + ////////////////////////////////////////////////////////////////////////////// + CHECK_RESULT(gpiProcessRecvBlockedList(connection, iconnection->inputBuffer)); + } + else + { + // This is an unrecognized message. + /////////////////////////////////// + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_HotError, + "Received an unrecognized message.\n"); + } + } + } + + + // Check for a closed connection. + ///////////////////////////////// + if(connClosed && iconnection->connectState != GPI_PROFILE_DELETING) + { + // We've been disconnected. + /////////////////////////// + // Let gpiDisconnect change the state to GPI_DISCONNECTED + //iconnection->connectState = GPI_DISCONNECTED; + gpiSetError(connection, GP_CONNECTION_CLOSED, "The server has closed the connection."); + gpiCallErrorCallback(connection, GP_NETWORK_ERROR, GP_FATAL); + return GP_NO_ERROR; + } + + //PANTS|05.23.00 - removed sleep + //crt - added it back 6/13/00 + //PANTS|07.10.00 - only sleep if looping + loop = gpiOperationsAreBlocking(connection); + if(loop) + msleep(10); + } + while(loop); + + // Send Keep-Alive. Just need TCP to ack the data + ///////////////////////////////////////////////// + if ( now - iconnection->kaTransmit > KEEPALIVE_TIMEOUT ) + { + // keep alive packet will be sent next think + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\ka\\\\final\\"); + iconnection->kaTransmit = now; + } + + return GP_NO_ERROR; +} + +GPResult +gpiProcess( + GPConnection * connection, + int blockingOperationID +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIOperation * operation; + GPIOperation * delOperation; + GPResult result = GP_NO_ERROR; + GPIBool loop; + + assert((iconnection->connectState == GPI_NOT_CONNECTED) || + (iconnection->connectState == GPI_CONNECTING) || + (iconnection->connectState == GPI_NEGOTIATING) || + (iconnection->connectState == GPI_CONNECTED) || + (iconnection->connectState == GPI_DISCONNECTED) || + (iconnection->connectState == GPI_PROFILE_DELETING)); + + // Check if no connection was attempted. + //////////////////////////////////////// +/* if(iconnection->connectState == GPI_NOT_CONNECTED) + return GP_NO_ERROR; + + // Check for a disconnection. + ///////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + return GP_NO_ERROR; +*/ + // Check if we're connecting. + ///////////////////////////// + if(iconnection->connectState == GPI_CONNECTING) + { + do + { + result = gpiCheckConnect(connection); + //PANTS|07.10.00 - only sleep if looping + loop = (((result == GP_NO_ERROR) && (blockingOperationID != 0) && (iconnection->connectState == GPI_CONNECTING))) ? GPITrue:GPIFalse; + if(loop) + msleep(10); + } + while(loop); + + if(result != GP_NO_ERROR) + { + // Find the connect operation. + ////////////////////////////// + if(gpiFindOperationByID(connection, &operation, 1)) + { + operation->result = GP_SERVER_ERROR; + } + else + { + // Couldn't find the connect operation. + /////////////////////////////////////// + assert(0); + } + } + } + + // Only do this stuff if we're connected. + ///////////////////////////////////////// + if((iconnection->connectState == GPI_CONNECTED) || (iconnection->connectState == GPI_NEGOTIATING) || + (iconnection->connectState == GPI_PROFILE_DELETING)) + { +#ifdef _PS3 + // initialize NP during the sync delay, if initialized wait for status == online + //////////////////////////////////////////////////////////////////////////////// + if (iconnection->npInitialized && !iconnection->npStatusRetrieved) + gpiCheckNpStatus(connection); + + // TODO: handle non-fatal errors (consider all errors from sync non-fatal?) + if (iconnection->npInitialized && iconnection->npStatusRetrieved) + { + // Delay sync after initialization to ensure block list has been received + ///////////////////////////////////////////////////////////////////////// + if ((current_time() - iconnection->loginTime) > GPI_NP_SYNC_DELAY) + { + if (iconnection->npPerformBuddySync) + gpiSyncNpBuddies(connection); + if (iconnection->npPerformBlockSync) + gpiSyncNpBlockList(connection); + } + + // Need to check callback for lookups + gpiProcessNp(connection); + } +#endif + + // Process the connection. + ////////////////////////// + if(result == GP_NO_ERROR) + result = gpiProcessConnectionManager(connection); + + // Process peer messaging stuff. + //////////////////////////////// + if(result == GP_NO_ERROR) + result = gpiProcessPeers(connection); + +#ifndef NOFILE + // Process transfers. + ///////////////////// + if(result == GP_NO_ERROR) + result = gpiProcessTransfers(connection); +#endif + } + + // Process searches. + //////////////////// + if(result == GP_NO_ERROR) + result = gpiProcessSearches(connection); + + // Look for failed operations. + ////////////////////////////// + for(operation = iconnection->operationList ; operation != NULL ; ) + { + if(operation->result != GP_NO_ERROR) + { + gpiFailedOpCallback(connection, operation); + delOperation = operation; + operation = operation->pnext; + gpiRemoveOperation(connection, delOperation); + } + else + { + operation = operation->pnext; + } + } + + // Call callbacks. + ////////////////// + CHECK_RESULT(gpiProcessCallbacks(connection, blockingOperationID)); + + if(iconnection->fatalError) + { + gpiDisconnect(connection, GPIFalse); + gpiReset(connection); + } + else + { + //assert(!((result != GP_NO_ERROR) && (iconnection->connectState != GPI_CONNECTED))); + } + + return result; +} + +GPResult +gpiEnable( + GPConnection * connection, + GPEnum state +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Enable the state. + //////////////////// + switch(state) + { + case GP_INFO_CACHING: + iconnection->infoCaching = GPITrue; + break; + + case GP_SIMULATION: + iconnection->simulation = GPITrue; + break; + + case GP_INFO_CACHING_BUDDY_AND_BLOCK_ONLY: + iconnection->infoCachingBuddyAndBlockOnly = GPITrue; + break; + + default: + Error(connection, GP_PARAMETER_ERROR, "Invalid state."); + } + + return GP_NO_ERROR; +} + +static GPIBool gpiFreeProfileInfo( + GPConnection * connection, + GPIProfile * profile, + void * data +) +{ + GSI_UNUSED(data); + + gpiFreeInfoCache(profile); + freeclear(profile->peerSig); + + if(gpiCanFreeProfile(profile)) + { + gpiRemoveProfile(connection, profile); + return GPIFalse; + } + + return GPITrue; +} + +GPResult +gpiDisable( + GPConnection * connection, + GPEnum state +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + if(state == GP_INFO_CACHING) + { + iconnection->infoCaching = GPIFalse; + + // freeclear everyone's info. + //////////////////////// + while(!gpiProfileMap(connection, gpiFreeProfileInfo, NULL)) { }; + } + else if(state == GP_SIMULATION) + { + iconnection->simulation = GPIFalse; + } + else if(state == GP_INFO_CACHING_BUDDY_AND_BLOCK_ONLY) + { + iconnection->infoCachingBuddyAndBlockOnly = GPIFalse; + } + else + { + Error(connection, GP_PARAMETER_ERROR, "Invalid state."); + } + + return GP_NO_ERROR; +} + +#ifdef _DEBUG +static int nProfiles; +static int nUserID; +static int nBuddyStatus; +static int nBuddyMemory; +static int nInfoCache; +static int nInfoMemory; +static int nAuthSig; +static int nPeerSig; +static int nTotalMemory; +static int nBlocked; + +static GPIBool +gpiReportProfile( + GPConnection * connection, + GPIProfile * profile, + void * data +) +{ + int temp; + + GSI_UNUSED(connection); + GSI_UNUSED(data); + + nProfiles++; + nTotalMemory += sizeof(GPIProfile); + if(profile->userId) nUserID++; + if(profile->buddyStatus) + { + nBuddyStatus++; + temp = sizeof(GPIBuddyStatus); + if(profile->buddyStatus->statusString) + temp += (int)(strlen(profile->buddyStatus->statusString) + 1); + if(profile->buddyStatus->locationString) + temp += (int)(strlen(profile->buddyStatus->locationString) + 1); +#ifdef GSI_UNICODE +// if(profile->buddyStatus->statusString_W) +// temp += (wcslen(profile->buddyStatus->statusString_W) + 2); +// if(profile->buddyStatus->locationString_W) +// temp += (wcslen(profile->buddyStatus->locationString_W) + 2); +#endif + nBuddyMemory += temp; + nTotalMemory += temp; + } + if(profile->cache) + { + nInfoCache++; + temp = sizeof(GPIInfoCache); + if(profile->cache->nick) + temp += (int)(strlen(profile->cache->nick) + 1); + if(profile->cache->uniquenick) + temp += (int)(strlen(profile->cache->uniquenick) + 1); + if(profile->cache->email) + temp += (int)(strlen(profile->cache->email) + 1); + if(profile->cache->firstname) + temp += (int)(strlen(profile->cache->firstname) + 1); + if(profile->cache->lastname) + temp += (int)(strlen(profile->cache->lastname) + 1); + if(profile->cache->homepage) + temp += (int)(strlen(profile->cache->homepage) + 1); + nInfoMemory += temp; + nTotalMemory += temp; + } + if(profile->authSig) nAuthSig++; + if(profile->peerSig) nPeerSig++; + if(profile->blocked) nBlocked++; + + return GPITrue; +} + +void +gpiReport( + GPConnection * connection, + void (* report)(const char * output) +) +{ + char buf[128]; + + nProfiles = 0; + nUserID = 0; + nBuddyStatus = 0; + nBuddyMemory = 0; + nInfoCache = 0; + nInfoMemory = 0; + nAuthSig = 0; + nPeerSig = 0; + nTotalMemory = 0; + nBlocked = 0; + + report("START PROFILE MAP"); + report("-----------------"); + gpiProfileMap(connection, gpiReportProfile, NULL); + + sprintf(buf, "%d profiles %d bytes (%d avg)", nProfiles, nTotalMemory, nTotalMemory / max(nProfiles, 1)); + report(buf); + if(nProfiles) + { + sprintf(buf, "UserID: %d (%d%%)", nUserID, nUserID * 100 / nProfiles); + report(buf); + sprintf(buf, "BuddyStatus: %d (%d%%) %d bytes (%d avg)", nBuddyStatus, nBuddyStatus * 100 / nProfiles, nBuddyMemory, nBuddyMemory / max(nBuddyStatus, 1)); + report(buf); + sprintf(buf, "InfoCache: %d (%d%%) %d bytes (%d avg)", nInfoCache, nInfoCache * 100 / nProfiles, nInfoMemory, nInfoMemory / max(nInfoCache, 1)); + report(buf); + sprintf(buf, "AuthSig: %d (%d%%)", nAuthSig, nAuthSig * 100 / nProfiles); + report(buf); + sprintf(buf, "PeerSig: %d (%d%%)", nPeerSig, nPeerSig * 100 / nProfiles); + report(buf); + sprintf(buf, "Blocked: %d (%d%%)", nBlocked, nBlocked * 100 / nProfiles); + report(buf); + } + + report("---------------"); + report("END PROFILE MAP"); + + +} +#endif diff --git a/code/gamespy/GP/gpi.h b/code/gamespy/GP/gpi.h new file mode 100644 index 00000000..0c23ad39 --- /dev/null +++ b/code/gamespy/GP/gpi.h @@ -0,0 +1,231 @@ +/* +gpi.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#ifndef _GPI_H_ +#define _GPI_H_ + +//INCLUDES +////////// +#include "../common/gsCommon.h" +#include "../common/gsAvailable.h" +#include "../common/gsUdpEngine.h" +#include "../hashtable.h" +#include "../darray.h" +#include "../md5.h" +#include "gp.h" + +// Extended message support +#define GPI_NEW_AUTH_NOTIFICATION (1<<0) +#define GPI_NEW_REVOKE_NOTIFICATION (1<<1) + +// New Status Info support +#define GPI_NEW_STATUS_NOTIFICATION (1<<2) + +// Buddy List + Block List retrieval on login +#define GPI_NEW_LIST_RETRIEVAL_ON_LOGIN (1<<3) + +// Extended SDK features +#ifndef GPI_SDKREV +#ifdef GP_NEW_STATUS_INFO +#define GPI_SDKREV (GPI_NEW_AUTH_NOTIFICATION | GPI_NEW_REVOKE_NOTIFICATION | GPI_NEW_STATUS_NOTIFICATION | GPI_NEW_LIST_RETRIEVAL_ON_LOGIN) +#else +#define GPI_SDKREV (GPI_NEW_AUTH_NOTIFICATION | GPI_NEW_REVOKE_NOTIFICATION | GPI_NEW_LIST_RETRIEVAL_ON_LOGIN) +#endif +#endif + +// New UDP Layer port +#define GPI_PEER_PORT 6500 + +//TYPES +/////// +// Boolean. +/////////// +typedef enum _GPIBool +{ + GPIFalse, + GPITrue +} GPIBool; + +#include "gpiUtility.h" +#include "gpiCallback.h" +#include "gpiOperation.h" +#include "gpiConnect.h" +#include "gpiBuffer.h" +#include "gpiInfo.h" +#include "gpiProfile.h" +#include "gpiPeer.h" +#include "gpiSearch.h" +#include "gpiBuddy.h" +#include "gpiTransfer.h" +#include "gpiUnique.h" +#include "gpiKeys.h" + +// For PS3 NP Sync functionality +#ifdef _PS3 +#include "gpiPS3.h" +#endif + +// Connection data. +/////////////////// +typedef struct +{ + char errorString[GP_ERROR_STRING_LEN]; + GPIBool infoCaching; + GPIBool infoCachingBuddyAndBlockOnly; + GPIBool simulation; + GPIBool firewall; + char nick[GP_NICK_LEN]; + char uniquenick[GP_UNIQUENICK_LEN]; + char email[GP_EMAIL_LEN]; + char password[GP_PASSWORD_LEN]; + int sessKey; + int userid; + int profileid; + int partnerID; + GPICallback callbacks[GPI_NUM_CALLBACKS]; + SOCKET cmSocket; + int connectState; + GPIBuffer socketBuffer; + char * inputBuffer; + int inputBufferSize; + GPIBuffer outputBuffer; + // Replaced by UDP Layer + //SOCKET peerSocket; + char mHeader[GS_UDP_MSG_HEADER_LEN]; + unsigned short peerPort; + int nextOperationID; + int numSearches; + + // new style status info + GPEnum lastStatusState; + unsigned int hostIp; + unsigned int hostPrivateIp; + unsigned short queryPort; + unsigned short hostPort; + unsigned int sessionFlags; + + char richStatus[GP_RICH_STATUS_LEN]; + char gameType[GP_STATUS_BASIC_STR_LEN]; + char gameVariant[GP_STATUS_BASIC_STR_LEN]; + char gameMapName[GP_STATUS_BASIC_STR_LEN]; + + // New Status Info extended info Keys + DArray extendedInfoKeys; + + // Deprecated + char lastStatusString[GP_STATUS_STRING_LEN]; + char lastLocationString[GP_LOCATION_STRING_LEN]; + + GPErrorCode errorCode; + GPIBool fatalError; + FILE * diskCache; + GPIOperation * operationList; + GPIProfileList profileList; + GPIPeer * peerList; + GPICallbackData * callbackList; + GPICallbackData * lastCallback; + GPIBuffer updateproBuffer; + GPIBuffer updateuiBuffer; + DArray transfers; + unsigned int nextTransferID; + int productID; + int namespaceID; + char loginTicket[GP_LOGIN_TICKET_LEN]; + GPEnum quietModeFlags; + gsi_time kaTransmit; + +#ifdef GSI_UNICODE + unsigned short errorString_W[GP_ERROR_STRING_LEN]; + unsigned short nick_W[GP_NICK_LEN]; + unsigned short uniquenick_W[GP_UNIQUENICK_LEN]; + unsigned short email_W[GP_EMAIL_LEN]; + unsigned short password_W[GP_PASSWORD_LEN]; + + // Deprecated + unsigned short lastStatusString_W[GP_STATUS_STRING_LEN]; + unsigned short lastLocationString_W[GP_LOCATION_STRING_LEN]; + + unsigned short richStatus_W[GP_RICH_STATUS_LEN]; + unsigned short gameType_W[GP_STATUS_BASIC_STR_LEN]; + unsigned short gameVariant_W[GP_STATUS_BASIC_STR_LEN]; + unsigned short gameMapName_W[GP_STATUS_BASIC_STR_LEN]; +#endif + +#ifdef _PS3 + // NP sync info + gsi_bool npInitialized; + gsi_bool npStatusRetrieved; + gsi_bool npBasicGameInitialized; + gsi_bool npLookupGameInitialized; + gsi_bool npPerformBuddySync; + gsi_bool npPerformBlockSync; + gsi_bool npSyncLock; + int npLookupTitleCtxId; + DArray npTransactionList; + gsi_time loginTime; +#endif + +} GPIConnection; + +//FUNCTIONS +/////////// +GPResult +gpiInitialize( + GPConnection * connection, + int productID, + int namespaceID, + int partnerID +); + +void +gpiDestroy( + GPConnection * connection +); + +GPResult +gpiReset( + GPConnection * connection +); + +GPResult +gpiProcessConnectionManager( + GPConnection * connection +); + +GPResult +gpiProcess( + GPConnection * connection, + int blockingOperationID +); + +GPResult +gpiEnable( + GPConnection * connection, + GPEnum state +); + +GPResult +gpiDisable( + GPConnection * connection, + GPEnum state +); + +#ifdef _DEBUG +void +gpiReport( + GPConnection * connection, + void (* report)(const char * output) +); +#endif + +#endif diff --git a/code/gamespy/GP/gpiBuddy.c b/code/gamespy/GP/gpiBuddy.c new file mode 100644 index 00000000..65f530d4 --- /dev/null +++ b/code/gamespy/GP/gpiBuddy.c @@ -0,0 +1,1064 @@ +/* +gpiBuddy.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +//INCLUDES +////////// +#include +#include +#include "gpi.h" + +//FUNCTIONS +/////////// +static GPResult +gpiSendAuthBuddyRequest( + GPConnection * connection, + GPIProfile * profile +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Send the auth. + ///////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\authadd\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\fromprofileid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profile->profileId); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sig\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, profile->authSig); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult +gpiProcessRecvBuddyMessage( + GPConnection * connection, + const char * input +) +{ + char buffer[4096]; + int type; + int profileid; + time_t date; + GPICallback callback; + GPIProfile * profile; + GPIBuddyStatus * buddyStatus; + char intValue[16]; + char * str; + unsigned short port; + int productID; + GPIConnection * iconnection = (GPIConnection*)*connection; + char strTemp[max(GP_STATUS_STRING_LEN, GP_LOCATION_STRING_LEN)]; + + // Check the type of bm. + //////////////////////// + if(!gpiValueForKey(input, "\\bm\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + type = atoi(buffer); + + // Get the profile this is from. + //////////////////////////////// + if(!gpiValueForKey(input, "\\f\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + profileid = atoi(buffer); + + // Get the time. + //////////////// + if(!gpiValueForKey(input, "\\date\\", buffer, sizeof(buffer))) + date = time(NULL); + else + date = atoi(buffer); + + // What type of message is this? + //////////////////////////////// + switch(type) + { + case GPI_BM_MESSAGE: + // Call the callback. + ///////////////////// + callback = iconnection->callbacks[GPI_RECV_BUDDY_MESSAGE]; + if(callback.callback != NULL) + { + GPRecvBuddyMessageArg * arg; + arg = (GPRecvBuddyMessageArg *)gsimalloc(sizeof(GPRecvBuddyMessageArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + if(!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); +#ifndef GSI_UNICODE + arg->message = (char *)gsimalloc(strlen(buffer) + 1); + if(arg->message == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + strcpy(arg->message, buffer); + arg->profile = (GPProfile)profileid; + arg->date = (unsigned int)date; +#else + arg->message = (unsigned short*)gsimalloc(strlen(buffer)*2+2); + if(arg->message == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + UTF8ToUCS2String(buffer, arg->message); + arg->profile = (GPProfile)profileid; + arg->date = (unsigned int)date; +#endif + CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_MESSAGE)); + } + break; + case GPI_BM_UTM: + // Call the callback. + ///////////////////// + callback = iconnection->callbacks[GPI_RECV_BUDDY_UTM]; + if(callback.callback != NULL) + { + GPRecvBuddyUTMArg * arg; + arg = (GPRecvBuddyUTMArg *)gsimalloc(sizeof(GPRecvBuddyUTMArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + if(!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); +#ifndef GSI_UNICODE + arg->message = (char *)gsimalloc(strlen(buffer) + 1); + if(arg->message == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + strcpy(arg->message, buffer); + arg->profile = (GPProfile)profileid; + arg->date = (unsigned int)date; +#else + arg->message = (unsigned short*)gsimalloc(strlen(buffer)*2+2); + if(arg->message == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + UTF8ToUCS2String(buffer, arg->message); + arg->profile = (GPProfile)profileid; + arg->date = (unsigned int)date; +#endif + CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_BUDDYUTM)); + } + break; + + case GPI_BM_REQUEST: + // Get the profile, adding if needed. + ///////////////////////////////////// + profile = gpiProfileListAdd(connection, profileid); + if(!profile) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Get the reason. + ////////////////// + if(!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Find where the sig starts. + ///////////////////////////// + str = strstr(buffer, "|signed|"); + if(str == NULL) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Get the sig out of the message. + ////////////////////////////////// + *str = '\0'; + str += 8; + if(strlen(str) != 32) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + freeclear(profile->authSig); + profile->authSig = goastrdup(str); + profile->requestCount++; + + // Call the callback. + ///////////////////// + callback = iconnection->callbacks[GPI_RECV_BUDDY_REQUEST]; + if(callback.callback != NULL) + { + GPRecvBuddyRequestArg * arg; + arg = (GPRecvBuddyRequestArg *)gsimalloc(sizeof(GPRecvBuddyRequestArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); +#ifndef GSI_UNICODE + strzcpy(arg->reason, buffer, GP_REASON_LEN); +#else + UTF8ToUCS2String(buffer, arg->reason); +#endif + arg->profile = (GPProfile)profileid; + arg->date = (unsigned int)date; + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_BUDDDYREQUEST)); + } + break; + + case GPI_BM_AUTH: + // call the callback + callback = iconnection->callbacks[GPI_RECV_BUDDY_AUTH]; + if(callback.callback != NULL) + { + GPRecvBuddyAuthArg * arg; + arg = (GPRecvBuddyAuthArg *)gsimalloc(sizeof(GPRecvBuddyAuthArg)); + + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->profile = (GPProfile)profileid; + arg->date = (unsigned int)date; + CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_BUDDYAUTH)); + } + break; + + case GPI_BM_REVOKE: + // call the callback + callback = iconnection->callbacks[GPI_RECV_BUDDY_REVOKE]; + if(callback.callback != NULL) + { + GPRecvBuddyRevokeArg * arg; + arg = (GPRecvBuddyRevokeArg *)gsimalloc(sizeof(GPRecvBuddyRevokeArg)); + + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->profile = (GPProfile)profileid; + arg->date = (unsigned int)date; + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_BUDDYREVOKE)); + } + break; + + + case GPI_BM_STATUS: + // Get the profile, adding if needed. + ///////////////////////////////////// + profile = gpiProfileListAdd(connection, profileid); + if(!profile) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Make sure profile wasn't blocked prior to getting the status update + ////////////////////////////////////////////////////////////////////// + if (!profile->blocked) + { + // This is a buddy. + /////////////////// + if(!profile->buddyStatus) + { + profile->buddyStatus = (GPIBuddyStatus *)gsimalloc(sizeof(GPIBuddyStatus)); + if(!profile->buddyStatus) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(profile->buddyStatus, 0, sizeof(GPIBuddyStatus)); + if (profile->buddyStatusInfo) + { + profile->buddyStatus->buddyIndex = profile->buddyStatusInfo->buddyIndex; + gpiRemoveBuddyStatusInfo(profile->buddyStatusInfo); + profile->buddyStatusInfo = NULL; + } + else + profile->buddyStatus->buddyIndex = iconnection->profileList.numBuddies++; + } + + // Get the buddy status. + //////////////////////// + buddyStatus = profile->buddyStatus; + + // Get the msg. + /////////////// + if(!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Get the status. + ////////////////// + if(!gpiValueForKey(buffer, "|s|", intValue, sizeof(intValue))) + { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + } + else + { + buddyStatus->status = (GPEnum)atoi(intValue); + } + // Get the status string. + ///////////////////////// + freeclear(buddyStatus->statusString); + if(!gpiValueForKey(buffer, "|ss|", strTemp, GP_STATUS_STRING_LEN)) + strTemp[0] = '\0'; + buddyStatus->statusString = goastrdup(strTemp); + if(!buddyStatus->statusString) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Get the location string. + /////////////////////////// + freeclear(buddyStatus->locationString); + if(!gpiValueForKey(buffer, "|ls|", strTemp, GP_LOCATION_STRING_LEN)) + strTemp[0] = '\0'; + buddyStatus->locationString = goastrdup(strTemp); + if(!buddyStatus->locationString) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Get the ip. + ////////////// + if(!gpiValueForKey(buffer, "|ip|", intValue, sizeof(intValue))) + buddyStatus->ip = 0; + else + buddyStatus->ip = htonl((unsigned int)atoi(intValue)); + + // Get the port. + //////////////// + if(!gpiValueForKey(buffer, "|p|", intValue, sizeof(intValue))) + buddyStatus->port = 0; + else + { + port = (unsigned short)atoi(intValue); + buddyStatus->port = htons(port); + } + + // Get the quiet mode flags. + //////////////////////////// + if(!gpiValueForKey(buffer, "|qm|", intValue, sizeof(intValue))) + buddyStatus->quietModeFlags = GP_SILENCE_NONE; + else + buddyStatus->quietModeFlags = (GPEnum)atoi(intValue); + + // Call the callback. + ///////////////////// + callback = iconnection->callbacks[GPI_RECV_BUDDY_STATUS]; + if(callback.callback != NULL) + { + GPRecvBuddyStatusArg * arg; + arg = (GPRecvBuddyStatusArg *)gsimalloc(sizeof(GPRecvBuddyStatusArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + arg->profile = (GPProfile)profileid; + arg->index = buddyStatus->buddyIndex; + arg->date = (unsigned int)date; + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_STATUS)); + } + } + break; + + case GPI_BM_INVITE: + // Get the msg. + /////////////// + if(!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Find the productid. + ////////////////////// + str = strstr(buffer, "|p|"); + if(str == NULL) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Skip the |p|. + //////////////// + str += 3; + if(str[0] == '\0') + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Get the productid. + ///////////////////// + productID = atoi(str); + + // Find the location string (optional - older versions won't have) + str = strstr(buffer, "|l|"); + if(str != NULL) + strzcpy(strTemp, (str+3), sizeof(strTemp)); + else + strTemp[0] = '\0'; // no location, set to empty string + + // Call the callback. + ///////////////////// + callback = iconnection->callbacks[GPI_RECV_GAME_INVITE]; + if(callback.callback != NULL) + { + GPRecvGameInviteArg * arg; + arg = (GPRecvGameInviteArg *)gsimalloc(sizeof(GPRecvGameInviteArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + arg->profile = (GPProfile)profileid; + arg->productID = productID; +#ifdef GSI_UNICODE + AsciiToUCS2String(strTemp, arg->location); +#else + strcpy(arg->location, strTemp); +#endif + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, 0)); + } + break; + + case GPI_BM_PING: + // Get the msg. + /////////////// + if(!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Send back a pong. + //////////////////// + gpiSendBuddyMessage(connection, profileid, GPI_BM_PONG, "1", 0, NULL); + + break; + +#ifndef NOFILE + case GPI_BM_PONG: + // Lets the transfers handle this. + ////////////////////////////////// + gpiTransfersHandlePong(connection, profileid, NULL); + + break; +#endif + } + + return GP_NO_ERROR; +} + +GPResult gpiProcessRecvBuddyStatusInfo(GPConnection *connection, const char *input) +{ + char buffer[1024]; + int profileid; + time_t date; + GPICallback callback; + GPIProfile * profile; + GPIBuddyStatusInfo * buddyStatusInfo; + GPIConnection * iconnection = (GPIConnection*)*connection; + + // This is what the message should look like. Its broken up for easy viewing. + // + // "\bsi\\state\\profile\\bip\\bport\\hostip\\hprivip\" + // "\qport\\hport\\sessflags\\rstatus\\gameType\" + // "\gameVnt\\gameMn\\product\\qmodeflags\" + //////////////////////////////// + date = time(NULL); + // Get the buddy's profile + //////////////////////////////// + if(!gpiValueForKey(input, "\\profile\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + profileid = atoi(buffer); + + // Get the profile from the SDK's list, adding it if needed. + ///////////////////////////////////// + profile = gpiProfileListAdd(connection, profileid); + if(!profile) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Make sure profile wasn't blocked prior to getting the status update + ////////////////////////////////////////////////////////////////////// + if (!profile->blocked) + { + // This is a buddy. + /////////////////// + if(!profile->buddyStatusInfo) + { + profile->buddyStatusInfo = (GPIBuddyStatusInfo *)gsimalloc(sizeof(GPIBuddyStatusInfo)); + if(!profile->buddyStatusInfo) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(profile->buddyStatusInfo, 0, sizeof(GPIBuddyStatusInfo)); + if (profile->buddyStatus) + { + profile->buddyStatusInfo->buddyIndex = profile->buddyStatus->buddyIndex; + gpiRemoveBuddyStatus(profile->buddyStatus); + profile->buddyStatus = NULL; + } + else + profile->buddyStatusInfo->buddyIndex = iconnection->profileList.numBuddies++; + profile->buddyStatusInfo->extendedInfoKeys = ArrayNew(sizeof(GPIKey), GPI_INITIAL_NUM_KEYS, gpiStatusInfoKeyFree); + if (!profile->buddyStatusInfo->extendedInfoKeys) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + } + + // extract the buddy status information and + // fill in appropriate information. + ///////////////////////////////////////////// + buddyStatusInfo = profile->buddyStatusInfo; + + if (!gpiValueForKey(input, "\\state\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + buddyStatusInfo->statusState = (GPEnum)atoi(buffer); + + if (!gpiValueForKey(input, "\\bip\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + buddyStatusInfo->buddyIp = htonl((unsigned int)atoi(buffer)); + + if (!gpiValueForKey(input, "\\bport\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + buddyStatusInfo->buddyPort = (unsigned short)atoi(buffer); + + if (!gpiValueForKey(input, "\\hostip\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + buddyStatusInfo->hostIp = htonl((unsigned int)atoi(buffer)); + + if (!gpiValueForKey(input, "\\hprivip\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + buddyStatusInfo->hostPrivateIp = htonl((unsigned int)atoi(buffer)); + + if (!gpiValueForKey(input, "\\qport\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + buddyStatusInfo->queryPort = (unsigned short)atoi(buffer); + + if (!gpiValueForKey(input, "\\hport\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + buddyStatusInfo->hostPort = (unsigned short)atoi(buffer); + + if (!gpiValueForKey(input, "\\sessflags\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + buddyStatusInfo->sessionFlags = (unsigned int)atoi(buffer); + + freeclear(buddyStatusInfo->richStatus); + if (!gpiValueForKey(input, "\\rstatus\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + buddyStatusInfo->richStatus = goastrdup(buffer); + + freeclear(buddyStatusInfo->gameType); + if (!gpiValueForKey(input, "\\gameType\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + buddyStatusInfo->gameType = goastrdup(buffer); + + freeclear(buddyStatusInfo->gameVariant); + if (!gpiValueForKey(input, "\\gameVnt\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + buddyStatusInfo->gameVariant = goastrdup(buffer); + + freeclear(buddyStatusInfo->gameMapName); + if (!gpiValueForKey(input, "\\gameMn\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + buddyStatusInfo->gameMapName = goastrdup(buffer); + + if (!gpiValueForKey(input, "\\product\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + buddyStatusInfo->productId = (int)atoi(buffer); + + if (!gpiValueForKey(input, "\\qmodeflags\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + buddyStatusInfo->quietModeFlags = (GPEnum)atoi(buffer); + + callback = iconnection->callbacks[GPI_RECV_BUDDY_STATUS]; + if (callback.callback != NULL) + { + GPRecvBuddyStatusArg *anArg; + anArg = (GPRecvBuddyStatusArg *)gsimalloc(sizeof(GPRecvBuddyStatusArg)); + if (anArg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + anArg->date = (unsigned int)date; + anArg->index = buddyStatusInfo->buddyIndex; + anArg->profile = profileid; + + CHECK_RESULT(gpiAddCallback(connection, callback, anArg, NULL, 0)); + } + } + return GP_NO_ERROR; + +} + +GPResult +gpiProcessRecvBuddyList( + GPConnection * connection, + const char * input +) +{ + int i=0, j=0; + int num = 0; + int index = 0; + char c; + char *str = NULL; + char buffer[512]; + GPIProfile * profile; + GPProfile profileid; + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Check for an error. + ////////////////////// + if(gpiCheckForError(connection, input, GPITrue)) + return GP_SERVER_ERROR; + + // Process Buddy List Retrieval msg - Format like: + /* =============================================== + \bdy\\list\\final\ + =============================================== */ + + if(!gpiValueForKeyWithIndex(input, "\\bdy\\", &index, buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + num = atoi(buffer); + + // Check to make sure list is there + /////////////////////////////////// + str = strstr(input, "\\list\\"); + if (str == NULL) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Then increment index to get ready for parsing + //////////////////////////////////////////////// + str += 6; + index += 6; + + for (i=0; i < num; i++) + { + if (i==0) + { + // Manually grab first profile in list - comma delimiter + //////////////////////////////////////////////////////// + for(j=0 ; (j < sizeof(buffer)) && ((c = str[j]) != '\0') && (c != ',') ; j++) + { + buffer[j] = c; + } + buffer[j] = '\0'; + index += j; + } + else + { + if(!gpiValueForKeyWithIndex(input, ",", &index, buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + } + + profileid = atoi(buffer); + + // Get the profile, adding if needed. + ///////////////////////////////////// + profile = gpiProfileListAdd(connection, profileid); + if(!profile) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Mark as offline buddy for now until we get the real status + ///////////////////////////////////////////////////////////// +#ifdef GP_NEW_STATUS_INFO + // Use new status info as placeholder + profile->buddyStatusInfo = (GPIBuddyStatusInfo *)gsimalloc(sizeof(GPIBuddyStatusInfo)); + if(!profile->buddyStatusInfo) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(profile->buddyStatusInfo, 0, sizeof(GPIBuddyStatusInfo)); + + profile->buddyStatusInfo->extendedInfoKeys = ArrayNew(sizeof(GPIKey), GPI_INITIAL_NUM_KEYS, gpiStatusInfoKeyFree); + if (!profile->buddyStatusInfo->extendedInfoKeys) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + profile->buddyStatusInfo->buddyIndex = iconnection->profileList.numBuddies++; + profile->buddyStatusInfo->statusState = GP_OFFLINE; +#else + // Use buddy status as placeholder + profile->buddyStatus = (GPIBuddyStatus *)gsimalloc(sizeof(GPIBuddyStatus)); + if(!profile->buddyStatus) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(profile->buddyStatus, 0, sizeof(GPIBuddyStatus)); + profile->buddyStatus->buddyIndex = iconnection->profileList.numBuddies++; + profile->buddyStatus->status = GP_OFFLINE; +#endif + } + + return GP_NO_ERROR; +} + +GPResult +gpiSendServerBuddyMessage( + GPConnection * connection, + int profileid, + int type, + const char * message +) +{ + char buffer[3501]; + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Copy the message into an internal buffer. + //////////////////////////////////////////// + strzcpy(buffer, message, sizeof(buffer)); + + // Setup the message. + ///////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\bm\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, type); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\t\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profileid); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\msg\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, buffer); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult +gpiSendBuddyMessage( + GPConnection * connection, + int profileid, + int type, + const char * message, + int sendOption, + GPIPeerOp *peerOp +) +{ + GPIPeer * peer; + GPIProfile * profile; + //GPIConnection *iconnection = (GPIConnection *)*connection; + peer = gpiGetPeerByProfile(connection, profileid); + if(!peer) + { + // Check if we should send this through the server. + //////////////////////////////////////////////////// + if(!gpiGetProfile(connection, profileid, &profile) || + (!profile->buddyStatusInfo || !profile->buddyStatusInfo->buddyPort)) + { + if (sendOption == GP_DONT_ROUTE) + return GP_NETWORK_ERROR; + return gpiSendServerBuddyMessage(connection, profileid, type, message); + } + + // Create a new peer connection for this message. + ///////////////////////////////////////////////// + peer = gpiAddPeer(connection, profileid, GPITrue); + if(!peer) + return GP_MEMORY_ERROR; + + // Check if we need a sig. + ////////////////////////// + if(!profile->peerSig) + { + // Get the sig. + /////////////// + CHECK_RESULT(gpiPeerGetSig(connection, peer)); + } + else + { + // Try to connect to the peer. + ////////////////////////////// + CHECK_RESULT(gpiPeerStartConnect(connection, peer)); + } + } + else if (peer->state == GPI_PEER_DISCONNECTED) + { + if (gpiGetProfile(connection, profileid, &profile)) + { + // clear the buddy port to prevent future messages from + // being sent via UDP layer + if (profile->buddyStatusInfo) + profile->buddyStatusInfo->buddyPort = 0; + + // send the message through the server + if (sendOption == GP_DONT_ROUTE) + return GP_NETWORK_ERROR; + if (type < 100) + return gpiSendServerBuddyMessage(connection, profileid, type, message); + } + } + + if (peerOp) + { + gpiPeerAddOp(peer, peerOp); + } + // Copy the message. + //////////////////// + CHECK_RESULT(gpiPeerAddMessage(connection, peer, type, message)); + + return GP_NO_ERROR; +} + +GPResult gpiBuddyHandleKeyRequest(GPConnection *connection, GPIPeer *peer) +{ + char *message; + + // get all the keys and put them in the message part of bm + ////////////////////////////////////////////////////////// + CHECK_RESULT(gpiSaveKeysToBuffer(connection, &message)); + + // Done in case we haven't set any keys + if (message == NULL) + message = ""; + + CHECK_RESULT(gpiSendBuddyMessage(connection, peer->profile, GPI_BM_KEYS_REPLY, message, GP_DONT_ROUTE, NULL)); + + if (strcmp(message, "")!= 0) + freeclear(message); + return GP_NO_ERROR; +} + +GPResult gpiBuddyHandleKeyReply(GPConnection *connection, GPIPeer *peer, char *buffer) +{ + GPIProfile *pProfile; + + // Get the profile object to store the keys internally + ////////////////////////////////////////////////////// + + if(!gpiGetProfile(connection, peer->profile, &pProfile)) + Error(connection, GP_PARAMETER_ERROR, "Invalid profile."); + + // calculate the B64Decoded string len + if (strcmp(buffer, "") == 0) + { + GPIPeerOp *anIterator; + + for (anIterator = peer->peerOpQueue.first; anIterator != NULL; anIterator = anIterator->next) + if (anIterator->type == GPI_BM_KEYS_REQUEST) + break; + + if (!anIterator) + { + return GP_NO_ERROR; + } + else if (anIterator->type == GPI_BM_KEYS_REQUEST && anIterator->callback) + { + GPGetBuddyStatusInfoKeysArg *arg = (GPGetBuddyStatusInfoKeysArg *)gsimalloc(sizeof(GPGetBuddyStatusInfoKeysArg)); + GPICallback callback; + callback.callback = anIterator->callback; + callback.param = anIterator->userData; + + arg->keys = NULL; + arg->numKeys = 0; + arg->values = NULL; + arg->profile = peer->profile; + gpiAddCallback(connection, callback, arg, NULL, 0); + gpiPeerRemoveOp(peer, anIterator); + } + } + else + { + int decodedLen = 0, + index = 0, numKeys, i; + char keyName[512]; + char keyVal[512]; + char decodeKey[512]; + char decodeVal[512]; + gsi_char **keys; + gsi_char **values; + GPIPeerOp *anIterator; + char *checkKey = NULL; + + // start by getting the number of keys + gpiReadKeyAndValue(connection, buffer, &index, keyName, keyVal); + + // do not continue further if the header is missing + if (strcmp(keyName, "keys") != 0) + CallbackError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading keys reply message"); + + numKeys = atoi(keyVal); + + if (numKeys == 0) + { + GPIPeerOp *anIterator; + + for (anIterator = peer->peerOpQueue.first; anIterator != NULL; anIterator = anIterator->next) + if (anIterator->type == GPI_BM_KEYS_REQUEST) + break; + + if (!anIterator) + { + return GP_NO_ERROR; + } + else if (anIterator->type == GPI_BM_KEYS_REQUEST && anIterator->callback) + { + GPGetBuddyStatusInfoKeysArg *arg = (GPGetBuddyStatusInfoKeysArg *)gsimalloc(sizeof(GPGetBuddyStatusInfoKeysArg)); + GPICallback callback; + callback.callback = anIterator->callback; + callback.param = anIterator->userData; + + arg->keys = NULL; + arg->numKeys = 0; + arg->values = NULL; + arg->profile = peer->profile; + gpiAddCallback(connection, callback, arg, NULL, 0); + gpiPeerRemoveOp(peer, anIterator); + } + } + else + { + keys = (gsi_char **)gsimalloc(sizeof(gsi_char *) * numKeys); + values = (gsi_char **)gsimalloc(sizeof(gsi_char *) * numKeys); + + for (i = 0; i < numKeys; i++) + { + gpiReadKeyAndValue(connection, buffer, &index, keyName, keyVal); + B64Decode(keyName, decodeKey, (int)strlen(keyName), &decodedLen, 2); + decodeKey[decodedLen] = '\0'; + B64Decode(keyVal, decodeVal, (int)strlen(keyVal), &decodedLen, 2); + decodeVal[decodedLen] = '\0'; + #ifdef GSI_UNICODE + keys[i] = UTF8ToUCS2StringAlloc(decodeKey); + values[i]= UTF8ToUCS2StringAlloc(decodeVal); + #else + keys[i] = goastrdup(decodeKey); + values[i] = goastrdup(decodeVal); + #endif + + if (gpiStatusInfoCheckKey(connection, pProfile->buddyStatusInfo->extendedInfoKeys, decodeKey, &checkKey) == GP_NO_ERROR + && checkKey == NULL) + { + gpiStatusInfoAddKey(connection, pProfile->buddyStatusInfo->extendedInfoKeys, decodeKey, decodeVal); + } + else + { + gpiStatusInfoSetKey(connection, pProfile->buddyStatusInfo->extendedInfoKeys, decodeKey, decodeVal); + } + } + + for (anIterator = peer->peerOpQueue.first; anIterator != NULL; anIterator = anIterator->next) + if (anIterator->type == GPI_BM_KEYS_REQUEST) + break; + + if (!anIterator) + { + return GP_NO_ERROR; + } + else if (anIterator->type == GPI_BM_KEYS_REQUEST && anIterator->callback) + { + GPICallback callback; + GPGetBuddyStatusInfoKeysArg *arg = (GPGetBuddyStatusInfoKeysArg *)gsimalloc(sizeof(GPGetBuddyStatusInfoKeysArg)); + + callback.callback = anIterator->callback; + callback.param = anIterator->userData; + + // allocate a key array that points to each extended info key for that player + arg->numKeys = numKeys; + + arg->keys = keys; + arg->values = values; + arg->profile = peer->profile; + + gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_BUDDYKEYS); + gpiPeerRemoveOp(peer, anIterator); + } + } + } + + return GP_NO_ERROR; +} + +GPResult gpiAuthBuddyRequest +( + GPConnection * connection, + GPProfile profile +) +{ + GPIProfile * pProfile; + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Get the profile object. + ////////////////////////// + if(!gpiGetProfile(connection, profile, &pProfile)) + Error(connection, GP_PARAMETER_ERROR, "Invalid profile."); + + // Check for a valid sig. + ///////////////////////// + if(!pProfile->authSig) + Error(connection, GP_PARAMETER_ERROR, "Invalid profile."); + + // Send the request. + //////////////////// + CHECK_RESULT(gpiSendAuthBuddyRequest(connection, pProfile)); + + // freeclear the sig if no more requests. + //////////////////////////////////// + pProfile->requestCount--; + if(!iconnection->infoCaching && (pProfile->requestCount <= 0)) + { + freeclear(pProfile->authSig); + if(gpiCanFreeProfile(pProfile)) + gpiRemoveProfile(connection, pProfile); + } + + return GP_NO_ERROR; +} + +GPIBool +gpiFixBuddyIndices( + GPConnection * connection, + GPIProfile * profile, + void * data +) +{ +#ifndef _PS2 + int baseIndex = (int)(unsigned long)data; +#else + int baseIndex = (int)data; +#endif + + GSI_UNUSED(connection); + + if(profile->buddyStatus && (profile->buddyStatus->buddyIndex > baseIndex)) + profile->buddyStatus->buddyIndex--; + else if (profile->buddyStatusInfo && profile->buddyStatusInfo->buddyIndex > baseIndex) + profile->buddyStatusInfo->buddyIndex--; + return GPITrue; +} + +GPResult +gpiDeleteBuddy( + GPConnection * connection, + GPProfile profile, + GPIBool sendServerRequest +) +{ + GPIProfile * pProfile; + GPIConnection * iconnection = (GPIConnection*)*connection; + int index; + + // Get the profile object. + ////////////////////////// + if(!gpiGetProfile(connection, profile, &pProfile)) + Error(connection, GP_PARAMETER_ERROR, "Invalid profile."); + + // Check that this is a buddy. + ////////////////////////////// + // Removed - 092404 BED - User could be a buddy even though we don't have the status + //if(!pProfile->buddyStatus) + // Error(connection, GP_PARAMETER_ERROR, "Profile not a buddy."); + + // Send the request. + //////////////////// + if (GPITrue == sendServerRequest) + { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\delbuddy\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\delprofileid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, pProfile->profileId); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + } + + // Need to fix up the buddy indexes. + //////////////////////////////////// + if (pProfile->buddyStatus) + { + index = pProfile->buddyStatus->buddyIndex; + assert(index >= 0); + freeclear(pProfile->buddyStatus->statusString); + freeclear(pProfile->buddyStatus->locationString); + freeclear(pProfile->buddyStatus); + if(gpiCanFreeProfile(pProfile)) + gpiRemoveProfile(connection, pProfile); + iconnection->profileList.numBuddies--; + assert(iconnection->profileList.numBuddies >= 0); +#ifndef _PS2 + gpiProfileMap(connection, gpiFixBuddyIndices, (void *)(unsigned long)index); +#else + gpiProfileMap(connection, gpiFixBuddyIndices, (void *)index); +#endif + } + if (pProfile->buddyStatusInfo) + { + index = pProfile->buddyStatusInfo->buddyIndex; + assert(index >= 0); + freeclear(pProfile->buddyStatusInfo->richStatus); + freeclear(pProfile->buddyStatusInfo->gameType); + freeclear(pProfile->buddyStatusInfo->gameVariant); + freeclear(pProfile->buddyStatusInfo->gameMapName); + freeclear(pProfile->buddyStatusInfo); + if (pProfile->buddyStatusInfo->extendedInfoKeys) + { + ArrayFree(pProfile->buddyStatusInfo->extendedInfoKeys); + pProfile->buddyStatusInfo->extendedInfoKeys = NULL; + } + + if(gpiCanFreeProfile(pProfile)) + gpiRemoveProfile(connection, pProfile); + iconnection->profileList.numBuddies--; + assert(iconnection->profileList.numBuddies >= 0); +#ifndef _PS2 + gpiProfileMap(connection, gpiFixBuddyIndices, (void *)(unsigned long)index); +#else + gpiProfileMap(connection, gpiFixBuddyIndices, (void *)index); +#endif + } + return GP_NO_ERROR; +} diff --git a/code/gamespy/GP/gpiBuddy.h b/code/gamespy/GP/gpiBuddy.h new file mode 100644 index 00000000..3b62d6d0 --- /dev/null +++ b/code/gamespy/GP/gpiBuddy.h @@ -0,0 +1,104 @@ +/* +gpiBuddy.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#ifndef _GPIBUDDY_H_ +#define _GPIBUDDY_H_ + +//INCLUDES +////////// +#include "gpi.h" + +//DEFINES +///////// +// Types of bm's. +///////////////// +#define GPI_BM_MESSAGE 1 +#define GPI_BM_REQUEST 2 +#define GPI_BM_REPLY 3 // only used on the backend +#define GPI_BM_AUTH 4 +#define GPI_BM_UTM 5 +#define GPI_BM_REVOKE 6 // remote buddy removed from local list +#define GPI_BM_STATUS 100 +#define GPI_BM_INVITE 101 +#define GPI_BM_PING 102 +#define GPI_BM_PONG 103 +#define GPI_BM_KEYS_REQUEST 104 +#define GPI_BM_KEYS_REPLY 105 +#define GPI_BM_FILE_SEND_REQUEST 200 +#define GPI_BM_FILE_SEND_REPLY 201 +#define GPI_BM_FILE_BEGIN 202 +#define GPI_BM_FILE_END 203 +#define GPI_BM_FILE_DATA 204 +#define GPI_BM_FILE_SKIP 205 +#define GPI_BM_FILE_TRANSFER_THROTTLE 206 +#define GPI_BM_FILE_TRANSFER_CANCEL 207 +#define GPI_BM_FILE_TRANSFER_KEEPALIVE 208 + +//FUNCTIONS +/////////// +GPResult +gpiProcessRecvBuddyMessage( + GPConnection * connection, + const char * input +); + +GPResult gpiProcessRecvBuddyStatusInfo(GPConnection *connection, const char *input); + +GPResult +gpiProcessRecvBuddyList( + GPConnection * connection, + const char * input +); + +GPResult +gpiSendServerBuddyMessage( + GPConnection * connection, + int profileid, + int type, + const char * message +); + +GPResult +gpiSendBuddyMessage( + GPConnection * connection, + int profileid, + int type, + const char * message, + int sendOptions, + GPIPeerOp *peerOp +); + +GPResult gpiBuddyHandleKeyRequest(GPConnection *connection, GPIPeer *peer); +GPResult gpiBuddyHandleKeyReply(GPConnection *connection, GPIPeer *peer, char *buffer); + +GPResult +gpiAuthBuddyRequest( + GPConnection * connection, + GPProfile profile +); + +GPIBool +gpiFixBuddyIndices( + GPConnection * connection, + GPIProfile * profile, + void * data +); + +GPResult +gpiDeleteBuddy( + GPConnection * connection, + GPProfile profile, + GPIBool sendServerRequest +); + +#endif diff --git a/code/gamespy/GP/gpiBuffer.c b/code/gamespy/GP/gpiBuffer.c new file mode 100644 index 00000000..095086a5 --- /dev/null +++ b/code/gamespy/GP/gpiBuffer.c @@ -0,0 +1,766 @@ +/* +gpiBuffer.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +//INCLUDES +////////// +#include +#include +#include "gpi.h" + +//DEFINES +///////// +#define GPI_DUMP_NET_TRAFFIC + +//FUNCTIONS +/////////// +GPResult +gpiAppendCharToBuffer( + GPConnection * connection, + GPIBuffer * outputBuffer, + char c +) +{ + int len; + int size; + char * output; + + assert(outputBuffer != NULL); + + // Init locals. + /////////////// + len = outputBuffer->len; + size = outputBuffer->size; + output = outputBuffer->buffer; + + // Check if it needs to be resized. + /////////////////////////////////// + if(size == len) + { + size += GPI_READ_SIZE; + output = (char*)gsirealloc(output, (unsigned int)size + 1); + if(output == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + } + + // Do the copy. + /////////////// + output[len] = c; + output[len + 1] = '\0'; + + // Update the buffer info. + ////////////////////////// + outputBuffer->len++; + outputBuffer->size = size; + outputBuffer->buffer = output; + + return GP_NO_ERROR; +} + +GPResult +gpiAppendStringToBufferLen( + GPConnection * connection, + GPIBuffer * outputBuffer, + const char * string, + int stringLen +) +{ + int len; + int size; + char * output; + + assert(string != NULL); + assert(stringLen >= 0); + assert(outputBuffer != NULL); + + if(!string) + return GP_NO_ERROR; + + // Init locals. + /////////////// + len = outputBuffer->len; + size = outputBuffer->size; + output = outputBuffer->buffer; + + // Check if it needs to be resized. + /////////////////////////////////// + if((size - len) < stringLen) + { + size += max(GPI_READ_SIZE, stringLen); + output = (char*)gsirealloc(output, (unsigned int)size + 1); + if(output == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + } + + // Do the copy. + /////////////// + memcpy(&output[len], string, (unsigned int)stringLen); + output[len + stringLen] = '\0'; + + // Update the buffer info. + ////////////////////////// + outputBuffer->len += stringLen; + outputBuffer->size = size; + outputBuffer->buffer = output; + + return GP_NO_ERROR; +} + +GPResult +gpiAppendStringToBuffer( + GPConnection * connection, + GPIBuffer * outputBuffer, + const char * buffer +) +{ + return gpiAppendStringToBufferLen(connection, outputBuffer, buffer, (int)strlen(buffer)); +} + +GPResult gpiAppendShortToBuffer( + GPConnection * connection, + GPIBuffer * outputBuffer, + short num) +{ + char shortVal[8]; + sprintf(shortVal, "%d", num); + return gpiAppendStringToBuffer(connection, outputBuffer, shortVal); +} + +GPResult gpiAppendUShortToBuffer( + GPConnection * connection, + GPIBuffer * outputBuffer, + unsigned short num) +{ + char shortVal[8]; + sprintf(shortVal, "%u", num); + return gpiAppendStringToBuffer(connection, outputBuffer, shortVal); +} + +GPResult +gpiAppendIntToBuffer( + GPConnection * connection, + GPIBuffer * outputBuffer, + int num +) +{ + char intValue[16]; + sprintf(intValue,"%d",num); + return gpiAppendStringToBuffer(connection, outputBuffer, intValue); +} + +GPResult +gpiAppendUIntToBuffer( + GPConnection * connection, + GPIBuffer * outputBuffer, + unsigned int num +) +{ + char intValue[16]; + sprintf(intValue,"%u",num); + return gpiAppendStringToBuffer(connection, outputBuffer, intValue); +} + +static GPResult +gpiSendData( + GPConnection * connection, + SOCKET sock, + const char * buffer, + int bufferLen, + GPIBool * closed, + int * sent, + char id[3] +) +{ + int rcode; + + rcode = send(sock, buffer, bufferLen, 0); + if(gsiSocketIsError(rcode)) + { + rcode = GOAGetLastError(sock); + if((rcode != WSAEWOULDBLOCK) && (rcode != WSAEINPROGRESS) && (rcode != WSAETIMEDOUT) ) + { + // handle peer connections specially + if((id[0] == 'P') && (id[1] == 'R')) + return GP_NETWORK_ERROR; + CallbackError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error sending on a socket."); + } + + *sent = 0; + *closed = GPIFalse; + } + else if(rcode == 0) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Comment, + "SENDXXXX(%s): Connection closed\n", id); + + *sent = 0; + *closed = GPITrue; + } + else + { + #if defined(GPI_DUMP_NET_TRAFFIC) && defined(GSI_COMMON_DEBUG) + { + static int sendCount; + char *buf = (char *)gsimalloc((size_t)(rcode + 1)); + memcpy(buf, buffer, (size_t)rcode); + buf[rcode] = '\0'; + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_RawDump, "SENT%04d(%s): %s\n", sendCount++, id, buf); + freeclear(buf); + } + #elif defined(GSI_COMMON_DEBUG) + { + static int sendCount; + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_RawDump, + "SENT%04d(%s): %d\n", sendCount++, id, rcode); + } + #endif + + *sent = rcode; + *closed = GPIFalse; + } + + return GP_NO_ERROR; +} + +GPResult +gpiSendOrBufferChar( + GPConnection * connection, + GPIPeer_st peer, + char c +) +{ + //GPIBool closed; + //int sent; +/* + assert(peer->outputBuffer.buffer != NULL); + + // Only try to send if the buffer is empty and there are no messages. + ///////////////////////////////////////////////////////////////////// + if(!(peer->outputBuffer.len - peer->outputBuffer.pos) && !ArrayLength(peer->messages)) + { + CHECK_RESULT(gpiSendData(connection, peer->sock, &c, 1, &closed, &sent, "PT")); + if(sent) + return GP_NO_ERROR; + } + + // Buffer if not sent. + ////////////////////// + return gpiAppendCharToBuffer(connection, &peer->outputBuffer, c); + */ + GSI_UNUSED(c); + GSI_UNUSED(peer); + GSI_UNUSED(connection); + return GP_NO_ERROR; +} + +GPResult +gpiSendOrBufferStringLenToPeer( + GPConnection * connection, + GPIPeer_st peer, + const char * string, + int stringLen +) +{ + GPIConnection *iconnection; + + unsigned int sent; + unsigned int total; + unsigned int remaining; + + assert(peer->outputBuffer.buffer != NULL); + + sent = 0; + iconnection = (GPIConnection *)*connection; + remaining = (unsigned int)stringLen; + total = 0; + + // Check for nothing to send. + ///////////////////////////// + if(stringLen == 0) + return GP_NO_ERROR; + + // Only try to send if the buffer is empty and there are no messages. + ///////////////////////////////////////////////////////////////////// + if(!(peer->outputBuffer.len - peer->outputBuffer.pos) && !ArrayLength(peer->messages)) + { + if ((int)remaining <= (gsUdpEngineGetPeerOutBufferFreeSpace(peer->ip, peer->port) - GS_UDP_RELIABLE_MSG_HEADER - GS_UDP_MSG_HEADER_LEN)) + { + gsUdpEngineSendMessage(peer->ip, peer->port, iconnection->mHeader, (unsigned char *)string, remaining, gsi_true); + total = remaining; + remaining = 0; + } + else + { + unsigned int freeSpace = (unsigned int)gsUdpEngineGetPeerOutBufferFreeSpace(peer->ip, peer->port); + if (freeSpace > (GS_UDP_MSG_HEADER_LEN + GS_UDP_RELIABLE_MSG_HEADER)) + { + sent = freeSpace - (GS_UDP_MSG_HEADER_LEN + GS_UDP_RELIABLE_MSG_HEADER); + gsUdpEngineSendMessage(peer->ip, peer->port, iconnection->mHeader, (unsigned char *)string, + sent, gsi_true); + total = sent; + remaining -= sent; + } + } + + } + + // Buffer what wasn't sent. + /////////////////////////// + if(remaining) + CHECK_RESULT(gpiAppendStringToBufferLen(connection, &peer->outputBuffer, &string[total], (int)remaining)); + + return GP_NO_ERROR; +} + +/* +GPResult +gpiSendOrBufferStringLen( + GPConnection * connection, + GPIPeer_st peer, + const char * string, + int stringLen + ) +{ + + GPIBool closed; + int sent; + int total; + int remaining; + + + assert(peer->outputBuffer.buffer != NULL); + + remaining = stringLen; + total = 0; + + // Check for nothing to send. + ///////////////////////////// + if(stringLen == 0) + return GP_NO_ERROR; + + // Only try to send if the buffer is empty and there are no messages. + ///////////////////////////////////////////////////////////////////// + if(!(peer->outputBuffer.len - peer->outputBuffer.pos) && !ArrayLength(peer->messages)) + { + do + { + CHECK_RESULT(gpiSendData(connection, peer->sock, &string[total], remaining, &closed, &sent, "PT")); + if(sent) + { + total += sent; + remaining -= sent; + } + } + while(sent && remaining); + } + + // Buffer what wasn't sent. + /////////////////////////// + if(remaining) + CHECK_RESULT(gpiAppendStringToBufferLen(connection, &peer->outputBuffer, &string[total], remaining)); + + + GSI_UNUSED(stringLen); + GSI_UNUSED(string); + GSI_UNUSED(peer); + GSI_UNUSED(connection); + return GP_NO_ERROR; +} +*/ + +GPResult +gpiSendOrBufferString( + GPConnection * connection, + GPIPeer_st peer, + char * string +) +{ + return gpiSendOrBufferStringLenToPeer(connection, peer, string, (int)strlen(string)); +} + +GPResult +gpiSendOrBufferInt( + GPConnection * connection, + GPIPeer_st peer, + int num +) +{ + char intValue[16]; + sprintf(intValue,"%d",num); + return gpiSendOrBufferString(connection, peer, intValue); +} + +GPResult +gpiSendOrBufferUInt( + GPConnection * connection, + GPIPeer_st peer, + unsigned int num +) +{ + char intValue[16]; + sprintf(intValue,"%u",num); + return gpiSendOrBufferString(connection, peer, intValue); +} + +GPResult +gpiRecvToBuffer( + GPConnection * connection, + SOCKET sock, + GPIBuffer * inputBuffer, + int * bytesRead, + GPIBool * connClosed, + char id[3] +) +{ + char * buffer; + int len; + int size; + int rcode; + int total; + GPIBool closed; + + assert(sock != INVALID_SOCKET); + assert(inputBuffer != NULL); + assert(bytesRead != NULL); + assert(connClosed != NULL); + + // Init locals. + /////////////// + buffer = inputBuffer->buffer; + len = inputBuffer->len; + size = inputBuffer->size; + total = 0; + closed = GPIFalse; + + do + { + // Check if the buffer needs to be resized. + /////////////////////////////////////////// + if((len + GPI_READ_SIZE) > size) + { + size = (len + GPI_READ_SIZE); + buffer = (char *)gsirealloc(buffer, (unsigned int)size + 1); + if(buffer == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + } + + // Read from the network. + rcode = recv(sock, &buffer[len], size - len, 0); + + + if(gsiSocketIsError(rcode)) + { + int error = GOAGetLastError(sock); + if((error != WSAEWOULDBLOCK) && (error != WSAEINPROGRESS) && (error != WSAETIMEDOUT) ) + { + Error(connection, GP_NETWORK_ERROR, "There was an error reading from a socket."); + } + } + else if(rcode == 0) + { + // Check for a closed connection. + ///////////////////////////////// + closed = GPITrue; + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Comment, + "RECVXXXX(%s): Connection closed\n", id); + } + else + { + #if defined(GPI_DUMP_NET_TRAFFIC) && defined(GSI_COMMON_DEBUG) + { + static int recvCount; + char *buf = (char *)gsimalloc((size_t)(rcode + 1)); + memcpy(buf, &buffer[len], (size_t)rcode); + buf[rcode] = '\0'; + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_RawDump, + "RECV%04d(%s): %s\n", recvCount++, id, buf); + freeclear(buf); + } + #elif defined(GSI_COMMON_DEBUG) + { + static int recvCount; + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_RawDump, + "RECV%04d(%s): %d\n", recvCount++, id, rcode); + } + #endif + // Update the buffer len. + ///////////////////////// + len += rcode; + + // Update the total. + //////////////////// + total += rcode; + } + + buffer[len] = '\0'; + } + while((rcode >= 0) && !closed && (total < (128 * 1024))); + + if(total) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_RawDump, + "RECVTOTL(%s): %d\n", id, total); + } + + // Set output stuff. + //////////////////// + inputBuffer->buffer = buffer; + inputBuffer->len = len; + inputBuffer->size = size; + *bytesRead = total; + *connClosed = closed; + + GSI_UNUSED(id); //to get rid of codewarrior warnings + + return GP_NO_ERROR; +} + +GPResult +gpiSendFromBuffer( + GPConnection * connection, + SOCKET sock, + GPIBuffer * outputBuffer, + GPIBool * connClosed, + GPIBool clipSentData, + char id[3] +) +{ + GPIBool closed; + int sent; + int total; + int remaining; + char * buffer; + int pos; + int len; + + assert(outputBuffer != NULL); + + buffer = outputBuffer->buffer; + len = outputBuffer->len; + pos = outputBuffer->pos; + remaining = (len - pos); + total = 0; + + // Check for nothing to send. + ///////////////////////////// + if(remaining == 0) + return GP_NO_ERROR; + + do + { + CHECK_RESULT(gpiSendData(connection, sock, &buffer[pos + total], remaining, &closed, &sent, id)); + if(sent) + { + total += sent; + remaining -= sent; + } + } + while(sent && remaining); + + if(clipSentData) + { + if(total > 0) + { + memmove(buffer, &buffer[total], (unsigned int)remaining + 1); + len -= total; + } + } + else + { + pos += total; + } + + assert(len >= 0); + assert(pos >= 0); + assert(pos <= len); + + // Set outputs. + /////////////// + outputBuffer->len = len; + outputBuffer->pos = pos; + if(connClosed) + *connClosed = closed; + + return GP_NO_ERROR; +} + +GPResult gpiSendBufferToPeer(GPConnection * connection, unsigned int ip, unsigned short port, + GPIBuffer * outputBuffer, GPIBool *closed, GPIBool clipSentData) +{ + GPIConnection *iconnection = (GPIConnection *)*connection; + //GPIBool closed; + unsigned int remaining; + unsigned char * buffer; + unsigned int pos; + unsigned int len; + unsigned int total = 0; + GSUdpPeerState aPeerState; + assert(outputBuffer != NULL); + + buffer = (unsigned char *)outputBuffer->buffer; + len = (unsigned int)outputBuffer->len; + pos = (unsigned int)outputBuffer->pos; + remaining = (len - pos); + + // Check for nothing to send. + ///////////////////////////// + if(remaining == 0) + return GP_NO_ERROR; + + // length of message remaining must be smaller than total buffer size minus gt2 reliable msg header size minus + // in order to send the message in one shot. + if ((int)remaining <= (gsUdpEngineGetPeerOutBufferFreeSpace(ip, port) - GS_UDP_RELIABLE_MSG_HEADER - GS_UDP_MSG_HEADER_LEN)) + { + gsUdpEngineSendMessage(ip, port, iconnection->mHeader, &buffer[pos], remaining, gsi_true); + total = remaining; + remaining = 0; + } + else + { + unsigned int freeSpace =0; + unsigned int sendAmount = 0; + do + { + freeSpace = (unsigned int)gsUdpEngineGetPeerOutBufferFreeSpace(ip, port); + sendAmount = freeSpace - (GS_UDP_MSG_HEADER_LEN + GS_UDP_RELIABLE_MSG_HEADER); + if (sendAmount <= (GS_UDP_MSG_HEADER_LEN + GS_UDP_RELIABLE_MSG_HEADER)) + break; + if (gsUdpEngineSendMessage(ip, port, iconnection->mHeader, &buffer[pos+total], sendAmount, gsi_true) == GS_UDP_SEND_FAILED) + break; + total += sendAmount; + remaining -= sendAmount; + }while (remaining); + } + + if(clipSentData) + { + if (total > 0) + { + memmove(buffer, &buffer[total], remaining + 1); + len -= total; + } + } + else + { + pos += total; + } + // Set outputs. + /////////////// + outputBuffer->len = (int)len; + outputBuffer->pos = (int)pos; + + gsUdpEngineGetPeerState(ip, port, &aPeerState); + if (aPeerState == GS_UDP_PEER_CLOSED) + *closed = GPITrue; + else + *closed = GPIFalse; + + return GP_NO_ERROR; +} + +GPResult +gpiReadMessageFromBuffer( + GPConnection * connection, + GPIBuffer * inputBuffer, + char ** message, + int * type, + int * plen +) +{ + char * str; + int len; + char intValue[16]; + + // Default. + /////////// + *message = NULL; + + // Check for not enough data. + ///////////////////////////// + if(inputBuffer->len < 5) + return GP_NO_ERROR; + + // Find the end of the header. + ////////////////////////////// + str = strchr(inputBuffer->buffer, '\n'); + if(str != NULL) + { + // Check that this is the msg. + ////////////////////////////// + if(strncmp(str - 5, "\\msg\\", 5) != 0) + return GP_NETWORK_ERROR; + + // Cap the header. + ////////////////// + *str = '\0'; + + // Read the header. + /////////////////// + if(!gpiValueForKey(inputBuffer->buffer, "\\m\\", intValue, sizeof(intValue))) + return GP_NETWORK_ERROR; + *type = atoi(intValue); + + // Get the length. + ////////////////// + if(!gpiValueForKey(inputBuffer->buffer, "\\len\\", intValue, sizeof(intValue))) + return GP_NETWORK_ERROR; + len = atoi(intValue); + len++; + + // Is the whole message available? + ////////////////////////////////// + if(inputBuffer->len > ((str - inputBuffer->buffer) + len)) + { + // Does it not end with a NUL? + ////////////////////////////// + if(str[len] != '\0') + return GP_NETWORK_ERROR; + + // Set the message stuff. + ///////////////////////// + *message = &str[1]; + *plen = (len - 1); + + // Set the position to the end of the message. + ////////////////////////////////////////////// + inputBuffer->pos = ((str - inputBuffer->buffer) + len + 1); + } + else + { + // Put the LF back. + /////////////////// + *str = '\n'; + } + } + + GSI_UNUSED(connection); + return GP_NO_ERROR; +} + +GPResult +gpiClipBufferToPosition( + GPConnection * connection, + GPIBuffer * buffer +) +{ + if(!buffer || !buffer->buffer || !buffer->pos) + return GP_NO_ERROR; + + buffer->len -= buffer->pos; + if(buffer->len) + memmove(buffer->buffer, buffer->buffer + buffer->pos, (unsigned int)buffer->len); + buffer->buffer[buffer->len] = '\0'; + buffer->pos = 0; + + GSI_UNUSED(connection); + return GP_NO_ERROR; +} diff --git a/code/gamespy/GP/gpiBuffer.h b/code/gamespy/GP/gpiBuffer.h new file mode 100644 index 00000000..2a2b7a71 --- /dev/null +++ b/code/gamespy/GP/gpiBuffer.h @@ -0,0 +1,167 @@ +/* +gpiBuffer.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#ifndef _GPIBUFFER_H_ +#define _GPIBUFFER_H_ + +//INCLUDES +////////// +#include "gpi.h" + +//TYPES +/////// +// A buffer. +//////////// +typedef struct +{ + char * buffer; + int size; + int len; + int pos; +} GPIBuffer; + +typedef struct GPIPeer_s * GPIPeer_st; + +//FUNCTIONS +/////////// +GPResult +gpiAppendCharToBuffer( + GPConnection * connection, + GPIBuffer * outputBuffer, + char c +); + +GPResult +gpiAppendStringToBufferLen( + GPConnection * connection, + GPIBuffer * outputBuffer, + const char * string, + int stringLen +); + +GPResult +gpiAppendStringToBuffer( + GPConnection * connection, + GPIBuffer * outputBuffer, + const char * buffer +); + +GPResult gpiAppendShortToBuffer( + GPConnection * connection, + GPIBuffer * outputBuffer, + short num +); + +GPResult gpiAppendUShortToBuffer( + GPConnection * connection, + GPIBuffer * outputBuffer, + unsigned short num +); + +GPResult +gpiAppendIntToBuffer( + GPConnection * connection, + GPIBuffer * outputBuffer, + int num +); + +GPResult +gpiAppendUIntToBuffer( + GPConnection * connection, + GPIBuffer * outputBuffer, + unsigned int num +); + +GPResult +gpiSendOrBufferChar( + GPConnection * connection, + GPIPeer_st peer, + char c +); + +/* +GPResult +gpiSendOrBufferStringLen( + GPConnection * connection, + GPIPeer_st peer, + const char * string, + int stringLen +); +*/ +GPResult +gpiSendOrBufferStringLenToPeer( + GPConnection * connection, + GPIPeer_st peer, + const char * string, + int stringLen +); + +GPResult +gpiSendOrBufferString( + GPConnection * connection, + GPIPeer_st peer, + char * string +); + +GPResult +gpiSendOrBufferInt( + GPConnection * connection, + GPIPeer_st peer, + int num +); + +GPResult +gpiSendOrBufferUInt( + GPConnection * connection, + GPIPeer_st peer, + unsigned int num +); + +GPResult +gpiSendFromBuffer( + GPConnection * connection, + SOCKET sock, + GPIBuffer * outputBuffer, + GPIBool * connClosed, + GPIBool clipSentData, + char id[3] +); + +GPResult +gpiRecvToBuffer( + GPConnection * connection, + SOCKET sock, + GPIBuffer * inputBuffer, + int * bytesRead, + GPIBool * connClosed, + char id[3] +); + +GPResult +gpiReadMessageFromBuffer( + GPConnection * connection, + GPIBuffer * inputBuffer, + char ** message, + int * type, + int * len +); + +GPResult +gpiClipBufferToPosition( + GPConnection * connection, + GPIBuffer * buffer +); + +GPResult gpiSendBufferToPeer(GPConnection * connection, unsigned int ip, unsigned short port, + GPIBuffer * outputBuffer, GPIBool *closed, GPIBool clipSentData); +#endif diff --git a/code/gamespy/GP/gpiCallback.c b/code/gamespy/GP/gpiCallback.c new file mode 100644 index 00000000..4de1049c --- /dev/null +++ b/code/gamespy/GP/gpiCallback.c @@ -0,0 +1,277 @@ +/* +gpiCallback.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +//INCLUDES +////////// +#include +#include "gpi.h" + +//FUNCTIONS +/////////// +void +gpiCallErrorCallback( + GPConnection * connection, + GPResult result, + GPEnum fatal +) +{ + GPICallback callback; + GPIConnection * iconnection = (GPIConnection*)*connection; + + assert(iconnection != NULL); + assert(result != GP_NO_ERROR); + assert((fatal == GP_FATAL) || (fatal == GP_NON_FATAL)); + + if(fatal == GP_FATAL) + iconnection->fatalError = GPITrue; + + callback = iconnection->callbacks[GPI_ERROR]; + if(callback.callback != NULL) + { + GPErrorArg * arg; + arg = (GPErrorArg *)gsimalloc(sizeof(GPErrorArg)); + + if(arg != NULL) + { + arg->result = result; + arg->fatal = fatal; + arg->errorCode = iconnection->errorCode; +#ifndef GSI_UNICODE + arg->errorString = iconnection->errorString; +#else + arg->errorString = iconnection->errorString_W; +#endif + + } + + gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_ERROR); + } +} + +GPResult +gpiAddCallback( + GPConnection * connection, + GPICallback callback, + void * arg, + const struct GPIOperation_s * operation, + int type +) +{ + GPICallbackData * data; + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Allocate the callback data. + ////////////////////////////// + data = (GPICallbackData *)gsimalloc(sizeof(GPICallbackData)); + if(data == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + data->callback = callback; + data->arg = arg; + if(operation != NULL) + data->operationID = operation->id; + else + data->operationID = 0; + data->type = type; + data->pnext = NULL; + + // Update the list. + /////////////////// + if(iconnection->callbackList == NULL) + iconnection->callbackList = data; + if(iconnection->lastCallback != NULL) + iconnection->lastCallback->pnext = data; + iconnection->lastCallback = data; + + return GP_NO_ERROR; +} + +static void +gpiCallCallback( + GPConnection * connection, + GPICallbackData * data +) +{ + // Call the callback. + ///////////////////// + assert(data->callback.callback != NULL); + assert(data->arg != NULL); + data->callback.callback(connection, data->arg, data->callback.param); + if(data->type == GPI_ADD_MESSAGE) + { + freeclear(((GPRecvBuddyMessageArg *)data->arg)->message); + } + else if (data->type == GPI_ADD_BUDDYUTM) + { + freeclear(((GPRecvBuddyUTMArg *)data->arg)->message); + } + else if(data->type == GPI_ADD_NICKS) + { + int i; + GPGetUserNicksResponseArg * arg = (GPGetUserNicksResponseArg *)data->arg; + + for(i = 0 ; i < arg->numNicks ; i++) + { + freeclear(arg->nicks[i]); + freeclear(arg->uniquenicks[i]); + } + freeclear(arg->nicks); + freeclear(arg->uniquenicks) + } + else if(data->type == GPI_ADD_PMATCH) + { + GPFindPlayersResponseArg * arg = (GPFindPlayersResponseArg *)data->arg; + + freeclear(arg->matches); + } + else if(data->type == GPI_ADD_TRANSFER_CALLBACK) + { + GPTransferCallbackArg * arg = (GPTransferCallbackArg *)data->arg; + + if(arg->message) + freeclear(arg->message); + } + else if(data->type == GPI_ADD_REVERSE_BUDDIES) + { + GPGetReverseBuddiesResponseArg * arg = (GPGetReverseBuddiesResponseArg *)data->arg; + + if(arg->profiles) + freeclear(arg->profiles); + } + else if(data->type == GPI_ADD_SUGGESTED_UNIQUE) + { + int i; + GPSuggestUniqueNickResponseArg * arg = (GPSuggestUniqueNickResponseArg *)data->arg; + + for(i = 0 ; i < arg->numSuggestedNicks ; i++) + { + freeclear(arg->suggestedNicks[i]); + } + freeclear(arg->suggestedNicks); + } + else if (data->type == GPI_ADD_BUDDYREVOKE) + { + GPRecvBuddyRevokeArg * arg = (GPRecvBuddyRevokeArg *)data->arg; + + // Remove the profile from our local lists AFTER the callback has been called + gpiDeleteBuddy(connection, arg->profile, GPIFalse); + } + else if (data->type == GPI_ADD_REVERSE_BUDDIES_LIST) + { + GPGetReverseBuddiesListResponseArg * arg = (GPGetReverseBuddiesListResponseArg *)data->arg; + + if(arg->matches) + freeclear(arg->matches); + } + else if (data->type == GPI_ADD_BUDDYKEYS) + { + GPGetBuddyStatusInfoKeysArg *arg = (GPGetBuddyStatusInfoKeysArg *)data->arg; + if (arg->numKeys != 0) + { + int i; + for (i=0; i < arg->numKeys; i++) + { + freeclear(arg->keys[i]); + freeclear(arg->values[i]); + } + freeclear(arg->keys); + freeclear(arg->values); + } + } + freeclear(data->arg); + freeclear(data); +} + +GPResult +gpiProcessCallbacks( + GPConnection * connection, + int blockingOperationID +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPICallbackData * list; + GPICallbackData * last; + GPICallbackData * pcurr; + GPICallbackData * pnext; + GPICallbackData * pprev; + + if(blockingOperationID != 0) + { + list = iconnection->callbackList; + last = iconnection->lastCallback; + iconnection->callbackList = NULL; + iconnection->lastCallback = NULL; + + pprev = NULL; + for(pcurr = list ; pcurr != NULL ; ) + { + pnext = pcurr->pnext; + + if((pcurr->operationID == blockingOperationID) || (pcurr->type == GPI_ADD_ERROR)) + { + // Take this one out of the list. + ///////////////////////////////// + if(pprev != NULL) + pprev->pnext = pcurr->pnext; + else + list = pcurr->pnext; + if(last == pcurr) + last = pprev; + + // Call the callback. + ///////////////////// + gpiCallCallback(connection, pcurr); + } + else + { + pprev = pcurr; + } + + pcurr = pnext; + } + + // Were callbacks added within the callback? + //////////////////////////////////////////// + if(iconnection->callbackList != NULL) + { + iconnection->lastCallback->pnext = list; + iconnection->lastCallback = last; + } + else + { + // Reset the list. + ////////////////// + iconnection->callbackList = list; + iconnection->lastCallback = last; + } + + return GP_NO_ERROR; + } + + while(iconnection->callbackList != NULL) + { + list = iconnection->callbackList; + iconnection->callbackList = NULL; + iconnection->lastCallback = NULL; + + for(pcurr = list ; pcurr != NULL ; pcurr = pnext) + { + pnext = pcurr->pnext; + + // Call the callback. + ///////////////////// + gpiCallCallback(connection, pcurr); + } + } + + return GP_NO_ERROR; +} diff --git a/code/gamespy/GP/gpiCallback.h b/code/gamespy/GP/gpiCallback.h new file mode 100644 index 00000000..599d0909 --- /dev/null +++ b/code/gamespy/GP/gpiCallback.h @@ -0,0 +1,109 @@ +/* +gpiCallback.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#ifndef _GPICALLBACK_H_ +#define _GPICALLBACK_H_ + +//INCLUDES +////////// +#include "gpi.h" + +//DEFINES +///////// +// Unsolicited Callbacks. +///////////////////////// +enum GPICallbackId +{ + GPI_ERROR = GP_ERROR, + GPI_RECV_BUDDY_REQUEST = GP_RECV_BUDDY_REQUEST, + GPI_RECV_BUDDY_STATUS = GP_RECV_BUDDY_STATUS, + GPI_RECV_BUDDY_MESSAGE = GP_RECV_BUDDY_MESSAGE, + GPI_RECV_BUDDY_UTM = GP_RECV_BUDDY_UTM, + GPI_RECV_GAME_INVITE = GP_RECV_GAME_INVITE, + GPI_TRANSFER_CALLBACK = GP_TRANSFER_CALLBACK, + GPI_RECV_BUDDY_AUTH = GP_RECV_BUDDY_AUTH, + GPI_RECV_BUDDY_REVOKE = GP_RECV_BUDDY_REVOKE, + GPI_NUM_CALLBACKS +}; + +// Add type - not 0 only for a few. +/////////////////////////////////// +enum GPIAddCallbackType +{ + GPI_ADD_NORMAL, + GPI_ADD_ERROR, + GPI_ADD_MESSAGE, + GPI_ADD_NICKS, + GPI_ADD_PMATCH, + GPI_ADD_STATUS, + GPI_ADD_BUDDDYREQUEST, + GPI_ADD_TRANSFER_CALLBACK, + GPI_ADD_REVERSE_BUDDIES, + GPI_ADD_SUGGESTED_UNIQUE, + GPI_ADD_BUDDYAUTH, + GPI_ADD_BUDDYUTM, + GPI_ADD_BUDDYREVOKE, + GPI_ADD_REVERSE_BUDDIES_LIST, + GPI_ADD_BUDDYKEYS, + GPI_NUM_ADD_CALLBACK_TYPES +}; + + +//TYPES +/////// +// A Callback. +////////////// +typedef struct +{ + GPCallback callback; + void * param; +} GPICallback; + +// Data for a pending callback. +/////////////////////////////// +typedef struct GPICallbackData +{ + GPICallback callback; + void * arg; + int type; + int operationID; + struct GPICallbackData * pnext; +} GPICallbackData; + +//FUNCTIONS +/////////// +void +gpiCallErrorCallback( + GPConnection * connection, + GPResult result, + GPEnum fatal +); + +typedef struct GPIOperation_s *GPIOperation_st; + +GPResult +gpiAddCallback( + GPConnection * connection, + GPICallback callback, + void * arg, + const struct GPIOperation_s * operation, + int type +); + +GPResult +gpiProcessCallbacks( + GPConnection * connection, + int blockingOperationID +); + +#endif diff --git a/code/gamespy/GP/gpiConnect.c b/code/gamespy/GP/gpiConnect.c new file mode 100644 index 00000000..86fd558b --- /dev/null +++ b/code/gamespy/GP/gpiConnect.c @@ -0,0 +1,988 @@ +/* +gpiConnect.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +//INCLUDES +////////// +#include +#include +#include "gpi.h" +#include + + +//DEFINES +///////// +// Connection Manager Address. +////////////////////////////// +#define GPI_CONNECTION_MANAGER_NAME "gpcm." GSI_DOMAIN_NAME +#define GPI_CONNECTION_MANAGER_PORT 29900 + +#define GPI_UDP_HEADER "gamespygp" +// Random String stuff. +/////////////////////// +#define RANDSTRING "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" +//this is off by one +//#define RANDOMCHAR() (RANDSTRING[(rand() * sizeof(RANDSTRING)) / (RAND_MAX + 1)]) +#define RANDOMCHAR() (RANDSTRING[rand() % (sizeof(RANDSTRING) - 1)]) + +//GLOBALS +///////// +char GPConnectionManagerHostname[64] = GPI_CONNECTION_MANAGER_NAME; + +//FUNCTIONS +/////////// +static void randomString( + char * buffer, + int numChars +) +{ + int i; + + for(i = 0 ; i < numChars ; i++) + buffer[i] = RANDOMCHAR(); + buffer[i] = '\0'; +} + +static GPResult +gpiStartConnect( + GPConnection * connection, + GPIOperation * operation +) +{ + struct sockaddr_in address; + int rcode; + //int len; + GPIConnection * iconnection = (GPIConnection*)*connection; + struct hostent * host; + + GSUdpErrorCode anError; + strncpy(iconnection->mHeader, GPI_UDP_HEADER, GS_UDP_MSG_HEADER_LEN); + + if (!gsUdpEngineIsInitialized()) + { + unsigned short peerPort = GPI_PEER_PORT; + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Notice, + "Initializing UDP Layer\n"); + anError = gsUdpEngineInitialize(peerPort, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + if (anError != GS_UDP_NO_ERROR) + { + while (anError != GS_UDP_NO_ERROR && peerPort < GPI_PEER_PORT + 100) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Comment, + "Port %d failed, trying next port\n", peerPort); + anError = gsUdpEngineInitialize(++peerPort, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + } + if (anError != GS_UDP_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_HotError, + "Tryed all 100 ports after default port, giving up.\n"); + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_UDP_LAYER, + "There was error starting the UDP layer."); + } + } + if (!iconnection->firewall) + { + iconnection->peerPort = peerPort; + } + } + else + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Notice, + "UDP Layer already initialized, using existing port.\n"); + iconnection->peerPort = gsUdpEngineGetLocalPort(); + } + anError = gsUdpEngineAddMsgHandler(iconnection->mHeader, iconnection->mHeader, NULL, gpiPeerAcceptedCallback, gpiPeerLeftCallback, + gpiPeerPingReplyCallback, gpiPeerMessageCallback, connection); + if (anError != GS_UDP_NO_ERROR) + { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_UDP_LAYER, "There was an error starting the UDP Layer."); + } + if(iconnection->firewall) + { + + /* + // Create the peer listening socket. + //////////////////////////////////// + iconnection->peerSocket = socket(AF_INET, SOCK_STREAM, 0); + if(iconnection->peerSocket == INVALID_SOCKET) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error creating a socket."); + + // Make it non-blocking. + //////////////////////// + rcode = SetSockBlocking(iconnection->peerSocket,0); + if (rcode == 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error making a socket non-blocking."); + // Bind the socket. + /////////////////// + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + rcode = bind(iconnection->peerSocket, (struct sockaddr *)&address, sizeof(struct sockaddr_in)); + if(gsiSocketIsError(rcode)) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error binding a socket."); + + // Start listening on the socket. + ///////////////////////////////// + rcode = listen(iconnection->peerSocket, SOMAXCONN); + if(gsiSocketIsError(rcode)) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error listening on a socket."); + + // Get the socket's port. + ///////////////////////// + len = sizeof(struct sockaddr_in); + rcode = getsockname(iconnection->peerSocket, (struct sockaddr *)&address, &len); + + if (gsiSocketIsError(rcode)) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error getting a socket's addres."); + iconnection->peerPort = address.sin_port; + */ + + + iconnection->peerPort = 0; + } + /* + else + { + // Deprecated TCP code; Replaced by UDP Layer + // No local port. + ///////////////// + //iconnection->peerSocket = INVALID_SOCKET; + + // Set to nothing because NN will determine this + ////////////////////////// + //iconnection->peerPort = 0; + } + */ + + + + // Create the cm socket. + //////////////////////// + iconnection->cmSocket = socket(AF_INET, SOCK_STREAM, 0); + if(iconnection->cmSocket == INVALID_SOCKET) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error creating a socket."); + + // Make it non-blocking. + //////////////////////// + rcode = SetSockBlocking(iconnection->cmSocket,0); + if(rcode == 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error making a socket non-blocking."); +/* + // Bind the socket. + /////////////////// + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + rcode = bind(iconnection->cmSocket, (struct sockaddr *)&address, sizeof(struct sockaddr_in)); + if (gsiSocketIsError(rcode)) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error binding a socket."); +*/ + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + // Get the server host. + /////////////////////// + if (inet_addr(GPConnectionManagerHostname) == INADDR_NONE) + { + host = gethostbyname(GPConnectionManagerHostname); + if(host == NULL) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "Could not resolve connection mananger host name."); + address.sin_addr.s_addr = *(unsigned int *)host->h_addr_list[0]; + //printf("Resolved Hostname and copied address: %s\n", inet_ntoa(address.sin_addr)); + } + else + { + address.sin_addr.s_addr = inet_addr(GPConnectionManagerHostname); + //printf("Using hardcoded address: %s", GPConnectionManagerHostname); + } + + // Connect the socket. + ////////////////////// + assert(address.sin_addr.s_addr != 0); + address.sin_port = htons(GPI_CONNECTION_MANAGER_PORT); + rcode = connect(iconnection->cmSocket, (struct sockaddr *)&address, sizeof(struct sockaddr_in)); + if (gsiSocketIsError(rcode)) + { + int error = GOAGetLastError(iconnection->cmSocket); + if((error != WSAEWOULDBLOCK) && (error != WSAEINPROGRESS) && (error != WSAETIMEDOUT)) + { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error connecting a socket."); + } + } + + // We're waiting for the connect to complete. + ///////////////////////////////////////////// + operation->state = GPI_CONNECTING; + iconnection->connectState = GPI_CONNECTING; + + return GP_NO_ERROR; +} + +GPResult +gpiConnect( + GPConnection * connection, + const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + const char authtoken[GP_AUTHTOKEN_LEN], + const char partnerchallenge[GP_PARTNERCHALLENGE_LEN], + const char cdkey[GP_CDKEY_LEN], + GPEnum firewall, + GPIBool newuser, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnectData * data; + GPIOperation * operation; + GPIConnection * iconnection = (GPIConnection*)*connection; + GPResult result; + + // Reset if this connection was already used. + ///////////////////////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + CHECK_RESULT(gpiReset(connection)); + + // Error check. + /////////////// + if(iconnection->connectState != GPI_NOT_CONNECTED) + Error(connection, GP_PARAMETER_ERROR, "Invalid connection."); + + // Get the firewall setting. + //////////////////////////// +#if defined(GS_WIRELESS_DEVICE) + GSI_UNUSED(firewall); + iconnection->firewall = GPITrue; +#else + switch(firewall) + { + case GP_FIREWALL: + iconnection->firewall = GPITrue; + break; + case GP_NO_FIREWALL: + iconnection->firewall = GPIFalse; + break; + default: + Error(connection, GP_PARAMETER_ERROR, "Invalid firewall."); + } +#endif + + // Get the nick, uniquenick, email, and password. + ///////////////////////////////////////////////// + strzcpy(iconnection->nick, nick, GP_NICK_LEN); + strzcpy(iconnection->uniquenick, uniquenick, GP_UNIQUENICK_LEN); + strzcpy(iconnection->email, email, GP_EMAIL_LEN); + strzcpy(iconnection->password, password, GP_PASSWORD_LEN); + +#ifdef GSI_UNICODE + // Create the _W version in addition + UTF8ToUCS2StringLen(iconnection->nick, iconnection->nick_W, GP_NICK_LEN); + UTF8ToUCS2StringLen(iconnection->uniquenick, iconnection->uniquenick_W, GP_UNIQUENICK_LEN); + UTF8ToUCS2StringLen(iconnection->email, iconnection->email_W, GP_EMAIL_LEN); + UTF8ToUCS2StringLen(iconnection->password, iconnection->password_W, GP_PASSWORD_LEN); +#endif + + // Lowercase the email. + /////////////////////// + _strlwr(iconnection->email); + +#ifdef GSI_UNICODE + // Update the UCS2 version (emails are ASCII anyhow so lowercasing didn't data) + AsciiToUCS2String(iconnection->email, iconnection->email_W); +#endif + + // Create a connect operation data struct. + ////////////////////////////////////////// + data = (GPIConnectData *)gsimalloc(sizeof(GPIConnectData)); + if(data == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(data, 0, sizeof(GPIConnectData)); + + // Check for new user. + ////////////////////// + data->newuser = newuser; + + // Store pre-auth data. + /////////////////////// + if(authtoken[0] && partnerchallenge[0]) + { + strzcpy(data->authtoken, authtoken, GP_AUTHTOKEN_LEN); + strzcpy(data->partnerchallenge, partnerchallenge, GP_PARTNERCHALLENGE_LEN); + } + + // Store cdkey if we have one. + ////////////////////////////// + if(cdkey) + strzcpy(data->cdkey, cdkey, GP_CDKEY_LEN); + + // Add the operation to the list. + ///////////////////////////////// + CHECK_RESULT(gpiAddOperation(connection, GPI_CONNECT, data, &operation, blocking, callback, param)); + + // Start it. + //////////// + result = gpiStartConnect(connection, operation); + if(result != GP_NO_ERROR) + { + operation->result = result; + gpiFailedOpCallback(connection, operation); + gpiDisconnect(connection, GPIFalse); + return result; + } + + // Process it if blocking. + ////////////////////////// + if(operation->blocking) + CHECK_RESULT(gpiProcess(connection, operation->id)); + + return GP_NO_ERROR; +} + +static GPResult +gpiSendLogin( + GPConnection * connection, + GPIConnectData * data +) +{ + char buffer[512]; + char response[33]; + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIProfile * profile; + char * passphrase; + char userBuffer[GP_NICK_LEN + GP_EMAIL_LEN]; + char partnerBuffer[11]; + char * user; + + // Construct the user challenge. + //////////////////////////////// + randomString(data->userChallenge, sizeof(data->userChallenge) - 1); + + // Hash the password. + ///////////////////// + if(data->partnerchallenge[0]) + passphrase = data->partnerchallenge; + else + passphrase = iconnection->password; + MD5Digest((unsigned char*)passphrase, strlen(passphrase), data->passwordHash); + + // Construct the user. + ////////////////////// + if(iconnection->partnerID != GP_PARTNERID_GAMESPY) + { + sprintf(partnerBuffer, "%d@", iconnection->partnerID); + } + else + { + // GS ID's do not stash the partner ID in the auth challenge to support legacy clients. + strcpy(partnerBuffer, ""); + } + + if(data->authtoken[0]) + user = data->authtoken; + else if(iconnection->uniquenick[0]) + { + sprintf(userBuffer, "%s%s", partnerBuffer, iconnection->uniquenick); + user = userBuffer; + } + else + { + sprintf(userBuffer, "%s%s@%s", partnerBuffer, iconnection->nick, iconnection->email); + user = userBuffer; + } + + // Construct the response. + ////////////////////////// + sprintf(buffer, "%s%s%s%s%s%s", + data->passwordHash, + " ", + user, + data->userChallenge, + data->serverChallenge, + data->passwordHash); + MD5Digest((unsigned char *)buffer, strlen(buffer), response); + + // Check for an existing profile. + ///////////////////////////////// + if(iconnection->infoCaching) + { + gpiFindProfileByUser(connection, iconnection->nick, iconnection->email, &profile); + if(profile != NULL) + { + // Get the userid and profileid. + //////////////////////////////// + iconnection->userid = profile->userId; + iconnection->profileid = profile->profileId; + } + } + + // Construct the outgoing message. + ////////////////////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\login\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\challenge\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, data->userChallenge); + if(data->authtoken[0]) + { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\authtoken\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, data->authtoken); + } + else if(iconnection->uniquenick[0]) + { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\uniquenick\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, iconnection->uniquenick); + } + else + { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\user\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, iconnection->nick); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "@"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, iconnection->email); + } + if(iconnection->userid != 0) + { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\userid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->userid); + } + if(iconnection->profileid != 0) + { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\profileid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->profileid); + } + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\partnerid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->partnerID); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\response\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, response); + if(iconnection->firewall == GP_FIREWALL) + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\firewall\\1"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\port\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->peerPort); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\productid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->productID); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\gamename\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, __GSIACGamename); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\namespaceid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->namespaceID); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sdkrevision\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, GPI_SDKREV); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\quiet\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->quietModeFlags); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\1"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +static GPResult +gpiSendNewuser( + GPConnection * connection, + GPIConnectData * data +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + size_t i; + const int useAlternateEncoding = 1; + + // Encrypt the password (xor with random values) + char passwordenc[GP_PASSWORDENC_LEN]; + gpiEncodeString(iconnection->password, passwordenc); + + // Construct the outgoing message. + ////////////////////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\newuser\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\email\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, iconnection->email); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\nick\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, iconnection->nick); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\passwordenc\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, passwordenc); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\productid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->productID); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\gamename\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, __GSIACGamename); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\namespaceid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->namespaceID); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\uniquenick\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, iconnection->uniquenick); + if(data->cdkey[0]) + { + // Encrypt the cdkey (xor with random values) + char cdkeyxor[GP_CDKEY_LEN]; + char cdkeyenc[GP_CDKEYENC_LEN]; + size_t cdkeylen = strlen(data->cdkey); + + Util_RandSeed((unsigned long)GP_XOR_SEED); + for (i=0; i < cdkeylen; i++) + { + // XOR each character with the next rand + char aRand = (char)Util_RandInt(0, 0xFF); + cdkeyxor[i] = (char)(data->cdkey[i] ^ aRand); + } + cdkeyxor[i] = '\0'; + + // Base 64 it (printable chars only) + B64Encode(cdkeyxor, cdkeyenc, (int)cdkeylen, useAlternateEncoding); + + //gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\cdkey\\"); + //gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, data->cdkey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\cdkeyenc\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, cdkeyenc); + } + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\partnerid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->partnerID); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\1"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult +gpiProcessConnect( + GPConnection * connection, + GPIOperation * operation, + const char * input +) +{ + char buffer[512]; + char check[33]; + char uniquenick[GP_UNIQUENICK_LEN]; + GPIConnectData * data; + GPIConnection * iconnection = (GPIConnection*)*connection; + GPICallback callback; + GPIProfile * profile; + char userBuffer[GP_NICK_LEN + GP_EMAIL_LEN]; + char partnerBuffer[11]; + char * user; + + // Check for an error. + ////////////////////// + if(gpiCheckForError(connection, input, GPIFalse)) + { + // Is this a deleted profile? + ///////////////////////////// + if((iconnection->errorCode == GP_LOGIN_PROFILE_DELETED) && iconnection->profileid) + { + // Remove this profile object. + ////////////////////////////// + gpiRemoveProfileByID(connection, iconnection->profileid); + + // If we have the profileid/userid cached, lose them. + ///////////////////////////////////////////////////// + iconnection->userid = 0; + iconnection->profileid = 0; + } + // Check for creating an existing profile. + ////////////////////////////////////////// + else if(iconnection->errorCode == GP_NEWUSER_BAD_NICK) + { + // Store the pid. + ///////////////// + if(gpiValueForKey(input, "\\pid\\", buffer, sizeof(buffer))) + iconnection->profileid = atoi(buffer); + } + + // Call the callbacks. + ////////////////////// + CallbackFatalError(connection, GP_SERVER_ERROR, iconnection->errorCode, iconnection->errorString); + } + + // Get a pointer to the data. + ///////////////////////////// + data = (GPIConnectData*)operation->data; + + switch(operation->state) + { + case GPI_CONNECTING: + // This should be \lc\1. + //////////////////////// + if(strncmp(input, "\\lc\\1", 5) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Get the server challenge. + //////////////////////////// + if(!gpiValueForKey(input, "\\challenge\\", data->serverChallenge, sizeof(data->serverChallenge))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Check if this is a new user. + /////////////////////////////// + if(data->newuser) + { + // Send a new user message. + /////////////////////////// + CHECK_RESULT(gpiSendNewuser(connection, data)); + + // Update the operation's state. + //////////////////////////////// + operation->state = GPI_REQUESTING; + } + else + { + // Send a login message. + //////////////////////// + CHECK_RESULT(gpiSendLogin(connection, data)); + + // Update the operation's state. + //////////////////////////////// + operation->state = GPI_LOGIN; + } + + break; + + case GPI_REQUESTING: + // This should be \nur\. + //////////////////////// + if(strncmp(input, "\\nur\\", 5) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Get the userid. + ////////////////// + if(!gpiValueForKey(input, "\\userid\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexepected data was received from the server."); + iconnection->userid = atoi(buffer); + + // Get the profileid. + ///////////////////// + if(!gpiValueForKey(input, "\\profileid\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexepected data was received from the server."); + iconnection->profileid = atoi(buffer); + + // Send a login request. + //////////////////////// + CHECK_RESULT(gpiSendLogin(connection, data)); + + // Update the operation's state. + //////////////////////////////// + operation->state = GPI_LOGIN; + + break; + + case GPI_LOGIN: + // This should be \lc\2. + //////////////////////// + if(strncmp(input, "\\lc\\2", 5) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Get the sesskey. + /////////////////// + if(!gpiValueForKey(input, "\\sesskey\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexepected data was received from the server."); + iconnection->sessKey = atoi(buffer); + + // Get the userid. + ////////////////// + if(!gpiValueForKey(input, "\\userid\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexepected data was received from the server."); + iconnection->userid = atoi(buffer); + + // Get the profileid. + ///////////////////// + if(!gpiValueForKey(input, "\\profileid\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexepected data was received from the server."); + iconnection->profileid = atoi(buffer); + + // Get the uniquenick. + ////////////////////// + if(!gpiValueForKey(input, "\\uniquenick\\", uniquenick, sizeof(uniquenick))) + uniquenick[0] = '\0'; + + // Get the loginticket. + ////////////////////// + if(!gpiValueForKey(input, "\\lt\\", iconnection->loginTicket, sizeof(iconnection->loginTicket))) + iconnection->loginTicket[0] = '\0'; + + + // Construct the user. + ////////////////////// + if(iconnection->partnerID != GP_PARTNERID_GAMESPY) + { + sprintf(partnerBuffer, "%d@", iconnection->partnerID); + } + else + { + // GS ID's do not stash the partner ID in the auth challenge to support legacy clients. + strcpy(partnerBuffer, ""); + } + + if(data->authtoken[0]) + user = data->authtoken; + else if(iconnection->uniquenick[0]) + { + sprintf(userBuffer, "%s%s", partnerBuffer, iconnection->uniquenick); + user = userBuffer; + } + else + { + sprintf(userBuffer, "%s%s@%s", partnerBuffer, iconnection->nick, iconnection->email); + user = userBuffer; + } + + // Construct the check. + /////////////////////// + sprintf(buffer, "%s%s%s%s%s%s", + data->passwordHash, + " ", + user, + data->serverChallenge, + data->userChallenge, + data->passwordHash); + MD5Digest((unsigned char *)buffer, strlen(buffer), check); + + // Get the proof. + ///////////////// + if(!gpiValueForKey(input, "\\proof\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexepected data was received from the server."); + + // Check the server authentication. + /////////////////////////////////// + if(memcmp(check, buffer, 32) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_LOGIN_SERVER_AUTH_FAILED, "Could not authenticate server."); + + // Add the local profile to the list. + ///////////////////////////////////// + if(iconnection->infoCaching) + { + profile = gpiProfileListAdd(connection, iconnection->profileid); + profile->profileId = iconnection->profileid; + profile->userId = iconnection->userid; + } + + // Set the connect state. + ///////////////////////// + iconnection->connectState = GPI_CONNECTED; + + // Call the connect-response callback. + ////////////////////////////////////// + callback = operation->callback; + if(callback.callback != NULL) + { + GPConnectResponseArg * arg; + arg = (GPConnectResponseArg *)gsimalloc(sizeof(GPConnectResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(arg, 0, sizeof(GPConnectResponseArg)); + + arg->profile = (GPProfile)iconnection->profileid; + arg->result = GP_NO_ERROR; +#ifndef GSI_UNICODE + strzcpy(arg->uniquenick, uniquenick, GP_UNIQUENICK_LEN); +#else + UTF8ToUCS2StringLen(uniquenick, arg->uniquenick, GP_UNIQUENICK_LEN); +#endif + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + } + + // This operation is complete. + ////////////////////////////// + gpiRemoveOperation(connection, operation); + + // Get the local profile's info. + //////////////////////////////// +#if 0 + gpiAddOperation(connection, GPI_GET_INFO, NULL, &operation, GP_NON_BLOCKING, NULL, NULL); + gpiSendGetInfo(connection, iconnection->profileid, operation->id); +#endif + + +#ifdef _PS3 + // We just connected, so setup buddy sync && start NP init + // For future, we can limit syncs by setting flags to turn on/off here + ////////////////////////////////////////////////////////////////////// + iconnection->npPerformBuddySync = gsi_true; + iconnection->npPerformBlockSync = gsi_true; + iconnection->loginTime = current_time(); + + if (!iconnection->npInitialized) + gpiInitializeNpBasic(connection); +#endif + + break; + + default: + break; + } + + return GP_NO_ERROR; +} + +GPResult +gpiCheckConnect( + GPConnection * connection +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + int state; + + // Check if the connection is completed. + //////////////////////////////////////// + CHECK_RESULT(gpiCheckSocketConnect(connection, iconnection->cmSocket, &state)); + + // Check for a failed attempt. + ////////////////////////////// + if(state == GPI_DISCONNECTED) + CallbackFatalError(connection, GP_SERVER_ERROR, GP_LOGIN_CONNECTION_FAILED, "The server has refused the connection."); + + // Check if not finished connecting. + //////////////////////////////////// + if(state == GPI_NOT_CONNECTED) + return GP_NO_ERROR; + + // We're now negotiating the connection. + //////////////////////////////////////// + assert(state == GPI_CONNECTED); + iconnection->connectState = GPI_NEGOTIATING; + + return GP_NO_ERROR; +} + +static GPIBool +gpiDisconnectCleanupProfile( + GPConnection * connection, + GPIProfile * profile, + void * data +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GSI_UNUSED(data); + + // Even if we cache buddy/block info, free it up to get rid of mem + // leaks, just don't remove the profile until we save the cache. + ////////////////////////////////////////////////////////////////// + if(profile->buddyStatus) + { + profile->buddyOrBlockCache = gsi_true; + freeclear(profile->buddyStatus->statusString); + freeclear(profile->buddyStatus->locationString); + freeclear(profile->buddyStatus); + } + if (profile->buddyStatusInfo) + { + profile->buddyOrBlockCache = gsi_true; + freeclear(profile->buddyStatusInfo->richStatus); + freeclear(profile->buddyStatusInfo->gameType); + freeclear(profile->buddyStatusInfo->gameVariant); + freeclear(profile->buddyStatusInfo->gameMapName); + if (profile->buddyStatusInfo->extendedInfoKeys) + { + ArrayFree(profile->buddyStatusInfo->extendedInfoKeys); + profile->buddyStatusInfo->extendedInfoKeys = NULL; + } + freeclear(profile->buddyStatusInfo); + } + if (profile->blocked) + profile->buddyOrBlockCache = gsi_true; + + freeclear(profile->authSig); + freeclear(profile->peerSig); + profile->requestCount = 0; + + // Remove Profile if: + // (there is no info to cache) or + // (we only cache buddies/blocked and the user is not a buddy or a block) + if ((!profile->cache) || + (iconnection->infoCachingBuddyAndBlockOnly==GPITrue && !profile->buddyOrBlockCache)) + { + gpiRemoveProfile(connection, profile); + return GPIFalse; + } + + return GPITrue; +} + +void +gpiDisconnect( + GPConnection * connection, + GPIBool tellServer +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIPeer * peer; + GPIPeer * delPeer; + GPIBool connClosed; + + // Check if we're already disconnected. + // PANTS|05.15.00 + /////////////////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + return; + + // Skip most of this stuff if we never actually connected. + // PANTS|05.16.00 + ////////////////////////////////////////////////////////// + if(iconnection->connectState != GPI_NOT_CONNECTED) + { + // Are we connected? + //////////////////// + if(tellServer && (iconnection->connectState == GPI_CONNECTED)) + { + // Send the disconnect. + /////////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\logout\\\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + } + + // Always flush remaining messages. + // PANTS|05.16.00 + /////////////////////////////////// + gpiSendFromBuffer(connection, iconnection->cmSocket, &iconnection->outputBuffer, &connClosed, GPITrue, "CM"); + + // Cleanup the connection. + ////////////////////////// + if(iconnection->cmSocket != INVALID_SOCKET) + { + shutdown(iconnection->cmSocket, 2); + closesocket(iconnection->cmSocket); + iconnection->cmSocket = INVALID_SOCKET; + } + + if(/*iconnection->peerSocket != INVALID_SOCKET*/ gsUdpEngineIsInitialized()) + { + //shutdown(iconnection->peerSocket, 2); + //closesocket(iconnection->peerSocket); + //iconnection->peerSocket = INVALID_SOCKET; + gsUdpEngineRemoveMsgHandler(iconnection->mHeader); + if (gsUdpEngineNoMoreMsgHandlers() && gsUdpEngineNoApp()) + gsUdpEngineShutdown(); + } + + // We're disconnected. + ////////////////////// + iconnection->connectState = GPI_DISCONNECTED; + + // Don't keep the userid/profileid. + /////////////////////////////////// + iconnection->userid = 0; + iconnection->profileid = 0; + } + + // freeclear all the memory. + /////////////////////// + freeclear(iconnection->socketBuffer.buffer); + freeclear(iconnection->inputBuffer); + freeclear(iconnection->outputBuffer.buffer); + freeclear(iconnection->updateproBuffer.buffer); + freeclear(iconnection->updateuiBuffer.buffer); + while(iconnection->operationList != NULL) + gpiRemoveOperation(connection, iconnection->operationList); + iconnection->operationList = NULL; + for(peer = iconnection->peerList ; peer != NULL ; ) + { + delPeer = peer; + peer = peer->pnext; + gpiDestroyPeer(connection, delPeer); + } + iconnection->peerList = NULL; + + // Cleanup buddies. + // This is not optimal - because we can't continue the mapping + // after freeing a profile, we need to start it all over again. + /////////////////////////////////////////////////////////////// + while(!gpiProfileMap(connection, gpiDisconnectCleanupProfile, NULL)) { }; +} diff --git a/code/gamespy/GP/gpiConnect.h b/code/gamespy/GP/gpiConnect.h new file mode 100644 index 00000000..b9910bf0 --- /dev/null +++ b/code/gamespy/GP/gpiConnect.h @@ -0,0 +1,69 @@ +/* +gpiConnect.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#ifndef _GPICONNECT_H_ +#define _GPICONNECT_H_ + +//INCLUDES +////////// +#include "gpi.h" + +//DEFINES +///////// +// Connect States. +////////////////// +#define GPI_NOT_CONNECTED 0 +#define GPI_CONNECTING 1 +#define GPI_NEGOTIATING 2 +#define GPI_CONNECTED 3 +#define GPI_DISCONNECTED 4 +#define GPI_PROFILE_DELETING 5 + +//FUNCTIONS +/////////// +GPResult +gpiConnect( + GPConnection * connection, + const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + const char authtoken[GP_AUTHTOKEN_LEN], + const char partnerchallenge[GP_PARTNERCHALLENGE_LEN], + const char cdkey[GP_CDKEY_LEN], + GPEnum firewall, + GPIBool newuser, + GPEnum blocking, + GPCallback callback, + void * param +); + +void +gpiDisconnect( + GPConnection * connection, + GPIBool tellServer +); + +GPResult +gpiProcessConnect( + GPConnection * connection, + GPIOperation * operation, + const char * input +); + +GPResult +gpiCheckConnect( + GPConnection * connection +); + +#endif diff --git a/code/gamespy/GP/gpiInfo.c b/code/gamespy/GP/gpiInfo.c new file mode 100644 index 00000000..60ae65f0 --- /dev/null +++ b/code/gamespy/GP/gpiInfo.c @@ -0,0 +1,1286 @@ +/* +gpiInfo.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +//INCLUDES +////////// +#include "gpi.h" + +//FUNCTIONS +/////////// +static GPIBool +gpiIsValidDate( + int day, + int month, + int year +) +{ + // Check for a blank. + ///////////////////// + if((day == 0) && (month == 0) && (year == 0)) + return GPITrue; + + // Check for negatives. + /////////////////////// + if((day < 0) || (month < 0) || (year < 0)) + return GPIFalse; + + // Validate the day of the month. + ///////////////////////////////// + switch(month) + { + // No month. + //////////// + case 0: + // Can't specify a day without a month. + /////////////////////////////////////// + if(day != 0) + return GPIFalse; + break; + + // 31-day month. + //////////////// + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12: + if(day > 31) + return GPIFalse; + break; + + // 30-day month. + //////////////// + case 4: + case 6: + case 9: + case 11: + if(day > 30) + return GPIFalse; + break; + + // 28/29-day month. + /////////////////// + case 2: + // Leap year? + ///////////// + if((((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0)) + { + if(day > 29) + return GPIFalse; + } + else + { + if(day > 28) + return GPIFalse; + } + break; + + // Invalid month. + ///////////////// + default: + return GPIFalse; + } + + // Check that the date is in the valid range. + // 01.01.1900 - 06.06.2079 + // PANTS|02.14.2000 + ///////////////////////////////////////////// + if(year < 1900) + return GPIFalse; + if(year > 2079) + return GPIFalse; + if(year == 2079) + { + if(month > 6) + return GPIFalse; + if((month == 6) && (day > 6)) + return GPIFalse; + } + + return GPITrue; +} + +static GPResult +gpiDateToInt( + GPConnection * connection, + int * date, + int day, + int month, + int year +) +{ + int temp; + + // Pack the day/month/year into an int. + // 31-22: day + // 23-16: month + // 15-00: year + /////////////////////////////////////// + + // Error check. + /////////////// + assert(gpiIsValidDate(day, month, year)); + if(!gpiIsValidDate(day, month, year)) + Error(connection, GP_PARAMETER_ERROR, "Invalid date."); + + // Pack! + //////// + temp = 0; + temp |= (day << 24); + temp |= (month << 16); + temp |= year; + + // Set it. + ////////// + *date = temp; + + return GP_NO_ERROR; +} + +static GPResult +gpiIntToDate( + GPConnection * connection, + int date, + int * day, + int * month, + int * year +) +{ + int d; + int m; + int y; + + // Unpack the int into a day/month/year. + // 31-22: day + // 23-16: month + // 15-00: year + //////////////////////////////////////// + + // Split up the date. + ///////////////////// + d = ((date >> 24) & 0xFF); + m = ((date >> 16) & 0xFF); + y = (date & 0xFFFF); + + // Error check. + /////////////// + assert(gpiIsValidDate(d, m, y)); + if(!gpiIsValidDate(d, m, y)) + Error(connection, GP_PARAMETER_ERROR, "Invalid date."); + + // It's all good. + ///////////////// + *day = d; + *month = m; + *year = y; + + return GP_NO_ERROR; +} + +void +gpiInfoCacheToArg( + const GPIInfoCache * cache, + GPGetInfoResponseArg * arg +) +{ +#ifndef GSI_UNICODE + // Copy.... + /////////// + if(cache->nick) + strzcpy(arg->nick, cache->nick, GP_NICK_LEN); + else + arg->nick[0] = '\0'; + if(cache->uniquenick) + strzcpy(arg->uniquenick, cache->uniquenick, GP_UNIQUENICK_LEN); + else + arg->uniquenick[0] = '\0'; + if(cache->email) + strzcpy(arg->email, cache->email, GP_EMAIL_LEN); + else + arg->email[0] = '\0'; + if(cache->firstname) + strzcpy(arg->firstname, cache->firstname, GP_FIRSTNAME_LEN); + else + arg->firstname[0] = '\0'; + if(cache->lastname) + strzcpy(arg->lastname, cache->lastname, GP_LASTNAME_LEN); + else + arg->lastname[0] = '\0'; + if(cache->homepage) + strzcpy(arg->homepage, cache->homepage, GP_HOMEPAGE_LEN); + else + arg->homepage[0] = '\0'; + arg->icquin = cache->icquin; + strzcpy(arg->zipcode, cache->zipcode, GP_ZIPCODE_LEN); + strzcpy(arg->countrycode, cache->countrycode, GP_COUNTRYCODE_LEN); + arg->longitude = cache->longitude; + arg->latitude = cache->latitude; + if(cache->place) + strzcpy(arg->place, cache->place, GP_PLACE_LEN); + else + arg->place[0] = '\0'; + arg->birthday = cache->birthday; + arg->birthmonth = cache->birthmonth; + arg->birthyear = cache->birthyear; + arg->sex = (GPEnum)cache->sex; + arg->publicmask = (GPEnum)cache->publicmask; + if(cache->aimname) + strzcpy(arg->aimname, cache->aimname, GP_AIMNAME_LEN); + else + arg->aimname[0] = '\0'; +#else + // Copy.... + /////////// + if(cache->nick) + UTF8ToUCS2StringLen(cache->nick, arg->nick, GP_NICK_LEN); + else + arg->nick[0] = '\0'; + if(cache->uniquenick) + UTF8ToUCS2StringLen(cache->uniquenick, arg->uniquenick, GP_UNIQUENICK_LEN); + else + arg->uniquenick[0] = '\0'; + if(cache->email) + UTF8ToUCS2StringLen(cache->email, arg->email, GP_EMAIL_LEN); + else + arg->email[0] = '\0'; + if(cache->firstname) + UTF8ToUCS2StringLen(cache->firstname, arg->firstname, GP_FIRSTNAME_LEN); + else + arg->firstname[0] = '\0'; + if(cache->lastname) + UTF8ToUCS2StringLen(cache->lastname, arg->lastname, GP_LASTNAME_LEN); + else + arg->lastname[0] = '\0'; + if(cache->homepage) + UTF8ToUCS2StringLen(cache->homepage, arg->homepage, GP_HOMEPAGE_LEN); + else + arg->homepage[0] = '\0'; + UTF8ToUCS2StringLen(cache->zipcode, arg->zipcode, GP_ZIPCODE_LEN); + UTF8ToUCS2StringLen(cache->countrycode, arg->countrycode, GP_COUNTRYCODE_LEN); + if(cache->place) + UTF8ToUCS2StringLen(cache->place, arg->place, GP_PLACE_LEN); + else + arg->place[0] = '\0'; + if(cache->aimname) + UTF8ToUCS2StringLen(cache->aimname, arg->aimname, GP_AIMNAME_LEN); + else + arg->aimname[0] = '\0'; +#endif + + // Non string members + arg->icquin = cache->icquin; + arg->longitude = cache->longitude; + arg->latitude = cache->latitude; + + arg->birthday = cache->birthday; + arg->birthmonth = cache->birthmonth; + arg->birthyear = cache->birthyear; + arg->sex = (GPEnum)cache->sex; + arg->publicmask = (GPEnum)cache->publicmask; + + arg->pic = cache->pic; + arg->occupationid = cache->occupationid; + arg->industryid = cache->industryid; + arg->incomeid = cache->incomeid; + arg->marriedid = cache->marriedid; + arg->childcount = cache->childcount; + arg->interests1 = cache->interests1; + arg->ownership1 = cache->ownership1; + arg->conntypeid = cache->conntypeid; +} + +GPResult +gpiProcessGetInfo( + GPConnection * connection, + GPIOperation * operation, + const char * input +) +{ + GPIInfoCache infoCache; + char buffer[64]; + int profileid; + GPIProfile * profile; + GPIConnection * iconnection = (GPIConnection*)*connection; + GPICallback callback; + GPIPeer * peer; + char nick[GP_NICK_LEN]; + char uniquenick[GP_UNIQUENICK_LEN]; + char email[GP_EMAIL_LEN]; + char firstname[GP_FIRSTNAME_LEN]; + char lastname[GP_LASTNAME_LEN]; + char homepage[GP_HOMEPAGE_LEN]; + char aimname[GP_AIMNAME_LEN]; + GPIBool saveSig; + + // Check for an error. + ////////////////////// + if(gpiCheckForError(connection, input, GPITrue)) + return GP_SERVER_ERROR; + + // This should be \pi\. + /////////////////////// + if(strncmp(input, "\\pi\\", 4) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Get the profile id. + ////////////////////// + if(!gpiValueForKey(input, "\\profileid\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + profileid = atoi(buffer); + assert(profileid > 0); + + // Get the profile - we might not have a profile object. + //////////////////////////////////////////////////////// + gpiGetProfile(connection, (GPProfile)profileid, &profile); + + // Setup the info cache. + //////////////////////// + memset(&infoCache, 0, sizeof(GPIInfoCache)); + infoCache.nick = nick; + infoCache.uniquenick = uniquenick; + infoCache.email = email; + infoCache.firstname = firstname; + infoCache.lastname = lastname; + infoCache.homepage = homepage; + infoCache.aimname = aimname; + + // Parse the info. + ////////////////// + if(!gpiValueForKey(input, "\\nick\\", infoCache.nick, GP_NICK_LEN)) + infoCache.nick[0] = '\0'; + if(!gpiValueForKey(input, "\\uniquenick\\", infoCache.uniquenick, GP_UNIQUENICK_LEN)) + infoCache.uniquenick[0] = '\0'; + if(!gpiValueForKey(input, "\\email\\", infoCache.email, GP_EMAIL_LEN)) + infoCache.email[0] = '\0'; + if(!gpiValueForKey(input, "\\firstname\\", infoCache.firstname, GP_FIRSTNAME_LEN)) + infoCache.firstname[0] = '\0'; + if(!gpiValueForKey(input, "\\lastname\\", infoCache.lastname, GP_LASTNAME_LEN)) + infoCache.lastname[0] = '\0'; + if(!gpiValueForKey(input, "\\icquin\\", buffer, sizeof(buffer))) + infoCache.icquin = -1; + else + infoCache.icquin = atoi(buffer); + if(!gpiValueForKey(input, "\\homepage\\", infoCache.homepage, GP_HOMEPAGE_LEN)) + infoCache.homepage[0] = '\0'; + if(!gpiValueForKey(input, "\\zipcode\\", infoCache.zipcode, sizeof(infoCache.zipcode))) + infoCache.zipcode[0] = '\0'; + if(!gpiValueForKey(input, "\\countrycode\\", infoCache.countrycode, sizeof(infoCache.countrycode))) + infoCache.countrycode[0] = '\0'; + if(!gpiValueForKey(input, "\\lon\\", buffer, sizeof(buffer))) + infoCache.longitude = 0; + else + infoCache.longitude = (float)atof(buffer); + if(!gpiValueForKey(input, "\\lat\\", buffer, sizeof(buffer))) + infoCache.latitude = 0; + else + infoCache.latitude = (float)atof(buffer); + if(!gpiValueForKey(input, "\\loc\\", infoCache.place, GP_PLACE_LEN)) + infoCache.place[0] = '\0'; + if(!gpiValueForKey(input, "\\birthday\\", buffer, sizeof(buffer))) + { + infoCache.birthday = 0; + infoCache.birthmonth = 0; + infoCache.birthyear = 0; + } + else + { + CHECK_RESULT(gpiIntToDate(connection, atoi(buffer), &infoCache.birthday, &infoCache.birthmonth, &infoCache.birthyear)); + } + if(!gpiValueForKey(input, "\\sex\\", buffer, sizeof(buffer))) + infoCache.sex = GP_PAT; + else if(buffer[0] == '0') + infoCache.sex = GP_MALE; + else if(buffer[0] == '1') + infoCache.sex = GP_FEMALE; + else + infoCache.sex = GP_PAT; + if(!gpiValueForKey(input, "\\pmask\\", buffer, sizeof(buffer))) + infoCache.publicmask = 0xFFFFFFFF; + else + infoCache.publicmask = atoi(buffer); + if(!gpiValueForKey(input, "\\aim\\", infoCache.aimname, GP_AIMNAME_LEN)) + infoCache.aimname[0] = '\0'; + if(!gpiValueForKey(input, "\\pic\\", buffer, sizeof(buffer))) + infoCache.pic = 0; + else + infoCache.pic = atoi(buffer); + if(!gpiValueForKey(input, "\\occ\\", buffer, sizeof(buffer))) + infoCache.occupationid = 0; + else + infoCache.occupationid = atoi(buffer); + if(!gpiValueForKey(input, "\\ind\\", buffer, sizeof(buffer))) + infoCache.industryid = 0; + else + infoCache.industryid = atoi(buffer); + if(!gpiValueForKey(input, "\\inc\\", buffer, sizeof(buffer))) + infoCache.incomeid = 0; + else + infoCache.incomeid = atoi(buffer); + if(!gpiValueForKey(input, "\\mar\\", buffer, sizeof(buffer))) + infoCache.marriedid = 0; + else + infoCache.marriedid = atoi(buffer); + if(!gpiValueForKey(input, "\\chc\\", buffer, sizeof(buffer))) + infoCache.childcount = 0; + else + infoCache.childcount = atoi(buffer); + if(!gpiValueForKey(input, "\\i1\\", buffer, sizeof(buffer))) + infoCache.interests1 = 0; + else + infoCache.interests1 = atoi(buffer); + if(!gpiValueForKey(input, "\\o1\\", buffer, sizeof(buffer))) + infoCache.ownership1 = 0; + else + infoCache.ownership1 = atoi(buffer); + if(!gpiValueForKey(input, "\\conn\\", buffer, sizeof(buffer))) + infoCache.conntypeid = 0; + else + infoCache.conntypeid = atoi(buffer); + + // Get the peer sig. + //////////////////// + if(!gpiValueForKey(input, "\\sig\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + saveSig = iconnection->infoCaching; + + // Is there a pending peer connection looking for a sig? + //////////////////////////////////////////////////////// + for(peer = iconnection->peerList ; peer != NULL ; peer = peer->pnext) + { + // Is it the same profile? + ////////////////////////// + if(peer->profile == profileid) + { + // Is it getting the sig? + ///////////////////////// + if(peer->state == GPI_PEER_GETTING_SIG) + { + // We need to make sure there's an actual profile object. + ///////////////////////////////////////////////////////// + if(!profile) + profile = gpiProfileListAdd(connection, profileid); + + // It got it. + ///////////// + peer->state = GPI_PEER_GOT_SIG; + + saveSig = GPITrue; + } + } + } + + // Cache info? + ////////////// + if(!profile && iconnection->infoCaching) + profile = gpiProfileListAdd(connection, profileid); + + // Set the peer sig. + //////////////////// + if(saveSig) + { + freeclear(profile->peerSig); + profile->peerSig = goastrdup(buffer); + } + + // Caching info? + //////////////// + if(iconnection->infoCaching) + gpiSetInfoCache(connection, profile, &infoCache); + + // Call the callback. + ///////////////////// + callback = operation->callback; + if(callback.callback != NULL) + { + GPGetInfoResponseArg * arg; + arg = (GPGetInfoResponseArg *)gsimalloc(sizeof(GPGetInfoResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + gpiInfoCacheToArg(&infoCache, arg); + arg->result = GP_NO_ERROR; + arg->profile = (GPProfile)profileid; + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + } + + // This operation is complete. + ////////////////////////////// + gpiRemoveOperation(connection, operation); + + return GP_NO_ERROR; +} + +GPResult +gpiAddLocalInfo( + GPConnection * connection, + GPIBuffer * buffer +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Add updatepro info. + ////////////////////// + if(iconnection->updateproBuffer.len > 0) + { + gpiAppendStringToBuffer(connection, buffer, "\\updatepro\\\\sesskey\\"); + gpiAppendIntToBuffer(connection, buffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, buffer, iconnection->updateproBuffer.buffer); + gpiAppendStringToBuffer(connection, buffer, "\\partnerid\\"); + gpiAppendIntToBuffer(connection, buffer, iconnection->partnerID); + gpiAppendStringToBuffer(connection, buffer, "\\final\\"); + + iconnection->updateproBuffer.len = 0; + } + + // Add updateui info. + ////////////////////// + if(iconnection->updateuiBuffer.len > 0) + { + gpiAppendStringToBuffer(connection, buffer, "\\updateui\\\\sesskey\\"); + gpiAppendIntToBuffer(connection, buffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, buffer, iconnection->updateuiBuffer.buffer); + gpiAppendStringToBuffer(connection, buffer, "\\final\\"); + + iconnection->updateuiBuffer.len = 0; + } + + return GP_NO_ERROR; +} + +static GPResult +gpiSendLocalInfo( + GPConnection * connection, + const char * info, + const char * value +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + CHECK_RESULT(gpiAppendStringToBuffer(connection, &iconnection->updateproBuffer, info)); + CHECK_RESULT(gpiAppendStringToBuffer(connection, &iconnection->updateproBuffer, value)); + + return GP_NO_ERROR; +} + +static GPResult +gpiSendUserInfo( + GPConnection * connection, + const char * info, + const char * value +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + CHECK_RESULT(gpiAppendStringToBuffer(connection, &iconnection->updateuiBuffer, info)); + CHECK_RESULT(gpiAppendStringToBuffer(connection, &iconnection->updateuiBuffer, value)); + + return GP_NO_ERROR; +} + +GPResult +gpiSetInfoi( + GPConnection * connection, + GPEnum info, + int value +) +{ + char intValue[16]; + + // Check the info param. + //////////////////////// + switch(info) + { + case GP_ZIPCODE: + // Error check. + /////////////// + if(value < 0) + Error(connection, GP_PARAMETER_ERROR, "Invalid zipcode."); + + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\zipcode\\", intValue)); + + break; + + case GP_SEX: + // Check the sex type. + ////////////////////// + switch(value) + { + case GP_MALE: + CHECK_RESULT(gpiSendLocalInfo(connection, "\\sex\\", "0")); + break; + + case GP_FEMALE: + CHECK_RESULT(gpiSendLocalInfo(connection, "\\sex\\", "1")); + break; + + case GP_PAT: + CHECK_RESULT(gpiSendLocalInfo(connection, "\\sex\\", "2")); + break; + + default: + Error(connection, GP_PARAMETER_ERROR, "Invalid sex."); + } + + break; + + case GP_ICQUIN: + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\icquin\\", intValue)); + + break; + + case GP_CPUBRANDID: + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendUserInfo(connection, "\\cpubrandid\\", intValue)); + + break; + + case GP_CPUSPEED: + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendUserInfo(connection, "\\cpuspeed\\", intValue)); + + break; + + case GP_MEMORY: + // Divide by 16. + //////////////// + value /= 16; + + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendUserInfo(connection, "\\memory\\", intValue)); + + break; + + case GP_VIDEOCARD1RAM: + // Divide by 4. + /////////////// + value /= 4; + + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendUserInfo(connection, "\\videocard1ram\\", intValue)); + + break; + + case GP_VIDEOCARD2RAM: + // Divide by 4. + /////////////// + value /= 4; + + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendUserInfo(connection, "\\videocard2ram\\", intValue)); + + break; + + case GP_CONNECTIONID: + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendUserInfo(connection, "\\connectionid\\", intValue)); + + break; + + case GP_CONNECTIONSPEED: + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendUserInfo(connection, "\\connectionspeed\\", intValue)); + + break; + + case GP_HASNETWORK: + // A boolean. + ///////////// + if(value) + value = 1; + + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendUserInfo(connection, "\\hasnetwork\\", intValue)); + + break; + + case GP_PIC: + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\pic\\", intValue)); + + break; + + case GP_OCCUPATIONID: + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\occ\\", intValue)); + + break; + + case GP_INDUSTRYID: + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\ind\\", intValue)); + + break; + + case GP_INCOMEID: + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\inc\\", intValue)); + + break; + + case GP_MARRIEDID: + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\mar\\", intValue)); + + break; + + case GP_CHILDCOUNT: + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\chc\\", intValue)); + + break; + + case GP_INTERESTS1: + // Convert it to a string. + ////////////////////////// + sprintf(intValue,"%d",value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\i1\\", intValue)); + + break; + + default: + Error(connection, GP_PARAMETER_ERROR, "Invalid info."); + } + + return GP_NO_ERROR; +} + +GPResult +gpiSetInfos( + GPConnection * connection, + GPEnum info, + const char * value +) +{ + + GPIConnection * iconnection = (GPIConnection*)*connection; + char buffer[256]; + char sex; + + //password encryption stuff + char passwordenc[GP_PASSWORDENC_LEN]; + + // Error check. + /////////////// + if(value == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid value."); + + // Check the info param. + //////////////////////// + switch(info) + { + case GP_NICK: + if(!value[0]) + Error(connection, GP_PARAMETER_ERROR, "Invalid value."); + strzcpy(buffer, value, GP_NICK_LEN); + strzcpy(iconnection->nick, buffer, GP_NICK_LEN); +#ifdef GSI_UNICODE + UTF8ToUCS2StringLen(iconnection->nick, iconnection->nick_W, GP_NICK_LEN); // update the UCS2 version +#endif + CHECK_RESULT(gpiSendLocalInfo(connection, "\\nick\\", buffer)); + break; + + case GP_UNIQUENICK: + if(!value[0]) + Error(connection, GP_PARAMETER_ERROR, "Invalid value."); + strzcpy(buffer, value, GP_UNIQUENICK_LEN); + strzcpy(iconnection->uniquenick, buffer, GP_UNIQUENICK_LEN); +#ifdef GSI_UNICODE + UTF8ToUCS2StringLen(iconnection->uniquenick, iconnection->uniquenick_W, GP_UNIQUENICK_LEN); +#endif + CHECK_RESULT(gpiSendLocalInfo(connection, "\\uniquenick\\", buffer)); + break; + + case GP_EMAIL: + if(!value[0]) + Error(connection, GP_PARAMETER_ERROR, "Invalid value."); + strzcpy(buffer, value, GP_EMAIL_LEN); + _strlwr(buffer); + strzcpy(iconnection->email, buffer, GP_EMAIL_LEN); +#ifdef GSI_UNICODE + UTF8ToUCS2StringLen(iconnection->email, iconnection->email_W, GP_EMAIL_LEN); +#endif + CHECK_RESULT(gpiSendUserInfo(connection, "\\email\\", buffer)); + break; + + case GP_PASSWORD: + if(!value[0]) + Error(connection, GP_PARAMETER_ERROR, "Invalid value."); + strzcpy(buffer, value, GP_PASSWORD_LEN); + strzcpy(iconnection->password, buffer, GP_PASSWORD_LEN); +#ifdef GSI_UNICODE + UTF8ToUCS2StringLen(iconnection->password, iconnection->password_W, GP_PASSWORD_LEN); +#endif + gpiEncodeString(iconnection->password, passwordenc); + CHECK_RESULT(gpiSendUserInfo(connection, "\\passwordenc\\", passwordenc)); + break; + + case GP_FIRSTNAME: + strzcpy(buffer, value, GP_FIRSTNAME_LEN); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\firstname\\", buffer)); + break; + + case GP_LASTNAME: + strzcpy(buffer, value, GP_LASTNAME_LEN); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\lastname\\", buffer)); + break; + + case GP_HOMEPAGE: + strzcpy(buffer, value, GP_HOMEPAGE_LEN); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\homepage\\", buffer)); + break; + + case GP_ZIPCODE: + strzcpy(buffer, value, GP_ZIPCODE_LEN); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\zipcode\\", buffer)); + break; + + case GP_COUNTRYCODE: + // Error check. + /////////////// + if(strlen(value) != 2) + Error(connection, GP_PARAMETER_ERROR, "Invalid countrycode."); + + strzcpy(buffer, value, GP_COUNTRYCODE_LEN); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\countrycode\\", buffer)); + break; + + case GP_SEX: + sex = (char)toupper(value[0]); + if(sex == 'M') + strcpy(buffer, "0"); + else if(sex == 'F') + strcpy(buffer, "1"); + else + strcpy(buffer, "2"); + + CHECK_RESULT(gpiSendLocalInfo(connection, "\\sex\\", buffer)); + break; + + case GP_ICQUIN: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\icquin\\", buffer)); + break; + + case GP_CPUSPEED: + CHECK_RESULT(gpiSetInfoi(connection, GP_CPUSPEED, atoi(value))); + break; + + case GP_MEMORY: + CHECK_RESULT(gpiSetInfoi(connection, GP_MEMORY, atoi(value))); + break; + + case GP_VIDEOCARD1STRING: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\videocard1string\\", buffer)); + break; + + case GP_VIDEOCARD1RAM: + CHECK_RESULT(gpiSetInfoi(connection, GP_VIDEOCARD1RAM, atoi(value))); + break; + + case GP_VIDEOCARD2STRING: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\videocard2string\\", buffer)); + break; + + case GP_VIDEOCARD2RAM: + CHECK_RESULT(gpiSetInfoi(connection, GP_VIDEOCARD2RAM, atoi(value))); + break; + + case GP_CONNECTIONSPEED: + CHECK_RESULT(gpiSetInfoi(connection, GP_CONNECTIONSPEED, atoi(value))); + break; + + case GP_HASNETWORK: + CHECK_RESULT(gpiSetInfoi(connection, GP_HASNETWORK, atoi(value))); + break; + + case GP_OSSTRING: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\osstring\\", buffer)); + break; + + case GP_AIMNAME: + strzcpy(buffer, value, GP_AIMNAME_LEN); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\aim\\", buffer)); + break; + + case GP_PIC: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\pic\\", buffer)); + break; + + case GP_OCCUPATIONID: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\occ\\", buffer)); + break; + + case GP_INDUSTRYID: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\ind\\", buffer)); + break; + + case GP_INCOMEID: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\inc\\", buffer)); + break; + + case GP_MARRIEDID: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\mar\\", buffer)); + break; + + case GP_CHILDCOUNT: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\chc\\", buffer)); + break; + + case GP_INTERESTS1: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\i1\\", buffer)); + break; + + default: + Error(connection, GP_PARAMETER_ERROR, "Invalid info."); + } + + return GP_NO_ERROR; +} + +GPResult +gpiSetInfod( + GPConnection * connection, + GPEnum info, + int day, + int month, + int year +) +{ + int date; + char intValue[16]; + + // Birthday is the only date supported. + /////////////////////////////////////// + if(info != GP_BIRTHDAY) + Error(connection, GP_PARAMETER_ERROR, "Invalid info."); + + // Convert the date into our internal format. + ///////////////////////////////////////////// + CHECK_RESULT(gpiDateToInt(connection, &date, day, month, year)); + + // Convert the int to a string. + /////////////////////////////// + sprintf(intValue,"%d",date); + + // Send the date. + ///////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\birthday\\", intValue)); + + return GP_NO_ERROR; +} + +GPResult +gpiSetInfoMask( + GPConnection * connection, + GPEnum mask +) +{ + char buffer[16]; + + // Convert the mask to a string. + //////////////////////////////// + sprintf(buffer,"%d",mask); + + // Send it. + /////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\publicmask\\", buffer)); + + return GP_NO_ERROR; + +} + +GPResult +gpiSendGetInfo( + GPConnection * connection, + int profileid, + int operationid +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\getprofile\\\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\profileid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profileid); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, operationid); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult +gpiGetInfo( + GPConnection * connection, + GPProfile profile, + GPEnum checkCache, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIProfile * pProfile; + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIOperation * operation = NULL; + GPIBool useCache; + GPResult result; + int id; + + // Check checkCache. + //////////////////// + useCache = (checkCache == GP_CHECK_CACHE) ? GPITrue:GPIFalse; + + // Check the info cache state. + ////////////////////////////// + if(!iconnection->infoCaching) + useCache = GPIFalse; + + // Check for using cached info. + /////////////////////////////// + if(callback && useCache && gpiGetProfile(connection, profile, &pProfile) && pProfile->cache) + { + GPICallback gpiCallback; + GPGetInfoResponseArg * arg; + + arg = (GPGetInfoResponseArg *)gsimalloc(sizeof(GPGetInfoResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + gpiInfoCacheToArg(pProfile->cache, arg); + arg->result = GP_NO_ERROR; + arg->profile = profile; + + gpiCallback.callback = callback; + gpiCallback.param = param; + + // Add a dummy operation. + ///////////////////////// + CHECK_RESULT(gpiAddOperation(connection, GPI_GET_INFO, NULL, &operation, GP_BLOCKING, callback, param)); + id = operation->id; + + // Add the callback. + //////////////////// + CHECK_RESULT(gpiAddCallback(connection, gpiCallback, arg, operation, 0)); + + // Remove the dummy operation. + ////////////////////////////// + gpiRemoveOperation(connection, operation); + } + else + { + // Add the operation. + ///////////////////// + CHECK_RESULT(gpiAddOperation(connection, GPI_GET_INFO, NULL, &operation, blocking, callback, param)); + id = operation->id; + + // Send a request for info. + /////////////////////////// + result = gpiSendGetInfo(connection, profile, operation->id); + CHECK_RESULT(result); + } + + // Process it if blocking. + ////////////////////////// + if(blocking) + { + result = gpiProcess(connection, id); + CHECK_RESULT(result); + } + + return GP_NO_ERROR; +} + +GPResult +gpiGetInfoNoWait( + GPConnection * connection, + GPProfile profile, + GPGetInfoResponseArg * arg +) +{ + GPIProfile * pProfile; + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Check the info cache state. + ////////////////////////////// + if(!iconnection->infoCaching) + return GP_NETWORK_ERROR; + + // Check to see if we have the info cached. + /////////////////////////////////////////// + if(!gpiGetProfile(connection, profile, &pProfile) || !pProfile->cache) + return GP_NETWORK_ERROR; + + // Fill in the arg. + /////////////////// + gpiInfoCacheToArg(pProfile->cache, arg); + arg->result = GP_NO_ERROR; + arg->profile = profile; + + return GP_NO_ERROR; +} + +GPIBool +gpiSetInfoCache( + GPConnection * connection, + pGPIProfile profile, + const GPIInfoCache * cache +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Check if we're caching info. + /////////////////////////////// + if(!iconnection->infoCaching) + return GPITrue; + + // Free any old cached info. + //////////////////////////// + gpiFreeInfoCache(profile); + + // Allocate the new info. + ///////////////////////// + profile->cache = (GPIInfoCache *)gsimalloc(sizeof(GPIInfoCache)); + + // Copy in the new info. + //////////////////////// + if(profile->cache) + { + *profile->cache = *cache; + profile->cache->nick = goastrdup(cache->nick); + profile->cache->uniquenick = goastrdup(cache->uniquenick); + profile->cache->email = goastrdup(cache->email); + profile->cache->firstname = goastrdup(cache->firstname); + profile->cache->lastname = goastrdup(cache->lastname); + profile->cache->homepage = goastrdup(cache->homepage); + profile->cache->aimname = goastrdup(cache->aimname); + } + + return (profile->cache != NULL) ? GPITrue:GPIFalse; +} + +void +gpiFreeInfoCache( + pGPIProfile profile +) +{ + if(!profile->cache) + return; + + freeclear(profile->cache->nick); + freeclear(profile->cache->uniquenick); + freeclear(profile->cache->email); + freeclear(profile->cache->firstname); + freeclear(profile->cache->lastname); + freeclear(profile->cache->homepage); + freeclear(profile->cache->aimname); + freeclear(profile->cache); +} diff --git a/code/gamespy/GP/gpiInfo.h b/code/gamespy/GP/gpiInfo.h new file mode 100644 index 00000000..79fe4bbc --- /dev/null +++ b/code/gamespy/GP/gpiInfo.h @@ -0,0 +1,144 @@ +/* +gpiInfo.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#ifndef _GPIINFO_H_ +#define _GPIINFO_H_ + +//INCLUDES +////////// +#include "gpi.h" + +//TYPES +/////// +// Profile info cache. +////////////////////// +typedef struct +{ + char * nick; + char * uniquenick; + char * email; + char * firstname; + char * lastname; + char * homepage; + int icquin; + char zipcode[GP_ZIPCODE_LEN]; + char countrycode[GP_COUNTRYCODE_LEN]; + float longitude; // negative is west, positive is east. (0, 0) means unknown. + float latitude; // negative is south, positive is north. (0, 0) means unknown. + char place[GP_PLACE_LEN]; // e.g., "USA|California|Irvine", "South Korea|Seoul", "Turkey" + int birthday; + int birthmonth; + int birthyear; + GPEnum sex; + int publicmask; + char * aimname; + int pic; + int occupationid; + int industryid; + int incomeid; + int marriedid; + int childcount; + int interests1; + int ownership1; + int conntypeid; +} GPIInfoCache; + +//FUNCTIONS +/////////// +GPResult +gpiSetInfoi( + GPConnection * connection, + GPEnum info, + int value +); + +GPResult +gpiSetInfos( + GPConnection * connection, + GPEnum info, + const char * value +); + +GPResult +gpiSetInfod( + GPConnection * connection, + GPEnum info, + int day, + int month, + int year +); + +GPResult +gpiSetInfoMask( + GPConnection * connection, + GPEnum mask +); + +void +gpiInfoCacheToArg( + const GPIInfoCache * cache, + GPGetInfoResponseArg * arg +); + +GPResult +gpiGetInfo( + GPConnection * connection, + GPProfile profile, + GPEnum checkCache, + GPEnum blocking, + GPCallback callback, + void * param +); + +GPResult +gpiGetInfoNoWait( + GPConnection * connection, + GPProfile profile, + GPGetInfoResponseArg * arg +); + +GPResult +gpiProcessGetInfo( + GPConnection * connection, + GPIOperation * operation, + const char * input +); + +GPResult +gpiSendGetInfo( + GPConnection * connection, + int profileid, + int operationid +); + +GPResult +gpiAddLocalInfo( + GPConnection * connection, + GPIBuffer * buffer +); + +typedef struct GPIProfile *pGPIProfile; + +GPIBool +gpiSetInfoCache( + GPConnection * connection, + pGPIProfile profile, + const GPIInfoCache * cache +); + +void +gpiFreeInfoCache( + pGPIProfile profile +); + +#endif diff --git a/code/gamespy/GP/gpiKeys.c b/code/gamespy/GP/gpiKeys.c new file mode 100644 index 00000000..009259a2 --- /dev/null +++ b/code/gamespy/GP/gpiKeys.c @@ -0,0 +1,196 @@ +#include "gpi.h" + +void gpiStatusInfoKeyFree(void *element) +{ + GPIKey *aKey = (GPIKey *)element; + freeclear(aKey->keyName); + freeclear(aKey->keyValue); +} + +GPResult gpiStatusInfoKeysInit(GPConnection * connection) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + iconnection->extendedInfoKeys = ArrayNew(sizeof(GPIKey), GPI_INITIAL_NUM_KEYS, gpiStatusInfoKeyFree); + if(!iconnection->extendedInfoKeys) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + return GP_NO_ERROR; +} + +void gpiStatusInfoKeysDestroy(GPConnection * connection) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + if (iconnection->extendedInfoKeys) + { + ArrayFree(iconnection->extendedInfoKeys); + iconnection->extendedInfoKeys = NULL; + } +} + +int gpiStatusInfoKeyCompFunc(const void *elem1, const void *elem2) +{ + GPIKey *key1 = (GPIKey *)elem1, + *key2 = (GPIKey *)elem2; + return strcmp(key1->keyName, key2->keyName); +} + +GPResult gpiStatusInfoAddKey(GPConnection *connection, DArray keys, const char *theKeyName, const char *theKeyValue) +{ + GPIKey aKey; + GS_ASSERT(keys); + GS_ASSERT(theKeyName); + GS_ASSERT(theKeyValue); + + if (!theKeyName) + Error(connection, GP_PARAMETER_ERROR, "Invalid key name"); + if (!theKeyValue) + Error(connection, GP_PARAMETER_ERROR, "Invalid key value"); + + aKey.keyName = goastrdup(theKeyName); + aKey.keyValue = goastrdup(theKeyValue); + + ArrayInsertSorted(keys, &aKey, gpiStatusInfoKeyCompFunc); + + return GP_NO_ERROR; +} + +GPResult gpiStatusInfoDelKey(GPConnection *connection, DArray keys, const char *keyName) +{ + GPIKey aKey; + int anIndex; + GS_ASSERT(keys); + GS_ASSERT(keyName); + + if (!keyName) + Error(connection, GP_PARAMETER_ERROR, "Invalid key name"); + + aKey.keyName = goastrdup(keyName); + anIndex = ArraySearch(keys, &aKey, gpiStatusInfoKeyCompFunc, 0, 1); + if (anIndex != NOT_FOUND) + { + ArrayDeleteAt(keys, anIndex); + } + + freeclear(aKey.keyName); + return GP_NO_ERROR; +} + +GPResult gpiStatusInfoSetKey(GPConnection *connection, DArray keys, const char *keyName, const char *newKeyValue) +{ + GPIKey aKey; + int anIndex; + GS_ASSERT(keys); + GS_ASSERT(keyName); + + if (!keyName) + Error(connection, GP_PARAMETER_ERROR, "Invalid key name"); + + aKey.keyName = goastrdup(keyName); + anIndex = ArraySearch(keys, &aKey, gpiStatusInfoKeyCompFunc, 0, 1); + if (anIndex != NOT_FOUND) + { + GPIKey *aKeyFound = (GPIKey *)ArrayNth(keys, anIndex); + gsifree(aKeyFound->keyValue); + aKeyFound->keyValue = goastrdup(newKeyValue); + } + freeclear(aKey.keyName); + return GP_NO_ERROR; +} + +GPResult gpiStatusInfoGetKey(GPConnection *connection, DArray keys, const char *keyName, char **keyValue) +{ + GPIKey aKey; + int anIndex; + GS_ASSERT(keys); + GS_ASSERT(keyName); + + if (!keyName) + Error(connection, GP_PARAMETER_ERROR, "Invalid key name"); + + aKey.keyName = goastrdup(keyName); + anIndex = ArraySearch(keys, &aKey, gpiStatusInfoKeyCompFunc, 0, 1); + if (anIndex != NOT_FOUND) + { + GPIKey *aKeyFound = (GPIKey *)ArrayNth(keys, anIndex); + *keyValue = goastrdup(aKeyFound->keyValue); + } + freeclear(aKey.keyName); + return GP_NO_ERROR; +} + +GPResult gpiStatusInfoCheckKey(GPConnection *connection, DArray keys, const char *keyName, char **keyValue) +{ + GPIKey aKey; + int anIndex; + GS_ASSERT(keys); + GS_ASSERT(keyName); + + if (!keyName) + Error(connection, GP_PARAMETER_ERROR, "Invalid key name"); + + aKey.keyName = goastrdup(keyName); + anIndex = ArraySearch(keys, &aKey, gpiStatusInfoKeyCompFunc, 0, 1); + if (anIndex != NOT_FOUND) + { + GPIKey *aKeyFound = (GPIKey *)ArrayNth(keys, anIndex); + *keyValue = aKeyFound->keyValue; + } + freeclear(aKey.keyName); + return GP_NO_ERROR; +} + +GPResult gpiSaveKeysToBuffer(GPConnection *connection, char **buffer) +{ + GPIConnection *iconnection = (GPIConnection *)*connection; + char *tempPoint; + int sizeKeys = 0, i, bytesWritten; + int base64KeyNameLen, base64KeyValLen; + int aLength = ArrayLength(iconnection->extendedInfoKeys); + + char keysHeader[64]; + sprintf(keysHeader, "\\keys\\%d", aLength); + + // figure out the size of the buffer to allocate + // by adding up the key value pairs with backslashes + for (i = 0; i < aLength; i++) + { + GPIKey *aKey = (GPIKey *)ArrayNth(iconnection->extendedInfoKeys, i); + if (strlen(aKey->keyName) % 3 != 0) + base64KeyNameLen = (int)(strlen(aKey->keyName) * 4 / 3) + (int)(4 - (strlen(aKey->keyName) % 3)); + else + base64KeyNameLen = (int)(strlen(aKey->keyName) * 4 / 3); + if (strlen(aKey->keyValue) % 3 != 0) + base64KeyValLen= (int)(strlen(aKey->keyValue) * 4 / 3) + (int)(4 - (strlen(aKey->keyValue) % 3)); + else + base64KeyValLen = (int)(strlen(aKey->keyValue) * 4 / 3); + sizeKeys += 1 + base64KeyNameLen + 1 + base64KeyValLen; + } + *buffer = (char *)gsimalloc(strlen(keysHeader) + (size_t)sizeKeys + 1); + if (*buffer == NULL) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Memory, GSIDebugLevel_HotError, "gpiSaveKeysToBuffer: buffer Out of memory."); + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + } + bytesWritten = sprintf(*buffer, keysHeader); + tempPoint = *buffer + bytesWritten; + for (i = 0; i < aLength; i++) + { + GPIKey *aKey = (GPIKey *)ArrayNth(iconnection->extendedInfoKeys, i); + strcat(tempPoint, "\\"); + tempPoint++; + B64Encode(aKey->keyName, tempPoint, (int)strlen(aKey->keyName), 2); + if (strlen(aKey->keyName) % 3 != 0) + tempPoint+= (int)(strlen(aKey->keyName) * 4 / 3) + (4 - (strlen(aKey->keyName) % 3)); + else + tempPoint+= (int)(strlen(aKey->keyName) * 4 / 3); + strcat(tempPoint, "\\"); + tempPoint++; + B64Encode(aKey->keyValue, tempPoint, (int)strlen(aKey->keyValue), 2); + if (strlen(aKey->keyValue) % 3 != 0) + tempPoint+= (int)(strlen(aKey->keyValue) * 4 / 3) + (4 - (strlen(aKey->keyValue) % 3)); + else + tempPoint+= (int)(strlen(aKey->keyValue) * 4 / 3); + } + return GP_NO_ERROR; +} diff --git a/code/gamespy/GP/gpiKeys.h b/code/gamespy/GP/gpiKeys.h new file mode 100644 index 00000000..10a13eff --- /dev/null +++ b/code/gamespy/GP/gpiKeys.h @@ -0,0 +1,23 @@ +#ifndef _GPIKEYS_H_ +#define _GPIKEYS_H_ + +#include "gpi.h" +#define GPI_INITIAL_NUM_KEYS 1 + +typedef struct +{ + char *keyName; + char *keyValue; +} GPIKey; + +void gpiStatusInfoKeyFree(void *element); +GPResult gpiStatusInfoKeysInit(GPConnection * connection); +void gpiStatusInfoKeysDestroy(GPConnection * connection); +int gpiStatusInfoKeyCompFunc(const void *elem1, const void *elem2); +GPResult gpiStatusInfoAddKey(GPConnection *connection, DArray keys, const char *theKeyName, const char *theKeyValue); +GPResult gpiStatusInfoDelKey(GPConnection *connection, DArray keys, const char *keyName); +GPResult gpiStatusInfoSetKey(GPConnection *connection, DArray keys, const char *keyName, const char *newKeyValue); +GPResult gpiStatusInfoGetKey(GPConnection *connection, DArray keys, const char *keyName, char **keyValue); +GPResult gpiSaveKeysToBuffer(GPConnection *connection, char **buffer); +GPResult gpiStatusInfoCheckKey(GPConnection *connection, DArray keys, const char *keyName, char **keyValue); +#endif diff --git a/code/gamespy/GP/gpiOperation.c b/code/gamespy/GP/gpiOperation.c new file mode 100644 index 00000000..bcb14803 --- /dev/null +++ b/code/gamespy/GP/gpiOperation.c @@ -0,0 +1,363 @@ +/* +gpiOperation.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +//INCLUDES +////////// +#include +#include "gpi.h" + +//FUNCTIONS +/////////// +GPResult +gpiFailedOpCallback( + GPConnection * connection, + const GPIOperation * operation +) +{ + GPICallback callback; + GPIConnection * iconnection = (GPIConnection*)*connection; + + assert(connection != NULL); + assert(*connection != NULL); + assert(operation != NULL); + + callback = operation->callback; + if(callback.callback != NULL) + { + // Handle based on operation type. + ////////////////////////////////// + switch(operation->type) + { + case GPI_CONNECT: + { + GPConnectResponseArg * arg; + arg = (GPConnectResponseArg *)gsimalloc(sizeof(GPConnectResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(arg, 0, sizeof(GPConnectResponseArg)); + arg->result = operation->result; + if(iconnection->errorCode == GP_NEWUSER_BAD_NICK) + { + arg->profile = (GPProfile)iconnection->profileid; + iconnection->profileid = 0; + } + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + break; + } + case GPI_NEW_PROFILE: + { + GPNewProfileResponseArg * arg; + arg = (GPNewProfileResponseArg *)gsimalloc(sizeof(GPNewProfileResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(arg, 0, sizeof(GPNewProfileResponseArg)); + arg->result = operation->result; + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + break; + } + case GPI_DELETE_PROFILE: + { + GPDeleteProfileResponseArg * arg; + arg = (GPDeleteProfileResponseArg *)gsimalloc(sizeof(GPDeleteProfileResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(arg, 0, sizeof(GPDeleteProfileResponseArg)); + arg->result = operation->result; + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + break; + + } + case GPI_GET_INFO: + { + GPGetInfoResponseArg * arg; + arg = (GPGetInfoResponseArg *)gsimalloc(sizeof(GPGetInfoResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(arg, 0, sizeof(GPGetInfoResponseArg)); + arg->result = operation->result; + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + break; + } + case GPI_PROFILE_SEARCH: + { + GPProfileSearchResponseArg * arg; + arg = (GPProfileSearchResponseArg *)gsimalloc(sizeof(GPProfileSearchResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(arg, 0, sizeof(GPProfileSearchResponseArg)); + arg->result = operation->result; + ((GPProfileSearchResponseArg *)arg)->matches = NULL; + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + break; + } + case GPI_REGISTER_UNIQUENICK: + { + GPRegisterUniqueNickResponseArg * arg; + arg = (GPRegisterUniqueNickResponseArg *)gsimalloc(sizeof(GPRegisterUniqueNickResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(arg, 0, sizeof(GPRegisterUniqueNickResponseArg)); + arg->result = operation->result; + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + break; + } + case GPI_REGISTER_CDKEY: + { + GPRegisterCdKeyResponseArg * arg; + arg = (GPRegisterCdKeyResponseArg *)gsimalloc(sizeof(GPRegisterCdKeyResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(arg, 0, sizeof(GPRegisterCdKeyResponseArg)); + arg->result = operation->result; + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + break; + } + default: + assert(0); + } + } + + return GP_NO_ERROR; +} + +GPResult +gpiAddOperation( + GPConnection * connection, + int type, + void * data, + GPIOperation ** op, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIOperation * operation; + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Create a new operation struct. + ///////////////////////////////// + operation = (GPIOperation *)gsimalloc(sizeof(GPIOperation)); + if(operation == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Set the data. + //////////////// + operation->type = type; + operation->data = data; + operation->blocking = (GPIBool)blocking; + operation->state = GPI_START; + if(type == GPI_CONNECT) + { + // Connect is always ID 1. + ////////////////////////// + operation->id = 1; + } + else + { + operation->id = iconnection->nextOperationID++; + if(iconnection->nextOperationID < 2) + iconnection->nextOperationID = 2; + } + operation->result = GP_NO_ERROR; + operation->callback.callback = callback; + operation->callback.param = param; + + // Add it to the list. + ////////////////////// + operation->pnext = iconnection->operationList; + iconnection->operationList = operation; + + *op = operation; + return GP_NO_ERROR; +} + +void +gpiDestroyOperation( + GPConnection * connection, + GPIOperation * operation +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Search? + ////////// + if(operation->type == GPI_PROFILE_SEARCH) + { + GPISearchData * data = (GPISearchData *)operation->data; + + // One less. + //////////// + iconnection->numSearches--; + assert(iconnection->numSearches >= 0); + + // Close the socket. + //////////////////// + shutdown(data->sock, 2); + closesocket(data->sock); + + // freeclear the buffers. + //////////////////// + freeclear(data->outputBuffer.buffer); + freeclear(data->inputBuffer.buffer); + } + + // freeclear the data. + ///////////////// + freeclear(operation->data); + + // freeclear the operation struct. + ///////////////////////////// + freeclear(operation); +} + +void +gpiRemoveOperation( + GPConnection * connection, + GPIOperation * operation +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIOperation * pcurr = iconnection->operationList; + GPIOperation * pprev = NULL; + + // Go through the list of operations. + ///////////////////////////////////// + while(pcurr != NULL) + { + // Check for a match. + ///////////////////// + if(pcurr == operation) + { + // Update the list. + /////////////////// + if(pprev == NULL) + iconnection->operationList = pcurr->pnext; + else + pprev->pnext = operation->pnext; + + gpiDestroyOperation(connection, operation); + + return; + } + + pprev = pcurr; + pcurr = pcurr->pnext; + } +} + +GPIBool +gpiFindOperationByID( + const GPConnection * connection, + GPIOperation ** operation, + int id +) +{ + GPIOperation * op; + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Go through the list of operations. + ///////////////////////////////////// + for(op = iconnection->operationList ; op != NULL ; op = op->pnext) + { + // Check the id. + //////////////// + if(op->id == id) + { + // Found it. + //////////// + if(operation != NULL) + *operation = op; + return GPITrue; + } + } + + // Didn't find it. + ////////////////// + if(operation != NULL) + *operation = NULL; + return GPIFalse; +} + +GPIBool +gpiOperationsAreBlocking( + const GPConnection * connection +) +{ + GPIOperation * operation; + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Loop through the operations. + /////////////////////////////// + for(operation = iconnection->operationList ; operation != NULL ; operation = operation->pnext) + { + // Check if it's blocking. + ////////////////////////// + if((operation->blocking) && (operation->type != GPI_PROFILE_SEARCH)) + return GPITrue; + } + + // Nothing was blocking. + //////////////////////// + return GPIFalse; +} + +GPResult +gpiProcessOperation( + GPConnection * connection, + GPIOperation * operation, + const char * input +) +{ + GPResult result = GP_NO_ERROR; + + // Check the operation type. + //////////////////////////// + switch(operation->type) + { + case GPI_CONNECT: + result = gpiProcessConnect(connection, operation, input); + break; + + case GPI_NEW_PROFILE: + result = gpiProcessNewProfile(connection, operation, input); + break; + + case GPI_DELETE_PROFILE: + result = gpiProcessDeleteProfle(connection, operation, input); + break; + + case GPI_GET_INFO: + result = gpiProcessGetInfo(connection, operation, input); + break; + + case GPI_REGISTER_UNIQUENICK: + result = gpiProcessRegisterUniqueNick(connection, operation, input); + break; + + case GPI_REGISTER_CDKEY: + result = gpiProcessRegisterCdKey(connection, operation, input); + break; + + default: + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "gpiProcessOperation was passed an operation with an invalid type (%d)\n", operation->type); + assert(0); + break; + } + + if(result != GP_NO_ERROR) + operation->result = result; + + return result; +} + diff --git a/code/gamespy/GP/gpiOperation.h b/code/gamespy/GP/gpiOperation.h new file mode 100644 index 00000000..06204cac --- /dev/null +++ b/code/gamespy/GP/gpiOperation.h @@ -0,0 +1,120 @@ +/* +gpiOperation.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#ifndef _GPIOPERATION_H_ +#define _GPIOPERATION_H_ + +//INCLUDES +////////// +#include "gpi.h" + +//DEFINES +///////// +// Operation Types. +/////////////////// +#define GPI_CONNECT 0 +#define GPI_NEW_PROFILE 1 +#define GPI_GET_INFO 2 +#define GPI_PROFILE_SEARCH 3 +#define GPI_REGISTER_UNIQUENICK 4 +#define GPI_DELETE_PROFILE 5 +#define GPI_REGISTER_CDKEY 6 +// Operation States. +//////////////////// +#define GPI_START 0 +//#define GPI_CONNECTING 1 +#define GPI_LOGIN 2 +#define GPI_REQUESTING 3 +#define GPI_WAITING 4 +#define GPI_FINISHING 5 + +//TYPES +/////// +// Operation data. +////////////////// +typedef struct GPIOperation_s +{ + int type; + void * data; + GPIBool blocking; + GPICallback callback; + int state; + int id; + GPResult result; + struct GPIOperation_s * pnext; +} GPIOperation; + +// Connect operation data. +////////////////////////// +typedef struct +{ + char serverChallenge[128]; + char userChallenge[33]; + char passwordHash[33]; + char authtoken[GP_AUTHTOKEN_LEN]; + char partnerchallenge[GP_PARTNERCHALLENGE_LEN]; + char cdkey[GP_CDKEY_LEN]; + GPIBool newuser; +} GPIConnectData; + +//FUNCTIONS +/////////// +GPResult +gpiAddOperation( + GPConnection * connection, + int type, + void * data, + GPIOperation ** op, + GPEnum blocking, + GPCallback callback, + void * param +); + +void +gpiRemoveOperation( + GPConnection * connection, + GPIOperation * operation +); + +void +gpiDestroyOperation( + GPConnection * connection, + GPIOperation * operation +); + +GPIBool +gpiFindOperationByID( + const GPConnection * connection, + GPIOperation ** operation, + int id +); + +GPIBool +gpiOperationsAreBlocking( + const GPConnection * connection +); + +GPResult +gpiProcessOperation( + GPConnection * connection, + GPIOperation * operation, + const char * input +); + +GPResult +gpiFailedOpCallback( + GPConnection * connection, + const GPIOperation * operation +); + +#endif diff --git a/code/gamespy/GP/gpiPS3.c b/code/gamespy/GP/gpiPS3.c new file mode 100644 index 00000000..cb11fbdc --- /dev/null +++ b/code/gamespy/GP/gpiPS3.c @@ -0,0 +1,592 @@ +/* +gpiPS3.c +GameSpy Presence SDK + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +//INCLUDES +////////// +#include +#include +#include "gpi.h" + +#ifdef _PS3 +//GLOBALS +////////// +uint8_t gpi_np_pool[SCE_NP_MIN_POOL_SIZE]; +SceNpCommunicationId gpi_communication_id = { + {'N','P','X','S','0','0','0','0','5'}, + '\0', + 0, + 0 +}; + +//FUNCTIONS +/////////// +int gpiNpBasicCallback( + int event, + int retCode, + uint32_t reqId, + void *arg +) +{ + // No-op - can ignore any events + //////////////////////////////// + return 0; +} + +GPResult gpiInitializeNpBasic( + GPConnection * connection +) +{ + int ret = 0; + GPIConnection * iconnection = (GPIConnection*)*connection; + + iconnection->npInitialized = gsi_true; + + // Initial NP init - after this we wait for status to get to online + //////////////////////////////////////////////////////////////////// + ret = sceNpInit(SCE_NP_MIN_POOL_SIZE, gpi_np_pool); + + if (ret == SCE_NP_ERROR_ALREADY_INITIALIZED) + { + // If already initialized - DO NOT terminate after sync (game might need it) + //////////////////////////////////////////////////////////////////////////// + iconnection->npBasicGameInitialized = gsi_true; + } + else if (ret < 0) + { + iconnection->npBasicGameInitialized = gsi_true; + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "gpiInitializeNpBasic: sceNpInit() failed, NP-functionality disabled. ret = 0x%x\n", ret); + return GP_MISC_ERROR; + } + else + iconnection->npBasicGameInitialized = gsi_false; //GP initialized, so destroy after complete + + return GP_NO_ERROR; +} + +// Freeing up transaction list darray +void gpiNpTransactionListFree(void *element) +{ + npIdLookupTrans *aTrans = (npIdLookupTrans *)element; + freeclear(aTrans->npIdForAdd); +} + + +GPResult gpiCheckNpStatus( + GPConnection * connection +) +{ + int ret = 0; + int status = SCE_NP_MANAGER_STATUS_OFFLINE; + SceNpId npId; + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Get NP status + //////////////// + ret = sceNpManagerGetStatus(&status); + if (ret < 0) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "gpiCheckNpStatus: sceNpGetStatus() failed. ret = 0x%x\n", ret); + } + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Comment, + "gpiCheckNpStatus: sceNpGetStatus - status = %d\n", status); + + + // If NP status != online after the timeout period, stop syncing + //////////////////////////////////////////////////////////////// + if (status != SCE_NP_MANAGER_STATUS_ONLINE && (current_time() - iconnection->loginTime > GPI_NP_STATUS_TIMEOUT)) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "gpiCheckNpStatus: NP Status not online - timed out\n"); + + // Flag to stop the sync process + //////////////////////////////// + iconnection->npPerformBuddySync = gsi_false; + iconnection->npPerformBlockSync = gsi_false; + + return GP_MISC_ERROR; + } + + // Once status is online, finish NP init + //////////////////////////////////////// + if (status == SCE_NP_MANAGER_STATUS_ONLINE) + { + iconnection->loginTime = current_time(); + + // Note - we ignore error messages here - if something fails we really don't care + ///////////////////////////////////////////////////////////////////////////////// + if (!iconnection->npBasicGameInitialized) + { + ret = sceNpBasicInit(); //obsolete? + if (ret < 0) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "gpiCheckNpStatus: sceNpBasicInit() failed. ret = 0x%x\n", ret); + } + + ret = sceNpBasicRegisterHandler(&gpi_communication_id, gpiNpBasicCallback, NULL); + if (ret < 0) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "gpiCheckNpStatus: sceNpBasicRegisterHandler() failed. ret = 0x%x\n", ret); + } + } + + ret = sceNpLookupInit(); + if (ret == SCE_NP_COMMUNITY_ERROR_ALREADY_INITIALIZED) + { + // If already initialized - DO NOT terminate after GP destroy (game might need it) + ////////////////////////////////////////////////////////////////////////////////// + iconnection->npLookupGameInitialized = gsi_true; + } + else if (ret < 0) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "gpiCheckNpStatus: sceNpLookupInit() failed. ret = 0x%x\n", ret); + iconnection->npLookupGameInitialized = gsi_true; + } + else + iconnection->npLookupGameInitialized = gsi_false; + + // Regardless of game, create a title context id for GP to use for lookups + /////////////////////////////////////////////////////////////////////////// + ret = sceNpManagerGetNpId(&npId); + if (ret < 0) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "gpiCheckNpStatus: sceNpManagerGetNpId() failed. ret = 0x%x\n", ret); + } + + ret = sceNpLookupCreateTitleCtx(&gpi_communication_id, &npId); + if (ret < 0) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "gpiCheckNpStatus: sceNpLookupCreateTitleCtx() failed. ret = 0x%x\n", ret); + } + + iconnection->npLookupTitleCtxId = ret; + + // Mark status retrieval completed + ////////////////////////////////// + iconnection->npStatusRetrieved = gsi_true; + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Comment, + "gpiCheckNpStatus: NP is now initialized with status.\n"); + + iconnection->npTransactionList = ArrayNew(sizeof(npIdLookupTrans), 1, gpiNpTransactionListFree); + if (!iconnection->npTransactionList) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + } + + return GP_NO_ERROR; +} + +GPResult gpiDestroyNpBasic( + GPConnection * connection +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Explicitly destroy title context we used for lookup + ////////////////////////////////////////////////////// + if (iconnection->npLookupTitleCtxId >= 0) + sceNpLookupDestroyTitleCtx(iconnection->npLookupTitleCtxId); + + // Do not destroy NpLookup or NpBasic if Game is using it + ///////////////////////////////////////////////////////// + if (!iconnection->npLookupGameInitialized) + sceNpLookupTerm(); + + if (!iconnection->npBasicGameInitialized) + { + sceNpBasicUnregisterHandler(); + + // Obsolete? + sceNpBasicTerm(); + + sceNpTerm(); + } + + // Free up transaction list used for NP lookups + /////////////////////////////////////////////// + if (iconnection->npTransactionList) + ArrayFree(iconnection->npTransactionList); + + iconnection->npInitialized = gsi_false; + iconnection->npStatusRetrieved = gsi_false; + + return GP_NO_ERROR; +} + +GPResult gpiSyncNpBuddies( + GPConnection * connection +) +{ + int ret; + SceNpId npId; //Buffer to store friend list entry's NP ID + gsi_u32 i, count = 0; + GPIConnection * iconnection = (GPIConnection*)*connection; + + + // Flag sync as complete so we don't do it more than once per login + //////////////////////////////////////////////////////////////////// + iconnection->npPerformBuddySync = gsi_false; + + // Get buddy count + /////////////////// + ret = sceNpBasicGetFriendListEntryCount(&count); + if ( ret < 0 ) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "PS3BuddySync: Failed to get NP friend list count\n"); + } + + // Loop through each buddy, check for existence of GSID account + /////////////////////////////////////////////////////////////// + for (i = 0; i < count; i++) + { + memset(&npId, 0x00, sizeof(npId)); + ret = sceNpBasicGetFriendListEntry(i, &npId); + if (ret < 0) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "PS3BuddySync: Failed to get NP friend entry #%d\n", i); + return GP_MISC_ERROR; + } + + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Comment, + "PS3BuddySync: NP friend entry #%d, npid = %s. Queueing Search.\n", i, npId.handle.data); + + gpiProfileSearchUniquenick(connection, npId.handle.data, &iconnection->namespaceID, + 1, GP_NON_BLOCKING, (GPCallback)gpiSyncNpBuddiesCallback, NULL); + } + + return GP_NO_ERROR; +} + +void gpiSyncNpBuddiesCallback( + GPConnection * pconnection, + GPProfileSearchResponseArg * arg, + void * param +) +{ + if(arg->result == GP_NO_ERROR) + { + if(arg->numMatches == 1) + { + // Check if already a buddy + //////////////////////////// + if (!gpIsBuddy(pconnection, arg->matches[0].profile)) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Comment, + "PS3BuddySync: NP Buddy \"%s\" found in namespace %d. Sending Request.\n", + arg->matches[0].uniquenick, arg->matches[0].namespaceID); + + // Send the add request + //////////////////////// + gpSendBuddyRequest(pconnection, arg->matches[0].profile, _T("PS3 Buddy Sync")); + } + else + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Comment, + "PS3BuddySync: \"%s\" is already a buddy\n", arg->matches[0].uniquenick); + } + else + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Comment, + "PS3BuddySync: No suitable match found\n"); + } + else + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "PS3BuddySync: Buddy Search FAILED!\n"); + + GSI_UNUSED(pconnection); + GSI_UNUSED(param); +} + +GPResult gpiSyncNpBlockList( + GPConnection * connection +) +{ + int ret; + SceNpId npId; //Buffer to store block list entry's NP ID + gsi_u32 i, count = 0; + GPIConnection * iconnection = (GPIConnection*)*connection; + + + // Flag sync as complete so we don't do it more than once per login + //////////////////////////////////////////////////////////////////// + iconnection->npPerformBlockSync = gsi_false; + + // Get block list count + /////////////////////// + ret = sceNpBasicGetBlockListEntryCount(&count); + if ( ret < 0 ) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "PS3BlockSync: Failed to get NP block list count\n"); + } + + // Loop through each entry, check for existence of GSID account + /////////////////////////////////////////////////////////////// + for (i = 0; i < count; i++) + { + memset(&npId, 0x00, sizeof(npId)); + ret = sceNpBasicGetBlockListEntry(i, &npId); + if (ret < 0) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "PS3BlockSync: Failed to get NP block entry #%d\n", i); + return GP_MISC_ERROR; + } + + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Comment, + "PS3BlockSync: NP block entry #%d, npid = %s. Queueing Search.\n", i, npId.handle.data); + + gpiProfileSearchUniquenick(connection, npId.handle.data, &iconnection->namespaceID, + 1, GP_NON_BLOCKING, (GPCallback)gpiSyncNpBlockListCallback, NULL); + } + + return GP_NO_ERROR; +} + +void gpiSyncNpBlockListCallback( + GPConnection * pconnection, + GPProfileSearchResponseArg * arg, + void * param +) +{ + GPIProfile * pProfile; + GPIConnection * iconnection = (GPIConnection*)*pconnection; + + if(arg->result == GP_NO_ERROR) + { + if(arg->numMatches == 1) + { + // Check if already blocked + //////////////////////////// + if(!gpiGetProfile(pconnection, arg->matches[0].profile, &pProfile) || !pProfile->blocked) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Comment, + "PS3BlockSync: NP Block Entry \"%s\" found in namespace %d. Adding to BlockedList.\n", + arg->matches[0].uniquenick, arg->matches[0].namespaceID); + + // Add to GP Blocked List - set lock to make sure we dont try to add to NP list + /////////////////////////////////////////////////////////////////////////////// + iconnection->npSyncLock = gsi_true; + gpiAddToBlockedList(pconnection, arg->matches[0].profile); + iconnection->npSyncLock = gsi_false; + } + else + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Comment, + "PS3BlockSync: \"%s\" is already blocked\n", arg->matches[0].uniquenick); + } + else + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Comment, + "PS3BlockSync: No suitable match found\n"); + } + else + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "PS3BlockSync: Block Entry Search FAILED!\n"); + + GSI_UNUSED(param); +} + +GPResult gpiAddToNpBlockList( + GPConnection * connection, + int profileid +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + // TODO: consider developer method for cache input in order to check HDD cache? + + // If NP status not resolved, don't bother with lookup + /////////////////////////////////////////////////////// + if (!iconnection->npTransactionList || iconnection->npLookupTitleCtxId < 0) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "PS3AddToNpBlockList: Cancelling add - NP status not yet resolved.\n"); + return GP_NO_ERROR; + } + + // Do an info lookup to find out if this player has an NP account. + ///////////////////////////////////////////////////////////////// + gpiGetInfo(connection, profileid, GP_CHECK_CACHE, GP_NON_BLOCKING, + (GPCallback)gpiAddToNpBlockListInfoCallback, NULL); + + return GP_NO_ERROR; +} + +void gpiAddToNpBlockListInfoCallback( + GPConnection * pconnection, + GPGetInfoResponseArg * arg, + void * param +) +{ + SceNpOnlineId onlineId; + int ret; + npIdLookupTrans transaction; + GPIConnection * iconnection = (GPIConnection*)*pconnection; +#ifdef GSI_UNICODE + char asciiUniquenick[GP_UNIQUENICK_LEN]; +#endif + + if(arg->result == GP_NO_ERROR) + { + // Make sure its a PS3 uniquenick (e.g. we have the uniquenick) + /////////////////////////////////////////////////////////////// + if (_tcslen(arg->uniquenick) != 0) + { + memset(&onlineId, 0, sizeof(onlineId)); + +#ifdef GSI_UNICODE + UCS2ToAsciiString(arg->uniquenick, (char*)asciiUniquenick); + strncpy(onlineId.data, asciiUniquenick, SCE_NET_NP_ONLINEID_MAX_LENGTH); +#else + strncpy(onlineId.data, arg->uniquenick, SCE_NET_NP_ONLINEID_MAX_LENGTH); +#endif + + if (ArrayLength(iconnection->npTransactionList) < GPI_NP_NUM_TRANSACTIONS) + { + ret = sceNpLookupCreateTransactionCtx(iconnection->npLookupTitleCtxId); + if (ret < 0) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "PS3AddToNpBlockList: sceNpLookupCreateTransactionCtx() failed. ret = 0x%x\n", ret); + } + else + { + transaction.npIdForAdd = (SceNpId*)gsimalloc(sizeof(SceNpId)); + if(transaction.npIdForAdd == NULL) + { + sceNpLookupDestroyTransactionCtx(ret); + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "PS3AddToNpBlockList: Out of memory.\n"); + return; + } + transaction.npTransId = ret; + transaction.npLookupDone = gsi_false; + ArrayAppend(iconnection->npTransactionList, &transaction); + + // Perform NP lookup to get the NpId + ///////////////////////////////////// + ret = sceNpLookupNpIdAsync(transaction.npTransId, &onlineId, + transaction.npIdForAdd, 0, NULL); + if (ret < 0) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "PS3AddToNpBlockList: sceNpLookupNpIdAsync() failed. ret = 0x%x\n", ret); + } + } + } + else + { + // Can only have a max of 32 simultaneous transactions (based on PS3 lib) + ///////////////////////////////////////////////////////////////////////// + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "PS3AddToNpBlockList: Transactions limit reached for np lookups\n"); + } + } + else + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "PS3AddToNpBlockList: Profile [%d] does not have a uniquenick in namespace %d!\n", + arg->profile, iconnection->namespaceID); + } + else + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "PS3AddToNpBlockList: Player Info lookup FAILED!\n"); + + GSI_UNUSED(pconnection); + GSI_UNUSED(param); +} + +GPResult gpiProcessNp(GPConnection * connection) +{ + int i, ret=0; + GPIConnection * iconnection = (GPIConnection*)*connection; + npIdLookupTrans * transaction; + + // Check for uninitialized transaction darray + ////////////////////////////////////////////// + if (!iconnection->npTransactionList) + return GP_NO_ERROR; + + // Need to process Sysutil for the Async lookups + ///////////////////////////////////////////////// + if (ArrayLength(iconnection->npTransactionList) > 0) + cellSysutilCheckCallback(); + + // Loop through all current transactions, check if complete + /////////////////////////////////////////////////////////// + for (i=0; i < ArrayLength(iconnection->npTransactionList); i++) + { + // Grab next transaction in the list + ///////////////////////////////////// + transaction = (npIdLookupTrans *)ArrayNth(iconnection->npTransactionList, i); + + if (!transaction->npLookupDone) + { + if (sceNpLookupPollAsync(transaction->npTransId, &ret)==0) + transaction->npLookupDone = gsi_true; + } + else + { + if (ret<0) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "PS3AddToNpBlockList: sceNpLookupWaitAsync. ret = 0x%x\n", ret); + if (ret == (int)SCE_NP_COMMUNITY_SERVER_ERROR_NO_SUCH_USER_NPID) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "PS3AddToNpBlockList: Player '%s' is not an NP user.\n", + transaction->npIdForAdd->handle.data); + } + } + else + { + // Found an NpId, try to add + ///////////////////////////// + ret = sceNpBasicAddBlockListEntry(transaction->npIdForAdd); + if (ret == (int)SCE_NP_BASIC_ERROR_BUSY) + { + // Oh nice, NP is too busy to help us.... keep on trying + ///////////////////////////////////////////////////////// + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Comment, + "PS3AddToNpBlockList: SCE_NP_BASIC_ERROR_BUSY. continue trying to add to NP\n"); + return GP_NO_ERROR; + } + else if ( ret < 0 ) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "PS3AddToNpBlockList: sceNpBasicAddBlockListEntry() failed. ret = 0x%x\n", ret); + } + else + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Comment, + "PS3AddToNpBlockList: Player '%s' added to NP Block list.\n", + transaction->npIdForAdd->handle.data); + } + } + + ret = sceNpLookupDestroyTransactionCtx(transaction->npTransId); + if (ret<0) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "PS3AddToNpBlockList: sceNpLookupDestroyTransactionCtx() failed. ret = 0x%x\n", ret); + } + + // Delete Transaction when its complete + //////////////////////////////////////// + ArrayDeleteAt(iconnection->npTransactionList, i); + } + } + + return GP_NO_ERROR; +} + +#endif diff --git a/code/gamespy/GP/gpiPS3.h b/code/gamespy/GP/gpiPS3.h new file mode 100644 index 00000000..fc09b15d --- /dev/null +++ b/code/gamespy/GP/gpiPS3.h @@ -0,0 +1,54 @@ +/* +gpiPS3.h +GameSpy Presence SDK + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#ifndef _GPIPS3_H_ +#define _GPIPS3_H_ + +//INCLUDES +////////// +#include "gpi.h" +#include +#include +#include + + +//DEFINES +///////// +#define GPI_NP_SYNC_DELAY 5000 //wait 5 seconds after login before doing any syncs +#define GPI_NP_STATUS_TIMEOUT 5000 //timeout after 5 second max if NP status is not online +#define GPI_NP_NUM_TRANSACTIONS 32 //Max num of simultaneous NP lookup transactions + +//STRUCTURES +//////////// +typedef struct +{ + int npTransId; + SceNpId *npIdForAdd; + gsi_bool npLookupDone; +} npIdLookupTrans; + +//FUNCTIONS +/////////// +GPResult gpiInitializeNpBasic(); +GPResult gpiCheckNpStatus(GPConnection * connection); +GPResult gpiDestroyNpBasic(GPConnection * connection); +GPResult gpiProcessNp(GPConnection * connection); +int gpiNpBasicCallback(int event, int retCode, uint32_t reqId, void *arg); + +GPResult gpiSyncNpBuddies(GPConnection * connection); +void gpiSyncNpBuddiesCallback(GPConnection * pconnection, GPProfileSearchResponseArg * arg, void * param); + +GPResult gpiSyncNpBlockList(GPConnection * connection); +void gpiSyncNpBlockListCallback(GPConnection * pconnection, GPProfileSearchResponseArg * arg, void * param); + +GPResult gpiAddToNpBlockList(GPConnection * connection, int profileid); +void gpiAddToNpBlockListInfoCallback(GPConnection * pconnection, GPGetInfoResponseArg * arg, void * param); + +#endif diff --git a/code/gamespy/GP/gpiPeer.c b/code/gamespy/GP/gpiPeer.c new file mode 100644 index 00000000..591460da --- /dev/null +++ b/code/gamespy/GP/gpiPeer.c @@ -0,0 +1,1308 @@ +/* +gpiPeer.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +//INCLUDES +////////// +#include +#include +#include +#include "gpi.h" + +//FUNCTIONS +/////////// +static GPResult +gpiProcessPeerInitiatingConnection( + GPConnection * connection, + GPIPeer * peer +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + //int state; + char * str = NULL; + //int len; + GPIBool connClosed; + GPIProfile * pProfile; + GPResult result; + GSUdpPeerState aPeerState; + + GS_ASSERT(peer); + if (!peer) + return GP_NETWORK_ERROR; + + GS_ASSERT(peer->state != GPI_PEER_DISCONNECTED && peer->state != GPI_PEER_NOT_CONNECTED); + if (peer->state == GPI_PEER_DISCONNECTED || peer->state == GPI_PEER_NOT_CONNECTED) + return GP_NETWORK_ERROR; + // Check the state. + /////////////////// + switch(peer->state) + { + case GPI_PEER_GETTING_SIG: + // Do nothing - we're waiting for getinfo to get the sig. + ///////////////////////////////////////////////////////// + break; + + case GPI_PEER_GOT_SIG: + { + // Start the connect. + ///////////////////// + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_State, GSIDebugLevel_Verbose, "Got the peer signature for profileid: %d\n", peer->profile); + CHECK_RESULT(gpiPeerStartConnect(connection, peer)); + + break; + } + case GPI_PEER_CONNECTING: + { + // Check if the connect finished. + ///////////////////////////////// + /* + CHECK_RESULT(gpiCheckSocketConnect(connection, peer->sock, &state)); + if(state == GPI_DISCONNECTED) + { + Error(connection, GP_NETWORK_ERROR, "Error connecting to a peer."); + } + */ + + gsUdpEngineGetPeerState(peer->ip, peer->port, &aPeerState); + + if(aPeerState == GS_UDP_PEER_CONNECTED) + { + GPIPeer * pcurr; + GPIBool freePeerSig = GPITrue; + + // Get the profile object. + ////////////////////////// + if(!gpiGetProfile(connection, peer->profile, &pProfile)) + Error(connection, GP_NETWORK_ERROR, "Error connecting to a peer."); + + // Send the auth. + ///////////////// + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\auth\\"); + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\pid\\"); + gpiAppendIntToBuffer(connection, &peer->outputBuffer, iconnection->profileid); + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\nick\\"); + gpiAppendStringToBuffer(connection, &peer->outputBuffer, iconnection->nick); + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\sig\\"); + gpiAppendStringToBuffer(connection, &peer->outputBuffer, pProfile->peerSig); + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\final\\"); + + // Are there any other peers still connecting? + ////////////////////////////////////////////// + for(pcurr = iconnection->peerList ; pcurr != NULL ; pcurr = pcurr->pnext) + if((pcurr->profile == peer->profile) && (pcurr != peer)) + if(pcurr->state <= GPI_PEER_CONNECTING) + freePeerSig = GPIFalse; + + // freeclear it? + /////////// + if(freePeerSig) + { + freeclear(pProfile->peerSig); + if(gpiCanFreeProfile(pProfile)) + gpiRemoveProfile(connection, pProfile); + } + + // Update the state. + //////////////////// + peer->state = GPI_PEER_WAITING; + } + + break; + } + case GPI_PEER_WAITING: + { + // Check for a response. + //////////////////////// + //CHECK_RESULT(gpiRecvToBuffer(connection, peer->sock, &peer->inputBuffer, &len, &connClosed, "PR")); + + // Check for a final. + ///////////////////// + if (peer->inputBuffer.buffer) + str = strstr(peer->inputBuffer.buffer, "\\final\\"); + if(str != NULL) + { + str[0] = '\0'; + str += 7; + + // Was it rejected? + /////////////////// + if(strncmp(peer->inputBuffer.buffer, "\\anack\\", 7) == 0) + { + // Rejected. + //////////// + peer->nackCount++; + + // Is this more than once? + ////////////////////////// + if(peer->nackCount > 1) + { + // we shouldn't reach this case unless there is a problem with + // the server when getting a buddy's signature + + // Give up already. + /////////////////// + Error(connection, GP_NETWORK_ERROR, "Error getting buddy authorization."); + } + + // Try getting the latest sig. + ////////////////////////////// + CHECK_RESULT(gpiPeerGetSig(connection, peer)); + } + else if(strncmp(peer->inputBuffer.buffer, "\\aack\\", 6) != 0) + { + // Unknown message. + /////////////////// + Error(connection, GP_NETWORK_ERROR, "Error parsing buddy message."); + } + + // The connection has been established. + /////////////////////////////////////// + peer->state = GPI_PEER_CONNECTED; + peer->inputBuffer.len = 0; + } + + break; + } + // code should not reach here. + default: break; + } + + // Send stuff that needs to be sent. + //////////////////////////////////// + if(peer->outputBuffer.len > 0) + { + //result = gpiSendFromBuffer(connection, peer->sock, &peer->outputBuffer, &connClosed, GPITrue, "PR"); + result = gpiSendBufferToPeer(connection, peer->ip, peer->port, &peer->outputBuffer, &connClosed, GPITrue); + if(connClosed || (result != GP_NO_ERROR)) + peer->state = GPI_PEER_DISCONNECTED; + } + + return GP_NO_ERROR; +} + +static GPResult +gpiProcessPeerAcceptingConnection( + GPConnection * connection, + GPIPeer * peer +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GSUdpPeerState aPeerState; + char * str; + //int len; + GPIBool connClosed; + char intValue[16]; + int pid; + char nick[GP_NICK_LEN]; + char sig[33]; + char sigCheck[33]; + char buffer[256]; + + // Check the state. + /////////////////// + GS_ASSERT(peer->state == GPI_PEER_WAITING); + if (peer->state != GPI_PEER_WAITING) + return GP_NETWORK_ERROR; + + // Read any pending info. + ///////////////////////// + //CHECK_RESULT(gpiRecvToBuffer(connection, peer->sock, &peer->inputBuffer, &len, &connClosed, "PR")); + gsUdpEngineGetPeerState(peer->ip, peer->port, &aPeerState); + + // Check for a closed connection. + ///////////////////////////////// + if(aPeerState == GS_UDP_PEER_CLOSED) + { + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + + // Check for a final. + ///////////////////// + str = strstr(peer->inputBuffer.buffer, "\\final\\"); + if(str != NULL) + { + str[0] = '\0'; + str += 7; + + // Is it an auth? + ///////////////// + if(strncmp(peer->inputBuffer.buffer, "\\auth\\", 6) == 0) + { + // Get the pid. + /////////////// + if(!gpiValueForKey(peer->inputBuffer.buffer, "\\pid\\", intValue, sizeof(intValue))) + { + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + pid = atoi(intValue); + + // Get the nick. + //////////////// + if(!gpiValueForKey(peer->inputBuffer.buffer, "\\nick\\", nick, sizeof(nick))) + { + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + + // Get the sig. + /////////////// + if(!gpiValueForKey(peer->inputBuffer.buffer, "\\sig\\", sig, sizeof(sig))) + { + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + + // Compute what the sig should be. + ////////////////////////////////// + sprintf(buffer, "%s%d%d", + iconnection->password, + iconnection->profileid, + pid); + MD5Digest((unsigned char *)buffer, strlen(buffer), sigCheck); + + // Check the sig. + ///////////////// + if(strcmp(sig, sigCheck) != 0) + { + // Bad sig. + /////////// + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\anack\\"); + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\final\\"); + + gpiSendBufferToPeer(connection, peer->ip, peer->port, &peer->outputBuffer, &connClosed, GPITrue); + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + + // Send an ack. + /////////////// + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\aack\\"); + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\final\\"); + + peer->state = GPI_PEER_CONNECTED; + peer->profile = (GPProfile)pid; + } + else + { + // Unrecognized command. + //////////////////////// + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + + // Update the buffer length. + //////////////////////////// + peer->inputBuffer.len = 0; + } + + return GP_NO_ERROR; +} + +GPResult +gpiPeerSendMessages( + GPConnection * connection, + GPIPeer * peer +) +{ + GPIBool connClosed; + GPIMessage * message; + GPResult result; + + GS_ASSERT(peer); + if (!peer) + return GP_NETWORK_ERROR; + // Only send messages if there's nothing waiting in the output buffer. + ////////////////////////////////////////////////////////////////////// + if(peer->outputBuffer.len) + return GP_NO_ERROR; + + // Send outgoing messages. + ////////////////////////// + while(ArrayLength(peer->messages)) + { + // Get the first message. + ///////////////////////// + message = (GPIMessage *)ArrayNth(peer->messages, 0); + + // Send as much as possible. + //////////////////////////// + //result = gpiSendFromBuffer(connection, peer->sock, &message->buffer, &connClosed, GPIFalse, "PR"); + result = gpiSendBufferToPeer(connection, peer->ip, peer->port, &message->buffer, &connClosed, GPIFalse); + if(connClosed || (result != GP_NO_ERROR)) + { + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + + // Did we not send it all? + ////////////////////////// + if(message->buffer.pos != message->buffer.len) + break; + + // Remove the message. + ////////////////////// + ArrayDeleteAt(peer->messages, 0); + } + + return GP_NO_ERROR; +} + +static GPResult +gpiProcessPeerConnected( + GPConnection * connection, + GPIPeer * peer +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + //int len; + GSUdpPeerState aPeerState; + GPIBool connClosed; + GPICallback callback; + char * buffer; + int type; + int messageLen; + GPResult result; + + GS_ASSERT(peer); + if (!peer) + return GP_NETWORK_ERROR; + // Send stuff. + ////////////// + if(peer->outputBuffer.len) + { + //result = gpiSendFromBuffer(connection, peer->sock, &peer->outputBuffer, &connClosed, GPITrue, "PR"); + result = gpiSendBufferToPeer(connection, peer->ip, peer->port, &peer->outputBuffer, &connClosed, GPITrue); + if(connClosed || (result != GP_NO_ERROR)) + { + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + } + + // Send outgoing messages. + ////////////////////////// + if(!peer->outputBuffer.len) + { + CHECK_RESULT(gpiPeerSendMessages(connection, peer)); + if(peer->state == GPI_PEER_DISCONNECTED) + return GP_NO_ERROR; + } + + // Read messages. + ///////////////// + /* + result = gpiRecvToBuffer(connection, peer->sock, &peer->inputBuffer, &len, &connClosed, "PR"); + if(result != GP_NO_ERROR) + { + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + */ + if(peer->inputBuffer.len > 0) + { + peer->timeout = (time(NULL) + GPI_PEER_TIMEOUT); + } + + // Grab the message header. + /////////////////////////// + do + { + // Read a message. + ////////////////// + CHECK_RESULT(gpiReadMessageFromBuffer(connection, &peer->inputBuffer, &buffer, &type, &messageLen)); + if(buffer != NULL) + { + // Got a message! + ///////////////// + switch(type) + { + case GPI_BM_MESSAGE: + callback = iconnection->callbacks[GPI_RECV_BUDDY_MESSAGE]; + if(callback.callback != NULL) + { + GPRecvBuddyMessageArg * arg; + + arg = (GPRecvBuddyMessageArg *)gsimalloc(sizeof(GPRecvBuddyMessageArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->profile = peer->profile; +#ifndef GSI_UNICODE + arg->message = goastrdup(buffer); +#else + arg->message = UTF8ToUCS2StringAlloc(buffer); +#endif + arg->date = (unsigned int)time(NULL); + CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_MESSAGE)); + } + break; + + case GPI_BM_UTM: + callback = iconnection->callbacks[GPI_RECV_BUDDY_UTM]; + if (callback.callback != NULL) + { + GPRecvBuddyUTMArg * arg; + + arg = (GPRecvBuddyUTMArg *)gsimalloc(sizeof(GPRecvBuddyUTMArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->profile = peer->profile; +#ifndef GSI_UNICODE + arg->message = goastrdup(buffer); +#else + arg->message = UTF8ToUCS2StringAlloc(buffer); +#endif + arg->date = (unsigned int)time(NULL); + CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_MESSAGE)); + } + break; + + case GPI_BM_PING: + // Send back a pong. + //////////////////// + gpiSendBuddyMessage(connection, peer->profile, GPI_BM_PONG, "1", 0, NULL); + + break; + +#ifndef NOFILE + case GPI_BM_PONG: + // Lets the transfers handle this. + ////////////////////////////////// + gpiTransfersHandlePong(connection, peer->profile, peer); + break; +#endif + case GPI_BM_KEYS_REQUEST: + CHECK_RESULT(gpiBuddyHandleKeyRequest(connection, peer)); + break; + case GPI_BM_KEYS_REPLY: + CHECK_RESULT(gpiBuddyHandleKeyReply(connection, peer, buffer)); + // Let the keys request reply handler take care of this. + //////////////////////////////////////////////////////// + break; + case GPI_BM_FILE_SEND_REQUEST: + case GPI_BM_FILE_SEND_REPLY: + case GPI_BM_FILE_BEGIN: + case GPI_BM_FILE_END: + case GPI_BM_FILE_DATA: + case GPI_BM_FILE_SKIP: + case GPI_BM_FILE_TRANSFER_THROTTLE: + case GPI_BM_FILE_TRANSFER_CANCEL: + case GPI_BM_FILE_TRANSFER_KEEPALIVE: + // Handle a transfer protocol message. + ////////////////////////////////////// + gpiHandleTransferMessage(connection, peer, type, peer->inputBuffer.buffer, buffer, messageLen); + + + break; + + default: + break; + } + + // Remove it from the buffer. + ///////////////////////////// + gpiClipBufferToPosition(connection, &peer->inputBuffer); + } + } + while(buffer); + + gsUdpEngineGetPeerState(peer->ip, peer->port, &aPeerState); + //if(connClosed) + if (aPeerState == GS_UDP_PEER_CLOSED) + peer->state = GPI_PEER_DISCONNECTED; + + return GP_NO_ERROR; +} + + +// Used to check for any timed out peer operations +// assumes peer is not NULL +// makes no assumption of the operation queue +void gpiCheckTimedOutPeerOperations(GPConnection * connection, GPIPeer *peer) +{ + GPIPeerOp *anIterator = peer->peerOpQueue.first; + GS_ASSERT(peer); + if (!peer) + return; + + while (anIterator && anIterator != peer->peerOpQueue.last) + { + if (anIterator->state != GPI_PEER_OP_STATE_FINISHED && current_time() > anIterator->timeout && anIterator->callback) + { + // currently only one type of peer operation exists + // when it's found, we need to provide the application with + // a result of no data + if (anIterator->type == GPI_BM_KEYS_REQUEST) + { + GPICallback callback; + GPGetBuddyStatusInfoKeysArg *arg = (GPGetBuddyStatusInfoKeysArg *)gsimalloc(sizeof(GPGetBuddyStatusInfoKeysArg)); + callback.callback = anIterator->callback; + callback.param = anIterator->userData; + arg->keys = NULL; + arg->numKeys = 0; + arg->values = NULL; + arg->profile = peer->profile; + gpiAddCallback(connection, callback, arg, NULL, 0); + + } + // The peer operation is removed regardless of type + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Notice, "Peer operation timed out"); + gpiPeerRemoveOp(peer, anIterator); + } + anIterator = anIterator->next; + } +} + + +static GPResult +gpiProcessPeer( + GPConnection * connection, + GPIPeer * peer +) +{ + GPResult result = GP_NO_ERROR; + + // This state should never get out of initialization. + ///////////////////////////////////////////////////// + GS_ASSERT(peer->state != GPI_PEER_NOT_CONNECTED); + if (peer->state == GPI_PEER_NOT_CONNECTED) + return GP_NETWORK_ERROR; + + // If we're not connected yet. + ////////////////////////////// + if(peer->state != GPI_PEER_CONNECTED) + { + if(peer->initiated) + result = gpiProcessPeerInitiatingConnection(connection, peer); + else + result = gpiProcessPeerAcceptingConnection(connection, peer); + } + + // If we're connected. + ////////////////////// + if((result == GP_NO_ERROR) && (peer->state == GPI_PEER_CONNECTED)) + { + result = gpiProcessPeerConnected(connection, peer); + gpiCheckTimedOutPeerOperations(connection, peer); + } + + return result; +} + +void +gpiDestroyPeer( + GPConnection * connection, + GPIPeer * peer +) +{ +#ifndef NOFILE + // Cleanup any transfers that use this peer. + //////////////////////////////////////////// + gpiTransferPeerDestroyed(connection, peer); +#endif + + //shutdown(peer->sock, 2); + //closesocket(peer->sock); + freeclear(peer->inputBuffer.buffer); + freeclear(peer->outputBuffer.buffer); + if(peer->messages) + { + ArrayFree(peer->messages); + peer->messages = NULL; + } + freeclear(peer); + + GSI_UNUSED(connection); +} + +void +gpiRemovePeer( + GPConnection * connection, + GPIPeer * peer +) +{ + GPIPeer * pprev; + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIMessage * message; + + GS_ASSERT(peer != NULL); + if (peer == NULL) + return; + + GS_ASSERT(iconnection->peerList); + if (iconnection->peerList == NULL) + return; + // Check if this is the first peer. + /////////////////////////////////// + if(iconnection->peerList == peer) + { + iconnection->peerList = peer->pnext; + } + else + { + // Find the previous peer. + ////////////////////////// + for(pprev = iconnection->peerList ; pprev->pnext != peer ; pprev = pprev->pnext) + { + if(pprev->pnext == NULL) + { + // Can't find this peer in the list! + //////////////////////////////////// + assert(0); + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "Tried to remove peer not in list."); + return; + } + } + pprev->pnext = peer->pnext; + } + + // Check for pending messages. + ////////////////////////////// + while(ArrayLength(peer->messages)) + { + // Get the next message. + //////////////////////// + message = (GPIMessage *)ArrayNth(peer->messages, 0); + + // Don't forward protocol messages. + /////////////////////////////////// + if(message->type < 100) + gpiSendServerBuddyMessage(connection, peer->profile, message->type, message->buffer.buffer + message->start); + + // Remove the message. + ////////////////////// + ArrayDeleteAt(peer->messages, 0); + } + + gpiDestroyPeer(connection, peer); +} + +GPResult gpiProcessPeers(GPConnection *connection) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIPeer * nextPeer; + GPIPeer * peer; + //SOCKET incoming; + GPResult result; + + /* + // Check for incoming peer connections. + /////////////////////////////////////// + if(iconnection->peerSocket != INVALID_SOCKET) + { + // Have to manually check if accept is possible since + // PS2 Insock only supports blocking sockets. + if (CanReceiveOnSocket(iconnection->peerSocket)) + { + incoming = accept(iconnection->peerSocket, NULL, NULL); + if(incoming != INVALID_SOCKET) + { + // This is a new peer. + ////////////////////// + peer = gpiAddPeer(connection, -1, GPIFalse); + if(peer) + { + peer->state = GPI_PEER_WAITING; + peer->sock = incoming; + SetSockBlocking(incoming, 0); + gpiSetPeerSocketSizes(peer->sock); + } + else + { + closesocket(incoming); + } + } + } + } + */ + gsUdpEngineThink(); + // Got through the list of peers. + ///////////////////////////////// + for(peer = iconnection->peerList ; peer != NULL ; peer = nextPeer) + { + // Store the next peer. + /////////////////////// + nextPeer = peer->pnext; + if(peer->state == GPI_PEER_DISCONNECTED) + { + // Remove it. + ///////////// + //gsDebug + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Notice, "Peer disconnected, pid: %d", peer->profile); + gpiRemovePeer(connection, peer); + } + else + { + // Process the peer. + //////////////////// + result = gpiProcessPeer(connection, peer); + + // Check for a disconnection or a timeout. + ////////////////////////////////////////// + if((peer->state == GPI_PEER_DISCONNECTED) || (result != GP_NO_ERROR) || (time(NULL) > peer->timeout)) + { + // Remove it. + ///////////// + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Notice, "Peer disconnected, pid: %d", peer->profile); + gpiRemovePeer(connection, peer); + } + } + } + + return GP_NO_ERROR; +} + +// NOTE: use this function when in a gp function +GPIPeer * gpiGetPeerByProfile(const GPConnection * connection, + int profileid) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIPeer * pcurr; + + // Go through the list of peers. + //////////////////////////////// + for(pcurr = iconnection->peerList ; pcurr != NULL ; pcurr = pcurr->pnext) + { + // Check for a match. + ///////////////////// + if(pcurr->profile == profileid) + { + // Got it. + ////////// + return pcurr; + } + } + + return NULL; +} + +// NOTE: use this function only when in a UDP layer callback +GPIPeer * gpiGetPeerByAddr(const GPConnection *connection, + unsigned int ip, + unsigned short port) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIPeer * pcurr; + + GS_ASSERT(ip); + GS_ASSERT(port); + if (!ip && !port) + return NULL; + // Go through the list of peers. + //////////////////////////////// + for(pcurr = iconnection->peerList ; pcurr != NULL ; pcurr = pcurr->pnext) + { + // Check for a match. + ///////////////////// + if(pcurr->ip == ip && pcurr->port == port) + { + // Got it. + ////////// + return pcurr; + } + } + + return NULL; +} + +gsi_bool gpiIsPeerConnected(GPIPeer *peer) +{ + GS_ASSERT(peer); + if (!peer) + return gsi_false; + + if (peer && peer->state != GPI_PEER_CONNECTED) + return gsi_false; + + return gsi_true; +} + +static void gpiFreeMessage(void * elem) +{ + GPIMessage * message = (GPIMessage *)elem; + + freeclear(message->buffer.buffer); +} + +GPIPeer * +gpiAddPeer( + GPConnection * connection, + int profileid, + GPIBool initiate +) +{ + GPIPeer * peer; + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Create a new peer. + ///////////////////// + peer = (GPIPeer *)gsimalloc(sizeof(GPIPeer)); + if(peer == NULL) + return NULL; + memset(peer, 0, sizeof(GPIPeer)); + peer->state = GPI_PEER_NOT_CONNECTED; + peer->initiated = initiate; + //peer->sock = INVALID_SOCKET; + peer->profile = profileid; + peer->timeout = (time(NULL) + GPI_PEER_TIMEOUT); + peer->pnext = iconnection->peerList; + peer->messages = ArrayNew(sizeof(GPIMessage), 0, gpiFreeMessage); + iconnection->peerList = peer; + peer->peerOpQueue.first = NULL; + peer->peerOpQueue.last = NULL; + peer->peerOpQueue.opList = NULL; + return peer; +} + +GPResult +gpiPeerGetSig( + GPConnection * connection, + GPIPeer * peer +) +{ + GPIOperation * operation; + + // Start a get info operation to get the sig. + ///////////////////////////////////////////// + CHECK_RESULT(gpiAddOperation(connection, GPI_GET_INFO, NULL, &operation, GP_NON_BLOCKING, NULL, NULL)); + + // Send the get info. + ///////////////////// + CHECK_RESULT(gpiSendGetInfo(connection, peer->profile, operation->id)); + + // Set the state. + ///////////////// + peer->state = GPI_PEER_GETTING_SIG; + + return GP_NO_ERROR; +} + +GPResult +gpiPeerStartConnect( + GPConnection * connection, + GPIPeer * peer +) +{ + //int rcode; + //struct sockaddr_in address; + GPIProfile * profile; + GPIConnection * iconnection = (GPIConnection*)*connection; + GSUdpErrorCode anError; + + // Get the profile object. + ////////////////////////// + if(!gpiGetProfile(connection, peer->profile, &profile)) + Error(connection, GP_NETWORK_ERROR, "Error connecting to a peer."); + + /* + // Create the socket. + ///////////////////// + peer->sock = socket(AF_INET, SOCK_STREAM, 0); + if(peer->sock == INVALID_SOCKET) + CallbackError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error creating a socket."); + + // Make it non-blocking. + //////////////////////// + rcode = SetSockBlocking(peer->sock, 0); + if(rcode == 0) + CallbackError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error making a socket non-blocking."); + + // Bind the socket. + /////////////////// + +// BD: PS2 Insock has bug with binding to port 0 +// No sockets after the first will be able to bind + + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + rcode = bind(peer->sock, (struct sockaddr *)&address, sizeof(struct sockaddr_in)); + if (gsiSocketIsError(rcode)) + CallbackError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error binding a socket."); + + // Set the socket sizes. + //////////////////////// + gpiSetPeerSocketSizes(peer->sock); + + // Connect the socket. + ////////////////////// + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = profile->buddyStatus->ip; + address.sin_port = (gsi_u16)profile->buddyStatus->port; + rcode = connect(peer->sock, (struct sockaddr *)&address, sizeof(struct sockaddr_in)); + if (gsiSocketIsError(rcode)) + { + int error = GOAGetLastError(peer->sock); + if((error != WSAEWOULDBLOCK) && (error != WSAEINPROGRESS) && (error != WSAETIMEDOUT) ) + { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error connecting a socket."); + } + } + */ + + if (profile->buddyStatusInfo) + { + GSUdpPeerState aPeerState; + gsUdpEngineGetPeerState(profile->buddyStatusInfo->buddyIp , profile->buddyStatusInfo->buddyPort, &aPeerState); + if (aPeerState != GS_UDP_PEER_CONNECTED || aPeerState != GS_UDP_PEER_CONNECTING) + { + anError = gsUdpEngineStartTalkingToPeer(profile->buddyStatusInfo->buddyIp , profile->buddyStatusInfo->buddyPort, + iconnection->mHeader, GPI_PEER_TIMEOUT); + if (anError != GS_UDP_ADDRESS_ALREADY_IN_USE) + CallbackError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error starting communication with a peer."); + } + peer->ip = profile->buddyStatusInfo->buddyIp; + peer->port = profile->buddyStatusInfo->buddyPort; + } + // We're waiting for the connect to complete. + ///////////////////////////////////////////// + peer->state = GPI_PEER_CONNECTING; + + return GP_NO_ERROR; +} + +GPResult +gpiPeerAddMessage( + GPConnection * connection, + GPIPeer * peer, + int type, + const char * message +) +{ + GPIMessage gpiMessage; + int len; + + GS_ASSERT(peer != NULL); + GS_ASSERT(message != NULL); + + if (peer == NULL) + return GP_NETWORK_ERROR; + if (message == NULL) + return GP_NETWORK_ERROR; + + // Get the length. + ////////////////// + len = (int)strlen(message); + + // Clear the message. + ///////////////////// + memset(&gpiMessage, 0, sizeof(GPIMessage)); + + // Copy the type. + ///////////////// + gpiMessage.type = type; + + // Copy the header to the buffer. + ///////////////////////////////// + CHECK_RESULT(gpiAppendStringToBuffer(connection, &gpiMessage.buffer, "\\m\\")); + CHECK_RESULT(gpiAppendIntToBuffer(connection, &gpiMessage.buffer, type)); + CHECK_RESULT(gpiAppendStringToBuffer(connection, &gpiMessage.buffer, "\\len\\")); + CHECK_RESULT(gpiAppendIntToBuffer(connection, &gpiMessage.buffer, len)); + CHECK_RESULT(gpiAppendStringToBuffer(connection, &gpiMessage.buffer, "\\msg\\\n")); + + // Copy the message to the buffer. + ////////////////////////////////// + gpiMessage.start = gpiMessage.buffer.len; + CHECK_RESULT(gpiAppendStringToBufferLen(connection, &gpiMessage.buffer, message, len)); + CHECK_RESULT(gpiAppendCharToBuffer(connection, &gpiMessage.buffer, '\0')); + + // Add it to the list. + ////////////////////// + ArrayAppend(peer->messages, &gpiMessage); + + // Reset the timeout. + ///////////////////// + peer->timeout = (time(NULL) + GPI_PEER_TIMEOUT); + + return GP_NO_ERROR; +} + +GPResult +gpiPeerStartTransferMessage( + GPConnection * connection, + GPIPeer * peer, + int type, + const struct GPITransferID_s * transferID +) +{ + char buffer[64]; + GPITransferID tid; + tid.count = transferID->count; + tid.profileid = transferID->profileid; + tid.time = transferID->time; + + GS_ASSERT(transferID); + if (!transferID) + return GP_NETWORK_ERROR; + // Start the message. + ///////////////////// + sprintf(buffer, "\\m\\%d\\xfer\\%d %u %u", type, tid.profileid, tid.count, tid.time); + + return gpiSendOrBufferString(connection, peer, buffer); +} + +GPResult +gpiPeerFinishTransferMessage( + GPConnection * connection, + GPIPeer * peer, + const char * message, + int len +) +{ + char buffer[32]; + GS_ASSERT(peer != NULL); + if (!peer) + return GP_NETWORK_ERROR; + + // Check the message. + ///////////////////// + if(!message) + message = ""; + + if(len == -1) + len = (int)strlen(message); + + // Set the len and the message. + /////////////////////////////// + sprintf(buffer, "\\len\\%d\\msg\\\n", len); + CHECK_RESULT(gpiSendOrBufferString(connection, peer, buffer)); + + // Copy the message to the buffer. + ////////////////////////////////// + CHECK_RESULT(gpiSendOrBufferStringLenToPeer(connection, peer, message, len)); + CHECK_RESULT(gpiSendOrBufferChar(connection, peer, '\0')); + + // Reset the timeout. + ///////////////////// + peer->timeout = (time(NULL) + GPI_PEER_TIMEOUT); + + return GP_NO_ERROR; +} + +void gpiPeerLeftCallback(unsigned int ip, unsigned short port, GSUdpCloseReason reason, void *userData) +{ + + GPConnection *connection = (GPConnection *)userData; + GPIPeer *aPeer; + IN_ADDR anAddr; + anAddr.s_addr = ip; + aPeer = gpiGetPeerByAddr(connection, ip, port); + //gpiRemovePeer(connection, aPeer); + if (aPeer) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Notice, + "Peer left: addr: %s:%d, profile: %d\n", inet_ntoa(anAddr), port, aPeer->profile); + aPeer->state = GPI_PEER_DISCONNECTED; + } + + GSI_UNUSED(anAddr); + GSI_UNUSED(reason); +} + +void gpiPeerMessageCallback(unsigned int ip, unsigned short port, unsigned char *message, + unsigned int messageLength, gsi_bool reliable, void *userData) +{ + GPConnection *connection = (GPConnection *)userData; + GPIPeer *aPeer; + unsigned char * buff; + int writePos; + int size; + IN_ADDR anAddr; + anAddr.s_addr = ip; + aPeer = gpiGetPeerByAddr(connection, ip, port); + if (!aPeer) + { + aPeer = gpiAddPeer(connection, -1, GPIFalse); + if (aPeer) + { + aPeer->state = GPI_PEER_WAITING; + aPeer->ip = ip; + aPeer->port = port; + } + else + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Memory, GSIDebugLevel_HotError, + "gpiPeerMessageCallback: out of memory when allocating peer, addr: %s:%d", inet_ntoa(anAddr), port); + return; + } + } + + buff = (unsigned char *)aPeer->inputBuffer.buffer; + writePos = aPeer->inputBuffer.len; + size = aPeer->inputBuffer.size; + + // Check if the buffer needs to be resized. + /////////////////////////////////////////// + if((int)messageLength > (size - writePos)) + { + unsigned char *reallocedBuff; + size = (writePos + max(GPI_READ_SIZE,(int)messageLength)); + reallocedBuff = (unsigned char *)gsirealloc(buff, (unsigned int)size + 1); + if(reallocedBuff == NULL) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Memory, GSIDebugLevel_HotError, + "gpiPeerMessageCallback: out of memory when reallocating buffer, addr: %s:%d", inet_ntoa(anAddr), port); + gsifree(buff); + gpiSetErrorString(connection, "Out of memory."); + gpiCallErrorCallback(connection, GP_MEMORY_ERROR, GP_NON_FATAL); + return; + } + else + buff = reallocedBuff; + } + + memcpy(&buff[writePos], message, messageLength); + + aPeer->inputBuffer.buffer = (char *)buff; + aPeer->inputBuffer.len += messageLength; + aPeer->inputBuffer.size = size; + buff[aPeer->inputBuffer.len] = '\0'; + GSI_UNUSED(reliable); + GSI_UNUSED(anAddr); +} + +void gpiPeerAcceptedCallback(unsigned int ip, unsigned short port, + GSUdpErrorCode error, gsi_bool rejected, void *userData) +{ + GPConnection *connection = (GPConnection *)userData; + GPIPeer *aPeer; + IN_ADDR anAddr; + anAddr.s_addr = ip; + + aPeer = gpiGetPeerByAddr(connection, ip, port); + if (!aPeer) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_HotError, + "Peer does not exist: ip-port: %s:%d\n", inet_ntoa(anAddr), port); + } + else + { + if (rejected) + { + aPeer->state = GPI_PEER_DISCONNECTED; + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Notice, + "Peer Connection rejected: ip-port: %s:%d\n", inet_ntoa(anAddr), port); + return; + } + } + + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Notice, + "Peer Connection accepted: ip-port: %s:%d\n", inet_ntoa(anAddr), port); + + GSI_UNUSED(userData); + GSI_UNUSED(rejected); + GSI_UNUSED(error); + GSI_UNUSED(anAddr); +} +void gpiPeerPingReplyCallback(unsigned int ip, unsigned short port, unsigned int latency, void *userData) +{ + GSI_UNUSED(userData); + GSI_UNUSED(latency); + GSI_UNUSED(port); + GSI_UNUSED(ip); +} + +// gpiPeerAddOp notes: +// Assumes non-null inputs! +// The queue should be empty when the first element is added. +// Any new element added will be added to the end of the queue. +void gpiPeerAddOp(GPIPeer *peer, GPIPeerOp *operation) +{ + GS_ASSERT(peer); + GS_ASSERT(operation); + + if (!peer || !operation) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_WarmError, "Peer operation not added"); + return; + } + // Three cases can occur: + // The list is empty - set all pointers to the new node + // The list has only one element - set the first element's next to the new + // and set the last element to the new + // The list has more than one element - add the new element to the end of + // the queue + if (peer->peerOpQueue.opList == NULL) + { + peer->peerOpQueue.first = operation; + peer->peerOpQueue.last = operation; + peer->peerOpQueue.opList = operation; + } + else if (peer->peerOpQueue.first == peer->peerOpQueue.last) + { + peer->peerOpQueue.first->next = operation; + peer->peerOpQueue.last = operation; + } + else + { + peer->peerOpQueue.last->next = operation; + peer->peerOpQueue.last = operation; + } + + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Notice, "Peer Operation Added"); +} + +// gpiPeerRemoveOp: +// Assumes the list is NOT NULL otherwise it returns. +// Assumes the operation being passed in is on the queue. +// Assumes non-null inputs! +// Completed or Timed out Operations are deleted from queue by finding +// the operation passed in. Removal of operations don't necessarily +// happen in order. +void gpiPeerRemoveOp(GPIPeer *peer, GPIPeerOp *operation) +{ + GS_ASSERT(peer); + GS_ASSERT(operation); + if (!peer || !operation) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_WarmError, "Peer operation not removed"); + return; + } + + GS_ASSERT(peer->peerOpQueue.opList != NULL); + if (peer->peerOpQueue.opList == NULL) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_WarmError, "Peer operation not removed"); + return; + } + + if (peer->peerOpQueue.first == peer->peerOpQueue.last && peer->peerOpQueue.first == operation) + { + peer->peerOpQueue.opList = peer->peerOpQueue.first = peer->peerOpQueue.last = operation->next; + } + else if (peer->peerOpQueue.first == operation) + { + peer->peerOpQueue.first = peer->peerOpQueue.first->next; + peer->peerOpQueue.opList = peer->peerOpQueue.first; + } + else + { + GPIPeerOp *aPrevOp = NULL; + for(aPrevOp = peer->peerOpQueue.first ; aPrevOp->next != operation ; aPrevOp = aPrevOp->next) + { + if(aPrevOp->next == NULL) + { + // Can't find this peer in the list! + //////////////////////////////////// + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "Tried to remove peer operation not in list."); + return; + } + } + aPrevOp->next = operation->next; + } + + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Notice, "Peer operation removed"); + freeclear(operation); +} diff --git a/code/gamespy/GP/gpiPeer.h b/code/gamespy/GP/gpiPeer.h new file mode 100644 index 00000000..8e864b9e --- /dev/null +++ b/code/gamespy/GP/gpiPeer.h @@ -0,0 +1,186 @@ +/* +gpiPeer.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#ifndef _GPIPEER_H_ +#define _GPIPEER_H_ + +//INCLUDES +////////// +#include "gpi.h" + +//DEFINES +///////// +// Peer states. +/////////////// +#define GPI_PEER_NOT_CONNECTED 100 +#define GPI_PEER_GETTING_SIG 101 +#define GPI_PEER_GOT_SIG 102 +#define GPI_PEER_CONNECTING 103 +#define GPI_PEER_WAITING 104 +#define GPI_PEER_CONNECTED 105 +#define GPI_PEER_DISCONNECTED 106 + +// Timeout for a peer connection, in milliseconds. +///////////////////////////////////////////// +#define GPI_PEER_TIMEOUT (10 * 1000) + +// Timeout for a peer operation, in milliseconds +//////////////////////////////////////////// +#define GPI_PEER_OP_TIMEOUT 60000 + +typedef enum +{ + GPI_PEER_OP_STATE_NONE, + GPI_PEER_OP_STATE_REQUESTED, + GPI_PEER_OP_STATE_FINISHED +} GPIPeerOpState; + +typedef struct GPITransferID_s * GPITransferID_st; + +//TYPES +/////// +// A peer message. +////////////////// +typedef struct GPIMessage +{ + GPIBuffer buffer; + int type; + int start; +} GPIMessage; + +typedef struct _GPIPeerOp +{ + GPIPeerOpState state; + void *userData; + GPCallback callback; + struct _GPIPeerOp * next; + int type; + gsi_time timeout; +} GPIPeerOp; + +typedef struct _GPIPeerOpQueue +{ + GPIPeerOp * opList; + GPIPeerOp * first; + GPIPeerOp * last; +} GPIPeerOpQueue; + +// A peer connection. +///////////////////// +typedef struct GPIPeer_s +{ + int state; + GPIBool initiated; + //SOCKET sock; + unsigned int ip; + unsigned short port; + GPProfile profile; + time_t timeout; + int nackCount; + GPIBuffer inputBuffer; + GPIBuffer outputBuffer; + DArray messages; + GPIPeerOpQueue peerOpQueue; + struct GPIPeer_s * pnext; +} GPIPeer; + +//FUNCTIONS +/////////// +GPResult +gpiProcessPeers( + GPConnection * connection +); + +GPResult +gpiPeerGetSig( + GPConnection * connection, + GPIPeer * peer +); + +GPResult +gpiPeerStartConnect( + GPConnection * connection, + GPIPeer * peer +); + +// NOTE: use this function when in a gp function +GPIPeer * gpiGetPeerByProfile(const GPConnection * connection, + int profileid); + +// NOTE: use this function only when in a UDP layer callback +GPIPeer *gpiGetPeerByAddr(const GPConnection *connection, + unsigned int ip, + unsigned short port); + +gsi_bool gpiIsPeerConnected(GPIPeer *peer); + +GPIPeer * +gpiAddPeer( + GPConnection * connection, + int profileid, + GPIBool initiate +); + +void +gpiDestroyPeer( + GPConnection * connection, + GPIPeer * peer +); + +void +gpiRemovePeer( + GPConnection * connection, + GPIPeer * peer +); + +GPResult +gpiPeerAddMessage( + GPConnection * connection, + GPIPeer * peer, + int type, + const char * message +); + +GPResult +gpiPeerStartTransferMessage( + GPConnection * connection, + GPIPeer * peer, + int type, + const struct GPITransferID_s * transferID +); + +GPResult +gpiPeerFinishTransferMessage( + GPConnection * connection, + GPIPeer * peer, + const char * message, + int len +); + +GPResult +gpiPeerSendMessages( + GPConnection * connection, + GPIPeer * peer +); + +void gpiPeerLeftCallback(unsigned int ip, unsigned short port, GSUdpCloseReason reason, void *userData); +void gpiPeerMessageCallback(unsigned int ip, unsigned short port, unsigned char *message, + unsigned int messageLength, gsi_bool reliable, void *userData); +void gpiPeerAcceptedCallback(unsigned int ip, unsigned short port, + GSUdpErrorCode error, gsi_bool rejected, void *userData); +void gpiPeerPingReplyCallback(unsigned int ip, unsigned short port, unsigned int latency, void *userData); + +void gpiPeerAddOp(GPIPeer *peer, GPIPeerOp *operation); +void gpiPeerRemoveOp(GPIPeer *peer, GPIPeerOp *operation); +void gpiCheckTimedOutPeerOperations(GPConnection * connection, GPIPeer * peer); +#endif diff --git a/code/gamespy/GP/gpiProfile.c b/code/gamespy/GP/gpiProfile.c new file mode 100644 index 00000000..35e29e12 --- /dev/null +++ b/code/gamespy/GP/gpiProfile.c @@ -0,0 +1,1390 @@ +/* +gpiProfile.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +//INCLUDES +////////// +#include "gpi.h" + +//DEFINES +///////// +#define GPI_PROFILE_GROW_SIZE 16 +#define GPI_PROFILE_CACHE_VERSION 2 + +// GLOBALS +////////// +static char GPIInfoCacheFilename[FILENAME_MAX + 1] = "gp.info"; + +//FUNCTIONS +/////////// +static int +gpiProfilesTableHash( + const void *arg, + int numBuckets +) +{ + const GPIProfile * profile = (const GPIProfile *)arg; + return (profile->profileId % numBuckets); +} + +static int +gpiProfilesTableCompare( + const void * arg1, + const void * arg2 +) +{ + const GPIProfile * profile1 = (const GPIProfile *)arg1; + const GPIProfile * profile2 = (const GPIProfile *)arg2; + return (profile1->profileId - profile2->profileId); +} + +static void +gpiProfilesTableFree( + void *arg +) +{ + GPIProfile * profile = (GPIProfile *)arg; + if(profile->buddyStatus) + { + freeclear(profile->buddyStatus->statusString); + freeclear(profile->buddyStatus->locationString); + freeclear(profile->buddyStatus); + } + if (profile->buddyStatusInfo) + { + freeclear(profile->buddyStatusInfo->richStatus); + freeclear(profile->buddyStatusInfo->gameType); + freeclear(profile->buddyStatusInfo->gameVariant); + freeclear(profile->buddyStatusInfo->gameMapName); + if (profile->buddyStatusInfo->extendedInfoKeys) + { + ArrayFree(profile->buddyStatusInfo->extendedInfoKeys); + profile->buddyStatusInfo->extendedInfoKeys = NULL; + } + freeclear(profile->buddyStatusInfo); + } + gpiFreeInfoCache(profile); + freeclear(profile->authSig); + freeclear(profile->peerSig); +} + +GPIBool +gpiInitProfiles( + GPConnection * connection +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + iconnection->profileList.numBuddies = 0; + iconnection->profileList.numBlocked = 0; + iconnection->profileList.num = 0; + iconnection->profileList.profileTable = TableNew( + sizeof(GPIProfile), + 32, + gpiProfilesTableHash, + gpiProfilesTableCompare, + gpiProfilesTableFree); + if(!iconnection->profileList.profileTable) + return GPIFalse; + + return GPITrue; +} + +#ifndef NOFILE + +static GPResult +gpiOpenDiskProfiles( + GPConnection * connection, + GPIBool write, + GPIBool * failed +) +{ + FILE * fp = NULL; + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Open the file. + ///////////////// + if(write) + fp = fopen(GPIInfoCacheFilename, "wt"); + else + fp = fopen(GPIInfoCacheFilename, "rt"); + if(fp == NULL) + { + *failed = GPITrue; + return GP_NO_ERROR; + } + + // Excellent. + ///////////// + iconnection->diskCache = fp; + *failed = GPIFalse; + + return GP_NO_ERROR; + + GSI_UNUSED(write); +} + +static void +gpiCloseDiskProfiles( + GPConnection * connection +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Close the file. + ////////////////// + fclose(iconnection->diskCache); + iconnection->diskCache = NULL; + + return; +} + +static GPResult +gpiReadDiskKeyValue( + GPConnection * connection, + GPIBool * failed, + char key[512], + char value[512] +) +{ + int c; + FILE * fp; + GPIConnection * iconnection = (GPIConnection*)*connection; + int i; + + // Grab the file pointer. + ///////////////////////// + fp = iconnection->diskCache; + + // Read the key. + //////////////// + i = 0; + do + { + if(i == 512) + { + *failed = GPITrue; + return GP_NO_ERROR; + } + c = fgetc(fp); + if((c == EOF) || (c == '\n')) + { + *failed = GPITrue; + return GP_NO_ERROR; + } + key[i++] = (char)c; + } + while(c != '='); + key[--i] = '\0'; + + // Check for no key. + //////////////////// + if(i == 0) + { + *failed = GPITrue; + return GP_NO_ERROR; + } + + // Read the value. + ////////////////// + i = 0; + do + { + if(i == 512) + { + *failed = GPITrue; + return GP_NO_ERROR; + } + c = fgetc(fp); + if(c == EOF) + { + c = '\n'; + } + value[i++] = (char)c; + } + while(c != '\n'); + value[--i] = '\0'; + + // Done. + //////// + *failed = GPIFalse; + return GP_NO_ERROR; + + GSI_UNUSED(value); + GSI_UNUSED(key); + GSI_UNUSED(connection); +} + +static GPResult +gpiReadDiskProfile( + GPConnection * connection, + GPIBool * failedOut +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + FILE * fp; + GPIProfile profile; + int c; + int rcode; + GPIBool failed; + char key[256]; + char value[256]; + GPIInfoCache infoCache; + GPIBool valid = GPIFalse; + GPIProfile * pProfile; + char nick[GP_NICK_LEN]; + char uniquenick[GP_UNIQUENICK_LEN]; + char email[GP_EMAIL_LEN]; + char firstname[GP_FIRSTNAME_LEN]; + char lastname[GP_LASTNAME_LEN]; + char homepage[GP_HOMEPAGE_LEN]; + char aimname[GP_AIMNAME_LEN]; + + // Grab the file pointer. + ///////////////////////// + fp = iconnection->diskCache; + + // Clear the temp profile. + ////////////////////////// + memset(&profile, 0, sizeof(GPIProfile)); + + // Clear the temp cache. + //////////////////////// + memset(&infoCache, 0, sizeof(GPIInfoCache)); + infoCache.nick = nick; + infoCache.uniquenick = uniquenick; + infoCache.email = email; + infoCache.firstname = firstname; + infoCache.lastname = lastname; + infoCache.homepage = homepage; + infoCache.aimname = aimname; + nick[0] = '\0'; + uniquenick[0] = '\0'; + email[0] = '\0'; + firstname[0] = '\0'; + lastname[0] = '\0'; + homepage[0] = '\0'; + aimname[0] = '\0'; + + // Read until we hit a [. + ///////////////////////// + do + { + c = fgetc(fp); + if(c == EOF) + { + *failedOut = GPITrue; + return GP_NO_ERROR; + } + } + while(c != '['); + + // Grab the profileid. + ////////////////////// + rcode = fscanf(fp, "%d]\n", &profile.profileId); + if(rcode != 1) + { + *failedOut = GPITrue; + return GP_NO_ERROR; + } + + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_Comment, + "Reading profile %d from disk cache:\n", profile.profileId); + + // Read key/value pairs. + //////////////////////// + do + { + CHECK_RESULT(gpiReadDiskKeyValue(connection, &failed, key, value)); + if(!failed) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_Comment, + "%d: %s=%s\n", profile.profileId, key, value); + + // Set the data based on the key. + ///////////////////////////////// + if(strcmp(key, "userid") == 0) + { + profile.userId = atoi(value); + } + else if(strcmp(key, "nick") == 0) + { + strzcpy(infoCache.nick, value, GP_NICK_LEN); + } + else if(strcmp(key, "uniquenick") == 0) + { + strzcpy(infoCache.uniquenick, value, GP_UNIQUENICK_LEN); + } + else if(strcmp(key, "email") == 0) + { + strzcpy(infoCache.email, value, GP_EMAIL_LEN); + } + else if(strcmp(key, "firstname") == 0) + { + strzcpy(infoCache.firstname, value, GP_FIRSTNAME_LEN); + } + else if(strcmp(key, "lastname") == 0) + { + strzcpy(infoCache.lastname, value, GP_LASTNAME_LEN); + } + else if(strcmp(key, "homepage") == 0) + { + strzcpy(infoCache.homepage, value, GP_HOMEPAGE_LEN); + } + else if(strcmp(key, "icquin") == 0) + { + infoCache.icquin = atoi(value); + } + else if(strcmp(key, "zipcode") == 0) + { + strzcpy(infoCache.zipcode, value, GP_ZIPCODE_LEN); + } + else if(strcmp(key, "countrycode") == 0) + { + strzcpy(infoCache.countrycode, value, GP_COUNTRYCODE_LEN); + } + else if(strcmp(key, "birthday") == 0) + { + infoCache.birthday = atoi(value); + } + else if(strcmp(key, "birthmonth") == 0) + { + infoCache.birthmonth = atoi(value); + } + else if(strcmp(key, "birthyear") == 0) + { + infoCache.birthyear = atoi(value); + } + else if(strcmp(key, "sex") == 0) + { + if(toupper(value[0]) == 'M') + infoCache.sex = GP_MALE; + else if(toupper(value[1] == 'F')) + infoCache.sex = GP_FEMALE; + else + infoCache.sex = GP_PAT; + } + else if(strcmp(key, "publicmask") == 0) + { + infoCache.publicmask = atoi(value); + } + else if(strcmp(key, "aimname") == 0) + { + strzcpy(infoCache.aimname, value, GP_AIMNAME_LEN); + } + else if(strcmp(key, "pic") == 0) + { + infoCache.pic = atoi(value); + } + else if(strcmp(key, "occupationid") == 0) + { + infoCache.occupationid = atoi(value); + } + else if(strcmp(key, "industryid") == 0) + { + infoCache.industryid = atoi(value); + } + else if(strcmp(key, "incomeid") == 0) + { + infoCache.incomeid = atoi(value); + } + else if(strcmp(key, "marriedid") == 0) + { + infoCache.marriedid = atoi(value); + } + else if(strcmp(key, "childcount") == 0) + { + infoCache.childcount = atoi(value); + } + else if(strcmp(key, "interests1") == 0) + { + infoCache.interests1 = atoi(value); + } + else if(strcmp(key, "ownership1") == 0) + { + infoCache.ownership1 = atoi(value); + } + else if(strcmp(key, "conntypeid") == 0) + { + infoCache.conntypeid = atoi(value); + } + else if(strcmp(key, "valid") == 0) + { + valid = (GPIBool)atoi(value); + } + else + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_HotError, + "Unrecognized profile key: %s=%s\n", key, value); + } + } + } + while(!failed); + + // Create a new profile. + //////////////////////// + pProfile = gpiProfileListAdd(connection, profile.profileId); + if(pProfile) + { + // Copy the profile we've set up into the list. + /////////////////////////////////////////////// + *pProfile = profile; + + // Copy the info if valid. + ////////////////////////// + if(valid) + gpiSetInfoCache(connection, pProfile, &infoCache); + } + *failedOut = GPIFalse; + return GP_NO_ERROR; + + GSI_UNUSED(connection); +} + +static GPResult +gpiReadVersion( + const GPConnection * connection, + int * version +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + FILE * fp; + + // Grab the file pointer. + ///////////////////////// + fp = iconnection->diskCache; + + // Read the version. + //////////////////// + if(fscanf(fp, "%d\n", version) != 1) + *version = 0; + + return GP_NO_ERROR; + + GSI_UNUSED(connection); + GSI_UNUSED(version); +} + +static void +gpiWriteVersion( + GPConnection * connection, + int version +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + FILE * fp; + + // Grab the file pointer. + ///////////////////////// + fp = iconnection->diskCache; + + // Write the version. + ///////////////////// + fprintf(fp, "%d\n", version); + + GSI_UNUSED(connection); + GSI_UNUSED(version); +} + +GPResult +gpiLoadDiskProfiles( + GPConnection * connection +) +{ + GPIBool failed; + int count; + int version = 0; + + // Open the disk cache. + /////////////////////// + CHECK_RESULT(gpiOpenDiskProfiles(connection, GPIFalse, &failed)); + if(failed) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_HotError, + "Failed to open the disk cache file.\n"); + return GP_NO_ERROR; + } + + // Check the version. + ////////////////////// + CHECK_RESULT(gpiReadVersion(connection, &version)); + if(version == GPI_PROFILE_CACHE_VERSION) + { + // Read profiles. + ///////////////// + count = 0; + do + { + CHECK_RESULT(gpiReadDiskProfile(connection, &failed)); + if(!failed) + count++; + } + while(!failed); + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_Comment, + "Loaded %d profiles from disk cache.\n", count); + } + + // Close the cache. + /////////////////// + gpiCloseDiskProfiles(connection); + + return GP_NO_ERROR; +} + +static GPIBool +gpiSaveDiskProfile( + GPConnection * connection, + GPIProfile * profile, + void * data +) +{ + FILE * fp; + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Grab the file pointer. + ///////////////////////// + fp = iconnection->diskCache; + + // Write the profile id. + //////////////////////// + fprintf(fp, "[%d]\n", profile->profileId); + + // Write the userid if not 0. + ///////////////////////////// + if(profile->userId != 0) + fprintf(fp, "userid=%d\n", profile->userId); + + // Is the cache valid? + ////////////////////// + if(profile->cache) + { + fprintf(fp, "valid=1\n"); + + fprintf(fp, "nick=%s\n", profile->cache->nick); + fprintf(fp, "uniquenick=%s\n", profile->cache->uniquenick); + fprintf(fp, "email=%s\n", profile->cache->email); + fprintf(fp, "firstname=%s\n", profile->cache->firstname); + fprintf(fp, "lastname=%s\n", profile->cache->lastname); + fprintf(fp, "homepage=%s\n", profile->cache->homepage); + fprintf(fp, "icquin=%d\n", profile->cache->icquin); + fprintf(fp, "zipcode=%s\n", profile->cache->zipcode); + fprintf(fp, "countrycode=%s\n", profile->cache->countrycode); + fprintf(fp, "birthday=%d\n", profile->cache->birthday); + fprintf(fp, "birthmonth=%d\n", profile->cache->birthmonth); + fprintf(fp, "birthyear=%d\n", profile->cache->birthyear); + if(profile->cache->sex == GP_MALE) + fprintf(fp, "sex=Male\n"); + if(profile->cache->sex == GP_FEMALE) + fprintf(fp, "sex=Female\n"); + if(profile->cache->sex == GP_PAT) + fprintf(fp, "sex=Pat\n"); + fprintf(fp, "publicmask=%d\n", profile->cache->publicmask); + fprintf(fp, "aimname=%s\n", profile->cache->aimname); + fprintf(fp, "pic=%d\n", profile->cache->pic); + fprintf(fp, "occupationid=%d\n", profile->cache->occupationid); + fprintf(fp, "industryid=%d\n", profile->cache->industryid); + fprintf(fp, "incomeid=%d\n", profile->cache->incomeid); + fprintf(fp, "marriedid=%d\n", profile->cache->marriedid); + fprintf(fp, "childcount=%d\n", profile->cache->childcount); + fprintf(fp, "interests1=%d\n", profile->cache->interests1); + fprintf(fp, "ownership1=%d\n", profile->cache->ownership1); + fprintf(fp, "conntypeid=%d\n", profile->cache->conntypeid); + } + + // End this profile. + //////////////////// + fprintf(fp, "\n"); + + GSI_UNUSED(data); + GSI_UNUSED(connection); + GSI_UNUSED(profile); + + return GPITrue; +} + +GPResult +gpiSaveDiskProfiles( + GPConnection * connection +) +{ + GPIBool failed; + + // Open the disk cache. + /////////////////////// + CHECK_RESULT(gpiOpenDiskProfiles(connection, GPITrue, &failed)); + if(failed) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_HotError, + "Failed to open the disk cache file.\n"); + return GP_NO_ERROR; + } + + // Write the version. + ///////////////////// + gpiWriteVersion(connection, GPI_PROFILE_CACHE_VERSION); + + // Save profiles. + ///////////////// + gpiProfileMap(connection, gpiSaveDiskProfile, NULL); + + // Close the cache. + /////////////////// + gpiCloseDiskProfiles(connection); + + return GP_NO_ERROR; +} + +#endif + +GPResult +gpiProcessNewProfile( + GPConnection * connection, + GPIOperation * operation, + const char * input +) +{ + char buffer[16]; + int pid; + GPICallback callback; + + // Check for an error. + ////////////////////// + if(gpiCheckForError(connection, input, GPITrue)) + return GP_SERVER_ERROR; + + // This should be \npr\. + //////////////////////// + if(strncmp(input, "\\npr\\", 5) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Get the profile id. + ////////////////////// + if(!gpiValueForKey(input, "\\profileid\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + pid = atoi(buffer); + + // Call the callback. + ///////////////////// + callback = operation->callback; + if(callback.callback != NULL) + { + GPNewProfileResponseArg * arg; + arg = (GPNewProfileResponseArg *)gsimalloc(sizeof(GPNewProfileResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + arg->profile = (GPProfile)pid; + arg->result = GP_NO_ERROR; + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + } + + // Remove the operation. + //////////////////////// + gpiRemoveOperation(connection, operation); + + return GP_NO_ERROR; +} + +GPIProfile * +gpiProfileListAdd( + GPConnection * connection, + int id +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIProfileList * profileList = &iconnection->profileList; + GPIProfile profile; + GPIProfile * pProfile; + + assert(id > 0); + + // Check the parameters. 2000.02.14.JED was not checked in release build. + ///////////////////////////////////////////////////////////////////////// + if(id <= 0) + return NULL; + + // Check if this id is already in the list. + /////////////////////////////////////////// + if(gpiGetProfile(connection, (GPProfile)id, &pProfile)) + return pProfile; + + // Setup the new profile. + ///////////////////////// + memset(&profile, 0, sizeof(GPIProfile)); + profile.profileId = id; + profile.userId = 0; + profile.cache = NULL; + profile.authSig = NULL; + profile.peerSig = NULL; + profile.requestCount = 0; + + // Add it to the table. + /////////////////////// + TableEnter(profileList->profileTable, &profile); + + // One new one. + /////////////// + profileList->num++; + + // Get a pointer to the profile. + //////////////////////////////// + if(gpiGetProfile(connection, (GPProfile)id, &pProfile)) + return pProfile; + + // It wasn't added. + /////////////////// + return NULL; +} + +GPIBool +gpiGetProfile( + GPConnection * connection, + GPProfile profileid, + GPIProfile ** pProfile +) +{ + GPIProfile * profile; + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIProfile profileTemp; + + profileTemp.profileId = profileid; + profile = (GPIProfile *)TableLookup(iconnection->profileList.profileTable, &profileTemp); + if(pProfile) + *pProfile = profile; + + return ((profile != NULL) ? GPITrue:GPIFalse); +} + +GPResult +gpiNewProfile( + GPConnection * connection, + const char nick[31], + GPEnum replace, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIOperation * operation; + GPResult result; + char buffer[31]; + + // Error check. + /////////////// + if(nick == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid nick."); + if((replace != GP_REPLACE) && (replace != GP_DONT_REPLACE)) + Error(connection, GP_PARAMETER_ERROR, "Invalid replace."); + + // Create a new operation. + ////////////////////////// + CHECK_RESULT(gpiAddOperation(connection, GPI_NEW_PROFILE, NULL, &operation, blocking, callback, param)); + + // Send the request. + //////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\newprofile\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\nick\\"); + strzcpy(buffer, nick, GP_NICK_LEN); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, buffer); + if(replace == GP_REPLACE) + { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\replace\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, 1); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\oldnick\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, iconnection->nick); + } + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, operation->id); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); +/* + if(string.result != GP_NO_ERROR) + { + gpiRemoveOperation(connection, operation); + return string.result; + } +*/ + // Process it if blocking. + ////////////////////////// + if(operation->blocking) + { + result = gpiProcess(connection, operation->id); + if(result != GP_NO_ERROR) + { + gpiRemoveOperation(connection, operation); + return result; + } + } + + return GP_NO_ERROR; +} + +GPResult gpiProcessDeleteProfle +( + GPConnection * connection, + GPIOperation * operation, + const char * input +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPICallback callback; + + // Check for an error. + ////////////////////// + if(gpiCheckForError(connection, input, GPITrue)) + return GP_SERVER_ERROR; + + // This should be \dpr\. + //////////////////////// + if(strncmp(input, "\\dpr\\", 5) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + + // Call the callback. + ///////////////////// + callback = operation->callback; + if(callback.callback != NULL) + { + GPDeleteProfileResponseArg * arg; + arg = (GPDeleteProfileResponseArg *)gsimalloc(sizeof(GPDeleteProfileResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + arg->profile = iconnection->profileid; + arg->result = GP_NO_ERROR; + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + } + + // Remove the operation. + //////////////////////// + gpiRemoveOperation(connection, operation); + + return GP_NO_ERROR; +} + + +GPResult +gpiDeleteProfile( + GPConnection * connection, + GPCallback callback, + void *param +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIOperation * operation; + GPResult result; + + CHECK_RESULT(gpiAddOperation(connection, GPI_DELETE_PROFILE, NULL, &operation, GP_BLOCKING, callback, param)); + // Send the message. + //////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\delprofile\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, operation->id); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + // Remove the profile object. + ///////////////////////////// + gpiRemoveProfileByID(connection, iconnection->profileid); + + // Disconnect the connection. + // PANTS|05.16.00 + ///////////////////////////// + iconnection->connectState = GPI_PROFILE_DELETING; + result = gpiProcess(connection, operation->id); + if (result != GP_NO_ERROR) + { + gpiRemoveOperation(connection, operation); + return result; + } + + gpiDisconnect(connection, GPIFalse); + return GP_NO_ERROR; +} + +void +gpiRemoveProfileByID( + GPConnection * connection, + int profileid +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIProfile * profile; + + if(gpiGetProfile(connection, (GPProfile)profileid, &profile)) + TableRemove(iconnection->profileList.profileTable, profile); +} + +void +gpiRemoveProfile( + GPConnection * connection, + GPIProfile * profile +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + TableRemove(iconnection->profileList.profileTable, profile); +} + +typedef struct GPIFindProfileByUserData +{ + char * nick; + char * email; + GPIProfile ** profile; + GPIBool found; +} GPIFindProfileByUserData; + +static GPIBool gpiCheckProfileForUser( + GPConnection * connection, + GPIProfile * profile, + void *udata +) +{ + GPIFindProfileByUserData * data = (GPIFindProfileByUserData *)udata; + + GSI_UNUSED(connection); + + // Check for a valid cache. + /////////////////////////// + if(profile->cache) + { + // Check the nick and email. + //////////////////////////// + if((strcmp(data->nick, profile->cache->nick) == 0) && (strcmp(data->email, profile->cache->email) == 0)) + { + // Found it. + //////////// + *data->profile = profile; + data->found = GPITrue; + return GPIFalse; + } + } + + return GPITrue; +} + +GPResult +gpiFindProfileByUser( + GPConnection * connection, + char nick[GP_NICK_LEN], + char email[GP_EMAIL_LEN], + GPIProfile ** profile +) +{ + GPIFindProfileByUserData data; + + data.nick = nick; + data.email = email; + data.profile = profile; + data.found = GPIFalse; + + gpiProfileMap(connection, gpiCheckProfileForUser, &data); + + if(!data.found) + *profile = NULL; + + return GP_NO_ERROR; +} + +typedef struct GPIProfileMapData +{ + GPConnection * connection; + gpiProfileMapFunc func; + void * data; +} GPIProfileMapData; + +static int +gpiProfileMapCallback( + void *arg, + void *udata +) +{ + GPIProfile * profile = (GPIProfile *)arg; + GPIProfileMapData * data = (GPIProfileMapData *)udata; + return (int)data->func(data->connection, profile, data->data); +} + +GPIBool +gpiProfileMap( + GPConnection * connection, + gpiProfileMapFunc func, + void * data +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIProfileMapData mapData; + + mapData.connection = connection; + mapData.func = func; + mapData.data = data; + + return (!TableMapSafe2(iconnection->profileList.profileTable, gpiProfileMapCallback, &mapData)) ? GPITrue:GPIFalse; +} + +typedef struct GPIFindProfileData +{ + int index; + GPIProfile * profile; +} GPIFindProfileData; + +static GPIBool +gpiCheckForBuddy( + GPConnection * connection, + GPIProfile * profile, + void *udata +) +{ + GPIFindProfileData * data = (GPIFindProfileData *)udata; + if (profile->buddyStatus && (data->index == profile->buddyStatus->buddyIndex)) + { + data->profile = profile; + return GPIFalse; + } + else if (profile->buddyStatusInfo && (data->index == profile->buddyStatusInfo->buddyIndex)) + { + data->profile = profile; + return GPIFalse; + } + + GSI_UNUSED(connection); + + return GPITrue; +} + +GPIProfile * +gpiFindBuddy( + GPConnection * connection, + int buddyIndex +) +{ + GPIFindProfileData data; + + data.index = buddyIndex; + data.profile = NULL; + + gpiProfileMap(connection, gpiCheckForBuddy, &data); + + return data.profile; +} + +void gpiRemoveBuddyStatus(GPIBuddyStatus *buddyStatus) +{ + GS_ASSERT(buddyStatus); + + freeclear(buddyStatus->locationString); + freeclear(buddyStatus->statusString); + freeclear(buddyStatus); +} + +void gpiRemoveBuddyStatusInfo(GPIBuddyStatusInfo *buddyStatusInfo) +{ + GS_ASSERT(buddyStatusInfo); + + freeclear(buddyStatusInfo->richStatus); + freeclear(buddyStatusInfo->gameType); + freeclear(buddyStatusInfo->gameVariant); + freeclear(buddyStatusInfo->gameMapName); + ArrayFree(buddyStatusInfo->extendedInfoKeys); + buddyStatusInfo->extendedInfoKeys = NULL; + freeclear(buddyStatusInfo); +} + +GPIBool +gpiCanFreeProfile( + GPIProfile * profile +) +{ + return ((profile && !profile->cache && !profile->buddyStatus && !profile->buddyStatusInfo && !profile->peerSig && !profile->authSig && !profile->blocked)) ? GPITrue:GPIFalse; +} + +void gpiSetInfoCacheFilename( + const char filename[FILENAME_MAX + 1] +) +{ + strzcpy(GPIInfoCacheFilename, filename, sizeof(GPIInfoCacheFilename)); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Blocked List functionality +GPResult +gpiAddToBlockedList( + GPConnection * connection, + int profileid +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIProfile * profile; + int index; + + // Check if profile already in list + /////////////////////////////////// + if(!gpiGetProfile(connection, profileid, &profile)) + { + // It's not, so Add profile + ///////////////////////////////////// + profile = gpiProfileListAdd(connection, profileid); + if(!profile) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + } + else + { + // Already in the list, nix the buddy status if it's a buddy + //////////////////////////////////////////////////////////// + if (profile->buddyStatus) + { + index = profile->buddyStatus->buddyIndex; + freeclear(profile->buddyStatus->statusString); + freeclear(profile->buddyStatus->locationString); + freeclear(profile->buddyStatus); + iconnection->profileList.numBuddies--; + assert(iconnection->profileList.numBuddies >= 0); +#ifndef _PS2 + gpiProfileMap(connection, gpiFixBuddyIndices, (void *)(unsigned long)index); +#else + gpiProfileMap(connection, gpiFixBuddyIndices, (void *)index); +#endif + } + if (profile->buddyStatusInfo) + { + index = profile->buddyStatusInfo->buddyIndex; + freeclear(profile->buddyStatusInfo->richStatus); + freeclear(profile->buddyStatusInfo->gameType); + freeclear(profile->buddyStatusInfo->gameVariant); + freeclear(profile->buddyStatusInfo->gameMapName); + freeclear(profile->buddyStatusInfo); + if (profile->buddyStatusInfo->extendedInfoKeys) + { + ArrayFree(profile->buddyStatusInfo->extendedInfoKeys); + profile->buddyStatusInfo->extendedInfoKeys = NULL; + } + + iconnection->profileList.numBuddies--; + assert(iconnection->profileList.numBuddies >= 0); +#ifndef _PS2 + gpiProfileMap(connection, gpiFixBuddyIndices, (void *)(unsigned long)index); +#else + gpiProfileMap(connection, gpiFixBuddyIndices, (void *)index); +#endif + } + } + + // Set profile as blocked if it wasn't already + ////////////////////////////////////////////// + if (!profile->blocked) + { + profile->blocked = gsi_true; + profile->blockIndex = iconnection->profileList.numBlocked++; + +#ifdef _PS3 + // Only perform if profile isn't already locally blocked and the sync isnt taking place + /////////////////////////////////////////////////////////////////////////////////////// + if (!iconnection->npSyncLock) + gpiAddToNpBlockList(connection, profileid); +#endif + } + + // NOTE: There is no callback for this function simply in order to remain consistent + // with the existing GP structure. If an error occurs, it gets passed to the error callback + // else its considered the request was processed correctly. + + // Add to outgoing buffer - have server handle error check if block already exists (consistency) + ///////////////////////////////////////////////////////////////////////////////////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\addblock\\\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\profileid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profileid); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +static GPIBool +gpiFixBlockIndices( + GPConnection * connection, + GPIProfile * profile, + void * data +) +{ +#ifndef _PS2 + int baseIndex = (int)(unsigned long)data; +#else + int baseIndex = (int)data; +#endif + + GSI_UNUSED(connection); + + if(profile->blocked && (profile->blockIndex > baseIndex)) + profile->blockIndex--; + return GPITrue; +} + +GPResult +gpiRemoveFromBlockedList( + GPConnection * connection, + int profileid +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIProfile * profile; + int index; + + // Grab the profile if already in list + ////////////////////////////////////// + if(gpiGetProfile(connection, profileid, &profile) && profile->blocked) + { + // Set profile as non-blocked + ///////////////////////////// + profile->blocked = gsi_false; + + iconnection->profileList.numBlocked--; + index = profile->blockIndex; + +#ifndef _PS2 + gpiProfileMap(connection, gpiFixBlockIndices, (void *)(unsigned long)index); +#else + gpiProfileMap(connection, gpiFixBlockIndices, (void *)index); +#endif + } + + // NOTE: There is no callback for this function simply in order to remain consistent + // with the existing GP structure. If an error occurs, it gets passed to the error callback + // else its considered the request was processed correctly. + + // Add to outgoing buffer - have server handle error check if block already removed (consistency) + ///////////////////////////////////////////////////////////////////////////////////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\removeblock\\\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\profileid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profileid); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +static GPIBool +gpiCheckForBlock( + GPConnection * connection, + GPIProfile * profile, + void *udata +) +{ + GPIFindProfileData * data = (GPIFindProfileData *)udata; + if (profile->blocked && data->index == profile->blockIndex) + { + data->profile = profile; + return GPIFalse; + } + + GSI_UNUSED(connection); + + return GPITrue; +} + +GPIProfile * +gpiFindBlockedProfile( + GPConnection * connection, + int blockIndex +) +{ + GPIFindProfileData data; + + data.index = blockIndex; + data.profile = NULL; + + gpiProfileMap(connection, gpiCheckForBlock, &data); + + return data.profile; +} + +GPResult +gpiProcessRecvBlockedList( + GPConnection * connection, + const char * input +) +{ + int i=0, j=0; + int num = 0; + int index = 0; + char c; + char *str = NULL; + char buffer[512]; + GPIProfile * profile; + GPProfile profileid; + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Check for an error. + ////////////////////// + if(gpiCheckForError(connection, input, GPITrue)) + return GP_SERVER_ERROR; + + // Process Block List Retrieval msg - Format like: + /* =============================================== + \blk\\list\\final\ + =============================================== */ + + if(!gpiValueForKeyWithIndex(input, "\\blk\\", &index, buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + num = atoi(buffer); + + // Check to make sure list is there + /////////////////////////////////// + str = strstr(input, "\\list\\"); + if (str == NULL) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Then increment index to get ready for parsing + //////////////////////////////////////////////// + str += 6; + index += 6; + + for (i=0; i < num; i++) + { + if (i==0) + { + // Manually grab first profile in list - comma delimiter + //////////////////////////////////////////////////////// + for(j=0 ; (j < sizeof(buffer)) && ((c = str[j]) != '\0') && (c != ',') ; j++) + { + buffer[j] = c; + } + buffer[j] = '\0'; + index += j; + } + else + { + if(!gpiValueForKeyWithIndex(input, ",", &index, buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + } + + profileid = atoi(buffer); + + // Get the profile, adding if needed. + ///////////////////////////////////// + profile = gpiProfileListAdd(connection, profileid); + if(!profile) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Mark as blocked, increment list counter + ////////////////////////////////////////// + profile->blocked = gsi_true; + profile->blockIndex = iconnection->profileList.numBlocked++; + } + + return GP_NO_ERROR; +} diff --git a/code/gamespy/GP/gpiProfile.h b/code/gamespy/GP/gpiProfile.h new file mode 100644 index 00000000..b2b7683d --- /dev/null +++ b/code/gamespy/GP/gpiProfile.h @@ -0,0 +1,229 @@ +/* +gpiProfile.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#ifndef _GPIPROFILE_H_ +#define _GPIPROFILE_H_ + +//INCLUDES +////////// +#include "gpi.h" + +//DEFINES +///////// +#define GPI_SIG_LEN 33 + +//TYPES +/////// +// The status for a buddy profile. +////////////////////////////////// + +// New Status Info +typedef struct _GPIBuddyStatusInfo +{ + int buddyIndex; + GPEnum statusState; + char *richStatus; + char *gameType; + char *gameVariant; + char *gameMapName; + unsigned int sessionFlags; + unsigned int buddyIp; + unsigned short buddyPort; + unsigned int hostIp; + unsigned int hostPrivateIp; + unsigned short queryPort; + unsigned short hostPort; + GPEnum quietModeFlags; + int productId; + // New Status Info extended info Keys + DArray extendedInfoKeys; +} GPIBuddyStatusInfo; + +// Old status +typedef struct +{ + int buddyIndex; + GPEnum status; + char * statusString; + char * locationString; + unsigned int ip; + unsigned short port; + GPEnum quietModeFlags; +} GPIBuddyStatus; + +// Profile data. +//////////////// +typedef struct GPIProfile +{ + int profileId; + int userId; + GPIBuddyStatus * buddyStatus; + GPIBuddyStatusInfo *buddyStatusInfo; + GPIInfoCache * cache; + char * authSig; + int requestCount; + char * peerSig; + gsi_bool blocked; + int blockIndex; + gsi_bool buddyOrBlockCache; +} GPIProfile; + +// A list of profiles. +////////////////////// +typedef struct +{ + HashTable profileTable; + int num; + int numBuddies; + int numBlocked; +} GPIProfileList; + +//FUNCTIONS +/////////// +GPIBool +gpiInitProfiles( + GPConnection * connection +); + +GPIProfile * +gpiProfileListAdd( + GPConnection * connection, + int id +); + +GPIBool +gpiGetProfile( + GPConnection * connection, + GPProfile profileid, + GPIProfile ** pProfile +); + +GPResult +gpiProcessNewProfile( + GPConnection * connection, + GPIOperation * operation, + const char * input +); + +GPResult +gpiNewProfile( + GPConnection * connection, + const char nick[GP_NICK_LEN], + GPEnum replace, + GPEnum blocking, + GPCallback callback, + void * param +); + +GPResult gpiProcessDeleteProfle +( + GPConnection * connection, + GPIOperation * operation, + const char * input +); + +GPResult gpiDeleteProfile( + GPConnection * connection, + GPCallback callback, + void * param +); + +void +gpiRemoveProfile( + GPConnection * connection, + GPIProfile * profile +); + +void +gpiRemoveProfileByID( + GPConnection * connection, + int profileid +); + +GPResult +gpiLoadDiskProfiles( + GPConnection * connection +); + +GPResult +gpiSaveDiskProfiles( + GPConnection * connection +); + +GPResult +gpiFindProfileByUser( + GPConnection * connection, + char nick[GP_NICK_LEN], + char email[GP_EMAIL_LEN], + GPIProfile ** profile +); + +// return false to stop the mapping +typedef GPIBool +(* gpiProfileMapFunc)( + GPConnection * connection, + GPIProfile * profile, + void * data +); + +GPIBool +gpiProfileMap( + GPConnection * connection, + gpiProfileMapFunc func, + void * data +); + +GPIProfile * +gpiFindBuddy( + GPConnection * connection, + int buddyIndex +); + +void gpiRemoveBuddyStatus(GPIBuddyStatus *buddyStatus); +void gpiRemoveBuddyStatusInfo(GPIBuddyStatusInfo *buddyStatusInfo); + +GPIBool +gpiCanFreeProfile( + GPIProfile * profile +); + +void gpiSetInfoCacheFilename( + const char filename[FILENAME_MAX + 1] +); + +// BLOCK LIST +GPResult +gpiAddToBlockedList( + GPConnection * connection, + int profileid +); + +GPResult +gpiRemoveFromBlockedList( + GPConnection * connection, + int profileid +); + +GPIProfile * +gpiFindBlockedProfile( + GPConnection * connection, + int blockIndex +); + +GPResult +gpiProcessRecvBlockedList( + GPConnection * connection, + const char * input +); + +#endif diff --git a/code/gamespy/GP/gpiSearch.c b/code/gamespy/GP/gpiSearch.c new file mode 100644 index 00000000..52b2a1ee --- /dev/null +++ b/code/gamespy/GP/gpiSearch.c @@ -0,0 +1,1656 @@ +/* +gpiSearch.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +//INCLUDES +////////// +#include +#include +#include "gpi.h" + +//DEFINES +///////// +// Search Manager Address. +////////////////////////// +#define GPI_SEARCH_MANAGER_NAME "gpsp." GSI_DOMAIN_NAME +#define GPI_SEARCH_MANAGER_PORT 29901 + + +//GLOBALS +///////// +char GPSearchManagerHostname[64] = GPI_SEARCH_MANAGER_NAME; +//char GPSearchManagerHostname[64] = "localhost"; + +//FUNCTIONS +/////////// +static GPResult +gpiStartProfileSearch( + GPConnection * connection, + GPIOperation * operation +) +{ + GPISearchData * data = (GPISearchData*)operation->data; + int rcode; + struct sockaddr_in address; + struct hostent * host; + + // Initialize the buffer. + ///////////////////////// + data->inputBuffer.size = 4096; + data->inputBuffer.buffer = (char *)gsimalloc((unsigned int)data->inputBuffer.size + 1); + if(data->inputBuffer.buffer == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Create the socket. + ///////////////////// + data->sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(data->sock == INVALID_SOCKET) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error creating a socket."); + + // Make it non-blocking. + //////////////////////// + rcode = SetSockBlocking(data->sock,0); + if(rcode == 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error making a socket non-blocking."); + + // Bind the socket. + /////////////////// + /* + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + rcode = bind(data->sock, (struct sockaddr *)&address, sizeof(struct sockaddr_in)); + if (gsiSocketIsError(rcode)) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error binding a socket."); + */ + + // Get the server host. + /////////////////////// + host = gethostbyname(GPSearchManagerHostname); + if(host == NULL) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "Could not resolve search mananger host name."); + + // Connect the socket. + ////////////////////// + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = *(unsigned int *)host->h_addr_list[0]; + assert(address.sin_addr.s_addr != 0); + address.sin_port = htons(GPI_SEARCH_MANAGER_PORT); + rcode = connect(data->sock, (struct sockaddr *)&address, sizeof(struct sockaddr_in)); + if (gsiSocketIsError(rcode)) + { + int error = GOAGetLastError(data->sock); + if((error != WSAEWOULDBLOCK) && (error != WSAEINPROGRESS) && (error != WSAETIMEDOUT) ) + { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error connecting a socket."); + } + } + + // We're waiting for the connect to complete. + ///////////////////////////////////////////// + operation->state = GPI_CONNECTING; + data->searchStartTime = current_time(); + return GP_NO_ERROR; +} + +static GPResult +gpiInitSearchData( + GPConnection * connection, + GPISearchData ** searchData, + int type +) +{ + GPISearchData * data; + + // Init the data. + ///////////////// + data = (GPISearchData *)gsimalloc(sizeof(GPISearchData)); + if(data == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(data, 0, sizeof(GPISearchData)); + data->type = type; + data->sock = INVALID_SOCKET; + data->inputBuffer.buffer = NULL; + data->inputBuffer.len = 0; + data->inputBuffer.pos = 0; + data->inputBuffer.size = 0; + data->outputBuffer.len = 0; + data->outputBuffer.pos = 0; + data->outputBuffer.size = 4096; + data->outputBuffer.buffer = (char *)gsimalloc((unsigned int)data->outputBuffer.size + 1); + if(data->outputBuffer.buffer == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + data->processing = GPIFalse; + data->remove = GPIFalse; + + *searchData = data; + + return GP_NO_ERROR; +} + +static GPResult +gpiStartSearch( + GPConnection * connection, + GPISearchData * data, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIOperation * operation; + GPIConnection * iconnection = (GPIConnection*)*connection; + + // One more search. + /////////////////// + iconnection->numSearches++; + + // Create a new operation. + ////////////////////////// + CHECK_RESULT(gpiAddOperation(connection, GPI_PROFILE_SEARCH, data, &operation, blocking, callback, param)); + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartProfileSearch(connection, operation)); + + // Process it if blocking. + ////////////////////////// + if(operation->blocking) + CHECK_RESULT(gpiProcess(connection, operation->id)); + + return GP_NO_ERROR; +} + +GPResult +gpiProfileSearch( + GPConnection * connection, + const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char firstname[GP_FIRSTNAME_LEN], + const char lastname[GP_LASTNAME_LEN], + int icquin, + int skip, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPISearchData * data; + + // Error check. + /////////////// + if((nick == NULL) || (*nick == '\0')) + if((email == NULL) || (*email == '\0')) + if((firstname == NULL) || (*firstname == '\0')) + if((lastname == NULL) || (*lastname == '\0')) + if(icquin == 0) + if((uniquenick == NULL) || (*uniquenick == '\0')) + Error(connection, GP_PARAMETER_ERROR, "No search criteria."); + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_PROFILE)); + + // Fill in the data. + //////////////////// + if(nick == NULL) + data->nick[0] = '\0'; + else + strzcpy(data->nick, nick, GP_NICK_LEN); + if(uniquenick == NULL) + data->uniquenick[0] = '\0'; + else + strzcpy(data->uniquenick, uniquenick, GP_UNIQUENICK_LEN); + if(email == NULL) + data->email[0] = '\0'; + else + strzcpy(data->email, email, GP_EMAIL_LEN); + _strlwr(data->email); + if(firstname == NULL) + data->firstname[0] = '\0'; + else + strzcpy(data->firstname, firstname, GP_FIRSTNAME_LEN); + if(lastname == NULL) + data->lastname[0] = '\0'; + else + strzcpy(data->lastname, lastname, GP_LASTNAME_LEN); + data->icquin = icquin; + if(skip < 0) + skip = 0; + data->skip = skip; + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +GPResult +gpiProfileSearchUniquenick( + GPConnection * connection, + const char uniquenick[GP_UNIQUENICK_LEN], + const int namespaceIDs[], + int numNamespaces, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPISearchData * data; + + // Error check. + /////////////// + if((uniquenick == NULL) || (*uniquenick == '\0')) + Error(connection, GP_PARAMETER_ERROR, "No search criteria."); + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_PROFILE_UNIQUENICK)); + + // Fill in the data. + //////////////////// + strzcpy(data->uniquenick, uniquenick, GP_UNIQUENICK_LEN); + if((namespaceIDs != NULL) && (numNamespaces > 0)) + { + data->numNamespaces = min(numNamespaces, GP_MAX_NAMESPACEIDS); + memcpy(data->namespaceIDs, namespaceIDs, sizeof(namespaceIDs[0]) * data->numNamespaces); + } + else + { + data->numNamespaces = 0; + } + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +GPResult +gpiIsValidEmail( + GPConnection * connection, + const char email[GP_EMAIL_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPISearchData * data; + + // Error check. + /////////////// + if((email == NULL) || (*email == '\0') || (strlen(email) >= GP_EMAIL_LEN)) + Error(connection, GP_PARAMETER_ERROR, "Invalid e-mail."); + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_IS_VALID)); + + // Fill in the data. + //////////////////// + strzcpy(data->email, email, GP_EMAIL_LEN); + _strlwr(data->email); + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +GPResult +gpiGetUserNicks( + GPConnection * connection, + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPISearchData * data; + + // Error check. + /////////////// + if((email == NULL) || (*email == '\0') || (strlen(email) >= GP_EMAIL_LEN)) + Error(connection, GP_PARAMETER_ERROR, "Invalid e-mail."); + if((password == NULL) || (strlen(password) >= GP_PASSWORD_LEN)) + Error(connection, GP_PARAMETER_ERROR, "Invalid password."); + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_NICKS)); + + // Fill in the data. + //////////////////// + strzcpy(data->email, email, GP_EMAIL_LEN); + _strlwr(data->email); + strzcpy(data->password, password, GP_PASSWORD_LEN); + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +GPResult +gpiFindPlayers( + GPConnection * connection, + int productID, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPISearchData * data; + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_PLAYERS)); + + // Fill in the data. + //////////////////// + data->productID = productID; + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +GPResult gpiCheckUser( + GPConnection * connection, + const char nick[GP_NICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPISearchData * data; + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_CHECK)); + + // Fill in the data. + //////////////////// + strzcpy(data->email, email, GP_EMAIL_LEN); + _strlwr(data->email); + strzcpy(data->nick, nick, GP_NICK_LEN); + if(password) + strzcpy(data->password, password, GP_PASSWORD_LEN); + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +GPResult gpiNewUser( + GPConnection * connection, + const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + const char cdkey[GP_CDKEY_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPISearchData * data; + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_NEWUSER)); + + // Fill in the data. + //////////////////// + strzcpy(data->email, email, GP_EMAIL_LEN); + strzcpy(data->nick, nick, GP_NICK_LEN); + strzcpy(data->password, password, GP_PASSWORD_LEN); + strzcpy(data->uniquenick, uniquenick, GP_UNIQUENICK_LEN); + if(cdkey) + strzcpy(data->cdkey, cdkey, GP_CDKEY_LEN); + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +GPResult gpiOthersBuddy( + GPConnection * connection, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPISearchData * data; + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_OTHERS_BUDDY)); + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +GPResult gpiOthersBuddyList( + GPConnection * connection, + int *profiles, + int numOfProfiles, + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPISearchData * data; + + + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_OTHERS_BUDDY_LIST)); + + data->revBuddyProfileIds = profiles; + data->numOfRevBuddyProfiles = numOfProfiles; + + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +GPResult gpiSuggestUniqueNick( + GPConnection * connection, + const char desirednick[GP_NICK_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPISearchData * data; + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_SUGGEST_UNIQUE)); + + // Fill in the data. + //////////////////// + strzcpy(data->uniquenick, desirednick, GP_UNIQUENICK_LEN); + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +static GPResult +gpiProcessSearch( + GPConnection * connection, + GPIOperation * operation +) +{ + int state; + GPISearchData * data; + char key[512]; + char value[512]; + GPIBool done; + int index; + int oldIndex; + GPIBool loop; + GPIBool more; + GPICallback callback; + GPIConnection * iconnection = (GPIConnection*)*connection; + int len; + GPIBool connClosed; + GPResult result; + void * tempPtr; + GPIBool doneParsingMatch; + int rcode; + int pid; + GPProfileSearchMatch * match; + GPUniqueMatch *uniqueNickMatch; + + //password encryption stuff + char passwordenc[GP_PASSWORDENC_LEN]; + + // Get a pointer to the data. + ///////////////////////////// + data = (GPISearchData*)operation->data; + + // Loop if blocking. + //////////////////// + if(operation->blocking) + loop = GPITrue; + else + loop = GPIFalse; + + if (!operation->blocking && (current_time() - data->searchStartTime > GPI_SEARCH_TIMEOUT)) + { + data->remove = GPITrue; + CallbackError(connection, GP_NETWORK_ERROR, GP_SEARCH_TIMED_OUT, "The search timed out"); + } + + do + { + // Send anything that needs to be sent. + /////////////////////////////////////// + CHECK_RESULT(gpiSendFromBuffer(connection, data->sock, &data->outputBuffer, &connClosed, GPITrue, "SM")); + + // Is it connecting? + //////////////////// + if(operation->state == GPI_CONNECTING) + { + // Check the connect state. + /////////////////////////// + CHECK_RESULT(gpiCheckSocketConnect(connection, data->sock, &state)); + + // Check for a failed attempt. + ////////////////////////////// + if(state == GPI_DISCONNECTED) + CallbackError(connection, GP_SERVER_ERROR, GP_SEARCH_CONNECTION_FAILED, "Could not connect to the search manager."); + + // Check if finished connecting. + //////////////////////////////// + if(state == GPI_CONNECTED) + { + // Send a request based on type. + //////////////////////////////// + if(data->type == GPI_SEARCH_PROFILE) + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\search\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\profileid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->profileid); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\namespaceid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->namespaceID); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\partnerid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->partnerID); + if(data->nick[0] != '\0') + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\nick\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->nick); + } + if(data->uniquenick[0] != '\0') + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\uniquenick\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->uniquenick); + } + if(data->email[0] != '\0') + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\email\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->email); + } + if(data->firstname[0] != '\0') + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\firstname\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->firstname); + } + if(data->lastname[0] != '\0') + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\lastname\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->lastname); + } + if(data->icquin != 0) + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\icquin\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, data->icquin); + } + if(data->skip > 0) + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\skip\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, data->skip); + } + } + else if(data->type == GPI_SEARCH_PROFILE_UNIQUENICK) + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\searchunique\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\profileid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->profileid); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\uniquenick\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->uniquenick); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\namespaces\\"); + if(data->numNamespaces > 0) + { + int i; + for(i = 0 ; i < data->numNamespaces ; i++) + { + if(i > 0) + gpiAppendCharToBuffer(connection, &data->outputBuffer, ','); + gpiAppendIntToBuffer(connection, &data->outputBuffer, data->namespaceIDs[i]); + } + } + else + { + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->namespaceID); + } + } + else if(data->type == GPI_SEARCH_IS_VALID) + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\valid\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\email\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->email); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\partnerid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->partnerID); + } + else if(data->type == GPI_SEARCH_NICKS) + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\nicks\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\email\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->email); + + gpiEncodeString(data->password, passwordenc); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\passenc\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, passwordenc); + + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\namespaceid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->namespaceID); + + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\partnerid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->partnerID); + } + else if(data->type == GPI_SEARCH_PLAYERS) + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\pmatch\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\profileid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->profileid); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\productid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, data->productID); + } + else if(data->type == GPI_SEARCH_CHECK) + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\check\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\nick\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->nick); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\email\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->email); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\partnerid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->partnerID); + gpiEncodeString(data->password, passwordenc); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\passenc\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, passwordenc); + } + else if(data->type == GPI_SEARCH_NEWUSER) + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\newuser\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\nick\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->nick); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\email\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->email); + gpiEncodeString(data->password, passwordenc); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\passenc\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, passwordenc); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\productID\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->productID); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\namespaceid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->namespaceID); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\uniquenick\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->uniquenick); + if(data->cdkey[0]) + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\cdkey\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->cdkey); + } + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\partnerid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->partnerID); + } + else if(data->type == GPI_SEARCH_OTHERS_BUDDY) + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\others\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\profileid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->profileid); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\namespaceid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->namespaceID); + } + else if(data->type == GPI_SEARCH_OTHERS_BUDDY_LIST) + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\otherslist\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\profileid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->profileid); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\numopids\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, data->numOfRevBuddyProfiles); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\opids\\"); + if (data->revBuddyProfileIds) + { + int i; + + gpiAppendIntToBuffer(connection, &data->outputBuffer, data->revBuddyProfileIds[0]); + + for (i = 1; i < data->numOfRevBuddyProfiles; i++) + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "|"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, data->revBuddyProfileIds[i]); + } + } + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\namespaceid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->namespaceID); + } + else if(data->type == GPI_SEARCH_SUGGEST_UNIQUE) + { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\uniquesearch\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\preferrednick\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->uniquenick); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\namespaceid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, iconnection->namespaceID); + } + else + { + assert(0); + } + + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\gamename\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, __GSIACGamename); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\final\\"); + + // Update the state. + //////////////////// + operation->state = GPI_WAITING; + } + } + // Is it waiting? + ///////////////// + else if(operation->state == GPI_WAITING) + { + // Read from the socket. + //////////////////////// + result = gpiRecvToBuffer(connection, data->sock, &data->inputBuffer, &len, &connClosed, "SM"); + if(result != GP_NO_ERROR) + { + if(result == GP_NETWORK_ERROR) + CallbackError(connection, GP_NETWORK_ERROR, GP_SEARCH_CONNECTION_FAILED, "There was an error reading from the server."); + return result; + } + if (operation->blocking && (current_time() - data->searchStartTime > GPI_SEARCH_TIMEOUT)) + { + data->remove = GPITrue; + CallbackError(connection, GP_NETWORK_ERROR, GP_SEARCH_TIMED_OUT, "The search timed out"); + } + // Is this the end of the response? + /////////////////////////////////// + if(strstr(data->inputBuffer.buffer, "\\final\\") != NULL) + { + // Reset the index. + /////////////////// + index = 0; + + // This operation is finishing up. + ////////////////////////////////// + operation->state = GPI_FINISHING; + + // Check for an error. + ////////////////////// + if(gpiCheckForError(connection, data->inputBuffer.buffer, GPITrue)) + { + data->remove = GPITrue; + return GP_SERVER_ERROR; + } + + // Process it based on type. + //////////////////////////// + if((data->type == GPI_SEARCH_PROFILE) || (data->type == GPI_SEARCH_PROFILE_UNIQUENICK)) + { + GPProfileSearchResponseArg arg; + // Start setting up the arg. + //////////////////////////// + arg.result = GP_NO_ERROR; + arg.numMatches = 0; + arg.matches = NULL; + arg.more = GP_DONE; + + // Parse the message. + ///////////////////// + done = GPIFalse; + do + { + // Read the next key and value. + /////////////////////////////// + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + + // Is the list done? + //////////////////// + if(strcmp(key, "bsrdone") == 0) + { + // Check for more. + ////////////////// + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + if(strcmp(key, "more") == 0) + { + // Make sure there are actually more. + ///////////////////////////////////// + if(strcmp(value, "0") != 0) + arg.more = GP_MORE; + } + + // Done. + //////// + done = GPITrue; + } + else if(strcmp(key, "bsr") == 0) + { + // Create a new match. + ////////////////////// + arg.numMatches++; + arg.matches = (GPProfileSearchMatch *)gsirealloc(arg.matches, sizeof(GPProfileSearchMatch) * arg.numMatches); + if(arg.matches == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + match = &arg.matches[arg.numMatches - 1]; + memset(match, 0, sizeof(GPProfileSearchMatch)); + + // Get the profile id. + ////////////////////// + match->profile = atoi(value); + + // PANTS|05.16.00 + // Changed to be order independent, and ignore unrecognized keys. + ///////////////////////////////////////////////////////////////// + + // Read key/value pairs. + //////////////////////// + doneParsingMatch = GPIFalse; + do + { + // Read the next key/value. + /////////////////////////// + oldIndex = index; + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + + // Set the field based on the key. + ////////////////////////////////// +#ifndef GSI_UNICODE + if(strcmp(key, "nick") == 0) + strzcpy(match->nick, value, GP_NICK_LEN); + else if(strcmp(key, "uniquenick") == 0) + strzcpy(match->uniquenick, value, GP_UNIQUENICK_LEN); + else if(strcmp(key, "namespaceid") == 0) + match->namespaceID = atoi(value); + else if(strcmp(key, "firstname") == 0) + strzcpy(match->firstname, value, GP_FIRSTNAME_LEN); + else if(strcmp(key, "lastname") == 0) + strzcpy(match->lastname, value, GP_LASTNAME_LEN); + else if(strcmp(key, "email") == 0) + strzcpy(match->email, value, GP_EMAIL_LEN); + else if((strcmp(key, "bsr") == 0) || (strcmp(key, "bsrdone") == 0)) + { + doneParsingMatch = GPITrue; + index = oldIndex; + } +#else + if(strcmp(key, "nick") == 0) + UTF8ToUCS2StringLen(value, match->nick, GP_NICK_LEN); + else if(strcmp(key, "uniquenick") == 0) + UTF8ToUCS2StringLen(value, match->uniquenick, GP_UNIQUENICK_LEN); + else if(strcmp(key, "namespaceid") == 0) + match->namespaceID = atoi(value); + else if(strcmp(key, "firstname") == 0) + UTF8ToUCS2StringLen(value, match->firstname, GP_FIRSTNAME_LEN); + else if(strcmp(key, "lastname") == 0) + UTF8ToUCS2StringLen(value, match->lastname, GP_LASTNAME_LEN); + else if(strcmp(key, "email") == 0) + UTF8ToUCS2StringLen(value, match->email, GP_EMAIL_LEN); + else if((strcmp(key, "bsr") == 0) || (strcmp(key, "bsrdone") == 0)) + { + doneParsingMatch = GPITrue; + index = oldIndex; + } +#endif + } + while(!doneParsingMatch); + } + else + { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading from the search server."); + } + } while(!done); + + // Save the more state. + /////////////////////// + more = (GPIBool)arg.more; + + // Get the callback. + //////////////////// + callback = operation->callback; + + + // Call the callback. + ///////////////////// + if(callback.callback != NULL) + callback.callback(connection, &arg, callback.param); + + // Start a new operation if they want more matches. + /////////////////////////////////////////////////// + if((more == GP_MORE) && (arg.more == GP_MORE)) + CHECK_RESULT(gpiProfileSearch(connection, data->nick, data->uniquenick, data->email, data->firstname, data->lastname, data->icquin, arg.numMatches + data->skip, (GPEnum)operation->blocking, operation->callback.callback, operation->callback.param)); + + // We're done. + ////////////// + freeclear(arg.matches); + } + else if(data->type == GPI_SEARCH_IS_VALID) + { + callback = operation->callback; + if(callback.callback != NULL) + { + GPIsValidEmailResponseArg * arg; + + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + if(strcmp(key, "vr") != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading from the search server."); + + // Setup the arg. + ///////////////// + arg = (GPIsValidEmailResponseArg *)gsimalloc(sizeof(GPIsValidEmailResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->result = GP_NO_ERROR; +#ifndef GSI_UNICODE + strzcpy(arg->email, data->email, GP_EMAIL_LEN); +#else + UTF8ToUCS2String(data->email, arg->email); +#endif + if(value[0] == '0') + arg->isValid = GP_INVALID; + else + arg->isValid = GP_VALID; + + // Add the callback. + //////////////////// + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + } + } + else if(data->type == GPI_SEARCH_NICKS) + { + callback = operation->callback; + if(callback.callback != NULL) + { + GPGetUserNicksResponseArg * arg; + + // Setup the arg. + ///////////////// + arg = (GPGetUserNicksResponseArg *)gsimalloc(sizeof(GPGetUserNicksResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->result = GP_NO_ERROR; +#ifndef GSI_UNICODE + strcpy(arg->email, data->email); +#else + UTF8ToUCS2String(data->email, arg->email); +#endif + arg->numNicks = 0; + arg->nicks = NULL; + arg->uniquenicks = NULL; + + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + if(strcmp(key, "nr") != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading from the search server."); + + // Get the nicks. + ///////////////// + done = GPIFalse; + do + { + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + if(strcmp(key, "nick") == 0) + { + // Add it. + ////////// +#ifndef GSI_UNICODE + tempPtr = gsirealloc(arg->nicks, sizeof(char *) * (arg->numNicks + 1)); + if(tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->nicks = (char **)tempPtr; + tempPtr = gsimalloc(GP_NICK_LEN); + if(tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->nicks[arg->numNicks] = (gsi_char*)tempPtr; + strzcpy(arg->nicks[arg->numNicks], value, GP_NICK_LEN); + arg->numNicks++; +#else + tempPtr = gsirealloc(arg->nicks, sizeof(unsigned short *) * (arg->numNicks + 1)); + if(tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->nicks = (unsigned short **)tempPtr; + tempPtr = gsimalloc(GP_NICK_LEN * sizeof(unsigned short)); + if(tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->nicks[arg->numNicks] = (gsi_char*)tempPtr; + UTF8ToUCS2StringLen(value, arg->nicks[arg->numNicks], GP_NICK_LEN); + arg->numNicks++; +#endif + } + else if(strcmp(key, "uniquenick") == 0) + { + if(arg->numNicks <= 0) + continue; + + // Add it. + ////////// +#ifndef GSI_UNICODE + tempPtr = gsirealloc(arg->uniquenicks, sizeof(char *) * arg->numNicks); + if(tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->uniquenicks = (char **)tempPtr; + tempPtr = gsimalloc(GP_UNIQUENICK_LEN); + if(tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->uniquenicks[arg->numNicks - 1] = (gsi_char*)tempPtr; + strzcpy(arg->uniquenicks[arg->numNicks - 1], value, GP_UNIQUENICK_LEN); +#else + tempPtr = gsirealloc(arg->uniquenicks, sizeof(unsigned short *) * arg->numNicks); + if(tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->uniquenicks = (unsigned short **)tempPtr; + tempPtr = gsimalloc(GP_UNIQUENICK_LEN * sizeof(unsigned short)); + if(tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->uniquenicks[arg->numNicks - 1] = (gsi_char*)tempPtr; + UTF8ToUCS2StringLen(value, arg->uniquenicks[arg->numNicks - 1], GP_UNIQUENICK_LEN); +#endif + } + else if(strcmp(key, "ndone") == 0) + { + // Done. + //////// + done = GPITrue; + } + else + { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading from the search server."); + } + } + while(!done); + + // Do it. + ///////// + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, GPI_ADD_NICKS)); + } + } + else if(data->type == GPI_SEARCH_PLAYERS) + { + callback = operation->callback; + if(callback.callback != NULL) + { + GPFindPlayersResponseArg * arg; + GPFindPlayerMatch * match; + + // Start setting up the arg. + //////////////////////////// + arg = (GPFindPlayersResponseArg *)gsimalloc(sizeof(GPFindPlayersResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->productID = data->productID; + arg->result = GP_NO_ERROR; + arg->numMatches = 0; + arg->matches = NULL; + + // Parse the message. + ///////////////////// + done = GPIFalse; + do + { + // Read the next key and value. + /////////////////////////////// + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + + // Is the list done? + //////////////////// + if(strcmp(key, "psrdone") == 0) + { + // Done. + //////// + done = GPITrue; + } + else if(strcmp(key, "psr") == 0) + { + // Create a new match. + ////////////////////// + arg->numMatches++; + arg->matches = (GPFindPlayerMatch *)gsirealloc(arg->matches, sizeof(GPFindPlayerMatch) * arg->numMatches); + if(arg->matches == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + match = &arg->matches[arg->numMatches - 1]; + memset(match, 0, sizeof(GPFindPlayerMatch)); + match->status = GP_ONLINE; + + // Get the profile id. + ////////////////////// + match->profile = atoi(value); + + // PANTS|05.16.00 + // Changed to be order independent, and ignore unrecognized keys. + ///////////////////////////////////////////////////////////////// + + // Read key/value pairs. + //////////////////////// + doneParsingMatch = GPIFalse; + do + { + // Read the next key/value. + /////////////////////////// + oldIndex = index; + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + +#ifndef GSI_UNICODE + // Set the field based on the key. + ////////////////////////////////// + if(strcmp(key, "status") == 0) + strzcpy(match->statusString, value, GP_STATUS_STRING_LEN); + else if(strcmp(key, "nick") == 0) + strzcpy(match->nick, value, GP_NICK_LEN); + if(strcmp(key, "statuscode") == 0) + match->status = (GPEnum)atoi(value); + else if((strcmp(key, "psr") == 0) || (strcmp(key, "psrdone") == 0)) + { + doneParsingMatch = GPITrue; + index = oldIndex; + } +#else + // Set the field based on the key. + ////////////////////////////////// + if(strcmp(key, "status") == 0) + UTF8ToUCS2StringLen(value, match->statusString, GP_STATUS_STRING_LEN); + else if(strcmp(key, "nick") == 0) + UTF8ToUCS2StringLen(value, match->nick, GP_NICK_LEN); + if(strcmp(key, "statuscode") == 0) + match->status = (GPEnum)atoi(value); + else if((strcmp(key, "psr") == 0) || (strcmp(key, "psrdone") == 0)) + { + doneParsingMatch = GPITrue; + index = oldIndex; + } +#endif + } + while(!doneParsingMatch); + } + else + { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading from the search server."); + } + } while(!done); + + // Do it. + ///////// + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, GPI_ADD_PMATCH)); + } + } + else if(data->type == GPI_SEARCH_CHECK) + { + callback = operation->callback; + if(callback.callback != NULL) + { + GPCheckResponseArg * arg; + + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + if(strcmp(key, "cur") != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading from the search server."); + + rcode = atoi(value); + if(rcode) + { + iconnection->errorCode = (GPErrorCode)rcode; + pid = 0; + } + else + { + if(!gpiValueForKey(data->inputBuffer.buffer, "\\pid\\", value, sizeof(value))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading from the search server."); + pid = atoi(value); + } + + // Setup the arg. + ///////////////// + arg = (GPCheckResponseArg *)gsimalloc(sizeof(GPCheckResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->result = (GPResult)rcode; + arg->profile = pid; + + // Add the callback. + //////////////////// + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + } + } + else if(data->type == GPI_SEARCH_NEWUSER) + { + callback = operation->callback; + if(callback.callback != NULL) + { + GPNewUserResponseArg * arg; + + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + if(strcmp(key, "nur") != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading from the search server."); + + rcode = atoi(value); + if(rcode) + iconnection->errorCode = (GPErrorCode)rcode; + if(!gpiValueForKey(data->inputBuffer.buffer, "\\pid\\", value, sizeof(value))) + { + if(rcode == 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading from the search server."); + pid = 0; + } + else + pid = atoi(value); + + // Setup the arg. + ///////////////// + arg = (GPNewUserResponseArg *)gsimalloc(sizeof(GPNewUserResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->result = (GPResult)rcode; + arg->profile = pid; + + // Add the callback. + //////////////////// + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + } + } + else if(data->type == GPI_SEARCH_OTHERS_BUDDY) + { + callback = operation->callback; + if(callback.callback != NULL) + { + GPGetReverseBuddiesResponseArg * arg; + + // Setup the arg. + ///////////////// + arg = (GPGetReverseBuddiesResponseArg *)gsimalloc(sizeof(GPGetReverseBuddiesResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->result = GP_NO_ERROR; + arg->numProfiles = 0; + arg->profiles = NULL; + + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + if(strcmp(key, "others") != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading from the search server."); + + // Get the profiles. + ///////////////// + done = GPIFalse; + do + { + // Read the next key and value. + /////////////////////////////// + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + + // Is the list done? + //////////////////// + if(strcmp(key, "odone") == 0) + { + // Done. + //////// + done = GPITrue; + } + else if(strcmp(key, "o") == 0) + { + // Add it. + ////////// + tempPtr = gsirealloc(arg->profiles, sizeof(GPProfileSearchMatch) * (arg->numProfiles + 1)); + if(tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->profiles = (GPProfileSearchMatch *)tempPtr; + match = &arg->profiles[arg->numProfiles]; + memset(match, 0, sizeof(GPProfileSearchMatch)); + arg->numProfiles++; + + // Get the profile id. + ////////////////////// + match->profile = atoi(value); + + // Read key/value pairs. + //////////////////////// + doneParsingMatch = GPIFalse; + do + { + // Read the next key/value. + /////////////////////////// + oldIndex = index; + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + +#ifndef GSI_UNICODE + // Set the field based on the key. + ////////////////////////////////// + if(strcmp(key, "nick") == 0) + strzcpy(match->nick, value, GP_NICK_LEN); + else if(strcmp(key, "uniquenick") == 0) + strzcpy(match->uniquenick, value, GP_UNIQUENICK_LEN); + else if(strcmp(key, "first") == 0) + strzcpy(match->firstname, value, GP_FIRSTNAME_LEN); + else if(strcmp(key, "last") == 0) + strzcpy(match->lastname, value, GP_LASTNAME_LEN); + else if(strcmp(key, "email") == 0) + strzcpy(match->email, value, GP_EMAIL_LEN); + else if((strcmp(key, "o") == 0) || (strcmp(key, "odone") == 0)) + { + doneParsingMatch = GPITrue; + index = oldIndex; + } +#else + // Set the field based on the key. + ////////////////////////////////// + if(strcmp(key, "nick") == 0) + UTF8ToUCS2StringLen(value, match->nick, GP_NICK_LEN); + else if(strcmp(key, "uniquenick") == 0) + UTF8ToUCS2StringLen(value, match->uniquenick, GP_UNIQUENICK_LEN); + else if(strcmp(key, "first") == 0) + UTF8ToUCS2StringLen(value, match->firstname, GP_FIRSTNAME_LEN); + else if(strcmp(key, "last") == 0) + UTF8ToUCS2StringLen(value, match->lastname, GP_LASTNAME_LEN); + else if(strcmp(key, "email") == 0) + UTF8ToUCS2StringLen(value, match->email, GP_EMAIL_LEN); + else if((strcmp(key, "o") == 0) || (strcmp(key, "odone") == 0)) + { + doneParsingMatch = GPITrue; + index = oldIndex; + } +#endif + } + while(!doneParsingMatch); + } + else + { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading from the search server."); + } + } + while(!done); + + // Do it. + ///////// + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, GPI_ADD_REVERSE_BUDDIES)); + } + } + else if (data->type == GPI_SEARCH_OTHERS_BUDDY_LIST) + { + callback = operation->callback; + if(callback.callback != NULL) + { + GPGetReverseBuddiesListResponseArg * arg; + + // Setup the arg. + ///////////////// + arg = (GPGetReverseBuddiesListResponseArg *)gsimalloc(sizeof(GPGetReverseBuddiesListResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->result = GP_NO_ERROR; + arg->numOfUniqueMatchs = 0; + arg->matches = NULL; + + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + if(strcmp(key, "otherslist") != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading from the search server."); + + // Get the profiles. + ///////////////// + done = GPIFalse; + do + { + // Read the next key and value. + /////////////////////////////// + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + + // Is the list done? + //////////////////// + if(strcmp(key, "oldone") == 0) + { + // Done. + //////// + done = GPITrue; + } + else if(strcmp(key, "o") == 0) + { + // Add it. + ////////// + tempPtr = gsirealloc(arg->matches, sizeof(GPUniqueMatch) * (arg->numOfUniqueMatchs + 1)); + if(tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->matches = (GPUniqueMatch *)tempPtr; + uniqueNickMatch = &arg->matches[arg->numOfUniqueMatchs]; + memset(uniqueNickMatch, 0, sizeof(GPUniqueMatch)); + arg->numOfUniqueMatchs++; + + // Get the profile id. + ////////////////////// + uniqueNickMatch->profile = atoi(value); + + // Read key/value pairs. + //////////////////////// + doneParsingMatch = GPIFalse; + do + { + // Read the next key/value. + /////////////////////////// + oldIndex = index; + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + +#ifndef GSI_UNICODE + // Set the field based on the key. + ////////////////////////////////// + if(strcmp(key, "uniquenick") == 0) + strzcpy(uniqueNickMatch->uniqueNick, value, GP_UNIQUENICK_LEN); + else if((strcmp(key, "o") == 0) || (strcmp(key, "oldone") == 0)) + { + doneParsingMatch = GPITrue; + index = oldIndex; + } +#else + // Set the field based on the key. + ////////////////////////////////// + if(strcmp(key, "uniquenick") == 0) + UTF8ToUCS2StringLen(value, uniqueNickMatch->uniqueNick, GP_UNIQUENICK_LEN); + else if((strcmp(key, "o") == 0) || (strcmp(key, "oldone") == 0)) + { + doneParsingMatch = GPITrue; + index = oldIndex; + } +#endif + } + while(!doneParsingMatch); + } + else + { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading from the search server."); + } + } + while(!done); + + // Do it. + ///////// + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, GPI_ADD_REVERSE_BUDDIES_LIST)); + } + + } + else if(data->type == GPI_SEARCH_SUGGEST_UNIQUE) + { + callback = operation->callback; + if(callback.callback != NULL) + { + int count = 0; + GPSuggestUniqueNickResponseArg * arg; + + // Setup the arg. + ///////////////// + arg = (GPSuggestUniqueNickResponseArg *)gsimalloc(sizeof(GPSuggestUniqueNickResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->result = GP_NO_ERROR; + arg->numSuggestedNicks = 0; + arg->suggestedNicks = NULL; + + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + if(strcmp(key, "us") != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading from the search server."); + arg->numSuggestedNicks = atoi(value); + + // Allocate memory for the nick array. + ////////////////////////////////////// + arg->suggestedNicks = (gsi_char **)gsimalloc(sizeof(gsi_char *) * arg->numSuggestedNicks); + if(!arg->suggestedNicks) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Get the nicks. + ///////////////// + done = GPIFalse; + do + { + CHECK_RESULT(gpiReadKeyAndValue(connection, data->inputBuffer.buffer, &index, key, value)); + if(strcmp(key, "nick") == 0) + { + // Add it. + ////////// +#ifndef GSI_UNICODE + arg->suggestedNicks[count] = gsimalloc(GP_UNIQUENICK_LEN); + if(arg->suggestedNicks[count] == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + strzcpy(arg->suggestedNicks[count], value, GP_UNIQUENICK_LEN); +#else + arg->suggestedNicks[count] = (unsigned short*)gsimalloc(GP_UNIQUENICK_LEN * sizeof(unsigned short)); + if(arg->suggestedNicks[count] == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + UTF8ToUCS2StringLen(value, arg->suggestedNicks[count], GP_UNIQUENICK_LEN); +#endif + count++; + } + else if(strcmp(key, "usdone") == 0) + { + // Check that the header matches the actual number of nicks. + //////////////////////////////////////////////////////////// + assert(count == arg->numSuggestedNicks); + arg->numSuggestedNicks = count; + + // Done. + //////// + done = GPITrue; + } + else + { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading from the search server."); + } + } + while(!done); + + // Do it. + ///////// + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, GPI_ADD_SUGGESTED_UNIQUE)); + } + } + else + { + assert(0); + } + + // Flag the operation for removal. + ////////////////////////////////// + data->remove = GPITrue; + + // If we're looping, stop. + ////////////////////////// + loop = GPIFalse; + } + } + //PANTS|05.23.00 - removed sleep + //crt - added it back 6/13/00 + //PANTS|07.10.00 - only sleep if looping + if(loop) + msleep(10); + } while(loop); + + + + return GP_NO_ERROR; +} + +GPResult +gpiProcessSearches( + GPConnection * connection +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIOperation ** searchList; + GPIOperation * operation; + GPISearchData * data; + GPResult result; + int num = 0; + int i; + + // Are there any searches? + ////////////////////////// + if(iconnection->numSearches > 0) + { + // Alloc mem for a search list. + /////////////////////////////// + searchList = (GPIOperation **)gsimalloc(sizeof(GPIOperation *) * iconnection->numSearches); + if(searchList == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Create the search list. + ////////////////////////// + for(operation = &iconnection->operationList[0] ; operation != NULL ; operation = operation->pnext) + { + // Is this a search? + //////////////////// + if((operation->type == GPI_PROFILE_SEARCH) && (operation->state != GPI_FINISHING)) + { + // Is this search being processed already? + ////////////////////////////////////////// + if(!((GPISearchData *)operation->data)->processing) + { + assert(num < iconnection->numSearches); + searchList[num++] = operation; + ((GPISearchData *)operation->data)->processing = GPITrue; + } + } + } + + // Process the searches. + //////////////////////// + for(i = 0 ; i < num ; i++) + { + result = gpiProcessSearch(connection, searchList[i]); + if(result != GP_NO_ERROR) + searchList[i]->result = result; + } + + // Clear the processing flags, and remove searches that are done. + ///////////////////////////////////////////////////////////////// + for(i = 0 ; i < num ; i++) + { + data = ((GPISearchData *)searchList[i]->data); + data->processing = GPIFalse; + if(data->remove) + gpiRemoveOperation(connection, searchList[i]); + } + + freeclear(searchList); + } + + return GP_NO_ERROR; +} diff --git a/code/gamespy/GP/gpiSearch.h b/code/gamespy/GP/gpiSearch.h new file mode 100644 index 00000000..13212588 --- /dev/null +++ b/code/gamespy/GP/gpiSearch.h @@ -0,0 +1,172 @@ +/* +gpiSearch.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#ifndef _GPISEARCH_H_ +#define _GPISEARCH_H_ + +//INCLUDES +////////// +#include "gpi.h" + +//TYPES +/////// +#define GPI_SEARCH_PROFILE 1 +#define GPI_SEARCH_IS_VALID 2 +#define GPI_SEARCH_NICKS 3 +#define GPI_SEARCH_PLAYERS 4 +#define GPI_SEARCH_CHECK 5 +#define GPI_SEARCH_NEWUSER 6 +#define GPI_SEARCH_OTHERS_BUDDY 7 +#define GPI_SEARCH_SUGGEST_UNIQUE 8 +#define GPI_SEARCH_OTHERS_BUDDY_LIST 9 +#define GPI_SEARCH_PROFILE_UNIQUENICK 10 + +// A timeout used to abort searches taking too long +#define GPI_SEARCH_TIMEOUT 60000 + +// Profile Search operation data. +///////////////////////////////// +typedef struct +{ + int type; + SOCKET sock; + GPIBuffer inputBuffer; + GPIBuffer outputBuffer; + char nick[GP_NICK_LEN]; + char uniquenick[GP_UNIQUENICK_LEN]; + int namespaceIDs[GP_MAX_NAMESPACEIDS]; + int numNamespaces; + char email[GP_EMAIL_LEN]; + char firstname[GP_FIRSTNAME_LEN]; + char lastname[GP_LASTNAME_LEN]; + char password[GP_PASSWORD_LEN]; + char cdkey[GP_CDKEY_LEN]; + int partnerID; + int icquin; + int skip; + int productID; + GPIBool processing; + GPIBool remove; + gsi_time searchStartTime; + int *revBuddyProfileIds; + int numOfRevBuddyProfiles; +} GPISearchData; + +//FUNCTIONS +/////////// +GPResult +gpiProfileSearch( + GPConnection * connection, + const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char firstname[GP_FIRSTNAME_LEN], + const char lastname[GP_LASTNAME_LEN], + int icquin, + int skip, + GPEnum blocking, + GPCallback callback, + void * param +); + +GPResult +gpiProfileSearchUniquenick( + GPConnection * connection, + const char uniquenick[GP_UNIQUENICK_LEN], + const int namespaceIDs[], + int numNamespaces, + GPEnum blocking, + GPCallback callback, + void * param +); + +GPResult +gpiIsValidEmail( + GPConnection * connection, + const char email[GP_EMAIL_LEN], + GPEnum blocking, + GPCallback callback, + void * param +); + +GPResult +gpiGetUserNicks( + GPConnection * connection, + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + GPEnum blocking, + GPCallback callback, + void * param +); + +GPResult +gpiFindPlayers( + GPConnection * connection, + int productID, + GPEnum blocking, + GPCallback callback, + void * param +); + +GPResult gpiCheckUser( + GPConnection * connection, + const char nick[GP_NICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + GPEnum blocking, + GPCallback callback, + void * param +); + +GPResult gpiNewUser( + GPConnection * connection, + const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + const char cdkey[GP_CDKEY_LEN], + GPEnum blocking, + GPCallback callback, + void * param +); + +GPResult gpiOthersBuddy( + GPConnection * connection, + GPEnum blocking, + GPCallback callback, + void * param +); + +GPResult gpiOthersBuddyList( + GPConnection * connection, + int *profiles, + int numOfProfiles, + GPEnum blocking, + GPCallback callback, + void * param +); + +GPResult gpiSuggestUniqueNick( + GPConnection * connection, + const char desirednick[GP_NICK_LEN], + GPEnum blocking, + GPCallback callback, + void * param +); + +GPResult +gpiProcessSearches( + GPConnection * connection +); + +#endif diff --git a/code/gamespy/GP/gpiTransfer.c b/code/gamespy/GP/gpiTransfer.c new file mode 100644 index 00000000..ce6132cb --- /dev/null +++ b/code/gamespy/GP/gpiTransfer.c @@ -0,0 +1,2000 @@ +/* +gpiTransfer.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +//INCLUDES +////////// +#include +#ifdef _WIN32 +#include +#endif +#include +#include "gpi.h" + +#define GPI_TRANSFER_VERSION 1 +#define GPI_PEER_TIMEOUT_TIME (1 * 60000) +#define GPI_KEEPALIVE_TIME (4 * 60000) + +//#define GPI_ACKNOWLEDGED_WINDOW (100000 * 1024) +#define GPI_DATA_SIZE (1 * 1024) +//#define GPI_CONFIRM_FILES + +//FUNCTIONS +/////////// +#ifndef NOFILE +static void gpiTransferFree(void * elem) +{ + GPITransfer * transfer = (GPITransfer *)elem; + + if(transfer->message) + freeclear(transfer->message); + + if(transfer->baseDirectory) + gsifree(transfer->baseDirectory); + + if(transfer->files) + { + ArrayFree(transfer->files); + transfer->files = NULL; + } +} + +static int gpiTransferCompare(const void * elem1, const void * elem2) +{ + GPITransfer * transfer1 = (GPITransfer *)elem1; + GPITransfer * transfer2 = (GPITransfer *)elem2; + + return (transfer1->localID - transfer2->localID); +} + +static void gpiFreeFile(void * elem) +{ + GPIFile * file = (GPIFile *)elem; + gsifree(file->path); + gsifree(file->name); + +#ifdef GSI_UNICODE + gsifree(file->path_W); + gsifree(file->name_W); +#endif +} + +static GPResult +gpiFinishTransferMessage( + GPConnection * connection, + GPITransfer * transfer, + const char * message, + int len +) +{ + GPResult result = gpiPeerFinishTransferMessage(connection, transfer->peer, message, len); + + transfer->lastSend = current_time(); + + return result; +} + +GPResult gpiInitTransfers( + GPConnection * connection +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + iconnection->transfers = ArrayNew(sizeof(GPITransfer), 0, gpiTransferFree); + if(!iconnection->transfers) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + return GP_NO_ERROR; +} + +void gpiCleanupTransfers( + GPConnection * connection +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Free the transfers. + ////////////////////// + if(iconnection->transfers) + { + ArrayFree(iconnection->transfers); + iconnection->transfers = NULL; + } +} + +static GPResult gpiNewTransfer +( + GPConnection * connection, + GPITransfer ** transfer, + GPProfile profile, + GPIBool sender +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPITransfer transferTemp; + + // Fill in the object. + ////////////////////// + memset(&transferTemp, 0, sizeof(GPITransfer)); + transferTemp.files = ArrayNew(sizeof(GPIFile), 0, gpiFreeFile); + if(!transferTemp.files) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + transferTemp.localID = iconnection->nextTransferID++; + transferTemp.sender = sender; + transferTemp.profile = profile; + transferTemp.throttle = -1; + transferTemp.currentFile = -1; + + // Add it. + ////////// + ArrayAppend(iconnection->transfers, &transferTemp); + + // Get it. + ////////// + *transfer = (GPITransfer *)ArrayNth(iconnection->transfers, ArrayLength(iconnection->transfers) - 1); + + return GP_NO_ERROR; +} + +GPResult gpiNewSenderTransfer +( + GPConnection * connection, + GPITransfer ** transfer, + GPProfile profile +) +{ + GPITransfer * pTransfer; + GPIConnection * iconnection = (GPIConnection*)*connection; + unsigned long now; + + CHECK_RESULT(gpiNewTransfer(connection, transfer, profile, GPITrue)); + + now = current_time(); + + pTransfer = *transfer; + pTransfer->state = GPITransferPinging; + pTransfer->transferID.profileid = iconnection->profileid; + pTransfer->transferID.count = pTransfer->localID; + pTransfer->transferID.time = (unsigned int)now; + pTransfer->lastSend = now; + + return GP_NO_ERROR; +} + +static GPResult gpiNewReceiverTransfer +( + GPConnection * connection, + GPITransfer ** transfer, + GPProfile profile, + GPITransferID * transferID +) +{ +#ifdef WIN32 + char buffer[FILENAME_MAX]; +#endif + GPITransfer * pTransfer; + + CHECK_RESULT(gpiNewTransfer(connection, transfer, profile, GPIFalse)); + + pTransfer = *transfer; + pTransfer->state = GPITransferWaiting; + memcpy(&pTransfer->transferID, transferID, sizeof(GPITransferID)); +#ifdef WIN32 + if(GetTempPathA(sizeof(buffer), buffer) != 0) + pTransfer->baseDirectory = goastrdup(buffer); +#endif + + return GP_NO_ERROR; +} + +void gpiFreeTransfer +( + GPConnection * connection, + GPITransfer * transfer +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + int pos; + + // Find the transfer. + ///////////////////// + pos = ArraySearch(iconnection->transfers, transfer, gpiTransferCompare, 0, 0); + assert(pos != NOT_FOUND); + if(pos == NOT_FOUND) + return; + + // Remove it. + ///////////// + ArrayDeleteAt(iconnection->transfers, pos); +} + +void gpiCancelTransfer +( + GPConnection * connection, + GPITransfer * transfer +) +{ + // Send the cancel message. + /////////////////////////// + if(transfer->peer) + { + // Start the message. + ///////////////////// + if(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_TRANSFER_CANCEL, (GPITransferID_st)&transfer->transferID) == GP_NO_ERROR) + { + // Finish the message. + ////////////////////// + gpiFinishTransferMessage(connection, transfer, NULL, 0); + } + } +} + +void gpiTransferError +( + GPConnection * connection, + const GPITransfer * transfer +) +{ + GPTransferCallbackArg * arg; + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_ERROR; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } +} + +GPIFile * gpiAddFileToTransfer +( + GPITransfer * transfer, + const char * path, + const char * name +) +{ + GPIFile file; + char * str; + + assert(name && name[0]); + + memset(&file, 0, sizeof(GPIFile)); + + // Copy the path. + ///////////////// + if(path) + { + file.path = goastrdup(path); + if(!file.path) + return NULL; + } + + // Copy the name. + ///////////////// + file.name = goastrdup(name); + if(!file.name) + { + gsifree(file.path); + return NULL; + } + + // Change all slashes to backslashes. + ///////////////////////////////////// + while((str = strchr(file.name, '\\')) != NULL) + *str = '/'; + + // Check for a directory. + ///////////////////////// + if(name[strlen(name) - 1] == '/') + file.flags = GPI_FILE_DIRECTORY; + + // No size yet. + /////////////// + file.size = -1; + + // Add it to the list of files. + /////////////////////////////// + ArrayAppend(transfer->files, &file); + +#ifdef GSI_UNICODE + // Copy the unicode versions + file.name_W = UTF8ToUCS2StringAlloc(file.name); + file.path_W = UTF8ToUCS2StringAlloc(file.path); +#endif + + // Return the file. + /////////////////// + return (GPIFile *)ArrayNth(transfer->files, ArrayLength(transfer->files) - 1); +} + +static GPResult gpiSendTransferRequest +( + GPConnection * connection, + GPITransfer * transfer +) +{ + char buffer[32]; + GPIFile * file; + int i; + int num; + + // Get the number of files. + /////////////////////////// + num = ArrayLength(transfer->files); + + // Start the message. + ///////////////////// + CHECK_RESULT(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_SEND_REQUEST, (GPITransferID_st)&transfer->transferID)); + + // Add the rest of the headers. + /////////////////////////////// + sprintf(buffer, "\\version\\%d\\num\\%d", GPI_TRANSFER_VERSION, num); + CHECK_RESULT(gpiSendOrBufferString(connection, transfer->peer, buffer)); + for(i = 0 ; i < num ; i++) + { + file = (GPIFile *)ArrayNth(transfer->files, i); + + sprintf(buffer, "\\name%d\\", i); + CHECK_RESULT(gpiSendOrBufferString(connection, transfer->peer, buffer)); + CHECK_RESULT(gpiSendOrBufferString(connection, transfer->peer, file->name)); + + sprintf(buffer, "\\size%d\\", i); + CHECK_RESULT(gpiSendOrBufferString(connection, transfer->peer, buffer)); + CHECK_RESULT(gpiSendOrBufferInt(connection, transfer->peer, file->size)); + + sprintf(buffer, "\\mtime%d\\", i); + CHECK_RESULT(gpiSendOrBufferString(connection, transfer->peer, buffer)); + CHECK_RESULT(gpiSendOrBufferUInt(connection, transfer->peer, file->modTime)); + } + + // Finish the message. + ////////////////////// + CHECK_RESULT(gpiFinishTransferMessage(connection, transfer, transfer->message, -1)); + + return GP_NO_ERROR; +} + +static GPITransfer * gpiFindTransferByTransferID +( + GPConnection * connection, + GPITransferID * transferID +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + int i; + int num; + GPITransfer * transfer; + + num = ArrayLength(iconnection->transfers); + for(i = 0 ; i < num ; i++) + { + transfer = (GPITransfer *)ArrayNth(iconnection->transfers, i); + if(memcmp(&transfer->transferID, transferID, sizeof(GPITransferID)) == 0) + return transfer; + } + + return NULL; +} + +GPITransfer * gpiFindTransferByLocalID +( + GPConnection * connection, + int localID +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + int i; + int num; + GPITransfer * transfer; + + num = ArrayLength(iconnection->transfers); + for(i = 0 ; i < num ; i++) + { + transfer = (GPITransfer *)ArrayNth(iconnection->transfers, i); + if(transfer->localID == localID) + return transfer; + } + + return NULL; +} + +int gpiGetTransferLocalIDByIndex +( + GPConnection * connection, + int index +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPITransfer * transfer; + int num; + + num = ArrayLength(iconnection->transfers); + assert(index >= 0); + assert(index < num); + if((index < 0) || (index >= num)) + return -1; + + transfer = (GPITransfer *)ArrayNth(iconnection->transfers, index); + assert(transfer); + if(!transfer) + return -1; + + return transfer->localID; +} + +void gpiSkipFile +( + GPConnection * connection, + GPITransfer * transfer, + int file, + int reason +) +{ + char buffer[32]; + + if(gpiPeerStartTransferMessage(connection, transfer->peer, GP_FILE_SKIP, (GPITransferID_st)&transfer->transferID) != GP_NO_ERROR) + return; + + sprintf(buffer, "\\file\\%d\\reason\\%d", file, reason); + gpiSendOrBufferString(connection, transfer->peer, buffer); + gpiFinishTransferMessage(connection, transfer, NULL, 0); +} + +void gpiSkipCurrentFile +( + GPConnection * connection, + GPITransfer * transfer, + int reason +) +{ + gpiSkipFile(connection, transfer, transfer->currentFile, reason); + + transfer->currentFile++; +} + +static GPIBool gpiHandleSendRequest +( + GPConnection * connection, + GPIPeer * peer, + GPITransferID * transferID, + const char * headers, + const char * buffer, + int bufferLen +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPITransfer * transfer; + GPIFile * file; + GPTransferCallbackArg * arg; + char key[16]; + char intValue[16]; + int version; + int numFiles; + char name[FILENAME_MAX]; + int size; + unsigned int mtime; + int i; + size_t len; + int totalSize = 0; + + // If we don't have a callback, we're not accepting requests. + ///////////////////////////////////////////////////////////// + if(!iconnection->callbacks[GPI_TRANSFER_CALLBACK].callback) + return GPIFalse; + + // Check the version. + ///////////////////// + if(!gpiValueForKey(headers, "\\version\\", intValue, sizeof(intValue))) + return GPIFalse; + version = atoi(intValue); + if(version < 1) + return GPIFalse; + + // Get the number of files. + /////////////////////////// + if(!gpiValueForKey(headers, "\\num\\", intValue, sizeof(intValue))) + return GPIFalse; + numFiles = atoi(intValue); + if(numFiles < 1) + return GPIFalse; + + // Create the transfer object. + ////////////////////////////// + if(gpiNewReceiverTransfer(connection, &transfer, peer->profile, transferID) != GP_NO_ERROR) + return GPIFalse; + + // Set the peer. + //////////////// + transfer->peer = peer; + + // Parse the file list. + /////////////////////// + for(i = 0 ; i < numFiles ; i++) + { + sprintf(key, "\\name%d\\", i); + if(!gpiValueForKey(headers, key, name, sizeof(name))) + { + gpiFreeTransfer(connection, transfer); + return GPIFalse; + } + len = strlen(name); + if(strstr(name, "//") || strstr(name, "./") || (name[len - 1] == '.') || (name[0] == '/') || (strcspn(name, ":*?\"<>|\n") != len)) + { + gpiFreeTransfer(connection, transfer); + return GPIFalse; + } + + sprintf(key, "\\size%d\\", i); + if(!gpiValueForKey(headers, key, intValue, sizeof(intValue))) + { + gpiFreeTransfer(connection, transfer); + return GPIFalse; + } + size = atoi(intValue); + if(size < 0) + { + gpiFreeTransfer(connection, transfer); + return GPIFalse; + } + totalSize += size; + + sprintf(key, "\\mtime%d\\", i); + if(!gpiValueForKey(headers, key, intValue, sizeof(intValue))) + { + gpiFreeTransfer(connection, transfer); + return GPIFalse; + } + mtime = (unsigned int)strtoul(intValue, NULL, 10); + + file = gpiAddFileToTransfer(transfer, NULL, name); + if(!file) + { + gpiFreeTransfer(connection, transfer); + return GPIFalse; + } + file->size = size; + file->modTime = mtime; + } + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(!arg) + { + gpiFreeTransfer(connection, transfer); + return GPIFalse; + } + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_SEND_REQUEST; + arg->num = numFiles; +#ifndef GSI_UNICODE + arg->message = goastrdup(buffer); +#else + arg->message = UTF8ToUCS2StringAlloc(buffer); +#endif + { + GPResult aResult = gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + if (aResult != GP_NO_ERROR) + return GPIFalse; + } + + // Store the total size. + //////////////////////// + transfer->totalSize = totalSize; + + return GPITrue; + + GSI_UNUSED(bufferLen); +} + +static GPIBool gpiHandleSendReply +( + GPConnection * connection, + GPITransfer * transfer, + const char * headers, + const char * buffer, + int bufferLen +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPTransferCallbackArg * arg; + char intValue[16]; + int version; + int result; + + if(!transfer->sender) + return GPIFalse; + + // Check the version. + //////////////////// + if(!gpiValueForKey(headers, "\\version\\", intValue, sizeof(intValue))) + return GPIFalse; + version = atoi(intValue); + if(version < 1) + return GPIFalse; + + // Get the result. + ////////////////// + if(!gpiValueForKey(headers, "\\result\\", intValue, sizeof(intValue))) + return GPIFalse; + result = atoi(intValue); + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + if(result == GPI_ACCEPTED) + arg->type = GP_TRANSFER_ACCEPTED; + else if(result == GPI_REJECTED) + arg->type = GP_TRANSFER_REJECTED; + else + arg->type = GP_TRANSFER_NOT_ACCEPTING; + +#ifndef GSI_UNICODE + arg->message = goastrdup(buffer); +#else + arg->message = UTF8ToUCS2StringAlloc(buffer); +#endif + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Update transfer state if accepted. + ///////////////////////////////////// + if(result == GPI_ACCEPTED) + { + transfer->state = GPITransferTransferring; + transfer->currentFile = 0; + } + + return GPITrue; + + GSI_UNUSED(bufferLen); +} + +static GPIBool gpiHandleBegin +( + GPConnection * connection, + GPITransfer * transfer, + const char * headers +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPTransferCallbackArg * arg; + GPIFile * file; + char intValue[16]; + int fileIndex; + int size; + unsigned int mtime; + char buffer[FILENAME_MAX]; + int count; + + if(transfer->sender) + return GPIFalse; + + // Get the file. + //////////////// + if(!gpiValueForKey(headers, "\\file\\", intValue, sizeof(intValue))) + return GPIFalse; + fileIndex = atoi(intValue); + if((fileIndex < 0) || (fileIndex >= ArrayLength(transfer->files))) + return GPIFalse; + if(fileIndex != transfer->currentFile) + return GPIFalse; + file = (GPIFile *)ArrayNth(transfer->files, fileIndex); + + // Is this a directory? + /////////////////////// + if(file->flags & GPI_FILE_DIRECTORY) + return GPIFalse; + + // Get the size. + //////////////// + if(!gpiValueForKey(headers, "\\size\\", intValue, sizeof(intValue))) + return GPIFalse; + size = atoi(intValue); + if(size < 0) + return GPIFalse; + + // Update the total size. + ///////////////////////// + transfer->totalSize -= file->size; + transfer->totalSize += size; + + // Get the mod time. + //////////////////// + if(!gpiValueForKey(headers, "\\mtime\\", intValue, sizeof(intValue))) + return GPIFalse; + mtime = (unsigned int)strtoul(intValue, NULL, 10); + + // Set file stuff. + ////////////////// + MD5Init(&file->md5); + file->modTime = mtime; + file->size = size; + + // Setup the temp path. + /////////////////////// + count = 0; + do + { + sprintf(buffer, "%sgpt_%d_%d_%d.gpt", transfer->baseDirectory, transfer->localID, fileIndex, rand()); + file->file = fopen(buffer, "wb"); + count++; + } + while(!file->file && (count < 5)); + + // Copy off the path. + ///////////////////// + if(file->file) + { + file->path = goastrdup(buffer); + if(!file->path) + return GPIFalse; + +#ifdef GSI_UNICODE + file->path_W = UTF8ToUCS2StringAlloc(file->path); +#endif + } + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = fileIndex; + if(file->file) + { + arg->type = GP_FILE_BEGIN; + } + else + { + arg->type = GP_FILE_FAILED; + arg->num = GP_FILE_WRITE_ERROR; + } + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Did it fail? + /////////////// + if(!file->file) + { + gpiSkipCurrentFile(connection, transfer, GPI_SKIP_WRITE_ERROR); + file->flags |= GPI_FILE_FAILED; + file->reason = GP_FILE_WRITE_ERROR; + } + + return GPITrue; +} + +static GPIBool gpiHandleEnd +( + GPConnection * connection, + GPITransfer * transfer, + const char * headers +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPTransferCallbackArg * arg; + GPIFile * file; + GPIBool md5Failed = GPITrue; + unsigned char rawMD5[16]; + char localMD5[33]; + char remoteMD5[33]; + char intValue[16]; + int fileIndex; + + if(transfer->currentFile == -1) + return GPIFalse; + + // Check the file index. + //////////////////////// + if(!gpiValueForKey(headers, "\\file\\", intValue, sizeof(intValue))) + return GPIFalse; + fileIndex = atoi(intValue); + if((fileIndex < 0) || (fileIndex >= ArrayLength(transfer->files))) + return GPIFalse; + if(fileIndex != transfer->currentFile) + return GPITrue; + + // Get the current file. + //////////////////////// + file = (GPIFile *)ArrayNth(transfer->files, transfer->currentFile); + + // Sender? + ////////// + if(transfer->sender) + { +#ifdef GPI_CONFIRM_FILES + // We should be waiting for confirmation. + ///////////////////////////////////////// + assert(file->flags & GPI_FILE_CONFIRMING); + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + arg->type = GP_FILE_END; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Done with the file. + ////////////////////// + file->flags &= ~GPI_FILE_CONFIRMING; + file->flags |= GPI_FILE_COMPLETED; + transfer->currentFile++; +#endif + return GPITrue; + } + + // Is this a directory? + /////////////////////// + if(file->flags & GPI_FILE_DIRECTORY) + { + // Directory completed. + /////////////////////// + file->flags |= GPI_FILE_COMPLETED; + } + else + { + // Check the file. + ////////////////// + assert(file->file); + if(!file->file) + return GPIFalse; + + // Get the remote md5. + ////////////////////// + if(!gpiValueForKey(headers, "\\md5\\", remoteMD5, sizeof(remoteMD5))) + return GPIFalse; + + // Get the local md5. + ///////////////////// + MD5Final(rawMD5, &file->md5); + MD5Print(rawMD5, localMD5); + + // Check the md5. + ///////////////// + md5Failed = (memcmp(localMD5, remoteMD5, 32) != 0) ? GPITrue:GPIFalse; + + // Set the state. + ///////////////// + if(md5Failed) + { + file->flags |= GPI_FILE_FAILED; + file->reason = GP_FILE_DATA_ERROR; + } + else + file->flags |= GPI_FILE_COMPLETED; + + // Close the file. + ////////////////// + fclose(file->file); + file->file = NULL; + + // If the md5 failed, remove the file. + ////////////////////////////////////// + if(md5Failed) + remove(file->path); + +#ifdef GPI_CONFIRM_FILES + // Send a confirmation. + /////////////////////// + if(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_END, (GPITransferID_st)&transfer->transferID) != GP_NO_ERROR) + return GPIFalse; + gpiFinishTransferMessage(connection, transfer, NULL, 0); +#endif + } + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + if(file->flags & GPI_FILE_DIRECTORY) + { + arg->type = GP_FILE_DIRECTORY; + } + else if(md5Failed) + { + arg->type = GP_FILE_FAILED; + arg->num = GP_FILE_DATA_ERROR; + } + else + { + arg->type = GP_FILE_END; + } + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Next file. + ///////////// + transfer->currentFile++; + + // Done? + //////// + if(transfer->currentFile == ArrayLength(transfer->files)) + { + // The transfer is complete. + //////////////////////////// + transfer->state = GPITransferComplete; + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_DONE; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + } + + return GPITrue; +} + +static GPIBool gpiHandleData +( + GPConnection * connection, + GPITransfer * transfer, + const char * headers, + const char * buffer, + int bufferLen +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPTransferCallbackArg * arg; + GPIFile * file; + GPIBool writeFailed; + char intValue[16]; + int fileIndex; + + if(transfer->currentFile == -1) + return GPIFalse; + + // Check the file index. + //////////////////////// + if(!gpiValueForKey(headers, "\\file\\", intValue, sizeof(intValue))) + return GPIFalse; + fileIndex = atoi(intValue); + if((fileIndex < 0) || (fileIndex >= ArrayLength(transfer->files))) + return GPIFalse; + if(fileIndex != transfer->currentFile) + return GPITrue; + + // Get the current file. + //////////////////////// + file = (GPIFile *)ArrayNth(transfer->files, transfer->currentFile); + + // Is this a directory? + /////////////////////// + if(file->flags & GPI_FILE_DIRECTORY) + return GPIFalse; + +#ifdef GPI_ACKNOWLEDGED_WINDOW + // Sender? + ////////// + if(transfer->sender) + { + char intValue[16]; + + // Get the progress. + //////////////////// + if(!gpiValueForKey(headers, "\\pro\\", intValue, sizeof(intValue))) + return GPIFalse; + file->acknowledged = atoi(intValue); + + return GPITrue; + } +#endif + + // Check the file. + ////////////////// + assert(file->file); + if(!file->file) + return GPIFalse; + + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_RawDump, + "HNDLDATA(PT): %d\n", bufferLen); + + // Write the data. + ////////////////// + writeFailed = (GPIBool)(fwrite(buffer, 1, bufferLen, file->file) != (size_t)bufferLen); + if(writeFailed) + { + // Flag the errors. + /////////////////// + file->flags |= GPI_FILE_FAILED; + file->reason = GP_FILE_WRITE_ERROR; + + // Remove the file. + /////////////////// + fclose(file->file); + file->file = NULL; + remove(file->path); + } + else + { + // Update the MD5. + /////////////////// + MD5Update(&file->md5, (unsigned char *)buffer, bufferLen); + + // Update the progress. + /////////////////////// + file->progress += bufferLen; + transfer->progress += bufferLen; + +#ifdef GPI_ACKNOWLEDGED_WINDOW + // Send an acknowledgment. + ////////////////////////// + if(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_DATA, (GPITransferID_st)&transfer->transferID) != GP_NO_ERROR) + return GPIFalse; + gpiSendOrBufferString(connection, transfer->peer, "\\pro\\"); + gpiSendOrBufferInt(connection, transfer->peer, file->progress); + gpiFinishTransferMessage(connection, transfer, NULL, 0); +#endif + } + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + if(!writeFailed) + { + arg->type = GP_FILE_PROGRESS; + arg->num = file->progress; + } + else + { + arg->type = GP_FILE_FAILED; + arg->num = GP_FILE_WRITE_ERROR; + } + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Did it fail? + /////////////// + if(writeFailed) + { + // Skip the file. + ///////////////// + gpiSkipCurrentFile(connection, transfer, GPI_SKIP_WRITE_ERROR); + } + + return GPITrue; +} + +static GPIBool gpiHandleSkip +( + GPConnection * connection, + GPITransfer * transfer, + const char * headers +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPTransferCallbackArg * arg; + GPIFile * file; + char intValue[16]; + int fileIndex; + int reason; + + // Get the file. + //////////////// + if(!gpiValueForKey(headers, "\\file\\", intValue, sizeof(intValue))) + return GPIFalse; + fileIndex = atoi(intValue); + if((fileIndex < 0) || (fileIndex >= ArrayLength(transfer->files))) + return GPIFalse; + file = (GPIFile *)ArrayNth(transfer->files, fileIndex); + + // Get the reason. + ////////////////// + if(!gpiValueForKey(headers, "\\reason\\", intValue, sizeof(intValue))) + return GPIFalse; + reason = atoi(intValue); + + // Is it not the current file? + ////////////////////////////// + if(fileIndex != transfer->currentFile) + { + // Check if we already finished this file. + ////////////////////////////////////////// + if(fileIndex < transfer->currentFile) + return GPIFalse; + + // Mark it for skipping later. + ////////////////////////////// + if(reason == GPI_SKIP_USER_SKIP) + { + file->flags |= GPI_FILE_SKIP; + } + else + { + file->flags |= GPI_FILE_FAILED; + if(reason == GPI_SKIP_READ_ERROR) + file->reason = GP_FILE_READ_ERROR; + else + file->reason = GP_FILE_WRITE_ERROR; + } + + return GPITrue; + } + + // Delete the file if its already opened. + ///////////////////////////////////////// + if(!transfer->sender && file->file) + { + fclose(file->file); + file->file = NULL; + remove(file->path); + } + + // Next file. + ///////////// + transfer->currentFile++; + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = fileIndex; + if(reason == GPI_SKIP_USER_SKIP) + { + arg->type = GP_FILE_SKIP; + } + else + { + arg->type = GP_FILE_FAILED; + if(reason == GPI_SKIP_READ_ERROR) + arg->num = GP_FILE_READ_ERROR; + else + arg->num = GP_FILE_WRITE_ERROR; + } + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + return GPITrue; +} + +static GPIBool gpiHandleTransferThrottle +( + GPConnection * connection, + GPITransfer * transfer, + const char * headers +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + int throttle; + char intValue[16]; + GPTransferCallbackArg * arg; + + // Get the throttle. + //////////////////// + if(!gpiValueForKey(headers, "\\rate\\", intValue, sizeof(intValue))) + return GPIFalse; + throttle = atoi(intValue); + + // Store the throttle. + ////////////////////// + transfer->throttle = throttle; + + // If we're the sender, send this back. + /////////////////////////////////////// + if(transfer->sender) + { + if(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_TRANSFER_THROTTLE, (GPITransferID_st)&transfer->transferID) != GP_NO_ERROR) + return GPIFalse; + gpiSendOrBufferString(connection, transfer->peer, "\\rate\\"); + gpiSendOrBufferInt(connection, transfer->peer, throttle); + gpiFinishTransferMessage(connection, transfer, NULL, 0); + } + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_THROTTLE; + arg->num = throttle; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + return GPITrue; +} + +static GPIBool gpiHandleTransferCancel +( + GPConnection * connection, + GPITransfer * transfer +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPTransferCallbackArg * arg; + +// if(transfer->sender) +// return GPIFalse; + + // Mark the transfer cancelled. + /////////////////////////////// + transfer->state = GPITransferCancelled; + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_CANCELLED; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + return GPITrue; +} + +static GPIBool gpiHandleTransferKeepalive +( + GPConnection * connection, + GPITransfer * transfer +) +{ + GSI_UNUSED(connection); + GSI_UNUSED(transfer); + + // Ignore keep-alive. + ///////////////////// + return GPITrue; +} + +static GPResult gpiSendFileEnd +( + GPConnection * connection, + GPITransfer * transfer, + GPIFile * file +) +{ + CHECK_RESULT(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_END, (GPITransferID_st)&transfer->transferID)); + + // Add the file index. + ////////////////////// + gpiSendOrBufferStringLenToPeer(connection, transfer->peer, "\\file\\", 6); + gpiSendOrBufferInt(connection, transfer->peer, transfer->currentFile); + + // Only add the MD5 for files. + ////////////////////////////// + if(!(file->flags & GPI_FILE_DIRECTORY)) + { + unsigned char md5Raw[16]; + char md5[33]; + + // Get the MD5. + /////////////// + MD5Final(md5Raw, &file->md5); + MD5Print(md5Raw, md5); + + // Add it. + ////////// + gpiSendOrBufferString(connection, transfer->peer, "\\md5\\"); + gpiSendOrBufferString(connection, transfer->peer, md5); + } + + gpiFinishTransferMessage(connection, transfer, NULL, 0); + + return GP_NO_ERROR; +} + +static GPResult gpiSendFileBegin +( + GPConnection * connection, + GPITransfer * transfer, + GPIFile * file +) +{ + char buffer[64]; + + // Get the file info. + ///////////////////// + if(!gpiGetTransferFileInfo(file->file, &file->size, &file->modTime)) + Error(connection, GP_PARAMETER_ERROR, "Can't get info on file."); + + CHECK_RESULT(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_BEGIN, (GPITransferID_st)&transfer->transferID)); + sprintf(buffer, "\\file\\%d\\size\\%d\\mtime\\%u", transfer->currentFile, file->size, (unsigned int)file->modTime); + gpiSendOrBufferString(connection, transfer->peer, buffer); + gpiFinishTransferMessage(connection, transfer, NULL, 0); + + return GP_NO_ERROR; +} + +static GPResult gpiSendFileData +( + GPConnection * connection, + GPITransfer * transfer, + unsigned char * data, + size_t len +) +{ + CHECK_RESULT(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_DATA, (GPITransferID_st)&transfer->transferID)); + + // Add the file index. + ////////////////////// + gpiSendOrBufferStringLenToPeer(connection, transfer->peer, "\\file\\", 6); + gpiSendOrBufferInt(connection, transfer->peer, transfer->currentFile); + + gpiFinishTransferMessage(connection, transfer, (char *)data, len); + + return GP_NO_ERROR; +} + +GPResult gpiProcessCurrentFile +( + GPConnection * connection, + GPITransfer * transfer +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPIFile * file; + GPTransferCallbackArg * arg; + size_t num; + int i; + int total; + + assert(transfer->currentFile >= 0); + assert(transfer->currentFile < ArrayLength(transfer->files)); + + // Get the current file. + //////////////////////// + file = (GPIFile *)ArrayNth(transfer->files, transfer->currentFile); + + assert(!(file->flags & GPI_FILE_FAILED)); + +#ifdef GPI_CONFIRM_FILES + // If it's being confirmed, just wait. + ////////////////////////////////////// + if(file->flags & GPI_FILE_CONFIRMING) + return GP_NO_ERROR; +#endif + + // Check if its been marked for skipping. + ///////////////////////////////////////// + if(file->flags & GPI_FILE_SKIP) + { + // Skip it. + /////////// + gpiSkipCurrentFile(connection, transfer, GPI_SKIP_USER_SKIP); + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + arg->type = GP_FILE_SKIP; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + } + else + { + // Is this a directory? + /////////////////////// + if(file->flags & GPI_FILE_DIRECTORY) + { + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + arg->type = GP_FILE_DIRECTORY; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Send the end. + //////////////// + gpiSendFileEnd(connection, transfer, file); + file->flags |= GPI_FILE_COMPLETED; + transfer->currentFile++; + } + else + { + static char buffer[GPI_DATA_SIZE]; + + // Open the file if we need to. + /////////////////////////////// + if(!file->file) + { + // Open it. + /////////// + file->file = fopen(file->path, "rb"); + if(file->file) + { + // Send the begin. + ////////////////// + CHECK_RESULT(gpiSendFileBegin(connection, transfer, file)); + + // Init the md5. + //////////////// + MD5Init(&file->md5); + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + arg->type = GP_FILE_BEGIN; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + } + else + { + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + arg->type = GP_FILE_FAILED; + arg->num = GP_FILE_READ_ERROR; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Failed to open. + ////////////////// + gpiSkipCurrentFile(connection, transfer, GPI_SKIP_READ_ERROR); + file->flags |= GPI_FILE_FAILED; + file->reason = GP_FILE_READ_ERROR; + + return GP_NO_ERROR; + } + } + + // TODO: THROTTLING + + // Send until done, and while messages are actually being sent. + /////////////////////////////////////////////////////////////// + total = 0; + for(i = 0 ; (file->progress < file->size) && !transfer->peer->outputBuffer.len /*&& (i < 20)*/ ; i++) + { +#ifdef GPI_ACKNOWLEDGED_WINDOW + // Don't get too far ahead. + /////////////////////////// + if((file->acknowledged + GPI_ACKNOWLEDGED_WINDOW) < file->progress) + break; +#endif + + // Read data. + ///////////// + num = fread(buffer, 1, sizeof(buffer), file->file); + if(num) + { + // Update the md5. + ////////////////// + MD5Update(&file->md5, (unsigned char*)buffer, num); + + // Send the data. + ///////////////// + CHECK_RESULT(gpiSendFileData(connection, transfer, (unsigned char*)buffer, num)); + + // Update progress. + /////////////////// + transfer->progress += num; + file->progress += num; + total += num; + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + arg->type = GP_FILE_PROGRESS; + arg->num = file->progress; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + } + + // Did we not get to the end? + ///////////////////////////// + if((num < sizeof(buffer)) && (file->progress != file->size)) + { + // Failed reading. + ////////////////// + gpiSkipCurrentFile(connection, transfer, GPI_SKIP_READ_ERROR); + file->flags |= GPI_FILE_FAILED; + file->reason = GP_FILE_READ_ERROR; + + return GP_NO_ERROR; + } + } + + if(total) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_RawDump, + "SENTTOTL(PT): %d\n", total); + } + + // Did we finish the file? + ////////////////////////// + if(file->progress == file->size) + { + // Close the file. + ////////////////// + fclose(file->file); + file->file = NULL; + + // Send the end. + //////////////// + gpiSendFileEnd(connection, transfer, file); + +#ifdef GPI_CONFIRM_FILES + // Wait for the confirmation. + ///////////////////////////// + file->flags |= GPI_FILE_CONFIRMING; +#else + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + arg->type = GP_FILE_END; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Done with the file. + ////////////////////// + file->flags |= GPI_FILE_COMPLETED; + transfer->currentFile++; +#endif + } + } + } + + return GP_NO_ERROR; +} + +GPResult gpiProcessTransfer +( + GPConnection * connection, + GPITransfer * transfer +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + int currentFile; + int len; + GPTransferCallbackArg * arg; + unsigned long now; + + // We only process sending transfers. + ///////////////////////////////////// + if(!transfer->sender) + return GP_NO_ERROR; + + // Is the transfer finished? + //////////////////////////// + if(transfer->state >= GPITransferComplete) + return GP_NO_ERROR; + + // Get the time. + //////////////// + now = current_time(); + + // Check for no peer connection established. + //////////////////////////////////////////// + if(!transfer->peer) + { + // If its been too long, the person probably isn't really online. + ///////////////////////////////////////////////////////////////// + if((now - transfer->lastSend) > GPI_PEER_TIMEOUT_TIME) + { + GPTransferCallbackArg * arg; + + // We couldn't connect. + /////////////////////// + transfer->state = GPITransferNoConnection; + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_NO_CONNECTION; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + return GP_NO_ERROR; + } + } + else + { + // Check for inactivity. + //////////////////////// + if((now - transfer->lastSend) > GPI_KEEPALIVE_TIME) + { + // Send a keepalive. + //////////////////// + CHECK_RESULT(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_TRANSFER_KEEPALIVE, (GPITransferID_st)&transfer->transferID)); + gpiFinishTransferMessage(connection, transfer, NULL, 0); + } + } + + // If we're paused, there's nothing else to do. + /////////////////////////////////////////////// + if(transfer->throttle == 0) + return GP_NO_ERROR; + + // Don't send files if we're not transfering yet. + ////////////////////////////////////////////////// + if(transfer->state < GPITransferTransferring) + return GP_NO_ERROR; + + // Don't send files if we have regular messages pending. + //////////////////////////////////////////////////////// + if(ArrayLength(transfer->peer->messages)) + return GP_NO_ERROR; + + // Process the current file. + //////////////////////////// + len = ArrayLength(transfer->files); + while(transfer->currentFile < len) + { + currentFile = transfer->currentFile; + CHECK_RESULT(gpiProcessCurrentFile(connection, transfer)); + if(currentFile == transfer->currentFile) + break; + } + + // Did we finish? + ///////////////// + if(transfer->currentFile == len) + { + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_DONE; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Mark it as complete. + /////////////////////// + transfer->state = GPITransferComplete; + } + + return GP_NO_ERROR; +} + +GPResult gpiProcessTransfers +( + GPConnection * connection +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + int len; + int i; + GPITransfer * transfer; + + // Go through each transfer. + //////////////////////////// + len = ArrayLength(iconnection->transfers); + for(i = 0 ; i < len ; i++) + { + // Get the transfer. + //////////////////// + transfer = (GPITransfer *)ArrayNth(iconnection->transfers, i); + + // Process it. + ////////////// + gpiProcessTransfer(connection, transfer); + } + + return GP_NO_ERROR; +} + +GPIBool gpiGetTransferFileInfo +( + FILE * file, + int * size, + gsi_time * modTime +) +{ +#ifdef _WIN32 + struct _stat stats; + + if(_fstat(_fileno(file), &stats) != 0) + return GPIFalse; + + *size = (int)stats.st_size; + *modTime = (gsi_time)stats.st_mtime; +#else + if(fseek(file, 0, SEEK_END) != 0) + return GPIFalse; + + *size = (int)ftell(file); + if(*size == -1) + return GPIFalse; + + *modTime = 0; + + fseek(file, 0, SEEK_SET); +#endif + + return GPITrue; +} + +void gpiTransferPeerDestroyed +( + GPConnection * connection, + GPIPeer * peer +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPTransferCallbackArg * arg; + GPITransfer * transfer; + int i; + int len; + + // Search for transfers that use this peer. + /////////////////////////////////////////// + len = ArrayLength(iconnection->transfers); + for(i = 0 ; i < len ; i++) + { + transfer = (GPITransfer *)ArrayNth(iconnection->transfers, i); + + if (transfer->peer == peer) + { + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_LOST_CONNECTION; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // So long tranfer. + /////////////////// + transfer->state = GPITransferNoConnection; + } + } + +} + +void gpiTransfersHandlePong +( + GPConnection * connection, + GPProfile profile, + GPIPeer * peer +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + GPITransfer * transfer; + int i; + int len; + + // Go through all the transfers. + //////////////////////////////// + len = ArrayLength(iconnection->transfers); + for(i = 0 ; i < len ; i++) + { + // Get this transfer. + ///////////////////// + transfer = (GPITransfer *)ArrayNth(iconnection->transfers, i); + assert(transfer); + + // Is it waiting on a pong from this profile? + ///////////////////////////////////////////// + if((transfer->state == GPITransferPinging) && (transfer->profile == profile)) + { + // Did we not get a connection? + /////////////////////////////// + if(!peer) + { + GPTransferCallbackArg * arg; + + // We couldn't connect. + /////////////////////// + transfer->state = GPITransferNoConnection; + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg)); + if(arg) + { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_NO_CONNECTION; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + } + else + { + // Store the peer we're connected on. + ///////////////////////////////////// + transfer->peer = peer; + + // We're connected, so send our request. + //////////////////////////////////////// + gpiSendTransferRequest(connection, transfer); + + // Waiting for a response. + ////////////////////////// + transfer->state = GPITransferWaiting; + } + } + } +} +#endif + +GPResult gpiSendTransferReply +( + GPConnection * connection, + const GPITransferID * transferID, + GPIPeer * peer, + int result, + const char * msg +) +{ + char buffer[32]; + + if(!msg) + msg = ""; + + // Start the message. + ///////////////////// + CHECK_RESULT(gpiPeerStartTransferMessage(connection, peer, GPI_BM_FILE_SEND_REPLY, transferID)); + + // Add the rest of the headers. + /////////////////////////////// + sprintf(buffer, "\\version\\%d\\result\\%d", GPI_TRANSFER_VERSION, result); + CHECK_RESULT(gpiSendOrBufferString(connection, peer, buffer)); + + // Finish the message. + ////////////////////// + CHECK_RESULT(gpiPeerFinishTransferMessage(connection, peer, msg, -1)); + + return GP_NO_ERROR; +} + +void gpiHandleTransferMessage +( + GPConnection * connection, + GPIPeer * peer, + int type, + const char * headers, + const char * buffer, + int len +) +{ + char value[64]; + GPITransferID transferID; +#ifndef NOFILE + GPITransfer * transfer; + GPIBool success; +#endif + + // Get the transfer ID. + /////////////////////// + if(!gpiValueForKey(headers, "\\xfer\\", value, sizeof(value))) + return; + if(sscanf(value, "%d %u %u", &transferID.profileid, &transferID.count, &transferID.time) != 3) + return; + +#ifdef NOFILE + gpiSendTransferReply(connection, &transferID, peer, GPI_NOT_ACCEPTING, NULL); +#else + + // Send request messages don't yet have a transfer object. + ////////////////////////////////////////////////////////// + if(type == GPI_BM_FILE_SEND_REQUEST) + { + // Check for not accepting connections. + /////////////////////////////////////// + if(!gpiHandleSendRequest(connection, peer, &transferID, headers, buffer, len)) + gpiSendTransferReply(connection, &transferID, peer, GPI_NOT_ACCEPTING, NULL); + + return; + } + + // Find the transfer based on the ID. + ///////////////////////////////////// + transfer = gpiFindTransferByTransferID(connection, &transferID); + if(!transfer || (transfer->peer != peer)) + return; + + // Handle it based on the type. + /////////////////////////////// + switch(type) + { + case GPI_BM_FILE_SEND_REPLY: + success = gpiHandleSendReply(connection, transfer, headers, buffer, len); + break; + case GPI_BM_FILE_BEGIN: + success = gpiHandleBegin(connection, transfer, headers); + break; + case GPI_BM_FILE_END: + success = gpiHandleEnd(connection, transfer, headers); + break; + case GPI_BM_FILE_DATA: + success = gpiHandleData(connection, transfer, headers, buffer, len); + break; + case GPI_BM_FILE_SKIP: + success = gpiHandleSkip(connection, transfer, headers); + break; + case GPI_BM_FILE_TRANSFER_THROTTLE: + success = gpiHandleTransferThrottle(connection, transfer, headers); + break; + case GPI_BM_FILE_TRANSFER_CANCEL: + success = gpiHandleTransferCancel(connection, transfer); + break; + case GPI_BM_FILE_TRANSFER_KEEPALIVE: + success = gpiHandleTransferKeepalive(connection, transfer); + break; + default: + success = GPITrue; + } + + // Check if there was a transfer error. + /////////////////////////////////////// + if(!success) + gpiTransferError(connection, transfer); +#endif + + GSI_UNUSED(type); + GSI_UNUSED(buffer); + GSI_UNUSED(len); +} diff --git a/code/gamespy/GP/gpiTransfer.h b/code/gamespy/GP/gpiTransfer.h new file mode 100644 index 00000000..630a6e36 --- /dev/null +++ b/code/gamespy/GP/gpiTransfer.h @@ -0,0 +1,210 @@ +/* +gpiTransfer.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#ifndef _GPITRANSFER_H_ +#define _GPITRANSFER_H_ + +//INCLUDES +////////// +#include "gpi.h" + +//DEFINES +///////// +#define GPI_FILE_DIRECTORY (1 << 1) +#define GPI_FILE_SKIP (1 << 2) +#define GPI_FILE_FAILED (1 << 3) +#define GPI_FILE_COMPLETED (1 << 4) +#define GPI_FILE_CONFIRMING (1 << 5) + +#define GPI_ACCEPTED 0 +#define GPI_REJECTED 1 +#define GPI_NOT_ACCEPTING 2 + +#define GPI_SKIP_READ_ERROR 0 +#define GPI_SKIP_WRITE_ERROR 1 +#define GPI_SKIP_USER_SKIP 2 + +//TYPES +/////// +typedef enum +{ + GPITransferPinging, + GPITransferWaiting, + GPITransferTransferring, + GPITransferComplete, + GPITransferCancelled, + GPITransferNoConnection +} GPITransferState; + +typedef struct GPITransferID_s +{ + int profileid; + unsigned int count; + unsigned int time; +} GPITransferID; + +typedef struct +{ + GPITransferState state; + DArray files; + GPITransferID transferID; + int localID; + GPIBool sender; + GPProfile profile; + GPIPeer * peer; + int currentFile; + int throttle; + char * baseDirectory; + unsigned long lastSend; + char * message; + int totalSize; + int progress; + void * userData; +} GPITransfer; + +typedef struct +{ + char * path; + char * name; + +#ifdef GSI_UNICODE + unsigned short* name_W; // must have this since developers are given pointers to internal memory + unsigned short* path_W; +#endif + + int progress; + int size; + int acknowledged; + FILE * file; + int flags; + gsi_time modTime; + MD5_CTX md5; + int reason; +} GPIFile; + +//FUNCTIONS +/////////// +#ifndef NOFILE +GPResult gpiInitTransfers( + GPConnection * connection +); + +void gpiCleanupTransfers( + GPConnection * connection +); + +GPResult gpiProcessTransfers +( + GPConnection * connection +); + +GPResult gpiNewSenderTransfer +( + GPConnection * connection, + GPITransfer ** transfer, + GPProfile profile +); + +void gpiFreeTransfer +( + GPConnection * connection, + GPITransfer * transfer +); + +void gpiCancelTransfer +( + GPConnection * connection, + GPITransfer * transfer +); + +void gpiTransferError +( + GPConnection * connection, + const GPITransfer * transfer +); + +GPITransfer * gpiFindTransferByLocalID +( + GPConnection * connection, + int localID +); + +int gpiGetTransferLocalIDByIndex +( + GPConnection * connection, + int index +); + +GPIFile * gpiAddFileToTransfer +( + GPITransfer * transfer, + const char * path, + const char * name +); + +void gpiSkipFile +( + GPConnection * connection, + GPITransfer * transfer, + int file, + int reason +); + +void gpiSkipCurrentFile +( + GPConnection * connection, + GPITransfer * transfer, + int reason +); + +GPIBool gpiGetTransferFileInfo +( + FILE * file, + int * size, + gsi_time * modTime +); + +void gpiTransferPeerDestroyed +( + GPConnection * connection, + GPIPeer * peer +); + +void gpiTransfersHandlePong +( + GPConnection * connection, + GPProfile profile, + GPIPeer * peer +); +#endif + +GPResult gpiSendTransferReply +( + GPConnection * connection, + const GPITransferID * transferID, + GPIPeer * peer, + int result, + const char * message +); + +void gpiHandleTransferMessage +( + GPConnection * connection, + GPIPeer * peer, + int type, + const char * headers, + const char * buffer, + int len +); + +#endif diff --git a/code/gamespy/GP/gpiUnique.c b/code/gamespy/GP/gpiUnique.c new file mode 100644 index 00000000..2f1b8437 --- /dev/null +++ b/code/gamespy/GP/gpiUnique.c @@ -0,0 +1,234 @@ +/* +gpiUnique.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +//INCLUDES +////////// +#include "gpi.h" + +//FUNCTIONS +/////////// +static GPResult +gpiSendRegisterUniqueNick( + GPConnection * connection, + const char uniquenick[GP_UNIQUENICK_LEN], + const char cdkey[GP_CDKEY_LEN], + int operationid +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\registernick\\\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\uniquenick\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, uniquenick); + if(cdkey) + { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\cdkey\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, cdkey); + } + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\partnerid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->partnerID); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, operationid); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult gpiRegisterUniqueNick( + GPConnection * connection, + const char uniquenick[GP_UNIQUENICK_LEN], + const char cdkey[GP_CDKEY_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIOperation * operation = NULL; + GPResult result; + + // Add the operation. + ///////////////////// + CHECK_RESULT(gpiAddOperation(connection, GPI_REGISTER_UNIQUENICK, NULL, &operation, blocking, callback, param)); + + // Send a request for info. + /////////////////////////// + result = gpiSendRegisterUniqueNick(connection, uniquenick, cdkey, operation->id); + CHECK_RESULT(result); + + // Process it if blocking. + ////////////////////////// + if(blocking) + { + result = gpiProcess(connection, operation->id); + CHECK_RESULT(result); + } + + return GP_NO_ERROR; +} + +GPResult gpiProcessRegisterUniqueNick( + GPConnection * connection, + GPIOperation * operation, + const char * input +) +{ + GPICallback callback; + + // Check for an error. + ////////////////////// + if(gpiCheckForError(connection, input, GPITrue)) + return GP_SERVER_ERROR; + + // This should be \rn\. + /////////////////////// + if(strncmp(input, "\\rn\\", 4) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Call the callback. + ///////////////////// + callback = operation->callback; + if(callback.callback != NULL) + { + GPRegisterUniqueNickResponseArg * arg; + arg = (GPRegisterUniqueNickResponseArg *)gsimalloc(sizeof(GPRegisterUniqueNickResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + arg->result = GP_NO_ERROR; + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + } + + // This operation is complete. + ////////////////////////////// + gpiRemoveOperation(connection, operation); + + return GP_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Registration of cdKey now offered separately from uniquenick +static GPResult +gpiSendRegisterCdKey( + GPConnection * connection, + const char cdkey[GP_CDKEY_LEN], + int operationid +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Encrypt the cdkey (xor with random values) + const int useAlternateEncoding = 1; + char cdkeyxor[GP_CDKEY_LEN]; + char cdkeyenc[GP_CDKEYENC_LEN]; + int cdkeylen = (int)strlen(cdkey); + int i=0; + + Util_RandSeed((unsigned long)GP_XOR_SEED); + for (i=0; i < cdkeylen; i++) + { + // XOR each character with the next rand + char aRand = (char)Util_RandInt(0, 0xFF); + cdkeyxor[i] = (char)(cdkey[i] ^ aRand); + } + cdkeyxor[i] = '\0'; + + // Base 64 it (printable chars only) + B64Encode(cdkeyxor, cdkeyenc, (int)cdkeylen, useAlternateEncoding); + + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\registercdkey\\\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\cdkeyenc\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, cdkeyenc); +// gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\partnerid\\"); +// gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->partnerID); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, operationid); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult gpiRegisterCdKey( + GPConnection * connection, + const char cdkey[GP_CDKEY_LEN], + GPEnum blocking, + GPCallback callback, + void * param +) +{ + GPIOperation * operation = NULL; + GPResult result; + + // Add the operation. + ///////////////////// + CHECK_RESULT(gpiAddOperation(connection, GPI_REGISTER_CDKEY, NULL, &operation, blocking, callback, param)); + + // Send a request for info. + /////////////////////////// + result = gpiSendRegisterCdKey(connection, cdkey, operation->id); + CHECK_RESULT(result); + + // Process it if blocking. + ////////////////////////// + if(blocking) + { + result = gpiProcess(connection, operation->id); + CHECK_RESULT(result); + } + + return GP_NO_ERROR; +} + +GPResult gpiProcessRegisterCdKey( + GPConnection * connection, + GPIOperation * operation, + const char * input +) +{ + GPICallback callback; + + // Check for an error. + ////////////////////// + if(gpiCheckForError(connection, input, GPITrue)) + return GP_SERVER_ERROR; + + // This should be \rc\. + /////////////////////// + if(strncmp(input, "\\rc\\", 4) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server."); + + // Call the callback. + ///////////////////// + callback = operation->callback; + if(callback.callback != NULL) + { + GPRegisterCdKeyResponseArg * arg; + arg = (GPRegisterCdKeyResponseArg *)gsimalloc(sizeof(GPRegisterCdKeyResponseArg)); + if(arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + arg->result = GP_NO_ERROR; + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + } + + // This operation is complete. + ////////////////////////////// + gpiRemoveOperation(connection, operation); + + return GP_NO_ERROR; +} diff --git a/code/gamespy/GP/gpiUnique.h b/code/gamespy/GP/gpiUnique.h new file mode 100644 index 00000000..82dbd055 --- /dev/null +++ b/code/gamespy/GP/gpiUnique.h @@ -0,0 +1,53 @@ +/* +gpiUnique.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#ifndef _GPIUNIQUE_H_ +#define _GPIUNIQUE_H_ + +//INCLUDES +////////// +#include "gpi.h" + +//FUNCTIONS +/////////// +GPResult gpiRegisterUniqueNick( + GPConnection * connection, + const char uniquenick[GP_UNIQUENICK_LEN], + const char cdkey[GP_CDKEY_LEN], + GPEnum blocking, + GPCallback callback, + void * param +); + +GPResult gpiProcessRegisterUniqueNick( + GPConnection * connection, + GPIOperation * operation, + const char * input +); + +// Seperated registration of unique nick and cdkey +GPResult gpiRegisterCdKey( + GPConnection * connection, + const char cdkey[GP_CDKEY_LEN], + GPEnum blocking, + GPCallback callback, + void * param +); + +GPResult gpiProcessRegisterCdKey( + GPConnection * connection, + GPIOperation * operation, + const char * input +); + +#endif diff --git a/code/gamespy/GP/gpiUtility.c b/code/gamespy/GP/gpiUtility.c new file mode 100644 index 00000000..d27c9de9 --- /dev/null +++ b/code/gamespy/GP/gpiUtility.c @@ -0,0 +1,407 @@ +/* +gpiUtility.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +//INCLUDES +////////// +#include +#include +#include +#include +#include "gpi.h" + +//DEFINES +///////// +#define OUTPUT_MAX_COL 100 + +// Disable compiler warnings for issues that are unavoidable. +///////////////////////////////////////////////////////////// +#if defined(_MSC_VER) // DevStudio +// Level4, "conditional expression is constant". +// Occurs with use of the MS provided macro FD_SET +#pragma warning ( disable: 4127 ) +#endif // _MSC_VER + +//FUNCTIONS +/////////// +void +strzcpy( + char * dest, + const char * src, + size_t len +) +{ + assert(dest != NULL); + assert(src != NULL); + + strncpy(dest, src, len); + dest[len - 1] = '\0'; +} + +GPIBool +gpiCheckForError( + GPConnection * connection, + const char * input, + GPIBool callErrorCallback +) +{ + char buffer[16]; + GPIConnection * iconnection = (GPIConnection*)*connection; + + if(strncmp(input, "\\error\\", 7) == 0) + { + // Get the err code. + //////////////////// + if(gpiValueForKey(input, "\\err\\", buffer, sizeof(buffer))) + iconnection->errorCode = (GPErrorCode)atoi(buffer); + + // Get the error string. + //////////////////////// + if(!gpiValueForKey(input, "\\errmsg\\", iconnection->errorString, sizeof(iconnection->errorString))) + iconnection->errorString[0] = '\0'; + +#ifdef GSI_UNICODE + // Update the UNICODE version + UTF8ToUCS2String(iconnection->errorString, iconnection->errorString_W); +#endif + // Call the error callback? + /////////////////////////// + if(callErrorCallback) + { + GPIBool fatal = (GPIBool)(strstr(input, "\\fatal\\") != NULL); + gpiCallErrorCallback(connection, GP_SERVER_ERROR, fatal ? GP_FATAL : GP_NON_FATAL); + } + + return GPITrue; + } + + return GPIFalse; +} + +GPIBool +gpiValueForKeyWithIndex( + const char * command, + const char * key, + int * index, + char * value, + int len +) +{ + char delimiter; + const char * start; + int i; + char c; + + // Check for NULL. + ////////////////// + assert(command != NULL); + assert(key != NULL); + assert(value != NULL); + assert(len > 0); + + // Find which char is the delimiter. + //////////////////////////////////// + delimiter = key[0]; + + // Find the key - first navigate to the index + ///////////////////////////////////////////// + command += *index; + start = strstr(command, key); + if(start == NULL) + return GPIFalse; + + // Get to the start of the value. + ///////////////////////////////// + start += strlen(key); + + // Copy in the value. + ///////////////////// + len--; + for(i = 0 ; (i < len) && ((c = start[i]) != '\0') && (c != delimiter) ; i++) + { + value[i] = c; + } + value[i] = '\0'; + + // Copy back current end point for index + //////////////////////////////////////// + *index += ((start - command) + strlen(value)); + + return GPITrue; +} + +GPIBool +gpiValueForKey( + const char * command, + const char * key, + char * value, + int len +) +{ + char delimiter; + const char * start; + int i; + char c; + + // Check for NULL. + ////////////////// + assert(command != NULL); + assert(key != NULL); + assert(value != NULL); + assert(len > 0); + + // Find which char is the delimiter. + //////////////////////////////////// + delimiter = key[0]; + + // Find the key. + //////////////// + start = strstr(command, key); + if(start == NULL) + return GPIFalse; + + // Get to the start of the value. + ///////////////////////////////// + start += strlen(key); + + // Copy in the value. + ///////////////////// + len--; + for(i = 0 ; (i < len) && ((c = start[i]) != '\0') && (c != delimiter) ; i++) + { + value[i] = c; + } + value[i] = '\0'; + + return GPITrue; +} + +char * +gpiValueForKeyAlloc( + const char * command, + const char * key +) +{ + char delimiter; + const char * start; + char c; + char * value; + int len; + + // Check for NULL. + ////////////////// + assert(command != NULL); + assert(key != NULL); + + // Find which char is the delimiter. + //////////////////////////////////// + delimiter = key[0]; + + // Find the key. + //////////////// + start = strstr(command, key); + if(start == NULL) + return NULL; + + // Get to the start of the value. + ///////////////////////////////// + start += strlen(key); + + // Find the key length. + /////////////////////// + for(len = 0 ; ((c = start[len]) != '\0') && (c != delimiter) ; len++) { }; + + // Allocate the value. + ////////////////////// + value = (char *)gsimalloc((unsigned int)len + 1); + if(!value) + return NULL; + + // Copy in the value. + ///////////////////// + memcpy(value, start, (unsigned int)len); + value[len] = '\0'; + + return value; +} + +GPResult +gpiCheckSocketConnect( + GPConnection * connection, + SOCKET sock, + int * state +) +{ + int aWriteFlag = 0; + int aExceptFlag = 0; + int aReturnCode = 0; + + // Check if the connect is completed. + ///////////////////////////////////// + aReturnCode = GSISocketSelect(sock, NULL, &aWriteFlag, &aExceptFlag); + if ( gsiSocketIsError(aReturnCode)) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_HotError, + "Error connecting\n"); + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error checking for a completed connection."); + } + + if (aReturnCode > 0) + { + // Check for a failed attempt. + ////////////////////////////// + if(aExceptFlag) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_HotError, + "Connection rejected\n"); + *state = GPI_DISCONNECTED; + return GP_NO_ERROR; + } + + // Check for a successful attempt. + ////////////////////////////////// + if(aWriteFlag) + { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Notice, + "Connection accepted\n"); + *state = GPI_CONNECTED; + return GP_NO_ERROR; + } + } + + // Not connected yet. + ///////////////////// + *state = GPI_NOT_CONNECTED; + return GP_NO_ERROR; +} + +GPResult +gpiReadKeyAndValue( + GPConnection * connection, + const char * buffer, + int * index, + char key[512], + char value[512] +) +{ + int c; + int i; + char * start; + + assert(buffer != NULL); + assert(key != NULL); + assert(value != NULL); + + buffer += *index; + start = (char *)buffer; + + if(*buffer++ != '\\') + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Parse Error."); + + for(i = 0 ; (c = *buffer++) != '\\' ; i++) + { + if(c == '\0') + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Parse Error."); + if(i == 511) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Parse Error."); + *key++ = (char)c; + } + *key = '\0'; + + for(i = 0 ; ((c = *buffer++) != '\\') && (c != '\0') ; i++) + { + if(i == 511) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Parse Error."); + *value++ = (char)c; + } + *value = '\0'; + + *index += (buffer - start - 1); + + return GP_NO_ERROR; +} + +void +gpiSetError( + GPConnection * connection, + GPErrorCode errorCode, + const char * errorString +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Copy the string. + /////////////////// + strzcpy(iconnection->errorString, errorString, GP_ERROR_STRING_LEN); + +#ifdef GSI_UNICODE + // Update the unicode version + UTF8ToUCS2StringLen(iconnection->errorString, iconnection->errorString_W, GP_ERROR_STRING_LEN); +#endif + + // Set the code. + //////////////// + iconnection->errorCode = errorCode; +} + +void +gpiSetErrorString( + GPConnection * connection, + const char * errorString +) +{ + GPIConnection * iconnection = (GPIConnection*)*connection; + + // Copy the string. + /////////////////// + strzcpy(iconnection->errorString, errorString, GP_ERROR_STRING_LEN); + +#ifdef GSI_UNICODE + // Update the unicode version + UTF8ToUCS2StringLen(iconnection->errorString, iconnection->errorString_W, GP_ERROR_STRING_LEN); +#endif + +} + +void +gpiEncodeString( + const char * unencodedString, + char * encodedString +) +{ + size_t i; + const int useAlternateEncoding = 1; + + // Encrypt the password (xor with random values) + char passwordxor[GP_PASSWORD_LEN]; + size_t passwordlen = strlen(unencodedString); + + Util_RandSeed((unsigned long)GP_XOR_SEED); + for (i=0; i < passwordlen; i++) + { + // XOR each character with the next rand + char aRand = (char)Util_RandInt(0, 0xFF); + passwordxor[i] = (char)(unencodedString[i] ^ aRand); + } + passwordxor[i] = '\0'; + + // Base 64 it (printable chars only) + B64Encode(passwordxor, encodedString, (int)passwordlen, useAlternateEncoding); +} + + +// Re-enable previously disabled compiler warnings +/////////////////////////////////////////////////// +#if defined(_MSC_VER) +#pragma warning ( default: 4127 ) +#endif // _MSC_VER + diff --git a/code/gamespy/GP/gpiUtility.h b/code/gamespy/GP/gpiUtility.h new file mode 100644 index 00000000..25891109 --- /dev/null +++ b/code/gamespy/GP/gpiUtility.h @@ -0,0 +1,127 @@ +/* +gpiUtility.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#ifndef _GPIUTILITY_H_ +#define _GPIUTILITY_H_ + +//INCLUDES +////////// +#include "gpi.h" + +//DEFINES +///////// +// Buffer read size. +//////////////////// +#define GPI_READ_SIZE (16 * 1024) + +//MACROS +//////// +#define freeclear(mem) { gsifree(mem); (mem) = NULL; } + +#define Error(connection, result, string) { gpiSetErrorString(connection, string);\ + return (result);} + +#define CallbackError(connection, result, code, string) { gpiSetError(connection, code, string);\ + gpiCallErrorCallback(connection, result, GP_NON_FATAL);\ + return result;} + +#define CallbackFatalError(connection, result, code, string) { gpiSetError(connection, code, string);\ + gpiCallErrorCallback(connection, result, GP_FATAL);\ + return result;} + +#define CHECK_RESULT(result) { GPResult __result__ = (result);\ + if(__result__ != GP_NO_ERROR){\ + return __result__;}} + +//FUNCTIONS +/////////// +void +strzcpy( + char * dest, + const char * src, + size_t len // length of buffer, including space for '\0' +); + +void +gpiDebug( + GPConnection * connection, + const char * fmt, + ... +); + +GPIBool +gpiValueForKeyWithIndex( + const char * command, + const char * key, + int * index, + char * value, + int len +); + +GPIBool +gpiValueForKey( + const char * command, + const char * key, + char * value, + int len +); + +char * +gpiValueForKeyAlloc( + const char * command, + const char * key +); + +GPResult +gpiCheckSocketConnect( + GPConnection * connection, + SOCKET sock, + int * state +); + +GPResult +gpiReadKeyAndValue( + GPConnection * connection, + const char * buffer, + int * index, + char key[512], + char value[512] +); + +GPIBool +gpiCheckForError( + GPConnection * connection, + const char * input, + GPIBool callErrorCallback +); + +void +gpiSetError( + GPConnection * connection, + GPErrorCode errorCode, + const char * errorString +); + +void +gpiSetErrorString( + GPConnection * connection, + const char * errorString +); + +void +gpiEncodeString( + const char * unencodedString, + char * encodedString +); + +#endif diff --git a/code/gamespy/GP/gpstress/gpstress.c b/code/gamespy/GP/gpstress/gpstress.c new file mode 100644 index 00000000..0c1307e9 --- /dev/null +++ b/code/gamespy/GP/gpstress/gpstress.c @@ -0,0 +1,1707 @@ +// GameSpy Presence and Messaging SDK Stress Test +// Dan "Mr. Pants" Schoenblum +// Copyright 2000 GameSpy Industries, Inc + +/************* +** INCLUDES ** +*************/ +#include +#include "../gp.h" +#include "../../common/gsAvailable.h" + +/************ +** DEFINES ** +************/ +//#define CONNECTION_MANAGER "localhost" + +#define PROFILES_MAX 10000 + +#define LOG_FILE "gpstress.log" + +#define NEXT_OP_TIME_MIN 2000000 +#define NEXT_OP_TIME_MAX 2000000 + +#define IS_INSTANT_OP(op) (((op) >= FIRST_INSTANT_OP) && ((op) <= LAST_INSTANT_OP)) + +#define CONNECT_TIME_MIN 20000 +#define CONNECT_TIME_MAX 20000 + +#define SHUTDOWN_DISCONNECTS_PER_SEC 50 + +#define DEFAULT_RUN_TIME (2 * 60 * 60 * 1000) +#define DEFAULT_MAX_CONNECTED 100 + +#define SHOW_INFO_TIME 1000 + +#define DEFAULT_CONNECTS_PER_SEC 15 + +#define MAX_OTHERS 20 + +#define PRODUCT_ID_MAX 18 + +#define VALIDATION_MAX_NICKS 512 + +#define MAX_OUTSTANDING_CONNECTIONS 1 + +/********** +** TYPES ** +**********/ +#ifndef __cplusplus +typedef enum +{ + false, + true +} bool; +#endif + +typedef enum +{ + // No active operation. + /////////////////////// + OpNull = -1, + + // These operations all take a ceratin amount of time. + ////////////////////////////////////////////////////// + OpConnect, + OpNewProfile, + OpGetInfo, +#ifdef TEST_SEARCH + OpProfileSearch, + OpFindPlayers, +#endif + + // These are all instant operations. + //////////////////////////////////// +#define FIRST_INSTANT_OP OpSetInfo + OpSetInfo, + OpBuddyRequest, + OpDeleteBuddy, + OpSetStatus1, // Do setstatus 5x as often as other ops + OpSetStatus2, + OpSetStatus3, + OpSetStatus4, + OpSetStatus5, + OpSendBuddyMessage, + OpSetInvitable, +#define LAST_INSTANT_OP OpSetInvitable + + // The number of possible operations. + ///////////////////////////////////// + NumOps +} Op; + +typedef struct +{ + // The index of this profile in the list. + ///////////////////////////////////////// + int index; + + // Loaded profile info. + /////////////////////// + int userID; + char email[GP_EMAIL_LEN]; + char password[GP_PASSWORD_LEN]; + int profileID; + char nick[GP_NICK_LEN]; + bool invalid; + + // GP stuff. + //////////// + GPConnection gp; // The profile's GP object. + bool gpInitialized; // False until gpInitialize is called for this profile. + unsigned long disconnectTime; // Disconnect at this time. + GPProfile others[MAX_OTHERS]; // Remote profiles that this profile knows about. + int numOthers; // The number of others. + unsigned long connectTime; // When the connect attempt was made. + bool connected; // True when gpConnect finished successfully. + + // Op data. + /////////// + Op activeOp; // The active operation, OpNull between operations. + unsigned long nextOpTime; // At this time, pick a new random operation. + int completedOps; // Total number of operations completed. +} Profile; + +typedef struct +{ + GPResult result; + char nicks[VALIDATION_MAX_NICKS][GP_NICK_LEN]; + int numNicks; + Profile * profile; +} ValidationData; + +/************ +** GLOBALS ** +************/ +char profilesFile[MAX_PATH]; // The file from which to load the profiles. +Profile profiles[PROFILES_MAX]; // All of the loaded profiles. +int numProfiles; // The number of loaded profiles. +int numConnections; // The number of profiles that are initialized/connected. +int maxConnections; // The maximum number of connected profiles. +int highestConnections; // The highest number of connections. +int totalConnections; // The total number of connections. +int numConnected; // The number of profiles that are actually logged in. +int highestConnected; // The highest simultaneous connected. +int totalConnected; // The total number connected over the entire run. +int connectsPerSecond; // Number of connection attempts per second. +unsigned long lastConnectTime; // The last time there was a connect. +unsigned long startShutdownTime; // When to start shutting down. +bool shuttingDown; // True if we're in the process of shutting down. +unsigned long runTime; // How long to run for. + +/******************** +** PROFILE LOADING ** +********************/ +// This warning needs to be fixed for this file +// Disabling for now +#pragma warning ( disable: 4702 ) +#pragma warning ( disable: 4127 ) +bool ReadField(FILE * fp, char * field) +{ + char * str = field; + int c; + *str = '\0'; + while(1) + { + // Get a char. + ////////////// + c = fgetc(fp); + + // Check for end of field. + ////////////////////////// + if((c == ',') || (c == '\n') || (c == -1)) + { + // Check for no chars read. + /////////////////////////// + if(field == str) + return false; + + // Cap it off and return. + ///////////////////////// + *str = '\0'; + return true; + } + + *str++ = (char)c; + } + + return false; +} + +bool ReadProfile(FILE * fp, Profile * profile) +{ + char intBuffer[16]; + + // Read the user id. + //////////////////// + if(!ReadField(fp, intBuffer)) + return false; + profile->userID = atoi(intBuffer); + assert(profile->userID); + + // Read the email. + ////////////////// + ReadField(fp, profile->email); + + // Read the password. + ///////////////////// + ReadField(fp, profile->password); + + // Read the profile id. + /////////////////////// + ReadField(fp, intBuffer); + profile->profileID = atoi(intBuffer); + assert(profile->profileID); + + // Read the nick. + ///////////////// + ReadField(fp, profile->nick); + + return true; +} + +bool LoadProfiles(void) +{ +#if 0 + FILE * fp; + + // Open the file. + ///////////////// + fp = fopen(profilesFile, "rt"); + if(!fp) + { + printf("Failed to open the profiles file (%s)!\n", profilesFile); + return false; + } + + // Read the profiles. + ///////////////////// + while(ReadProfile(fp, &profiles[numProfiles])) + { + // One more. + //////////// + numProfiles++; + + // Check for too many. + ////////////////////// + if(numProfiles == PROFILES_MAX) + { + printf("Too many profiles in the profile file!\nIncrease PROFILES_MAX (%d)\n", PROFILES_MAX); + return false; + } + } + + // Close the file. + ////////////////// + fclose(fp); + + printf("Loaded %d profiles from %s\n", numProfiles, profilesFile); +#else + int i; + + for(i = 0 ; i < maxConnections ; i++) + { + strcpy(profiles[i].nick, "gpstress"); + sprintf(profiles[i].email, "gpstress%04d@gamespy.com", i); + strcpy(profiles[i].password, "gpstress"); + } + + numProfiles = i; +#endif + + return true; +} + +/********************* +** RANDOM FUNCTIONS ** +**********************/ +char RandomChar(void) +{ + static const char characters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789!@#$%^&*()_+-=~`<,>.?/:;\"'{[}]|\\"; + int index; + + index = (rand() % (sizeof(characters) - 1)); + + return characters[index]; +} + +char * RandomString(char * buffer) +{ + int i; + int len; + + // Random length. + ///////////////// + len = ((rand() % 10) + 5); + + // Set random chars. + //////////////////// + for(i = 0 ; i < len ; i++) + buffer[i] = RandomChar(); + buffer[i] = '\0'; + + return buffer; +} + +// min & max are inclusive. +/////////////////////////// +int RandomInt(int min, int max) +{ + int range = ((max - min) + 1); + return (min + ((rand() * range) / (RAND_MAX + 1))); +} + +bool RandomBool(void) +{ + return (bool)RandomInt(0, 1); +} + +GPProfile RandomOther(Profile * profile) +{ + int nIndex; + + assert(profile->numOthers > 0); + + nIndex = RandomInt(0, profile->numOthers - 1); + assert(nIndex >= 0); + assert(nIndex < profile->numOthers); + + return profile->others[nIndex]; +} + +void RandomServer(void) +{ +#if 0 + strcpy(GPConnectionManagerHostname, "mrpants"); +#else + static const char * servers[] = + { +#if 1 + "mrpants", + "mrpants1" +#else + "aphexgp1", + "aphexgp2" +#endif + }; + + const char * server = servers[RandomInt(0, (sizeof(servers) / sizeof(*servers)) - 1)]; + strcpy(GPConnectionManagerHostname, server); +#endif +} + +/************ +** GENERAL ** +************/ +void MessageOther(Profile * profile, GPProfile other) +{ +#if 1 + int pid; + + // Filter out certain profiles. + /////////////////////////////// + gpIDFromProfile(&profile->gp, other, &pid); + switch(pid) + { + case 100001: // Mr. Pants@dan@gamespy.com + case 100002: // walla@bryan@gamespy.com + case 100013: // lumberjack@jason@gamespy.com + return; + } + + gpSendBuddyMessage(&profile->gp, other, "STRESS-TEST"); +#endif +} + +void Log(const char * buffer) +{ + FILE * fp; + + static char timeBuffer[64]; + time_t thetime; + struct tm *ltime; + time(&thetime); + ltime = localtime(&thetime); + sprintf(timeBuffer, "%-2.2d/%-2.2d/%-2.2d %-2.2d:%-2.2d:%-2.2d: ", ltime->tm_mon+1, ltime->tm_mday, ltime->tm_year % 100, ltime->tm_hour, ltime->tm_min, ltime->tm_sec); + + fp = fopen(LOG_FILE, "at"); + if(fp) + { + fprintf(fp, "%s%s", timeBuffer, buffer); + fclose(fp); + } + + OutputDebugString(timeBuffer); + OutputDebugString(buffer); + + printf("%s%s", timeBuffer, buffer); +} + +/************************ +** GP GLOBAL CALLBACKS ** +************************/ +void GetInfoCallback(GPConnection * connnection, GPGetInfoResponseArg * arg, Profile * profile); +void AddOther(Profile * profile, GPProfile gpProfile); +void PrintOp(Profile * profile, const char * opString); + +void ErrorCallback(GPConnection * connection, GPErrorArg * arg, Profile * profile) +{ + char * errorCodeString; + char * resultString; + static char buffer[4098]; + + // Ignore errors that we're expecting to generate (due to randomness). + ////////////////////////////////////////////////////////////////////// + switch(arg->errorCode) + { + case GP_ADDBUDDY_ALREADY_BUDDY: + case GP_BM_NOT_BUDDY: + return; + } + + // Get a string for the result. + /////////////////////////////// +#define RESULT(x) case x: resultString = #x; break; + switch(arg->result) + { + RESULT(GP_NO_ERROR) + RESULT(GP_MEMORY_ERROR) + RESULT(GP_PARAMETER_ERROR) + RESULT(GP_NETWORK_ERROR) + RESULT(GP_SERVER_ERROR) + default: + resultString = "Unknown result!\n"; + } + + // Get a string for the error code. + /////////////////////////////////// +#define ERRORCODE(x) case x: errorCodeString = #x; break; + switch(arg->errorCode) + { + ERRORCODE(GP_GENERAL) + ERRORCODE(GP_PARSE) + ERRORCODE(GP_NOT_LOGGED_IN) + ERRORCODE(GP_BAD_SESSKEY) + ERRORCODE(GP_DATABASE) + ERRORCODE(GP_NETWORK) + ERRORCODE(GP_FORCED_DISCONNECT) + ERRORCODE(GP_CONNECTION_CLOSED) + ERRORCODE(GP_LOGIN) + ERRORCODE(GP_LOGIN_TIMEOUT) + ERRORCODE(GP_LOGIN_BAD_NICK) + ERRORCODE(GP_LOGIN_BAD_EMAIL) + ERRORCODE(GP_LOGIN_BAD_PASSWORD) + ERRORCODE(GP_LOGIN_BAD_PROFILE) + ERRORCODE(GP_LOGIN_PROFILE_DELETED) + ERRORCODE(GP_LOGIN_CONNECTION_FAILED) + ERRORCODE(GP_LOGIN_SERVER_AUTH_FAILED) + ERRORCODE(GP_LOGIN_BAD_UNIQUENICK) + ERRORCODE(GP_LOGIN_BAD_PREAUTH) + ERRORCODE(GP_NEWUSER) + ERRORCODE(GP_NEWUSER_BAD_NICK) + ERRORCODE(GP_NEWUSER_BAD_PASSWORD) + ERRORCODE(GP_NEWUSER_UNIQUENICK_INVALID) + ERRORCODE(GP_NEWUSER_UNIQUENICK_INUSE) + ERRORCODE(GP_UPDATEUI) + ERRORCODE(GP_UPDATEUI_BAD_EMAIL) + ERRORCODE(GP_NEWPROFILE) + ERRORCODE(GP_NEWPROFILE_BAD_NICK) + ERRORCODE(GP_NEWPROFILE_BAD_OLD_NICK) + ERRORCODE(GP_UPDATEPRO) + ERRORCODE(GP_UPDATEPRO_BAD_NICK) + ERRORCODE(GP_ADDBUDDY) + ERRORCODE(GP_ADDBUDDY_BAD_FROM) + ERRORCODE(GP_ADDBUDDY_BAD_NEW) + ERRORCODE(GP_ADDBUDDY_ALREADY_BUDDY) + ERRORCODE(GP_AUTHADD) + ERRORCODE(GP_AUTHADD_BAD_FROM) + ERRORCODE(GP_AUTHADD_BAD_SIG) + ERRORCODE(GP_STATUS) + ERRORCODE(GP_BM) + ERRORCODE(GP_BM_NOT_BUDDY) + ERRORCODE(GP_GETPROFILE) + ERRORCODE(GP_GETPROFILE_BAD_PROFILE) + ERRORCODE(GP_DELBUDDY) + ERRORCODE(GP_DELBUDDY_NOT_BUDDY) + ERRORCODE(GP_DELPROFILE) + ERRORCODE(GP_DELPROFILE_LAST_PROFILE) + ERRORCODE(GP_SEARCH) + ERRORCODE(GP_SEARCH_CONNECTION_FAILED) + ERRORCODE(GP_CHECK) + ERRORCODE(GP_CHECK_BAD_EMAIL) + ERRORCODE(GP_CHECK_BAD_NICK) + ERRORCODE(GP_CHECK_BAD_PASSWORD) + ERRORCODE(GP_REVOKE) + ERRORCODE(GP_REVOKE_NOT_BUDDY) + ERRORCODE(GP_REGISTERUNIQUENICK) + ERRORCODE(GP_REGISTERUNIQUENICK_TAKEN) + ERRORCODE(GP_REGISTERUNIQUENICK_RESERVED) + ERRORCODE(GP_REGISTERUNIQUENICK_BAD_NAMESPACE) + default: + errorCodeString = "Unknown error code!\n"; + } + + // Print out the info. + ////////////////////// + if(arg->fatal) + sprintf(buffer, "%04d: FATAL ERROR", profile->index); + else + sprintf(buffer, "%04d: ERROR", profile->index); + sprintf(buffer + strlen(buffer), ", RESULT: %s (%d)", resultString, arg->result); + sprintf(buffer + strlen(buffer), ", ERROR CODE: %s (0x%X)", errorCodeString, arg->errorCode); + sprintf(buffer + strlen(buffer), ", ERROR STRING: %s\n", arg->errorString); + Log(buffer); + + // Disconnect. + ////////////// + profile->disconnectTime = 0; + GSI_UNUSED(connection); +} + +void RecvBuddyStatusCallback(GPConnection * connection, GPRecvBuddyStatusArg * arg, Profile * profile) +{ + PrintOp(profile, "RecvBuddyStatusCallback"); + +#if 1 + // Get info half the time. + ////////////////////////// + if(RandomBool()) + gpGetInfo(&profile->gp, arg->profile, GP_DONT_CHECK_CACHE, GP_NON_BLOCKING, GetInfoCallback, profile); + + // Send a message sometimes. + //////////////////////////// + if(RandomInt(0, 9) == 0) + MessageOther(profile, arg->profile); + + // Add the other. + ///////////////// + AddOther(profile, arg->profile); +#endif + GSI_UNUSED(connection); +} + +#if 1 +void RecvBuddyRequestCallback(GPConnection * connection, GPRecvBuddyRequestArg * arg, Profile * profile) +{ + PrintOp(profile, "RecvBuddyRequestCallback"); + + // Get info half the time. + ////////////////////////// + if(RandomBool()) + gpGetInfo(&profile->gp, arg->profile, GP_DONT_CHECK_CACHE, GP_NON_BLOCKING, GetInfoCallback, profile); + + // Accept it half the time. + /////////////////////////// + if(RandomBool()) + gpAuthBuddyRequest(&profile->gp, arg->profile); + + // Add the other. + ///////////////// + AddOther(profile, arg->profile); + GSI_UNUSED(connection); +} + +void RecvBuddyMessageCallback(GPConnection * connection, GPRecvBuddyMessageArg * arg, Profile * profile) +{ + PrintOp(profile, "RecvBuddyMessageCallback"); + + // Get info half the time. + ////////////////////////// + if(RandomBool()) + gpGetInfo(&profile->gp, arg->profile, GP_DONT_CHECK_CACHE, GP_NON_BLOCKING, GetInfoCallback, profile); + + // Reply sometimes. + /////////////////// + if(RandomInt(0, 9) == 0) + MessageOther(profile, arg->profile); + + // Add the other. + ///////////////// + AddOther(profile, arg->profile); + GSI_UNUSED(connection); +} + +void RecvGameInviteCallback(GPConnection * connection, GPRecvGameInviteArg * arg, Profile * profile) +{ + PrintOp(profile, "RecvGameInviteCallback"); + + // Get info half the time. + ////////////////////////// + if(RandomBool()) + gpGetInfo(&profile->gp, arg->profile, GP_DONT_CHECK_CACHE, GP_NON_BLOCKING, GetInfoCallback, profile); + + // Send a message sometimes. + //////////////////////////// + if(RandomInt(0, 9) == 0) + MessageOther(profile, arg->profile); + + // Add the other. + ///////////////// + AddOther(profile, arg->profile); + GSI_UNUSED(connection); +} +#endif + +/******************** +** GP OP CALLBACKS ** +********************/ +void EndOp(Profile * profile); + +const int RemoteAuthProfiles[] = +{ + 58214083, + 58214074, + 58214075, + 58214076, + 58214077, + 58214078, + 58214079, + 58214080, + 58214081, + 58214082 +}; + +void ConnectCallback(GPConnection * connection, GPConnectResponseArg * arg, Profile * profile) +{ + if(arg->result == GP_NO_ERROR) + { + // Finished connecting. + /////////////////////// + profile->connected = true; + numConnected++; + totalConnected++; + highestConnected = max(numConnected, highestConnected); + + if(arg->profile != RemoteAuthProfiles[profile->index % 10]) + { + char buffer[4096]; + gsi_time ms = (current_time() - profile->connectTime); + sprintf(buffer, "%d: XXX Connection ProfileID Error (%d != %d) [%d]: %d.%03ds\n", totalConnected, arg->profile, RemoteAuthProfiles[profile->index % 10], (profile->index % 10), ms / 1000, ms % 1000); + Log(buffer); + } + +#if 1 + // log the connection + { + char buffer[256]; + gsi_time ms = (current_time() - profile->connectTime); + sprintf(buffer, "%sConnected: %d time: %d.%03ds\n", (ms>=1000)?"XX ":"", totalConnected, ms / 1000, ms % 1000); + Log(buffer); + } +#endif + +#if 1 + // always disconnect right away + profile->disconnectTime = 0; +#endif + } + else + { +#if 1 + // log the error + { + char buffer[4096]; + gsi_time ms = (current_time() - profile->connectTime); + sprintf(buffer, "%d: XXX Connection Failed [%d]: %d.%03ds\n", totalConnected, (profile->index % 10), ms / 1000, ms % 1000); + Log(buffer); + } +#endif + // Disconnect this profile. + /////////////////////////// + profile->disconnectTime = 0; + } + + // End the op. + ////////////// + EndOp(profile); + GSI_UNUSED(connection); +} + +void GetInfoCallback(GPConnection * connection, GPGetInfoResponseArg * arg, Profile * profile) +{ + if(arg->result != GP_NO_ERROR) + { + printf("%04d: gpGetInfo failed\n", profile->index); + return; + } + GSI_UNUSED(connection); +} + +void FindPlayersCallback(GPConnection * connection, GPFindPlayersResponseArg * arg, Profile * profile) +{ + int i; + + if(arg->result != GP_NO_ERROR) + { + printf("%04d: gpFindPlayers failed\n", profile->index); + return; + } + + // Get all of their info. + ///////////////////////// + for(i = 0 ; i < arg->numMatches ; i++) + gpGetInfo(&profile->gp, arg->matches[i].profile, GP_DONT_CHECK_CACHE, GP_NON_BLOCKING, GetInfoCallback, profile); + + // Invite someone. + ////////////////// + if(arg->numMatches > 0) + { + int index = RandomInt(0, arg->numMatches - 1); + int productID = RandomInt(1, PRODUCT_ID_MAX); + + gpInvitePlayer(&profile->gp, arg->matches[index].profile, productID,NULL); + } + GSI_UNUSED(connection); +} + +void ProfileSearchCallback(GPConnection * connection, GPProfileSearchResponseArg * arg, Profile * profile) +{ + int i; + + if(arg->result != GP_NO_ERROR) + { + printf("%04d: gpProfileSearch failed\n", profile->index); + return; + } + + // Get all of their info. + ///////////////////////// + for(i = 0 ; i < arg->numMatches ; i++) + gpGetInfo(&profile->gp, arg->matches[i].profile, GP_DONT_CHECK_CACHE, GP_NON_BLOCKING, GetInfoCallback, profile); + GSI_UNUSED(connection); +} + +/***************** +** OP FUNCTIONS ** +*****************/ +void AddOther(Profile * profile, GPProfile gpProfile) +{ + int i; + + // Check for full. + ////////////////// + if(profile->numOthers == MAX_OTHERS) + return; + + // Check if we already have this one. + ///////////////////////////////////// + for(i = 0 ; i < profile->numOthers ; i++) + if(profile->others[i] == gpProfile) + return; + + // Add it. + ////////// + profile->others[profile->numOthers] = gpProfile; + profile->numOthers++; +} + +unsigned long GetRandomOpTime(void) +{ + // Between NEXT_OP_TIME_MIN and NEXT_OP_TIME_MAX. + ///////////////////////////////////////////////// + return RandomInt(NEXT_OP_TIME_MIN, NEXT_OP_TIME_MAX); +} + +Op GetRandomOp(void) +{ + // Between 1 (skip OpConnect) and (NumOps - 1). + /////////////////////////////////////////////// + Op op = (Op)(((rand() * (NumOps - 1)) / (RAND_MAX + 1)) + 1); + + assert(op > OpConnect); + assert(op < NumOps); + + return op; +} + +void NewOp(Profile * profile) +{ + // Is this the first op? + //////////////////////// + if(profile->completedOps == 0) + { + // The first op is always connect. + ////////////////////////////////// + profile->activeOp = OpConnect; + } + else + { +#if 1 + // Pick a random op. + //////////////////// + profile->activeOp = GetRandomOp(); +#else + // Always do status. + //////////////////// + profile->activeOp = OpSetStatus1; +#endif + } +} + +void EndOp(Profile * profile) +{ + // No active op. + //////////////// + profile->activeOp = OpNull; + + // Set the next op time. + //////////////////////// + profile->nextOpTime = (current_time() + GetRandomOpTime()); + + // One more op completed. + ///////////////////////// + profile->completedOps++; +} + +void PrintOp(Profile * profile, const char * opString) +{ +#if 0 + static char buffer[256]; + sprintf(buffer, "%04d: %s\n", profile->index, opString); + OutputDebugString(buffer); +#endif + GSI_UNUSED(profile); + GSI_UNUSED(opString); +} + +void StartOp(Profile * profile) +{ + int i; + int num; + int intArray[16]; + char string1[16]; + char string2[16]; + + // Handle based on op. + ////////////////////// + switch(profile->activeOp) + { + case OpNull: + assert(0); + printf("%04d: Tried to start null op\n", profile->index); + break; + + case OpConnect: + PrintOp(profile, "OpConnect"); + + // Start the connect. + ///////////////////// +#if 0 + RandomServer(); +#endif +#if 0 + gpConnect(&profile->gp, profile->nick, profile->email, profile->password, (RandomInt(0, 9) == 0)?GP_FIREWALL:GP_NO_FIREWALL, GP_NON_BLOCKING, ConnectCallback, profile); +#else + { + const char * authtokens[] = + { + "GMT/61LHe4Yu+pHI0eDXu8GMdc51iXjsNvcz1GGoWJ9orpnbZIbp3CVBQddeSZOSq58", + "GMTcUM+iiQuzaw8crHxBFPCVV6J9V14KnSNHnHbQIXGwN6XpKv97yqSbOqxrgIWZCeZ", + "GMTxiKFKsnGdOeBaJxmJOQBNSqbm9zOjbu/cTiqpBJp1pqqOAnI1Jh81TPlWmFuvt0h", + "GMTqUg6QEerwnwi87fZQMcfzQ0xA/zNC96VCtx6GavTv7wzDtGpOWxJHUkm20qMxVsg", + "GMTV2uHalvcDgtvBsz62QF1ifbCJJ93QONO9TNGVGlDghs6ocL2qOiQVqNJS7LvTZ39", + "GMTq9Z7Tg/tkEMzqEODuQdNSQPgt5MdAwH26+lQGHliL9n3nH0b0D38fTFWRbkmk4K9", + "GMTwHyinDuomW0XmQswNyVpgdlQvnuGCR1it+kHCnUDmR5CG3svSufrDcjuHdlwfVON", + "GMTBZgPikENuiEON0by04RMR112ShZsDctzVbqljZ3ZufDvyugygztRlKIM9gyLwSdy", + "GMTmHNpmNufV9WuGIGET8E0Xd//56eYWO4mDaD+4KebWQWvc8CqWbqSeII6x9BxQMxQ", + "GMTyI447fNIYVh1vdI6aErka4TzJcYWctTU/Dzi2GK+4J3Vf/VAqQOs43R73I7dyX+u" + }; + const char * partnerchallenges[] = + { + "WI[A)IuA", + "RNd$)q)0", + "LQTi;4#X", + "AOA)(57=", + "ob=0XiWJ", + "mRnTENJ@", + "9%@A+NKA", + "L=0O)Lg2", + "YR6[Zz%B", + "$kWZ%]~@" + }; + int tokenIndex = (profile->index % 10); + gpConnectPreAuthenticated(&profile->gp, authtokens[tokenIndex], partnerchallenges[tokenIndex], GP_FIREWALL, GP_NON_BLOCKING, ConnectCallback, profile); + } +#endif + break; + + case OpNewProfile: + PrintOp(profile, "OpNewProfile"); + + // Pretend this op didn't happen, another will be picked next frame. + //////////////////////////////////////////////////////////////////// + profile->activeOp = OpNull; + break; + + case OpGetInfo: + PrintOp(profile, "OpGetInfo"); + + // Do we know of any other profiles? + //////////////////////////////////// + if(profile->numOthers > 0) + gpGetInfo(&profile->gp, RandomOther(profile), GP_DONT_CHECK_CACHE, GP_NON_BLOCKING, GetInfoCallback, profile); + else + profile->activeOp = OpNull; + break; + +#ifdef TEST_SEARCH + case OpProfileSearch: + PrintOp(profile, "OpProfileSearch"); + + // Do a search. + /////////////// + num = RandomInt(0, 3); + if(num == 0) + gpProfileSearch(&profile->gp, "pants", "dan@gamespy.com", NULL, NULL, 0, GP_NON_BLOCKING, ProfileSearchCallback, profile); + else if(num == 1) + gpProfileSearch(&profile->gp, "crt", NULL, NULL, NULL, 0, GP_NON_BLOCKING, ProfileSearchCallback, profile); + else + gpProfileSearch(&profile->gp, "STRESS-TEST", NULL, NULL, NULL, 0, GP_NON_BLOCKING, ProfileSearchCallback, profile); + break; + + case OpFindPlayers: + PrintOp(profile, "OpFindPlayers"); + + // Find some players. + ///////////////////// + gpFindPlayers(&profile->gp, RandomInt(1, PRODUCT_ID_MAX), GP_NON_BLOCKING, FindPlayersCallback, profile); + break; +#endif + + case OpSetInfo: + PrintOp(profile, "OpSetInfo"); + + // Set some random info. + //////////////////////// + gpSetInfos(&profile->gp, GP_FIRSTNAME, "STRESS-TEST"); + gpSetInfos(&profile->gp, GP_LASTNAME, "STRESS-TEST"); + gpSetInfoi(&profile->gp, GP_ICQUIN, RandomInt(100000, 40000000)); + gpSetInfos(&profile->gp, GP_HOMEPAGE, "STRESS-TEST"); + gpSetInfod(&profile->gp, GP_BIRTHDAY, RandomInt(1, 28), RandomInt(1, 12), RandomInt(1940, 1990)); + break; + + case OpBuddyRequest: + PrintOp(profile, "OpBuddyRequest"); + +#if 1 + // Pretend this op didn't happen, another will be picked next frame. + //////////////////////////////////////////////////////////////////// + profile->activeOp = OpNull; +#else + // Send someone a buddy request. + //////////////////////////////// + if(profile->numOthers > 0) + gpSendBuddyRequest(&profile->gp, RandomOther(profile), "STRESS-TEST"); +#endif + break; + + case OpDeleteBuddy: + PrintOp(profile, "OpDeleteBuddy"); + + // Pretend this op didn't happen, another will be picked next frame. + //////////////////////////////////////////////////////////////////// + profile->activeOp = OpNull; + break; + + case OpSetStatus1: + case OpSetStatus2: + case OpSetStatus3: + case OpSetStatus4: + case OpSetStatus5: + PrintOp(profile, "OpSetStatus"); + + // Set the status to some random stuff. + /////////////////////////////////////// + gpSetStatus(&profile->gp, GP_ONLINE, RandomString(string1), RandomString(string2)); + break; + + case OpSendBuddyMessage: + PrintOp(profile, "OpSendBuddyMessage"); + + // Send someone a message. + ///////////////////////// + if(profile->numOthers > 0) + MessageOther(profile, RandomOther(profile)); + break; + + case OpSetInvitable: + PrintOp(profile, "OpSetInvitable"); + + // Get the num games. + ///////////////////// + num = RandomInt(0, 10); + + // Get the product ids. + /////////////////////// + for(i = 0 ; i < num ; i++) + intArray[i] = RandomInt(1, PRODUCT_ID_MAX); + + // Set the games. + ///////////////// + gpSetInvitableGames(&profile->gp, num, intArray); + break; + + default: + assert(0); + printf("%04d: Tried to start unknown op: %d\n", profile->index, profile->activeOp); + break; + } + + // Was this an instant op? + ////////////////////////// + if(IS_INSTANT_OP(profile->activeOp)) + { + // End it. + ////////// + EndOp(profile); + } +} + +/********************** +** PROFILE FUNCTIONS ** +**********************/ +void ProcessProfile(Profile * profile) +{ + DWORD now = current_time(); + + // Is GP initialized? + ///////////////////// + if(profile->gpInitialized) + { + // Do GP processing. + //////////////////// + gpProcess(&profile->gp); + + // Has a connection attempt been going for 2 minutes? + ///////////////////////////////////////////////////// + if((profile->activeOp == OpConnect) && ((current_time() - profile->connectTime) > (200 * 60 * 1000))) + { + char buffer[256]; + gsi_time ms = (current_time() - profile->connectTime); + sprintf(buffer, "%04d: XXX Excessive Connect Time: %d.%03ds\n", profile->index, ms / 1000, ms % 1000); + Log(buffer); + } + + // Check for disconnect. + //////////////////////// + if((now > profile->disconnectTime) && ((profile->activeOp != OpConnect) || shuttingDown)) + { + // Cleanup GP for this profile. + /////////////////////////////// + if(profile->connected) + { + numConnected--; + gpDisconnect(&profile->gp); + } + gpDestroy(&profile->gp); + + // We're no longer initialized/connected. + ///////////////////////////////////////// + profile->gpInitialized = false; + numConnections--; + } + // Is there no active op? + ///////////////////////// + else if(!shuttingDown && (profile->activeOp == OpNull)) + { + // Is it time for a new op? + /////////////////////////// +#if 1 + if(now > profile->nextOpTime) +#else + if((now > profile->nextOpTime) && (profile->completedOps == 0)) +#endif + { + // Select a new op. + /////////////////// + NewOp(profile); + + // Start the op. + //////////////// + StartOp(profile); + } + } + } +} + +Profile * GetUninitializedProfile(void) +{ + int index; + + // Loop until we get an uninitialized (and valid) profile. + ////////////////////////////////////////////////////////// + do + { + // Get an index between 0 and (numProfiles - 1). + //////////////////////////////////////////////// + index = ((rand() * numProfiles) / (RAND_MAX + 1)); + assert(index >= 0); + assert(index < numProfiles); + } + while(profiles[index].gpInitialized || profiles[index].invalid); + + return &profiles[index]; +} + +void InitializeProfile(Profile * profile) +{ + GPResult result; + + // Init the profile's GP object. + //////////////////////////////// + result = gpInitialize(&profile->gp, 0, 0, GP_PARTNERID_GAMESPY); + if(result != GP_NO_ERROR) + { + printf("%04d: Failed to initialize GP for %s@%s\n", profile->index, profile->nick, profile->email); + return; + } + + // Set the GP global callbacks. + /////////////////////////////// + gpSetCallback(&profile->gp, GP_ERROR, ErrorCallback, profile); + gpSetCallback(&profile->gp, GP_RECV_BUDDY_STATUS, RecvBuddyStatusCallback, profile); +#if 1 + gpSetCallback(&profile->gp, GP_RECV_BUDDY_REQUEST, RecvBuddyRequestCallback, profile); + gpSetCallback(&profile->gp, GP_RECV_BUDDY_MESSAGE, RecvBuddyMessageCallback, profile); + gpSetCallback(&profile->gp, GP_RECV_GAME_INVITE, RecvGameInviteCallback, profile); +#endif + + // We're initialized. + ///////////////////// + profile->gpInitialized = true; + + // Not connected yet. + ///////////////////// + profile->connected = false; + + // Remember when we tried to connect. + ///////////////////////////////////// + profile->connectTime = current_time(); + + // Haven't checked yet. + /////////////////////// + profile->invalid = false; + + // When to disconnect. + ////////////////////// + profile->disconnectTime = current_time(); + profile->disconnectTime += (((rand() * (CONNECT_TIME_MAX - CONNECT_TIME_MIN)) / RAND_MAX) + CONNECT_TIME_MIN); + + // No others yet. + ///////////////// + profile->numOthers = 0; + + // Op stuff. + //////////// + profile->activeOp = OpNull; + profile->nextOpTime = 0; + profile->completedOps = 0; + + // It's a connection, but we're not connected. + ////////////////////////////////////////////// + numConnections++; + totalConnections++; + highestConnections = max(highestConnections, numConnections); +} + +/*************** +** VALIDATION ** +***************/ +void GetUserNicksCallback(GPConnection * connection, GPGetUserNicksResponseArg * arg, ValidationData * validationData) +{ + int i; + + // Copy the result. + /////////////////// + validationData->result = arg->result; + + // Check for error. + /////////////////// + if(arg->result != GP_NO_ERROR) + { + printf("%04d: gpGetUserNicks failed\n", validationData->profile->index); + return; + } + + // Copy the nicks. + ////////////////// + validationData->numNicks = min(arg->numNicks, VALIDATION_MAX_NICKS); + for(i = 0 ; i < arg->numNicks ; i++) + strcpy(validationData->nicks[i], arg->nicks[i]); + GSI_UNUSED(connection); +} + +void NewUserResponse(GPConnection * connection, GPNewUserResponseArg * arg, void * param) +{ + GSI_UNUSED(connection); + GSI_UNUSED(arg); + GSI_UNUSED(param); +} + +bool Validate(void) +{ + int i; + GPConnection gp; + char email[GP_EMAIL_LEN] = ""; + Profile * profile; + ValidationData validationData; + + validationData.profile = NULL; + validationData.result = GP_NO_ERROR; + validationData.numNicks = 0; + // Set this here so ctrl-c can cancel the validation. + ///////////////////////////////////////////////////// + startShutdownTime = ULONG_MAX; + + // Init GP. + /////////// + if(gpInitialize(&gp, 0, 0, GP_PARTNERID_GAMESPY) != GP_NO_ERROR) + { + printf("Failed to initialize GP for validation\n"); + return false; + } +/* + for(i = 41 ; i <= 1000 ; i++) + { + sprintf(email, "gpstress%04d@gamespy.com", i); + gpNewUser(&gp, "gpstress", email, "gpstress", GP_BLOCKING, NewUserResponse, NULL); + printf("%d\n", i); + } +*/ + // Loop through all the profiles. + ///////////////////////////////// + for(i = 0 ; i < numProfiles ; i++) + { + // Check for shutdown. + ////////////////////// + if(current_time() > startShutdownTime) + return false; + + // Cache the profile. + ///////////////////// + profile = &profiles[i]; + + // Is the e-mail address different than the previous one? + ///////////////////////////////////////////////////////// + if(strcmp(email, profile->email) != 0) + { + // Copy the e-mail. + /////////////////// + strcpy(email, profile->email); + + // Put the profile in the validation struct. + //////////////////////////////////////////// + validationData.profile = profile; + + // Show what we're doing. + ///////////////////////// + printf("%04d: Getting nicks for: %s\n", profile->index, email); + + // Get the nicks for this e-mail. + ///////////////////////////////// + gpGetUserNicks(&gp, email, profile->password, GP_BLOCKING, GetUserNicksCallback, &validationData); + } + + // Only do this if the validation succeeded. + //////////////////////////////////////////// + if(validationData.result == GP_NO_ERROR) + { + int n; + bool match = false; + + // Loop through the nicks. + ////////////////////////// + for(n = 0 ; (n < validationData.numNicks) && !match ; n++) + { + // Does this nick match the one being validated? + //////////////////////////////////////////////// + if(strcasecmp(profile->nick, validationData.nicks[n]) == 0) + match = true; + } + + // Set invalid based on match. + ////////////////////////////// + profile->invalid = !match; + } + else + { + // Probably invalid e-mail. + /////////////////////////// + profile->invalid = true; + } + + // Show if we got something invalid. + //////////////////////////////////// + if(profile->invalid) + { + static char buffer[256]; + sprintf(buffer, "%s (%d) : %04d: Invalid profile (%s@%s)\n", profilesFile, (profile->index + 1), profile->index, profile->nick, profile->email); + printf(buffer); + OutputDebugString(buffer); + } + } + + return true; +} + +/******************* +** MAIN FUNCTIONS ** +*******************/ +#ifdef WIN32 +BOOL WINAPI CtrlHandlerRoutine(DWORD dwCtrlType) +{ + // Start shutting down soon. + //////////////////////////// + startShutdownTime = 0; + + GSI_UNUSED(dwCtrlType); + // Handled. + return TRUE; +} +#endif + +bool Initialize(void) +{ + int i; + Profile * profile; + DWORD now; + GSIACResult aResult = GSIACWaiting; + + // Seed the random number generator. + //////////////////////////////////// + srand(time(NULL)); + + // Perform the availability check + GSIStartAvailableCheck("gmtest"); + while(aResult == GSIACWaiting) + aResult = GSIAvailableCheckThink(); + if (aResult != GSIACAvailable) + { + printf("Backend services are not available.\r\n"); + return false; + } + + +#ifdef WIN32 + // Set the ctrl-c handler. + ////////////////////////// + SetConsoleCtrlHandler(CtrlHandlerRoutine, TRUE); +#endif + + // Set our own hostname. + //////////////////////// +#ifdef CONNECTION_MANAGER + if(CONNECTION_MANAGER[0]) + strcpy(GPConnectionManagerHostname, CONNECTION_MANAGER); +#endif + + // Load the profiles. + ///////////////////// + if(!LoadProfiles()) + return false; + + // Cap off the max connected profiles. + ////////////////////////////////////// + if(maxConnections > numProfiles) + maxConnections = numProfiles; + + // Get the current time. + //////////////////////// + now = current_time(); + + // Initalize profile stuff. + /////////////////////////// + for(i = 0 ; i < numProfiles ; i++) + { + // Cache the profile pointer. + /////////////////////////////. + profile = &profiles[i]; + + // Set its index. + ///////////////// + profile->index = i; + + // No op yet. + ///////////// + profile->activeOp = OpNull; + + // Print out a message every 100. + ///////////////////////////////// +#if 0 + if(((i + 1) % 100) == 0) + { + printf("%d/%d profiles initialized\n", i + 1, numProfiles); + msleep(1); + } +#endif + } + + return true; +} + +void Shutdown(void) +{ + int i; + + // Cleanup all the GP objects. + ////////////////////////////// + for(i = 0 ; i < numProfiles ; i++) + { + // Check if GP was intialized. + ////////////////////////////// + if(profiles[i].gpInitialized) + { + // Destroy the GP object. + ///////////////////////// + gpDestroy(&profiles[i].gp); + + // Print out a message every 100. + ///////////////////////////////// + if(((i + 1) % 100) == 0) + { + printf("%d/%d profiles destroyed\n", i + 1, numProfiles); + msleep(1); + } + } + } +} + +void StartShutdown(unsigned long now) +{ + Profile * profile; + int i; + + // Loop through all the profiles. + ///////////////////////////////// + for(i = 0 ; i < numProfiles ; i++) + { + // Cache the profile pointer. + ///////////////////////////// + profile = &profiles[i]; + + // Check if its GP object is initialized. + ///////////////////////////////////////// + if(profile->gpInitialized) + { +#if 0 + // Is it not actually connected yet? + //////////////////////////////////// + if(!profile->connected) + { + char buffer[64]; + + // How long has it been waiting? + //////////////////////////////// + sprintf(buffer, "%04d: Waiting %ds for connection attempt\n", profile->index, (current_time() - profile->connectTime) / 1000); + Log(buffer); + } +#endif + + // Set when to disconnect, + ////////////////////////// + profile->disconnectTime = (now + ((rand() * ((numConnections / SHUTDOWN_DISCONNECTS_PER_SEC) * 1000)) / RAND_MAX)); + } + } + + // We're shutting down, don't add new connections, etc. + /////////////////////////////////////////////////////// + shuttingDown = true; +} + +void Frame(unsigned long now) +{ + int i; + + // Is it time to connect a new profile? + /////////////////////////////////////// + if(!shuttingDown && (numConnections < maxConnections) && ((numConnections - numConnected) < MAX_OUTSTANDING_CONNECTIONS)) + { + Profile * profile; + unsigned long timeDiff; + int numConnects; + + // How long since the last connect? + /////////////////////////////////// + timeDiff = (now - lastConnectTime); + + // How many connects should we do? + ////////////////////////////////// + numConnects = ((timeDiff * connectsPerSecond) / 1000); + numConnects = min(numConnects, (MAX_OUTSTANDING_CONNECTIONS - (numConnections - numConnected))); + + // Do the connects. + /////////////////// + for(i = 0 ; i < numConnects ; i++) + { + // Need to check because it can change in the loop. + /////////////////////////////////////////////////// + if(numConnections < maxConnections) + { + // Pick a random profile. + ///////////////////////// + profile = GetUninitializedProfile(); + + // Init it. + /////////// + InitializeProfile(profile); + + // Remember when we did the connect(s). + /////////////////////////////////////// + lastConnectTime = now; + } + } + } + + // Loop through all the profiles. + ///////////////////////////////// + for(i = 0 ; i < numProfiles ; i++) + { + // Process the profile. + /////////////////////// + ProcessProfile(&profiles[i]); + } +} + +void Run(void) +{ + bool done; + unsigned long now; +#if 0 + unsigned long nextInfoTime = 0; +#endif + // Get the current time. + //////////////////////// + now = current_time(); + + // Set when to shut down. + ///////////////////////// + startShutdownTime = (now + runTime); + + // Make up the last connect time. + ///////////////////////////////// + lastConnectTime = (now - 100); + + // Loop until we're done. + ///////////////////////// + for(done = false ; !done ; ) + { + // Get the current time. + //////////////////////// + now = current_time(); + + // Are we shutting down? + //////////////////////// + if(shuttingDown) + { + // Are we done? + /////////////// + if(numConnections == 0) + { + done = true; + } + } + else if(now > startShutdownTime) + { + // Time to start shutting down. + /////////////////////////////// + StartShutdown(now); + } + +#if 0 + // Is it time to print some info? + ///////////////////////////////// + if(now > nextInfoTime) + { + static char buffer[256]; + + // Set when to next show info. + ////////////////////////////// + nextInfoTime = (now + SHOW_INFO_TIME); + + // Show the connection numbers. + /////////////////////////////// + sprintf(buffer, "INFO: Connections: %d current, %d highest, %d total\n", numConnections, highestConnections, totalConnections); + Log(buffer); + + // Show the connected numbers. + ////////////////////////////// + sprintf(buffer, "INFO: Connected: %d current, %d highest, %d total\n", numConnected, highestConnected, totalConnected); + Log(buffer); + + // Show that we're shutting down, or when we're shutting down. + ////////////////////////////////////////////////////////////// + if(shuttingDown) + sprintf(buffer, "INFO: Shutting down\n"); + else + { + unsigned long totalSeconds = ((startShutdownTime - now) / 1000); + unsigned long hours, minutes, seconds; + seconds = (totalSeconds % 60); + minutes = (totalSeconds / 60 % 60); + hours = (totalSeconds / 60 / 60); + sprintf(buffer, "INFO: Shut down in %d:%02d:%02d\n", hours, minutes, seconds); + } + Log(buffer); + } +#endif + + // Run a frame. + /////////////// + Frame(now); + + // Take a break. + //////////////// + msleep(10); + } +} + +bool ParseArgs(int argc, char ** argv) +{ + int i; +/* + // Check for the file index. + //////////////////////////// + if(argc < 2) + { + printf("Usage: %s \n", argv[0]); + return false; + } + sprintf(profilesFile, "stress%d.txt", atoi(argv[1])); +*/ + // Set defaults. + //////////////// + maxConnections = DEFAULT_MAX_CONNECTED; + runTime = DEFAULT_RUN_TIME; + connectsPerSecond = DEFAULT_CONNECTS_PER_SEC; + + // Check args. + ////////////// + for(i = 1 ; i < argc ; i++) + { + // Check for max connected profiles. + //////////////////////////////////// + if(strcasecmp(argv[i], "-m") == 0) + { + if(++i < argc) + { + maxConnections = atoi(argv[i]); + if(maxConnections <= 0) + maxConnections = DEFAULT_MAX_CONNECTED; + } + } + // Check for how long to run. + ///////////////////////////// + else if(strcasecmp(argv[i], "-t") == 0) + { + if(++i < argc) + { + runTime = (atoi(argv[i]) * 1000); + if(runTime <= 0) + runTime = DEFAULT_RUN_TIME; + } + } + // Check for connections per second. + //////////////////////////////////// + else if(strcasecmp(argv[i], "-s") == 0) + { + if(++i < argc) + { + connectsPerSecond = atoi(argv[i]); + if(connectsPerSecond <= 0) + connectsPerSecond = DEFAULT_CONNECTS_PER_SEC; + } + } + } + + return true; +} + +int main(int argc, char ** argv) +{ + // Parse args. + ////////////// + if(!ParseArgs(argc, argv)) + return 1; + + // Initialize. + ////////////// + if(!Initialize()) + return 1; + +#if 0 + // Validate the loaded profiles. + //////////////////////////////// + if(!Validate()) + return 1; +#endif + + // Do all that stuff. + ///////////////////// + Run(); + + // Cleanup. + /////////// + Shutdown(); + + return 0; +} \ No newline at end of file diff --git a/code/gamespy/Peer/PeerLobby/ConnectPage.cpp b/code/gamespy/Peer/PeerLobby/ConnectPage.cpp new file mode 100644 index 00000000..6194c4f5 --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/ConnectPage.cpp @@ -0,0 +1,762 @@ +// ConnectPage.cpp : implementation file +// + +#include "stdafx.h" +#include "PeerLobby.h" +#include "ConnectPage.h" +#include "TitlePage.h" +#include "LobbyWizard.h" +#include "GroupPage.h" +#include "StagingPage.h" +#include "CreatePage.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +CConnectPage * ConnectPage; + +///////////////////////////////////////////////////////////////////////////// +// CConnectPage property page + +IMPLEMENT_DYNCREATE(CConnectPage, CPropertyPage) + +// Set page defaults. +///////////////////// +CConnectPage::CConnectPage() : CPropertyPage(CConnectPage::IDD) +{ + //{{AFX_DATA_INIT(CConnectPage) + m_nick = _T("PeerPlayer"); + m_title = _T("gmtest"); + m_groupRooms = FALSE; + m_key = _T("HA6zkS"); + //}}AFX_DATA_INIT +} + +CConnectPage::~CConnectPage() +{ +} + +void CConnectPage::DoDataExchange(CDataExchange* pDX) +{ + CPropertyPage::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CConnectPage) + DDX_Text(pDX, IDC_NICK, m_nick); + DDX_Text(pDX, IDC_TITLE, m_title); + DDX_Check(pDX, IDC_GROUP_ROOMS, m_groupRooms); + DDX_Text(pDX, IDC_KEY, m_key); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CConnectPage, CPropertyPage) + //{{AFX_MSG_MAP(CConnectPage) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +// Used for printing callback info to the debug window. +/////////////////////////////////////////////////////// +static void PrintCallback(const char * callback) +{ + CString buffer; + buffer = callback; + buffer += " (callback)\n"; + OutputDebugString(buffer); +} +static void PrintStringParam(const char * param, const char * value) +{ + CString buffer; + buffer += " "; + buffer += param; + buffer += " = \""; + buffer += value; + buffer += "\"\n"; + OutputDebugString(buffer); +} +static void PrintIntParam(const char * param, int value) +{ + char string[16]; + sprintf(string, "%d", value); + PrintStringParam(param, string); +} +static void PrintBoolParam(const char * param, PEERBool value) +{ + if(value) + PrintStringParam(param, "True"); + else + PrintStringParam(param, "False"); +} +static void PrintRoomParam(const char * param, RoomType roomType) +{ + if(roomType == TitleRoom) + PrintStringParam("roomType", "Title Room"); + else if(roomType == GroupRoom) + PrintStringParam("roomType", "Group Room"); + else if(roomType == StagingRoom) + PrintStringParam("roomType", "Staging Room"); + else + PrintStringParam("roomType", ""); + + GSI_UNUSED(param); +} + +/////////////////////////// +// Peer Global Callbacks // +/////////////////////////// + +// Called if peer gets disconnected from chat. +////////////////////////////////////////////// +static void DisconnectedCallback +( + PEER peer, + const char * reason, + void * param +) +{ + PrintCallback("Disconnected"); + PrintStringParam("reason", reason); + + CString text = "You were disconnected from the server: "; + if(reason) + text += reason; + Wizard->MessageBox(text); + + GSI_UNUSED(param); + GSI_UNUSED(peer); +} + +// Called when a message arrives in a room. +/////////////////////////////////////////// +static void RoomMessageCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * message, + MessageType messageType, + void * param +) +{ + PrintCallback("RoomMessage"); + PrintStringParam("nick", nick); + PrintStringParam("message", message); + + // Form the message. + //////////////////// + CString buffer; + if(messageType == NormalMessage) + buffer.Format("%s: %s", nick, message); + else if(messageType == ActionMessage) + buffer.Format("%s %s", nick, message); + else + buffer.Format("*%s* %s", nick, message); + + // Send it to the right place. + ////////////////////////////// + if(roomType == StagingRoom) + { + StagingPage->m_chatWindow.InsertString(-1, buffer); + StagingPage->m_chatWindow.SetTopIndex(StagingPage->m_chatWindow.GetCount() - 1); + } + else if((roomType == GroupRoom) || !Wizard->m_groupRooms) + { + GroupPage->m_chatWindow.InsertString(-1, buffer); + GroupPage->m_chatWindow.SetTopIndex(GroupPage->m_chatWindow.GetCount() - 1); + } + else + { + TitlePage->m_chatWindow.InsertString(-1, buffer); + TitlePage->m_chatWindow.SetTopIndex(TitlePage->m_chatWindow.GetCount() - 1); + } + + GSI_UNUSED(param); + GSI_UNUSED(peer); +} + +// Called when a staging room player's ready state changes. +/////////////////////////////////////////////////////////// +static void ReadyChangedCallback +( + PEER peer, + const char * nick, + PEERBool ready, + void * param +) +{ + PrintCallback("ReadyChanged"); + PrintStringParam("nick", nick); + PrintBoolParam("ready", ready); + + // Update his ready state. + ////////////////////////// + StagingPage->UpdatePlayerReady(nick, (BOOL)ready); + + // Check if we should enable the finish button. + /////////////////////////////////////////////// + if(Wizard->m_hosting) + StagingPage->CheckEnableFinish(); + + GSI_UNUSED(param); + GSI_UNUSED(peer); +} + +// Called when the game launches in a staging room we've joined. +//////////////////////////////////////////////////////////////// +static void GameStartedCallback +( + PEER peer, + SBServer server, + const char * message, + void * param +) +{ + const char * address = SBServerGetPublicAddress(server); + + PrintCallback("GameStarted"); + PrintStringParam("IP", address); + PrintStringParam("message", message); + + char buffer[256]; + sprintf(buffer, + "The game has been started.\n" + "The host is at %s.\n" + "Message: %s\n" + "Hit OK to return to the staging room.", + address, + message); + StagingPage->MessageBox(buffer); + + GSI_UNUSED(param); + GSI_UNUSED(peer); +} + +// Called when a player joins a room we're in. +////////////////////////////////////////////// +static void PlayerJoinedCallback +( + PEER peer, + RoomType roomType, + const char * nick, + void * param +) +{ + PrintCallback("PlayerJoined"); + PrintRoomParam("roomType", roomType); + PrintStringParam("nick", nick); + + if(roomType == StagingRoom) + { + StagingPage->UpdatePlayerPing(nick, 9999); + + if(Wizard->m_hosting) + Wizard->SetWizardButtons(PSWIZB_BACK | PSWIZB_DISABLEDFINISH); + } + else if((roomType == GroupRoom) || !Wizard->m_groupRooms) + GroupPage->UpdatePlayerPing(nick, 9999); + else + TitlePage->UpdatePlayerPing(nick, 9999); + + GSI_UNUSED(param); + GSI_UNUSED(peer); +} + +// Called when a player leaves a room we're in. +/////////////////////////////////////////////// +static void PlayerLeftCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * reason, + void * param +) +{ + PrintCallback("PlayerLeft"); + PrintRoomParam("roomType", roomType); + PrintStringParam("nick", nick); + + if(roomType == StagingRoom) + { + StagingPage->RemovePlayer(nick); + + if(Wizard->m_hosting) + StagingPage->CheckEnableFinish(); + } + else if((roomType == GroupRoom) || !Wizard->m_groupRooms) + GroupPage->RemovePlayer(nick); + else + TitlePage->RemovePlayer(nick); + + GSI_UNUSED(param); + GSI_UNUSED(reason); + GSI_UNUSED(peer); +} + +// Called when a player's nickname changes. +/////////////////////////////////////////// +static void PlayerChangedNickCallback +( + PEER peer, + RoomType roomType, + const char * oldNick, + const char * newNick, + void * param +) +{ + PrintCallback("PlayerChangedNick"); + PrintRoomParam("roomType", roomType); + PrintStringParam("oldNick", oldNick); + PrintStringParam("newNick", newNick); + + if(roomType == StagingRoom) + StagingPage->ChangePlayerNick(oldNick, newNick); + else if((roomType == GroupRoom) || !Wizard->m_groupRooms) + GroupPage->ChangePlayerNick(oldNick, newNick); + else + TitlePage->ChangePlayerNick(oldNick, newNick); + + GSI_UNUSED(param); + GSI_UNUSED(peer); +} + +// Called whenever a new ping time is available. +//////////////////////////////////////////////// +static void PingCallback +( + PEER peer, + const char * nick, + int ping, + void * param +) +{ + PrintCallback("Ping"); + PrintStringParam("nick", nick); + PrintIntParam("ping", ping); + + if(Wizard->m_groupRooms && TitlePage->m_hWnd && TitlePage->FindPlayer(nick) != -1) + TitlePage->UpdatePlayerPing(nick, ping); + if(GroupPage->m_hWnd && GroupPage->FindPlayer(nick) != -1) + GroupPage->UpdatePlayerPing(nick, ping); + if(StagingPage->m_hWnd && StagingPage->FindPlayer(nick) != -1) + StagingPage->UpdatePlayerPing(nick, ping); + + GSI_UNUSED(param); + GSI_UNUSED(peer); +} + +// Called whenever a crossping is available. +//////////////////////////////////////////// +static void CrossPingCallback +( + PEER peer, + const char * nick1, + const char * nick2, + int crossPing, + void * param +) +{ + PrintCallback("CrossPing"); + PrintStringParam("nick1", nick1); + PrintStringParam("nick2", nick2); + PrintIntParam("crossPing", crossPing); + + GSI_UNUSED(param); + GSI_UNUSED(peer); +} + +// Player info for server reporting. +//////////////////////////////////// +const int NumPlayers = 2; +const char * Players[] = { "Bob", "Joe" }; + +// Converts a key index to a string. +//////////////////////////////////// +static const char * KeyToString(int key) +{ + return qr2_registered_key_list[key]; +} + +// Converts a key type to a string. +/////////////////////////////////// +static const char * KeyTypeToString(qr2_key_type type) +{ + switch(type) + { + case key_server: + return "server"; + case key_player: + return "player"; + case key_team: + return "team"; + } + + ASSERT(0); + return "Unkown key type"; +} + +// Converts an error code to a string. +////////////////////////////////////// +static const char * ErrorTypeToString(qr2_error_t error) +{ + switch(error) + { + case e_qrnoerror: + return "noerror"; + case e_qrwsockerror: + return "wsockerror"; + case e_qrbinderror: + return "rbinderror"; + case e_qrdnserror: + return "dnserror"; + case e_qrconnerror: + return "connerror"; + } + + ASSERT(0); + return "Unknown error type"; +} + +// Reports server keys. +/////////////////////// +static void QRServerKeyCallback +( + PEER peer, + int key, + qr2_buffer_t buffer, + void * param +) +{ + PrintCallback("QRServerKey"); + PrintStringParam("key", KeyToString(key)); + + switch(key) + { + case GAMEVER_KEY: + qr2_buffer_add(buffer, "1.01"); + break; + case HOSTNAME_KEY: + qr2_buffer_add(buffer, (LPCSTR)CreatePage->m_name); + break; + case NUMPLAYERS_KEY: + qr2_buffer_add_int(buffer, NumPlayers); + break; + case MAXPLAYERS_KEY: + qr2_buffer_add_int(buffer, NumPlayers + 2); + break; + default: + qr2_buffer_add(buffer, ""); + break; + } + GSI_UNUSED(param); + GSI_UNUSED(peer); +} + +// Reports player keys. +/////////////////////// +static void QRPlayerKeyCallback +( + PEER peer, + int key, + int index, + qr2_buffer_t buffer, + void * param +) +{ + PrintCallback("QRPlayerKey"); + PrintStringParam("key", KeyToString(key)); + PrintIntParam("index", index); + + switch(key) + { + case PLAYER__KEY: + qr2_buffer_add(buffer, Players[index]); + break; + case PING__KEY: + qr2_buffer_add_int(buffer, rand() % 100); + break; + default: + qr2_buffer_add(buffer, ""); + break; + } + GSI_UNUSED(param); + GSI_UNUSED(peer); +} + +// Reports team keys. +///////////////////// +static void QRTeamKeyCallback +( + PEER peer, + int key, + int index, + qr2_buffer_t buffer, + void * param +) +{ + PrintCallback("QRTeamKey"); + PrintStringParam("key", KeyToString(key)); + PrintIntParam("index", index); + + // we don't report teams, so this shouldn't get called + qr2_buffer_add(buffer, ""); + + GSI_UNUSED(param); + GSI_UNUSED(peer); +} + +// Reports supported keys. +////////////////////////// +static void QRKeyListCallback +( + PEER peer, + qr2_key_type type, + qr2_keybuffer_t keyBuffer, + void * param +) +{ + PrintCallback("QRKeyList"); + PrintStringParam("type", KeyTypeToString(type)); + + // register the keys we use + switch(type) + { + case key_server: + qr2_keybuffer_add(keyBuffer, GAMEVER_KEY); + break; + case key_player: + // no custom player keys + break; + case key_team: + // no custom team keys + break; + } + GSI_UNUSED(param); + GSI_UNUSED(peer); +} + +// Reports the players and team counts. +/////////////////////////////////////// +static int QRCountCallback +( + PEER peer, + qr2_key_type type, + void * param +) +{ + PrintCallback("QRCount"); + PrintStringParam("type", KeyTypeToString(type)); + + if(type == key_player) + return NumPlayers; + else if(type == key_team) + return 0; + + return 0; + + GSI_UNUSED(param); + GSI_UNUSED(peer); +} + +// Called when there is a server reporting error. +///////////////////////////////////////////////// +static void QRAddErrorCallback +( + PEER peer, + qr2_error_t error, + char * errorString, + void * param +) +{ + PrintCallback("QRKeyList"); + PrintStringParam("type", ErrorTypeToString(error)); + PrintStringParam("errorString", errorString); + + CString str; + str.Format("Peer: Server Reporting error: %s (%s)", ErrorTypeToString(error), errorString); + Wizard->MessageBox(str); + + GSI_UNUSED(param); + GSI_UNUSED(peer); +} + +// Switching to this page. +////////////////////////// +BOOL CConnectPage::OnSetActive() +{ + // Set which buttons the wizard shows. + ////////////////////////////////////// + Wizard->SetWizardButtons(PSWIZB_NEXT); + +//PEERSTART + if(Wizard->m_peer) + { + // Shutdown peer. + ///////////////// + peerShutdown(Wizard->m_peer); + Wizard->m_peer = NULL; + } +//PEERSTOP + + return CPropertyPage::OnSetActive(); +} + +// Called when peerConnect completes. +///////////////////////////////////// +static PEERBool connectSuccess; +static void ConnectCallback +( + PEER peer, + PEERBool success, + int failureReason, + void * param +) +{ + connectSuccess = success; + + if(!success) + ConnectPage->MessageBox("Failed to connect."); + + GSI_UNUSED(param); + GSI_UNUSED(peer); + GSI_UNUSED(param); + GSI_UNUSED(failureReason); +} + +// Called if there's an error with the nick. +//////////////////////////////////////////// +static void NickErrorCallback +( + PEER peer, + int type, + const char * nick, + int numSuggestedNicks, + const char ** suggestedNicks, + void * param +) +{ + connectSuccess = PEERFalse; + + // Let the user know. + ///////////////////// + if(type == PEER_IN_USE) + ConnectPage->MessageBox("That nickname is already taken, please choose another one."); + else + ConnectPage->MessageBox("That nickname contains at least 1 invalid character, please choose another one."); + +//PEERSTART + // Cancel the connect. + // Could display a window here asking for an alternate nick. + //////////////////////////////////////////////////////////// + peerRetryWithNick(peer, NULL); +//PEERSTOP + + GSI_UNUSED(param); + GSI_UNUSED(suggestedNicks); + GSI_UNUSED(numSuggestedNicks); + GSI_UNUSED(nick); +} + +// Going to the next page. +////////////////////////// +LRESULT CConnectPage::OnWizardNext() +{ + // Check data. + ////////////// + UpdateData(); + Wizard->m_groupRooms = m_groupRooms; + if(m_nick == "") + { + MessageBox("You must enter a nickname."); + return -1; + } + if(m_title == "") + { + MessageBox("You must enter a title."); + return -1; + } + +//PEERSTART + // Check that the game's backend is available. + ////////////////////////////////////////////// + GSIACResult result; + GSIStartAvailableCheck(m_title); + while((result = GSIAvailableCheckThink()) == GSIACWaiting) + msleep(5); + if(result != GSIACAvailable) + { + MessageBox("The backend is not available\n"); + return -1; + } + + // Setup the callbacks. + /////////////////////// + PEERCallbacks callbacks; + memset(&callbacks, 0, sizeof(PEERCallbacks)); + callbacks.disconnected = DisconnectedCallback; + callbacks.readyChanged = ReadyChangedCallback; + callbacks.roomMessage = RoomMessageCallback; + callbacks.gameStarted = GameStartedCallback; + callbacks.playerJoined = PlayerJoinedCallback; + callbacks.playerLeft = PlayerLeftCallback; + callbacks.playerChangedNick = PlayerChangedNickCallback; + callbacks.ping = PingCallback; + callbacks.crossPing = CrossPingCallback; + callbacks.qrServerKey = QRServerKeyCallback; + callbacks.qrPlayerKey = QRPlayerKeyCallback; + callbacks.qrTeamKey = QRTeamKeyCallback; + callbacks.qrKeyList = QRKeyListCallback; + callbacks.qrCount = QRCountCallback; + callbacks.qrAddError = QRAddErrorCallback; + callbacks.param = NULL; + + // Init peer. + ///////////// + Wizard->m_peer = peerInitialize(&callbacks); + if(!Wizard->m_peer) + { + MessageBox("Error initializing peer."); + return -1; + } + + // Setup which rooms to do pings and cross-pings in. + //////////////////////////////////////////////////// + PEERBool pingRooms[NumRooms]; + PEERBool crossPingRooms[NumRooms]; + pingRooms[TitleRoom] = PEERFalse; + pingRooms[GroupRoom] = PEERTrue; + pingRooms[StagingRoom] = PEERTrue; + crossPingRooms[TitleRoom] = PEERFalse; + crossPingRooms[GroupRoom] = PEERFalse; + crossPingRooms[StagingRoom] = PEERTrue; + + // Set the title. + ///////////////// + if(!peerSetTitle(Wizard->m_peer, m_title, m_key, m_title, m_key, 0, 30, PEERFalse, pingRooms, crossPingRooms)) + { + MessageBox("Error setting title."); + peerShutdown(Wizard->m_peer); + Wizard->m_peer = NULL; + return -1; + } + + // Connect to chat. + /////////////////// + Wizard->StartHourglass(); + peerConnect(Wizard->m_peer, m_nick, 0, NickErrorCallback, ConnectCallback, NULL, PEERTrue); + Wizard->StopHourglass(); + if(!connectSuccess) + { + MessageBox("Error connecting."); + peerShutdown(Wizard->m_peer); + Wizard->m_peer = NULL; + return -1; + } +//PEERSTOP + + if(!m_groupRooms) + return IDD_GROUP_PAGE; + + return CPropertyPage::OnWizardNext(); +} diff --git a/code/gamespy/Peer/PeerLobby/ConnectPage.h b/code/gamespy/Peer/PeerLobby/ConnectPage.h new file mode 100644 index 00000000..e4b6c986 --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/ConnectPage.h @@ -0,0 +1,57 @@ +#if !defined(AFX_CONNECTPAGE_H__70C3619F_ED14_49F8_9155_9F96147FF4C2__INCLUDED_) +#define AFX_CONNECTPAGE_H__70C3619F_ED14_49F8_9155_9F96147FF4C2__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// ConnectPage.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CConnectPage dialog + +class CConnectPage : public CPropertyPage +{ + DECLARE_DYNCREATE(CConnectPage) + +// Construction +public: + CConnectPage(); + ~CConnectPage(); + +// Dialog Data + //{{AFX_DATA(CConnectPage) + enum { IDD = IDD_CONNECT_PAGE }; + CString m_nick; + CString m_title; + BOOL m_groupRooms; + CString m_key; + //}}AFX_DATA + + +// Overrides + // ClassWizard generate virtual function overrides + //{{AFX_VIRTUAL(CConnectPage) + public: + virtual BOOL OnSetActive(); + virtual LRESULT OnWizardNext(); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + // Generated message map functions + //{{AFX_MSG(CConnectPage) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() + +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +extern CConnectPage * ConnectPage; + +#endif // !defined(AFX_CONNECTPAGE_H__70C3619F_ED14_49F8_9155_9F96147FF4C2__INCLUDED_) diff --git a/code/gamespy/Peer/PeerLobby/CreatePage.cpp b/code/gamespy/Peer/PeerLobby/CreatePage.cpp new file mode 100644 index 00000000..473352bd --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/CreatePage.cpp @@ -0,0 +1,115 @@ +// CreatePage.cpp : implementation file +// + +#include "stdafx.h" +#include "PeerLobby.h" +#include "CreatePage.h" +#include "LobbyWizard.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +CCreatePage * CreatePage; + +///////////////////////////////////////////////////////////////////////////// +// CCreatePage property page + +IMPLEMENT_DYNCREATE(CCreatePage, CPropertyPage) + +// Set page defaults. +///////////////////// +CCreatePage::CCreatePage() : CPropertyPage(CCreatePage::IDD) +{ + //{{AFX_DATA_INIT(CCreatePage) + m_name = _T("My Server"); + m_maxPlayers = 8; + //}}AFX_DATA_INIT +} + +CCreatePage::~CCreatePage() +{ +} + +void CCreatePage::DoDataExchange(CDataExchange* pDX) +{ + CPropertyPage::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CCreatePage) + DDX_Text(pDX, IDC_NAME, m_name); + DDX_Text(pDX, IDC_MAX_PLAYERS, m_maxPlayers); + DDV_MinMaxInt(pDX, m_maxPlayers, 2, 9999); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CCreatePage, CPropertyPage) + //{{AFX_MSG_MAP(CCreatePage) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +// Switching to this page. +////////////////////////// +BOOL CCreatePage::OnSetActive() +{ + // Show the back and next buttons. + ////////////////////////////////// + Wizard->SetWizardButtons(PSWIZB_BACK | PSWIZB_NEXT); + + // We're hosting the room. + ////////////////////////// + Wizard->m_hosting = TRUE; + + return CPropertyPage::OnSetActive(); +} + +// Going to the previous page. +////////////////////////////// +LRESULT CCreatePage::OnWizardBack() +{ + // Not hosting anymore. + /////////////////////// + Wizard->m_hosting = FALSE; + + return CPropertyPage::OnWizardBack(); +} + +// Called when the staging room has been created (or the attempt failed). +///////////////////////////////////////////////////////////////////////// +static PEERBool createStagingSuccess; +static void CreateStagingRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + void * param +) +{ + createStagingSuccess = success; + + GSI_UNUSED(param); + GSI_UNUSED(roomType); + GSI_UNUSED(result); + GSI_UNUSED(peer); +} + +// Going to the next page. +////////////////////////// +LRESULT CCreatePage::OnWizardNext() +{ + // Update the data. + /////////////////// + UpdateData(); + +//PEERSTART + // Create the room. + /////////////////// + Wizard->StartHourglass(); + peerCreateStagingRoom(Wizard->m_peer, m_name, m_maxPlayers, NULL, CreateStagingRoomCallback, NULL, PEERTrue); + Wizard->StopHourglass(); +//PEERSTOP + + return CPropertyPage::OnWizardNext(); +} diff --git a/code/gamespy/Peer/PeerLobby/CreatePage.h b/code/gamespy/Peer/PeerLobby/CreatePage.h new file mode 100644 index 00000000..7c08ec3a --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/CreatePage.h @@ -0,0 +1,55 @@ +#if !defined(AFX_CREATEPAGE_H__4FC1C8CA_9B4C_47F6_B226_C211DC74D504__INCLUDED_) +#define AFX_CREATEPAGE_H__4FC1C8CA_9B4C_47F6_B226_C211DC74D504__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// CreatePage.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CCreatePage dialog + +class CCreatePage : public CPropertyPage +{ + DECLARE_DYNCREATE(CCreatePage) + +// Construction +public: + CCreatePage(); + ~CCreatePage(); + +// Dialog Data + //{{AFX_DATA(CCreatePage) + enum { IDD = IDD_CREATE_PAGE }; + CString m_name; + int m_maxPlayers; + //}}AFX_DATA + + +// Overrides + // ClassWizard generate virtual function overrides + //{{AFX_VIRTUAL(CCreatePage) + public: + virtual BOOL OnSetActive(); + virtual LRESULT OnWizardBack(); + virtual LRESULT OnWizardNext(); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + // Generated message map functions + //{{AFX_MSG(CCreatePage) + //}}AFX_MSG + DECLARE_MESSAGE_MAP() + +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +extern CCreatePage * CreatePage; + +#endif // !defined(AFX_CREATEPAGE_H__4FC1C8CA_9B4C_47F6_B226_C211DC74D504__INCLUDED_) diff --git a/code/gamespy/Peer/PeerLobby/GroupPage.cpp b/code/gamespy/Peer/PeerLobby/GroupPage.cpp new file mode 100644 index 00000000..7defec5e --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/GroupPage.cpp @@ -0,0 +1,668 @@ +// GroupPage.cpp : implementation file +// + +#include "stdafx.h" +#include "PeerLobby.h" +#include "GroupPage.h" +#include "LobbyWizard.h" +#include "ConnectPage.h" +#include "TitlePage.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +CGroupPage * GroupPage; + +#define COL_NAME 0 +#define COL_PING 1 +#define COL_RUNNING 2 +#define COL_NUM_PLAYERS 3 + +///////////////////////////////////////////////////////////////////////////// +// CGroupPage property page + +IMPLEMENT_DYNCREATE(CGroupPage, CPropertyPage) + +// Set page defaults. +///////////////////// +CGroupPage::CGroupPage() : CPropertyPage(CGroupPage::IDD) +{ + //{{AFX_DATA_INIT(CGroupPage) + m_message = _T(""); + //}}AFX_DATA_INIT +} + +CGroupPage::~CGroupPage() +{ +} + +void CGroupPage::DoDataExchange(CDataExchange* pDX) +{ + CPropertyPage::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CGroupPage) + DDX_Control(pDX, IDC_PLAYERS, m_players); + DDX_Control(pDX, IDC_GAMES, m_games); + DDX_Control(pDX, IDC_CHAT_WINDOW, m_chatWindow); + DDX_Control(pDX, IDC_PROGRESS, m_progress); + DDX_Text(pDX, IDC_MESSAGE, m_message); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CGroupPage, CPropertyPage) + //{{AFX_MSG_MAP(CGroupPage) + ON_BN_CLICKED(IDC_CREATE, OnCreate) + ON_NOTIFY(NM_CLICK, IDC_GAMES, OnClickGames) + ON_NOTIFY(LVN_BEGINDRAG, IDC_GAMES, OnBegindragGames) + ON_NOTIFY(NM_DBLCLK, IDC_GAMES, OnDblclkGames) + ON_WM_DESTROY() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +// Gets called to maintain the game list. +///////////////////////////////////////// +static void ListingGamesCallback +( + PEER peer, + PEERBool success, + const char * name, + SBServer server, + PEERBool staging, + int msg, + int progress, + void * param +) +{ + LVITEM item; + + // Check for failure. + ///////////////////// + if(!success) + { + Wizard->MessageBox("Listing games failed!"); + Wizard->EndDialog(IDOK); + return; + } + + // Cache pointers. + ////////////////// + CListCtrl * games = &GroupPage->m_games; + CListedGame * game = NULL; + BOOL doUpdate = FALSE; + int nIndex = -1; + + // Set the progress. + //////////////////// + GroupPage->m_progress.SetPos(progress); + + // Handle the message based on its type. + //////////////////////////////////////// + if(msg == PEER_CLEAR) + { + GroupPage->ClearGames(); + } + else if(msg == PEER_ADD) + { + // Add this to the list. + //////////////////////// + game = new CListedGame; + game->server = server; + game->name = name; + game->staging = staging; + game->ping = SBServerGetPing(server); + game->numPlayers = SBServerGetIntValue(server, "numplayers", 0); + game->maxPlayers = SBServerGetIntValue(server, "maxplayers", 0); + + nIndex = games->InsertItem(LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE, + 0, + (LPCTSTR)name, + 0, 0, + staging ? Wizard->m_stagingRoomIndex : Wizard->m_runningGameIndex, + (LPARAM)game); + if(nIndex == -1) + { + delete game; + return; + } + + doUpdate = TRUE; + } + else if(msg == PEER_UPDATE) + { + nIndex = GroupPage->FindListedGame(server); + if(nIndex != -1) + { + // Update values. + ///////////////// + game = (CListedGame *)games->GetItemData(nIndex); + game->name = name; + game->staging = staging; + game->ping = SBServerGetPing(server); + game->numPlayers = SBServerGetIntValue(server, "numplayers", 0); + game->maxPlayers = SBServerGetIntValue(server, "maxplayers", 0); + + // Update the list. + /////////////////// + item.mask = LVIF_IMAGE; + item.iItem = nIndex; + item.iSubItem = 0; + item.iImage = staging ? Wizard->m_stagingRoomIndex : Wizard->m_runningGameIndex; + games->SetItem(&item); + + doUpdate = TRUE; + } + } + else if(msg == PEER_REMOVE) + { + nIndex = GroupPage->FindListedGame(server); + if(nIndex != -1) + { + delete (CListedGame *)games->GetItemData(nIndex); + games->DeleteItem(nIndex); + } + } + + if(doUpdate) + { + item.mask = LVIF_TEXT; + item.iItem = nIndex; + item.iSubItem = COL_PING; + char buffer[32]; + sprintf(buffer, "%d", game->ping); + item.pszText = buffer; + games->SetItem(&item); + item.iSubItem = COL_RUNNING; + if(staging) + item.pszText = "No"; + else + item.pszText = "Yes"; + games->SetItem(&item); + item.iSubItem = COL_NUM_PLAYERS; + sprintf(buffer, "%d/%d", game->numPlayers, game->maxPlayers); + item.pszText = buffer; + games->SetItem(&item); + } + + GSI_UNUSED(param); + GSI_UNUSED(peer); +} + +// Used to list the players in the room. +//////////////////////////////////////// +static void EnumPlayersCallback +( + PEER peer, + PEERBool success, + RoomType roomType, + int index, + const char * nick, + int flags, + void * param +) +{ + if(!success) + { + Wizard->MessageBox("Error listing players."); + return; + } + + if(index == -1) + return; + + GroupPage->UpdatePlayerPing(nick, 9999); + + GSI_UNUSED(param); + GSI_UNUSED(flags); + GSI_UNUSED(roomType); + GSI_UNUSED(peer); +} + +static PEERBool joinSuccess; +static void JoinTitleRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + void * param +) +{ + joinSuccess = success; + + if(success) + { +//PEERSTART + // List the players in the room. + //////////////////////////////// + peerEnumPlayers(Wizard->m_peer, TitleRoom, EnumPlayersCallback, NULL); +//PEERSTOP + } + + GSI_UNUSED(param); + GSI_UNUSED(roomType); + GSI_UNUSED(result); + GSI_UNUSED(peer); +} + +// Switching to this page. +////////////////////////// +BOOL CGroupPage::OnSetActive() +{ + // Start off with only a back button. + ///////////////////////////////////// + Wizard->SetWizardButtons(PSWIZB_BACK); + + // Rename the next button. + ////////////////////////// + ::SetWindowText(Wizard->m_nextButtonWnd, "&Join >"); + + // Clear the progress bar. + ////////////////////////// + m_progress.SetPos(0); + + // Clear the game list. + /////////////////////// + ClearGames(); + + // Clear the chat log. + ////////////////////// + m_chatWindow.ResetContent(); + +//PEERSTART + if(!Wizard->m_groupRooms) + { + // Join the title room. + /////////////////////// + Wizard->StartHourglass(); + peerJoinTitleRoom(Wizard->m_peer, NULL, JoinTitleRoomCallback, NULL, PEERTrue); + Wizard->StopHourglass(); + if(!joinSuccess) + { + MessageBox("Error joining the title room."); + return FALSE; + } + } + else + { + // List the players in the room. + //////////////////////////////// + peerEnumPlayers(Wizard->m_peer, GroupRoom, EnumPlayersCallback, NULL); + } + + // Start listing games. + //////////////////////// + unsigned char fields[] = { GAMEVER_KEY, NUMPLAYERS_KEY, MAXPLAYERS_KEY }; + peerStartListingGames(Wizard->m_peer, fields, sizeof(fields), NULL, ListingGamesCallback, NULL); +//PEERSTOP + + // Setup the player's box columns. + ////////////////////////////////// + if(Wizard->m_groupRooms) + { + m_players.InsertColumn(COL_NAME, "Player", LVCFMT_LEFT, 70); + m_players.InsertColumn(COL_PING, "Ping", LVCFMT_LEFT, 50); + } + else + m_players.InsertColumn(COL_NAME, "Player", LVCFMT_LEFT, 120); + + return CPropertyPage::OnSetActive(); +} + +// Leaving this page. +///////////////////// +BOOL CGroupPage::OnKillActive() +{ +//PEERSTART + if(!Wizard->m_groupRooms) + peerLeaveRoom(Wizard->m_peer, TitleRoom, NULL); +//PEERSTOP + + // Delete player columns. + ///////////////////////// + m_players.DeleteColumn(0); + if(Wizard->m_groupRooms) + m_players.DeleteColumn(1); + + // Reset the "next" button's name. + ////////////////////////////////// + ::SetWindowText(Wizard->m_nextButtonWnd, "&Next >"); + + // Clear the players. + ///////////////////// + m_players.DeleteAllItems(); + + return CPropertyPage::OnKillActive(); +} + +// Going to the next page. +////////////////////////// +LRESULT CGroupPage::OnWizardNext() +{ + // Update vars. + /////////////// + UpdateData(); + + // Make sure something was selected. + //////////////////////////////////// + int nIndex = m_games.GetSelectionMark(); + if(nIndex == -1) + { + MessageBox("You must have a game selected."); + return -1; + } + + // Join the game. + ///////////////// + JoinGame(nIndex); + + return (LRESULT)1; +} + +LRESULT CGroupPage::OnWizardBack() +{ +//PEERSTART + // Leave the group room. + //////////////////////// + if(Wizard->m_groupRooms) + peerLeaveRoom(Wizard->m_peer, GroupRoom, NULL); + else + return IDD_CONNECT_PAGE; +//PEERSTOP + + return CPropertyPage::OnWizardBack(); +} + +// Init the page. +///////////////// +BOOL CGroupPage::OnInitDialog() +{ + CPropertyPage::OnInitDialog(); + + // Set the games columns. + ///////////////////////// + m_games.InsertColumn(COL_NAME, "Server Name", LVCFMT_LEFT, 170); + m_games.InsertColumn(COL_PING, "Ping", LVCFMT_LEFT, 50); + m_games.InsertColumn(COL_RUNNING, "Running", LVCFMT_LEFT, 60); + m_games.InsertColumn(COL_NUM_PLAYERS, "Num Players", LVCFMT_LEFT, 75); + ListView_SetExtendedListViewStyle(m_games.m_hWnd,LVS_EX_FULLROWSELECT); + + // Image list setup. + //////////////////// + m_games.SetImageList(&Wizard->m_imageList, LVSIL_SMALL); + + return TRUE; +} + +// Cleanup the page. +//////////////////// +void CGroupPage::OnDestroy() +{ + CPropertyPage::OnDestroy(); + + // Clear the game list. + /////////////////////// + ClearGames(); +} + +// Clear the list of games. +/////////////////////////// +void CGroupPage::ClearGames() +{ + // Free the data first. + /////////////////////// + int count = m_games.GetItemCount(); + int i; + for(i = 0 ; i < count ; i++) + { + LVITEM item; + item.mask = LVIF_PARAM; + item.iItem = i; + item.iSubItem = 0; + if(m_games.GetItem(&item) && item.lParam) + delete (CListedGame *)item.lParam; + } + + // Clear the list. + ////////////////// + m_games.DeleteAllItems(); +} + +// Create a staging room. +///////////////////////// +void CGroupPage::OnCreate() +{ +//PEERSTART + // Stop listing games. + ////////////////////// + peerStopListingGames(Wizard->m_peer); +//PEERSTOP + + // Goto the create page. + //////////////////////// + Wizard->SetActivePage(CREATE_PAGE); +} + +// Join a game. +/////////////// +void CGroupPage::OnDblclkGames(NMHDR* pNMHDR, LRESULT* pResult) +{ + NMHEADER * header = (NMHEADER *)pNMHDR; + + // If something was double-clicked, join it. + //////////////////////////////////////////// + if(header->iItem != -1) + JoinGame(header->iItem); + + *pResult = 0; +} + +// Handle game list clicks. +/////////////////////////// +void CGroupPage::OnClickGames(NMHDR* pNMHDR, LRESULT* pResult) +{ + // Enable/Disable join based on if something is selected. + ///////////////////////////////////////////////////////// + BOOL enable = (m_games.GetSelectedCount() > 0); + ::EnableWindow(Wizard->m_nextButtonWnd, enable); + + *pResult = 0; + + GSI_UNUSED(pNMHDR); +} +void CGroupPage::OnBegindragGames(NMHDR* pNMHDR, LRESULT* pResult) +{ + // Treat this like a click. + // (OnClick() isn't called for "drags". + /////////////////////////////////////// + OnClickGames(pNMHDR, pResult); +} + +// Find the index of a game in the list by its SBServer object. +/////////////////////////////////////////////////////////////// +int CGroupPage::FindListedGame(SBServer server) +{ + int count = m_games.GetItemCount(); + int i; + CListedGame * game; + for(i = 0 ; i < count ; i++) + { + game = (CListedGame *)m_games.GetItemData(i); + if(game && game->server == server) + return i; + } + return -1; +} + +// Find the index of a player in the list by its nick. +////////////////////////////////////////////////////// +int CGroupPage::FindPlayer(const char * nick) +{ + // Always deal in lower-case. + ///////////////////////////// + CString loweredNick = nick; + loweredNick.MakeLower(); + nick = loweredNick; + + // Look for this player. + //////////////////////// + LVFINDINFO findInfo; + findInfo.flags = LVFI_STRING; + findInfo.psz = nick; + + // Find the player. + /////////////////// + int nIndex = m_players.FindItem(&findInfo); + + return nIndex; +} + +// Updates the player's ping in the player list, and adds the player +// if its not on the list. +//////////////////////////////////////////////////////////////////// +void CGroupPage::UpdatePlayerPing(const char * nick, int ping) +{ + LVITEM item; + + // Is this us? + ////////////// + if(strcasecmp(nick, ConnectPage->m_nick) == 0) + ping = 0; + + // Always deal in lower-case. + ///////////////////////////// + CString loweredNick = nick; + loweredNick.MakeLower(); + nick = loweredNick; + + // Find the player. + /////////////////// + int nIndex = FindPlayer(nick); + + // Check for a new nick. + //////////////////////// + if(nIndex == -1) + { + item.iItem = 0; + item.iSubItem = 0; + item.mask = LVIF_TEXT; + item.pszText = (char *)nick; + + nIndex = m_players.InsertItem(&item); + if(nIndex == -1) + return; + } + + // Add the ping. + //////////////// + char intValue[16]; + sprintf(intValue, "%d", ping); + item.iItem = nIndex; + item.iSubItem = 1; + item.mask = LVIF_TEXT; + item.pszText = intValue; + m_players.SetItem(&item); +} + +// Remove the player from the list. +/////////////////////////////////// +void CGroupPage::RemovePlayer(const char * nick) +{ + // Always deal in lower-case. + ///////////////////////////// + CString loweredNick = nick; + loweredNick.MakeLower(); + nick = loweredNick; + + // Find the player. + /////////////////// + int nIndex = FindPlayer(nick); + + // Remove it. + ///////////// + m_players.DeleteItem(nIndex); +} + +// Change a nick in the player list. +//////////////////////////////////// +void CGroupPage::ChangePlayerNick(const char * oldNick, const char * newNick) +{ + // Always deal in lower-case. + ///////////////////////////// + CString loweredNick = oldNick; + loweredNick.MakeLower(); + oldNick = loweredNick; + loweredNick = newNick; + loweredNick.MakeLower(); + newNick = loweredNick; + + // Find the player. + /////////////////// + int nIndex = FindPlayer(oldNick); + + // Update the nick. + /////////////////// + LVITEM item; + item.iItem = nIndex; + item.iSubItem = 0; + item.mask = LVIF_TEXT; + item.pszText = (char *)newNick; + m_players.SetItem(&item); +} + +// Join a game based on its index in the game list. +/////////////////////////////////////////////////// +void CGroupPage::JoinGame(int nIndex) +{ + // Get the data. + //////////////// + CListedGame * game = (CListedGame *)m_games.GetItemData(nIndex); + ASSERT(game); + + // Is it staging? + ///////////////// + if(game->staging) + { + // Goto the staging room page. + ////////////////////////////// + Wizard->SetActivePage(STAGING_PAGE); + } + else + { +//PEERSTART + // Because there's no staging room for this game, it can just be joined. + // You can get info on the server using the GOA ServerGet*() functions. + //////////////////////////////////////////////////////////////////////// + CString buffer = "You are now playing at "; + buffer += SBServerGetPublicAddress(game->server); + buffer += ".\nHit enter when you are done."; + MessageBox(buffer); +//PEERSTOP + } +} + +// Does the actual chat message sending. +//////////////////////////////////////// +void CGroupPage::SendMessage() +{ + UpdateData(); + + // Ignore blank message. + //////////////////////// + if(m_message == "") + return; + +//PEERSTART + // Send it. + /////////// + peerMessageRoom(Wizard->m_peer, Wizard->m_groupRooms?GroupRoom:TitleRoom, m_message, NormalMessage); +//PEERSTOP + + // Clear it. + //////////// + m_message = ""; + + UpdateData(FALSE); +} diff --git a/code/gamespy/Peer/PeerLobby/GroupPage.h b/code/gamespy/Peer/PeerLobby/GroupPage.h new file mode 100644 index 00000000..4c8bca84 --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/GroupPage.h @@ -0,0 +1,88 @@ +#if !defined(AFX_GROUPPAGE_H__BC96C600_E6B9_49FF_8E49_75A013B824B2__INCLUDED_) +#define AFX_GROUPPAGE_H__BC96C600_E6B9_49FF_8E49_75A013B824B2__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// GroupPage.h : header file +// + +#include "../peer.h" + +///////////////////////////////////////////////////////////////////////////// +// CGroupPage dialog + +struct CListedGame +{ + SBServer server; + CString name; + PEERBool staging; + int ping; + int numPlayers; + int maxPlayers; +}; + +class CGroupPage : public CPropertyPage +{ + DECLARE_DYNCREATE(CGroupPage) + +// Construction +public: + CGroupPage(); + ~CGroupPage(); + +// Dialog Data + //{{AFX_DATA(CGroupPage) + enum { IDD = IDD_GROUP_PAGE }; + CListCtrl m_players; + CListCtrl m_games; + CListBox m_chatWindow; + CProgressCtrl m_progress; + CButton m_sendButton; + CString m_message; + //}}AFX_DATA + + void ClearGames(); + int FindListedGame(SBServer server); + + int FindPlayer(const char * nick); + void UpdatePlayerPing(const char * nick, int ping); + void RemovePlayer(const char * nick); + void ChangePlayerNick(const char * oldNick, const char * newNick); + + void JoinGame(int nIndex); + + void SendMessage(); + +// Overrides + // ClassWizard generate virtual function overrides + //{{AFX_VIRTUAL(CGroupPage) + public: + virtual BOOL OnSetActive(); + virtual LRESULT OnWizardNext(); + virtual BOOL OnKillActive(); + virtual LRESULT OnWizardBack(); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + // Generated message map functions + //{{AFX_MSG(CGroupPage) + virtual BOOL OnInitDialog(); + afx_msg void OnCreate(); + afx_msg void OnClickGames(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnBegindragGames(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnDblclkGames(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnDestroy(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +extern CGroupPage * GroupPage; + +#endif // !defined(AFX_GROUPPAGE_H__BC96C600_E6B9_49FF_8E49_75A013B824B2__INCLUDED_) diff --git a/code/gamespy/Peer/PeerLobby/LobbyWizard.cpp b/code/gamespy/Peer/PeerLobby/LobbyWizard.cpp new file mode 100644 index 00000000..ac720e0f --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/LobbyWizard.cpp @@ -0,0 +1,200 @@ +// LobbyWizard.cpp : implementation file +// + +#include "stdafx.h" +#include "PeerLobby.h" +#include "LobbyWizard.h" +#include "GroupPage.h" +#include "StagingPage.h" +#include "TitlePage.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +CLobbyWizard * Wizard; + +#define THINK_TIMER 100 + +///////////////////////////////////////////////////////////////////////////// +// CLobbyWizard + +IMPLEMENT_DYNAMIC(CLobbyWizard, CPropertySheet) + +CLobbyWizard::CLobbyWizard(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage) + :CPropertySheet(nIDCaption, pParentWnd, iSelectPage) +{ +//PEERSTART + // Initialize our peer pointer to NULL. + /////////////////////////////////////// + m_peer = NULL; +//PEERSTOP +} + +CLobbyWizard::CLobbyWizard(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage) + :CPropertySheet(pszCaption, pParentWnd, iSelectPage) +{ +//PEERSTART + // Initialize our peer pointer to NULL. + /////////////////////////////////////// + m_peer = NULL; +//PEERSTOP +} + +CLobbyWizard::~CLobbyWizard() +{ +} + + +BEGIN_MESSAGE_MAP(CLobbyWizard, CPropertySheet) + //{{AFX_MSG_MAP(CLobbyWizard) + ON_WM_TIMER() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CLobbyWizard message handlers + +// Init the wizard. +/////////////////// +BOOL CLobbyWizard::OnInitDialog() +{ + BOOL bResult = CPropertySheet::OnInitDialog(); + + // Get the "next" button wnd. + ///////////////////////////// + m_nextButtonWnd = ::FindWindowEx(this->m_hWnd, NULL, "Button", "&Next >"); + + // Rename "Cancel" to "Quit". + ///////////////////////////// + HWND hWnd = ::FindWindowEx(this->m_hWnd, NULL, "Button", "Cancel"); + if(hWnd) + ::SetWindowText(hWnd, "&Quit"); + + // Hide the "Help" button. + ////////////////////////// + hWnd = ::FindWindowEx(this->m_hWnd, NULL, "Button", "Help"); + if(hWnd) + ::ShowWindow(hWnd, SW_HIDE); + + // Load icons. + ////////////// + m_greenSmileyIcon = AfxGetApp()->LoadIcon(IDI_GREEN_SMILEY); + m_yellowSmileyIcon = AfxGetApp()->LoadIcon(IDI_YELLOW_SMILEY); + m_redSmileyIcon = AfxGetApp()->LoadIcon(IDI_RED_SMILEY); + m_stagingRoomIcon = AfxGetApp()->LoadIcon(IDI_STAGING_ROOM); + m_runningGameIcon = AfxGetApp()->LoadIcon(IDI_RUNNING_GAME); + if(!m_greenSmileyIcon || !m_yellowSmileyIcon || !m_redSmileyIcon || !m_stagingRoomIcon || !m_runningGameIcon) + return FALSE; + + // Create the image list. + ///////////////////////// + if(!m_imageList.Create(16, 16, ILC_COLOR, 5, 5)) + return FALSE; + m_greenSmileyIndex = m_imageList.Add(m_greenSmileyIcon); + m_yellowSmileyIndex = m_imageList.Add(m_yellowSmileyIcon); + m_redSmileyIndex = m_imageList.Add(m_redSmileyIcon); + m_stagingRoomIndex = m_imageList.Add(m_stagingRoomIcon); + m_runningGameIndex = m_imageList.Add(m_runningGameIcon); + if((m_yellowSmileyIndex == -1) || (m_redSmileyIndex == -1) || (m_greenSmileyIndex == -1) || (m_runningGameIndex == -1) || (m_stagingRoomIndex == -1)) + return FALSE; + +//PEERSTART + // We're not hosting. + ///////////////////// + m_hosting = FALSE; + + // Set the timer for every 10 seconds. + ////////////////////////////////////// + SetTimer(THINK_TIMER, 10, NULL); +//PEERSTOP + + return bResult; +} + +// Think every 10ms. +//////////////////// +void CLobbyWizard::OnTimer(UINT nIDEvent) +{ + if(nIDEvent == THINK_TIMER) + { +//PEERSTART + // Let peer think. + ////////////////// + if(m_peer) + peerThink(m_peer); +//PEERSTOP + } + + CPropertySheet::OnTimer(nIDEvent); +} + +// Show the hourglass. +////////////////////// +void CLobbyWizard::StartHourglass() +{ + // Load the hourglass. + ////////////////////// + HCURSOR hourglass = LoadCursor(NULL, IDC_WAIT); + if(!hourglass) + return; + + // Set the cursor. + ////////////////// + m_lastCursor = SetCursor(hourglass); +} + +// Back to normal pointer. +////////////////////////// +void CLobbyWizard::StopHourglass() +{ + // Reset the old cursor. + //////////////////////// + SetCursor(m_lastCursor); +} + +// Catch [Enter] when pressed in a chat message box. +//////////////////////////////////////////////////// +BOOL CLobbyWizard::PreTranslateMessage(MSG* pMsg) +{ + // Check for enter. + /////////////////// + if((pMsg->message == WM_KEYDOWN) && (pMsg->wParam == 0x0d)) + { + // Check what page we're on. + ///////////////////////////// + int page = GetActiveIndex(); + CWnd * focus; + if(page == TITLE_PAGE) + { + focus = TitlePage->GetFocus(); + if(focus->m_hWnd == TitlePage->GetDlgItem(IDC_MESSAGE)->m_hWnd) + { + TitlePage->SendMessage(); + return TRUE; + } + } + else if(page == GROUP_PAGE) + { + focus = GroupPage->GetFocus(); + if(focus->m_hWnd == GroupPage->GetDlgItem(IDC_MESSAGE)->m_hWnd) + { + GroupPage->SendMessage(); + return TRUE; + } + } + else if(page == STAGING_PAGE) + { + focus = StagingPage->GetFocus(); + if(focus->m_hWnd == StagingPage->GetDlgItem(IDC_MESSAGE)->m_hWnd) + { + StagingPage->SendMessage(); + return TRUE; + } + } + } + + return CPropertySheet::PreTranslateMessage(pMsg); +} diff --git a/code/gamespy/Peer/PeerLobby/LobbyWizard.h b/code/gamespy/Peer/PeerLobby/LobbyWizard.h new file mode 100644 index 00000000..9fe58b4d --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/LobbyWizard.h @@ -0,0 +1,84 @@ +#if !defined(AFX_LOBBYWIZARD_H__683E0CE1_CBA4_46D1_948A_6047481CDBF6__INCLUDED_) +#define AFX_LOBBYWIZARD_H__683E0CE1_CBA4_46D1_948A_6047481CDBF6__INCLUDED_ + +#include "../peer.h" + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// LobbyWizard.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CLobbyWizard + +#define CONNECT_PAGE 0 +#define TITLE_PAGE 1 +#define GROUP_PAGE 2 +#define CREATE_PAGE 3 +#define STAGING_PAGE 4 + +class CLobbyWizard : public CPropertySheet +{ + DECLARE_DYNAMIC(CLobbyWizard) + +// Construction +public: + CLobbyWizard(UINT nIDCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0); + CLobbyWizard(LPCTSTR pszCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0); + +// Attributes +public: + PEER m_peer; // The peer object. + BOOL m_hosting; // TRUE if hosting a game. + BOOL m_groupRooms; // TRUE if using group rooms. + + HWND m_nextButtonWnd; + + HICON m_greenSmileyIcon; + int m_greenSmileyIndex; + HICON m_yellowSmileyIcon; + int m_yellowSmileyIndex; + HICON m_redSmileyIcon; + int m_redSmileyIndex; + HICON m_stagingRoomIcon; + int m_stagingRoomIndex; + HICON m_runningGameIcon; + int m_runningGameIndex; + CImageList m_imageList; + + HCURSOR m_lastCursor; + +// Operations +public: + void StartHourglass(); + void StopHourglass(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CLobbyWizard) + public: + virtual BOOL OnInitDialog(); + virtual BOOL PreTranslateMessage(MSG* pMsg); + //}}AFX_VIRTUAL + +// Implementation +public: + virtual ~CLobbyWizard(); + + // Generated message map functions +protected: + //{{AFX_MSG(CLobbyWizard) + afx_msg void OnTimer(UINT nIDEvent); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +extern CLobbyWizard * Wizard; + +#endif // !defined(AFX_LOBBYWIZARD_H__683E0CE1_CBA4_46D1_948A_6047481CDBF6__INCLUDED_) diff --git a/code/gamespy/Peer/PeerLobby/PeerLobby.cpp b/code/gamespy/Peer/PeerLobby/PeerLobby.cpp new file mode 100644 index 00000000..c2cb4a24 --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/PeerLobby.cpp @@ -0,0 +1,101 @@ +// PeerLobby.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "PeerLobby.h" +#include "ConnectPage.h" +#include "TitlePage.h" +#include "GroupPage.h" +#include "CreatePage.h" +#include "StagingPage.h" +#include "LobbyWizard.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CPeerLobbyApp + +BEGIN_MESSAGE_MAP(CPeerLobbyApp, CWinApp) + //{{AFX_MSG_MAP(CPeerLobbyApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CPeerLobbyApp construction + +CPeerLobbyApp::CPeerLobbyApp() +{ +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CPeerLobbyApp object + +CPeerLobbyApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CPeerLobbyApp initialization + +BOOL CPeerLobbyApp::InitInstance() +{ + if (!AfxSocketInit()) + { + AfxMessageBox(IDP_SOCKETS_INIT_FAILED); + return FALSE; + } + + AfxEnableControlContainer(); + + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. +/* +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif +*/ + // Setup the wizard. + //////////////////// + CLobbyWizard wizard("Peer Lobby"); + Wizard = &wizard; + CConnectPage connectPage; + ConnectPage = &connectPage; + CTitlePage titlePage; + TitlePage = &titlePage; + CGroupPage groupPage; + GroupPage = &groupPage; + CCreatePage createPage; + CreatePage = &createPage; + CStagingPage stagingPage; + StagingPage = &stagingPage; + wizard.AddPage(ConnectPage); + wizard.AddPage(TitlePage); + wizard.AddPage(GroupPage); + wizard.AddPage(CreatePage); + wizard.AddPage(StagingPage); + wizard.SetWizardMode(); + + // Main loop. + ///////////// + wizard.DoModal(); + +//PEERSTART + // Shutdown peer. + ///////////////// + if(wizard.m_peer) + peerShutdown(wizard.m_peer); +//PEERSTOP + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/code/gamespy/Peer/PeerLobby/PeerLobby.h b/code/gamespy/Peer/PeerLobby/PeerLobby.h new file mode 100644 index 00000000..2187defa --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/PeerLobby.h @@ -0,0 +1,48 @@ +// PeerLobby.h : main header file for the PEERLOBBY application +// + +#if !defined(AFX_PEERLOBBY_H__A167413C_51DD_4C60_BA40_97CCA3876DDB__INCLUDED_) +#define AFX_PEERLOBBY_H__A167413C_51DD_4C60_BA40_97CCA3876DDB__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CPeerLobbyApp: +// See PeerLobby.cpp for the implementation of this class +// + +class CPeerLobbyApp : public CWinApp +{ +public: + CPeerLobbyApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CPeerLobbyApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CPeerLobbyApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_PEERLOBBY_H__A167413C_51DD_4C60_BA40_97CCA3876DDB__INCLUDED_) diff --git a/code/gamespy/Peer/PeerLobby/PeerLobby.rc b/code/gamespy/Peer/PeerLobby/PeerLobby.rc new file mode 100644 index 00000000..413bbbb5 --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/PeerLobby.rc @@ -0,0 +1,308 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\PeerLobby.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\PeerLobby.ico" +IDI_GREEN_SMILEY ICON DISCARDABLE "res\\ico00004.ico" +IDI_RED_SMILEY ICON DISCARDABLE "res\\ico00001.ico" +IDI_RUNNING_GAME ICON DISCARDABLE "res\\ico00002.ico" +IDI_STAGING_ROOM ICON DISCARDABLE "res\\ico00003.ico" +IDI_YELLOW_SMILEY ICON DISCARDABLE "res\\icon1.ico" + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "PeerLobby MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "PeerLobby\0" + VALUE "LegalCopyright", "Copyright (C) 2000\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "PeerLobby.EXE\0" + VALUE "ProductName", "PeerLobby Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_CONNECT_PAGE DIALOG DISCARDABLE 0, 0, 277, 242 +STYLE WS_CHILD | WS_DISABLED | WS_CAPTION +CAPTION "Connect" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Enter your nickname:",IDC_STATIC,86,96,68,8 + EDITTEXT IDC_NICK,86,109,95,12,ES_AUTOHSCROLL + EDITTEXT IDC_TITLE,220,0,55,12,ES_AUTOHSCROLL + CONTROL "Use Group Rooms",IDC_GROUP_ROOMS,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,86,128,74,10 + LTEXT "The use of group rooms is game-dependant. Group rooms are used to split up staging rooms (and running games) into various sections, grouped by type of game, skill level, or any other criteria.", + IDC_STATIC,86,145,154,45 + LTEXT "Title:",IDC_STATIC,200,2,16,8 + EDITTEXT IDC_KEY,220,15,55,12,ES_AUTOHSCROLL + LTEXT "Key:",IDC_STATIC,200,17,15,8 +END + +IDD_GROUP_PAGE DIALOGEX 0, 0, 277, 242 +STYLE WS_CHILD | WS_DISABLED | WS_CAPTION +CAPTION "Lobby" +FONT 8, "MS Sans Serif", 0, 0, 0x1 +BEGIN + EDITTEXT IDC_MESSAGE,10,216,168,15,ES_AUTOHSCROLL | ES_WANTRETURN + PUSHBUTTON "&Create Game",IDC_CREATE,7,7,50,14 + CONTROL "List2",IDC_GAMES,"SysListView32",LVS_REPORT | + LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_SORTASCENDING | + WS_BORDER | WS_TABSTOP,7,30,263,82 + CONTROL "List2",IDC_PLAYERS,"SysListView32",LVS_REPORT | + LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_SORTASCENDING | + WS_BORDER | WS_TABSTOP,182,124,83,107 + LISTBOX IDC_CHAT_WINDOW,10,124,168,92,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Chat",IDC_STATIC,7,115,263,120 + CONTROL "Progress1",IDC_PROGRESS,"msctls_progress32",PBS_SMOOTH | + WS_BORDER,61,7,143,14 + ICON IDI_STAGING_ROOM,IDC_STATIC,207,1,20,20,SS_REALSIZEIMAGE | + WS_BORDER,WS_EX_CLIENTEDGE + LTEXT "Staging Room",IDC_STATIC,222,3,46,8 + ICON IDI_RUNNING_GAME,IDC_STATIC,207,15,20,20, + SS_REALSIZEIMAGE | WS_BORDER,WS_EX_CLIENTEDGE + LTEXT "Running Game",IDC_STATIC,222,17,48,8 +END + +IDD_CREATE_PAGE DIALOG DISCARDABLE 0, 0, 277, 242 +STYLE WS_CHILD | WS_DISABLED | WS_CAPTION +CAPTION "Create" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Enter the room name:",IDC_STATIC,46,87,68,8 + EDITTEXT IDC_NAME,46,100,175,12,ES_AUTOHSCROLL + LTEXT "Set the maximum number of players:",IDC_STATIC,46,127, + 114,8 + EDITTEXT IDC_MAX_PLAYERS,46,142,30,12,ES_AUTOHSCROLL | ES_NUMBER +END + +IDD_STAGING_PAGE DIALOGEX 0, 0, 277, 242 +STYLE WS_CHILD | WS_DISABLED | WS_CAPTION +CAPTION "Staging" +FONT 8, "MS Sans Serif", 0, 0, 0x1 +BEGIN + EDITTEXT IDC_MESSAGE,7,219,175,15,ES_AUTOHSCROLL | ES_WANTRETURN + CONTROL "Not Ready",IDC_NOT_READY,"Button",BS_AUTORADIOBUTTON | + BS_PUSHLIKE | WS_GROUP,185,7,40,20 + CONTROL "Ready",IDC_READY,"Button",BS_AUTORADIOBUTTON | + BS_PUSHLIKE,230,7,40,20 + CONTROL "List2",IDC_PLAYERS,"SysListView32",LVS_REPORT | + LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_SORTASCENDING | + WS_BORDER | WS_GROUP | WS_TABSTOP,185,59,85,176 + LISTBOX IDC_CHAT_WINDOW,7,7,175,211,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_GROUP | + WS_TABSTOP + ICON IDI_GREEN_SMILEY,IDC_STATIC,197,31,20,20, + SS_REALSIZEIMAGE | WS_BORDER,WS_EX_CLIENTEDGE + LTEXT "Ready",IDC_STATIC,213,33,22,8 + ICON IDI_RED_SMILEY,IDC_STATIC,197,44,20,20,SS_REALSIZEIMAGE | + WS_BORDER,WS_EX_CLIENTEDGE + LTEXT "Not Ready",IDC_STATIC,213,47,35,8 +END + +IDD_TITLE_PAGE DIALOG DISCARDABLE 0, 0, 277, 242 +STYLE WS_CHILD | WS_DISABLED | WS_CAPTION +CAPTION "Title" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_MESSAGE,10,216,168,15,ES_AUTOHSCROLL | ES_WANTRETURN + CONTROL "List2",IDC_PLAYERS,"SysListView32",LVS_REPORT | + LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_SORTASCENDING | + WS_BORDER | WS_TABSTOP,182,124,83,107 + LISTBOX IDC_CHAT_WINDOW,10,124,168,92,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Chat",IDC_STATIC,7,115,263,120 + CONTROL "List2",IDC_GROUPS,"SysListView32",LVS_REPORT | + LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_SORTASCENDING | + WS_BORDER | WS_TABSTOP,7,10,263,102 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_CONNECT_PAGE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 270 + TOPMARGIN, 7 + BOTTOMMARGIN, 235 + END + + IDD_GROUP_PAGE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 270 + TOPMARGIN, 7 + BOTTOMMARGIN, 235 + END + + IDD_CREATE_PAGE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 270 + TOPMARGIN, 7 + BOTTOMMARGIN, 235 + END + + IDD_STAGING_PAGE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 270 + TOPMARGIN, 7 + BOTTOMMARGIN, 235 + END + + IDD_TITLE_PAGE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 270 + TOPMARGIN, 7 + BOTTOMMARGIN, 235 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDP_SOCKETS_INIT_FAILED "Windows sockets initialization failed." +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\PeerLobby.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/gamespy/Peer/PeerLobby/ReadMe.txt b/code/gamespy/Peer/PeerLobby/ReadMe.txt new file mode 100644 index 00000000..9b1b93d3 --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/ReadMe.txt @@ -0,0 +1,88 @@ +======================================================================== + MICROSOFT FOUNDATION CLASS LIBRARY : PeerLobby +======================================================================== + + +AppWizard has created this PeerLobby application for you. This application +not only demonstrates the basics of using the Microsoft Foundation classes +but is also a starting point for writing your application. + +This file contains a summary of what you will find in each of the files that +make up your PeerLobby application. + +PeerLobby.dsp + This file (the project file) contains information at the project level and + is used to build a single project or subproject. Other users can share the + project (.dsp) file, but they should export the makefiles locally. + +PeerLobby.h + This is the main header file for the application. It includes other + project specific headers (including Resource.h) and declares the + CPeerLobbyApp application class. + +PeerLobby.cpp + This is the main application source file that contains the application + class CPeerLobbyApp. + +PeerLobby.rc + This is a listing of all of the Microsoft Windows resources that the + program uses. It includes the icons, bitmaps, and cursors that are stored + in the RES subdirectory. This file can be directly edited in Microsoft + Visual C++. + +PeerLobby.clw + This file contains information used by ClassWizard to edit existing + classes or add new classes. ClassWizard also uses this file to store + information needed to create and edit message maps and dialog data + maps and to create prototype member functions. + +res\PeerLobby.ico + This is an icon file, which is used as the application's icon. This + icon is included by the main resource file PeerLobby.rc. + +res\PeerLobby.rc2 + This file contains resources that are not edited by Microsoft + Visual C++. You should place all resources not editable by + the resource editor in this file. + + + + +///////////////////////////////////////////////////////////////////////////// + +AppWizard creates one dialog class: + +PeerLobbyDlg.h, PeerLobbyDlg.cpp - the dialog + These files contain your CPeerLobbyDlg class. This class defines + the behavior of your application's main dialog. The dialog's + template is in PeerLobby.rc, which can be edited in Microsoft + Visual C++. + + +///////////////////////////////////////////////////////////////////////////// +Other standard files: + +StdAfx.h, StdAfx.cpp + These files are used to build a precompiled header (PCH) file + named PeerLobby.pch and a precompiled types file named StdAfx.obj. + +Resource.h + This is the standard header file, which defines new resource IDs. + Microsoft Visual C++ reads and updates this file. + +///////////////////////////////////////////////////////////////////////////// +Other notes: + +AppWizard uses "TODO:" to indicate parts of the source code you +should add to or customize. + +If your application uses MFC in a shared DLL, and your application is +in a language other than the operating system's current language, you +will need to copy the corresponding localized resources MFC42XXX.DLL +from the Microsoft Visual C++ CD-ROM onto the system or system32 directory, +and rename it to be MFCLOC.DLL. ("XXX" stands for the language abbreviation. +For example, MFC42DEU.DLL contains resources translated to German.) If you +don't do this, some of the UI elements of your application will remain in the +language of the operating system. + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/Peer/PeerLobby/SideBarCtrl.cpp b/code/gamespy/Peer/PeerLobby/SideBarCtrl.cpp new file mode 100644 index 00000000..e11ede56 --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/SideBarCtrl.cpp @@ -0,0 +1,217 @@ +// SideBarCtrl.cpp : implementation file +// + +#include "stdafx.h" +#include "peerlobby.h" +#include "SideBarCtrl.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CSideBarCtrl + +CSideBarCtrl::CSideBarCtrl() +{ +} + +CSideBarCtrl::~CSideBarCtrl() +{ +} + + +BEGIN_MESSAGE_MAP(CSideBarCtrl, CStatic) + //{{AFX_MSG_MAP(CSideBarCtrl) + ON_WM_ERASEBKGND() + ON_WM_PAINT() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CSideBarCtrl message handlers + +BOOL CSideBarCtrl::OnEraseBkgnd(CDC* pDC) +{ + return TRUE; + +// return CStatic::OnEraseBkgnd(pDC); +} + +void CSideBarCtrl::OnPaint() +{ + CPaintDC dc(this); // device context for painting + + CRect rcClient; + GetClientRect(rcClient); + + DrawGradient(&dc, rcClient, RGB(0,0,0), RGB(0,255,0), TRUE ); + + int oldBkMode = dc.SetBkMode(TRANSPARENT); + HFONT oldFont = (HFONT)dc.SelectObject( (HFONT)::GetStockObject(ANSI_VAR_FONT) ); + COLORREF oldTextColor = dc.SetTextColor( RGB(0,204,0) ); + + // + // Bottom Text + // + CString str; + GetWindowText(str); + + CRect rcText = rcClient; + rcText.DeflateRect(3,3); + + dc.DrawText( str, rcText, DT_BOTTOM|DT_CENTER|DT_SINGLELINE ); + + rcText.OffsetRect(-1,-1); + + dc.SetTextColor( RGB(0,0,0) ); + + dc.DrawText( str, rcText, DT_BOTTOM|DT_CENTER|DT_SINGLELINE ); + + + // + // Top Text + // + CFont fntBold; + LOGFONT lf = {0}; + ::GetObject((HFONT)::GetStockObject(ANSI_VAR_FONT),sizeof(LOGFONT),&lf); + lf.lfWeight = FW_SEMIBOLD; + fntBold.CreateFontIndirect(&lf); + + dc.SelectObject(fntBold); + + dc.SetTextColor(RGB(255,255,255)); + + dc.DrawText("\nGameSpy\n\n\"PeerLobby\"\n\nSample\nApplication",-1,rcClient,DT_CENTER); + + + // + // Cleanup + // + + dc.SetTextColor(oldTextColor); + dc.SetBkMode(oldBkMode); + dc.SelectObject(oldFont); + + fntBold.DeleteObject(); +} + +// +// Helpers +// +BYTE findMidTone( BYTE bColorOne, BYTE bColorTwo ) +{ + BYTE bResult = 0; + WORD wMax = (WORD)(bColorOne + bColorTwo); + if( wMax > 0 ) + { + bResult = ( (BYTE)(wMax/2) ); + } + return bResult; +} + +COLORREF findMidColor( COLORREF cOne, COLORREF cTwo ) +{ + return( RGB( findMidTone( GetRValue(cOne), GetRValue(cTwo) ), + findMidTone( GetGValue(cOne), GetGValue(cTwo) ), + findMidTone( GetBValue(cOne), GetBValue(cTwo) ) ) ); +} + +/***************************************************************************** +* NAME: +* DrawGradient +* +* DESCRIPTION: +* Found this as part of an overly complicated gradient progress bar on codeguru +* So, I simplified it for my own uses :) -Lumberjack +* +*******************************************************************************/ +void CSideBarCtrl::DrawGradient(CDC* pDC, const CRect &rcGrad, COLORREF clrStart, COLORREF clrEnd, BOOL bVertical, COLORREF clrDither /*=0xFFFFFFFF*/ ) +{ + // Split colors to RGB chanels, find chanel with maximum difference + // between the start and end colors. This distance will determine + // number of steps of gradient + int r = (GetRValue(clrEnd) - GetRValue(clrStart)); + int g = (GetGValue(clrEnd) - GetGValue(clrStart)); + int b = (GetBValue(clrEnd) - GetBValue(clrStart)); + int nSteps = max(abs(r), max(abs(g), abs(b))); + // if number of pixels in gradient less than number of steps - + // use it as numberof steps + int nPixels = rcGrad.Width(); + nSteps = min(nPixels, nSteps); + if(nSteps == 0) nSteps = 1; + + float rStep = (float)r/nSteps; + float gStep = (float)g/nSteps; + float bStep = (float)b/nSteps; + + r = GetRValue(clrStart); + g = GetGValue(clrStart); + b = GetBValue(clrStart); + + BOOL fLowColor = pDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE; + if(!fLowColor && nSteps > 1) + if(pDC->GetDeviceCaps(BITSPIXEL)*pDC->GetDeviceCaps(PLANES) < 8) + nSteps = 1; // for 16 colors no gradient + + float nSpacePerStep = bVertical?(float)rcGrad.Height()/nSteps:(float)rcGrad.Width()/nSteps; + CRect rcFill(rcGrad); + CBrush br; + + // Start filling + for (int i = 0; i < nSteps; i++) + { + if( bVertical ) + { + rcFill.top = rcGrad.top + (int)(nSpacePerStep * i); + rcFill.bottom = rcGrad.top + (int)(nSpacePerStep * (i+1)); + if(i == nSteps-1) //last step (because of problems with float) + rcFill.bottom = rcGrad.bottom; + } + else + { + rcFill.left = rcGrad.left + (int)(nSpacePerStep * i); + rcFill.right = rcGrad.left + (int)(nSpacePerStep * (i+1)); + if(i == nSteps-1) //last step (because of problems with float) + rcFill.right = rcGrad.right; + } + + COLORREF clrFill = RGB(r + (int)(i * rStep), g + (int)(i * gStep), b + (int)(i * bStep)); + + if( fLowColor ) + { + br.CreateSolidBrush(clrFill); + // CDC::FillSolidRect is faster, but it does not handle 8-bit color depth + pDC->FillRect(&rcFill, &br); + br.DeleteObject(); + } + else + { + if( 0xFFFFFFFF != clrDither ) + { + COLORREF crExisting = 0; + for( int nHoriz = rcFill.left; nHoriz < rcFill.right; nHoriz++ ) + { + for( int nVert = rcFill.top; nVert < rcFill.bottom; nVert++ ) + { + crExisting = pDC->GetPixel(nHoriz,nVert); + if( crExisting == clrDither ) + { + pDC->SetPixel(nHoriz,nVert,findMidColor(clrFill,clrDither) ); + } + else + { + pDC->SetPixel(nHoriz,nVert,clrFill); + } + } + } + } + else + { + pDC->FillSolidRect(&rcFill, clrFill); + } + } + } +} \ No newline at end of file diff --git a/code/gamespy/Peer/PeerLobby/SideBarCtrl.h b/code/gamespy/Peer/PeerLobby/SideBarCtrl.h new file mode 100644 index 00000000..6af569ed --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/SideBarCtrl.h @@ -0,0 +1,51 @@ +#if !defined(AFX_SIDEBARCTRL_H__CC0DCA41_C330_11D5_A480_000102C2601F__INCLUDED_) +#define AFX_SIDEBARCTRL_H__CC0DCA41_C330_11D5_A480_000102C2601F__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// SideBarCtrl.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CSideBarCtrl window + +class CSideBarCtrl : public CStatic +{ +// Construction +public: + CSideBarCtrl(); + +// Attributes +public: + +// Operations +public: + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CSideBarCtrl) + //}}AFX_VIRTUAL + +// Implementation +public: + virtual ~CSideBarCtrl(); + + // Generated message map functions +protected: + //{{AFX_MSG(CSideBarCtrl) + afx_msg BOOL OnEraseBkgnd(CDC* pDC); + afx_msg void OnPaint(); + //}}AFX_MSG + + void DrawGradient(CDC* pDC, const CRect &rcGrad, COLORREF clrStart, COLORREF clrEnd, BOOL bVertical, COLORREF clrDither = 0xFFFFFFFF ); + + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_SIDEBARCTRL_H__CC0DCA41_C330_11D5_A480_000102C2601F__INCLUDED_) diff --git a/code/gamespy/Peer/PeerLobby/StagingPage.cpp b/code/gamespy/Peer/PeerLobby/StagingPage.cpp new file mode 100644 index 00000000..bc1464e1 --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/StagingPage.cpp @@ -0,0 +1,486 @@ +// StagingPage.cpp : implementation file +// + +#include "stdafx.h" +#include "PeerLobby.h" +#include "StagingPage.h" +#include "LobbyWizard.h" +#include "GroupPage.h" +#include "ConnectPage.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +CStagingPage * StagingPage; + +#define COL_NAME 0 +#define COL_PING 1 + +///////////////////////////////////////////////////////////////////////////// +// CStagingPage property page + +IMPLEMENT_DYNCREATE(CStagingPage, CPropertyPage) + +// Set page defaults. +///////////////////// +CStagingPage::CStagingPage() : CPropertyPage(CStagingPage::IDD) +{ + //{{AFX_DATA_INIT(CStagingPage) + m_message = _T(""); + m_ready = 0; + //}}AFX_DATA_INIT +} + +CStagingPage::~CStagingPage() +{ +} + +void CStagingPage::DoDataExchange(CDataExchange* pDX) +{ + CPropertyPage::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CStagingPage) + DDX_Control(pDX, IDC_PLAYERS, m_players); + DDX_Control(pDX, IDC_CHAT_WINDOW, m_chatWindow); + DDX_Text(pDX, IDC_MESSAGE, m_message); + DDX_Radio(pDX, IDC_NOT_READY, m_ready); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CStagingPage, CPropertyPage) + //{{AFX_MSG_MAP(CStagingPage) + ON_BN_CLICKED(IDC_READY, OnReady) + ON_BN_CLICKED(IDC_NOT_READY, OnNotReady) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +// Used to list staging room players. +///////////////////////////////////// +static void EnumStagingPlayersCallback +( + PEER peer, + PEERBool success, + RoomType roomType, + int index, + const char * nick, + int flags, + void * param +) +{ + // Add the player to the list. + ////////////////////////////// + if(nick) + StagingPage->UpdatePlayerPing(nick, 9999); + GSI_UNUSED(peer); + GSI_UNUSED(param); + GSI_UNUSED(flags); + GSI_UNUSED(index); + GSI_UNUSED(roomType); + GSI_UNUSED(success); +} + +// Called when the join attempt has completed (successfully or unsuccessfully). +/////////////////////////////////////////////////////////////////////////////// +PEERBool joinStagingSuccess; +void JoinStagingRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + void * param +) +{ + joinStagingSuccess = success; + GSI_UNUSED(peer); + GSI_UNUSED(roomType); + GSI_UNUSED(param); + GSI_UNUSED(result); +} + +// Switching to this page. +////////////////////////// +BOOL CStagingPage::OnSetActive() +{ + // Show back and a disabled-finish. + /////////////////////////////////// + Wizard->SetWizardButtons(PSWIZB_BACK | PSWIZB_DISABLEDFINISH); + + // Clear the chat log. + ////////////////////// + m_chatWindow.ResetContent(); + + // Are we joining? + ////////////////// + if(!Wizard->m_hosting) + { + // Make sure something was selected. + //////////////////////////////////// + int nIndex = GroupPage->m_games.GetSelectionMark(); + if(nIndex == -1) + { + MessageBox("You must have a game selected."); + return 1; + } + + // Get the data. + //////////////// + CListedGame * game = (CListedGame *)GroupPage->m_games.GetItemData(nIndex); + +//PEERSTART + // Join the room. + ///////////////// + Wizard->StartHourglass(); + peerJoinStagingRoom(Wizard->m_peer, game->server, NULL, JoinStagingRoomCallback, Wizard, PEERTrue); + Wizard->StopHourglass(); + if(!joinStagingSuccess) + { + MessageBox("Error joining the staging room."); + return FALSE; + } + + // Stop listing. + //////////////// + peerStopListingGames(Wizard->m_peer); + } + + // Fill the player list. + //////////////////////// + peerEnumPlayers(Wizard->m_peer, StagingRoom, EnumStagingPlayersCallback, Wizard); +//PEERSTOP + + // Default to not ready. + //////////////////////// + m_ready = 0; + + return CPropertyPage::OnSetActive(); +} + +// Leaving this page. +///////////////////// +BOOL CStagingPage::OnKillActive() +{ + // Clear the players. + ///////////////////// + m_players.DeleteAllItems(); + + return CPropertyPage::OnKillActive(); +} + +// Going to the previous page. +////////////////////////////// +LRESULT CStagingPage::OnWizardBack() +{ +//PEERSTART + // Leave the room. + ////////////////// + peerLeaveRoom(Wizard->m_peer, StagingRoom, NULL); +//PEERSTOP + + // Do stuff based on hosting. + ///////////////////////////// + if(Wizard->m_hosting) + { + // Goto the create page. + //////////////////////// + return IDD_CREATE_PAGE; + } + else + { + // Goto the lobby room. + /////////////////////// + return IDD_GROUP_PAGE; + } + + //return CPropertyPage::OnWizardBack(); +} + +// The game is being launched. +////////////////////////////// +BOOL CStagingPage::OnWizardFinish() +{ +//PEERSTART + // Start the game. + ////////////////// + peerStartGame(Wizard->m_peer, "", PEER_KEEP_REPORTING); + + // This is where the host would start up. + ///////////////////////////////////////// + MessageBox("You are now playing!\nHit enter when you are done."); + + // Stop the game. + // This should be called when the host is ready to return to the staging room. + ////////////////////////////////////////////////////////////////////////////// + peerStopGame(Wizard->m_peer); +//PEERSTOP + + return FALSE; +} + +// Init the page. +///////////////// +BOOL CStagingPage::OnInitDialog() +{ + CPropertyPage::OnInitDialog(); + + // Change "Finish". + /////////////////// + HWND hWnd = ::FindWindowEx(GetParent()->m_hWnd, NULL, "Button", "Finish"); + if(hWnd) + ::SetWindowText(hWnd, "&Start Playing"); + + // Set the players columns. + /////////////////////////// + m_players.InsertColumn(COL_NAME, "Player", LVCFMT_LEFT, 70); + m_players.InsertColumn(COL_PING, "Ping", LVCFMT_LEFT, 50); + + // Image list setup. + //////////////////// + m_players.SetImageList(&Wizard->m_imageList, LVSIL_SMALL); + + return TRUE; +} + +// The player's ready. +////////////////////// +void CStagingPage::OnReady() +{ +//PEERSTART + // Set ready to true. + ///////////////////// + peerSetReady(Wizard->m_peer, PEERTrue); +//PEERSTOP +} + +// The player's not ready. +////////////////////////// +void CStagingPage::OnNotReady() +{ +//PEERSTART + // Set ready to false. + ////////////////////// + peerSetReady(Wizard->m_peer, PEERFalse); +//PEERSTOP +} + +// Find the index of a player in the list ny its nick. +////////////////////////////////////////////////////// +int CStagingPage::FindPlayer(const char * nick) +{ + // Always deal in lower-case. + ///////////////////////////// + CString loweredNick = nick; + loweredNick.MakeLower(); + nick = loweredNick; + + // Look for this player. + //////////////////////// + LVFINDINFO findInfo; + findInfo.flags = LVFI_STRING; + findInfo.psz = nick; + + // Find the player. + /////////////////// + int nIndex = m_players.FindItem(&findInfo); + + return nIndex; +} + +// Update this player's ping in the list, and add the player if +// not already on the list. +/////////////////////////////////////////////////////////////// +void CStagingPage::UpdatePlayerPing(const char * nick, int ping) +{ + LVITEM item; + + // Is this us? + ////////////// + if(strcasecmp(nick, ConnectPage->m_nick) == 0) + ping = 0; + + // Always deal in lower-case. + ///////////////////////////// + CString loweredNick = nick; + loweredNick.MakeLower(); + nick = loweredNick; + + // Find the player. + /////////////////// + int nIndex = FindPlayer(nick); + + // Check for a new nick. + //////////////////////// + if(nIndex == -1) + { + item.iItem = 0; + item.iSubItem = 0; + item.mask = LVIF_TEXT | LVIF_IMAGE; + item.pszText = (char *)nick; + item.iImage = Wizard->m_redSmileyIndex; + + nIndex = m_players.InsertItem(&item); + if(nIndex == -1) + return; + } + + // Add the ping. + //////////////// + char intValue[16]; + sprintf(intValue, "%d", ping); + item.iItem = nIndex; + item.iSubItem = 1; + item.mask = LVIF_TEXT; + item.pszText = intValue; + m_players.SetItem(&item); +} + +// Update the player's ready state, and add the player if +// not alreayd on the list. +///////////////////////////////////////////////////////// +void CStagingPage::UpdatePlayerReady(const char * nick, BOOL ready) +{ + LVITEM item; + + // Always deal in lower-case. + ///////////////////////////// + CString loweredNick = nick; + loweredNick.MakeLower(); + nick = loweredNick; + + // Find the player. + /////////////////// + int nIndex = FindPlayer(nick); + + // Check for a new nick. + //////////////////////// + if(nIndex != -1) + { + // Update the image. + //////////////////// + item.iItem = nIndex; + item.iSubItem = 0; + item.mask = LVIF_IMAGE; + if(ready) + item.iImage = Wizard->m_greenSmileyIndex; + else + item.iImage = Wizard->m_redSmileyIndex; + m_players.SetItem(&item); + } + else + { + // Add it. + ////////// + item.iItem = 0; + item.iSubItem = 0; + item.mask = LVIF_TEXT | LVIF_IMAGE; + item.pszText = (char *)nick; + if(ready) + item.iImage = Wizard->m_greenSmileyIndex; + else + item.iImage = Wizard->m_redSmileyIndex; + + nIndex = m_players.InsertItem(&item); + if(nIndex == -1) + return; + + // Set a ping. + ////////////// + item.iItem = nIndex; + item.iSubItem = 1; + item.mask = LVIF_TEXT; + item.pszText = "9999"; + m_players.SetItem(&item); + } +} + +// Remove a player from the list. +///////////////////////////////// +void CStagingPage::RemovePlayer(const char * nick) +{ + // Always deal in lower-case. + ///////////////////////////// + CString loweredNick = nick; + loweredNick.MakeLower(); + nick = loweredNick; + + // Find the player. + /////////////////// + int nIndex = FindPlayer(nick); + + // Remove it. + ///////////// + m_players.DeleteItem(nIndex); +} + +// Change a player's nick on the list. +////////////////////////////////////// +void CStagingPage::ChangePlayerNick(const char * oldNick, const char * newNick) +{ + // Always deal in lower-case. + ///////////////////////////// + CString loweredNick = oldNick; + loweredNick.MakeLower(); + oldNick = loweredNick; + loweredNick = newNick; + loweredNick.MakeLower(); + newNick = loweredNick; + + // Find the player. + /////////////////// + int nIndex = FindPlayer(oldNick); + + // Update the nick. + /////////////////// + LVITEM item; + item.iItem = nIndex; + item.iSubItem = 0; + item.mask = LVIF_TEXT; + item.pszText = (char *)newNick; + m_players.SetItem(&item); +} + +// Does the actual chat message sending. +//////////////////////////////////////// +void CStagingPage::SendMessage() +{ + UpdateData(); + + // Ignore blank message. + //////////////////////// + if(m_message == "") + return; + +//PEERSTART + // Send it. + /////////// + peerMessageRoom(Wizard->m_peer, StagingRoom, m_message, NormalMessage); +//PEERSTOP + + // Clear it. + //////////// + m_message = ""; + + UpdateData(FALSE); +} + +// Either enables or disables the finish button based on if +// all the players in the room are ready or not. +/////////////////////////////////////////////////////////// +void CStagingPage::CheckEnableFinish() +{ +//PEERSTART + // Check if all the player's in the room are ready. + /////////////////////////////////////////////////// + PEERBool allReady = peerAreAllReady(Wizard->m_peer); +//PEERSTOP + + // Only enable the finish button if we're hosting and everyone's ready. + /////////////////////////////////////////////////////////////////////// + if(Wizard->m_hosting && allReady) + Wizard->SetWizardButtons(PSWIZB_BACK | PSWIZB_FINISH); + else + Wizard->SetWizardButtons(PSWIZB_BACK | PSWIZB_DISABLEDFINISH); +} \ No newline at end of file diff --git a/code/gamespy/Peer/PeerLobby/StagingPage.h b/code/gamespy/Peer/PeerLobby/StagingPage.h new file mode 100644 index 00000000..275b492a --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/StagingPage.h @@ -0,0 +1,70 @@ +#if !defined(AFX_STAGINGPAGE_H__E66D2915_B2B7_4A43_B875_D0C905FA1395__INCLUDED_) +#define AFX_STAGINGPAGE_H__E66D2915_B2B7_4A43_B875_D0C905FA1395__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// StagingPage.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CStagingPage dialog + +class CStagingPage : public CPropertyPage +{ + DECLARE_DYNCREATE(CStagingPage) + +// Construction +public: + CStagingPage(); + ~CStagingPage(); + +// Dialog Data + //{{AFX_DATA(CStagingPage) + enum { IDD = IDD_STAGING_PAGE }; + CListCtrl m_players; + CListBox m_chatWindow; + CString m_message; + int m_ready; + //}}AFX_DATA + + int FindPlayer(const char * nick); + void UpdatePlayerPing(const char * nick, int ping); + void UpdatePlayerReady(const char * nick, BOOL ready); + void RemovePlayer(const char * nick); + void ChangePlayerNick(const char * oldNick, const char * newNick); + + void SendMessage(); + + void CheckEnableFinish(); + +// Overrides + // ClassWizard generate virtual function overrides + //{{AFX_VIRTUAL(CStagingPage) + public: + virtual BOOL OnSetActive(); + virtual LRESULT OnWizardBack(); + virtual BOOL OnWizardFinish(); + virtual BOOL OnKillActive(); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + // Generated message map functions + //{{AFX_MSG(CStagingPage) + afx_msg void OnReady(); + afx_msg void OnNotReady(); + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() + +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +extern CStagingPage * StagingPage; + +#endif // !defined(AFX_STAGINGPAGE_H__E66D2915_B2B7_4A43_B875_D0C905FA1395__INCLUDED_) diff --git a/code/gamespy/Peer/PeerLobby/StdAfx.cpp b/code/gamespy/Peer/PeerLobby/StdAfx.cpp new file mode 100644 index 00000000..52d66784 --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// PeerLobby.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/code/gamespy/Peer/PeerLobby/StdAfx.h b/code/gamespy/Peer/PeerLobby/StdAfx.h new file mode 100644 index 00000000..4e708714 --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/StdAfx.h @@ -0,0 +1,31 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__A460F6D9_024E_4B08_A827_B0B0D48AB46D__INCLUDED_) +#define AFX_STDAFX_H__A460F6D9_024E_4B08_A827_B0B0D48AB46D__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +#include // MFC socket extensions + +#include "../peer.h" +#include "../../common/gsAvailable.h" + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__A460F6D9_024E_4B08_A827_B0B0D48AB46D__INCLUDED_) diff --git a/code/gamespy/Peer/PeerLobby/TitlePage.cpp b/code/gamespy/Peer/PeerLobby/TitlePage.cpp new file mode 100644 index 00000000..dcccec8d --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/TitlePage.cpp @@ -0,0 +1,502 @@ +// TitlePage.cpp : implementation file +// + +#include "stdafx.h" +#include "PeerLobby.h" +#include "TitlePage.h" +#include "LobbyWizard.h" +#include "ConnectPage.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +CTitlePage * TitlePage; + +#define COL_NAME 0 +#define COL_NUM_WAITING 1 +#define COL_MAX_WAITING 2 +#define COL_NUM_GAMES 3 +#define COL_NUM_PLAYING 4 + +///////////////////////////////////////////////////////////////////////////// +// CTitlePage property page + +IMPLEMENT_DYNCREATE(CTitlePage, CPropertyPage) + +CTitlePage::CTitlePage() : CPropertyPage(CTitlePage::IDD) +{ + //{{AFX_DATA_INIT(CTitlePage) + m_message = _T(""); + //}}AFX_DATA_INIT +} + +CTitlePage::~CTitlePage() +{ +} + +void CTitlePage::DoDataExchange(CDataExchange* pDX) +{ + CPropertyPage::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CTitlePage) + DDX_Control(pDX, IDC_GROUPS, m_groups); + DDX_Control(pDX, IDC_PLAYERS, m_players); + DDX_Control(pDX, IDC_CHAT_WINDOW, m_chatWindow); + DDX_Text(pDX, IDC_MESSAGE, m_message); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CTitlePage, CPropertyPage) + //{{AFX_MSG_MAP(CTitlePage) + ON_NOTIFY(NM_CLICK, IDC_GROUPS, OnClickGroups) + ON_NOTIFY(NM_DBLCLK, IDC_GROUPS, OnDblclkGroups) + ON_NOTIFY(LVN_BEGINDRAG, IDC_GROUPS, OnBegindragGroups) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CTitlePage message handlers + +// Used to list the players in the title room. +////////////////////////////////////////////// +static void EnumTitlePlayersCallback +( + PEER peer, + PEERBool success, + RoomType roomType, + int index, + const char * nick, + int flags, + void * param +) +{ + if(!success) + { + Wizard->MessageBox("Error listing title-room players."); + return; + } + + if(index == -1) + return; + + TitlePage->UpdatePlayerPing(nick, 9999); + GSI_UNUSED(peer); + GSI_UNUSED(flags); + GSI_UNUSED(roomType); + GSI_UNUSED(param); +} + +// Called when the join is complete (successful or unsuccessful). +///////////////////////////////////////////////////////////////// +static PEERBool joinSuccess; +static void JoinTitleRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + void * param +) +{ + joinSuccess = success; + + if(success) + { +//PEERSTART + // List the players in the room. + //////////////////////////////// + peerEnumPlayers(Wizard->m_peer, TitleRoom, EnumTitlePlayersCallback, NULL); +//PEERSTOP + } + GSI_UNUSED(peer); + GSI_UNUSED(param); + GSI_UNUSED(roomType); + GSI_UNUSED(result); +} + +static void ListGroupRoomsCallback +( + PEER peer, + PEERBool success, + int groupID, + SBServer server, + const char * name, + int numWaiting, + int maxWaiting, + int numGames, + int numPlaying, + void * param +) +{ + // Check for failure. + ///////////////////// + if(!success) + { + Wizard->MessageBox("Listing groups failed!"); + Wizard->EndDialog(IDOK); + return; + } + + // Check for done. + ////////////////// + if(groupID == 0) + return; + + CListCtrl * groups = &TitlePage->m_groups; + int nIndex = groups->InsertItem(LVIF_TEXT | LVIF_PARAM, + 0, + name, + 0, 0, + 0, + (LPARAM)groupID); + if(nIndex == -1) + return; + + LV_ITEM item; + CString str; + item.mask = LVIF_TEXT; + item.iItem = nIndex; + item.iSubItem = COL_NUM_WAITING; + str.Format("%d", numWaiting); + item.pszText = (LPSTR)(LPCSTR)str; + groups->SetItem(&item); + item.iSubItem = COL_MAX_WAITING; + str.Format("%d", maxWaiting); + item.pszText = (LPSTR)(LPCSTR)str; + groups->SetItem(&item); + item.iSubItem = COL_NUM_GAMES; + str.Format("%d", numGames); + item.pszText = (LPSTR)(LPCSTR)str; + groups->SetItem(&item); + item.iSubItem = COL_NUM_PLAYING; + str.Format("%d", numPlaying); + item.pszText = (LPSTR)(LPCSTR)str; + groups->SetItem(&item); + GSI_UNUSED(peer); + GSI_UNUSED(param); + GSI_UNUSED(server); +} + +BOOL CTitlePage::OnSetActive() +{ + // Show the back and next buttons. + ////////////////////////////////// + Wizard->SetWizardButtons(PSWIZB_BACK); + + // Rename the next button. + ////////////////////////// + ::SetWindowText(Wizard->m_nextButtonWnd, "&Join >"); + + // Clear the groups list. + ///////////////////////// + m_groups.DeleteAllItems(); + + // Clear the chat log. + ////////////////////// + m_chatWindow.ResetContent(); + +//PEERSTART + // Join the title room. + /////////////////////// + Wizard->StartHourglass(); + peerJoinTitleRoom(Wizard->m_peer, NULL, JoinTitleRoomCallback, NULL, PEERTrue); + Wizard->StopHourglass(); + if(!joinSuccess) + { + MessageBox("Error joining the title room."); + return FALSE; + } + + // Start listing group rooms. + ///////////////////////////// + peerListGroupRooms(Wizard->m_peer, "", ListGroupRoomsCallback, NULL, PEERFalse); +//PEERSTOP + + return CPropertyPage::OnSetActive(); +} + +BOOL CTitlePage::OnKillActive() +{ + // Reset the "next" button's name. + ////////////////////////////////// + ::SetWindowText(Wizard->m_nextButtonWnd, "&Next >"); + + // Clear the player's. + ///////////////////// + m_players.DeleteAllItems(); + +//PEERSTART + // Leave the title room. + //////////////////////// + if(Wizard->m_peer) + peerLeaveRoom(Wizard->m_peer, TitleRoom, NULL); +//PEERSTOP + + return CPropertyPage::OnKillActive(); +} + +LRESULT CTitlePage::OnWizardNext() +{ + // Update vars. + /////////////// + UpdateData(); + + // Make sure something was selected. + //////////////////////////////////// + int nIndex = m_groups.GetSelectionMark(); + if(nIndex == -1) + { + MessageBox("You must have a group selected."); + return -1; + } + + // Join the group. + ////////////////// + JoinGroup(nIndex); + + return (LRESULT)GROUP_PAGE; +} + +BOOL CTitlePage::OnInitDialog() +{ + CPropertyPage::OnInitDialog(); + + // Set the groups columns. + ////////////////////////// + m_groups.InsertColumn(COL_NAME, "Group", LVCFMT_LEFT, 90); + m_groups.InsertColumn(COL_NUM_WAITING, "Num Waiting", LVCFMT_LEFT, 75); + m_groups.InsertColumn(COL_MAX_WAITING, "Max Waiting", LVCFMT_LEFT, 75); + m_groups.InsertColumn(COL_NUM_GAMES, "Num Games", LVCFMT_LEFT, 75); + m_groups.InsertColumn(COL_NUM_PLAYING, "Num Playing", LVCFMT_LEFT, 75); + ListView_SetExtendedListViewStyle(m_groups.m_hWnd,LVS_EX_FULLROWSELECT); + + // Set the players columns. + /////////////////////////// + m_players.InsertColumn(COL_NAME, "Player", LVCFMT_LEFT, 120); + + return TRUE; +} + +// Find the index of a player in the list by its nick. +////////////////////////////////////////////////////// +int CTitlePage::FindPlayer(const char *nick) +{ + // Always deal in lower-case. + ///////////////////////////// + CString loweredNick = nick; + loweredNick.MakeLower(); + nick = loweredNick; + + // Look for this player. + //////////////////////// + LVFINDINFO findInfo; + findInfo.flags = LVFI_STRING; + findInfo.psz = nick; + + // Find the player. + /////////////////// + int nIndex = m_players.FindItem(&findInfo); + + return nIndex; +} + +// Updates the player's ping in the player list, and adds the player +// if its not on the list. +//////////////////////////////////////////////////////////////////// +void CTitlePage::UpdatePlayerPing(const char *nick, int ping) +{ + LVITEM item; + + // Is this us? + ////////////// + if(strcasecmp(nick, ConnectPage->m_nick) == 0) + ping = 0; + + // Always deal in lower-case. + ///////////////////////////// + CString loweredNick = nick; + loweredNick.MakeLower(); + nick = loweredNick; + + // Find the player. + /////////////////// + int nIndex = FindPlayer(nick); + + // Check for a new nick. + //////////////////////// + if(nIndex == -1) + { + item.iItem = 0; + item.iSubItem = 0; + item.mask = LVIF_TEXT; + item.pszText = (char *)nick; + + nIndex = m_players.InsertItem(&item); + if(nIndex == -1) + return; + } + +#if 0 + // Add the ping. + //////////////// + char intValue[16]; + sprintf(intValue, "%d", ping); + item.iItem = nIndex; + item.iSubItem = 1; + item.mask = LVIF_TEXT; + item.pszText = intValue; + m_players.SetItem(&item); +#endif +} + +// Remove the player from the list. +/////////////////////////////////// +void CTitlePage::RemovePlayer(const char * nick) +{ + // Always deal in lower-case. + ///////////////////////////// + CString loweredNick = nick; + loweredNick.MakeLower(); + nick = loweredNick; + + // Find the player. + /////////////////// + int nIndex = FindPlayer(nick); + + // Remove it. + ///////////// + m_players.DeleteItem(nIndex); +} + +// Change a nick in the player list. +//////////////////////////////////// +void CTitlePage::ChangePlayerNick(const char * oldNick, const char * newNick) +{ + // Always deal in lower-case. + ///////////////////////////// + CString loweredNick = oldNick; + loweredNick.MakeLower(); + oldNick = loweredNick; + loweredNick = newNick; + loweredNick.MakeLower(); + newNick = loweredNick; + + // Find the player. + /////////////////// + int nIndex = FindPlayer(oldNick); + + // Update the nick. + /////////////////// + LVITEM item; + item.iItem = nIndex; + item.iSubItem = 0; + item.mask = LVIF_TEXT; + item.pszText = (char *)newNick; + m_players.SetItem(&item); +} + +// Does the actual chat message sending. +//////////////////////////////////////// +void CTitlePage::SendMessage() +{ + UpdateData(); + + // Ignore blank message. + //////////////////////// + if(m_message == "") + return; + +//PEERSTART + // Send it. + /////////// + peerMessageRoom(Wizard->m_peer, TitleRoom, m_message, NormalMessage); +//PEERSTOP + + // Clear it. + //////////// + m_message = ""; + + UpdateData(FALSE); +} + +// Join a group. +//////////////// +void CTitlePage::OnDblclkGroups(NMHDR* pNMHDR, LRESULT* pResult) +{ + NMHEADER * header = (NMHEADER *)pNMHDR; + + // If something was double-clicked, join it. + //////////////////////////////////////////// + if(header->iItem != -1) + JoinGroup(header->iItem); + + *pResult = 0; +} + +// Handle group list clicks. +//////////////////////////// +void CTitlePage::OnClickGroups(NMHDR* pNMHDR, LRESULT* pResult) +{ + // Enable/Disable join based on if something is selected. + ///////////////////////////////////////////////////////// + BOOL enable = (m_groups.GetSelectedCount() > 0); + ::EnableWindow(Wizard->m_nextButtonWnd, enable); + + *pResult = 0; + GSI_UNUSED(pNMHDR); +} +void CTitlePage::OnBegindragGroups(NMHDR* pNMHDR, LRESULT* pResult) +{ + // Treat this like a click. + // (OnClick() isn't called for "drags". + /////////////////////////////////////// + OnClickGroups(pNMHDR, pResult); +} + +// Called when the join is complete (successful or unsuccessful). +///////////////////////////////////////////////////////////////// +static void JoinGroupRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + void * param +) +{ + joinSuccess = success; + GSI_UNUSED(roomType); + GSI_UNUSED(peer); + GSI_UNUSED(param); + GSI_UNUSED(result); +} + +// Join a group based on its index in the groups list. +////////////////////////////////////////////////////// +void CTitlePage::JoinGroup(int nIndex) +{ + // Get the group ID. + ///////////////////////// + int groupID = (int)m_groups.GetItemData(nIndex); + ASSERT(groupID); + +//PEERSTART + // Join the group room. + /////////////////////// + Wizard->StartHourglass(); + peerJoinGroupRoom(Wizard->m_peer, groupID, JoinGroupRoomCallback, NULL, PEERTrue); + Wizard->StopHourglass(); + if(!joinSuccess) + { + MessageBox("Error joining the group room."); + return; + } +//PEERSTOP + + // Goto the group room page. + //////////////////////////// + Wizard->SetActivePage(GROUP_PAGE); +} diff --git a/code/gamespy/Peer/PeerLobby/TitlePage.h b/code/gamespy/Peer/PeerLobby/TitlePage.h new file mode 100644 index 00000000..0d356d21 --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/TitlePage.h @@ -0,0 +1,70 @@ +#if !defined(AFX_TITLEPAGE_H__15BC897B_80B2_4DED_8622_568C60F30225__INCLUDED_) +#define AFX_TITLEPAGE_H__15BC897B_80B2_4DED_8622_568C60F30225__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// TitlePage.h : header file +// + +#include "../peer.h" + +///////////////////////////////////////////////////////////////////////////// +// CTitlePage dialog + +class CTitlePage : public CPropertyPage +{ + DECLARE_DYNCREATE(CTitlePage) + +// Construction +public: + void JoinGroup(int nIndex); + CTitlePage(); + ~CTitlePage(); + +// Dialog Data + //{{AFX_DATA(CTitlePage) + enum { IDD = IDD_TITLE_PAGE }; + CListCtrl m_groups; + CListCtrl m_players; + CListBox m_chatWindow; + CString m_message; + //}}AFX_DATA + + void UpdatePlayerPing(const char * nick, int ping); + int FindPlayer(const char * nick); + void RemovePlayer(const char * nick); + void ChangePlayerNick(const char * oldNick, const char * newNick); + + void SendMessage(); + +// Overrides + // ClassWizard generate virtual function overrides + //{{AFX_VIRTUAL(CTitlePage) + public: + virtual BOOL OnSetActive(); + virtual BOOL OnKillActive(); + virtual LRESULT OnWizardNext(); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + // Generated message map functions + //{{AFX_MSG(CTitlePage) + virtual BOOL OnInitDialog(); + afx_msg void OnClickGroups(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnDblclkGroups(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnBegindragGroups(NMHDR* pNMHDR, LRESULT* pResult); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() + +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +extern CTitlePage * TitlePage; + +#endif // !defined(AFX_TITLEPAGE_H__15BC897B_80B2_4DED_8622_568C60F30225__INCLUDED_) diff --git a/code/gamespy/Peer/PeerLobby/res/PeerLobby.ico b/code/gamespy/Peer/PeerLobby/res/PeerLobby.ico new file mode 100644 index 0000000000000000000000000000000000000000..7eef0bcbe6580a6f464d688906172c2d9de44262 GIT binary patch literal 1078 zcmc&zF>b>!3}jLb9s)T}@Kod(893@u8ajANzT`op9^o+)S?=nU(FD@%0s)Sg^oyC8{H z9myetc;MEP)59v(LMa~xK8Yu^jIR*H22uCFiq5%C{s7(PJi>o15i^bmX4(vPxWAio z9ryY#AU_jfnd047-@`)XzL?%iS$gQyFP{44kS9X)fN{{QoL~hO-&=q&20Zr*cxFAt PkaNE{wR~2C$NfnjhSXWT literal 0 HcmV?d00001 diff --git a/code/gamespy/Peer/PeerLobby/res/PeerLobby.rc2 b/code/gamespy/Peer/PeerLobby/res/PeerLobby.rc2 new file mode 100644 index 00000000..9fb0ea68 --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/res/PeerLobby.rc2 @@ -0,0 +1,13 @@ +// +// PEERLOBBY.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/Peer/PeerLobby/res/ico00001.ico b/code/gamespy/Peer/PeerLobby/res/ico00001.ico new file mode 100644 index 0000000000000000000000000000000000000000..4d3d729a698b3e28fe8371e929a413447dba8eca GIT binary patch literal 318 zcmbu3xfKH;3`B?boG3DpV}~x25=hkzC2a@nANFwK%(_N10)bTUTB~fIBE5{Hl{Z=R zph{!aU!_TeT04MBt1$-X!Cav^-I2H@^V>G*X%d+o*b+ksE`$!sV95-=*fYai(RCpU U!aBGPdhxk;UhnuV|KdN}0qBZlhyVZp literal 0 HcmV?d00001 diff --git a/code/gamespy/Peer/PeerLobby/res/ico00002.ico b/code/gamespy/Peer/PeerLobby/res/ico00002.ico new file mode 100644 index 0000000000000000000000000000000000000000..f988b9373748e9f884ecf74c39be692d28179931 GIT binary patch literal 318 zcmbu3u@wU$3`0NfrJ^~i)G0G2qqz1IjKl_GIu%kX5NC|hedo(1k^l_w8X z;UvVs%4BJ_Qg65Yq?V?J+gksgw3L6hG?c)PX8{>6W>1L$C63jhEB literal 0 HcmV?d00001 diff --git a/code/gamespy/Peer/PeerLobby/res/ico00003.ico b/code/gamespy/Peer/PeerLobby/res/ico00003.ico new file mode 100644 index 0000000000000000000000000000000000000000..dc5c8127c3d044e8f5908912fd2d842d27a472fb GIT binary patch literal 318 zcmbu3F%m-|2t?PJbQa$#b;=ymBhk}%ByV8e`hSupT|jnO7(l7utu@)6MY@{_R~|&s z4V1>lU(h9;*afXk`EZ literal 0 HcmV?d00001 diff --git a/code/gamespy/Peer/PeerLobby/res/ico00004.ico b/code/gamespy/Peer/PeerLobby/res/ico00004.ico new file mode 100644 index 0000000000000000000000000000000000000000..771d4e1437071c66755c27e717a270637f62f582 GIT binary patch literal 318 zcmbu3F%m-|2t=PVsc61c>XbRAN3rG<9?2V+UH_OWT|jnWVL&MIS}V49fu~tv<)tV* zgfKS#f(ClDc9T#kV+^qyYd_XXbRAN3y5!NZ!Eg`o~o10~cYh1`&kVDs VIIIO>U3~VOz3be$59>RB*#|m6YB~S_ literal 0 HcmV?d00001 diff --git a/code/gamespy/Peer/PeerLobby/resource.h b/code/gamespy/Peer/PeerLobby/resource.h new file mode 100644 index 00000000..3bfc1876 --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/resource.h @@ -0,0 +1,45 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by PeerLobby.rc +// +#define IDD_PEERLOBBY_DIALOG 102 +#define IDP_SOCKETS_INIT_FAILED 103 +#define IDD_TITLE_PAGE 106 +#define IDD_CONNECT_PAGE 107 +#define IDD_GROUP_PAGE 108 +#define IDD_CREATE_PAGE 109 +#define IDD_STAGING_PAGE 110 +#define IDR_MAINFRAME 128 +#define IDI_YELLOW_SMILEY 130 +#define IDI_RED_SMILEY 131 +#define IDI_RUNNING_GAME 132 +#define IDI_STAGING_ROOM 133 +#define IDI_GREEN_SMILEY 134 +#define IDC_NICK 1000 +#define IDC_NAME 1001 +#define IDC_MAX_PLAYERS 1002 +#define IDC_CREATE_GAME 1004 +#define IDC_JOIN_GAME 1005 +#define IDC_GAMES 1006 +#define IDC_CHAT_WINDOW 1007 +#define IDC_PLAYERS 1008 +#define IDC_MESSAGE 1009 +#define IDC_NOT_READY 1017 +#define IDC_READY 1018 +#define IDC_CREATE 1020 +#define IDC_TITLE 1021 +#define IDC_KEY 1022 +#define IDC_GROUPS 1029 +#define IDC_GROUP_ROOMS 1030 +#define IDC_PROGRESS 10126 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 130 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1031 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/gamespy/Peer/changelog.txt b/code/gamespy/Peer/changelog.txt new file mode 100644 index 00000000..34fce00d --- /dev/null +++ b/code/gamespy/Peer/changelog.txt @@ -0,0 +1,255 @@ +Changelog for: GameSpy Peer SDK +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 2.07.00 RMV RELEASE Released to Developer Site +12-11-2007 2.06.03 SAH OTHER Added NatNeg to projects to remove compiler errors +11-27-2007 2.06.01 SAH CLEANUP Moved extern "c" block below includes to prevent linker errors +08-06-2007 2.06.00 RMV RELEASE Released to Developer Site +07-10-2007 2.05.02 RMV FIX Fixed peerc Project files to get rid of Unicode warnings and fixed other compiler warnings +02-16-2006 2.05.01 SAH FIX Fixed bug where room name for UNICODE version was not being set +12-15-2006 2.05.00 MJW RELEASE Released to Developer Site +12-07-2006 2.04.66 SAH FIX Fixed bug where pending IMCP queries were not removed from gameEngine in piSBGamesListCallback +10-10-2006 2.04.65 SN FIX Stop automatch now automatically has the player leave the room. +10-05-2006 2.04.65 SAH FIX Updated MacOSX Makefile +09-28-2006 2.04.64 SAH FIX Fixed PS3 project to work with PS3 095.00x SDK; changed included libaries in linker input. +08-02-2006 2.04.63 SAH RELEASE Releasing to developer site +07-31-2006 2.04.63 SAH FIX Fixed PS3 project file - added post-build step to create *.SELF for execution +07-25-2006 2.04.62 SAH FIX Fixed NITRO project, include for crt0.o now above others so it add correct file +07-06-2006 2.04.61 SAH FIX Fixed PSP project file to not explicitly include the PSP linker file +07-06-2006 2.04.60 SAH FIX Fixed Linux makefile to work with pthreads (for http asynch DNS lookup) + SAH FIX Fixed NITRO project & linker command file (to work with CW 2.0/NitroSDK 3.1) +06-05-2006 2.04.59 SN FIX Modified the start listing groups to use correct function for stopping the listing of groups +06-02-2006 2.04.58 SAH FIX Added GS_STATIC_CALLBACK on comparator functions for __fastcall support +05-31-2006 2.04.57 SAH RELEASE Releasing to developer site +05-30-2006 2.04.56 SAH FIX Fixed PS3 projects to work with PS3(084_001 SDK) +05-25-2006 2.04.55 SAH FIX Added GSI_UNUSED calls to get rid of PSP warnings + SAH FIX Changed PSP project warning levels +05-23-2006 2.04.54 SAH FIX added GSI_UNUSED calls to get rid of PS3 warnings +05-19-2006 2.04.53 SAH FIX Added gsTestMain.c to nitro CodeWarrior project +05-15-2006 2.04.52 SAH FIX Added "PS3 Release" configuration to project +04-25-2006 2.04.51 SAH RELEASE Releasing to developer site +04-24-2006 2.04.51 SAH FIX Fixed some compile errors with UNICODE build, mainly typecasts +04-24-2006 2.04.50 SAH FIX Fixed Nitro project files to work on build machine +04-20-2006 2.04.49 SAH FIX Commented out unused variables, Moved GSI_UNUSED above return calls + SAH FIX Removed uncessary check (if unsigned int > 0) + SAH FIX Removed uncessary break; statements +04-18-2006 2.04.48 SAH FIX Added || defined(_PS3) to peerc.c for PS3 support +04-14-2006 2.04.47 SAH FIX Got rid of GsCore in the project files, added compile errors +04-10-2006 2.04.46 SAH FEATURE Multiple sorts will retain previous sorting arrangement when values are equal +02-10-2006 2.02.45 SN FIX peerParseQueryW was redundant since qr2_parse_query simply needs an char stream + As encountered by a developer. +02-07-2006 2.02.44 SN FIX peerSetStagingRoomMaxPlayers sets the channel limit in addition to the state change +01-27-2006 2.02.43 SN RELEASE Releasing to developer site +01-26-2006 2.02.43 SN OTHER Added psp prodg project and solution to sgv +12-22-2005 2.02.42 SN OTHER Cleaned up vc6 projects and added latest common code if missing +12-16-2005 2.02.41 SN OTHER Added a solution for the SDK to vault and fixed bindings on vcproj files +12-13-2005 2.02.40 SN FIX Cleaned up .vcproj files to have latest common code + Fixed a compiler issue that would not get caught on VC6 +11-17-2005 2.02.39 DES FEATURE Added ability to send client messages to servers. + DES FIX Updated Nitro Makefile. + DES FIX Made peerc printf Unicode compatible. +11-14-2005 2.02.38 DES FIX Updated the OSX Makefile. + DES FEATURE Added GSI_DOMAIN_NAME support. +10-13-2005 2.02.37 BED FEATURE peerSB now supports QR2 query challenge. + BED FIX Function pointers now cast to int to prevent strict ANSI warnings +09-21-2005 2.02.36 DES FEATURE Updated DS support +08-16-2005 2.02.35 SN FIX Added support for NatNeg in automatch. +07-28-2005 2.02.34 SN RELEASE Releasing to developer site. +07-28-2005 2.02.34 SN FIX Fixed code causing warning in peerc test app +06-03-2004 2.02.33 SN RELEASE Releasing to developer site. +06-03-2005 2.02.33 SN FIX Fixed issue where people behind same firewall as host could join full staging rooms. +05-13-2005 2.02.32 SN FIX Fixed issue where group room internal data was being incompletely cleared, causing crashes. +05-05-2005 2.02.31 BED FIX Update header files for new common code. +05-05-2005 2.02.30 SN OTHER Created Visual Studio .NET project +04-28-2005 2.02.30 SN RELEASE Releasing to developer site. +04-27-2005 2.02.30 DES RELEASE Limited release to Nintendo DS developers. +04-27-2005 2.02.30 DES FEATURE Updates to make peerc more friendly. + DES FIX On Nitro only, Ping the chat server ever 20 seconds to stay connected. +04-27-2005 2.02.29 SN FEATURE Made changes to allow developers to override default chat ping time. +04-25-2005 2.02.28 DES CLEANUP Disable Win32 linker warning. +04-04-2005 2.02.27 SN RELEASE Releasing to developer site. +04-01-2005 2.02.27 SN FIX Fixed and handled case where getting automatch channel modes failed +03-14-2005 2.02.26 DES FEATURE Nintendo DS support +01-27-2005 2.02.25 DES FIX Fixed custom SN sendto and moved it to nonport +11-06-2004 2.02.24 SN FIX Fixed hashing algorithm to minimize collisions +11-04-2004 2.02.23 SN FIX Fixed calls to SB Interal calls +10-15-2004 2.02.22 SN FEATURE Added SDK side nickname checking +09-27-2004 2.02.21 SN RELEASE Releasing with Qr2 fix to developer site +09-17-2004 2.02.21 JED FIX Added robustness to piConnectFillInUserCallback & PEER_CONNECTION_OP, Arcade reported some null pointer crashes +09-16-2004 2.02.20 SN RELEASE Releasing to developer site. +08-27-2004 2.02.20 DES CLEANUP Fixed warnings under OSX + DES CLEANUP Removed peerc.dsp's dependency on peer.dsp + DES CLEANUP General Unicode cleanup + DES CLEANUP Removed MacOS style includes + DES CLEANUP Updated Win32 project configurations + DES CLEANUP Removed headers already included in nonport.h + DES CLEANUP Updated OSX Makefile +08-26-2004 2.02.19 SN FEATURE Added peerUpdateServerByMaster function to update a server through the master server +08-26-2004 2.02.18 DES CLEANUP Removed MacOS stlye includes +08-16-2004 2.02.18 BED FIX Fixed case where players would become stuck in automatch searching phase +08-05-2004 2.02.17 SN RELEASE Releasing to developer site. +08-05-2004 2.02.17 SN FIX Added a parameter missing in a prototype (BAD, compiler could not catch it!!!). +07-19-2004 2.02.17 SN FIX Updated code with explicit casts to remove implicit cast error + when compiling at highest level and warnings treated as errors. +06-21-2004 2.02.16 BED FIX Fixed bug with peerc sample's handling of NickErrorCallback +06-18-2004 2.02.15 BED RELEASE Releasing to developer site. +06-17-2004 2.02.15 BED FIX Fixed bug where piListingGamesCall was prematurely showing 100% progress. + BED FEATURE Added PS2 Insock support +05-21-2004 2.02.14 DDW FEATURE Minor change to accomodate Peer/VEngine compatibility +03-04-2004 2.02.13 BED FIX Fixed bug in Unicode wrapper when retrieving keys for user "*" +12-03-2003 2.02.12 BED FIX Fixed bug where duplicate key change callbacks were called for broadcast keys in staging rooms +12-02-2003 2.02.11 BED FIX Fixed parameter mismatch when using cached player info. +11-10-2003 2.02.10 DES RELEASE Releasing to developer site. +11-07-2003 2.02.10 BED FIX Updated CodeWarrior project files. +11-07-2003 2.02.09 DES FIX Updated linux and PS2 makefiles. + FIX Fixed a case mismatch for a chat include. + FIX Took some debug code out of peerc.c. + FIX Wrapped some internal unicode functions in GSI_UNICODE ifdefs. + FIX Commented out functions in peerc.c that were no longer being used. +11-04-2003 2.02.08 DES FEATURE Added availability check code. +10-29-2003 2.02.07 DES FEATURE Added code to PeerTest for doing the available check. + DES FIX Removed SN compiler options from PeerTest's project. +10-21-2003 2.02.06 BED RELEASE Releasing to developer site. (UNIQUE NICK AND UNICODE SUPPORT) + BED FIX Removed misc compiler warnings on strict settings. +10-21-2003 2.02.05 DES FEATURE Updated PeerTest to handle new connect methods. +10-16-2003 2.02.04 BED FIX Changed from UCS2ToUTF8String to UCS2ToAsciiString when dealing with nicknames. + FEATURE Added peerSetStagingRoomMaxPlayers. Developer request to allow host to update this. + DES FIX peerPingPlayer no longer returns PEERFalse if there is an outstanding ping for the player. + FIX Correctly handle peerConnectLogin with a 0 namespaceID. +10-09-2003 2.02.03 BED FIX Switched to gsi_time type instead of unsinged long for PS2 compatibility +10-08-2003 2.02.02 DES FEATURE Added peerPingPlayer for doing one-time pings in non-ping rooms. +10-01-2003 2.02.01 DES FEATURE Added a reason code for failed connect attempts. + FEATURE Added suggested nicks for a CHAT_INVALID_UNIQUENICK nick errors. +09-30-2003 2.02.00 DES FEATURE Uniquenick support. +09-08-2003 2.01.27 BED FEATURE Added UTF-8 wrapper for UNICODE support. +08-29-2003 2.01.26 DES RELEASE Released to Stainless Steel for Empires: DotMW. + FIX Compatible AM's that start at the same time will now get matched. + FIX No longer queries an AM server if a query is already pending. +08-18-2003 2.01.25 DES FEATURE Added a callback for getting your public reporting address. +07-24-2003 2.01.24 DES RELEASE Releasing to developer site. +07-24-2003 2.01.24 DES CLEANUP Removed unused roomType parameter from piPingPlayerLeftRoom. +07-23-2003 2.01.23 BED FEATURE Added Linux sample Makefile. +07-18-2003 2.01.22 BED FEATURE Added CodeWarrior (PS2) sample project file. + CLEANUP General cleanup to remove CodeWarrior warnings. +07-17-2003 2.01.21 DES CLEANUP Cleaned up the PS2 Makefile, it now uses Makefile.commmon. +07-16-2003 2.01.20 DES FIX Changed a __mips64 check to a _PS2 check. + BED FEATURE Added ProDG sample project files. +07-10-2003 2.01.19 DES FIX Fixed unused value warning in pinger. +06-26-2003 2.01.18 DES FEATURE Added peerGetRoomHost to get the SBServer for the staging room host. +06-26-2003 2.01.17 DES RELEASE Releasing to developer site. + FEATURE peerJoinStagingRoomByChannel added for joining a staging room based on channel name. + FIX Fixed peerShutdown to disconnect before clearing the title. + FIX PeerTest now uses PEER_FLAG_HOST instead of PEER_FLAG_OP to determine the room host. + FIX Fixed PeerTest to refresh a room's players when a player's host flag changes. +06-24-2003 2.01.16 DES FIX Fixed peerStopAutoMatch comment to say it will not leave a staging room. +06-11-2003 2.01.15 DES RELEASE Releasing to developer site. +06-04-2003 2.01.15 DDW FIX Fixed binding to private IP when both private and public are present +05-11-2003 2.01.14 DDW CLEANUP Compatibility with SB internal API changes +05-09-2003 2.01.13 DES CLEANUP Removed Dreamcast support. + FIX Metrowerks for Win32 is no longer falsely identified as MacOS. +05-07-2003 2.01.12 DES RELEASE Releasing to developer site. + FIX Updated PS2 Makefile for AutoMatch. +05-07-2003 2.01.12 JED CLEANUP Replaced refrences in comments to the dyslexic peerGameStart() with the proper peerStartGame() +05-03-2003 2.01.11 DES FIX Was unable to join an AutoMatch room if the host was behind a firewall. +04-25-2003 2.01.10 DES FIX Flipped peerGetPlayerInfo IP and profileID paramters to match the NoWait version. + FIX peerParseQuery wasn't handling queries correctly during an AutoMatch attempt. + CLEANUP peerStartAutoMatch[WithSocket] now returns void. If it fails to start the + attempt, it calls the statusCallback with PEERFailed. + FIX peerStartAutoMatch[WithSocket] wasn't doing blocking correctly. +04-24-2003 2.01.09 DES FEATURE Added peerGetPlayerInfo to replace peerGetPlayerProfileID and peerGetPlayerIP. + Both old functions are now deprecated and will be removed in a future version. +04-23-2003 2.01.08 DES FEATURE peerGameStartedCallback is now passed the host's SBServer instead of IP. + FEATURE peerGetLocalIP has been replaced by peerGetPublicIP and peerGetPrivateIP. + FIX AutoMatch now correctly checks if a listed match is the local player or not. +04-03-2003 2.01.07 DES FIX If maxPlayers was set to 0 in peerCreateStagingRoom[WithSocket], + the qrServerKey callback was not being called for MAXPLAYERS_KEY. +03-27-2003 2.01.06 DES FIX piGetPrivateIP now returns the IP instead of true/false. +03-26-2003 2.01.05 DES FEATURE Added peerSetGroupID and peerGetGroupID, which allows for getting a list + of games in a group room and reporting a game as being in a group, + without having to actually be in that group room. + CLEANUP Removed piListingGamesGroupID method of setting group ID. + OTHER Updated to pass the new extra parameter to SBServerListConnectAndQuery(). +03-24-2003 2.01.04 DES FIX piStartHosting and piStartReportingWithSocket will now correctly return + PEERFalse if piStartReporting fails. + FIX Updated the PS2 Makefile to compile files recently added to Peer. + OTHER Changed interal IP code to use the common code's IsPrivateIP. +03-21-2003 2.01.03 DES FEATURE Added the ability to join a passworded title room. + FEATURE Added a global (piListingGamesGroupID) to control what group ID to use + when listing games. Use -1 for games that aren't in a group. + OTHER Doesn't initialize pinger if there are no ping rooms. + OTHER During initial server listing, don't query server that already have keys. +03-19-2003 2.01.02 DES FEATURE Added a player flag PEER_FLAG_HOST to identify a staging room host. + FIX Switched to a new set of crypt characters for encoding IPs, the old set + had both 'F' and 'f', which could cause conflicting staging room names. + FIX No longer need to pass a list of fields to peerStartAutoMatch[WithSocket](). + FIX General AutoMatch fixes. +03-13-2003 2.01.01 DES OTHER Shortened the length of the new staging room names. + OTHER If peerIsPlayerHost() is called for the local player, return Peer's hosting flag. + CLEANUP Changed code that used internal members of in_addr (not always fully supported). +03-12-2003 2.01.00 DES FEATURE Full AutoMatch support. + FEATURE Added peerAlwaysGetPlayerInfo(), which is used to tell Peer to always + get players' IPs and Profile IDs whether rooms are ping rooms or not. + FEATURE Added the ability to host multiple staging rooms behind the same public IP. + The staging room name is now based on public IP, private IP, and private queryport. + FEATURE Peer now sets limits on staging rooms to allow no more than the maxPlayers. + This behavior can be turned off by defining PI_NO_STAGING_ROOM_LIMIT. + If maxPlayers is 0, no limit is set, and maxPlayers is not reported. + FEATURE If 0 keys are requested on a room or a player, all of the keys will be returned. + FEATURE Added piSBQueryVersion global flag to set the type of server queries to send. + FIX If a server is removed, clear any pending add/update calls for the same server. + CLEANUP Changed _strdup() calls to goastrdup(). + CLEANUP Added some explicit casts to remove compiler warnings. + CLEANUP Removed peerJoinStagingRoomByIP(). + CLEANUP Peer no longer stores passwords, it only stores whether or not one has been set. + CLEANUP Improved the process of cancelling a join that is already in progress. +02-10-2003 2.00.19 DES CLEANUP Better seperation between hosting (hosting a staging room) + and reporting (reporting a staging room or running game). + FEATURE First steps for AutoMatch support. +02-28-2003 2.00.18 DES RELEASE Releasing to developer site with updated QR2. +02-05-2003 2.00.18 DES RELEASE Releasing to developer site. +02-05-2003 2.00.18 DES CLEANUP Switched to use CanReceiveOnSocket instead of select. +02-04-2003 2.00.17 DES RELEASE Releasing to developer site. +01-23-2003 2.00.17 DES FIX Replaced several calls to free with gsifree. +01-08-2003 2.00.16 DES FIX Fixed passing the incorrect param value to the peerListGroupRooms callback. +12-19-2002 2.00.15 DES RELEASE Releasing to developer site. +12-19-2002 2.00.15 DES CLEANUP Removed assert.h includes. +12-16-2002 2.00.14 DES CLEANUP Removed call to GOAClearSocketError in pinger. +12-13-2002 2.00.13 DES FEATURE Added PS2 eenet stack support. +12-06-2002 2.00.12 DES RELEASE Releasing to developer site with updated QR2. +12-05-2002 2.00.12 DES CLEANUP Removed an extraneous assert. + CLEANUP Changes to remove warnings. +12-03-2002 2.00.11 DES RELEASE Releasing to developer site. +12-03-2002 2.00.11 DES FIX Fixed PeerTest to correctly show server state flags. + FEATURE Added Update button to PeerTest, uses peerUpdateGame to get a server's full keys. +11-25-2002 2.00.10 DES FIX Fixed PeerTest to handle e_qrnochallengeerror. +11-22-2002 2.00.09 DES RELEASE Releasing to developer site. +11-20-2002 2.00.09 DES CLEANUP Cleaned up to remove compiler warings on the PS2. +11-20-2002 2.00.08 DES FIX Fixed bug which caused blocking functions to lockup if disconnected while blocking. +11-14-2002 2.00.07 DES FEATURE Added peerKickPlayer function to SDK and to PeerTest +11-07-2002 2.00.06 DES RELEASE Limited release on developer site and to EAPAC for Generals + Incorporates serverbrowsing change to fix invalid SBServer in listing games callback. +11-07-2002 2.00.06 DES FIX Fixed negative hash due to high-ascii characters in hashed string. +10-28-2002 2.00.05 DES RELEASE Limited release on developer site and to EAPAC for Generals +10-28-2002 2.00.05 DES FIX Correctly passing request fields to SB when starting listing. + Changed peerStartListingGames API to pass in keys by id instead of name. + Changed tests/samples to use new API. + SB callbacks debugging code now also shows the server address or group id. +10-22-2002 2.00.04 DES RELEASE Limited release on developer site and to EAPAC for Generals +10-22-2002 2.00.04 DES FIX Updated SB querying to use QR2 style instead of old GOA style + Update PeerTest to always check for no currently selected server +10-22-2002 2.00.03 DDW FIX Clears query engine when stopping listing of games + Correctly removes servers from query engine when deleted message arrives +10-17-2002 2.00.02 DES RELEASE Limited release on developer site and to EAPAC for Generals + Incorporates fix to Chat SDK +10-10-2002 2.00.02 DES RELEASE Limited release on developer site and to EAPAC for Generals +10-10-2002 2.00.02 DES FIX SBQueryEngineSetPublicIP is now called when the serverlist + determines the public IP (slc_publicipdetermined). +09-26-2002 2.00.01 DES RELEASE Limited release on developer site +09-25-2002 2.00.01 DDW OTHER Changelog started +09-23-2002 2.00.01 DES RELEASE Release to EAPAC for Generals +09-23-2002 2.00.01 DES FEATURE Added peerUpdateGame function +09-06-2002 2.00.00 DDW RELEASE Release to EAPAC for Generals +09-06-2002 2.00.00 DES FEATURE Replaces CEngine/QR with ServerBrowsing/QR2 +06-25-2002 1.20.00 DES RELEASE Release on deveoper site \ No newline at end of file diff --git a/code/gamespy/Peer/peer.h b/code/gamespy/Peer/peer.h new file mode 100644 index 00000000..1b0978ac --- /dev/null +++ b/code/gamespy/Peer/peer.h @@ -0,0 +1,1866 @@ + /* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEER_H_ +#define _PEER_H_ + +/************* +** INCLUDES ** +*************/ +#include "../common/gsCommon.h" +#include "../Chat/chat.h" +#include "../qr2/qr2.h" +#include "../serverbrowsing/sb_internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/************ +** DEFINES ** +************/ +// Defines for the msg param that's passed into peerListingGamesCallback(). +// PANTS-04.20.00-changed from PI_* to PEER_* +// PANTS-09.26.02-added PEER_COMPLETE +/////////////////////////////////////////////////////////////////////////// +#define PEER_ADD 0 // a server is being added +#define PEER_UPDATE 1 // a server has been updated +#define PEER_REMOVE 2 // a server has been removed +#define PEER_CLEAR 3 // all the servers have been cleared +#define PEER_COMPLETE 4 // the initial listing of servers is complete + +// Nick errors, for peerNickErrorCallback. +////////////////////////////////////////// +#define PEER_NICK_OK 0 +#define PEER_IN_USE 1 // the nick is already being used +#define PEER_INVALID 2 // the nick contains invalid characters +#define PEER_UNIQUENICK_EXPIRED 3 // the uniquenick for this account has expired +#define PEER_NO_UNIQUENICK 4 // there is no uniquenick for this account +#define PEER_INVALID_UNIQUENICK 5 // the uniquenick to associate with the account is invalid or in use +#define PEER_NICK_TOO_LONG 6 // the nick was too long + +// Possible values for the failureReason passed to the peerConnectCallback. +/////////////////////////////////////////////////////////////////////////// +#define PEER_DISCONNECTED 0 // disconnected from, or unable to connect to, the server +#define PEER_NICK_ERROR 1 // a nick error was either ignored or not handled +#define PEER_LOGIN_FAILED 2 // the login info passed to peerConnectLogin was not valid + +// Maximum length of a room password, including the terminating NUL. +//////////////////////////////////////////////////////////////////// +#define PEER_PASSWORD_LEN 24 + +// Each player can have various flags set for each room they are in. +//////////////////////////////////////////////////////////////////// +#define PEER_FLAG_STAGING 0x01 // s +#define PEER_FLAG_READY 0x02 // r +#define PEER_FLAG_PLAYING 0x04 // g +#define PEER_FLAG_AWAY 0x08 // a +#define PEER_FLAG_HOST 0x10 // h +#define PEER_FLAG_OP 0x20 +#define PEER_FLAG_VOICE 0x40 + +// Bitfield reporting options for peerStartGame. +//////////////////////////////////////////////// +#define PEER_KEEP_REPORTING 0 // Continue reporting. +#define PEER_STOP_REPORTING 1 // Stop reporting. Cannot be used with other options. +#define PEER_REPORT_INFO 2 // Continue reporting server keys (as if it were not playing). +#define PEER_REPORT_PLAYERS 4 // Continue reporting player keys (as if it were not playing). + +/********** +** TYPES ** +**********/ +// The peer object. +/////////////////// +typedef void * PEER; + +// Boolean. +/////////// +typedef enum +{ + PEERFalse, + PEERTrue +} PEERBool; + +// Types of rooms. +////////////////// +typedef enum +{ + TitleRoom, // The main room for a game. + GroupRoom, // A room which is, in general, for a particular type of gameplay (team, dm, etc.). + StagingRoom, // A room where players meet before starting a game. + NumRooms +} RoomType; + +// Types of messages. These have the same +// values as their CHAT SDK counterparts. +// PANTS-01.08.01 +///////////////////////////////////////// +typedef enum +{ + NormalMessage, + ActionMessage, + NoticeMessage +} MessageType; + +// Possible results when attempting to join a room. +// Passed into peerJoinRoomCallback(). +/////////////////////////////////////////////////// +typedef enum +{ + PEERJoinSuccess, // The room was joined. + + PEERFullRoom, // The room is full. + PEERInviteOnlyRoom, // The room is invite only. + PEERBannedFromRoom, // The local user is banned from the room. + PEERBadPassword, // An incorrect password (or none) was given for a passworded room. + + PEERAlreadyInRoom, // The local user is already in or entering a room of the same type. + PEERNoTitleSet, // Can't join a room if no title is set. + PEERNoConnection, // Can't join a room if there's no chat connection. + PEERAutoMatching, // The user can't join a staging room during an auto match attempt. + + PEERJoinFailed // Generic failure. +} PEERJoinResult; + +// Possible status values passed to the peerAutoMatchStatusCallback. +// If PEERFailed, the match failed, otherwise this is the current status +// of the automatch attempt. +//////////////////////////////////////////////////////////////////////// +typedef enum +{ + PEERFailed, // The automatch attempt failed. + + PEERSearching, // Searching for a match (active). + PEERWaiting, // Waiting for a match (passive). + PEERStaging, // In a staging room with at least one other player, possibly waiting for more. + PEERReady, // All players are in the staging room, the game is ready to be launched. + PEERComplete // The game is launching, the automatch attempt is now complete. + // The player is still in the staging room. +} PEERAutoMatchStatus; + +/************** +** CALLBACKS ** +**************/ +// Called when the connection to the server gets disconnected. +////////////////////////////////////////////////////////////// +typedef void (* peerDisconnectedCallback) +( + PEER peer, // The peer object. + const gsi_char * reason, // The reason for the disconnection. + void * param // User-data. +); + +// Called when a message is sent to a room the local player is in. +////////////////////////////////////////////////////////////////// +typedef void (* peerRoomMessageCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of room that the message was in. + const gsi_char * nick, // The nick of the player who sent the message. + const gsi_char * message, // The text of the message. + MessageType messageType, // The type of message. + void * param // User-data. +); + +// Called when a UTM is sent to a room the local player is in. +////////////////////////////////////////////////////////////// +typedef void (* peerRoomUTMCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of room that the UTM was in. + const gsi_char * nick, // The nick of the player who sent the UTM. + const gsi_char * command, // The UTM command for this message. + const gsi_char * parameters, // Any parameters for this UTM. + PEERBool authenticated, // True if this has been authenticated by the server. + void * param // User-data. +); + +// Called when the name of a room the player is in changes. +// The new name can be checked with peerGetRoomName. +// PANTS|09.11.00 +/////////////////////////////////////////////////////////// +typedef void (* peerRoomNameChangedCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of room that the name changed in. + void * param // User-data +); + +// Called when a room's mode changes. +// PANTS|04.17.00 +/////////////////////////////////////////////////////////// +typedef void (* peerRoomModeChangedCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of room that the name changed in. + CHATChannelMode * mode, // The current mode for this room. + void * param // User-data +); + +// Called when a private message is received from another player. +///////////////////////////////////////////////////////////////// +typedef void (* peerPlayerMessageCallback) +( + PEER peer, // The peer object. + const gsi_char * nick, // The nick of the player who sent the message. + const gsi_char * message, // The text of the message. + MessageType messageType, // The type of message. + void * param // User-data +); + +// Called when a private UTM is received from another player. +///////////////////////////////////////////////////////////// +typedef void (* peerPlayerUTMCallback) +( + PEER peer, // The peer object. + const gsi_char * nick, // The nick of the player who sent the UTM. + const gsi_char * command, // The UTM command for this message. + const gsi_char * parameters, // Any parameters for this UTM. + PEERBool authenticated, // True if this has been authenticated by the server. + void * param // User-data +); + +// Called when a player's ready state changes, +// from a call to peerSetReady(). +////////////////////////////////////////////// +typedef void (* peerReadyChangedCallback) +( + PEER peer, // The peer object. + const gsi_char * nick, // The nick of the player who's ready state changed. + PEERBool ready, // The player's new ready state. + void * param // User-data. +); + +// Called when the host of a staging room launches the game, +// with a call to peerStartGame(). +// The public and private IPs and ports of the server can +// be obtained from the server object. +//////////////////////////////////////////////////////////// +typedef void (* peerGameStartedCallback) +( + PEER peer, // The peer object. + SBServer server, // A server object representing this host. + const gsi_char * message, // A message that was passed into peerStartGame(). + void * param // User-data. +); + +// A player joined a room. +////////////////////////// +typedef void (* peerPlayerJoinedCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of room that the player joined. + const gsi_char * nick, // The nick of the player that joined. + void * param // User-data. +); + +// A player left a room. +//////////////////////// +typedef void (* peerPlayerLeftCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of room that the player left. + const gsi_char * nick, // The nick of the player that left. + const gsi_char * reason, // The reason the player left. + void * param // User-data. +); + +// The local player was kicked from a room. +/////////////////////////////////////////// +typedef void (* peerKickedCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of room that the player was kicked from. + const gsi_char * nick, // The nick of the player that did the kicking. + const gsi_char * reason, // An optional reason for the kick. + void * param // User-data. +); + +// The entire player list for this room has been updated. +///////////////////////////////////////////////////////// +typedef void (* peerNewPlayerListCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of room. + void * param // User-data +); + +// A player in one of the rooms changed his nick. +///////////////////////////////////////////////// +typedef void (* peerPlayerChangedNickCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of the room the nick changed was in. + const gsi_char * oldNick, // The player's old nick. + const gsi_char * newNick, // The player's new nick. + void * param // User-data. +); + +// The IP and ProfileID for this player has just been received. +// PANTS|01.08.01 +// This gets called for all players (who are using peer) in a room +// shortly after joining. It will be called with nick==NULL after +// getting info for all the players. +////////////////////////////////////////////////////////////////// +typedef void (* peerPlayerInfoCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The room the info was gotten in. + const gsi_char * nick, // The nick of the player the info is for. + unsigned int IP, // The player's IP. + int profileID, // The player's profile ID. + void * param // User-data. +); + +// This gets called when a player's flags have changed. +/////////////////////////////////////////////////////// +typedef void (* peerPlayerFlagsChangedCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The room the flags were changed in. + const gsi_char * nick, // The player whose flags have changed. + int oldFlags, // The player's old flags. + int newFlags, // The player's new flags. + void * param // User-data +); + +// An updated ping for a player, who may be in any room(s). +/////////////////////////////////////////////////////////// +typedef void (* peerPingCallback) +( + PEER peer, // The peer object. + const gsi_char * nick, // The other player's nick. + int ping, // The ping. + void * param // User-data. +); + +// An updated cross-ping between two players in the staging room. +///////////////////////////////////////////////////////////////// +typedef void (* peerCrossPingCallback) +( + PEER peer, // The peer object. + const gsi_char * nick1, // The first player's nick. + const gsi_char * nick2, // The second player's nick. + int crossPing, // The cross-ping. + void * param // User-data. +); + +// This is called for watch keys when a room is joined, for +// watch keys when another player joins, and for any newly +// set watch keys. +/////////////////////////////////////////////////////////// +typedef void (* peerGlobalKeyChangedCallback) +( + PEER peer, // The peer object. + const gsi_char * nick, // The player whose key changed. + const gsi_char * key, // The key. + const gsi_char * value, // The value. + void * param // User-data. +); + +// This is called for watch keys when a room is joined, for +// watch keys when another player joins, for any newly +// set watch keys, and when a broadcast key is changed. +/////////////////////////////////////////////////////////// +typedef void (* peerRoomKeyChangedCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The room the player is in. + const gsi_char * nick, // The player whose key changed. + const gsi_char * key, // The key. + const gsi_char * value, // The value. + void * param // User-data. +); + +// Called to report QR server keys. +// Use qr2_buffer_add or qr2_buffer_add_int to +// add this key's information to the buffer. +////////////////////////////////////////////// +typedef void (* peerQRServerKeyCallback) +( + PEER peer, // The peer object. + int key, // The key for which to report information. + qr2_buffer_t buffer, // Fill in the information using this buffer. + void * param // User-data. +); + +// Called to report QR player keys. +// Use qr2_buffer_add or qr2_buffer_add_int to +// add this key's information to the buffer. +////////////////////////////////////////////// +typedef void (* peerQRPlayerKeyCallback) +( + PEER peer, // The peer object. + int key, // The key for which to report information. + int index, // The index of the player for which to report info. + qr2_buffer_t buffer, // Fill in the information using this buffer. + void * param // User-data. +); + +// Called to report QR team keys. +// Use qr2_buffer_add or qr2_buffer_add_int to +// add this key's information to the buffer. +////////////////////////////////////////////// +typedef void (* peerQRTeamKeyCallback) +( + PEER peer, // The peer object. + int key, // The key for which to report information. + int index, // The index of the team for which to report info. + qr2_buffer_t buffer, // Fill in the information using this buffer. + void * param // User-data. +); + +// Called to get a list of keys to be reported. +// Use qr2_keybuffer_add() to add keys. +// The following keys do not need to be added, as peer already adds them: +// GAMENAME_KEY, HOSTNAME_KEY, NUMPLAYERS_KEY, MAXPLAYERS_KEY, +// GAMEMODE_KEY, PASSWORD_KEY, GROUPID_KEY, PLAYER__KEY, PING__KEY. +///////////////////////////////////////////////////////////////////////// +typedef void (* peerQRKeyListCallback) +( + PEER peer, // The peer object. + qr2_key_type type, // The type of keys being asked for (key_server, key_player, or key_team). + qr2_keybuffer_t keyBuffer, // Fill in the keys using this buffer. + void * param // User-data. +); + +// Called to get a count of the number of players or teams. +/////////////////////////////////////////////////////////// +typedef int (* peerQRCountCallback) +( + PEER peer, // The peer object. + qr2_key_type type, // The type of count to return (key_player or key_team). + void * param // User-data. +); + +// Called when there is an error reporting the server. +////////////////////////////////////////////////////// +typedef void (* peerQRAddErrorCallback) +( + PEER peer, // The peer object. + qr2_error_t error, // The type of error. + gsi_char * errorString, // A text string containing the error. + void * param // User-data. +); + +// Called when hosting a server and a nat-negotiate cookie is received. +/////////////////////////////////////////////////////////////////////// +typedef void (* peerQRNatNegotiateCallback) +( + PEER peer, // The peer object. + int cookie, // A cookie sent from a potential client. + void * param // User-data. +); + +// Called when hosting a server with the server's public reporting address. +/////////////////////////////////////////////////////////////////////////// +typedef void (* peerQRPublicAddressCallback) +( + PEER peer, // The peer object. + unsigned int ip, // The public reporting IP + unsigned short port, // The public reporting port + void * param // User-data. +); + +// This struct gets passed into peerInitialize(). +// param will be passed as the last parameter to each of the callbacks. +/////////////////////////////////////////////////////////////////////// +typedef struct PEERCallbacks +{ + peerDisconnectedCallback disconnected; + peerRoomMessageCallback roomMessage; + peerRoomUTMCallback roomUTM; + peerRoomNameChangedCallback roomNameChanged; // PANTS|09.11.00 + peerRoomModeChangedCallback roomModeChanged; // PANTS|04.17.01 + peerPlayerMessageCallback playerMessage; + peerPlayerUTMCallback playerUTM; + peerReadyChangedCallback readyChanged; + peerGameStartedCallback gameStarted; + peerPlayerJoinedCallback playerJoined; + peerPlayerLeftCallback playerLeft; + peerKickedCallback kicked; + peerNewPlayerListCallback newPlayerList; + peerPlayerChangedNickCallback playerChangedNick; + peerPlayerInfoCallback playerInfo; // PANTS|01.08.01 + peerPlayerFlagsChangedCallback playerFlagsChanged; // PANTS|03.12.01 + peerPingCallback ping; + peerCrossPingCallback crossPing; + peerGlobalKeyChangedCallback globalKeyChanged; + peerRoomKeyChangedCallback roomKeyChanged; + peerQRServerKeyCallback qrServerKey; + peerQRPlayerKeyCallback qrPlayerKey; + peerQRTeamKeyCallback qrTeamKey; + peerQRKeyListCallback qrKeyList; + peerQRCountCallback qrCount; + peerQRAddErrorCallback qrAddError; + peerQRNatNegotiateCallback qrNatNegotiateCallback; + peerQRPublicAddressCallback qrPublicAddressCallback; + void * param; +} PEERCallbacks; + +/************ +** UNICODE ** +************/ +#ifndef GSI_UNICODE +#define peerConnect peerConnectA +#define peerConnectLogin peerConnectLoginA +#define peerConnectPreAuth peerConnectPreAuthA +#define peerRetryWithNick peerRetryWithNickA +#define peerRegisterUniqueNick peerRegisterUniqueNickA +#define peerSetTitle peerSetTitleA +#define peerGetTitle peerGetTitleA +#define peerGetNick peerGetNickA +#define peerFixNick peerFixNickA +#define peerTranslateNick peerTranslateNickA +#define peerChangeNick peerChangeNickA +#define peerSetAwayMode peerSetAwayModeA +#define peerParseQuery peerParseQueryA +#define peerAuthenticateCDKey peerAuthenticateCDKeyA +#define peerJoinTitleRoom peerJoinTitleRoomA +#define peerJoinStagingRoom peerJoinStagingRoomA +#define peerJoinStagingRoomByChannel peerJoinStagingRoomByChannelA +#define peerCreateStagingRoom peerCreateStagingRoomA +#define peerCreateStagingRoomWithSocket peerCreateStagingRoomWithSocketA +#define peerLeaveRoom peerLeaveRoomA +#define peerListGroupRooms peerListGroupRoomsA +#define peerStartListingGames peerStartListingGamesA +#define peerMessageRoom peerMessageRoomA +#define peerUTMRoom peerUTMRoomA +#define peerSetPassword peerSetPasswordA +#define peerSetRoomName peerSetRoomNameA +#define peerGetRoomName peerGetRoomNameA +#define peerGetRoomChannel peerGetRoomChannelA +#define peerSetTitleRoomChannel peerSetTitleRoomChannelA +#define peerMessagePlayer peerMessagePlayerA +#define peerUTMPlayer peerUTMPlayerA +#define peerKickPlayer peerKickPlayerA +#define peerGetPlayerPing peerGetPlayerPingA +#define peerGetPlayersCrossPing peerGetPlayersCrossPingA +#define peerPingPlayer peerPingPlayerA +#define peerGetPlayerInfoNoWait peerGetPlayerInfoNoWaitA +#define peerGetPlayerInfo peerGetPlayerInfoA +#define peerGetPlayerProfileID peerGetPlayerProfileIDA +#define peerGetPlayerIP peerGetPlayerIPA +#define peerIsPlayerHost peerIsPlayerHostA +#define peerGetPlayerFlags peerGetPlayerFlagsA +#define peerGetReady peerGetReadyA +#define peerStartGame peerStartGameA +#define peerSetGlobalKeys peerSetGlobalKeysA +#define peerGetPlayerGlobalKeys peerGetPlayerGlobalKeysA +#define peerGetRoomGlobalKeys peerGetRoomGlobalKeysA +#define peerSetRoomKeys peerSetRoomKeysA +#define peerGetRoomKeys peerGetRoomKeysA +#define peerSetGlobalWatchKeys peerSetGlobalWatchKeysA +#define peerSetRoomWatchKeys peerSetRoomWatchKeysA +#define peerGetGlobalWatchKey peerGetGlobalWatchKeyA +#define peerGetRoomWatchKey peerGetRoomWatchKeyA +#define peerStartAutoMatch peerStartAutoMatchA +#define peerStartAutoMatchWithSocket peerStartAutoMatchWithSocketA +#else +#define peerConnect peerConnectW +#define peerConnectLogin peerConnectLoginW +#define peerConnectPreAuth peerConnectPreAuthW +#define peerRetryWithNick peerRetryWithNickW +#define peerRegisterUniqueNick peerRegisterUniqueNickW +#define peerSetTitle peerSetTitleW +#define peerGetTitle peerGetTitleW +#define peerGetNick peerGetNickW +#define peerFixNick peerFixNickW +#define peerTranslateNick peerTranslateNickW +#define peerChangeNick peerChangeNickW +#define peerSetAwayMode peerSetAwayModeW +#define peerParseQuery peerParseQueryA +#define peerAuthenticateCDKey peerAuthenticateCDKeyW +#define peerJoinTitleRoom peerJoinTitleRoomW +#define peerJoinStagingRoom peerJoinStagingRoomW +#define peerJoinStagingRoomByChannel peerJoinStagingRoomByChannelW +#define peerCreateStagingRoom peerCreateStagingRoomW +#define peerCreateStagingRoomWithSocket peerCreateStagingRoomWithSocketW +#define peerLeaveRoom peerLeaveRoomW +#define peerListGroupRooms peerListGroupRoomsW +#define peerStartListingGames peerStartListingGamesW +#define peerMessageRoom peerMessageRoomW +#define peerUTMRoom peerUTMRoomW +#define peerSetPassword peerSetPasswordW +#define peerSetRoomName peerSetRoomNameW +#define peerGetRoomName peerGetRoomNameW +#define peerGetRoomChannel peerGetRoomChannelW +#define peerSetTitleRoomChannel peerSetTitleRoomChannelW +#define peerMessagePlayer peerMessagePlayerW +#define peerUTMPlayer peerUTMPlayerW +#define peerKickPlayer peerKickPlayerW +#define peerGetPlayerPing peerGetPlayerPingW +#define peerGetPlayersCrossPing peerGetPlayersCrossPingW +#define peerPingPlayer peerPingPlayerW +#define peerGetPlayerInfoNoWait peerGetPlayerInfoNoWaitW +#define peerGetPlayerInfo peerGetPlayerInfoW +#define peerGetPlayerProfileID peerGetPlayerProfileIDW +#define peerGetPlayerIP peerGetPlayerIPW +#define peerIsPlayerHost peerIsPlayerHostW +#define peerGetPlayerFlags peerGetPlayerFlagsW +#define peerGetReady peerGetReadyW +#define peerStartGame peerStartGameW +#define peerSetGlobalKeys peerSetGlobalKeysW +#define peerGetPlayerGlobalKeys peerGetPlayerGlobalKeysW +#define peerGetRoomGlobalKeys peerGetRoomGlobalKeysW +#define peerSetRoomKeys peerSetRoomKeysW +#define peerGetRoomKeys peerGetRoomKeysW +#define peerSetGlobalWatchKeys peerSetGlobalWatchKeysW +#define peerSetRoomWatchKeys peerSetRoomWatchKeysW +#define peerGetGlobalWatchKey peerGetGlobalWatchKeyW +#define peerGetRoomWatchKey peerGetRoomWatchKeyW +#define peerStartAutoMatch peerStartAutoMatchW +#define peerStartAutoMatchWithSocket peerStartAutoMatchWithSocketW +#endif + +/************ +** GENERAL ** +************/ +// This creates and intializes a peer object. +// NULL is returned in case of an error, otherwise a peer +// object is returned. +// If a peer object is returned, peerShutdown() must be called +// to cleanup the object +/////////////////////////////////////////////////////////////// +PEER peerInitialize +( + PEERCallbacks * callbacks // Global callbacks. +); + +// This gets called when the connection attempt finishes. +///////////////////////////////////////////////////////// +typedef void (* peerConnectCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + int failureReason, // If failure, the reason for it (PEER_DISCONNECTED, etc.) + void * param // User-data. +); + +// This gets called if there is an error with +// the nickname while connecting. +// Call peerRetryWithNick() to try another nick. If it +// is called with a NULL nick, then the connect will be +// stopped, and peerConnectCallback will be called with +// failure. +// The suggested nicks are only provided if type +// is PEER_INVALID_UNIQUENICK. +///////////////////////////////////////////////////////// +typedef void (* peerNickErrorCallback) +( + PEER peer, // The peer object. + int type, // The type of nick error (PEER_IN_USE, PEER_INVALID, etc.) + const gsi_char * nick, // The bad nick. + int numSuggestedNicks, // The number of suggested nicks. + const gsi_char ** suggestedNicks, // The array of nicks. + void * param // User-data. +); + +// This connects a peer object to a chat server. +// Call peerDisconnect() to close the connection. +// A title must be set with peerSetTitle before connecting. +/////////////////////////////////////////////////////////// +void peerConnect +( + PEER peer, // The peer object. + const gsi_char * nick, // The nick to connect with. + int profileID, // The profileID, or 0 if no profileID. + peerNickErrorCallback nickErrorCallback, // Called if nick error. + peerConnectCallback connectCallback, // Called on complete. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Same as peerConnect, but also authenticates using a uniquenick +// and password or an email, profilenick, and password. +///////////////////////////////////////////////////////////////// +void peerConnectLogin +( + PEER peer, // The peer object. + int namespaceID, // The namespace in which to login. + const gsi_char * email, // The email to login with. + const gsi_char * profilenick, // The profile to login with. + const gsi_char * uniquenick, // The uniquenick to login with. + const gsi_char * password, // The password for the login account. + peerNickErrorCallback nickErrorCallback, // Called if nick error. + peerConnectCallback connectCallback, // Called on complete. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Same as peerConnect, but also authenticates using an authtoken +// and partnerchallenge from a partner account system. +///////////////////////////////////////////////////////////////// +void peerConnectPreAuth +( + PEER peer, // The peer object. + const gsi_char * authtoken, // The authtoken for this login. + const gsi_char * partnerchallenge, // The partner challenge for this login. + peerNickErrorCallback nickErrorCallback, // Called if nick error. + peerConnectCallback connectCallback, // Called on complete. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// If peerNickErrorCallback is called, call this to +// try and continue the connection with a new nickname. +// If there is an error with this nick, the +// peerNickErrCallback will be called again. +/////////////////////////////////////////////////////// +void peerRetryWithNick +( + PEER peer, + const gsi_char * nick +); + +// Register a uniquenick. +// Should be called in response to the peerNickErrorCallback being called +// with a type of PEER_UNIQUENICK_EXPIRED or PEER_NO_UNIQUENICK. +// If the uniquenick cannot be registered, the peerNickErrorCallback will +// be called again with a type of PEER_IN_USE or PEER_INVALID. +///////////////////////////////////////////////////////////////////////// +void peerRegisterUniqueNick +( + PEER peer, + int namespaceID, + const gsi_char * uniquenick, + const gsi_char * cdkey +); + +// Returns true if peer is connected. +// PANTS|09.11.00 +///////////////////////////////////// +PEERBool peerIsConnected(PEER peer); + +// Sets the current title. +// A title must be set before connecting. +// Returns PEERFalse if an error, or PEERTrue if success. +// For most games the title/qrSecretKey and +// sbName/sbSecretKey pairs will be the same. +///////////////////////////////////////////////////////// +PEERBool peerSetTitle +( + PEER peer, // The peer object. + const gsi_char * title, // The title to make current (ie., ut, gmtest). + const gsi_char * qrSecretKey, // The queryreporting secret key. + const gsi_char * sbName, // The serverbrowsing name. + const gsi_char * sbSecretKey, // The serverbrowsing secret key. + int sbGameVersion, // The version of the game doing the browsing. + int sbMaxUpdates, // The maximum number of concurent updates (10-15 for modem users, 20-30 for high-bandwidth). + PEERBool natNegotiate, // PEERTrue if the title supports GameSpy's NAT-negotiation technology (or another similar technology). + PEERBool pingRooms[NumRooms], // To do pings int a room, set it to PEERTrue. + PEERBool crossPingRooms[NumRooms] // To do cross-pings in a room, set it to PEERTrue. +); + +// Resets peer to no title. +/////////////////////////// +void peerClearTitle(PEER peer); + +// Gets the currently set title. +// Returns NULL if no title is set. +/////////////////////////////////// +const gsi_char * peerGetTitle(PEER peer); + +// Disconnect peer from the chat server. +//////////////////////////////////////// +void peerDisconnect(PEER peer); + +// Shutdown peer. +// The peer object should not be used again after this call. +//////////////////////////////////////////////////////////// +void peerShutdown(PEER peer); + +// Let's peer think. +// This should be called at least every ~10ms, +// typically, within the program's main loop. +////////////////////////////////////////////// +void peerThink(PEER peer); + +// Get the chat object associated with this peer object. +// This returns NULL if peer isn't connected. +//////////////////////////////////////////////////////// +CHAT peerGetChat(PEER peer); + +// Get the local user's nickname. +///////////////////////////////// +const gsi_char * peerGetNick(PEER peer); + +// Replaces any invalid characters in nick with underscores. +//////////////////////////////////////////////////////////// +void peerFixNick +( + gsi_char * newNick, + const gsi_char * oldNick +); + +// Removes the namespace extension from a chat unique nick. +// Returns the nick if it ended with the extension. +// Otherwise returns NULL. +/////////////////////////////////////////////////////////// +const gsi_char * peerTranslateNick +( + gsi_char * nick, // The nick to translate + const gsi_char * extension // The extension to be removed. +); + +// Gets the local IP address. If called before +// the peer object is connected, will return 0. +// DEPRECATED - Use peerGetPublicIP. +/////////////////////////////////////////////// +#define peerGetLocalIP peerGetPublicIP + +// Gets the local public IP address. If called before +// the peer object is connected, will return 0. +////////////////////////////////////////////////////// +unsigned int peerGetPublicIP(PEER peer); + +// Gets the local private IP address. +///////////////////////////////////// +unsigned int peerGetPrivateIP(PEER peer); + +// Gets the local userID. +// Only valid if connected with peerConnectLogin or peerConnectPreAuth. +/////////////////////////////////////////////////////////////////////// +int peerGetUserID(PEER peer); + +// Gets the local profileID. +// Only valid if connected with peerConnectLogin or peerConnectPreAuth. +/////////////////////////////////////////////////////////////////////// +int peerGetProfileID(PEER peer); + +// This gets called when an attempt to change nicks is finished. +//////////////////////////////////////////////////////////////// +typedef void (* peerChangeNickCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + const gsi_char * oldNick, // The old nickname. + const gsi_char * newNick, // The new nickname. + void * param // User-data. +); + +// Changes the user's nickname. +/////////////////////////////// +void peerChangeNick +( + PEER peer, // The peer object. + const gsi_char * newNick, // The nickname to which to change. + peerChangeNickCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Causes Peer to not automatically leave the +// room the next time peerSetTitle or peerClearTitle +// is called. No effect if a title isn't set. When +// the next title is set, the flag is cleared. +// Only TitleRoom is currently supported. +// This function is normally not needed. +//////////////////////////////////////////////////// +void peerStayInRoom +( + PEER peer, // The peer object. + RoomType roomType // Only TitleRoom is currently supproted. +); + +// Turns quiet mode on/off. +/////////////////////////// +void peerSetQuietMode +( + PEER peer, // The peer object. + PEERBool quiet // If PEERTrue, enable quiet mode. +); + +// Sets the away mode. +// If an empty string or NULL, away mode is off. +// If a valid string, away mode is on. +//////////////////////////////////////////////// +void peerSetAwayMode +( + PEER peer, // The peer object. + const gsi_char * reason // The away reason. If NULL or "", not away. +); + +// When using peerStartReportingWithSocket or peerCreateStagingRoomWithSocket, +// any qureries received on the sockets need to be passed to the SDK. Pass +// the data using this function. +////////////////////////////////////////////////////////////////////////////// +void peerParseQuery +( + PEER peer, // The peer object. + char * query, // String of query data. + int len, // The length of the string, not including the NUL. + struct sockaddr * sender // The address the query was received from. +); + +// This gets called when an attempt to authenticate a CD key is finished. +// If the result is 1, the CD key was authenticated. Otherwise, the CD +// key was not authenticated. +///////////////////////////////////////////////////////////////////////// +typedef void (* peerAuthenticateCDKeyCallback) +( + PEER peer, // The peer object. + int result, // 1 if authenticated, otherwise not authenticated. + const gsi_char * message, // A string representing the result. + void * param // User-data. +); + +// Sends a CD Key to the chat server for authentication. +// The callback gets called when the chat server responds. +// This call is required by some games to enter chat rooms. +// It must be called after connecting to the chat server, but +// before entering a room. +///////////////////////////////////////////////////////////// +void peerAuthenticateCDKey +( + PEER peer, // The peer object. + const gsi_char * cdkey, // The CD key to authenticate. + peerAuthenticateCDKeyCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Sends a nat-negotiate cookie to a server. +//////////////////////////////////////////// +void peerSendNatNegotiateCookie +( + PEER peer, // The peer object. + unsigned int ip, // IP (network byte order) of the server to send the cookie to. + unsigned short port, // Port (host byte order) of the server to send the cookie to. + int cookie // The cookie to send. +); + +// Sends a client message to a QR2 server. +////////////////////////////////////////// +void peerSendMessageToServer +( + PEER peer, + unsigned int ip, + unsigned short port, + const char * data, + int len +); + +// If always is PEERTrue, then players' IP and profile ID will +// always be requested when joining a room. This info is normally +// only requested if pings are set in a room. Use this function +// to always request the information. +////////////////////////////////////////////////////////////////// +void peerAlwaysGetPlayerInfo +( + PEER peer, // The peer object. + PEERBool always // If true, always get player info. +); + +/********** +** ROOMS ** +**********/ +// This gets called when an attempt to join or create a room has finished. +////////////////////////////////////////////////////////////////////////// +typedef void (* peerJoinRoomCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + PEERJoinResult result, // The result of the attempt. + RoomType roomType, // The type of room joined/created. + void * param // User-data. +); + +// Joins the currently selected title's title room. +/////////////////////////////////////////////////// +void peerJoinTitleRoom +( + PEER peer, // The peer object. + const gsi_char password[PEER_PASSWORD_LEN], // An optional password, normally NULL. + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Joins a group room. +// The groupID comes from the peerListGroupRoomsCallback. +///////////////////////////////////////////////////////// +void peerJoinGroupRoom +( + PEER peer, // The peer object. + int groupID, // The ID for the group to join. + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Sets the group ID. +// Used to get a list of games in a group room and/or report or create a game +// in a particular group, without actually being in that group room. This is +// not needed if already using peerJoinGroupRoom. +// Setting a group ID of 0 is equivalent to leaving the group room. +///////////////////////////////////////////////////////////////////////////// +void peerSetGroupID +( + PEER peer, // The peer object. + int groupID // The group ID to set +); + +// Returns the current group ID. +// The group ID is set with either peerJoinGroupRoom or peerSetGroupID. +/////////////////////////////////////////////////////////////////////// +int peerGetGroupID +( + PEER peer // The peer object. +); + +// Joins a staging room. +// server is one of the server objects passed to peerListingGamesCallback(). +// This call will only work if staging==PEERTrue for the server. +// PANTS|09.11.00 - The password is only needed for passworded rooms. +// PANTS|03.15.01 - No longer requires you to be actively listing games. +//////////////////////////////////////////////////////////////////////////// +void peerJoinStagingRoom +( + PEER peer, // The peer object. + SBServer server, // The server passed into peerlistingGamesCallback(). + const gsi_char password[PEER_PASSWORD_LEN], // The password of the room being joined. Can be NULL or "". + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Joins a staging room by its channel name. +// Same as peerJoinStagingRoom, except it takes the staging room's +// channel name instead of an SBServer object. Also, when the +// peerGameStartedCallback is called, the server paramter passed to +// it will be NULL. +/////////////////////////////////////////////////////////////////// +void peerJoinStagingRoomByChannel +( + PEER peer, // The peer object. + const gsi_char * channel, // The channel to join. + const gsi_char password[PEER_PASSWORD_LEN], // The password of the room being joined. Can be NULL or "". + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Creates a new staging room, with the local player hosting. +// PANTS|09.11.00 - If the password parameter is not NULL +// or "", this will create a passworded room. The same +// case-sensitive password needs to be passed into +// peerJoinStagingRoom() for other player's to join the room. +// PANTS|09.11.00 - The staging room will be reported as part +// of whatever group room the local player was in when the +// room was created. Leaving the group room will not affect +// what group the staging room is reported as part of. +///////////////////////////////////////////////////////////// +void peerCreateStagingRoom +( + PEER peer, // The peer object. + const gsi_char * name, // The name of the room. + int maxPlayers, // The max number of players allowed in the room. + const gsi_char password[PEER_PASSWORD_LEN], // An optional password for the staging room + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Same as peerCreateStagingRoom, but uses the provided socket for +// sending heartbeats and query replies. This allows the game +// to share a socket with the peer SDK, which can make hosting +// games behind a NAT proxy possible. +////////////////////////////////////////////////////////////////// +void peerCreateStagingRoomWithSocket +( + PEER peer, // The peer object. + const gsi_char * name, // The name of the room. + int maxPlayers, // The max number of players allowed in the room. + const gsi_char password[PEER_PASSWORD_LEN], // An optional password for the staging room + SOCKET socket, // The socket to be used for reporting. + unsigned short port, // The local port to which the socket is bound. + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Leave a room. +// PANTS|09.11.00 - You can now leave a group room +// without being forcibly removed from a staging room. +////////////////////////////////////////////////////// +void peerLeaveRoom +( + PEER peer, // The peer object. + RoomType roomType, // The room you want to leave (TitleRoom, GroupRoom, or StagingRoom). + const gsi_char * reason // The reason the player is leaving (can be NULL). PANTS|03.13.01 +); + +// Gets called once for each group room when listing group rooms. +// After this has been called for each group room, it will be +// called one more time with groupID==0 and name==NULL. +///////////////////////////////////////////////////////////////// +typedef void (* peerListGroupRoomsCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + int groupID, // A unique ID for this group. + SBServer server, // The server object for this group room. + const gsi_char * name, // The group room's name. + int numWaiting, // The number of players in the room. + int maxWaiting, // The maximum number of players allowed in the room. + int numGames, // The number of games either staging or running in the group. + int numPlaying, // The total number of players in games in the group. + void * param // User-data. +); + +// List all the groups rooms for the currently set title. +// The fields parameter allows you to request extra info +// on each group room. +///////////////////////////////////////////////////////// +void peerListGroupRooms +( + PEER peer, // The peer object. + const gsi_char * fields, // A backslash delimited list of fields. + peerListGroupRoomsCallback callback, // Called for each group room. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Called with info on games being listed. +// Used to maintain a list of running games and staging rooms. +// The server object is a unique way of identifying each game. +// It can also be used with the calls in the "SBServer Object Functions" section +// of sb_serverbrowsing.h to find out more info about the server. +// If staging==PEERTrue, the game hasn't started yet, it's still in the staging room +// use peerJoinStagingRoom() to join the staging room, or if staging==peerfalse +// use the server object to get the game's IP and port to join with. +// During the _intial_ listing of games, progress is the percentage (0-100) of the +// games that have been added. Once the initial listing is completed, +// progress will always be 100. +// PANTS|09.11.00 - The "password" key will be set to 1 for games that are +// passworded. This can be checked with ServerGetIntValue(server, "password", 0). +///////////////////////////////////////////////////////////////////////////////////// +typedef void (* peerListingGamesCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + const gsi_char * name, // The name of the game being listed. + SBServer server, // The server object for this game. + PEERBool staging, // If PEERTrue, this is a staging room and not a running game. + int msg, // The type of message this is. + // PEER_CLEAR: + // Clear the list. This has the same effect as if this was called + // with PEER_REMOVE for every server listed. + // PEER_ADD: + // This is a new server. Add it to the list. + // PEER_UPDATE: + // This server is already on the list, and its been updated. + // PEER_REMOVE: + // Remove this server from the list. The server object for this server + // should not be used again after this callback returns. + int progress, // The percent of servers that have been added. + void * param // User-data. +); + +// Start listing the currently running games and staging rooms. +// This is used to maintain a list that can presented to the user, +// so they can pick a game (or staging room) to join. +// Games and staging rooms are filtered based on what group the local +// user is in. If the local user isn't in a group, then only games +// and staging rooms that aren't part of any group are listed. +// The fields determine which info to request from the server. These +// must be registered QR2 keys (see qr2regkeys.h). HOSTNAME_KEY and +// GAMEMODE_KEY are automatically requested by Peer, so do not need +// to included. +///////////////////////////////////////////////////////////////////// +void peerStartListingGames +( + PEER peer, // The peer object. + const unsigned char * fields, // An array of registered QR2 keys to request from servers. + int numFields, // The number of keys in the fields array. + const gsi_char * filter, // A SQL-like rule filter. + peerListingGamesCallback callback, // Called when finished. + void * param // Passed to the callback. +); + +// Stop games from being listed. This does NOT clear the games list. +// So all games listed are still considered valid. They stay valid +// until either another call to peerStartListingGames or until the +// title is cleared (or a new one set). +//////////////////////////////////////////////////////////////////// +void peerStopListingGames +( + PEER peer // The peer object. +); + +// Queries the game server for the latest information. +// If fullUpdate is PEERTrue, all of the keys will be requested. +// If fullUpdate is PEERFalse, only the keys passed to +// peerStartListingGames as the fields parameter will be requested. +/////////////////////////////////////////////////////////////////// +void peerUpdateGame +( + PEER peer, // The peer object. + SBServer server, // The server object for the game to update. + PEERBool fullUpdate // If true, get all of the game's keys. +); + +// 08-26-2004 Added by Saad Nader +// Queries the game server for the latest information via the master +// server. +// If fullUpdate is PEERTrue, all of the keys will be requested. +// If fullUpdate is PEERFalse, only the keys passed to +// peerStartListingGames as the fields parameter will be requested. +/////////////////////////////////////////////////////////////////// +void peerUpdateGameByMaster +( + PEER peer, // The peer object. + SBServer server, // The server object for the game to update. + PEERBool fullUpdate // If true, get all of the game's keys. +); + +// Send a ping to the server +// ICMP echo reply will be sent as response. The firewall +// or NAT between server and internet does not have to accept +// ICMP echo requests. +/////////////////////////////////////////////////////////////////// +void peerUpdateGamePing(PEER peer, SBServer server); + +// Send a message to a room. +//////////////////////////// +void peerMessageRoom +( + PEER peer, // The peer object. + RoomType roomType, // The room to send the message to. + const gsi_char * message, // The message. + MessageType messageType // The type of message. +); + +// Send a UTM to a room. +//////////////////////// +void peerUTMRoom +( + PEER peer, // The peer object. + RoomType roomType, // The room to send the UTM to. + const gsi_char * command, // The command. + const gsi_char * parameters, // The UTM's parameters. + PEERBool authenticate // If true, the server will authenticate this UTM (should normally be false). +); + +// Set a password in a room you're hosting. +// The only roomType currently supported is StagingRoom. +// This will only work if the player is hosting the room. +// If password is NULL or "", the password will be cleared. +/////////////////////////////////////////////////////////// +void peerSetPassword +( + PEER peer, // The peer object. + RoomType roomType, // The room in which to set the password. + const gsi_char password[PEER_PASSWORD_LEN] // The password to set. +); + +// Set the name of a room you're hosting. +// The only roomType currently supported is StagingRoom. +// PANTS|09.11.00 +//////////////////////////////////////////////////////// +void peerSetRoomName +( + PEER peer, // The peer object. + RoomType roomType, // The room in which to set the name. + const gsi_char * name // The new name +); + +// Get a room's name (the channel's title). +// Returns NULL if not in the room. +/////////////////////////////////////////// +const gsi_char * peerGetRoomName +( + PEER peer, // The peer object. + RoomType roomType // The room to get the name for. +); + +// Get's the chat channel associated with the room. +// Returns NULL if not in the room. +/////////////////////////////////////////////////// +const gsi_char * peerGetRoomChannel +( + PEER peer, // The peer object. + RoomType roomType // The room to get the channel for. +); + +// Returns PEERTrue if in the room. +/////////////////////////////////// +PEERBool peerInRoom +( + PEER peer, // The peer object. + RoomType roomType // The room to check for. +); + +// Use this channel for the title room for the currently set title. +// This function is normally not needed. +/////////////////////////////////////////////////////////////////// +void peerSetTitleRoomChannel +( + PEER peer, // The peer object. + const gsi_char * channel // The channel to use for the title room. +); + +// Use this to get the SBServer object for the staging room host. +// This will return NULL if the local player is not in a staging room, +// is the staging room host, or joined using peerJoinStagingRoomByChannel. +////////////////////////////////////////////////////////////////////////// +SBServer peerGetHostServer(PEER peer); + +// Update the maximum number of players for a staging room +///////////////////////////////////////////////////////// +void peerSetStagingRoomMaxPlayers +( + PEER peer, + int maxPlayers +); + +/************ +** PLAYERS ** +************/ +// Called for each player in a room being enumerated, and once +// when finished, with index==-1 and nick==NULL. +////////////////////////////////////////////////////////////// +typedef void (* peerEnumPlayersCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + RoomType roomType, // The room whose players are being enumerated. + int index, // The index of the player (0 to (N - 1)). -1 when finished. + const gsi_char * nick, // The nick of the player. + int flags, // This player's flags (see #define's above). PANTS|03.12.01 + void * param // User-data. +); + +// Enumerates through the players in a room. +//////////////////////////////////////////// +void peerEnumPlayers +( + PEER peer, // The peer object. + RoomType roomType, // The room to enum the players in. + peerEnumPlayersCallback callback, // Called when finished. + void * param // Passed to callback. +); + +// Send a message to a player. +////////////////////////////// +void peerMessagePlayer +( + PEER peer, // The peer object. + const gsi_char * nick, // The nick of the player to send the message to. + const gsi_char * message, // The message to send. + MessageType messageType // The type of message. +); + +// Send a UTM to a player. +////////////////////////// +void peerUTMPlayer +( + PEER peer, // The peer object. + const gsi_char * nick, // The nick of the player to send the UTM to. + const gsi_char * command, // The command. + const gsi_char * parameters, // The UTM's parameters. + PEERBool authenticate // If true, the server will authenticate this UTM (should normally be false). +); + +// Kick a player from a room. +// Can only be called by a player with host privileges. +/////////////////////////////////////////////////////// +void peerKickPlayer +( + PEER peer, // The peer object. + RoomType roomType, // The room to kick the player from. + const gsi_char * nick, // The nick of the player to kick. + const gsi_char * reason // An optional reason for kicking the player +); + +// Gets a player's ping (between the local machine and the player's machine). +// Returns PEERFalse if we don't have or can't get the player's ping. +///////////////////////////////////////////////////////////////////////////// +PEERBool peerGetPlayerPing +( + PEER peer, // The peer object. + const gsi_char * nick, // The player to get the ping for. + int * ping // The player's ping is stored here, if we have it. +); + +// Gets the cross-ping between 2 players. +// Returns PEERFalse if we don't have or can't get the player's cross-ping. +/////////////////////////////////////////////////////////////////////////// +PEERBool peerGetPlayersCrossPing +( + PEER peer, // The peer object. + const gsi_char * nick1, // The first player. + const gsi_char * nick2, // The second player. + int * crossPing // The cross-ping is stored here, if we have it. +); + +// Peer already automatically pings all players that are in ping rooms. +// This function does a one-time ping of a remote player in a non-ping room. +// However pings must be enabled in at least one room for this to work, +// otherwise Peer will not open the UDP ping socket. +// Also, peerAlwaysGetPlayerInfo() must be enabled so that Peer has IPs for +// players that are only in non-ping rooms. +//////////////////////////////////////////////////////////////////////////// +PEERBool peerPingPlayer +( + PEER peer, // The peer object. + const gsi_char * nick // The player to ping. +); + +// Gets a player's info immediately. +// Returns PEERFalse if the info is no available. +///////////////////////////////////////////////// +PEERBool peerGetPlayerInfoNoWait +( + PEER peer, + const gsi_char * nick, + unsigned int * IP, + int * profileID +); + +// Called as a result of a call to peerGetPlayerInfo(). +/////////////////////////////////////////////////////// +typedef void (* peerGetPlayerInfoCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + const gsi_char * nick, // The player's nick. + unsigned int IP, // The player's IP, in network byte order. + int profileID, // The player's profile ID. + void * param // User-data. +); + +// Called to get a player's IP and profile ID. +////////////////////////////////////////////// +void peerGetPlayerInfo +( + PEER peer, // The peer object. + const gsi_char * nick, // The player's nick. + peerGetPlayerInfoCallback callback, // Called when finished. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Called as a result of a call to peerGetPlayerProfileID(). +//////////////////////////////////////////////////////////// +typedef void (* peerGetPlayerProfileIDCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + const gsi_char * nick, // The player's nick. + int profileID, // The player's profile ID. + void * param // User-data. +); + +// Called to get a player's profile ID. +// DEPRECATED - Use peerGetPlayerInfo. +/////////////////////////////////////// +void peerGetPlayerProfileID +( + PEER peer, // The peer object. + const gsi_char * nick, // The player's nick. + peerGetPlayerProfileIDCallback callback, // Called when finished. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Called as a result of a call to peerGetPlayerIP(). +///////////////////////////////////////////////////// +typedef void (* peerGetPlayerIPCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + const gsi_char * nick, // The player's nick. + unsigned int IP, // The player's IP, in network byte order. PANTS|09.11.00 - was unsigned long + void * param // User-data. +); + +// Called to get a player's IP. +// DEPRECATED - Use peerGetPlayerInfo. +////////////////////////////////////// +void peerGetPlayerIP +( + PEER peer, // The peer object. + const gsi_char * nick, // The player's nick. + peerGetPlayerIPCallback callback, // Called when finished. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Checks if a player is a host (has ops). +// Returns PEERTrue if yes, PEERFalse if no. +//////////////////////////////////////////// +PEERBool peerIsPlayerHost +( + PEER peer, // The peer object. + const gsi_char * nick, // The player's nick. + RoomType roomType // The room to check in. +); + +// Gets a player's flags in a room. Returns PEERFalse if +// the local player or this player is not in the room. +///////////////////////////////////////////////////////// +PEERBool peerGetPlayerFlags +( + PEER peer, + const gsi_char * nick, + RoomType roomType, + int * flags +); + +/********* +** GAME ** +*********/ +/**************** Disabled *************************/ +// Returns the qr2 record used to report +// Primarily used for CD Key integration to prevent +// game intrusion and and post staging hacks +//////////////////////////////////////////////////// +//qr2_t peerGetReportingRecord(PEER peer); + +// Sets the local player's ready state. +// PEERTrue if ready, PEERFalse if not ready. +///////////////////////////////////////////// +void peerSetReady +( + PEER peer, // The peer object. + PEERBool ready // Ready or not. +); + +// Gets a player's ready state. +/////////////////////////////// +PEERBool peerGetReady +( + PEER peer, // The peer object. + const gsi_char * nick, // The player's nick. + PEERBool * ready // The player's ready state gets stored in here. +); + +// Checks if all the player's in the staging room are ready. +//////////////////////////////////////////////////////////// +PEERBool peerAreAllReady +( + PEER peer // The peer object. +); + +// Called only by a staging room host to start the game. +// All the other people in the staging room will have their +// peerGameStartedCallback() called. +// The message gets passed to everyone in the peerGameStartedCallback(), and +// can be used to pass information such as a special port or password. +// If (reportingOptions & PEER_STOP_REPORTING), Peer will stop game reporting, +// so the program is responsible for any server reporting. +// If !(reportingOptions & PEER_STOP_REPORTING), Peer will continue doing +// game reporting, and calling the program-supplied callbacks. If +// (reportingOptions & PEER_REPORT_INFO), all server keys will still be +// reported. If (reportingOptions & PEER_REPORT_PLAYERS), all player keys +// will still be reported. +///////////////////////////////////////////////////////////////////////////// +void peerStartGame +( + PEER peer, // The peer object. + const gsi_char * message, // A message to send to everyone. + int reportingOptions // Bitfield flags used to set reporting options. +); + +// Starts server reporting, without creating a staging room. +// Call peerStopGame() to stop reporting. +//////////////////////////////////////////////////////////////// +PEERBool peerStartReporting +( + PEER peer // The peer object. +); + +// Same as peerStartReporting, but uses the provided socket for +// sending heartbeats and query replies. This allows the game +// to share a socket with the peer SDK, , which can make hosting +// games behind a NAT proxy possible. +//////////////////////////////////////////////////////////////// +PEERBool peerStartReportingWithSocket +( + PEER peer, // The peer object. + SOCKET socket, // The socket to be used for reporting. + unsigned short port // The local port to which the socket is bound. +); + +// Mark the local player as playing. +// Use this if the player is in a game not +// started by peer, but he should be marked as playing. +/////////////////////////////////////////////////////// +void peerStartPlaying +( + PEER peer // The peer object. +); + +// Check to see if the local player is playing. +/////////////////////////////////////////////// +PEERBool peerIsPlaying +( + PEER peer // The peer object. +); + +// Needs to be called by the host when the game has stopped. +//////////////////////////////////////////////////////////// +void peerStopGame +( + PEER peer // The peer object. +); + +// Call this when hosting a staging room or a game to force peer +// to report the game again. An example of when to call this is +// when a player joins or leaves a game. +// PANTS|09.11.00 +/////////////////////////////////////////////////////////////////// +void peerStateChanged +( + PEER peer // The peer object. +); + +/********* +** KEYS ** +*********/ +// Set global keys on the local player. +/////////////////////////////////////// +void peerSetGlobalKeys +( + PEER peer, // The peer object. + int num, // The number of keys to set. + const gsi_char ** keys, // The keys to set. + const gsi_char ** values // The values for the keys. +); + +// Called with a player's global keys. +////////////////////////////////////// +typedef void (* peerGetGlobalKeysCallback) +( + PEER peer, // The peer object. + PEERBool success, // If PEERFalse, unable to get the keys. + const gsi_char * nick, // The player the keys are for. + int num, // The number of keys. + const gsi_char ** keys, // The keys got. + const gsi_char ** values, // The values for the keys. + void * param // User-data. +); + +// Get a player's global keys. +////////////////////////////// +void peerGetPlayerGlobalKeys +( + PEER peer, // The peer object. + const gsi_char * nick, // The player to get the keys for. + int num, // The number of keys. + const gsi_char ** keys, // The keys to get. + peerGetGlobalKeysCallback callback, // Called with the keys. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Get the global keys for all players in a room we're in. +////////////////////////////////////////////////////////// +void peerGetRoomGlobalKeys +( + PEER peer, // The peer object. + RoomType roomType, // The room to get the keys in. + int num, // The number of keys. + const gsi_char ** keys, // The keys to get. + peerGetGlobalKeysCallback callback, // Called with the keys. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Set the room keys for a player. +// Use NULL or "" to set keys on the room itself. +///////////////////////////////////////////////// +void peerSetRoomKeys +( + PEER peer, // The peer object. + RoomType roomType, // The room to set the keys in. + const gsi_char * nick, // The player to set the keys on (NULL or "" for the room). + int num, // The number of keys. + const gsi_char ** keys, // The keys to set. + const gsi_char ** values // The values to set. +); + +// Called with a player's room keys. +//////////////////////////////////// +typedef void (* peerGetRoomKeysCallback) +( + PEER peer, // The peer object. + PEERBool success, // If PEERFalse, unable to get the keys. + RoomType roomType, // The room the keys are in. + const gsi_char * nick, // The player the keys are for, or NULL for the room. + int num, // The number of keys. + gsi_char ** keys, // The keys. + gsi_char ** values, // The values for the keys. + void * param // User-data. +); + +// Get the room keys for either a single player of an entire room. +// Use "*" for the player to get the keys for the entire room. +// Use NULL or "" for the player to get keys on the room itself. +////////////////////////////////////////////////////////////////// +void peerGetRoomKeys +( + PEER peer, // The peer object. + RoomType roomType, // The room to get the keys in. + const gsi_char * nick, // The player to get the keys for. + int num, // The number of keys. + const gsi_char ** keys, // The keys to get. + peerGetRoomKeysCallback callback, // Called with the keys. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Set the global watch keys for a room type. +// If addKeys is set to PEERTrue, the keys will be +// added to the current global watch keys for this room. +// If addKeys is PEERFalse, these will replace any existing +// global watch keys for this room. +// When entering a room of the given type, peer will get and +// cache these keys for all players in the room. +//////////////////////////////////////////////////////////// +void peerSetGlobalWatchKeys +( + PEER peer, // The peer object. + RoomType roomType, // The type of room to set the watch keys for. + int num, // The number of keys. + const gsi_char ** keys, // The keys to watch for. + PEERBool addKeys // If PEERTrue, add these keys to the existing global watch keys for this room. +); + +// Set the room watch keys for a room type. +// If addKeys is set to PEERTrue, the keys will be +// added to the current room watch keys for this room. +// If addKeys is PEERFalse, these will replace any existing +// room watch keys for this room. +// When entering a room of the given type, peer will get and +// cache these keys for all players in the room. +//////////////////////////////////////////////////////////// +void peerSetRoomWatchKeys +( + PEER peer, // The peer object. + RoomType roomType, // The type of room to set the watch keys for. + int num, // The number of keys. + const gsi_char ** keys, // The keys to watch for. + PEERBool addKeys // If PEERTrue, add these keys to the existing room watch keys for this room. +); + +// Get the global watch key for a particular player. +// If the key isn't cached locally (either because it isn't +// a watch key or just isn't yet known), NULL will be returned. +// If the key is empty, "" will be returned. +/////////////////////////////////////////////////////////////// +const gsi_char * peerGetGlobalWatchKey +( + PEER peer, // The peer object. + const gsi_char * nick, // The player to get the key for. + const gsi_char * key // The key to get. +); + +// Get the room watch key for a particular player in a room. +// If the key isn't cached locally (either because it isn't +// a watch key or just isn't yet known), NULL will be returned. +// If the key is empty, "" will be returned. +/////////////////////////////////////////////////////////////// +const gsi_char * peerGetRoomWatchKey +( + PEER peer, // The peer object. + RoomType roomType, // The room to get the key in. + const gsi_char * nick, // The player to get the key for. + const gsi_char * key // The key to get. +); + +/************** +** AUTOMATCH ** +**************/ + +// Called during a automatch attempt to inform the app of the status. +///////////////////////////////////////////////////////////////////// +typedef void (* peerAutoMatchStatusCallback) +( + PEER peer, // The peer object. + PEERAutoMatchStatus status, // The current status. + void * param // User-data. +); + +// This callback may be called during the automatch attempt. +// Peer uses the callback to ask the application how well a particular +// match fits what the user is looking for. The application checks the match +// and then returns a rating for it. If the rating is <=0 the match is +// considered unsuitable and Peer will not attempt that match. If the rating +// is >0 this match is considered suitable. The higher the rating, up to a +// maximum of INT_MAX, the more suitable the match. The exact scheme used +// to generate a rating is entirely up to the application. The match ratings +// are used by Peer both to check if a particular match meets the user's wishes +// and to compare matches to each other. Peer will first try to match with the +// highest rated possible match, then the second highest, etc. +/////////////////////////////////////////////////////////////////////////////// +typedef int (* peerAutoMatchRateCallback) +( + PEER peer, // The peer object. + SBServer match, // Possible match to rate. + void * param // User-data. +); + +// Used to start a automatch attempt. +// The filter contains any hard criteria. This is used to narrow down the list +// of possible matches to those the user might consider. +// The status callback will be called as the attempt progresses, and the rate +// callback will be used to sort possible matches. +///////////////////////////////////////////////////////////////////////////////// +void peerStartAutoMatch +( + PEER peer, // The peer object. + int maxPlayers, // The total number of players to match (including the local player). + const gsi_char * filter, // Hard criteria - filters out servers. + peerAutoMatchStatusCallback statusCallback, // Called as the attempt status changes. + peerAutoMatchRateCallback rateCallback, // Used to rate possible matches. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Same as peerStartAutoMatch, but uses the provided socket for +// sending heartbeats and query replies. This allows the game +// to share a socket with the peer SDK, which can make hosting +// games behind a NAT proxy possible. +//////////////////////////////////////////////////////////////// +void peerStartAutoMatchWithSocket +( + PEER peer, // The peer object. + int maxPlayers, // The total number of players to match (including the local player). + const gsi_char * filter, // Hard criteria - filters out servers. + SOCKET socket, // The socket to be used for reporting. + unsigned short port, // The local port to which the socket is bound. + peerAutoMatchStatusCallback statusCallback, // Called as the attempt status changes. + peerAutoMatchRateCallback rateCallback, // Used to rate possible matches. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Stops an automatch attempt in progress. +// The user will not automatically be removed from any staging room he is in. +///////////////////////////////////////////////////////////////////////////// +void peerStopAutoMatch(PEER peer); + +// Checks if a automatch attempt is in progress. +//////////////////////////////////////////////// +PEERBool peerIsAutoMatching(PEER peer); + +// Gets the current status of the automatch attempt. +// If no attempt is in progress, PEERFailed will be returned. +///////////////////////////////////////////////////////////// +PEERAutoMatchStatus peerGetAutoMatchStatus(PEER peer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerAscii.h b/code/gamespy/Peer/peerAscii.h new file mode 100644 index 00000000..8ed2352e --- /dev/null +++ b/code/gamespy/Peer/peerAscii.h @@ -0,0 +1,734 @@ + /* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERASCII_H_ +#define _PEERASCII_H_ + +// PROTOTYPES OF ASCII FUNCTIONS +// The only purpose of this file is to silence the CodeWarrior +// "function not prototyped" warnings when using GSI_UNICODE mode + +#ifdef __cplusplus +extern "C" { +#endif + + +/************ +** GENERAL ** +************/ +// This connects a peer object to a chat server. +// Call peerDisconnect() to close the connection. +// A title must be set with peerSetTitle before connecting. +/////////////////////////////////////////////////////////// +void peerConnectA +( + PEER peer, // The peer object. + const char * nick, // The nick to connect with. + int profileID, // The profileID, or 0 if no profileID. + peerNickErrorCallback nickErrorCallback, // Called if nick error. + peerConnectCallback connectCallback, // Called on complete. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Same as peerConnect, but also authenticates using a uniquenick +// and password or an email, profilenick, and password. +///////////////////////////////////////////////////////////////// +void peerConnectLoginA +( + PEER peer, // The peer object. + int namespaceID, // The namespace in which to login. + const char * email, // The email to login with. + const char * profilenick, // The profile to login with. + const char * uniquenick, // The uniquenick to login with. + const char * password, // The password for the login account. + peerNickErrorCallback nickErrorCallback, // Called if nick error. + peerConnectCallback connectCallback, // Called on complete. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Same as peerConnect, but also authenticates using an authtoken +// and partnerchallenge from a partner account system. +///////////////////////////////////////////////////////////////// +void peerConnectPreAuthA +( + PEER peer, // The peer object. + const char * authtoken, // The authtoken for this login. + const char * partnerchallenge, // The partner challenge for this login. + peerNickErrorCallback nickErrorCallback, // Called if nick error. + peerConnectCallback connectCallback, // Called on complete. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// If peerNickErrorCallback is called, call this to +// try and continue the connection with a new nickname. +// If there is an error with this nick, the +// peerNickErrCallback will be called again. +/////////////////////////////////////////////////////// +void peerRetryWithNickA +( + PEER peer, + const char * nick +); + +// Register a uniquenick. +// Should be called in response to the peerNickErrorCallback being called +// with a type of PEER_UNIQUENICK_EXPIRED or PEER_NO_UNIQUENICK. +// If the uniquenick cannot be registered, the peerNickErrorCallback will +// be called again with a type of PEER_IN_USE or PEER_INVALID. +///////////////////////////////////////////////////////////////////////// +void peerRegisterUniqueNickA +( + PEER peer, + int namespaceID, + const char * uniquenick, + const char * cdkey +); + +// Sets the current title. +// A title must be set before connecting. +// Returns PEERFalse if an error, or PEERTrue if success. +// For most games the title/qrSecretKey and +// sbName/sbSecretKey pairs will be the same. +///////////////////////////////////////////////////////// +PEERBool peerSetTitleA +( + PEER peer, // The peer object. + const char * title, // The title to make current (ie., ut, gmtest). + const char * qrSecretKey, // The queryreporting secret key. + const char * sbName, // The serverbrowsing name. + const char * sbSecretKey, // The serverbrowsing secret key. + int sbGameVersion, // The version of the game doing the browsing. + int sbMaxUpdates, // The maximum number of concurent updates (10-15 for modem users, 20-30 for high-bandwidth). + PEERBool natNegotiate, // PEERTrue if the title supports GameSpy's NAT-negotiation technology (or another similar technology). + PEERBool pingRooms[NumRooms], // To do pings int a room, set it to PEERTrue. + PEERBool crossPingRooms[NumRooms] // To do cross-pings in a room, set it to PEERTrue. +); + +// Gets the currently set title. +// Returns NULL if no title is set. +/////////////////////////////////// +const char * peerGetTitleA(PEER peer); + +// Get the local user's nickname. +///////////////////////////////// +const char * peerGetNickA(PEER peer); + +// Replaces any invalid characters in nick with underscores. +//////////////////////////////////////////////////////////// +void peerFixNickA +( + char * newNick, + const char * oldNick +); + +// Removes the namespace extension from a chat unique nick. +// Returns the nick if it ended with the extension. +// Otherwise returns NULL. +/////////////////////////////////////////////////////////// +const char * peerTranslateNickA +( + char * nick, // The nick to translate + const char * extension // The extension to be removed. +); + +// Changes the user's nickname. +/////////////////////////////// +void peerChangeNickA +( + PEER peer, // The peer object. + const char * newNick, // The nickname to which to change. + peerChangeNickCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Sets the away mode. +// If an empty string or NULL, away mode is off. +// If a valid string, away mode is on. +//////////////////////////////////////////////// +void peerSetAwayModeA +( + PEER peer, // The peer object. + const char * reason // The away reason. If NULL or "", not away. +); + +// When using peerStartReportingWithSocket or peerCreateStagingRoomWithSocket, +// any qureries received on the sockets need to be passed to the SDK. Pass +// the data using this function. +////////////////////////////////////////////////////////////////////////////// +void peerParseQueryA +( + PEER peer, // The peer object. + char * query, // String of query data. + int len, // The length of the string, not including the NUL. + struct sockaddr * sender // The address the query was received from. +); + +// Sends a CD Key to the chat server for authentication. +// The callback gets called when the chat server responds. +// This call is required by some games to enter chat rooms. +// It must be called after connecting to the chat server, but +// before entering a room. +///////////////////////////////////////////////////////////// +void peerAuthenticateCDKeyA +( + PEER peer, // The peer object. + const char * cdkey, // The CD key to authenticate. + peerAuthenticateCDKeyCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +/********** +** ROOMS ** +**********/ +// Joins the currently selected title's title room. +/////////////////////////////////////////////////// +void peerJoinTitleRoomA +( + PEER peer, // The peer object. + const char password[PEER_PASSWORD_LEN], // An optional password, normally NULL. + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Joins a staging room. +// server is one of the server objects passed to peerListingGamesCallback(). +// This call will only work if staging==PEERTrue for the server. +// PANTS|09.11.00 - The password is only needed for passworded rooms. +// PANTS|03.15.01 - No longer requires you to be actively listing games. +//////////////////////////////////////////////////////////////////////////// +void peerJoinStagingRoomA +( + PEER peer, // The peer object. + SBServer server, // The server passed into peerlistingGamesCallback(). + const char password[PEER_PASSWORD_LEN], // The password of the room being joined. Can be NULL or "". + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Joins a staging room by its channel name. +// Same as peerJoinStagingRoom, except it takes the staging room's +// channel name instead of an SBServer object. Also, when the +// peerGameStartedCallback is called, the server paramter passed to +// it will be NULL. +/////////////////////////////////////////////////////////////////// +void peerJoinStagingRoomByChannelA +( + PEER peer, // The peer object. + const char * channel, // The channel to join. + const char password[PEER_PASSWORD_LEN], // The password of the room being joined. Can be NULL or "". + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Creates a new staging room, with the local player hosting. +// PANTS|09.11.00 - If the password parameter is not NULL +// or "", this will create a passworded room. The same +// case-sensitive password needs to be passed into +// peerJoinStagingRoom() for other player's to join the room. +// PANTS|09.11.00 - The staging room will be reported as part +// of whatever group room the local player was in when the +// room was created. Leaving the group room will not affect +// what group the staging room is reported as part of. +///////////////////////////////////////////////////////////// +void peerCreateStagingRoomA +( + PEER peer, // The peer object. + const char * name, // The name of the room. + int maxPlayers, // The max number of players allowed in the room. + const char password[PEER_PASSWORD_LEN], // An optional password for the staging room + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Same as peerCreateStagingRoom, but uses the provided socket for +// sending heartbeats and query replies. This allows the game +// to share a socket with the peer SDK, which can make hosting +// games behind a NAT proxy possible. +////////////////////////////////////////////////////////////////// +void peerCreateStagingRoomWithSocketA +( + PEER peer, // The peer object. + const char * name, // The name of the room. + int maxPlayers, // The max number of players allowed in the room. + const char password[PEER_PASSWORD_LEN], // An optional password for the staging room + SOCKET socket, // The socket to be used for reporting. + unsigned short port, // The local port to which the socket is bound. + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Leave a room. +// PANTS|09.11.00 - You can now leave a group room +// without being forcibly removed from a staging room. +////////////////////////////////////////////////////// +void peerLeaveRoomA +( + PEER peer, // The peer object. + RoomType roomType, // The room you want to leave (TitleRoom, GroupRoom, or StagingRoom). + const char * reason // The reason the player is leaving (can be NULL). PANTS|03.13.01 +); + +// List all the groups rooms for the currently set title. +// The fields parameter allows you to request extra info +// on each group room. +///////////////////////////////////////////////////////// +void peerListGroupRoomsA +( + PEER peer, // The peer object. + const char * fields, // A backslash delimited list of fields. + peerListGroupRoomsCallback callback, // Called for each group room. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Start listing the currently running games and staging rooms. +// This is used to maintain a list that can presented to the user, +// so they can pick a game (or staging room) to join. +// Games and staging rooms are filtered based on what group the local +// user is in. If the local user isn't in a group, then only games +// and staging rooms that aren't part of any group are listed. +// The fields determine which info to request from the server. These +// must be registered QR2 keys (see qr2regkeys.h). HOSTNAME_KEY and +// GAMEMODE_KEY are automatically requested by Peer, so do not need +// to included. +///////////////////////////////////////////////////////////////////// +void peerStartListingGamesA +( + PEER peer, // The peer object. + const unsigned char * fields, // An array of registered QR2 keys to request from servers. + int numFields, // The number of keys in the fields array. + const char * filter, // A SQL-like rule filter. + peerListingGamesCallback callback, // Called when finished. + void * param // Passed to the callback. +); + +// Send a message to a room. +//////////////////////////// +void peerMessageRoomA +( + PEER peer, // The peer object. + RoomType roomType, // The room to send the message to. + const char * message, // The message. + MessageType messageType // The type of message. +); + +// Send a UTM to a room. +//////////////////////// +void peerUTMRoomA +( + PEER peer, // The peer object. + RoomType roomType, // The room to send the UTM to. + const char * command, // The command. + const char * parameters, // The UTM's parameters. + PEERBool authenticate // If true, the server will authenticate this UTM (should normally be false). +); + +// Set a password in a room you're hosting. +// The only roomType currently supported is StagingRoom. +// This will only work if the player is hosting the room. +// If password is NULL or "", the password will be cleared. +/////////////////////////////////////////////////////////// +void peerSetPasswordA +( + PEER peer, // The peer object. + RoomType roomType, // The room in which to set the password. + const char password[PEER_PASSWORD_LEN] // The password to set. +); + +// Set the name of a room you're hosting. +// The only roomType currently supported is StagingRoom. +// PANTS|09.11.00 +//////////////////////////////////////////////////////// +void peerSetRoomNameA +( + PEER peer, // The peer object. + RoomType roomType, // The room in which to set the name. + const char * name // The new name +); + +// Get a room's name (the channel's title). +// Returns NULL if not in the room. +/////////////////////////////////////////// +const char * peerGetRoomNameA +( + PEER peer, // The peer object. + RoomType roomType // The room to get the name for. +); + +// Get's the chat channel associated with the room. +// Returns NULL if not in the room. +/////////////////////////////////////////////////// +const char * peerGetRoomChannelA +( + PEER peer, // The peer object. + RoomType roomType // The room to get the channel for. +); + +// Use this channel for the title room for the currently set title. +// This function is normally not needed. +/////////////////////////////////////////////////////////////////// +void peerSetTitleRoomChannelA +( + PEER peer, // The peer object. + const char * channel // The channel to use for the title room. +); + + +/************ +** PLAYERS ** +************/ +// Send a message to a player. +////////////////////////////// +void peerMessagePlayerA +( + PEER peer, // The peer object. + const char * nick, // The nick of the player to send the message to. + const char * message, // The message to send. + MessageType messageType // The type of message. +); + +// Send a UTM to a player. +////////////////////////// +void peerUTMPlayerA +( + PEER peer, // The peer object. + const char * nick, // The nick of the player to send the UTM to. + const char * command, // The command. + const char * parameters, // The UTM's parameters. + PEERBool authenticate // If true, the server will authenticate this UTM (should normally be false). +); + +// Kick a player from a room. +// Can only be called by a player with host privileges. +/////////////////////////////////////////////////////// +void peerKickPlayerA +( + PEER peer, // The peer object. + RoomType roomType, // The room to kick the player from. + const char * nick, // The nick of the player to kick. + const char * reason // An optional reason for kicking the player +); + +// Gets a player's ping (between the local machine and the player's machine). +// Returns PEERFalse if we don't have or can't get the player's ping. +///////////////////////////////////////////////////////////////////////////// +PEERBool peerGetPlayerPingA +( + PEER peer, // The peer object. + const char * nick, // The player to get the ping for. + int * ping // The player's ping is stored here, if we have it. +); + +// Gets the cross-ping between 2 players. +// Returns PEERFalse if we don't have or can't get the player's cross-ping. +/////////////////////////////////////////////////////////////////////////// +PEERBool peerGetPlayersCrossPingA +( + PEER peer, // The peer object. + const char * nick1, // The first player. + const char * nick2, // The second player. + int * crossPing // The cross-ping is stored here, if we have it. +); + +// Peer already automatically pings all players that are in ping rooms. +// This function does a one-time ping of a remote player in a non-ping room. +// However pings must be enabled in at least one room for this to work, +// otherwise Peer will not open the UDP ping socket. +// Also, peerAlwaysGetPlayerInfo() must be enabled so that Peer has IPs for +// players that are only in non-ping rooms. +//////////////////////////////////////////////////////////////////////////// +PEERBool peerPingPlayerA +( + PEER peer, // The peer object. + const char * nick // The player to ping. +); + +// Gets a player's info immediately. +// Returns PEERFalse if the info is no available. +///////////////////////////////////////////////// +PEERBool peerGetPlayerInfoNoWaitA +( + PEER peer, + const char * nick, + unsigned int * IP, + int * profileID +); + +// Called to get a player's IP and profile ID. +////////////////////////////////////////////// +void peerGetPlayerInfoA +( + PEER peer, // The peer object. + const char * nick, // The player's nick. + peerGetPlayerInfoCallback callback, // Called when finished. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Called to get a player's profile ID. +// DEPRECATED - Use peerGetPlayerInfo. +/////////////////////////////////////// +void peerGetPlayerProfileIDA +( + PEER peer, // The peer object. + const char * nick, // The player's nick. + peerGetPlayerProfileIDCallback callback, // Called when finished. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Called to get a player's IP. +// DEPRECATED - Use peerGetPlayerInfo. +////////////////////////////////////// +void peerGetPlayerIPA +( + PEER peer, // The peer object. + const char * nick, // The player's nick. + peerGetPlayerIPCallback callback, // Called when finished. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Checks if a player is a host (has ops). +// Returns PEERTrue if yes, PEERFalse if no. +//////////////////////////////////////////// +PEERBool peerIsPlayerHostA +( + PEER peer, // The peer object. + const char * nick, // The player's nick. + RoomType roomType // The room to check in. +); + +// Gets a player's flags in a room. Returns PEERFalse if +// the local player or this player is not in the room. +///////////////////////////////////////////////////////// +PEERBool peerGetPlayerFlagsA +( + PEER peer, + const char * nick, + RoomType roomType, + int * flags +); + +/********* +** GAME ** +*********/ +// Gets a player's ready state. +/////////////////////////////// +PEERBool peerGetReadyA +( + PEER peer, // The peer object. + const char * nick, // The player's nick. + PEERBool * ready // The player's ready state gets stored in here. +); + +// Called only by a staging room host to start the game. +// All the other people in the staging room will have their +// peerGameStartedCallback() called. +// The message gets passed to everyone in the peerGameStartedCallback(), and +// can be used to pass information such as a special port or password. +// If (reportingOptions & PEER_STOP_REPORTING), Peer will stop game reporting, +// so the program is responsible for any server reporting. +// If !(reportingOptions & PEER_STOP_REPORTING), Peer will continue doing +// game reporting, and calling the program-supplied callbacks. If +// (reportingOptions & PEER_REPORT_INFO), all server keys will still be +// reported. If (reportingOptions & PEER_REPORT_PLAYERS), all player keys +// will still be reported. +///////////////////////////////////////////////////////////////////////////// +void peerStartGameA +( + PEER peer, // The peer object. + const char * message, // A message to send to everyone. + int reportingOptions // Bitfield flags used to set reporting options. +); + +/********* +** KEYS ** +*********/ +// Set global keys on the local player. +/////////////////////////////////////// +void peerSetGlobalKeysA +( + PEER peer, // The peer object. + int num, // The number of keys to set. + const char ** keys, // The keys to set. + const char ** values // The values for the keys. +); + + +// Get a player's global keys. +////////////////////////////// +void peerGetPlayerGlobalKeysA +( + PEER peer, // The peer object. + const char * nick, // The player to get the keys for. + int num, // The number of keys. + const char ** keys, // The keys to get. + peerGetGlobalKeysCallback callback, // Called with the keys. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Get the global keys for all players in a room we're in. +////////////////////////////////////////////////////////// +void peerGetRoomGlobalKeysA +( + PEER peer, // The peer object. + RoomType roomType, // The room to get the keys in. + int num, // The number of keys. + const char ** keys, // The keys to get. + peerGetGlobalKeysCallback callback, // Called with the keys. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Set the room keys for a player. +// Use NULL or "" to set keys on the room itself. +///////////////////////////////////////////////// +void peerSetRoomKeysA +( + PEER peer, // The peer object. + RoomType roomType, // The room to set the keys in. + const char * nick, // The player to set the keys on (NULL or "" for the room). + int num, // The number of keys. + const char ** keys, // The keys to set. + const char ** values // The values to set. +); + +// Get the room keys for either a single player of an entire room. +// Use "*" for the player to get the keys for the entire room. +// Use NULL or "" for the player to get keys on the room itself. +////////////////////////////////////////////////////////////////// +void peerGetRoomKeysA +( + PEER peer, // The peer object. + RoomType roomType, // The room to get the keys in. + const char * nick, // The player to get the keys for. + int num, // The number of keys. + const char ** keys, // The keys to get. + peerGetRoomKeysCallback callback, // Called with the keys. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Set the global watch keys for a room type. +// If addKeys is set to PEERTrue, the keys will be +// added to the current global watch keys for this room. +// If addKeys is PEERFalse, these will replace any existing +// global watch keys for this room. +// When entering a room of the given type, peer will get and +// cache these keys for all players in the room. +//////////////////////////////////////////////////////////// +void peerSetGlobalWatchKeysA +( + PEER peer, // The peer object. + RoomType roomType, // The type of room to set the watch keys for. + int num, // The number of keys. + const char ** keys, // The keys to watch for. + PEERBool addKeys // If PEERTrue, add these keys to the existing global watch keys for this room. +); + +// Set the room watch keys for a room type. +// If addKeys is set to PEERTrue, the keys will be +// added to the current room watch keys for this room. +// If addKeys is PEERFalse, these will replace any existing +// room watch keys for this room. +// When entering a room of the given type, peer will get and +// cache these keys for all players in the room. +//////////////////////////////////////////////////////////// +void peerSetRoomWatchKeysA +( + PEER peer, // The peer object. + RoomType roomType, // The type of room to set the watch keys for. + int num, // The number of keys. + const char ** keys, // The keys to watch for. + PEERBool addKeys // If PEERTrue, add these keys to the existing room watch keys for this room. +); + +// Get the global watch key for a particular player. +// If the key isn't cached locally (either because it isn't +// a watch key or just isn't yet known), NULL will be returned. +// If the key is empty, "" will be returned. +/////////////////////////////////////////////////////////////// +const char * peerGetGlobalWatchKeyA +( + PEER peer, // The peer object. + const char * nick, // The player to get the key for. + const char * key // The key to get. +); + +// Get the room watch key for a particular player in a room. +// If the key isn't cached locally (either because it isn't +// a watch key or just isn't yet known), NULL will be returned. +// If the key is empty, "" will be returned. +/////////////////////////////////////////////////////////////// +const char * peerGetRoomWatchKeyA +( + PEER peer, // The peer object. + RoomType roomType, // The room to get the key in. + const char * nick, // The player to get the key for. + const char * key // The key to get. +); + +/************** +** AUTOMATCH ** +**************/ + +// Used to start a automatch attempt. +// The filter contains any hard criteria. This is used to narrow down the list +// of possible matches to those the user might consider. +// The status callback will be called as the attempt progresses, and the rate +// callback will be used to sort possible matches. +///////////////////////////////////////////////////////////////////////////////// +void peerStartAutoMatchA +( + PEER peer, // The peer object. + int maxPlayers, // The total number of players to match (including the local player). + const char * filter, // Hard criteria - filters out servers. + peerAutoMatchStatusCallback statusCallback, // Called as the attempt status changes. + peerAutoMatchRateCallback rateCallback, // Used to rate possible matches. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Same as peerStartAutoMatch, but uses the provided socket for +// sending heartbeats and query replies. This allows the game +// to share a socket with the peer SDK, which can make hosting +// games behind a NAT proxy possible. +//////////////////////////////////////////////////////////////// +void peerStartAutoMatchWithSocketA +( + PEER peer, // The peer object. + int maxPlayers, // The total number of players to match (including the local player). + const char * filter, // Hard criteria - filters out servers. + SOCKET socket, // The socket to be used for reporting. + unsigned short port, // The local port to which the socket is bound. + peerAutoMatchStatusCallback statusCallback, // Called as the attempt status changes. + peerAutoMatchRateCallback rateCallback, // Used to rate possible matches. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerAutoMatch.c b/code/gamespy/Peer/peerAutoMatch.c new file mode 100644 index 00000000..9a8c8fd9 --- /dev/null +++ b/code/gamespy/Peer/peerAutoMatch.c @@ -0,0 +1,334 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include "peerAutoMatch.h" +#include "peerMangle.h" +#include "peerOperations.h" +#include "peerSB.h" +#include "peerQR.h" +#include "peerCallbacks.h" +#include "peerPlayers.h" +#include "peerRooms.h" + +/************** +** FUNCTIONS ** +**************/ +static PEERBool piCreateAutoMatchRoom(PEER peer); + +static void piCleanAutoMatch(PEER peer) +{ + PEER_CONNECTION; + + // free memory + gsifree(connection->autoMatchFilter); + + // remove the operation + piRemoveOperation(peer, connection->autoMatchOperation); + + connection->autoMatchOperation = NULL; +} + +void piSetAutoMatchStatus(PEER peer, PEERAutoMatchStatus status) +{ + piOperation * operation; + PEER_CONNECTION; + + // make sure there is an operation + operation = connection->autoMatchOperation; + assert(operation); + if(!operation) + return; + + // we need to create a room before we can really switch to PEERWaiting + if((status == PEERWaiting) && !connection->inRoom[StagingRoom]) + { + if(!piCreateAutoMatchRoom(peer)) + piSetAutoMatchStatus(peer, connection->autoMatchSBFailed?PEERFailed:PEERSearching); + return; + } + + // check if this is already the current status + if(connection->autoMatchStatus != status) + { + // set the new status + connection->autoMatchStatus = status; + + // add the callback + piAddAutoMatchStatusCallback(peer); + } + + // handle the status + switch(status) + { + case PEERFailed: + // stop + piSBStopListingAutoMatches(peer); + piStopAutoMatchReporting(peer); + piLeaveRoom(peer, StagingRoom, ""); + + // clean + piCleanAutoMatch(peer); + break; + + case PEERSearching: + // stop + piStopAutoMatchReporting(peer); + piLeaveRoom(peer, StagingRoom, ""); + + // start + if(!connection->autoMatchBrowsing) + { + if(!piSBStartListingAutoMatches(peer)) + { + piSetAutoMatchStatus(peer, connection->autoMatchQRFailed?PEERFailed:PEERWaiting); + return; + } + } + break; + + case PEERWaiting: + // start + assert(connection->inRoom[StagingRoom]); + if(!connection->autoMatchBrowsing && !connection->autoMatchSBFailed) + piSBStartListingAutoMatches(peer); + + if(!connection->autoMatchReporting) + { + if(!piStartAutoMatchReporting(peer)) + { + piSetAutoMatchStatus(peer, connection->autoMatchSBFailed?PEERFailed:PEERSearching); + return; + } + } + break; + + case PEERStaging: + // stop + if(!connection->hosting) + { + piStopAutoMatchReporting(peer); + } + piSBStopListingAutoMatches(peer); + + // start + if(connection->hosting && !connection->autoMatchReporting) + { + if(!piStartAutoMatchReporting(peer)) + { + piSetAutoMatchStatus(peer, PEERSearching); + return; + } + } + break; + + case PEERReady: + // start + if (connection->hosting && !connection->autoMatchReporting) + piStartAutoMatchReporting(peer); + + break; + case PEERComplete: + // stop + piSBStopListingAutoMatches(peer); + piStopAutoMatchReporting(peer); + + // clean + piCleanAutoMatch(peer); + break; + } +} + +void piStopAutoMatch(PEER peer) +{ + //PEERBool inRoom; + + PEER_CONNECTION; + + // make sure we're AutoMatching + if(peerIsAutoMatching(peer)) + { + // we don't want the status callback called + if(connection->autoMatchOperation) + connection->autoMatchOperation->callback = (PEERCBType)NULL; + + // trick it into thinking it's not in a staging room (if it is) + //inRoom = connection->inRoom[StagingRoom]; + //connection->inRoom[StagingRoom] = PEERFalse; + + // cleanup + piSetAutoMatchStatus(peer, PEERFailed); + + // reset the inRoom flag + //connection->inRoom[StagingRoom] = inRoom; + } +} + +static void piJoinAutoMatchRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + void * param +) +{ + PEER_CONNECTION; + + // if we're the only one, or if there's no host, leave + if(success && ((connection->numPlayers[StagingRoom] <= 1) || !piCountRoomOps(peer, StagingRoom, connection->nick))) + { + piLeaveRoom(peer, StagingRoom, ""); + success = PEERFalse; + } + + // check if it succeeded + if(success) + { + // we're now staging + piSetAutoMatchStatus(peer, PEERStaging); + + // if we've got maxplayers, we're now Ready + if((connection->autoMatchStatus == PEERStaging) && (connection->numPlayers[StagingRoom] >= connection->maxPlayers)) + piSetAutoMatchStatus(peer, PEERReady); + } + + // if we were waiting, and this failed, restart waiting + if(!success && (connection->autoMatchStatus == PEERWaiting)) + piSetAutoMatchStatus(peer, PEERWaiting); + + GSI_UNUSED(result); + GSI_UNUSED(roomType); + GSI_UNUSED(param); +} + +PEERBool piJoinAutoMatchRoom(PEER peer, SBServer server) +{ + unsigned int publicIP; + unsigned int privateIP; + unsigned short privatePort; + char room[PI_ROOM_MAX_LEN]; + + PEER_CONNECTION; + + // Get the public and private IPs and ports. + publicIP = SBServerGetPublicInetAddress(server); + privateIP = SBServerGetPrivateInetAddress(server); + if(SBServerHasPrivateAddress(server)) + privatePort = SBServerGetPrivateQueryPort(server); + else + privatePort = SBServerGetPublicQueryPort(server); + + if(!publicIP) + return PEERFalse; + + // get the staging room. + piMangleStagingRoom(room, connection->title, publicIP, privateIP, privatePort); + + // start the operation. + if(!piNewJoinRoomOperation(peer, StagingRoom, room, NULL, piJoinAutoMatchRoomCallback, NULL, piGetNextID(peer))) + return PEERFalse; + + // clone the server + connection->hostServer = piSBCloneServer(server); + + return PEERTrue; +} + +// GetChannelModeCallback used to get the channel modes +// and fix the modes: Limit and OpsObeyChannelLimit +static void piAutoMatchGetChannelCallback(CHAT chat, CHATBool success, const gsi_char *channel, CHATChannelMode *mode, void *param) +{ + piConnection *connection = (piConnection *)param; + if (success) + { + + mode->Limit = connection->maxPlayers; + mode->OpsObeyChannelLimit = CHATTrue; + + // Don't let ops bypass channel limit on the number of players in room + chatSetChannelMode(chat, channel, mode); + } + else + { + // handle the failure + connection->autoMatchQRFailed = PEERTrue; + piSetAutoMatchStatus((PEER *)&connection, connection->autoMatchSBFailed?PEERFailed:PEERSearching); + } +} + +static void piCreateAutoMatchRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + void * param +) +{ + PEERAutoMatchStatus status; + + PEER_CONNECTION; + + if(success) + { + // set the status based on the number of people in the room + if(connection->numPlayers[StagingRoom] <= 1) + { + status = PEERWaiting; + + // get the channel modes so we can set the limits on our staging channel + chatGetChannelMode(connection->chat, peerGetRoomChannel(peer, StagingRoom), piAutoMatchGetChannelCallback, + (void *)connection, CHATFalse); + } + else if(connection->numPlayers[StagingRoom] >= connection->maxPlayers) + { + status = PEERReady; + } + else + { + status = PEERStaging; + } + + // set the Waiting status + piSetAutoMatchStatus(peer, status); + } + else + { + // handle the failure + connection->autoMatchQRFailed = PEERTrue; + piSetAutoMatchStatus(peer, connection->autoMatchSBFailed?PEERFailed:PEERSearching); + } + + GSI_UNUSED(result); + GSI_UNUSED(roomType); + GSI_UNUSED(param); +} + +static PEERBool piCreateAutoMatchRoom(PEER peer) +{ + piOperation * operation; + + PEER_CONNECTION; + + // get the AutoMatch operation + operation = connection->autoMatchOperation; + + // start the operations + if(!piNewCreateStagingRoomOperation(peer, connection->nick, "", connection->maxPlayers, operation->socket, operation->port, piCreateAutoMatchRoomCallback, NULL, piGetNextID(peer))) + { + connection->autoMatchQRFailed = PEERTrue; + return PEERFalse; + } + + return PEERTrue; +} diff --git a/code/gamespy/Peer/peerAutoMatch.h b/code/gamespy/Peer/peerAutoMatch.h new file mode 100644 index 00000000..73234a49 --- /dev/null +++ b/code/gamespy/Peer/peerAutoMatch.h @@ -0,0 +1,39 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERAUTOMATCH_H_ +#define _PEERAUTOMATCH_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/************ +** DEFINES ** +************/ +#define PI_AUTOMATCH_RATING_KEY "gsi_am_rating" + +/************** +** FUNCTIONS ** +**************/ +void piSetAutoMatchStatus(PEER peer, PEERAutoMatchStatus status); +void piStopAutoMatch(PEER peer); +PEERBool piJoinAutoMatchRoom(PEER peer, SBServer server); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerCallbacks.c b/code/gamespy/Peer/peerCallbacks.c new file mode 100644 index 00000000..87d1997a --- /dev/null +++ b/code/gamespy/Peer/peerCallbacks.c @@ -0,0 +1,3695 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include +#include "peer.h" +#include "peerMain.h" +#include "peerCallbacks.h" +#include "peerOperations.h" + +/************ +** DEFINES ** +************/ +#define ASSERT_DATA(data) assert(data->type >= 0);\ + assert(data->callback);\ + assert(data->params); + +/********** +** TYPES ** +**********/ +typedef struct piCallbackData +{ + piCallbackType type; // PI__CALLBACK + PEERBool success; // operation success + PEERCBType callback; // the function callback -- int type for ANSI compatability + void * callbackParam; // user-data for the callback + void * params; // extra callback params + int ID; // unique ID for this callback + PEERBool inCall; // set to true immediately before callback is called, then to false +} piCallbackData; + +typedef struct piCallbackFuncs +{ + piCallbackType type; + PEERBool (* copy) + ( + void * paramsOut, + void * paramsIn + ); + void (* gsifree) + ( + void * params + ); + void (* call) + ( + PEER peer, + piCallbackData * data + ); +} piCallbackFuncs; + +/************** +** CALLBACKS ** +**************/ +static int piAddCallback +( + PEER peer, + PEERBool success, + PEERCBType callback, + void * param, + piCallbackType type, + void * paramsIn, + size_t paramsSize, + int opID +); + +/* Connect. +**********/ +typedef struct piConnectParams +{ + int failureReason; +} piConnectParams; +static PEERBool piConnectCopy(void * paramsOut_, void * paramsIn_) +{ + piConnectParams * paramsOut = (piConnectParams *)paramsOut_; + piConnectParams * paramsIn = (piConnectParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->failureReason = paramsIn->failureReason; + + return PEERTrue; +} +static void piConnectFree(void * params_) +{ + piConnectParams * params = (piConnectParams *)params_; + + assert(params); + GSI_UNUSED(params); +} +static void piConnectCall(PEER peer, piCallbackData * data) +{ + piConnectParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_CONNECT_CALLBACK); + + params = data->params; + ((peerConnectCallback)data->callback)(peer, data->success, params->failureReason, data->callbackParam); +} +void piAddConnectCallback +( + PEER peer, + PEERBool success, + int failureReason, + peerConnectCallback callback, + void * param, + int opID +) +{ + piConnectParams params; + + params.failureReason = failureReason; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_CONNECT_CALLBACK, ¶ms, sizeof(piConnectParams), opID); +} + +/* JoinRoom. +***********/ +typedef struct piJoinRoomParams +{ + PEERJoinResult result; + RoomType roomType; +} piJoinRoomParams; +static PEERBool piJoinRoomCopy(void * paramsOut_, void * paramsIn_) +{ + piJoinRoomParams * paramsOut = (piJoinRoomParams *)paramsOut_; + piJoinRoomParams * paramsIn = (piJoinRoomParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->result = paramsIn->result; + paramsOut->roomType = paramsIn->roomType; + + return PEERTrue; +} +static void piJoinRoomFree(void * params_) +{ + piJoinRoomParams * params = (piJoinRoomParams *)params_; + + assert(params); + GSI_UNUSED(params); +} +static void piJoinRoomCall(PEER peer, piCallbackData * data) +{ + piJoinRoomParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_JOIN_ROOM_CALLBACK); + + params = data->params; + ((peerJoinRoomCallback)(PEERCBType)data->callback)(peer, data->success, params->result, params->roomType, data->callbackParam); +} +void piAddJoinRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + peerJoinRoomCallback callback, + void * param, + int opID +) +{ + piJoinRoomParams params; + + // if this was called as part of an AutoMatch attempt, call the callback directly + // this is safe because we're only calling into Peer code + if(peerIsAutoMatching(peer) && (roomType == StagingRoom)) + { + callback(peer, success, result, roomType, param); + return; + } + + params.result = result; + params.roomType = roomType; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_JOIN_ROOM_CALLBACK, ¶ms, sizeof(piJoinRoomParams), opID); +} + +/* ListGroupsRooms. +******************/ +typedef struct piListGroupRoomsParams +{ + int groupID; + SBServer server; + char * name; + int numWaiting; + int maxWaiting; + int numGames; + int numPlaying; +} piListGroupRoomsParams; +static PEERBool piListGroupRoomsCopy(void * paramsOut_, void * paramsIn_) +{ + piListGroupRoomsParams * paramsOut = (piListGroupRoomsParams *)paramsOut_; + piListGroupRoomsParams * paramsIn = (piListGroupRoomsParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->groupID = paramsIn->groupID; + paramsOut->server = paramsIn->server; + paramsOut->numWaiting = paramsIn->numWaiting; + paramsOut->maxWaiting = paramsIn->maxWaiting; + paramsOut->numGames = paramsIn->numGames; + paramsOut->numPlaying = paramsIn->numPlaying; + if(paramsIn->name) + { + paramsOut->name = goastrdup(paramsIn->name); + if(paramsIn->name && !paramsOut->name) + return PEERFalse; + } + else + paramsOut->name = NULL; + + return PEERTrue; +} +static void piListGroupRoomsFree(void * params_) +{ + piListGroupRoomsParams * params = (piListGroupRoomsParams *)params_; + + assert(params); + + gsifree(params->name); +} +static void piListGroupRoomsCall(PEER peer, piCallbackData * data) +{ + piListGroupRoomsParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_LIST_GROUP_ROOMS_CALLBACK); + + params = data->params; + +#ifndef GSI_UNICODE + ((peerListGroupRoomsCallback)(PEERCBType)data->callback)(peer, data->success, params->groupID, params->server, params->name, params->numWaiting, params->maxWaiting, params->numGames, params->numPlaying, data->callbackParam); +#else + { + unsigned short* name_W = NULL; + if (params->name != NULL) + name_W = UTF8ToUCS2StringAlloc(params->name); + ((peerListGroupRoomsCallback)(int)data->callback)(peer, data->success, params->groupID, params->server, name_W, params->numWaiting, params->maxWaiting, params->numGames, params->numPlaying, data->callbackParam); + gsifree(name_W); + } +#endif +} +void piAddListGroupRoomsCallback +( + PEER peer, + PEERBool success, + int groupID, + SBServer server, + const char * name, + int numWaiting, + int maxWaiting, + int numGames, + int numPlaying, + peerListGroupRoomsCallback callback, + void * param, + int opID +) +{ + piListGroupRoomsParams params; + params.groupID = groupID; + params.server = server; + params.name = (char *)name; + params.numWaiting = numWaiting; + params.maxWaiting = maxWaiting; + params.numGames = numGames; + params.numPlaying = numPlaying; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_LIST_GROUP_ROOMS_CALLBACK, ¶ms, sizeof(piListGroupRoomsParams), opID); +} + +/* ListingGames. +***************/ +typedef struct piListingGamesParams +{ + char * name; + SBServer server; + PEERBool staging; + int msg; + int progress; +} piListingGamesParams; +static PEERBool piListingGamesCopy(void * paramsOut_, void * paramsIn_) +{ + piListingGamesParams * paramsOut = (piListingGamesParams *)paramsOut_; + piListingGamesParams * paramsIn = (piListingGamesParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + if(paramsIn->name) + { + paramsOut->name = goastrdup(paramsIn->name); + if(paramsIn->name && !paramsOut->name) + return PEERFalse; + } + else + paramsOut->name = NULL; + paramsOut->staging = paramsIn->staging; + paramsOut->server = paramsIn->server; + paramsOut->msg = paramsIn->msg; + paramsOut->progress = paramsIn->progress; + + return PEERTrue; +} +static void piListingGamesFree(void * params_) +{ + piListingGamesParams * params = (piListingGamesParams *)params_; + + assert(params); + + gsifree(params->name); +} +static void piListingGamesCall(PEER peer, piCallbackData * data) +{ + piListingGamesParams * params; + int len; + int i; + + PEER_CONNECTION; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_LISTING_GAMES_CALLBACK); + + params = data->params; + + // This is a bit of a hack. We don't want the server browser progess to show + // 100% until the last callback is being called. + if (params->progress == 100) + { + len = ArrayLength(connection->callbackList); + for(i = 0; i < len; i++) + { + // look for a listing games callback + piCallbackData* anotherData = (piCallbackData *)ArrayNth(connection->callbackList, i); + if(anotherData != data && anotherData->type == PI_LISTING_GAMES_CALLBACK) + { + piListingGamesParams* anotherParams = (piListingGamesParams *)anotherData->params; + if(anotherParams->msg == PEER_UPDATE) + { + // Another update callback will follow this one, + // limit the current callback's progress to 99% + params->progress = 99; + break; + } + } + } + } + +#ifndef GSI_UNICODE + ((peerListingGamesCallback)(PEERCBType)data->callback)(peer, data->success, params->name, params->server, params->staging, params->msg, params->progress, data->callbackParam); +#else + { + unsigned short* name_W = NULL; + if (params->name != NULL) + name_W = UTF8ToUCS2StringAlloc(params->name); + ((peerListingGamesCallback)(int)data->callback)(peer, data->success, name_W, params->server, params->staging, params->msg, params->progress, data->callbackParam); + gsifree(name_W); + } +#endif +} + +void piAddListingGamesCallback +( + PEER peer, + PEERBool success, + SBServer server, + int msg +) +{ + piListingGamesParams params; + const char * name; + const char * gamemode; + PEERBool staging; + int count; + int pendingCount; + int progress; + PEER_CONNECTION; + + // if this is a remove, remove any queued adds or updates + if(msg == PEER_REMOVE) + piClearListingGameServerCallbacks(peer, server); + + // get info from the server object + if(server) + { + name = SBServerGetStringValueA(server, "hostname", "(No Name)"); + gamemode = SBServerGetStringValueA(server, "gamemode", ""); + staging = (PEERBool)(strcasecmp(gamemode, "openstaging") == 0); + } + else + { + name = NULL; + staging = PEERFalse; + } + + // set the progress + if(connection->initialGameList) + { + count = SBServerListCount(&connection->gameList); + if(count) + { + pendingCount = (connection->gameEngine.querylist.count + connection->gameEngine.pendinglist.count); + progress = (((count - pendingCount) * 100) / count); + } + else + { + progress = 0; + } + } + else + { + progress = 100; + } + + params.name = (char *)name; + params.server = server; + params.staging = staging; + params.msg = msg; + params.progress = progress; + piAddCallback(peer, success, (PEERCBType)connection->gameListCallback, connection->gameListParam, PI_LISTING_GAMES_CALLBACK, ¶ms, sizeof(piListingGamesParams), -1); +} + +/* NickError. +*************/ +typedef struct piNickErrorParams +{ + int type; + char * nick; + int numSuggestedNicks; + char ** suggestedNicks; +} piNickErrorParams; +static PEERBool piNickErrorCopy(void * paramsOut_, void * paramsIn_) +{ + int i; + int num; + PEERBool success = PEERTrue; + piNickErrorParams * paramsOut = (piNickErrorParams *)paramsOut_; + piNickErrorParams * paramsIn = (piNickErrorParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + memset(paramsOut, 0, sizeof(piNickErrorParams)); + num = paramsOut->numSuggestedNicks = paramsIn->numSuggestedNicks; + + paramsOut->type = paramsIn->type; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + success = PEERFalse; + } + else + paramsOut->nick = NULL; + + if(success && num && paramsIn->suggestedNicks) + { + paramsOut->suggestedNicks = (char **)gsimalloc(sizeof(char *) * num); + if(!paramsOut->suggestedNicks) + success = PEERFalse; + else + memset(paramsOut->suggestedNicks, 0, sizeof(char *) * num); + + for(i = 0 ; success && (i < num) ; i++) + { + paramsOut->suggestedNicks[i] = goastrdup(paramsIn->suggestedNicks[i]); + if(!paramsOut->suggestedNicks[i]) + success = PEERFalse; + } + } + + if(!success) + { + gsifree(paramsOut->nick); + for(i = 0 ; i < num ; i++) + { + if(paramsOut->suggestedNicks) + gsifree(paramsOut->suggestedNicks[i]); + } + gsifree(paramsOut->suggestedNicks); + } + + return success; +} +static void piNickErrorFree(void * params_) +{ + int i; + piNickErrorParams * params = (piNickErrorParams *)params_; + + assert(params); + + gsifree(params->nick); + for(i = 0 ; i < params->numSuggestedNicks ; i++) + { + if(params->suggestedNicks) + gsifree(params->suggestedNicks[i]); + } + gsifree(params->suggestedNicks); +} +static void piNickErrorCall(PEER peer, piCallbackData * data) +{ + piNickErrorParams * params; + + //PEER_CONNECTION; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_NICK_ERROR_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerNickErrorCallback)(PEERCBType)data->callback)(peer, params->type, params->nick, params->numSuggestedNicks, (const char **)params->suggestedNicks, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short** suggestedNicks_W = UTF8ToUCS2StringArrayAlloc((const UTF8String *)params->suggestedNicks, params->numSuggestedNicks); + int i; + ((peerNickErrorCallback)data->callback)(peer, params->type, nick_W, params->numSuggestedNicks, (const unsigned short**)suggestedNicks_W, data->callbackParam); + gsifree(nick_W); + for (i=0; i < params->numSuggestedNicks; i++) + { + gsifree(suggestedNicks_W[i]); + } + gsifree(suggestedNicks_W); + } +#endif +} + +void piAddNickErrorCallback +( + PEER peer, + int type, + const char * nick, + int numSuggestedNicks, + const char ** suggestedNicks, + void * param, + int opID +) +{ + piNickErrorParams params; + + PEER_CONNECTION; + + params.type = type; + params.nick = (char *)nick; + params.numSuggestedNicks = numSuggestedNicks; + params.suggestedNicks = (char **)suggestedNicks; + piAddCallback(peer, PEERFalse, (PEERCBType)connection->nickErrorCallback, param, PI_NICK_ERROR_CALLBACK, ¶ms, sizeof(piNickErrorParams), opID); +} + +/* EnumPlayers. +**************/ +typedef struct piEnumPlayersParams +{ + RoomType roomType; + int index; + char * nick; + int flags; +} piEnumPlayersParams; +static PEERBool piEnumPlayersCopy(void * paramsOut_, void * paramsIn_) +{ + piEnumPlayersParams * paramsOut = (piEnumPlayersParams *)paramsOut_; + piEnumPlayersParams * paramsIn = (piEnumPlayersParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + paramsOut->index = paramsIn->index; + paramsOut->flags = paramsIn->flags; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(paramsIn->nick && !paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + + return PEERTrue; +} +static void piEnumPlayersFree(void * params_) +{ + piEnumPlayersParams * params = (piEnumPlayersParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piEnumPlayersCall(PEER peer, piCallbackData * data) +{ + piEnumPlayersParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_ENUM_PLAYERS_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerEnumPlayersCallback)data->callback)(peer, data->success, params->roomType, params->index, params->nick, params->flags, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerEnumPlayersCallback)data->callback)(peer, data->success, params->roomType, params->index, nick_W, params->flags, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddEnumPlayersCallback +( + PEER peer, + PEERBool success, + RoomType roomType, + int index, + const char * nick, + int flags, + peerEnumPlayersCallback callback, + void * param, + int opID +) +{ + piEnumPlayersParams params; + params.roomType = roomType; + params.index = index; + params.nick = (char *)nick; + params.flags = flags; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_ENUM_PLAYERS_CALLBACK, ¶ms, sizeof(piEnumPlayersParams), opID); +} + +/* GetPlayerInfo. +****************/ +typedef struct piGetPlayerInfoParams +{ + char * nick; + unsigned int IP; + int profileID; +} piGetPlayerInfoParams; +static PEERBool piGetPlayerInfoCopy(void * paramsOut_, void * paramsIn_) +{ + piGetPlayerInfoParams * paramsOut = (piGetPlayerInfoParams *)paramsOut_; + piGetPlayerInfoParams * paramsIn = (piGetPlayerInfoParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + paramsOut->IP = paramsIn->IP; + paramsOut->profileID = paramsIn->profileID; + + return PEERTrue; +} +static void piGetPlayerInfoFree(void * params_) +{ + piGetPlayerInfoParams * params = (piGetPlayerInfoParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piGetPlayerInfoCall(PEER peer, piCallbackData * data) +{ + piGetPlayerInfoParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_GET_PLAYER_INFO_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerGetPlayerInfoCallback)data->callback)(peer, data->success, params->nick, params->IP, params->profileID, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerGetPlayerInfoCallback)data->callback)(peer, data->success, nick_W, params->IP, params->profileID, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddGetPlayerInfoCallback +( + PEER peer, + PEERBool success, + const char * nick, + unsigned int IP, + int profileID, + peerGetPlayerInfoCallback callback, + void * param, + int opID +) +{ + piGetPlayerInfoParams params; + params.nick = (char *)nick; + params.IP = IP; + params.profileID = profileID; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_GET_PLAYER_INFO_CALLBACK, ¶ms, sizeof(piGetPlayerInfoParams), opID); +} + +/* GetPlayerProfileID. +*********************/ +typedef struct piGetPlayerProfileIDParams +{ + char * nick; + int profileID; +} piGetPlayerProfileIDParams; +static PEERBool piGetPlayerProfileIDCopy(void * paramsOut_, void * paramsIn_) +{ + piGetPlayerProfileIDParams * paramsOut = (piGetPlayerProfileIDParams *)paramsOut_; + piGetPlayerProfileIDParams * paramsIn = (piGetPlayerProfileIDParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + paramsOut->profileID = paramsIn->profileID; + + return PEERTrue; +} +static void piGetPlayerProfileIDFree(void * params_) +{ + piGetPlayerProfileIDParams * params = (piGetPlayerProfileIDParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piGetPlayerProfileIDCall(PEER peer, piCallbackData * data) +{ + piGetPlayerProfileIDParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_GET_PLAYER_PROFILE_ID_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerGetPlayerProfileIDCallback)data->callback)(peer, data->success, params->nick, params->profileID, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerGetPlayerProfileIDCallback)data->callback)(peer, data->success, nick_W, params->profileID, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddGetPlayerProfileIDCallback +( + PEER peer, + PEERBool success, + const char * nick, + int profileID, + peerGetPlayerProfileIDCallback callback, + void * param, + int opID +) +{ + piGetPlayerProfileIDParams params; + params.nick = (char *)nick; + params.profileID = profileID; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_GET_PLAYER_PROFILE_ID_CALLBACK, ¶ms, sizeof(piGetPlayerProfileIDParams), opID); +} + +/* GetPlayerIP. +**************/ +typedef struct piGetPlayerIPParams +{ + char * nick; + unsigned int IP; +} piGetPlayerIPParams; +static PEERBool piGetPlayerIPCopy(void * paramsOut_, void * paramsIn_) +{ + piGetPlayerIPParams * paramsOut = (piGetPlayerIPParams *)paramsOut_; + piGetPlayerIPParams * paramsIn = (piGetPlayerIPParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->IP = paramsIn->IP; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + + return PEERTrue; +} +static void piGetPlayerIPFree(void * params_) +{ + piGetPlayerIPParams * params = (piGetPlayerIPParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piGetPlayerIPCall(PEER peer, piCallbackData * data) +{ + piGetPlayerIPParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_GET_PLAYER_IP_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerGetPlayerIPCallback)data->callback)(peer, data->success, params->nick, params->IP, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerGetPlayerIPCallback)data->callback)(peer, data->success, nick_W, params->IP, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddGetPlayerIPCallback +( + PEER peer, + PEERBool success, + const char * nick, + unsigned int IP, + peerGetPlayerIPCallback callback, + void * param, + int opID +) +{ + piGetPlayerIPParams params; + params.nick = (char *)nick; + params.IP = IP; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_GET_PLAYER_IP_CALLBACK, ¶ms, sizeof(piGetPlayerIPParams), opID); +} + +/* Room Message. +***************/ +typedef struct piRoomMessageParams +{ + RoomType roomType; + char * nick; + char * message; + MessageType messageType; +} piRoomMessageParams; +static PEERBool piRoomMessageCopy(void * paramsOut_, void * paramsIn_) +{ + piRoomMessageParams * paramsOut = (piRoomMessageParams *)paramsOut_; + piRoomMessageParams * paramsIn = (piRoomMessageParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + paramsOut->messageType = paramsIn->messageType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + if(paramsIn->message) + { + paramsOut->message = goastrdup(paramsIn->message); + if(!paramsOut->message) + { + gsifree(paramsOut->nick); + return PEERFalse; + } + } + else + paramsOut->message = NULL; + + return PEERTrue; +} +static void piRoomMessageFree(void * params_) +{ + piRoomMessageParams * params = (piRoomMessageParams *)params_; + + assert(params); + + gsifree(params->nick); + gsifree(params->message); +} +static void piRoomMessageCall(PEER peer, piCallbackData * data) +{ + piRoomMessageParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_ROOM_MESSAGE_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerRoomMessageCallback)data->callback)(peer, params->roomType, params->nick, params->message, params->messageType, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short* message_W = UTF8ToUCS2StringAlloc(params->message); + + ((peerRoomMessageCallback)data->callback)(peer, params->roomType, nick_W, message_W, params->messageType, data->callbackParam); + gsifree(nick_W); + gsifree(message_W); + } +#endif +} +void piAddRoomMessageCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * message, + MessageType messageType +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->roomMessage) + { + piRoomMessageParams params; + params.roomType = roomType; + params.nick = (char *)nick; + params.message = (char *)message; + params.messageType = messageType; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->roomMessage, callbacks->param, PI_ROOM_MESSAGE_CALLBACK, ¶ms, sizeof(piRoomMessageParams), -1); + } +} + +/* Room UTM. +***********/ +typedef struct piRoomUTMParams +{ + RoomType roomType; + char * nick; + char * command; + char * parameters; + PEERBool authenticated; +} piRoomUTMParams; +static PEERBool piRoomUTMCopy(void * paramsOut_, void * paramsIn_) +{ + piRoomUTMParams * paramsOut = (piRoomUTMParams *)paramsOut_; + piRoomUTMParams * paramsIn = (piRoomUTMParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->authenticated = paramsIn->authenticated; + paramsOut->roomType = paramsIn->roomType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + if(paramsIn->command) + { + paramsOut->command = goastrdup(paramsIn->command); + if(!paramsOut->command) + { + gsifree(paramsOut->nick); + return PEERFalse; + } + } + else + paramsOut->command = NULL; + if(paramsIn->parameters) + { + paramsOut->parameters = goastrdup(paramsIn->parameters); + if(!paramsOut->parameters) + { + gsifree(paramsOut->nick); + gsifree(paramsOut->command); + return PEERFalse; + } + } + else + paramsOut->parameters = NULL; + + return PEERTrue; +} +static void piRoomUTMFree(void * params_) +{ + piRoomUTMParams * params = (piRoomUTMParams *)params_; + + assert(params); + + gsifree(params->nick); + gsifree(params->command); + gsifree(params->parameters); +} +static void piRoomUTMCall(PEER peer, piCallbackData * data) +{ + piRoomUTMParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_ROOM_UTM_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerRoomUTMCallback)data->callback)(peer, params->roomType, params->nick, params->command, params->parameters, params->authenticated, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short* command_W = UTF8ToUCS2StringAlloc(params->command); + unsigned short* parameters_W = UTF8ToUCS2StringAlloc(params->parameters); + ((peerRoomUTMCallback)data->callback)(peer, params->roomType, nick_W, command_W, parameters_W, params->authenticated, data->callbackParam); + gsifree(nick_W); + gsifree(command_W); + gsifree(parameters_W); + } +#endif +} +void piAddRoomUTMCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * command, + const char * parameters, + PEERBool authenticated +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->roomUTM) + { + piRoomUTMParams params; + params.roomType = roomType; + params.nick = (char *)nick; + params.command = (char *)command; + params.parameters = (char *)parameters; + params.authenticated = authenticated; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->roomUTM, callbacks->param, PI_ROOM_UTM_CALLBACK, ¶ms, sizeof(piRoomUTMParams), -1); + } +} + +/* Room Name Changed. +********************/ +typedef struct piRoomNameChangedParams +{ + RoomType roomType; +} piRoomNameChangedParams; +static PEERBool piRoomNameChangedCopy(void * paramsOut_, void * paramsIn_) +{ + piRoomNameChangedParams * paramsOut = (piRoomNameChangedParams *)paramsOut_; + piRoomNameChangedParams * paramsIn = (piRoomNameChangedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + + return PEERTrue; +} +static void piRoomNameChangedFree(void * params_) +{ + piRoomNameChangedParams * params = (piRoomNameChangedParams *)params_; + + assert(params); + GSI_UNUSED(params); +} +static void piRoomNameChangedCall(PEER peer, piCallbackData * data) +{ + piRoomNameChangedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_ROOM_NAME_CHANGED_CALLBACK); + + params = data->params; + ((peerRoomNameChangedCallback)data->callback)(peer, params->roomType, data->callbackParam); +} +void piAddRoomNameChangedCallback +( + PEER peer, + RoomType roomType +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->roomNameChanged) + { + piRoomNameChangedParams params; + params.roomType = roomType; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->roomNameChanged, callbacks->param, PI_ROOM_NAME_CHANGED_CALLBACK, ¶ms, sizeof(piRoomNameChangedParams), -1); + } +} + +/* Room Mode Changed. +********************/ +typedef struct piRoomModeChangedParams +{ + RoomType roomType; + CHATChannelMode mode; +} piRoomModeChangedParams; +static PEERBool piRoomModeChangedCopy(void * paramsOut_, void * paramsIn_) +{ + piRoomModeChangedParams * paramsOut = (piRoomModeChangedParams *)paramsOut_; + piRoomModeChangedParams * paramsIn = (piRoomModeChangedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + paramsOut->mode = paramsIn->mode; + + return PEERTrue; +} +static void piRoomModeChangedFree(void * params_) +{ + piRoomModeChangedParams * params = (piRoomModeChangedParams *)params_; + + assert(params); + GSI_UNUSED(params); +} +static void piRoomModeChangedCall(PEER peer, piCallbackData * data) +{ + piRoomModeChangedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_ROOM_MODE_CHANGED_CALLBACK); + + params = data->params; + ((peerRoomModeChangedCallback)data->callback)(peer, params->roomType, ¶ms->mode, data->callbackParam); +} +void piAddRoomModeChangedCallback +( + PEER peer, + RoomType roomType, + CHATChannelMode * mode +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->roomModeChanged) + { + piRoomModeChangedParams params; + params.roomType = roomType; + params.mode = *mode; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->roomModeChanged, callbacks->param, PI_ROOM_MODE_CHANGED_CALLBACK, ¶ms, sizeof(piRoomModeChangedParams), -1); + } +} + +/* Player Message. +*****************/ +typedef struct piPlayerMessageParams +{ + char * nick; + char * message; + MessageType messageType; +} piPlayerMessageParams; +static PEERBool piPlayerMessageCopy(void * paramsOut_, void * paramsIn_) +{ + piPlayerMessageParams * paramsOut = (piPlayerMessageParams *)paramsOut_; + piPlayerMessageParams * paramsIn = (piPlayerMessageParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->messageType = paramsIn->messageType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + if(paramsIn->message) + { + paramsOut->message = goastrdup(paramsIn->message); + if(!paramsOut->message) + { + gsifree(paramsOut->nick); + return PEERFalse; + } + } + else + paramsOut->message = NULL; + + return PEERTrue; +} +static void piPlayerMessageFree(void * params_) +{ + piPlayerMessageParams * params = (piPlayerMessageParams *)params_; + + assert(params); + + gsifree(params->nick); + gsifree(params->message); +} +static void piPlayerMessageCall(PEER peer, piCallbackData * data) +{ + piPlayerMessageParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_PLAYER_MESSAGE_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerPlayerMessageCallback)data->callback)(peer, params->nick, params->message, params->messageType, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short* message_W = UTF8ToUCS2StringAlloc(params->message); + ((peerPlayerMessageCallback)data->callback)(peer, nick_W, message_W, params->messageType, data->callbackParam); + gsifree(nick_W); + gsifree(message_W); + } +#endif +} +void piAddPlayerMessageCallback +( + PEER peer, + const char * nick, + const char * message, + MessageType messageType +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->playerMessage) + { + piPlayerMessageParams params; + params.nick = (char *)nick; + params.message = (char *)message; + params.messageType = messageType; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->playerMessage, callbacks->param, PI_PLAYER_MESSAGE_CALLBACK, ¶ms, sizeof(piPlayerMessageParams), -1); + } +} + +/* Player UTM. +*************/ +typedef struct piPlayerUTMParams +{ + char * nick; + char * command; + char * parameters; + PEERBool authenticated; +} piPlayerUTMParams; +static PEERBool piPlayerUTMCopy(void * paramsOut_, void * paramsIn_) +{ + piPlayerUTMParams * paramsOut = (piPlayerUTMParams *)paramsOut_; + piPlayerUTMParams * paramsIn = (piPlayerUTMParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->authenticated = paramsIn->authenticated; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + if(paramsIn->command) + { + paramsOut->command = goastrdup(paramsIn->command); + if(!paramsOut->command) + { + gsifree(paramsOut->nick); + return PEERFalse; + } + } + else + paramsOut->command = NULL; + if(paramsIn->parameters) + { + paramsOut->parameters = goastrdup(paramsIn->parameters); + if(!paramsOut->parameters) + { + gsifree(paramsOut->nick); + gsifree(paramsOut->command); + return PEERFalse; + } + } + else + paramsOut->parameters = NULL; + + return PEERTrue; +} +static void piPlayerUTMFree(void * params_) +{ + piPlayerUTMParams * params = (piPlayerUTMParams *)params_; + + assert(params); + + gsifree(params->nick); + gsifree(params->command); + gsifree(params->parameters); +} +static void piPlayerUTMCall(PEER peer, piCallbackData * data) +{ + piPlayerUTMParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_PLAYER_UTM_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerPlayerUTMCallback)data->callback)(peer, params->nick, params->command, params->parameters, params->authenticated, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short* command_W = UTF8ToUCS2StringAlloc(params->command); + unsigned short* parameters_W = UTF8ToUCS2StringAlloc(params->parameters); + ((peerPlayerUTMCallback)data->callback)(peer, nick_W, command_W, parameters_W, params->authenticated, data->callbackParam); + gsifree(nick_W); + gsifree(command_W); + gsifree(parameters_W); + } +#endif +} +void piAddPlayerUTMCallback +( + PEER peer, + const char * nick, + const char * command, + const char * parameters, + PEERBool authenticated +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->playerUTM) + { + piPlayerUTMParams params; + params.nick = (char *)nick; + params.command = (char *)command; + params.parameters = (char *)parameters; + params.authenticated = authenticated; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->playerUTM, callbacks->param, PI_PLAYER_UTM_CALLBACK, ¶ms, sizeof(piPlayerUTMParams), -1); + } +} + +/* Ready Changed. +****************/ +typedef struct piReadyChangedParams +{ + char * nick; + PEERBool ready; +} piReadyChangedParams; +static PEERBool piReadyChangedCopy(void * paramsOut_, void * paramsIn_) +{ + piReadyChangedParams * paramsOut = (piReadyChangedParams *)paramsOut_; + piReadyChangedParams * paramsIn = (piReadyChangedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + paramsOut->ready = paramsIn->ready; + + return PEERTrue; +} +static void piReadyChangedFree(void * params_) +{ + piReadyChangedParams * params = (piReadyChangedParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piReadyChangedCall(PEER peer, piCallbackData * data) +{ + piReadyChangedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_READY_CHANGED_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerReadyChangedCallback)data->callback)(peer, params->nick, params->ready, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerReadyChangedCallback)data->callback)(peer, nick_W, params->ready, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddReadyChangedCallback +( + PEER peer, + const char * nick, + PEERBool ready +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->readyChanged) + { + piReadyChangedParams params; + params.nick = (char *)nick; + params.ready = ready; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->readyChanged, callbacks->param, PI_READY_CHANGED_CALLBACK, ¶ms, sizeof(piReadyChangedParams), -1); + } +} + +/* GameStarted. +**************/ +typedef struct piGameStartedParams +{ + SBServer server; + char * message; +} piGameStartedParams; +static PEERBool piGameStartedCopy(void * paramsOut_, void * paramsIn_) +{ + piGameStartedParams * paramsOut = (piGameStartedParams *)paramsOut_; + piGameStartedParams * paramsIn = (piGameStartedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->server = paramsIn->server; + if(paramsIn->message) + { + paramsOut->message = goastrdup(paramsIn->message); + if(!paramsOut->message) + return PEERFalse; + } + else + paramsOut->message = NULL; + + return PEERTrue; +} +static void piGameStartedFree(void * params_) +{ + piGameStartedParams * params = (piGameStartedParams *)params_; + + assert(params); + + gsifree(params->message); +} +static void piGameStartedCall(PEER peer, piCallbackData * data) +{ + piGameStartedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_GAME_STARTED_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerGameStartedCallback)data->callback)(peer, params->server, params->message, data->callbackParam); +#else + { + unsigned short* message_W = UTF8ToUCS2StringAlloc(params->message); + ((peerGameStartedCallback)data->callback)(peer, params->server, message_W, data->callbackParam); + gsifree(message_W); + } +#endif +} +void piAddGameStartedCallback +( + PEER peer, + SBServer server, + const char * message +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->gameStarted) + { + piGameStartedParams params; + params.server = server; + params.message = (char *)message; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->gameStarted, callbacks->param, PI_GAME_STARTED_CALLBACK, ¶ms, sizeof(piGameStartedParams), -1); + } +} + +/* Player Joined. +****************/ +typedef struct piPlayerJoinedParams +{ + RoomType roomType; + char * nick; +} piPlayerJoinedParams; +static PEERBool piPlayerJoinedCopy(void * paramsOut_, void * paramsIn_) +{ + piPlayerJoinedParams * paramsOut = (piPlayerJoinedParams *)paramsOut_; + piPlayerJoinedParams * paramsIn = (piPlayerJoinedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + + return PEERTrue; +} +static void piPlayerJoinedFree(void * params_) +{ + piPlayerJoinedParams * params = (piPlayerJoinedParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piPlayerJoinedCall(PEER peer, piCallbackData * data) +{ + piPlayerJoinedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_PLAYER_JOINED_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerPlayerJoinedCallback)data->callback)(peer, params->roomType, params->nick, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerPlayerJoinedCallback)data->callback)(peer, params->roomType, nick_W, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddPlayerJoinedCallback +( + PEER peer, + RoomType roomType, + const char * nick +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->playerJoined) + { + piPlayerJoinedParams params; + params.roomType = roomType; + params.nick = (char *)nick; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->playerJoined, callbacks->param, PI_PLAYER_JOINED_CALLBACK, ¶ms, sizeof(piPlayerJoinedParams), -1); + } +} + +/* Player Left. +**************/ +typedef struct piPlayerLeftParams +{ + RoomType roomType; + char * nick; + char * reason; +} piPlayerLeftParams; +static PEERBool piPlayerLeftCopy(void * paramsOut_, void * paramsIn_) +{ + piPlayerLeftParams * paramsOut = (piPlayerLeftParams *)paramsOut_; + piPlayerLeftParams * paramsIn = (piPlayerLeftParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + if(paramsIn->reason) + { + paramsOut->reason = goastrdup(paramsIn->reason); + if(!paramsOut->reason) + { + gsifree(paramsOut->nick); + return PEERFalse; + } + } + else + paramsOut->reason = NULL; + + return PEERTrue; +} +static void piPlayerLeftFree(void * params_) +{ + piPlayerLeftParams * params = (piPlayerLeftParams *)params_; + + assert(params); + + gsifree(params->nick); + gsifree(params->reason); +} +static void piPlayerLeftCall(PEER peer, piCallbackData * data) +{ + piPlayerLeftParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_PLAYER_LEFT_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerPlayerLeftCallback)data->callback)(peer, params->roomType, params->nick, params->reason, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short* reason_W = UTF8ToUCS2StringAlloc(params->reason); + ((peerPlayerLeftCallback)data->callback)(peer, params->roomType, nick_W, reason_W, data->callbackParam); + gsifree(nick_W); + gsifree(reason_W); + } +#endif +} +void piAddPlayerLeftCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * reason +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->playerLeft) + { + piPlayerLeftParams params; + params.roomType = roomType; + params.nick = (char *)nick; + params.reason = (char *)reason; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->playerLeft, callbacks->param, PI_PLAYER_LEFT_CALLBACK, ¶ms, sizeof(piPlayerLeftParams), -1); + } +} + +/* Kicked. +*********/ +typedef struct piKickedParams +{ + RoomType roomType; + char * nick; + char * reason; +} piKickedParams; +static PEERBool piKickedCopy(void * paramsOut_, void * paramsIn_) +{ + piKickedParams * paramsOut = (piKickedParams *)paramsOut_; + piKickedParams * paramsIn = (piKickedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + if(paramsIn->reason) + { + paramsOut->reason = goastrdup(paramsIn->reason); + if(!paramsOut->reason) + { + gsifree(paramsOut->nick); + return PEERFalse; + } + } + else + paramsOut->reason = NULL; + + return PEERTrue; +} +static void piKickedFree(void * params_) +{ + piKickedParams * params = (piKickedParams *)params_; + + assert(params); + + gsifree(params->nick); + gsifree(params->reason); +} +static void piKickedCall(PEER peer, piCallbackData * data) +{ + piKickedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_KICKED_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerKickedCallback)data->callback)(peer, params->roomType, params->nick, params->reason, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short* reason_W = UTF8ToUCS2StringAlloc(params->reason); + ((peerKickedCallback)data->callback)(peer, params->roomType, nick_W, reason_W, data->callbackParam); + gsifree(nick_W); + gsifree(reason_W); + } +#endif +} +void piAddKickedCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * reason +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->kicked) + { + piKickedParams params; + params.roomType = roomType; + params.nick = (char *)nick; + params.reason = (char *)reason; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->kicked, callbacks->param, PI_KICKED_CALLBACK, ¶ms, sizeof(piKickedParams), -1); + } +} + +/* New Player List. +******************/ +typedef struct piNewPlayerListParams +{ + RoomType roomType; +} piNewPlayerListParams; +static PEERBool piNewPlayerListCopy(void * paramsOut_, void * paramsIn_) +{ + piNewPlayerListParams * paramsOut = (piNewPlayerListParams *)paramsOut_; + piNewPlayerListParams * paramsIn = (piNewPlayerListParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + + return PEERTrue; +} +static void piNewPlayerListFree(void * params_) +{ + piNewPlayerListParams * params = (piNewPlayerListParams *)params_; + + assert(params); + GSI_UNUSED(params); +} +static void piNewPlayerListCall(PEER peer, piCallbackData * data) +{ + piNewPlayerListParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_NEW_PLAYER_LIST_CALLBACK); + + params = data->params; + ((peerNewPlayerListCallback)data->callback)(peer, params->roomType, data->callbackParam); +} +void piAddNewPlayerListCallback +( + PEER peer, + RoomType roomType +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->newPlayerList) + { + piNewPlayerListParams params; + params.roomType = roomType; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->newPlayerList, callbacks->param, PI_NEW_PLAYER_LIST_CALLBACK, ¶ms, sizeof(piNewPlayerListParams), -1); + } +} + +/* Player Changed Nick. +**********************/ +typedef struct piPlayerChangedNickParams +{ + RoomType roomType; + char * oldNick; + char * newNick; +} piPlayerChangedNickParams; +static PEERBool piPlayerChangedNickCopy(void * paramsOut_, void * paramsIn_) +{ + piPlayerChangedNickParams * paramsOut = (piPlayerChangedNickParams *)paramsOut_; + piPlayerChangedNickParams * paramsIn = (piPlayerChangedNickParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + if(paramsIn->oldNick) + { + paramsOut->oldNick = goastrdup(paramsIn->oldNick); + if(!paramsOut->oldNick) + return PEERFalse; + } + else + paramsOut->oldNick = NULL; + if(paramsIn->newNick) + { + paramsOut->newNick = goastrdup(paramsIn->newNick); + if(!paramsOut->newNick) + { + gsifree(paramsOut->oldNick); + return PEERFalse; + } + } + else + paramsOut->newNick = NULL; + + return PEERTrue; +} +static void piPlayerChangedNickFree(void * params_) +{ + piPlayerChangedNickParams * params = (piPlayerChangedNickParams *)params_; + + assert(params); + + gsifree(params->oldNick); + gsifree(params->newNick); +} +static void piPlayerChangedNickCall(PEER peer, piCallbackData * data) +{ + piPlayerChangedNickParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_PLAYER_CHANGED_NICK_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerPlayerChangedNickCallback)data->callback)(peer, params->roomType, params->oldNick, params->newNick, data->callbackParam); +#else + { + unsigned short* oldNick_W = UTF8ToUCS2StringAlloc(params->oldNick); + unsigned short* newNick_W = UTF8ToUCS2StringAlloc(params->newNick); + ((peerPlayerChangedNickCallback)data->callback)(peer, params->roomType, oldNick_W, newNick_W, data->callbackParam); + gsifree(oldNick_W); + gsifree(newNick_W); + } +#endif +} +void piAddPlayerChangedNickCallback +( + PEER peer, + RoomType roomType, + const char * oldNick, + const char * newNick +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->playerChangedNick) + { + piPlayerChangedNickParams params; + params.roomType = roomType; + params.oldNick = (char *)oldNick; + params.newNick = (char *)newNick; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->playerChangedNick, callbacks->param, PI_PLAYER_CHANGED_NICK_CALLBACK, ¶ms, sizeof(piPlayerChangedNickParams), -1); + } +} + +/* Player Info. +**************/ +typedef struct piPlayerInfoParams +{ + RoomType roomType; + char * nick; + unsigned int IP; + int profileID; +} piPlayerInfoParams; +static PEERBool piPlayerInfoCopy(void * paramsOut_, void * paramsIn_) +{ + piPlayerInfoParams * paramsOut = (piPlayerInfoParams *)paramsOut_; + piPlayerInfoParams * paramsIn = (piPlayerInfoParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + paramsOut->IP = paramsIn->IP; + paramsOut->profileID = paramsIn->profileID; + + return PEERTrue; +} +static void piPlayerInfoFree(void * params_) +{ + piPlayerInfoParams * params = (piPlayerInfoParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piPlayerInfoCall(PEER peer, piCallbackData * data) +{ + piPlayerInfoParams * params; + PEER_CONNECTION; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_PLAYER_INFO_CALLBACK); + + params = data->params; + + // Don't call this if we're not in the room anymore. + //////////////////////////////////////////////////// + if(!connection->inRoom[params->roomType]) + return; + +#ifndef GSI_UNICODE + ((peerPlayerInfoCallback)data->callback)(peer, params->roomType, params->nick, params->IP, params->profileID, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerPlayerInfoCallback)data->callback)(peer, params->roomType, nick_W, params->IP, params->profileID, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddPlayerInfoCallback +( + PEER peer, + RoomType roomType, + const char * nick, + unsigned int IP, + int profileID +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->playerInfo) + { + piPlayerInfoParams params; + params.roomType = roomType; + params.nick = (char *)nick; + params.IP = IP; + params.profileID = profileID; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->playerInfo, callbacks->param, PI_PLAYER_INFO_CALLBACK, ¶ms, sizeof(piPlayerInfoParams), -1); + } +} + +/* Disconnected. +***************/ +typedef struct piDisconnectedParams +{ + char * reason; +} piDisconnectedParams; +static PEERBool piDisconnectedCopy(void * paramsOut_, void * paramsIn_) +{ + piDisconnectedParams * paramsOut = (piDisconnectedParams *)paramsOut_; + piDisconnectedParams * paramsIn = (piDisconnectedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + if(paramsIn->reason) + { + paramsOut->reason = goastrdup(paramsIn->reason); + if(!paramsOut->reason) + return PEERFalse; + } + else + paramsOut->reason = NULL; + + return PEERTrue; +} +static void piDisconnectedFree(void * params_) +{ + piDisconnectedParams * params = (piDisconnectedParams *)params_; + + assert(params); + + gsifree(params->reason); +} +static void piDisconnectedCall(PEER peer, piCallbackData * data) +{ + piDisconnectedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_DISCONNECTED_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerDisconnectedCallback)data->callback)(peer, params->reason, data->callbackParam); +#else + { + unsigned short* reason_W = UTF8ToUCS2StringAlloc(params->reason); + ((peerDisconnectedCallback)data->callback)(peer, reason_W, data->callbackParam); + gsifree(reason_W); + } +#endif +} +void piAddDisconnectedCallback +( + PEER peer, + const char * reason +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->disconnected) + { + piDisconnectedParams params; + params.reason = (char *)reason; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->disconnected, callbacks->param, PI_DISCONNECTED_CALLBACK, ¶ms, sizeof(piDisconnectedParams), -1); + } +} + +/* Ping. +*******/ +typedef struct piPingParams +{ + char * nick; + int ping; +} piPingParams; +static PEERBool piPingCopy(void * paramsOut_, void * paramsIn_) +{ + piPingParams * paramsOut = (piPingParams *)paramsOut_; + piPingParams * paramsIn = (piPingParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->ping = paramsIn->ping; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + + return PEERTrue; +} +static void piPingFree(void * params_) +{ + piPingParams * params = (piPingParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piPingCall(PEER peer, piCallbackData * data) +{ + piPingParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_PING_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerPingCallback)data->callback)(peer, params->nick, params->ping, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerPingCallback)data->callback)(peer, nick_W, params->ping, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddPingCallback +( + PEER peer, + const char * nick, + int ping +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->ping) + { + piPingParams params; + params.nick = (char *)nick; + params.ping = ping; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->ping, callbacks->param, PI_PING_CALLBACK, ¶ms, sizeof(piPingParams), -1); + } +} + +/* CrossPing. +************/ +typedef struct piCrossPingParams +{ + char * nick1; + char * nick2; + int crossPing; +} piCrossPingParams; +static PEERBool piCrossPingCopy(void * paramsOut_, void * paramsIn_) +{ + piCrossPingParams * paramsOut = (piCrossPingParams *)paramsOut_; + piCrossPingParams * paramsIn = (piCrossPingParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->crossPing = paramsIn->crossPing; + if(paramsIn->nick1) + { + paramsOut->nick1 = goastrdup(paramsIn->nick1); + if(!paramsOut->nick1) + return PEERFalse; + } + else + paramsOut->nick1 = NULL; + if(paramsIn->nick2) + { + paramsOut->nick2 = goastrdup(paramsIn->nick2); + if(!paramsOut->nick2) + { + gsifree(paramsOut->nick1); + return PEERFalse; + } + } + else + paramsOut->nick2 = NULL; + + return PEERTrue; +} +static void piCrossPingFree(void * params_) +{ + piCrossPingParams * params = (piCrossPingParams *)params_; + + assert(params); + + gsifree(params->nick1); + gsifree(params->nick2); +} +static void piCrossPingCall(PEER peer, piCallbackData * data) +{ + piCrossPingParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_CROSS_PING_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerCrossPingCallback)data->callback)(peer, params->nick1, params->nick2, params->crossPing, data->callbackParam); +#else + { + unsigned short* nick1_W = UTF8ToUCS2StringAlloc(params->nick1); + unsigned short* nick2_W = UTF8ToUCS2StringAlloc(params->nick2); + ((peerCrossPingCallback)data->callback)(peer, nick1_W, nick2_W, params->crossPing, data->callbackParam); + gsifree(nick1_W); + gsifree(nick2_W); + } +#endif +} +void piAddCrossPingCallback +( + PEER peer, + const char * nick1, + const char * nick2, + int crossPing +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->crossPing) + { + piCrossPingParams params; + params.nick1 = (char *)nick1; + params.nick2 = (char *)nick2; + params.crossPing = crossPing; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->crossPing, callbacks->param, PI_CROSS_PING_CALLBACK, ¶ms, sizeof(piCrossPingParams), -1); + } +} + +/* Change Nick. +**************/ +typedef struct piChangeNickParams +{ + char * oldNick; + char * newNick; +} piChangeNickParams; +static PEERBool piChangeNickCopy(void * paramsOut_, void * paramsIn_) +{ + piChangeNickParams * paramsOut = (piChangeNickParams *)paramsOut_; + piChangeNickParams * paramsIn = (piChangeNickParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + if(paramsIn->newNick) + { + paramsOut->newNick = goastrdup(paramsIn->newNick); + if(!paramsOut->newNick) + return PEERFalse; + } + else + paramsOut->newNick = NULL; + if(paramsIn->oldNick) + { + paramsOut->oldNick = goastrdup(paramsIn->oldNick); + if(!paramsOut->oldNick) + { + gsifree(paramsOut->newNick); + return PEERFalse; + } + } + else + paramsOut->oldNick = NULL; + + return PEERTrue; +} +static void piChangeNickFree(void * params_) +{ + piChangeNickParams * params = (piChangeNickParams *)params_; + + assert(params); + + gsifree(params->newNick); + gsifree(params->oldNick); +} +static void piChangeNickCall(PEER peer, piCallbackData * data) +{ + piChangeNickParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_CHANGE_NICK_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerChangeNickCallback)data->callback)(peer, data->success, params->oldNick, params->newNick, data->callbackParam); +#else + { + unsigned short* oldNick_W = UTF8ToUCS2StringAlloc(params->oldNick); + unsigned short* newNick_W = UTF8ToUCS2StringAlloc(params->newNick); + ((peerChangeNickCallback)data->callback)(peer, data->success, oldNick_W, newNick_W, data->callbackParam); + gsifree(oldNick_W); + gsifree(newNick_W); + } +#endif +} +void piAddChangeNickCallback +( + PEER peer, + PEERBool success, + const char * oldNick, + const char * newNick, + peerChangeNickCallback callback, + void * param, + int opID +) +{ + piChangeNickParams params; + params.newNick = (char *)newNick; + params.oldNick = (char *)oldNick; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_CHANGE_NICK_CALLBACK, ¶ms, sizeof(piChangeNickParams), opID); +} + +/* GlobalKeyChanged. +*******************/ +typedef struct piGlobalGlobalKeyChangedParams +{ + RoomType roomType; + char * nick; + char * key; + char * value; +} piGlobalKeyChangedParams; +static PEERBool piGlobalKeyChangedCopy(void * paramsOut_, void * paramsIn_) +{ + piGlobalKeyChangedParams * paramsOut = (piGlobalKeyChangedParams *)paramsOut_; + piGlobalKeyChangedParams * paramsIn = (piGlobalKeyChangedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + if(paramsIn->key) + { + paramsOut->key = goastrdup(paramsIn->key); + if(!paramsOut->key) + { + gsifree(paramsOut->nick); + return PEERFalse; + } + } + else + paramsOut->key = NULL; + if(paramsIn->value) + { + paramsOut->value = goastrdup(paramsIn->value); + if(!paramsOut->value) + { + gsifree(paramsOut->nick); + gsifree(paramsOut->key); + return PEERFalse; + } + } + else + paramsOut->value = NULL; + + return PEERTrue; +} +static void piGlobalKeyChangedFree(void * params_) +{ + piGlobalKeyChangedParams * params = (piGlobalKeyChangedParams *)params_; + + assert(params); + + gsifree(params->nick); + gsifree(params->key); + gsifree(params->value); +} +static void piGlobalKeyChangedCall(PEER peer, piCallbackData * data) +{ + piGlobalKeyChangedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_GLOBAL_KEY_CHANGED_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerGlobalKeyChangedCallback)data->callback)(peer, params->nick, params->key, params->value, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short* key_W = UTF8ToUCS2StringAlloc(params->key); + unsigned short* value_W = UTF8ToUCS2StringAlloc(params->value); + ((peerGlobalKeyChangedCallback)data->callback)(peer, nick_W, key_W, value_W, data->callbackParam); + gsifree(nick_W); + gsifree(key_W); + gsifree(value_W); + } +#endif +} +void piAddGlobalKeyChangedCallback +( + PEER peer, + const char * nick, + const char * key, + const char * value +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->globalKeyChanged) + { + piGlobalKeyChangedParams params; + params.nick = (char *)nick; + params.key = (char *)key; + params.value = (char *)value; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->globalKeyChanged, callbacks->param, PI_GLOBAL_KEY_CHANGED_CALLBACK, ¶ms, sizeof(piGlobalKeyChangedParams), -1); + } +} + +/* RoomKeyChanged. +*****************/ +typedef struct piRoomKeyChangedParams +{ + RoomType roomType; + char * nick; + char * key; + char * value; +} piRoomKeyChangedParams; +static PEERBool piRoomKeyChangedCopy(void * paramsOut_, void * paramsIn_) +{ + piRoomKeyChangedParams * paramsOut = (piRoomKeyChangedParams *)paramsOut_; + piRoomKeyChangedParams * paramsIn = (piRoomKeyChangedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + if(paramsIn->key) + { + paramsOut->key = goastrdup(paramsIn->key); + if(!paramsOut->key) + { + gsifree(paramsOut->nick); + return PEERFalse; + } + } + else + paramsOut->key = NULL; + if(paramsIn->value) + { + paramsOut->value = goastrdup(paramsIn->value); + if(!paramsOut->value) + { + gsifree(paramsOut->nick); + gsifree(paramsOut->key); + return PEERFalse; + } + } + else + paramsOut->value = NULL; + + return PEERTrue; +} +static void piRoomKeyChangedFree(void * params_) +{ + piRoomKeyChangedParams * params = (piRoomKeyChangedParams *)params_; + + assert(params); + + gsifree(params->nick); + gsifree(params->key); + gsifree(params->value); +} +static void piRoomKeyChangedCall(PEER peer, piCallbackData * data) +{ + piRoomKeyChangedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_ROOM_KEY_CHANGED_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerRoomKeyChangedCallback)data->callback)(peer, params->roomType, params->nick, params->key, params->value, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short* key_W = UTF8ToUCS2StringAlloc(params->key); + unsigned short* value_W = UTF8ToUCS2StringAlloc(params->value); + ((peerRoomKeyChangedCallback)data->callback)(peer, params->roomType, nick_W, key_W, value_W, data->callbackParam); + gsifree(nick_W); + gsifree(key_W); + gsifree(value_W); + } +#endif +} +void piAddRoomKeyChangedCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * key, + const char * value +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->roomKeyChanged) + { + piRoomKeyChangedParams params; + params.roomType = roomType; + params.nick = (char *)nick; + params.key = (char *)key; + params.value = (char *)value; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->roomKeyChanged, callbacks->param, PI_ROOM_KEY_CHANGED_CALLBACK, ¶ms, sizeof(piRoomKeyChangedParams), -1); + } +} + +/* GetGlobalKeys. +****************/ +typedef struct piGetGlobalKeysParams +{ + char * nick; + int num; + char ** keys; + char ** values; +} piGetGlobalKeysParams; +static PEERBool piGetGlobalKeysCopy(void * paramsOut_, void * paramsIn_) +{ + int i; + int num; + PEERBool success = PEERTrue; + piGetGlobalKeysParams * paramsOut = (piGetGlobalKeysParams *)paramsOut_; + piGetGlobalKeysParams * paramsIn = (piGetGlobalKeysParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + memset(paramsOut, 0, sizeof(piGetGlobalKeysParams)); + num = paramsOut->num = paramsIn->num; + + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + success = PEERFalse; + } + else + paramsOut->nick = NULL; + + if(success && num) + { + paramsOut->keys = (char **)gsimalloc(sizeof(char *) * num); + if(!paramsOut->keys) + success = PEERFalse; + else + memset(paramsOut->keys, 0, sizeof(char *) * num); + } + + if(success && num && paramsIn->values) + { + paramsOut->values = (char **)gsimalloc(sizeof(char *) * num); + if(!paramsOut->values) + success = PEERFalse; + else + memset(paramsOut->values, 0, sizeof(char *) * num); + } + + if(success && num && paramsIn->values) + { + for(i = 0 ; success && (i < num) ; i++) + { + paramsOut->keys[i] = goastrdup(paramsIn->keys[i]); + if(!paramsOut->keys[i]) + success = PEERFalse; + else + { + paramsOut->values[i] = goastrdup(paramsIn->values[i]); + if(!paramsOut->values[i]) + success = PEERFalse; + } + } + } + + if(!success) + { + gsifree(paramsOut->nick); + for(i = 0 ; i < num ; i++) + { + if(paramsOut->keys) + gsifree(paramsOut->keys[i]); + if(paramsOut->values) + gsifree(paramsOut->values[i]); + } + gsifree(paramsOut->keys); + gsifree(paramsOut->values); + } + + return success; +} +static void piGetGlobalKeysFree(void * params_) +{ + int i; + piGetGlobalKeysParams * params = (piGetGlobalKeysParams *)params_; + + assert(params); + + gsifree(params->nick); + for(i = 0 ; i < params->num ; i++) + { + gsifree(params->keys[i]); + if(params->values) + gsifree(params->values[i]); + } + gsifree(params->keys); + gsifree(params->values); +} +static void piGetGlobalKeysCall(PEER peer, piCallbackData * data) +{ + piGetGlobalKeysParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_GET_GLOBAL_KEYS_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerGetGlobalKeysCallback)data->callback)(peer, data->success, params->nick, params->num, (const char **)params->keys, (const char **)params->values, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short** keys_W = UTF8ToUCS2StringArrayAlloc((const UTF8String *)params->keys, params->num); + unsigned short** values_W = UTF8ToUCS2StringArrayAlloc((const UTF8String *)params->values, params->num); + int i; + ((peerGetGlobalKeysCallback)data->callback)(peer, data->success, nick_W, params->num, (const unsigned short**)keys_W, (const unsigned short**)values_W, data->callbackParam); + gsifree(nick_W); + for (i=0; i < params->num; i++) + { + gsifree(keys_W[i]); + if (values_W != NULL) // may be a NULL when getting keys for "*" + gsifree(values_W[i]); + } + gsifree(keys_W); + gsifree(values_W); + } +#endif +} +void piAddGetGlobalKeysCallback +( + PEER peer, + PEERBool success, + const char * nick, + int num, + const char ** keys, + const char ** values, + peerGetGlobalKeysCallback callback, + void * param, + int opID +) +{ + piGetGlobalKeysParams params; + params.nick = (char *)nick; + params.num = num; + params.keys = (char **)keys; + params.values = (char **)values; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_GET_GLOBAL_KEYS_CALLBACK, ¶ms, sizeof(piGetGlobalKeysParams), opID); +} + +/* GetRoomKeys. +****************/ +typedef struct piGetRoomKeysParams +{ + RoomType roomType; + char * nick; + int num; + char ** keys; + char ** values; +} piGetRoomKeysParams; +static PEERBool piGetRoomKeysCopy(void * paramsOut_, void * paramsIn_) +{ + int i; + int num; + PEERBool success = PEERTrue; + piGetRoomKeysParams * paramsOut = (piGetRoomKeysParams *)paramsOut_; + piGetRoomKeysParams * paramsIn = (piGetRoomKeysParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + memset(paramsOut, 0, sizeof(piGetRoomKeysParams)); + num = paramsOut->num = paramsIn->num; + + paramsOut->roomType = paramsIn->roomType; + + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + success = PEERFalse; + } + else + paramsOut->nick = NULL; + + if(success && num) + { + paramsOut->keys = (char **)gsimalloc(sizeof(char *) * num); + if(!paramsOut->keys) + success = PEERFalse; + else + memset(paramsOut->keys, 0, sizeof(char *) * num); + } + + if(success && num && paramsIn->values) + { + paramsOut->values = (char **)gsimalloc(sizeof(char *) * num); + if(!paramsOut->values) + success = PEERFalse; + else + memset(paramsOut->values, 0, sizeof(char *) * num); + } + + if(success && num && paramsIn->values) + { + for(i = 0 ; success && (i < num) ; i++) + { + paramsOut->keys[i] = goastrdup(paramsIn->keys[i]); + if(!paramsOut->keys[i]) + success = PEERFalse; + else if(paramsOut->values) + { + paramsOut->values[i] = goastrdup(paramsIn->values[i]); + if(!paramsOut->values[i]) + success = PEERFalse; + } + } + } + + if(!success) + { + gsifree(paramsOut->nick); + for(i = 0 ; i < num ; i++) + { + if(paramsOut->keys) + gsifree(paramsOut->keys[i]); + if(paramsOut->values) + gsifree(paramsOut->values[i]); + } + gsifree(paramsOut->keys); + gsifree(paramsOut->values); + } + + return success; +} +static void piGetRoomKeysFree(void * params_) +{ + int i; + piGetRoomKeysParams * params = (piGetRoomKeysParams *)params_; + + assert(params); + + gsifree(params->nick); + for(i = 0 ; i < params->num ; i++) + { + gsifree(params->keys[i]); + if(params->values) + gsifree(params->values[i]); + } + gsifree(params->keys); + gsifree(params->values); +} +static void piGetRoomKeysCall(PEER peer, piCallbackData * data) +{ + piGetRoomKeysParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_GET_ROOM_KEYS_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerGetRoomKeysCallback)data->callback)(peer, data->success, params->roomType, params->nick, params->num, params->keys, params->values, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short** keys_W = UTF8ToUCS2StringArrayAlloc((const UTF8String *)params->keys, params->num); + unsigned short** values_W = UTF8ToUCS2StringArrayAlloc((const UTF8String *)params->values, params->num); + int i; + ((peerGetRoomKeysCallback)data->callback)(peer, data->success, params->roomType, nick_W, params->num, keys_W, values_W, data->callbackParam); + gsifree(nick_W); + for (i=0; i < params->num; i++) + { + gsifree(keys_W[i]); + if (values_W != NULL) // may be a NULL when getting keys for "*" + gsifree(values_W[i]); + } + gsifree(keys_W); + gsifree(values_W); + } +#endif +} +void piAddGetRoomKeysCallback +( + PEER peer, + PEERBool success, + RoomType roomType, + const char * nick, + int num, + const char ** keys, + const char ** values, + peerGetRoomKeysCallback callback, + void * param, + int opID +) +{ + piGetRoomKeysParams params; + params.roomType = roomType; + params.nick = (char *)nick; + params.num = num; + params.keys = (char **)keys; + params.values = (char **)values; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_GET_ROOM_KEYS_CALLBACK, ¶ms, sizeof(piGetRoomKeysParams), opID); +} + +/* PlayerFlagsChanged. +*********************/ +typedef struct piPlayerFlagsChangedParams +{ + RoomType roomType; + char * nick; + int oldFlags; + int newFlags; +} piPlayerFlagsChangedParams; +static PEERBool piPlayerFlagsChangedCopy(void * paramsOut_, void * paramsIn_) +{ + piPlayerFlagsChangedParams * paramsOut = (piPlayerFlagsChangedParams *)paramsOut_; + piPlayerFlagsChangedParams * paramsIn = (piPlayerFlagsChangedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + paramsOut->newFlags = paramsIn->newFlags; + paramsOut->oldFlags = paramsIn->oldFlags; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + + return PEERTrue; +} +static void piPlayerFlagsChangedFree(void * params_) +{ + piPlayerFlagsChangedParams * params = (piPlayerFlagsChangedParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piPlayerFlagsChangedCall(PEER peer, piCallbackData * data) +{ + piPlayerFlagsChangedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_PLAYER_FLAGS_CHANGED_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerPlayerFlagsChangedCallback)data->callback)(peer, params->roomType, params->nick, params->oldFlags, params->newFlags, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerPlayerFlagsChangedCallback)data->callback)(peer, params->roomType, nick_W, params->oldFlags, params->newFlags, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddPlayerFlagsChangedCallback +( + PEER peer, + RoomType roomType, + const char * nick, + int oldFlags, + int newFlags +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->playerFlagsChanged) + { + piPlayerFlagsChangedParams params; + params.roomType = roomType; + params.nick = (char *)nick; + params.oldFlags = oldFlags; + params.newFlags = newFlags; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->playerFlagsChanged, callbacks->param, PI_PLAYER_FLAGS_CHANGED_CALLBACK, ¶ms, sizeof(piPlayerFlagsChangedParams), -1); + } +} + +/* Authenticate CD Key. +**********************/ +typedef struct piAuthenticateCDKeyParams +{ + int result; + char * message; +} piAuthenticateCDKeyParams; +static PEERBool piAuthenticateCDKeyCopy(void * paramsOut_, void * paramsIn_) +{ + piAuthenticateCDKeyParams * paramsOut = (piAuthenticateCDKeyParams *)paramsOut_; + piAuthenticateCDKeyParams * paramsIn = (piAuthenticateCDKeyParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->result = paramsIn->result; + if(paramsIn->message) + { + paramsOut->message = goastrdup(paramsIn->message); + if(!paramsOut->message) + return PEERFalse; + } + else + paramsOut->message = NULL; + + return PEERTrue; +} +static void piAuthenticateCDKeyFree(void * params_) +{ + piAuthenticateCDKeyParams * params = (piAuthenticateCDKeyParams *)params_; + + assert(params); + + gsifree(params->message); +} +static void piAuthenticateCDKeyCall(PEER peer, piCallbackData * data) +{ + piAuthenticateCDKeyParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_AUTHENTICATE_CDKEY_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerAuthenticateCDKeyCallback)data->callback)(peer, params->result, params->message, data->callbackParam); +#else + { + unsigned short* message_W = UTF8ToUCS2StringAlloc(params->message); + ((peerAuthenticateCDKeyCallback)data->callback)(peer, params->result, message_W, data->callbackParam); + gsifree(message_W); + } +#endif +} +void piAddAuthenticateCDKeyCallback +( + PEER peer, + int result, + const char * message, + peerAuthenticateCDKeyCallback callback, + void * param, + int opID +) +{ + piAuthenticateCDKeyParams params; + params.result = result; + params.message = (char *)message; + + piAddCallback(peer, PEERTrue, (PEERCBType)callback, param, PI_AUTHENTICATE_CDKEY_CALLBACK, ¶ms, sizeof(piAuthenticateCDKeyParams), opID); +} + +/* AutoMatch Status. +*******************/ +typedef struct piAutoMatchStatusParams +{ + PEERAutoMatchStatus status; +} piAutoMatchStatusParams; +static PEERBool piAutoMatchStatusCopy(void * paramsOut_, void * paramsIn_) +{ + piAutoMatchStatusParams * paramsOut = (piAutoMatchStatusParams *)paramsOut_; + piAutoMatchStatusParams * paramsIn = (piAutoMatchStatusParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->status = paramsIn->status; + + return PEERTrue; +} +static void piAutoMatchStatusFree(void * params_) +{ + piAutoMatchStatusParams * params = (piAutoMatchStatusParams *)params_; + + assert(params); + GSI_UNUSED(params); +} +static void piAutoMatchStatusCall(PEER peer, piCallbackData * data) +{ + piAutoMatchStatusParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_AUTO_MATCH_STATUS_CALLBACK); + + params = data->params; + ((peerAutoMatchStatusCallback)data->callback)(peer, params->status, data->callbackParam); +} +void piAddAutoMatchStatusCallback +( + PEER peer +) +{ + piAutoMatchStatusParams params; + piOperation * operation; + + PEER_CONNECTION; + + operation = connection->autoMatchOperation; + if(!operation || !operation->callback) + return; + + params.status = connection->autoMatchStatus; + + piAddCallback(peer, PEERTrue, operation->callback, operation->callbackParam, PI_AUTO_MATCH_STATUS_CALLBACK, ¶ms, sizeof(piAutoMatchStatusParams), operation->ID); +} + +/* AutoMatch Rate. +*****************/ +int piCallAutoMatchRateCallback +( + PEER peer, + SBServer server +) +{ + piOperation * operation; + + PEER_CONNECTION; + + operation = connection->autoMatchOperation; + if(!operation || !operation->callback2) + return 0; + + return ((peerAutoMatchRateCallback)connection->autoMatchOperation->callback2)(peer, server, operation->callbackParam); +} + +/************ +** GLOBALS ** +************/ +static const piCallbackFuncs callbackFuncs[] = +{ + { + PI_CONNECT_CALLBACK, + piConnectCopy, + piConnectFree, + piConnectCall + }, + { + PI_JOIN_ROOM_CALLBACK, + piJoinRoomCopy, + piJoinRoomFree, + piJoinRoomCall + }, + { + PI_LIST_GROUP_ROOMS_CALLBACK, + piListGroupRoomsCopy, + piListGroupRoomsFree, + piListGroupRoomsCall + }, + { + PI_LISTING_GAMES_CALLBACK, + piListingGamesCopy, + piListingGamesFree, + piListingGamesCall + }, + { + PI_NICK_ERROR_CALLBACK, + piNickErrorCopy, + piNickErrorFree, + piNickErrorCall + }, + { + PI_ENUM_PLAYERS_CALLBACK, + piEnumPlayersCopy, + piEnumPlayersFree, + piEnumPlayersCall + }, + { + PI_GET_PLAYER_INFO_CALLBACK, + piGetPlayerInfoCopy, + piGetPlayerInfoFree, + piGetPlayerInfoCall + }, + { + PI_GET_PLAYER_PROFILE_ID_CALLBACK, + piGetPlayerProfileIDCopy, + piGetPlayerProfileIDFree, + piGetPlayerProfileIDCall + }, + { + PI_GET_PLAYER_IP_CALLBACK, + piGetPlayerIPCopy, + piGetPlayerIPFree, + piGetPlayerIPCall + }, + { + PI_ROOM_MESSAGE_CALLBACK, + piRoomMessageCopy, + piRoomMessageFree, + piRoomMessageCall + }, + { + PI_ROOM_UTM_CALLBACK, + piRoomUTMCopy, + piRoomUTMFree, + piRoomUTMCall + }, + { + PI_ROOM_NAME_CHANGED_CALLBACK, + piRoomNameChangedCopy, + piRoomNameChangedFree, + piRoomNameChangedCall + }, + { + PI_ROOM_MODE_CHANGED_CALLBACK, + piRoomModeChangedCopy, + piRoomModeChangedFree, + piRoomModeChangedCall + }, + { + PI_PLAYER_MESSAGE_CALLBACK, + piPlayerMessageCopy, + piPlayerMessageFree, + piPlayerMessageCall + }, + { + PI_PLAYER_UTM_CALLBACK, + piPlayerUTMCopy, + piPlayerUTMFree, + piPlayerUTMCall + }, + { + PI_READY_CHANGED_CALLBACK, + piReadyChangedCopy, + piReadyChangedFree, + piReadyChangedCall + }, + { + PI_GAME_STARTED_CALLBACK, + piGameStartedCopy, + piGameStartedFree, + piGameStartedCall + }, + { + PI_PLAYER_JOINED_CALLBACK, + piPlayerJoinedCopy, + piPlayerJoinedFree, + piPlayerJoinedCall + }, + { + PI_PLAYER_LEFT_CALLBACK, + piPlayerLeftCopy, + piPlayerLeftFree, + piPlayerLeftCall + }, + { + PI_KICKED_CALLBACK, + piKickedCopy, + piKickedFree, + piKickedCall + }, + { + PI_NEW_PLAYER_LIST_CALLBACK, + piNewPlayerListCopy, + piNewPlayerListFree, + piNewPlayerListCall + }, + { + PI_PLAYER_CHANGED_NICK_CALLBACK, + piPlayerChangedNickCopy, + piPlayerChangedNickFree, + piPlayerChangedNickCall + }, + { + PI_PLAYER_INFO_CALLBACK, + piPlayerInfoCopy, + piPlayerInfoFree, + piPlayerInfoCall + }, + { + PI_DISCONNECTED_CALLBACK, + piDisconnectedCopy, + piDisconnectedFree, + piDisconnectedCall + }, + { + PI_PING_CALLBACK, + piPingCopy, + piPingFree, + piPingCall + }, + { + PI_CROSS_PING_CALLBACK, + piCrossPingCopy, + piCrossPingFree, + piCrossPingCall + }, + { + PI_CHANGE_NICK_CALLBACK, + piChangeNickCopy, + piChangeNickFree, + piChangeNickCall + }, + { + PI_GLOBAL_KEY_CHANGED_CALLBACK, + piGlobalKeyChangedCopy, + piGlobalKeyChangedFree, + piGlobalKeyChangedCall + }, + { + PI_ROOM_KEY_CHANGED_CALLBACK, + piRoomKeyChangedCopy, + piRoomKeyChangedFree, + piRoomKeyChangedCall + }, + { + PI_GET_GLOBAL_KEYS_CALLBACK, + piGetGlobalKeysCopy, + piGetGlobalKeysFree, + piGetGlobalKeysCall + }, + { + PI_GET_ROOM_KEYS_CALLBACK, + piGetRoomKeysCopy, + piGetRoomKeysFree, + piGetRoomKeysCall + }, + { + PI_PLAYER_FLAGS_CHANGED_CALLBACK, + piPlayerFlagsChangedCopy, + piPlayerFlagsChangedFree, + piPlayerFlagsChangedCall + }, + { + PI_AUTHENTICATE_CDKEY_CALLBACK, + piAuthenticateCDKeyCopy, + piAuthenticateCDKeyFree, + piAuthenticateCDKeyCall + }, + { + PI_AUTO_MATCH_STATUS_CALLBACK, + piAutoMatchStatusCopy, + piAutoMatchStatusFree, + piAutoMatchStatusCall + }, + { + PI_NUM_CALLBACK_TYPES, + NULL, + NULL, + NULL + } +}; + +/************** +** FUNCTIONS ** +**************/ +static void piCallbackListFree(void *elem1) +{ + piCallbackData * data = (piCallbackData *)elem1; + //ASSERT_DATA(data); + + // Call the gsifree func. + ////////////////////// + callbackFuncs[data->type].gsifree(data->params); + + // Cleanup the callback data. + ///////////////////////////// +#ifdef GSI_MANIC_DEBUG + // Set the data to a fill value so we catch the overwrite + memset(data->params, 0xea, sizeof(data->params)); +#endif + gsifree(data->params); + +} + +PEERBool piCallbacksInit +( + PEER peer +) +{ + PEER_CONNECTION; + +#ifdef _DEBUG +{ + // Consistency check. + ///////////////////// + int i; + for(i = 0 ; i <= PI_NUM_CALLBACK_TYPES ; i++) + assert(callbackFuncs[i].type == i); +} +#endif + + // No callbacks yet. + //////////////////// + connection->callbacksQueued = 0; + connection->callbacksCalled = 0; + connection->callbackDepth = 0; + + // Init the list. + ///////////////// + connection->callbackList = ArrayNew(sizeof(piCallbackData), 0, piCallbackListFree); + if(!connection->callbackList) + return PEERFalse; + + return PEERTrue; +} + +void piCallbacksCleanup +( + PEER peer +) +{ + PEER_CONNECTION; + + // gsifree the callback list. + ////////////////////////// + if(connection->callbackList) + ArrayFree(connection->callbackList); +} + +#ifdef GSI_MANIC_DEBUG +#include "stdio.h" +#endif +static void piCallCallback(PEER peer, piCallbackData * data, int index) +{ + PEER_CONNECTION; + + // In the call. + /////////////// + data->inCall = PEERTrue; + connection->callbackDepth++; + + // Call it. + /////////// + callbackFuncs[data->type].call(peer, data); + + // Out of the call. + /////////////////// + data->inCall = PEERFalse; + connection->callbackDepth--; + + // One more called. + /////////////////// + connection->callbacksCalled++; + + // gsifree it. + /////////// + ArrayDeleteAt(connection->callbackList, index); +} + +void piCallbacksThink +( + PEER peer, + int blockingID +) +{ + int index; + int len; + piCallbackData * data; + + PEER_CONNECTION; + + assert(blockingID >= -1); + + // Blocking call? + ///////////////// + if(blockingID != -1) + { + // How many? + //////////// + len = ArrayLength(connection->callbackList); + assert(len >= 0); + + // Check if this callback is finished. + ////////////////////////////////////// + for(index = 0 ; index < len ; index++) + { + // Get the nth element. + /////////////////////// + data = (piCallbackData *)ArrayNth(connection->callbackList, index); + assert(data); + + // Check the ID and specifically for disconnect. + //////////////////////////////////////////////// + if((data->ID == blockingID) || (data->type == PI_DISCONNECTED_CALLBACK)) + { + // Call it. + /////////// + piCallCallback(peer, data, index); + + break; + } + } + } + else + { + int numInCalls = 0; + while(ArrayLength(connection->callbackList) > numInCalls) + { + // Get the callback data. + ///////////////////////// + data = (piCallbackData *)ArrayNth(connection->callbackList, numInCalls); + assert(data); + + // Are we already in this call? (how philosophical) + /////////////////////////////////////////////////// + if(data->inCall) + { + numInCalls++; + } + else + { + // Call it. + /////////// + piCallCallback(peer, data, numInCalls); + } + } + } +} + +static int piAddCallback +( + PEER peer, + PEERBool success, + PEERCBType callback, + void * param, + piCallbackType type, + void * paramsIn, + size_t paramsSize, + int opID +) +{ + piCallbackData data; + void * paramsOut; + + PEER_CONNECTION; + + assert(callback); + //assert(type >= 0); + assert(type < PI_NUM_CALLBACK_TYPES); + assert(paramsIn); + assert(paramsSize > 0); + + // If no callback, nothing to do. + ///////////////////////////////// + if(!callback) + return -1; + + // Allocate the output struct. + ////////////////////////////// + paramsOut = gsimalloc(paramsSize); + if(!paramsOut) + return -1; + + // Zero it. + /////////// + memset(paramsOut, 0, paramsSize); + + // Copy the input to the output. + //////////////////////////////// + if(!callbackFuncs[type].copy(paramsOut, paramsIn)) + { + assert(0); + gsifree(paramsOut); + return -1; + } + + // Fill in the data. + //////////////////// + data.type = type; + data.success = success; + data.callback = callback; + data.callbackParam = param; + data.params = paramsOut; + data.ID = opID; + data.inCall = PEERFalse; + + // Add it to the list. + ////////////////////// + ArrayAppend(connection->callbackList, &data); + connection->callbacksQueued++; + + return data.ID; +} + +static int GS_STATIC_CALLBACK piIsCallbackFinishedCompareCallback +( + const void *elem1, + const void *elem2 +) +{ + piCallbackData * data1 = (piCallbackData *)elem1; + piCallbackData * data2 = (piCallbackData *)elem2; + assert(data1); + assert(data2); + + return (data1->ID - data2->ID); +} + +PEERBool piIsCallbackFinished +( + PEER peer, + int opID +) +{ + int index; + piCallbackData data; + + PEER_CONNECTION; + + // Search for it. + ///////////////// + data.ID = opID; + index = ArraySearch(connection->callbackList, &data, piIsCallbackFinishedCompareCallback, 0, 0); + + return (index == NOT_FOUND)?PEERTrue:PEERFalse; +} + +void piClearCallbacks +( + PEER peer, + piCallbackType type +) +{ + piCallbackData * data; + int len; + int i; + + PEER_CONNECTION; + + len = ArrayLength(connection->callbackList); + for(i = (len - 1) ; i >= 0 ; i--) + { + data = (piCallbackData *)ArrayNth(connection->callbackList, i); + if(data->type == type) + ArrayDeleteAt(connection->callbackList, i); + } +} + +void piClearListingGameServerCallbacks(PEER peer, SBServer server) +{ + piCallbackData * data; + piListingGamesParams * params; + int len; + int i; + + PEER_CONNECTION; + + len = ArrayLength(connection->callbackList); + for(i = (len - 1) ; i >= 0 ; i--) + { + data = (piCallbackData *)ArrayNth(connection->callbackList, i); + if(data->type == PI_LISTING_GAMES_CALLBACK) + { + params = (piListingGamesParams *)data->params; + if(params->server == server) + ArrayDeleteAt(connection->callbackList, i); + } + } +} diff --git a/code/gamespy/Peer/peerCallbacks.h b/code/gamespy/Peer/peerCallbacks.h new file mode 100644 index 00000000..db8475d9 --- /dev/null +++ b/code/gamespy/Peer/peerCallbacks.h @@ -0,0 +1,401 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERCALLBACKS_H_ +#define _PEERCALLBACKS_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/********** +** TYPES ** +**********/ +typedef enum piCallbackType +{ + PI_CONNECT_CALLBACK, + PI_JOIN_ROOM_CALLBACK, + PI_LIST_GROUP_ROOMS_CALLBACK, + PI_LISTING_GAMES_CALLBACK, + PI_NICK_ERROR_CALLBACK, + PI_ENUM_PLAYERS_CALLBACK, + PI_GET_PLAYER_INFO_CALLBACK, + PI_GET_PLAYER_PROFILE_ID_CALLBACK, + PI_GET_PLAYER_IP_CALLBACK, + PI_ROOM_MESSAGE_CALLBACK, + PI_ROOM_UTM_CALLBACK, + PI_ROOM_NAME_CHANGED_CALLBACK, + PI_ROOM_MODE_CHANGED_CALLBACK, + PI_PLAYER_MESSAGE_CALLBACK, + PI_PLAYER_UTM_CALLBACK, + PI_READY_CHANGED_CALLBACK, + PI_GAME_STARTED_CALLBACK, + PI_PLAYER_JOINED_CALLBACK, + PI_PLAYER_LEFT_CALLBACK, + PI_KICKED_CALLBACK, + PI_NEW_PLAYER_LIST_CALLBACK, + PI_PLAYER_CHANGED_NICK_CALLBACK, + PI_PLAYER_INFO_CALLBACK, + PI_DISCONNECTED_CALLBACK, + PI_PING_CALLBACK, + PI_CROSS_PING_CALLBACK, + PI_CHANGE_NICK_CALLBACK, + PI_GLOBAL_KEY_CHANGED_CALLBACK, + PI_ROOM_KEY_CHANGED_CALLBACK, + PI_GET_GLOBAL_KEYS_CALLBACK, + PI_GET_ROOM_KEYS_CALLBACK, + PI_PLAYER_FLAGS_CHANGED_CALLBACK, + PI_AUTHENTICATE_CDKEY_CALLBACK, + PI_AUTO_MATCH_STATUS_CALLBACK, + PI_NUM_CALLBACK_TYPES +} piCallbackType; + +/************** +** FUNCTIONS ** +**************/ +PEERBool piCallbacksInit(PEER peer); +void piCallbacksCleanup(PEER peer); +void piCallbacksThink(PEER peer, int blockingID); +PEERBool piIsCallbackFinished(PEER peer, int opID); +void piClearCallbacks(PEER peer, piCallbackType type); +void piClearListingGameServerCallbacks(PEER peer, SBServer server); + +/************** +** CALLBACKS ** +**************/ + +void piAddConnectCallback +( + PEER peer, + PEERBool success, + int failureReason, + peerConnectCallback callback, + void * param, + int opID +); + +void piAddJoinRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + peerJoinRoomCallback callback, + void * param, + int opID +); + +void piAddListGroupRoomsCallback +( + PEER peer, + PEERBool success, + int groupID, + SBServer server, + const char * name, + int numWaiting, + int maxWaiting, + int numGames, + int numPlaying, + peerListGroupRoomsCallback callback, + void * param, + int opID +); + +void piAddListingGamesCallback +( + PEER peer, + PEERBool success, + SBServer server, + int msg +); + +void piAddEnumPlayersCallback +( + PEER peer, + PEERBool success, + RoomType roomType, + int index, + const char * nick, + int flags, + peerEnumPlayersCallback callback, + void * param, + int opID +); + +void piAddGetPlayerInfoCallback +( + PEER peer, + PEERBool success, + const char * nick, + unsigned int IP, + int profileID, + peerGetPlayerInfoCallback callback, + void * param, + int opID +); + +void piAddGetPlayerProfileIDCallback +( + PEER peer, + PEERBool success, + const char * nick, + int profileID, + peerGetPlayerProfileIDCallback callback, + void * param, + int opID +); + +void piAddGetPlayerIPCallback +( + PEER peer, + PEERBool success, + const char * nick, + unsigned int IP, + peerGetPlayerIPCallback callback, + void * param, + int opID +); + +void piAddRoomMessageCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * message, + MessageType messageType +); + +void piAddRoomUTMCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * command, + const char * parameters, + PEERBool authenticated +); + +void piAddRoomNameChangedCallback +( + PEER peer, + RoomType roomType +); + +void piAddRoomModeChangedCallback +( + PEER peer, + RoomType roomType, + CHATChannelMode * mode +); + +void piAddPlayerMessageCallback +( + PEER peer, + const char * nick, + const char * message, + MessageType messageType +); + +void piAddPlayerUTMCallback +( + PEER peer, + const char * nick, + const char * command, + const char * parameters, + PEERBool authenticated +); + +void piAddReadyChangedCallback +( + PEER peer, + const char * nick, + PEERBool ready +); + +void piAddGameStartedCallback +( + PEER peer, + SBServer server, + const char * message +); + +void piAddPlayerJoinedCallback +( + PEER peer, + RoomType roomType, + const char * nick +); + +void piAddPlayerLeftCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * reason +); + +void piAddKickedCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * reason +); + +void piAddNewPlayerListCallback +( + PEER peer, + RoomType roomType +); + +void piAddPlayerChangedNickCallback +( + PEER peer, + RoomType roomType, + const char * oldNick, + const char * newNick +); + +void piAddPlayerInfoCallback +( + PEER peer, + RoomType roomType, + const char * nick, + unsigned int IP, + int profileID +); + +void piAddDisconnectedCallback +( + PEER peer, + const char * reason +); + +void piAddPingCallback +( + PEER peer, + const char * nick, + int ping +); + +void piAddCrossPingCallback +( + PEER peer, + const char * nick1, + const char * nick2, + int crossPing +); + +void piAddNickErrorCallback +( + PEER peer, + int type, + const char * nick, + int numSuggestedNicks, + const char ** suggestedNicks, + void * param, + int opID +); + +void piAddChangeNickCallback +( + PEER peer, + PEERBool success, + const char * oldNick, + const char * newNick, + peerChangeNickCallback callback, + void * param, + int opID +); + +void piAddGlobalKeyChangedCallback +( + PEER peer, + const char * nick, + const char * key, + const char * value +); + +void piAddRoomKeyChangedCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * key, + const char * value +); + +void piAddGetGlobalKeysCallback +( + PEER peer, + PEERBool success, + const char * nick, + int num, + const char ** keys, + const char ** values, + peerGetGlobalKeysCallback callback, + void * param, + int opID +); + +void piAddGetRoomKeysCallback +( + PEER peer, + PEERBool success, + RoomType roomType, + const char * nick, + int num, + const char ** keys, + const char ** values, + peerGetRoomKeysCallback callback, + void * param, + int opID +); + +void piAddPlayerFlagsChangedCallback +( + PEER peer, + RoomType roomType, + const char * nick, + int oldFlags, + int newFlags +); + +void piAddAuthenticateCDKeyCallback +( + PEER peer, + int result, + const char * message, + peerAuthenticateCDKeyCallback callback, + void * param, + int opID +); + +void piAddAutoMatchStatusCallback +( + PEER peer +); + +int piCallAutoMatchRateCallback +( + PEER peer, + SBServer server +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerGlobalCallbacks.c b/code/gamespy/Peer/peerGlobalCallbacks.c new file mode 100644 index 00000000..781d8503 --- /dev/null +++ b/code/gamespy/Peer/peerGlobalCallbacks.c @@ -0,0 +1,1081 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include +#include +#include "peer.h" +#include "peerAscii.h" +#include "peerMain.h" +#include "peerGlobalCallbacks.h" +#include "peerRooms.h" +#include "peerPlayers.h" +#include "peerCallbacks.h" +#include "peerOperations.h" +#include "peerPing.h" +#include "peerMangle.h" +#include "peerKeys.h" +#include "peerAutoMatch.h" + +/************ +** DEFINES ** +************/ +#define PI_UTM_MATCH(utm) (strncmp(piUTMCommand, utm, strlen(utm) - 1) == 0) + +#define PI_UTM_COMMAND_LEN 8 +#define PI_UTM_PARAMATERS_LEN 512 + +/************ +** GLOBALS ** +************/ +static char piUTMCommand[PI_UTM_COMMAND_LEN]; +static char piUTMParameters[PI_UTM_PARAMATERS_LEN]; + +/************** +** FUNCTIONS ** +**************/ +// Prototype the ASCII versions that are needed even in unicode mode +void peerMessagePlayerA(PEER peer, const char * nick, const char * message, MessageType messageType); + + +/* Chat. +*******/ +static PEERBool piIsOldUTM +( + const char * message +) +{ + // Check for no message. + //////////////////////// + assert(message); + if(!message) + return PEERFalse; + + // Check for too short for prefix + 1 char. + /////////////////////////////////////////// + if(strlen(message) < 4) + return PEERFalse; + + // Check for no prefix. + /////////////////////// + if((message[0] != '@') || + (message[1] != '@') || + (message[2] != '@') || + (message[3] == ' ')) + { + return PEERFalse; + } + + return PEERTrue; +} + +// Returns PEERTrue if a UTM. +///////////////////////////// +static PEERBool piParseUTM +( + const char * message +) +{ + int len; + + // Check for no message. + //////////////////////// + assert(message); + if(!message) + return PEERFalse; + + // Find the end of the command. + /////////////////////////////// + len = (int)strcspn(message, "/ "); + if(len >= PI_UTM_COMMAND_LEN) + return PEERFalse; + memcpy(piUTMCommand, message, (unsigned int)len); + piUTMCommand[len] = '\0'; + + // Copy off the parameters. + /////////////////////////// + message += len; + if(message[0]) + { + message++; + if(strlen(message) >= PI_UTM_PARAMATERS_LEN) + return PEERFalse; + strcpy(piUTMParameters, message); + } + else + { + piUTMParameters[0] = '\0'; + } + + return PEERTrue; +} + +static void piProcessUTM +( + PEER peer, + piPlayer * player, + PEERBool inRoom, + RoomType roomType +) +{ + char * params = piUTMParameters; + + PEER_CONNECTION; + + assert(piUTMCommand[0]); + assert(player); + + if(PI_UTM_MATCH(PI_UTM_LAUNCH)) + { +#ifdef _DEBUG + assert(connection->inRoom[StagingRoom]); + if(inRoom) + assert(roomType == StagingRoom); + else + assert(player->inRoom[StagingRoom]); +#endif + if(!connection->inRoom[StagingRoom]) + return; + if(inRoom && (roomType != StagingRoom)) + return; + if(!inRoom && !player->inRoom[StagingRoom]) + return; + + // Ignore if we're hosting. + /////////////////////////// + if(connection->hosting) + return; + + // Only accept launch from ops. + /////////////////////////////// + if(!piIsPlayerOp(player)) + return; + + // We're playing. + ///////////////// + connection->playing = PEERTrue; + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); + + // Add the callback. + //////////////////// + piAddGameStartedCallback(peer, connection->hostServer, params); + + // If we're AutoMatching, we're now done. + ///////////////////////////////////////// + if(peerIsAutoMatching(peer)) + piSetAutoMatchStatus(peer, PEERComplete); + } + else if(PI_UTM_MATCH(PI_UTM_XPING)) + { + piPlayer * other; + int ping; + unsigned int IP; + +#ifdef _DEBUG +// if(inRoom) +// assert(connection->xpingRoom[roomType]); +#endif + if(inRoom && !connection->xpingRoom[roomType]) + return; + + // Check for no params. + /////////////////////// + if(!params[0]) + return; + + // Get the IP. + ////////////// + IP = piDemangleIP(params); + + // Get the ping. + //////////////// + params += 11; + ping = atoi(params); + + // Figure out who this ping is to. + ////////////////////////////////// + other = piFindPlayerByIP(peer, IP); + if(!other) + return; + if(strcasecmp(player->nick, other->nick) == 0) + return; + if(inRoom && !player->inRoom[roomType]) + return; + if(!inRoom) + { + int i; + PEERBool success = PEERFalse; + + // Check that the three of us are in a room with xping enabled. + /////////////////////////////////////////////////////////////// + for(i = 0 ; i < NumRooms ; i++) + { + if(connection->xpingRoom[i] && connection->inRoom[i] && player->inRoom[i] && other->inRoom[i]) + success = PEERTrue; + } + if(!success) + return; + } + + // Update. + ////////// + piUpdateXping(peer, player->nick, other->nick, ping); + + // Add a xping callback. + //////////////////////// + piAddCrossPingCallback(peer, player->nick, other->nick, ping); + } +} + +void piChatDisconnectedA +( + CHAT chat, + const char * reason, + PEER peer +) +{ + PEER_CONNECTION; + + // We're disconnected. + ////////////////////// + connection->disconnect = PEERTrue; + + // Add the disconnected callback. + ///////////////////////////////// + piAddDisconnectedCallback(peer, reason); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +void piChatDisconnectedW +( + CHAT chat, + const unsigned short * reason, + PEER peer +) +{ + char* reason_A = UCS2ToUTF8StringAlloc(reason); + piChatDisconnectedA(chat, reason_A, peer); + gsifree(reason_A); +} +#endif + +static void piHandleOldNFO +( + PEER peer, + piPlayer * player, + const char * message +) +{ + // Ignore old NFOs from new clients. + //////////////////////////////////// + if(strncmp(message + strlen(message) - 2, "X\\", 2) != 0) + { + const char * str; + + if(!player->inRoom[StagingRoom]) + return; + + str = strstr(message, "\\$flags$\\"); + if(str) + { + PEERBool ready = PEERFalse; + str += 9; + while(*str && (*str != '\\')) + { + if(*str++ == 'r') + { + ready = PEERTrue; + break; + } + } + + if(ready) + player->flags[StagingRoom] |= PEER_FLAG_READY; + else + player->flags[StagingRoom] &= ~PEER_FLAG_READY; + } + } + + GSI_UNUSED(peer); +} + +void piChatPrivateMessageA +( + CHAT chat, + const char * user, + const char * message, + int type, + PEER peer +) +{ + assert(message); + + if(!user || !user[0]) + return; + + // Check for old-style UTMs. + //////////////////////////// + if(piIsOldUTM(message)) + { + // Check for ready. + /////////////////// + if(strncasecmp(message, "@@@NFO", 6) == 0) + { + piPlayer * player; + + player = piGetPlayer(peer, user); + if(player) + piHandleOldNFO(peer, player, message); + } + + return; + } + + // Check if it's a UTM. + /////////////////////// + if((type == CHAT_UTM) || (type == CHAT_ATM)) + { + if(piParseUTM(message)) + { + piPlayer * player; + + // Get the player it's from. + //////////////////////////// + player = piGetPlayer(peer, user); + if(player) + { + // Process it. + ////////////// + piProcessUTM(peer, player, PEERFalse, (RoomType)0); + } + + // Pass it along. + ///////////////// + piAddPlayerUTMCallback(peer, user, piUTMCommand, piUTMParameters, (PEERBool)(type == CHAT_ATM)); + } + + return; + } + + // It's a regular message, deliver it. + ////////////////////////////////////// + piAddPlayerMessageCallback(peer, user, message, (MessageType)type); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +void piChatPrivateMessageW +( + CHAT chat, + const unsigned short * user, + const unsigned short * message, + int type, + PEER peer +) +{ + char* user_A = UCS2ToUTF8StringAlloc(user); + char* message_A = UCS2ToUTF8StringAlloc(message); + piChatPrivateMessageA(chat, user_A, message_A, type, peer); + gsifree(user_A); + gsifree(message_A); +} +#endif + +static void piChannelMessageA +( + CHAT chat, + const char * channel, + const char * user, + const char * message, + int type, + PEER peer +) +{ + piPlayer * player; + RoomType roomType; + + //PEER_CONNECTION; + + assert(message); + + // Check the room type. + /////////////////////// + if(!piRoomToType(peer, channel, &roomType)) + return; + + // Get the player. + ////////////////// + player = piGetPlayer(peer, user); + + // Check for old-style UTMs. + //////////////////////////// + if(player && piIsOldUTM(message)) + { + // Only take stuff in staging rooms. + //////////////////////////////////// + if(roomType != StagingRoom) + return; + + // Check for a launch. + ////////////////////// + if(strncasecmp(message, "@@@GML", 6) == 0) + { + // Ignore old launches from new clients. + //////////////////////////////////////// + if(strncmp(message + strlen(message) - 4, "/OLD", 4) == 0) + return; + + // Convert this into its modern equivalent. + /////////////////////////////////////////// + type = CHAT_UTM; + message = "GML"; + } + // Check for ready. + /////////////////// + else if(strncasecmp(message, "@@@NFO", 6) == 0) + { + piHandleOldNFO(peer, player, message); + + return; + } + else + { + return; + } + } + + // Check if it's a UTM. + /////////////////////// + if((type == CHAT_UTM) || (type == CHAT_ATM)) + { + if(piParseUTM(message)) + { + // Process it. + ////////////// + if(player) + piProcessUTM(peer, player, PEERTrue, roomType); + + // Pass it along. + ///////////////// + piAddRoomUTMCallback(peer, roomType, user, piUTMCommand, piUTMParameters, (type == CHAT_ATM)?PEERTrue:PEERFalse); + } + + return; + } + + // Add the callback. + //////////////////// + piAddRoomMessageCallback(peer, roomType, user, message, (MessageType)type); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piChannelMessageW +( + CHAT chat, + const unsigned short * channel, + const unsigned short * user, + const unsigned short * message, + int type, + PEER peer +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* user_A = UCS2ToUTF8StringAlloc(user); + char* message_A = UCS2ToUTF8StringAlloc(message); + piChannelMessageA(chat, channel_A, user_A, message_A, type, peer); + gsifree(channel_A); + gsifree(user_A); + gsifree(message_A); +} +#endif + +static void piChannelKickedA +( + CHAT chat, + const char * channel, + const char * user, + const char * reason, + PEER peer +) +{ + RoomType roomType; + + //PEER_CONNECTION; + + // Figure out the room type. + //////////////////////////// + if(!piRoomToType(peer, channel, &roomType)) + return; + + // Leave the room. + ////////////////// + piLeaveRoom(peer, roomType, NULL); + + // Add the callback. + //////////////////// + piAddKickedCallback(peer, roomType, user, reason); + + // If we were kicked from an AutoMatch room, start searching. + ///////////////////////////////////////////////////////////// + if((roomType == StagingRoom) && peerIsAutoMatching(peer)) + piSetAutoMatchStatus(peer, PEERSearching); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piChannelKickedW +( + CHAT chat, + const unsigned short * channel, + const unsigned short * user, + const unsigned short * reason, + PEER peer +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* user_A = UCS2ToUTF8StringAlloc(user); + char* reason_A = UCS2ToUTF8StringAlloc(reason); + piChannelKickedA(chat, channel_A, user_A, reason_A, peer); + gsifree(channel_A); + gsifree(user_A); + gsifree(reason_A); +} +#endif + +static void piChannelUserJoinedA +( + CHAT chat, + const char * channel, + const char * user, + int mode, + PEER peer +) +{ + RoomType roomType; + piPlayer * player; + + PEER_CONNECTION; + + // Figure out the room type. + //////////////////////////// + if(!piRoomToType(peer, channel, &roomType)) + return; + + // Add this player to the room. + /////////////////////////////// + player = piPlayerJoinedRoom(peer, user, roomType, mode); + if(!player) + return; + + // Get IP and profile ID if we don't already have it. + ///////////////////////////////////////////////////// + if(!player->gotIPAndProfileID) + { + const char * info; + unsigned int IP; + int profileID; + + if(chatGetBasicUserInfoNoWaitA(connection->chat, user, &info, NULL) && piDemangleUser(info, &IP, &profileID)) + { + piSetPlayerIPAndProfileID(peer, user, IP, profileID); + } + } + + // Refresh this player's watch keys. + //////////////////////////////////// + piKeyCacheRefreshPlayer(peer, roomType, user); + + // Add the callback. + //////////////////// + piAddPlayerJoinedCallback(peer, roomType, user); + +#if 1 + // If this is the staging room, send our ready state. + ///////////////////////////////////////////////////// + if((roomType == StagingRoom) && connection->ready) + peerMessagePlayerA(peer, user, "@@@NFO \\$flags$\\rX\\", NormalMessage); +#endif + + // Check if this is an AutoMatch room. + ////////////////////////////////////// + if((roomType == StagingRoom) && peerIsAutoMatching(peer)) + { + // If we are Waiting, we're now Staging. + //////////////////////////////////////// + if(connection->autoMatchStatus == PEERWaiting) + piSetAutoMatchStatus(peer, PEERStaging); + + // If we've got maxplayers, we're now Ready. + //////////////////////////////////////////// + if((connection->autoMatchStatus == PEERStaging) && (connection->numPlayers[StagingRoom] >= connection->maxPlayers)) + piSetAutoMatchStatus(peer, PEERReady); + } + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piChannelUserJoinedW +( + CHAT chat, + const unsigned short * channel, + const unsigned short * user, + int mode, + PEER peer +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* user_A = UCS2ToUTF8StringAlloc(user); + piChannelUserJoinedA(chat, channel_A, user_A, mode, peer); + gsifree(channel_A); + gsifree(user_A); +} +#endif + +static void piChannelUserPartedA +( + CHAT chat, + const char * channel, + const char * user, + int why, + const char * reason, + const char * kicker, + PEER peer +) +{ + RoomType roomType; + PEERAutoMatchStatus status = PEERFailed; + PEERBool newStatus = PEERFalse; + PEERBool autoMatchRoom; + + PEER_CONNECTION; + + // Figure out the room type. + //////////////////////////// + if(!piRoomToType(peer, channel, &roomType)) + return; + + // Check if this is an AutoMatch room. + ////////////////////////////////////// + autoMatchRoom = ((roomType == StagingRoom) && peerIsAutoMatching(peer))?PEERTrue:PEERFalse; + if(autoMatchRoom) + { + piPlayer * player; + + // Get the players. + /////////////////// + player = piGetPlayer(peer, user); + + // Check if he was the last op. + /////////////////////////////// + if(!connection->hosting && piIsPlayerHost(player)) + { + status = PEERSearching; + newStatus = PEERTrue; + } + // Check for host and everyone left. + //////////////////////////////////// + else if(connection->hosting && connection->numPlayers[StagingRoom] == 2) + { + status = PEERWaiting; + newStatus = PEERTrue; + } + // Check for no longer at maxplayers. + ///////////////////////////////////// + else if(connection->numPlayers[StagingRoom] == connection->maxPlayers) + { + status = PEERStaging; + newStatus = PEERTrue; + } + } + + // Remove this player from the room. + //////////////////////////////////// + piPlayerLeftRoom(peer, user, roomType); + + // Figure out the reason. + ///////////////////////// + if((why == CHAT_KICKED) || (why == CHAT_KILLED)) + reason = "Kicked"; + else if(!reason) + reason = ""; + + // Add the callback. + //////////////////// + piAddPlayerLeftCallback(peer, roomType, user, reason); + + // Set status if needed. + //////////////////////// + if(newStatus) + piSetAutoMatchStatus(peer, status); + + GSI_UNUSED(chat); + GSI_UNUSED(kicker); +} +#ifdef GSI_UNICODE +static void piChannelUserPartedW +( + CHAT chat, + const unsigned short * channel, + const unsigned short * user, + int why, + const unsigned short * reason, + const unsigned short * kicker, + PEER peer +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* user_A = UCS2ToUTF8StringAlloc(user); + char* reason_A = UCS2ToUTF8StringAlloc(reason); + char* kicker_A = UCS2ToUTF8StringAlloc(kicker); + piChannelUserPartedA(chat, channel_A, user_A, why, reason_A, kicker_A, peer); + gsifree(channel_A); + gsifree(user_A); + gsifree(reason_A); + gsifree(kicker_A); +} +#endif + +static void piChannelUserChangedNickA +( + CHAT chat, + const char * channel, + const char * oldNick, + const char * newNick, + PEER peer +) +{ + RoomType roomType; + + // Figure out the room type. + //////////////////////////// + if(!piRoomToType(peer, channel, &roomType)) + return; + + // Change the nick locally. + /////////////////////////// + piPlayerChangedNick(peer, oldNick, newNick); + + // Add the callback. + //////////////////// + piAddPlayerChangedNickCallback(peer, roomType, oldNick, newNick); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piChannelUserChangedNickW +( + CHAT chat, + const unsigned short * channel, + const unsigned short * oldNick, + const unsigned short * newNick, + PEER peer +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* oldNick_A = UCS2ToUTF8StringAlloc(oldNick); + char* newNick_A = UCS2ToUTF8StringAlloc(newNick); + piChannelUserChangedNickA(chat, channel_A, oldNick_A, newNick_A, peer); + gsifree(channel_A); + gsifree(oldNick_A); + gsifree(newNick_A); +} +#endif + +static void piChannelTopicChangedA +( + CHAT chat, + const char * channel, + const char * topic, + PEER peer +) +{ + RoomType roomType; + + PEER_CONNECTION; + + // Figure out the room type. + //////////////////////////// + if(!piRoomToType(peer, channel, &roomType)) + return; + + // Don't allow blank names. + /////////////////////////// + if(!topic[0]) + return; + + // Is it the same as the old name? + ////////////////////////////////// + //if(strcmp(NAME, topic) == 0) + // return; + + // Copy the new name. + ///////////////////// + strzcpy(NAME, topic, PI_NAME_MAX_LEN); + + // Add a callback. + ////////////////// + if(IN_ROOM) + piAddRoomNameChangedCallback(peer, roomType); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piChannelTopicChangedW +( + CHAT chat, + const unsigned short * channel, + const unsigned short * topic, + PEER peer +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* topic_A = UCS2ToUTF8StringAlloc(topic); + piChannelTopicChangedA(chat, channel_A, topic_A, peer); + gsifree(channel_A); + gsifree(topic_A); +} +#endif + +static void piChannelNewUserListA +( + CHAT chat, + const char * channel, + int num, + const char ** users, + int * modes, + PEER peer +) +{ + int i; + RoomType roomType; + + //PEER_CONNECTION; + + // Figure out the room type. + //////////////////////////// + if(!piRoomToType(peer, channel, &roomType)) + return; + + // Clear all the players out of this room. + ////////////////////////////////////////// + piClearRoomPlayers(peer, roomType); + + // Add all the new ones. + //////////////////////// + for(i = 0 ; i < num ; i++) + piPlayerJoinedRoom(peer, users[i], roomType, modes[i]); + + // Refresh keys. + //////////////// + piKeyCacheRefreshRoom(peer, roomType); + + // Call the callback. + ///////////////////// + piAddNewPlayerListCallback(peer, roomType); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piChannelNewUserListW +( + CHAT chat, + const unsigned short * channel, + int num, + const unsigned short ** users, + int * modes, + PEER peer +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char** users_A = UCS2ToUTF8StringArrayAlloc(users, num); + int i; + piChannelNewUserListA(chat, channel_A, num, (const char**)users_A, modes, peer); + gsifree(channel_A); + for (i=0; iparam = peer; +#ifndef GSI_UNICODE // NOT GSI_PEER_UNICODE + channelCallbacks->channelMessage = piChannelMessageA; + channelCallbacks->kicked = piChannelKickedA; + channelCallbacks->userJoined = piChannelUserJoinedA; + channelCallbacks->userParted = piChannelUserPartedA; + channelCallbacks->userChangedNick = piChannelUserChangedNickA; + channelCallbacks->topicChanged = piChannelTopicChangedA; + channelCallbacks->newUserList = piChannelNewUserListA; + channelCallbacks->broadcastKeyChanged = piBroadcastKeyChangedA; + channelCallbacks->userModeChanged = piUserModeChangedA; + channelCallbacks->channelModeChanged = piChannelModeChangedA; +#else + channelCallbacks->channelMessage = piChannelMessageW; + channelCallbacks->kicked = piChannelKickedW; + channelCallbacks->userJoined = piChannelUserJoinedW; + channelCallbacks->userParted = piChannelUserPartedW; + channelCallbacks->userChangedNick = piChannelUserChangedNickW; + channelCallbacks->topicChanged = piChannelTopicChangedW; + channelCallbacks->newUserList = piChannelNewUserListW; + channelCallbacks->broadcastKeyChanged = piBroadcastKeyChangedW; + channelCallbacks->userModeChanged = piUserModeChangedW; + channelCallbacks->channelModeChanged = piChannelModeChangedW; +#endif +} + diff --git a/code/gamespy/Peer/peerGlobalCallbacks.h b/code/gamespy/Peer/peerGlobalCallbacks.h new file mode 100644 index 00000000..290278c2 --- /dev/null +++ b/code/gamespy/Peer/peerGlobalCallbacks.h @@ -0,0 +1,56 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERGLOBALCALLBACKS_H_ +#define _PEERGLOBALCALLBACKS_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/************ +** DEFINES ** +************/ +#define PI_UTM_LAUNCH "GML" +#define PI_UTM_XPING "PNG" + +/************** +** FUNCTIONS ** +**************/ + +/* Chat. +*******/ +#ifndef GSI_UNICODE +#define piChatDisconnected piChatDisconnectedA +#define piChatPrivateMessage piChatPrivateMessageA +#else +#define piChatDisconnected piChatDisconnectedW +#define piChatPrivateMessage piChatPrivateMessageW +#endif + +// Include both forms here so CodeWarrior will have it's happy prototypes +void piChatDisconnectedA(CHAT chat, const char * reason, PEER peer); +void piChatDisconnectedW(CHAT chat, const unsigned short * reason, PEER peer); + +void piChatPrivateMessageA(CHAT chat, const char * user, const char * message, int type, PEER peer); +void piChatPrivateMessageW(CHAT chat, const unsigned short * user, const unsigned short * message, int type, PEER peer); + +void piSetChannelCallbacks(PEER peer, chatChannelCallbacks * channelCallbacks); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerHost.c b/code/gamespy/Peer/peerHost.c new file mode 100644 index 00000000..f21c2c27 --- /dev/null +++ b/code/gamespy/Peer/peerHost.c @@ -0,0 +1,79 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include "peerQR.h" +#include "peerRooms.h" + +/************** +** FUNCTIONS ** +**************/ +#ifdef __MWERKS__ // CodeWarrior will warn if not prototyped +PEERBool piStartHosting (PEER peer, SOCKET socket, unsigned short port); // peerOperations.c +void piStopHosting(PEER peer, PEERBool stopReporting); // peerOperations.c +#endif + +PEERBool piStartHosting +( + PEER peer, + SOCKET socket, + unsigned short port +) +{ + PEER_CONNECTION; + + // Check that we're not hosting. + //////////////////////////////// + assert(!connection->hosting); + if(connection->hosting) + return PEERFalse; + + // Now we're hosting. + ///////////////////// + connection->hosting = PEERTrue; + + // Start reporting. + /////////////////// + if(!piStartReporting(peer, socket, port)) + return PEERFalse; + + return PEERTrue; +} + +void piStopHosting +( + PEER peer, + PEERBool stopReporting +) +{ + PEER_CONNECTION; + + // Stop reporting. + ////////////////// + if(stopReporting) + piStopReporting(peer); + + // Check that we're hosting. + //////////////////////////// + if(!connection->hosting) + return; + + // Reset states. + //////////////// + connection->hosting = PEERFalse; + connection->playing = PEERFalse; + connection->ready = PEERFalse; + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); +} diff --git a/code/gamespy/Peer/peerHost.h b/code/gamespy/Peer/peerHost.h new file mode 100644 index 00000000..4fe94df3 --- /dev/null +++ b/code/gamespy/Peer/peerHost.h @@ -0,0 +1,34 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERHOST_H_ +#define _PEERHOST_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/************** +** FUNCTIONS ** +**************/ +PEERBool piStartHosting(PEER peer, SOCKET socket, unsigned short port); +void piStopHosting(PEER peer, PEERBool stopReporting); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerKeys.c b/code/gamespy/Peer/peerKeys.c new file mode 100644 index 00000000..d96ff7ce --- /dev/null +++ b/code/gamespy/Peer/peerKeys.c @@ -0,0 +1,999 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include "peerKeys.h" +#include "peerCallbacks.h" +#include "peerRooms.h" +#include "peerGlobalCallbacks.h" +#include "peerMangle.h" + +/************ +** DEFINES ** +************/ +#define PI_WATCH_KEYS_NUM_BUCKETS 16 +#define PI_WATCH_CACHE_NUM_BUCKETS 128 + +/********** +** TYPES ** +**********/ +// Stored in the *WatckKey tables. +////////////////////////////////// +typedef struct piWatchKey +{ + char * key; +} piWatchKey; + +// Stored in the *WatchCache tables. +//////////////////////////////////// +typedef struct piCacheKey +{ + char * nick; + char * key; + char * value; +#ifdef GSI_UNICODE + unsigned short * value_W; +#endif +} piCacheKey; + +typedef struct piClearWatchKeyCacheData +{ + const char * key; + HashTable watchCache; +} piClearWatchKeyCacheData; + +typedef struct piRemoveExistingKeysData +{ + int num; + const char ** keys; + HashTable watchKeys; +} piRemoveExistingKeysData; + +typedef struct piPlayerChangedNickMapData +{ + const char * oldNick; + const char * newNick; +} piPlayerChangedNickMapData; + +typedef struct piSetupKeysMapData +{ + int next; + char ** keys; +} piSetupKeysMapData; + +typedef struct piCleanseRoomCacheMapData +{ + PEER peer; + RoomType roomType; +} piCleanseRoomCacheMapData; + +/************** +** FUNCTIONS ** +**************/ +#ifndef GSI_UNICODE // NOT GSI_UNICODE, these have to coincide with the chatsdk build +#define piGetRoomKeysCallback piGetRoomKeysCallbackA +#define piGetGlobalKeysCallback piGetGlobalKeysCallbackA +#else +#define piGetRoomKeysCallback piGetRoomKeysCallbackW +#define piGetGlobalKeysCallback piGetGlobalKeysCallbackW +#endif + + +static int WatchKeysHash +( + const void * elem, + int numBuckets +) +{ + piWatchKey * key = (piWatchKey *)elem; + int c; + const char * str; + unsigned int hash; + + assert(key->key && key->key[0]); + + // Get the hash. + //////////////// + str = key->key; + hash = 0; + while((c = *str++) != '\0') + hash += (unsigned int)tolower(c); + hash %= (unsigned int)numBuckets; + + return (int)hash; +} + +static int GS_STATIC_CALLBACK WatchKeysCompare +( + const void * elem1, + const void * elem2 +) +{ + piWatchKey * key1 = (piWatchKey *)elem1; + piWatchKey * key2 = (piWatchKey *)elem2; + + assert(key1->key && key1->key[0] && key2->key && key2->key[0]); + + return strcasecmp(key1->key, key2->key); +} + +static void WatchKeysFree +( + void * elem +) +{ + piWatchKey * key = (piWatchKey *)elem; + + gsifree(key->key); +} + +static int WatchCacheHash +( + const void * elem, + int numBuckets +) +{ + piCacheKey * key = (piCacheKey *)elem; + int c; + const char * str; + unsigned int hash; + + assert(key); + assert(key->nick[0]); + + // Get the hash. + //////////////// + str = key->key; + hash = 0; + while((c = *str++) != '\0') + hash += (unsigned int)tolower(c); + hash %= (unsigned int)numBuckets; + + return (int)hash; +} + +static int GS_STATIC_CALLBACK WatchCacheCompare +( + const void * elem1, + const void * elem2 +) +{ + int rcode; + + piCacheKey * key1 = (piCacheKey *)elem1; + piCacheKey * key2 = (piCacheKey *)elem2; + + rcode = strcasecmp(key1->nick, key2->nick); + if(rcode) + return rcode; + + return strcasecmp(key1->key, key2->key); +} + +static void WatchCacheFree +( + void * elem +) +{ + piCacheKey * key = (piCacheKey *)elem; + + gsifree(key->nick); + gsifree(key->key); + gsifree(key->value); +#ifdef GSI_UNICODE + gsifree(key->value_W); +#endif +} + +PEERBool piKeysInit +( + PEER peer +) +{ + int roomType; + PEER_CONNECTION; + + memset(connection->globalWatchKeys, 0, sizeof(HashTable) * NumRooms); + memset(connection->roomWatchKeys, 0, sizeof(HashTable) * NumRooms); + memset(connection->roomWatchCache, 0, sizeof(HashTable) * NumRooms); + + // Create all the tables. + ///////////////////////// + connection->globalWatchCache = TableNew(sizeof(piCacheKey), PI_WATCH_CACHE_NUM_BUCKETS, + WatchCacheHash, WatchCacheCompare, WatchCacheFree); + if(!connection->globalWatchCache) + return PEERFalse; + for(roomType = 0 ; roomType < NumRooms ; roomType++) + { + connection->globalWatchKeys[roomType] = TableNew(sizeof(piWatchKey), PI_WATCH_KEYS_NUM_BUCKETS, + WatchKeysHash, WatchKeysCompare, WatchKeysFree); + connection->roomWatchKeys[roomType] = TableNew(sizeof(piWatchKey), PI_WATCH_KEYS_NUM_BUCKETS, + WatchKeysHash, WatchKeysCompare, WatchKeysFree); + connection->roomWatchCache[roomType] = TableNew(sizeof(piCacheKey), PI_WATCH_CACHE_NUM_BUCKETS, + WatchCacheHash, WatchCacheCompare, WatchCacheFree); + + if(!connection->globalWatchKeys[roomType] || !connection->roomWatchKeys[roomType] || + !connection->roomWatchCache[roomType]) + { + return PEERFalse; + } + } + + return PEERTrue; +} + +void piKeysCleanup +( + PEER peer +) +{ + int roomType; + + PEER_CONNECTION; + + if(connection->globalWatchCache) + TableFree(connection->globalWatchCache); + + for(roomType = 0 ; roomType < NumRooms ; roomType++) + { + if(connection->globalWatchKeys[roomType]) + TableFree(connection->globalWatchKeys[roomType]); + if(connection->roomWatchKeys[roomType]) + TableFree(connection->roomWatchKeys[roomType]); + if(connection->roomWatchCache[roomType]) + TableFree(connection->roomWatchCache[roomType]); + } +} + +static const char * piGetWatchKeyA +( + const char * nick, + const char * key, + HashTable watchCache +) +{ + piCacheKey keyTemp; + piCacheKey * cacheKey; + + keyTemp.nick = (char *)nick; + keyTemp.key = (char *)key; + cacheKey = (piCacheKey *)TableLookup(watchCache, &keyTemp); + + if(!cacheKey) + return NULL; + + if(cacheKey->value) + return cacheKey->value; + return ""; +} +static const unsigned short * piGetWatchKeyW +( + const unsigned short * nick, + const unsigned short * key, + HashTable watchCache +) +{ +#ifndef GSI_UNICODE + GSI_UNUSED(nick); + GSI_UNUSED(key); + GSI_UNUSED(watchCache); + return NULL; // can't use this function unless in GSI_UNICODE mode +#else + piCacheKey keyTemp; + piCacheKey * cacheKey; + + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char* key_A = UCS2ToUTF8StringAlloc(key); + + keyTemp.nick = (char *)nick_A; + keyTemp.key = (char *)key_A; + cacheKey = (piCacheKey *)TableLookup(watchCache, &keyTemp); + + gsifree(nick_A); + gsifree(key_A); + + if (!cacheKey) + return NULL; + + if(cacheKey->value_W) + return cacheKey->value_W; + return L""; +#endif +} + +const char * piGetGlobalWatchKeyA +( + PEER peer, + const char * nick, + const char * key +) +{ + PEER_CONNECTION; + + return piGetWatchKeyA(nick, key, connection->globalWatchCache); +} +const unsigned short * piGetGlobalWatchKeyW +( + PEER peer, + const unsigned short * nick, + const unsigned short * key +) +{ + PEER_CONNECTION; + + return piGetWatchKeyW(nick, key, connection->globalWatchCache); +} + +const char * piGetRoomWatchKeyA +( + PEER peer, + RoomType roomType, + const char * nick, + const char * key +) +{ + PEER_CONNECTION; + + return piGetWatchKeyA(nick, key, connection->roomWatchCache[roomType]); +} +const unsigned short * piGetRoomWatchKeyW +( + PEER peer, + RoomType roomType, + const unsigned short * nick, + const unsigned short * key +) +{ + PEER_CONNECTION; + + return piGetWatchKeyW(nick, key, connection->roomWatchCache[roomType]); +} + +static void piCleanseGlobalCacheMap +( + void * elem, + void * clientData +) +{ + piPlayer * player; + int roomType; + piCacheKey * cacheKey = (piCacheKey *)elem; + PEER peer = (PEER)clientData; + piConnection * connection = (piConnection *)peer; + piWatchKey watchKeyTemp; + + watchKeyTemp.key = cacheKey->key; + + player = piGetPlayer(peer, cacheKey->nick); + if(player) + { + for(roomType = 0 ; roomType < NumRooms ; roomType++) + { + if(player->inRoom[roomType]) + { + if(TableLookup(connection->globalWatchKeys[roomType], &watchKeyTemp)) + return; + } + } + } + + TableRemove(connection->globalWatchCache, cacheKey); +} + +static void piCleanseRoomCacheMap +( + void * elem, + void * clientData +) +{ + piPlayer * player; + piCacheKey * cacheKey = (piCacheKey *)elem; + piCleanseRoomCacheMapData * data = (piCleanseRoomCacheMapData *)clientData; + piConnection * connection = (piConnection *)data->peer; + piWatchKey watchKeyTemp; + + watchKeyTemp.key = cacheKey->key; + + player = piGetPlayer(data->peer, cacheKey->nick); + if(player && player->inRoom[data->roomType]) + { + if(TableLookup(connection->roomWatchKeys[data->roomType], &watchKeyTemp)) + return; + } + + TableRemove(connection->globalWatchCache, cacheKey); +} + +void piKeyCacheCleanse +( + PEER peer +) +{ + int roomType; + piCleanseRoomCacheMapData data; + + PEER_CONNECTION; + + TableMapSafe(connection->globalWatchCache, piCleanseGlobalCacheMap, peer); + + data.peer = peer; + for(roomType = 0 ; roomType < NumRooms ; roomType++) + { + if(IN_ROOM || ENTERING_ROOM) + { + data.roomType = (RoomType)roomType; + TableMapSafe(connection->roomWatchCache[roomType], piCleanseRoomCacheMap, &data); + } + else + { + TableClear(connection->roomWatchCache[roomType]); + } + } +} + +static void piGetGlobalKeysCallbackA +( + CHAT chat, + CHATBool success, + const char * user, + int num, + const char ** keys, + const char ** values, + void * param +) +{ + PEER peer = (PEER)param; + + if(success && user) + { + int i; + + for(i = 0 ; i < num ; i++) + piGlobalKeyChanged(peer, user, keys[i], values[i]); + } + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piGetGlobalKeysCallbackW +( + CHAT chat, + CHATBool success, + const unsigned short * user, + int num, + const unsigned short ** keys, + const unsigned short ** values, + void * param +) +{ + char* user_A = UCS2ToUTF8StringAlloc(user); + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + char** values_A = UCS2ToUTF8StringArrayAlloc(values, num); + int i; + piGetGlobalKeysCallbackA(chat, success, user_A, num, (const char**)keys_A, (const char**)values_A, param); + gsifree(user_A); + for (i=0; i is NULL, then this has been called by the + // GETCKEY list-end handler (ciRplEndGetCKeyHandler), + // and the PlayerInfo callback should be called one final + // time with NULL as the user. + // + // Alter the username and proceed so that all of the + // subsequent handlers and callbacks have a real name to + // work with. Note "(END)" is illegal as an IRC nickname + // (09mar01/bgw). + // + int i; + RoomType roomType; + static const char * szEndName = "(END)"; + + if(!piRoomToType(peer, channel, &roomType)) + return; + + for(i = 0 ; i < num ; i++) + piRoomKeyChanged(peer, roomType, szEndName, keys[i], NULL); + return; + } + + if(success /*&& user*/) + { + int i; + RoomType roomType; + + if(!piRoomToType(peer, channel, &roomType)) + return; + + for(i = 0 ; i < num ; i++) + piRoomKeyChanged(peer, roomType, user, keys[i], values[i]); + } + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piGetRoomKeysCallbackW +( + CHAT chat, + CHATBool success, + const unsigned short * channel, + const unsigned short * user, + int num, + const unsigned short ** keys, + const unsigned short ** values, + void * param +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* user_A = UCS2ToUTF8StringAlloc(user); + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + char** values_A = UCS2ToUTF8StringArrayAlloc(values, num); + int i; + piGetRoomKeysCallbackA(chat, success, channel_A, user_A, num, (const char**)keys_A, (const char**)values_A, param); + gsifree(channel_A); + gsifree(user_A); + for(i=0; inum ; i++) + if(strcasecmp(key->key, data->keys[i]) == 0) + return; + + TableRemove(data->watchKeys, key); +} + +void piSetGlobalWatchKeys +( + PEER peer, + RoomType roomType, + int num, + const char ** keys, + PEERBool addKeys +) +{ + piWatchKey watchKey; + int i; + + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + assert(num >= 0); + + // If no keys. + ////////////// + if(num == 0) + { + // Nothing to add. + ////////////////// + if(addKeys) + return; + + // Clear the list and cache. + //////////////////////////// + TableClear(connection->globalWatchKeys[roomType]); + piKeyCacheCleanse(peer); + + return; + } + + assert(keys); + + if(!addKeys) + { + piRemoveExistingKeysData data; + data.num = num; + data.keys = keys; + data.watchKeys = connection->globalWatchKeys[roomType]; + TableMapSafe(connection->globalWatchKeys[roomType], piRemoveExistingKeysMap, &data); + } + + for(i = 0 ; i < num ; i++) + { + watchKey.key = goastrdup(keys[i]); + TableEnter(connection->globalWatchKeys[roomType], &watchKey); + } + + // Update them all. + /////////////////// + if(ENTERING_ROOM || IN_ROOM) + chatGetGlobalKeysA(connection->chat, ROOM, num, keys, piGetGlobalKeysCallback, peer, CHATFalse); +} + +void piSetRoomWatchKeys +( + PEER peer, + RoomType roomType, + int num, + const char ** keys, + PEERBool addKeys +) +{ + piWatchKey watchKey; + int i; + + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + assert(num >= 0); + + // If no keys. + ////////////// + if(num == 0) + { + // Nothing to add. + ////////////////// + if(addKeys) + return; + + // Clear the list and cache. + //////////////////////////// + TableClear(connection->roomWatchKeys[roomType]); + TableClear(connection->roomWatchCache[roomType]); + + return; + } + + assert(keys); + + if(!addKeys) + { + piRemoveExistingKeysData data; + data.num = num; + data.keys = keys; + data.watchKeys = connection->roomWatchKeys[roomType]; + TableMapSafe(connection->roomWatchKeys[roomType], piRemoveExistingKeysMap, &data); + } + + for(i = 0 ; i < num ; i++) + { + watchKey.key = goastrdup(keys[i]); + TableEnter(connection->roomWatchKeys[roomType], &watchKey); + } + + // Update them all. + /////////////////// + if(ENTERING_ROOM || IN_ROOM) + chatGetChannelKeysA(connection->chat, ROOM, "*", num, keys, piGetRoomKeysCallback, peer, CHATFalse); +} + +static PEERBool piKeyChanged +( + PEER peer, + const char * nick, + const char * key, + const char * value, + HashTable watchKeys, + HashTable watchCache, + PEERBool inRoom, + RoomType roomType +) +{ + piWatchKey watchKeyTemp; + piCacheKey cacheKey; + + assert(key && key[0]); + + // We don't track anything for rooms. + ///////////////////////////////////// + if(!nick || !nick[0]) + return PEERTrue; + + // No NULL values. + ////////////////// + if(!value) + value = ""; + + // Is this a username (IP and Profile ID)? + ////////////////////////////////////////// + if(strcasecmp(key, "username") == 0) + { + piPlayer * player; + + if(strcmp(nick, "(END)") == 0) + { + // + // If is "(END)", this is the final message from + // a GETCKEYS list. Call straight into the PlayerInfo + // callback with zeroed arguments (09mar01/bgw). + // + assert(inRoom); + piAddPlayerInfoCallback(peer, roomType, NULL, 0, 0); + return PEERFalse; + } + + // Get the player. + ////////////////// + player = piGetPlayer(peer, nick); + if(player && !player->gotIPAndProfileID) + { + int profileID; + unsigned int IP; + + // Get the info. + //////////////// + if(piDemangleUser(value, &IP, &profileID)) + { + // Cache the info. + ////////////////// + piSetPlayerIPAndProfileID(peer, nick, IP, profileID); + } + } + + // If this was in a room, call the callback. + //////////////////////////////////////////// + if(inRoom) + { + if(player && player->gotIPAndProfileID) + piAddPlayerInfoCallback(peer, roomType, nick, player->IP, player->profileID); + else + piAddPlayerInfoCallback(peer, roomType, nick, 0, 0); + } + } + + // Is this flags? + ///////////////// + if(inRoom && strcasecmp(key, "b_flags") == 0) + piSetPlayerRoomFlags(peer, nick, roomType, value); + + // Check if this is a watch key. + //////////////////////////////// + watchKeyTemp.key = (char *)key; + if(!TableLookup(watchKeys, &watchKeyTemp)) + { + // Is it a broadcast key? + ///////////////////////// + if(inRoom && (strncmp(key, "b_", 2) == 0)) + return PEERTrue; + return PEERFalse; + } + + // Cache it. + //////////// + memset(&cacheKey, 0, sizeof(piCacheKey)); + cacheKey.nick = goastrdup(nick); + cacheKey.key = goastrdup(key); + cacheKey.value = goastrdup(value); +#ifdef GSI_UNICODE + cacheKey.value_W = UTF8ToUCS2StringAlloc(cacheKey.value); +#endif + TableEnter(watchCache, &cacheKey); + + return PEERTrue; +} + +void piGlobalKeyChanged +( + PEER peer, + const char * nick, + const char * key, + const char * value +) +{ + PEER_CONNECTION; + + if(piKeyChanged(peer, nick, key, value, connection->globalWatchKeys[TitleRoom], connection->globalWatchCache, PEERFalse, (RoomType)0) || + piKeyChanged(peer, nick, key, value, connection->globalWatchKeys[GroupRoom], connection->globalWatchCache, PEERFalse, (RoomType)0) || + piKeyChanged(peer, nick, key, value, connection->globalWatchKeys[StagingRoom], connection->globalWatchCache, PEERFalse, (RoomType)0)) + piAddGlobalKeyChangedCallback(peer, nick, key, value); +} + +void piRoomKeyChanged +( + PEER peer, + RoomType roomType, + const char * nick, + const char * key, + const char * value +) +{ + PEER_CONNECTION; + + if(piKeyChanged(peer, nick, key, value, connection->roomWatchKeys[roomType], connection->roomWatchCache[roomType], PEERTrue, roomType)) + piAddRoomKeyChangedCallback(peer, roomType, nick, key, value); +} + +static void piPlayerChangedNickMap +( + void * elem, + void * clientData +) +{ + piCacheKey * key = (piCacheKey *)elem; + piPlayerChangedNickMapData * data = (piPlayerChangedNickMapData *)clientData; + + if(strcasecmp(key->nick, data->oldNick) == 0) + { + gsifree(key->nick); + key->nick = goastrdup(data->newNick); + } +} + +void piKeyCachePlayerChangedNick +( + PEER peer, + const char * oldNick, + const char * newNick +) +{ + piPlayerChangedNickMapData data; + + PEER_CONNECTION; + + data.oldNick = oldNick; + data.newNick = newNick; + + TableMap(connection->globalWatchCache, piPlayerChangedNickMap, &data); + TableMap(connection->roomWatchCache[0], piPlayerChangedNickMap, &data); + TableMap(connection->roomWatchCache[1], piPlayerChangedNickMap, &data); + TableMap(connection->roomWatchCache[2], piPlayerChangedNickMap, &data); +} + +static void piSetupKeysMap +( + void * elem, + void * clientData +) +{ + piWatchKey * key = (piWatchKey *)elem; + piSetupKeysMapData * data = (piSetupKeysMapData *)clientData; + + // Add the key to the list. + /////////////////////////// + data->keys[data->next++] = key->key; +} + +static void piKeyCacheRefresh +( + PEER peer, + RoomType roomType, + const char * nick +) +{ + int num; + piSetupKeysMapData data; + PEERBool getIP; + PEERBool getFlags; + piWatchKey watchKey; + + PEER_CONNECTION; + + assert(IN_ROOM || ENTERING_ROOM); + if(!IN_ROOM && !ENTERING_ROOM) + return; + + // Get the IP if we're pinging this room. + ///////////////////////////////////////// + if(!nick) + getIP = (connection->pingRoom[roomType] || connection->alwaysRequestPlayerInfo)?PEERTrue:PEERFalse; + else + getIP = PEERFalse; + + // Check if we're already getting the IP. + ///////////////////////////////////////// + if(getIP) + { + watchKey.key = "username"; + getIP = (PEERBool)(!TableLookup(connection->roomWatchKeys[roomType], &watchKey)); + } + + // Don't get flags on a player - he just joined, and will set them. + /////////////////////////////////////////////////////////////////// + getFlags = (PEERBool)(nick == NULL); + + // Check for a b_flags watch key. + ///////////////////////////////// + if(getFlags) + { + watchKey.key = "b_flags"; + getFlags = (PEERBool)(!(TableLookup(connection->globalWatchKeys[roomType], &watchKey) || + TableLookup(connection->roomWatchKeys[roomType], &watchKey))); + } + + data.next = 0; + num = TableCount(connection->globalWatchKeys[roomType]); + if(num) + { + data.keys = (char **)gsimalloc(sizeof(char *) * num); + if(!data.keys) + return; + TableMap(connection->globalWatchKeys[roomType], piSetupKeysMap, &data); + assert(data.next == num); + chatGetGlobalKeysA(connection->chat, nick?nick:ROOM, num, (const char **)data.keys, piGetGlobalKeysCallback, peer, CHATFalse); + gsifree(data.keys); + } + + if(!nick) + { + data.next = 0; + num = TableCount(connection->roomWatchKeys[roomType]); + if(getIP) + num++; + if(getFlags) + num++; + if(num) + { + data.keys = (char **)gsimalloc(sizeof(char *) * num); + if(!data.keys) + return; + TableMap(connection->roomWatchKeys[roomType], piSetupKeysMap, &data); + if(getIP) + data.keys[data.next++] = "username"; + if(getFlags) + data.keys[data.next++] = "b_flags"; + assert(data.next == num); + chatGetChannelKeysA(connection->chat, ROOM, nick?nick:"*", num, (const char **)data.keys, piGetRoomKeysCallback, peer, CHATFalse); + gsifree(data.keys); + } + } +} + +void piKeyCacheRefreshPlayer +( + PEER peer, + RoomType roomType, + const char * nick +) +{ + piKeyCacheRefresh(peer, roomType, nick); +} + +void piKeyCacheRefreshRoom +( + PEER peer, + RoomType roomType +) +{ + piKeyCacheRefresh(peer, roomType, NULL); +} diff --git a/code/gamespy/Peer/peerKeys.h b/code/gamespy/Peer/peerKeys.h new file mode 100644 index 00000000..f27333d2 --- /dev/null +++ b/code/gamespy/Peer/peerKeys.h @@ -0,0 +1,49 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERKEYS_H_ +#define _PEERKEYS_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" +#include "peerPlayers.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/************** +** FUNCTIONS ** +**************/ +PEERBool piKeysInit(PEER peer); +void piKeysCleanup(PEER peer); +const char * piGetGlobalWatchKeyA(PEER peer, const char * nick, const char * key); +const char * piGetRoomWatchKeyA(PEER peer, RoomType roomType, const char * nick, const char * key); +const unsigned short * piGetGlobalWatchKeyW(PEER peer, const unsigned short * nick, const unsigned short * key); +const unsigned short * piGetRoomWatchKeyW(PEER peer, RoomType roomType, const unsigned short * nick, const unsigned short * key); +void piSetGlobalWatchKeys(PEER peer, RoomType roomType, int num, const char ** keys, PEERBool addKeys); +void piSetRoomWatchKeys(PEER peer, RoomType roomType, int num, const char ** keys, PEERBool addKeys); +void piGlobalKeyChanged(PEER peer, const char * nick, const char * key, const char * value); +void piRoomKeyChanged(PEER peer, RoomType roomType, const char * nick, const char * key, const char * value); +void piKeyCachePlayerChangedNick(PEER peer, const char * oldNick, const char * newNick); +void piKeyCachePlayerLeftRoom(PEER peer, RoomType roomType, piPlayer * player); +void piKeyCacheLeftRoom(PEER peer, RoomType roomType); +void piKeyCacheRefreshPlayer(PEER peer, RoomType roomType, const char * nick); +void piKeyCacheRefreshRoom(PEER peer, RoomType roomType); +void piKeyCacheCleanse(PEER peer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerMain.c b/code/gamespy/Peer/peerMain.c new file mode 100644 index 00000000..c40c98be --- /dev/null +++ b/code/gamespy/Peer/peerMain.c @@ -0,0 +1,4409 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include "peer.h" +#include "peerAscii.h" +#include "peerMain.h" +#include "peerCallbacks.h" +#include "peerGlobalCallbacks.h" +#include "peerOperations.h" +#include "peerPlayers.h" +#include "peerRooms.h" +#include "peerPing.h" +#include "peerSB.h" +#include "peerMangle.h" +#include "peerKeys.h" +#include "peerQR.h" +#include "peerHost.h" +#include "peerAutoMatch.h" +#include "../common/gsAvailable.h" + +/************ +** DEFINES ** +************/ +#define PEER_CONNECTED assert(connection->connected); + +#define PI_CHECK_SHUTDOWN if(connection->shutdown && (connection->callbackDepth == 0))\ + {\ + peerShutdown(peer);\ + } + +#define PI_INIT_FAILED {\ + piShutdownCleanup(peer);\ + return NULL;\ + } + +#define PI_DO_BLOCKING if(blocking)\ + {\ + do\ + {\ + msleep(1);\ + piThink(peer, opID);\ + }\ + while(!piCheckBlockingID(peer, opID));\ + PI_CHECK_SHUTDOWN\ + } + +#define PI_OP_ID int opID = piGetNextID(peer) +#if 0 +int opID; // for visual assist +#endif + +/*************** +** PROTOTYPES ** +***************/ +static void piShutdownCleanup(PEER peer); +static void piDisconnectCleanup(PEER peer); +static void piThink(PEER peer,int ID); + +void peerCreateStagingRoomWithSocketA(PEER peer, const char * name, int maxPlayers, const char password[PEER_PASSWORD_LEN], SOCKET socket, unsigned short port, peerJoinRoomCallback callback, void * param, PEERBool blocking); +void peerStartAutoMatchWithSocketA(PEER peer, int maxPlayers, const char * filter, SOCKET socket, unsigned short port, peerAutoMatchStatusCallback statusCallback, peerAutoMatchRateCallback rateCallback, void * param, PEERBool blocking); + +/************ +** GENERAL ** +************/ +static PEERBool piCheckBlockingID +( + PEER peer, + int ID +) +{ + return (PEERBool)(piIsOperationFinished(peer, ID) && piIsCallbackFinished(peer, ID)); +} + +static unsigned int piGetPrivateIP(void) +{ + HOSTENT * host; + IN_ADDR * addr; + int i; + + host = getlocalhost(); + if(!host) + return 0; + + for(i = 0 ; host->h_addr_list[i] ; i++) + { + addr = (IN_ADDR *)host->h_addr_list[i]; + if(IsPrivateIP(addr)) + return addr->s_addr; + } + + return 0; +} + +PEER peerInitialize +( + PEERCallbacks * callbacks +) +{ + PEER peer; + piConnection * connection; + + assert(callbacks); + + // Check if the backend is available. + ///////////////////////////////////// + if(__GSIACResult != GSIACAvailable) + return NULL; + + // Init sockets. + //////////////// + SocketStartUp(); + + // Create an object. + //////////////////// + connection = (piConnection *)gsimalloc(sizeof(piConnection)); + if(!connection) + return NULL; + memset(connection, 0, sizeof(piConnection)); + peer = (PEER)connection; + + // Chat. + //////// + connection->chat = NULL; + connection->nick[0] = '\0'; + connection->connecting = PEERFalse; + connection->connected = PEERFalse; + + // Game. + //////// + connection->privateIP = piGetPrivateIP(); + connection->title[0] = '\0'; + +#ifdef GSI_UNICODE + connection->title_W[0] = '\0'; + connection->nick_W[0] = '\0'; +#endif + + // ID. + ////// + connection->nextID = 0; + + // Operations. + ////////////// + if(!piOperationsInit(peer)) + PI_INIT_FAILED + + // Callbacks. + ///////////// + connection->callbacks = *callbacks; + if(!piCallbacksInit(peer)) + PI_INIT_FAILED + + // Keys. + //////// + if(!piKeysInit(peer)) + PI_INIT_FAILED + + // Misc. + //////// + connection->shutdown = PEERFalse; + + return peer; +} + +void peerConnectA +( + PEER peer, + const char * nick, + int profileID, + peerNickErrorCallback nickErrorCallback, + peerConnectCallback connectCallback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + + PI_OP_ID; + PEER_CONNECTION; + + assert(nick); + assert(nick[0]); + assert(profileID >= 0); + assert(connectCallback); + assert(strlen(nick) < PI_NICK_MAX_LEN); + + // Are we already connecting or connected? + ////////////////////////////////////////// + if(connection->connected || connection->connecting) + success = PEERFalse; + + // We must have a title set to connect. + /////////////////////////////////////// + if(success && !connection->title[0]) + success = PEERFalse; + + if(success) + { + // Chat. + //////// + connection->chat = NULL; + strzcpy(connection->nick, nick, PI_NICK_MAX_LEN); + connection->connected = PEERFalse; + connection->connecting = PEERTrue; + connection->nickErrorCallback = nickErrorCallback; + +#ifdef GSI_UNICODE + UTF8ToUCS2String(connection->nick, connection->nick_W); +#endif + // Misc. + //////// + connection->profileID = profileID; + connection->disconnect = PEERFalse; + + // Start connecting. + //////////////////// + if(!piNewConnectOperation(peer, PI_CONNECT, nick, 0, NULL, NULL, NULL, NULL, NULL, NULL, connectCallback, param, opID)) + { + success = PEERFalse; + piDisconnectCleanup(peer); + } + } + + if(!success) + piAddConnectCallback(peer, PEERFalse, PEER_DISCONNECTED, connectCallback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerConnectW +( + PEER peer, + const unsigned short * nick, + int profileID, + peerNickErrorCallback nickErrorCallback, + peerConnectCallback connectCallback, + void * param, + PEERBool blocking +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + peerConnectA(peer, nick_A, profileID, nickErrorCallback, connectCallback, param, blocking); + gsifree(nick_A); +} +#endif + +void peerConnectLoginA +( + PEER peer, + int namespaceID, + const char * email, + const char * profilenick, + const char * uniquenick, + const char * password, + peerNickErrorCallback nickErrorCallback, + peerConnectCallback connectCallback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + + PI_OP_ID; + PEER_CONNECTION; + + assert(connectCallback); + + // Are we already connecting or connected? + ////////////////////////////////////////// + if(connection->connected || connection->connecting) + success = PEERFalse; + + // We must have a title set to connect. + /////////////////////////////////////// + if(success && !connection->title[0]) + success = PEERFalse; + + if(success) + { + // Chat. + //////// + connection->chat = NULL; + connection->nick[0] = '\0'; + connection->connected = PEERFalse; + connection->connecting = PEERTrue; + connection->nickErrorCallback = nickErrorCallback; + +#ifdef GSI_UNICODE + UTF8ToUCS2String(connection->nick, connection->nick_W); +#endif + // Misc. + //////// + connection->profileID = 0; + connection->disconnect = PEERFalse; + + // Start connecting. + //////////////////// + if(!piNewConnectOperation(peer, (uniquenick && uniquenick[0])?PI_CONNECT_UNIQUENICK_LOGIN:PI_CONNECT_PROFILENICK_LOGIN, NULL, namespaceID, email, profilenick, uniquenick, password, NULL, NULL, connectCallback, param, opID)) + { + success = PEERFalse; + piDisconnectCleanup(peer); + } + } + + if(!success) + piAddConnectCallback(peer, PEERFalse, PEER_DISCONNECTED, connectCallback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerConnectLoginW +( + PEER peer, + int namespaceID, + const unsigned short * email, + const unsigned short * profilenick, + const unsigned short * uniquenick, + const unsigned short * password, + peerNickErrorCallback nickErrorCallback, + peerConnectCallback connectCallback, + void * param, + PEERBool blocking +) +{ + char* email_A = UCS2ToUTF8StringAlloc(email); + char* profilenick_A = UCS2ToUTF8StringAlloc(profilenick); + char* uniquenick_A = UCS2ToUTF8StringAlloc(uniquenick); + char* password_A = UCS2ToUTF8StringAlloc(password); + peerConnectLoginA(peer, namespaceID, email_A, profilenick_A, uniquenick_A, password_A, nickErrorCallback, connectCallback, param, blocking); + gsifree(email_A); + gsifree(profilenick_A); + gsifree(uniquenick_A); + gsifree(password_A); +} +#endif + +void peerConnectPreAuthA +( + PEER peer, + const char * authtoken, + const char * partnerchallenge, + peerNickErrorCallback nickErrorCallback, + peerConnectCallback connectCallback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + + PI_OP_ID; + PEER_CONNECTION; + + assert(authtoken && authtoken[0]); + assert(partnerchallenge && partnerchallenge[0]); + assert(connectCallback); + + // Are we already connecting or connected? + ////////////////////////////////////////// + if(connection->connected || connection->connecting) + success = PEERFalse; + + // We must have a title set to connect. + /////////////////////////////////////// + if(success && !connection->title[0]) + success = PEERFalse; + + if(success) + { + // Chat. + //////// + connection->chat = NULL; + connection->nick[0] = '\0'; + connection->connected = PEERFalse; + connection->connecting = PEERTrue; + connection->nickErrorCallback = nickErrorCallback; + +#ifdef GSI_UNICODE + UTF8ToUCS2String(connection->nick, connection->nick_W); +#endif + // Misc. + //////// + connection->profileID = 0; + connection->disconnect = PEERFalse; + + // Start connecting. + //////////////////// + if(!piNewConnectOperation(peer, PI_CONNECT_PREAUTH, NULL, 0, NULL, NULL, NULL, NULL, authtoken, partnerchallenge, connectCallback, param, opID)) + { + success = PEERFalse; + piDisconnectCleanup(peer); + } + } + + if(!success) + piAddConnectCallback(peer, PEERFalse, PEER_DISCONNECTED, connectCallback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerConnectPreAuthW +( + PEER peer, + const unsigned short * authtoken, + const unsigned short * partnerchallenge, + peerNickErrorCallback nickErrorCallback, + peerConnectCallback connectCallback, + void * param, + PEERBool blocking +) +{ + char* authtoken_A = UCS2ToUTF8StringAlloc(authtoken); + char* partnerchallenge_A = UCS2ToUTF8StringAlloc(partnerchallenge); + peerConnectPreAuthA(peer, authtoken_A, partnerchallenge_A, nickErrorCallback, connectCallback, param, blocking); + gsifree(authtoken_A); + gsifree(partnerchallenge_A); +} +#endif + +void peerRetryWithNickA +( + PEER peer, + const char * nick +) +{ + PEER_CONNECTION; + + // Check that we're connecting. + /////////////////////////////// + assert(connection->connecting); + if(!connection->connecting) + return; + + // Set the new nick we're using. + //////////////////////////////// + if(nick && nick[0]) + { + strzcpy(connection->nick, nick, PI_NICK_MAX_LEN); + +#ifdef GSI_UNICODE + UTF8ToUCS2String(connection->nick, connection->nick_W); +#endif + } + + // Retry with the new nick. + /////////////////////////// + chatRetryWithNickA(connection->chat, nick); +} +#ifdef GSI_UNICODE +void peerRetryWithNickW +( + PEER peer, + const unsigned short * nick +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + peerRetryWithNickA(peer, nick_A); + gsifree(nick_A); +} +#endif + +void peerRegisterUniqueNickA +( + PEER peer, + int namespaceID, + const char * uniquenick, + const char * cdkey) +{ + PEER_CONNECTION; + + // Check that we're connecting. + /////////////////////////////// + assert(connection->connecting); + if(!connection->connecting) + return; + + // Register the nick. + ///////////////////// + chatRegisterUniqueNickA(connection->chat, namespaceID, uniquenick, cdkey); +} +#ifdef GSI_UNICODE +void peerRegisterUniqueNickW +( + PEER peer, + int namespaceID, + const unsigned short * uniquenick, + const unsigned short * cdkey +) +{ + char* uniquenick_A = UCS2ToUTF8StringAlloc(uniquenick); + char* cdkey_A = UCS2ToUTF8StringAlloc(cdkey); + peerRegisterUniqueNickA(peer, namespaceID, uniquenick_A, cdkey_A); + gsifree(uniquenick_A); + gsifree(cdkey_A); +} +#endif + +PEERBool peerIsConnected(PEER peer) +{ + PEER_CONNECTION; + + return connection->connected; +} + +PEERBool piConnectTitle(PEER peer) +{ + // Rooms. + ///////// + if(!piRoomsInit(peer)) + return PEERFalse; + + // Players. + /////////// + if(!piPlayersInit(peer)) + return PEERFalse; + + // Ping. + // If it fails, keep going. + /////////////////////////// + piPingInit(peer); + + return PEERTrue; +} + +void piDisconnectTitle(PEER peer) +{ + // Rooms. + ///////// + piRoomsCleanup(peer); + + // Players. + /////////// + piPlayersCleanup(peer); + + // Ping. + //////// + piPingCleanup(peer); + + // AutoMatch. + ///////////// + piStopAutoMatch(peer); +} + +PEERBool peerSetTitleA +( + PEER peer, + const char * title, + const char * qrSecretKey, + const char * sbName, + const char * sbSecretKey, + int sbGameVersion, + int sbMaxUpdates, + PEERBool natNegotiate, + PEERBool pingRooms[NumRooms], + PEERBool crossPingRooms[NumRooms] +) +{ + static PEERBool noPings[NumRooms]; + PEERBool pingTitleRoom = PEERFalse; + PEERBool xpingTitleRoom = PEERFalse; + + PEER_CONNECTION; + + assert(title); + assert(title[0]); + assert(strlen(title) < PI_TITLE_MAX_LEN); + assert(qrSecretKey); + assert(sbName); + assert(sbName[0]); + assert(sbSecretKey); + + // Check if a title is set. + /////////////////////////// + if(connection->title[0]) + peerClearTitle(peer); + + // Game. + //////// + strcpy(connection->title, title); + +#ifdef GSI_UNICODE + AsciiToUCS2String(title, connection->title_W); +#endif + + // Null pings means don't do pings. + /////////////////////////////////// + if(!pingRooms) + pingRooms = noPings; + if(!crossPingRooms) + crossPingRooms = noPings; + + // If staying in the title room, leave the room's ping setting alone. + ///////////////////////////////////////////////////////////////////// + if(connection->stayInTitleRoom) + { + pingTitleRoom = connection->pingRoom[TitleRoom]; + xpingTitleRoom = connection->xpingRoom[TitleRoom]; + } + + // Save our ping settings. + ////////////////////////// + memcpy(connection->pingRoom, pingRooms, sizeof(PEERBool) * NumRooms); + memcpy(connection->xpingRoom, crossPingRooms, sizeof(PEERBool) * NumRooms); + + // If staying in the title room, leave the room's ping setting alone. + ///////////////////////////////////////////////////////////////////// + if(connection->stayInTitleRoom) + { + connection->pingRoom[TitleRoom] = pingTitleRoom; + connection->xpingRoom[TitleRoom] = xpingTitleRoom; + } + + // Save SB settings. + //////////////////// + strzcpy(connection->sbName, sbName, PI_SB_LEN); + strzcpy(connection->sbSecretKey, sbSecretKey, PI_SB_LEN); + connection->sbGameVersion = sbGameVersion; + connection->sbMaxUpdates = sbMaxUpdates; + + // Init SB. + /////////// + if(!piSBInit(peer)) + return PEERFalse; + + // If we're already connected, do the connect stuff. + //////////////////////////////////////////////////// + if(connection->connected) + { + if(!piConnectTitle(peer)) + { + peerClearTitle(peer); + return PEERFalse; + } + } + + // Hosting. + /////////// + strcpy(connection->qrSecretKey, qrSecretKey); + piStopHosting(peer, PEERTrue); + connection->hosting = PEERFalse; + connection->playing = PEERFalse; + connection->natNegotiate = natNegotiate; + + // Game states. + /////////////// + connection->ready = PEERFalse; + + // Make sure the "stay in title room" setting is cleared. + ///////////////////////////////////////////////////////// + connection->stayInTitleRoom = PEERFalse; + + return PEERTrue; +} +#ifdef GSI_UNICODE +PEERBool peerSetTitleW +( + PEER peer, + const unsigned short * title, + const unsigned short * qrSecretKey, + const unsigned short * sbName, + const unsigned short * sbSecretKey, + int sbGameVersion, + int sbMaxUpdates, + PEERBool natNegotiate, + PEERBool pingRooms[NumRooms], + PEERBool crossPingRooms[NumRooms] +) +{ + char* title_A = UCS2ToUTF8StringAlloc(title); + char* qrSecretKey_A = UCS2ToUTF8StringAlloc(qrSecretKey); + char* sbName_A = UCS2ToUTF8StringAlloc(sbName); + char* sbSecretKey_A = UCS2ToUTF8StringAlloc(sbSecretKey); + PEERBool result = peerSetTitleA(peer, title_A, qrSecretKey_A, sbName_A, sbSecretKey_A, sbGameVersion, sbMaxUpdates, natNegotiate, pingRooms, crossPingRooms); + gsifree(title_A); + gsifree(qrSecretKey_A); + gsifree(sbName_A); + gsifree(sbSecretKey_A); + return result; +} +#endif + +void peerClearTitle(PEER peer) +{ + PEER_CONNECTION; + + // Stop hosting if we are. + ////////////////////////// + piStopHosting(peer, PEERTrue); + + // Cleanup SB. + ////////////// + piSBCleanup(peer); + + // Cleanup title stuff. + /////////////////////// + piDisconnectTitle(peer); + + // Cleanup game. + //////////////// + connection->title[0] = '\0'; + +#ifdef GSI_UNICODE + connection->title_W[0] = '\0'; +#endif + + // Cleanup qr secret key. + ///////////////////////// + connection->qrSecretKey[0] = '\0'; +} + +const char* peerGetTitleA(PEER peer) +{ + PEER_CONNECTION; + + if(!connection->title[0]) + return NULL; + + return connection->title; +} +#ifdef GSI_UNICODE +const unsigned short* peerGetTitleW(PEER peer) +{ + PEER_CONNECTION; + + if(!connection->title_W[0]) + return NULL; + + return connection->title_W; +} +#endif + +static void piDisconnectCleanup(PEER peer) +{ + PEER_CONNECTION; + + // Chat. + //////// + if(connection->chat) + chatDisconnect(connection->chat); + connection->chat = NULL; + connection->nick[0] = '\0'; + connection->connecting = PEERFalse; + connection->connected = PEERFalse; + +#ifdef GSI_UNICODE + connection->nick_W[0] = '\0'; +#endif + + // Operations. + ////////////// + piOperationsReset(peer); + + // Title. + ///////// + piDisconnectTitle(peer); + + // Away. + //////// + connection->away = PEERFalse; + connection->awayReason[0] = '\0'; + + // We're not trying to disconnect. + ////////////////////////////////// + connection->disconnect = PEERFalse; +} + +static void piDisconnect(PEER peer) +{ + PEER_CONNECTION; + + // Are we within a callback? + //////////////////////////// + if(connection->callbackDepth > 0) + { + // Flag for disconnect later. + ///////////////////////////// + connection->disconnect = PEERTrue; + + return; + } + + // Can't stay in the title room if we're disconnecting. + /////////////////////////////////////////////////////// + connection->stayInTitleRoom = PEERFalse; + + // Cleanup the connection. + ////////////////////////// + piDisconnectCleanup(peer); + + // Think to make sure the disconnected callback gets called. + //////////////////////////////////////////////////////////// + piThink(peer, -1); +} + +void peerDisconnect(PEER peer) +{ + PEER_CONNECTION; + + // Do the disconnect. + ///////////////////// + piDisconnect(peer); + + // Check if we got shutdown in the disconnect callback. + /////////////////////////////////////////////////////// + PI_CHECK_SHUTDOWN +} + +static void piShutdownCleanup(PEER peer) +{ + PEER_CONNECTION; + + // Operations. + ////////////// + piOperationsCleanup(peer); + + // Callbacks. + ///////////// + piCallbacksCleanup(peer); + + // Shut down sockets. + ///////////////////// + SocketShutDown(); + + // Keys. + //////// + piKeysCleanup(peer); + + // gsifree the connection. + /////////////////////// + gsifree(connection); +} + +void peerShutdown(PEER peer) +{ + PEER_CONNECTION; + + // Cleanup the connection? + ////////////////////////// + if(connection->connected || connection->connecting) + { + peerDisconnectedCallback callback; + + // We don't want the disconnected callback + // called if we're being explicitly shutdown. + ///////////////////////////////////////////// + callback = connection->callbacks.disconnected; + connection->callbacks.disconnected = NULL; + piDisconnect(peer); + connection->callbacks.disconnected = callback; + } + + // Cleanup title if needed. + /////////////////////////// + if(connection->title[0]) + peerClearTitle(peer); + + // Are we within a callback? + //////////////////////////// + if(connection->callbackDepth > 0) + { + // Flag for shutdown later. + /////////////////////////// + connection->shutdown = PEERTrue; + + return; + } + + // Cleanup. + /////////// + piShutdownCleanup(peer); +} + +#ifdef _DEBUG +static void piNumPlayersConsistencyCheckMap +( + piPlayer * player, + int count[NumRooms] +) +{ + int i; + + assert(player); + assert(count); + + for(i = 0 ; i < NumRooms ; i++) + { + if(player->inRoom[i]) + count[i]++; + } +} +#endif + +static void piThink +( + PEER peer, + int opID +) +{ + gsi_time now; + PEER_CONNECTION; + +#ifdef _DEBUG + if(connection->players) + { + // Consistency check number of players in each room. + //////////////////////////////////////////////////// + { + int count[NumRooms]; + int i; + + // Init the counts to 0. + //////////////////////// + for(i = 0 ; i < NumRooms ; i++) + count[i] = 0; + + // Map through the players checking the count. + ////////////////////////////////////////////// + TableMap(connection->players, (TableMapFn)piNumPlayersConsistencyCheckMap, count); + + // Check the counts. + //////////////////// + for(i = 0 ; i < NumRooms ; i++) + assert(count[i] == connection->numPlayers[i]); + } + } +#endif + +#if 0 + // Show info. + ///////////// + { + char buffer[1024]; + static int counter = -1; + + counter++; + counter %= 20; + if(counter == 0) + { + sprintf(buffer, + "---------------------\n" + "operationsStarted: %d\n" + "operationsFinished: %d\n" + "callbacksQueued: %d\n" + "callbacksCalled: %d\n" + "callbackDepth: %d\n" + "titleRoomPlayers: %d\n" + "groupRoomPlayers: %d\n" + "stagingRoomPlayers: %d\n", + connection->operationsStarted, + connection->operationsFinished, + connection->callbacksQueued, + connection->callbacksCalled, + connection->callbackDepth, + connection->numPlayers[TitleRoom], + connection->numPlayers[GroupRoom], + connection->numPlayers[StagingRoom]); + OutputDebugString(buffer); + } + } +#endif + + // Let chat think. + ////////////////// + if(connection->connected || connection->connecting) + { + chatThink(connection->chat); + + // Only do this if we weren't disconnected. + /////////////////////////////////////////// + if(!connection->disconnect) + { + // Is a title set? + ////////////////// + if(connection->title[0]) + { + // Do ping stuff. + ///////////////// + piPingThink(peer); + } + + // Are we connected? + //////////////////// + if(connection->connected) + { + // Ge the current time. + /////////////////////// + now = current_time(); + + // Check if we need to ping the chat server. + //////////////////////////////////////////// + if((now - connection->lastChatPing) > PI_CHAT_PING_TIME) + { + // Send the ping. + ///////////////// + chatSendRawA(connection->chat, "PING"); + + // Store the current time. + ////////////////////////// + connection->lastChatPing = now; + } + } + } + } + + // Let SB think. + //////////////// + piSBThink(peer); + + // Let query-reporting think. + ///////////////////////////// + piQRThink(peer); + + // If we got disconnected from chat, do the cleanup before calling the callback. + //////////////////////////////////////////////////////////////////////////////// + if(connection->disconnect && (connection->callbackDepth == 0)) + piDisconnect(peer); + + // Let the callbacks think. + /////////////////////////// + piCallbacksThink(peer, opID); +} + +void peerThink(PEER peer) +{ + PEER_CONNECTION; + + // Think. + ///////// + piThink(peer, -1); + + PI_CHECK_SHUTDOWN +} + +CHAT peerGetChat(PEER peer) +{ + PEER_CONNECTION; + + // Return it. + ///////////// + return connection->chat; +} + +const char * peerGetNickA(PEER peer) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + // Check if connected. + ////////////////////// + if(!connection->connected) + return NULL; + + // Return it. + ///////////// + return connection->nick; +} +#ifdef GSI_UNICODE +const unsigned short * peerGetNickW(PEER peer) +{ + PEER_CONNECTION; + PEER_CONNECTED; + if (!connection->connected) + return NULL; + return connection->nick_W; +} +#endif + +#ifndef GSI_UNICODE +void peerFixNickA +( + char * newNick, + const char * oldNick +) +{ + chatFixNickA(newNick, oldNick); +} +const char * peerTranslateNickA +( + char * nick, + const char * extension +) +{ + return chatTranslateNickA(nick, extension); +} +#else +void peerFixNickW +( + unsigned short * newNick, + const unsigned short * oldNick +) +{ + chatFixNickW(newNick, oldNick); +} +const unsigned short * peerTranslateNickW +( + unsigned short * nick, + const unsigned short * extension +) +{ + return chatTranslateNickW(nick, extension); +} +#endif + +unsigned int peerGetPublicIP(PEER peer) +{ + PEER_CONNECTION; + + // Return it. + ///////////// + return connection->publicIP; +} + +unsigned int peerGetPrivateIP(PEER peer) +{ + PEER_CONNECTION; + + // Return it. + ///////////// + return connection->privateIP; +} + +int peerGetUserID(PEER peer) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + if(!connection->connected) + return 0; + + return chatGetUserID(connection->chat); +} + +int peerGetProfileID(PEER peer) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + if(!connection->connected) + return 0; + + return chatGetProfileID(connection->chat); +} + +void peerChangeNickA +( + PEER peer, + const char * newNick, + peerChangeNickCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + + PI_OP_ID; + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + + // Start the operation. + /////////////////////// + if(!piNewChangeNickOperation(peer, newNick, callback, param, opID)) + success = PEERFalse; + + // Check for failure. + ///////////////////// + if(!success) + piAddChangeNickCallback(peer, PEERFalse, connection->nick, newNick, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerChangeNickW +( + PEER peer, + const unsigned short * newNick, + peerChangeNickCallback callback, + void * param, + PEERBool blocking +) +{ + char* newNick_A = UCS2ToUTF8StringAlloc(newNick); + peerChangeNickA(peer, newNick_A, callback, param, blocking); + gsifree(newNick_A); +} +#endif + +void peerStayInRoom +( + PEER peer, + RoomType roomType +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + assert(roomType == TitleRoom); + if(roomType != TitleRoom) + return; + + if(!connection->title[0]) + return; + + connection->stayInTitleRoom = PEERTrue; +} + +void peerSetQuietMode +( + PEER peer, + PEERBool quiet +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + chatSetQuietMode(connection->chat, (CHATBool)quiet); +} + +void peerSetAwayModeA +( + PEER peer, + const char * reason +) +{ + char buffer[PI_AWAY_MAX_LEN + 6]; + PEER_CONNECTION; + PEER_CONNECTED; + + if(!reason) + reason = ""; + + // Store the setting. + ///////////////////// + connection->away = (PEERBool)(reason[0] != '\0'); + strzcpy(connection->awayReason, reason, PI_AWAY_MAX_LEN); + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); + + // Send the chat command. + ///////////////////////// + sprintf(buffer, "AWAY :%s", connection->awayReason); + chatSendRawA(connection->chat, buffer); +} +#ifdef GSI_UNICODE +void peerSetAwayModeW +( + PEER peer, + const unsigned short* reason +) +{ + char* reason_A = UCS2ToUTF8StringAlloc(reason); + peerSetAwayModeA(peer, reason_A); + gsifree(reason_A); +} +#endif + +void peerParseQueryA +( + PEER peer, + char * query, + int len, + struct sockaddr * sender +) +{ + PEER_CONNECTION; + + assert(query); + assert(sender); + + // Handle the query based on what type of reporting we're doing. + //////////////////////////////////////////////////////////////// + if(connection->queryReporting) + qr2_parse_queryA(connection->queryReporting, query, len, sender); + else if(connection->autoMatchReporting) + qr2_parse_queryA(connection->autoMatchReporting, query, len, sender); +} +/* NOT necessary since qr2_parse_query uses char not unsigned short +#ifdef GSI_UNICODE +void peerParseQueryW +( + PEER peer, + unsigned short * query, + int len, + struct sockaddr * sender +) +{ + char* query_A = UCS2ToUTF8StringAlloc(query); + peerParseQueryA(peer, query_A, len, sender); + gsifree(query_A); +} +#endif +*/ +void peerAuthenticateCDKeyA +( + PEER peer, + const char * cdkey, + peerAuthenticateCDKeyCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + + PI_OP_ID; + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + + // Start the operation. + /////////////////////// + if(!piNewAuthenticateCDKeyOperation(peer, cdkey, callback, param, opID)) + success = PEERFalse; + + // Check for failure. + ///////////////////// + if(!success) + piAddAuthenticateCDKeyCallback(peer, 0, "Error starting CD Key check", callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerAuthenticateCDKeyW +( + PEER peer, + const unsigned short* cdkey, + peerAuthenticateCDKeyCallback callback, + void * param, + PEERBool blocking +) +{ + char* cdkey_A = UCS2ToUTF8StringAlloc(cdkey); + peerAuthenticateCDKeyA(peer, cdkey_A, callback, param, blocking); + gsifree(cdkey_A); +} +#endif + +void peerSendNatNegotiateCookie +( + PEER peer, + unsigned int ip, + unsigned short port, + int cookie +) +{ + piSendNatNegotiateCookie(peer, ip, port, cookie); +} + +void peerSendMessageToServer +( + PEER peer, + unsigned int ip, + unsigned short port, + const char * data, + int len +) +{ + piSendMessageToServer(peer, ip, port, data, len); +} + +void peerAlwaysGetPlayerInfo +( + PEER peer, + PEERBool always +) +{ + PEER_CONNECTION; + + connection->alwaysRequestPlayerInfo = always; +} + +/********** +** ROOMS ** +**********/ +void peerJoinTitleRoomA +( + PEER peer, + const char password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + PEERJoinResult result = PEERJoinFailed; + char buffer[PI_ROOM_MAX_LEN]; + + PI_OP_ID; + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + + // NULL password is the same as empty password. + /////////////////////////////////////////////// + if(!password) + password = ""; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + { + success = PEERFalse; + result = PEERNoTitleSet; + } + + // Check for a connection. + ////////////////////////// + if(success && !connection->connected) + { + success = PEERFalse; + result = PEERNoConnection; + } + + // Check if we're in the title room. + //////////////////////////////////// + assert(!connection->enteringRoom[TitleRoom] && !connection->inRoom[TitleRoom]); + if((success && connection->enteringRoom[TitleRoom]) || connection->inRoom[TitleRoom]) + { + success = PEERFalse; + result = PEERAlreadyInRoom; + } + + // Check if we're AutoMatching. + /////////////////////////////// + assert(!peerIsAutoMatching(peer)); + if(success && peerIsAutoMatching(peer)) + { + success = PEERFalse; + result = PEERAutoMatching; + } + + // Get the room name. + ///////////////////// + if(success) + { + if(connection->titleRoomChannel[0]) + strcpy(buffer, connection->titleRoomChannel); + else + piMangleTitleRoom(buffer, connection->title); + } + + // Start the operation. + /////////////////////// + if(success && !piNewJoinRoomOperation(peer, TitleRoom, buffer, password, callback, param, opID)) + success = PEERFalse; + + // Check for failure. + ///////////////////// + if(!success) + piAddJoinRoomCallback(peer, PEERFalse, result, TitleRoom, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerJoinTitleRoomW +( + PEER peer, + const unsigned short password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + char* password_A = NULL; + if (password != NULL) + password_A = UCS2ToUTF8StringAlloc(password); + peerJoinTitleRoomA(peer, password_A, callback, param, blocking); + gsifree(password_A); +} +#endif + +void peerJoinGroupRoom +( + PEER peer, + int groupID, + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + PEERJoinResult result = PEERJoinFailed; + char room[PI_ROOM_MAX_LEN]; + + PI_OP_ID; + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + { + success = PEERFalse; + result = PEERNoTitleSet; + } + + // Check for a connection. + ////////////////////////// + if(success && !connection->connected) + { + success = PEERFalse; + result = PEERNoConnection; + } + + // Check if we're AutoMatching. + /////////////////////////////// + assert(!peerIsAutoMatching(peer)); + if(success && peerIsAutoMatching(peer)) + { + success = PEERFalse; + result = PEERAutoMatching; + } + + // Check the ID. + //////////////// + assert(groupID); + if(success && !groupID) + success = PEERFalse; + + // Check if we're in a group room. + ////////////////////////////////// + if(success && (connection->enteringRoom[GroupRoom] || connection->inRoom[GroupRoom])) + { + success = PEERFalse; + result = PEERAlreadyInRoom; + } + + // Create the name. + /////////////////// + piMangleGroupRoom(room, groupID); + + // Save off the group id. + ///////////////////////// + connection->groupID = groupID; + + // Start the operation. + /////////////////////// + if(success && !piNewJoinRoomOperation(peer, GroupRoom, room, NULL, callback, param, opID)) + success = PEERFalse; + + // Check for failure. + ///////////////////// + if(!success) + piAddJoinRoomCallback(peer, PEERFalse, result, GroupRoom, callback, param, opID); + + PI_DO_BLOCKING; +} + +void peerSetGroupID +( + PEER peer, + int groupID +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return; + + // Save off the group id. + ///////////////////////// + connection->groupID = groupID; +} + +int peerGetGroupID +( + PEER peer +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return 0; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return 0; + + // Get the group id. + //////////////////// + return connection->groupID; +} + +static void piJoinStagingRoom +( + PEER peer, + SBServer server, + const char * channel, + const char password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + unsigned int publicIP = 0; + unsigned int privateIP = 0; + unsigned short privatePort = 0; + char room[PI_ROOM_MAX_LEN]; + PEERBool success = PEERTrue; + PEERJoinResult result = PEERJoinFailed; + + PI_OP_ID; + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + + // NULL password is the same as empty password. + /////////////////////////////////////////////// + if(!password) + password = ""; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + { + success = PEERFalse; + result = PEERNoTitleSet; + } + + // Check for a connection. + ////////////////////////// + if(success && !connection->connected) + { + success = PEERFalse; + result = PEERNoConnection; + } + + // Check if we're in a staging room. + //////////////////////////////////// + if(success && (connection->enteringRoom[StagingRoom] || connection->inRoom[StagingRoom])) + { + success = PEERFalse; + result = PEERAlreadyInRoom; + } + + // Check if we're AutoMatching. + /////////////////////////////// + assert(!peerIsAutoMatching(peer)); + if(success && peerIsAutoMatching(peer)) + { + success = PEERFalse; + result = PEERAutoMatching; + } + + // If we have a server, get the public and private IPs and ports. + ///////////////////////////////////////////////////////////////// + if(success && server) + { + publicIP = SBServerGetPublicInetAddress(server); + privateIP = SBServerGetPrivateInetAddress(server); + if(SBServerHasPrivateAddress(server)) + privatePort = SBServerGetPrivateQueryPort(server); + else + privatePort = SBServerGetPublicQueryPort(server); + + if(!publicIP) + success = PEERFalse; + } + + // If we have a channel, check it. + ////////////////////////////////// + if(success && !server) + { + assert(channel); + assert(channel[0]); + if(!channel || !channel[0]) + success = PEERFalse; + } + + // Stop hosting. + //////////////// + if(success) + piStopHosting(peer, PEERTrue); + + // If we have a server, get the staging room. + ///////////////////////////////////////////// + if(success && server) + piMangleStagingRoom(room, connection->title, publicIP, privateIP, privatePort); + + // Start the operation. + /////////////////////// + if(success && !piNewJoinRoomOperation(peer, StagingRoom, server?room:channel, password, callback, param, opID)) + success = PEERFalse; + + // If we have a server, clone it. + ///////////////////////////////// + if(success && server) + connection->hostServer = piSBCloneServer(server); + + // Check for failure. + ///////////////////// + if(!success) + piAddJoinRoomCallback(peer, PEERFalse, result, StagingRoom, callback, param, opID); + + PI_DO_BLOCKING; +} + +void peerJoinStagingRoomA +( + PEER peer, + SBServer server, + const char password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + piJoinStagingRoom(peer, server, NULL, password, callback, param, blocking); +} +#ifdef GSI_UNICODE +void peerJoinStagingRoomW +( + PEER peer, + SBServer server, + const unsigned short password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + char* password_A = UCS2ToUTF8StringAlloc(password); + peerJoinStagingRoomA(peer, server, password_A, callback, param, blocking); + gsifree(password_A); +} +#endif + +void peerJoinStagingRoomByChannelA +( + PEER peer, + const char * channel, + const char password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + piJoinStagingRoom(peer, NULL, channel, password, callback, param, blocking); +} +#ifdef GSI_UNICODE +void peerJoinStagingRoomByChannelW +( + PEER peer, + const unsigned short * channel, + const unsigned short password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* password_A = UCS2ToUTF8StringAlloc(password); + peerJoinStagingRoomByChannelA(peer, channel_A, password_A, callback, param, blocking); + gsifree(password_A); + gsifree(channel_A); +} +#endif + +void peerCreateStagingRoomA +( + PEER peer, + const char * name, + int maxPlayers, + const char password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + peerCreateStagingRoomWithSocketA(peer, name, maxPlayers, password, INVALID_SOCKET, 0, callback, param, blocking); +} +#ifdef GSI_UNICODE +void peerCreateStagingRoomW +( + PEER peer, + const unsigned short * name, + int maxPlayers, + const unsigned short password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + char* name_A = UCS2ToUTF8StringAlloc(name); + char* password_A = UCS2ToUTF8StringAlloc(password); + peerCreateStagingRoomA(peer, name_A, maxPlayers, password_A, callback, param, blocking); + gsifree(password_A); + gsifree(name_A); +} +#endif + +void peerCreateStagingRoomWithSocketA +( + PEER peer, + const char * name, + int maxPlayers, + const char password[PEER_PASSWORD_LEN], + SOCKET socket, + unsigned short port, + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + PEERJoinResult result = PEERJoinFailed; + PI_OP_ID; + + PEER_CONNECTION; + + assert(name); + assert(connection->title[0]); + assert(callback); + assert(maxPlayers >= 0); + + // NULL password is the same as empty password. + /////////////////////////////////////////////// + if(!password) + password = ""; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + { + success = PEERFalse; + result = PEERNoTitleSet; + } + + // Check for a connection. + ////////////////////////// + if(success && !connection->connected) + { + success = PEERFalse; + result = PEERNoConnection; + } + + // Check if we're in a staging room. + //////////////////////////////////// + if(success && (connection->enteringRoom[StagingRoom] || connection->inRoom[StagingRoom])) + { + success = PEERFalse; + result = PEERAlreadyInRoom; + } + + // Check if we're AutoMatching. + /////////////////////////////// + assert(!peerIsAutoMatching(peer)); + if(success && peerIsAutoMatching(peer)) + { + success = PEERFalse; + result = PEERAutoMatching; + } + + // Stop hosting. + //////////////// + if(success) + piStopHosting(peer, PEERTrue); + + // Start the operation. + /////////////////////// + if(success && !piNewCreateStagingRoomOperation(peer, name, password, maxPlayers, socket, port, callback, param, opID)) + success = PEERFalse; + + // Add callback if error. + ///////////////////////// + if(!success) + piAddJoinRoomCallback(peer, PEERFalse, result, StagingRoom, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerCreateStagingRoomWithSocketW +( + PEER peer, + const unsigned short * name, + int maxPlayers, + const unsigned short password[PEER_PASSWORD_LEN], + SOCKET socket, + unsigned short port, + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + char* name_A = UCS2ToUTF8StringAlloc(name); + char* password_A = UCS2ToUTF8StringAlloc(password); + peerCreateStagingRoomWithSocketA(peer, name_A, maxPlayers, password_A, socket, port, callback, param, blocking); + gsifree(password_A); + gsifree(name_A); +} +#endif + +// Should only be called when a game report is already in progress +// Always call after creating staging room, starting reporting, +// starting automatch +////////////////////////////////////////////////////////////////////////// +/* +qr2_t peerGetReportingRecord(PEER peer) +{ + PEER_CONNECTION; + if (!connection->title[0]) + return NULL; + + if (!connection->connected) + return NULL; + + assert(connection->queryReporting || connection->autoMatchReporting); + // When we are reporting normal games, the normal qr2 record + // is returned. + if (connection->queryReporting) + { + return connection->queryReporting; + } + + + // When we are reporting automatch games, the automatch qr2 record + // is returned. + if (peerIsAutoMatching(peer) && connection->autoMatchReporting) + { + return connection->autoMatchReporting; + } + + return NULL; +} +*/ + +void peerLeaveRoomA +( + PEER peer, + RoomType roomType, + const char * reason +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return; + + // Check if we're in or entering. + ///////////////////////////////// + if(!ENTERING_ROOM && !IN_ROOM) + return; + + // Leave. + ///////// + piLeaveRoom(peer, roomType, reason); + + // Is this an AutoMatch room? + ///////////////////////////// + if((roomType == StagingRoom) && peerIsAutoMatching(peer)) + { + // Go back to searching. + //////////////////////// + piSetAutoMatchStatus(peer, PEERSearching); + } +} +#ifdef GSI_UNICODE +void peerLeaveRoomW +( + PEER peer, + RoomType roomType, + const unsigned short* reason +) +{ + if (reason != NULL) + { + char* reason_A = UCS2ToUTF8StringAlloc(reason); + peerLeaveRoomA(peer, roomType, reason_A); + gsifree(reason_A); + } + else + peerLeaveRoomA(peer, roomType, NULL); +} +#endif + +void peerListGroupRoomsA +( + PEER peer, + const char * fields, + peerListGroupRoomsCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + PI_OP_ID; + PEER_CONNECTION; + + assert(callback); + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + success = PEERFalse; + + // Can't have a NULL fields. + //////////////////////////// + if(!fields) + fields = ""; + + // Start the listing. + ///////////////////// + if(success && !piNewListGroupRoomsOperation(peer, fields, callback, param, opID)) + success = PEERFalse; + + // Call the callback if failed. + /////////////////////////////// + if(!success) + piAddListGroupRoomsCallback(peer, PEERFalse, 0, NULL, NULL, 0, 0, 0, 0, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerListGroupRoomsW +( + PEER peer, + const unsigned short * fields, + peerListGroupRoomsCallback callback, + void * param, + PEERBool blocking +) +{ + char* fields_A = UCS2ToUTF8StringAlloc(fields); + peerListGroupRoomsA(peer, fields_A, callback, param, blocking); + gsifree(fields_A); +} +#endif + +void peerStartListingGamesA +( + PEER peer, + const unsigned char * fields, + int numFields, + const char * filter, + peerListingGamesCallback callback, + void * param +) +{ + PEERBool success; + + PEER_CONNECTION; + + assert(callback); + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Can't have an empty filter. + ////////////////////////////// + if(filter && !filter[0]) + filter = NULL; + + // Check the fields. + //////////////////// + if(!fields || (numFields <= 0)) + numFields = 0; + + // Save the callback info. + ////////////////////////// + connection->gameListCallback = callback; + connection->gameListParam = param; + + // Start the listing. + ///////////////////// + success = piSBStartListingGames(peer, fields, numFields, filter); + + // Call the callback if failed. + /////////////////////////////// + if(!success) + piAddListingGamesCallback(peer, PEERFalse, NULL, 0); +} +#ifdef GSI_UNICODE +void peerStartListingGamesW +( + PEER peer, + const unsigned char * fields, + int numFields, + const unsigned short * filter, + peerListingGamesCallback callback, + void * param +) +{ + char* filter_A = UCS2ToUTF8StringAlloc(filter); + peerStartListingGamesA(peer, fields, numFields, filter_A, callback, param); + gsifree(filter_A); +} +#endif + +void peerStopListingGames +( + PEER peer +) +{ + PEER_CONNECTION; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Stop the listing. + //////////////////// + piSBStopListingGames(peer); +} + +void peerUpdateGame +( + PEER peer, + SBServer server, + PEERBool fullUpdate +) +{ + PEER_CONNECTION; + + assert(server); + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Update the server. + // Changed 08-26-2004 + // Saad Nader + // Added force update by master server parameter + // for internal update function + ///////////////////// + piSBUpdateGame(peer, server, fullUpdate, PEERFalse, PEERFalse); +} + +// Added 08-26-2004 +// By Saad Nader +// per request of developer +//////////////////////////////////////////////////////////////////////////// +void peerUpdateGameByMaster(PEER peer, SBServer server, PEERBool fullUpdate) +{ + // obtain and check the peer connection object + PEER_CONNECTION; + + // validate server for sanity check + assert(server); + + // Check that we have set a title + if(!connection->title[0]) + return; + + // Let internal update take place via the master server + piSBUpdateGame(peer, server, fullUpdate, PEERTrue, PEERFalse); +} + +void peerUpdateGamePing(PEER peer, SBServer server) +{ + // obtain and check the peer connection object + PEER_CONNECTION; + + // validate server for sanity check + assert(server); + + // Check that we have set a title + if(!connection->title[0]) + return; + + // Let internal update take place via the master server + piSBUpdateGame(peer, server, PEERFalse, PEERFalse, PEERTrue); +} + +void peerMessageRoomA +( + PEER peer, + RoomType roomType, + const char * message, + MessageType messageType +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + ASSERT_MESSAGETYPE(messageType); + + // Check for no message. + //////////////////////// + if(!message || !message[0]) + return; + + // Check that we're in this room. + ///////////////////////////////// + assert(IN_ROOM); + if(!IN_ROOM) + return; + + // Send the message. + //////////////////// + chatSendChannelMessageA(connection->chat, ROOM, message, (int)messageType); +} +#ifdef GSI_UNICODE +void peerMessageRoomW +( + PEER peer, + RoomType roomType, + const unsigned short * message, + MessageType messageType +) +{ + char* message_A = UCS2ToUTF8StringAlloc(message); + peerMessageRoomA(peer, roomType, message_A, messageType); + gsifree(message_A); +} +#endif + +void peerUTMRoomA +( + PEER peer, + RoomType roomType, + const char * command, + const char * parameters, + PEERBool authenticate +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + + // Check that we're in this room. + ///////////////////////////////// + assert(IN_ROOM); + if(!IN_ROOM) + return; + + // Send it. + /////////// + piSendChannelUTM(peer, ROOM, command, parameters, authenticate); +} +#ifdef GSI_UNICODE +void peerUTMRoomW +( + PEER peer, + RoomType roomType, + const unsigned short * command, + const unsigned short * parameters, + PEERBool authenticate +) +{ + char* command_A = UCS2ToUTF8StringAlloc(command); + char* parameters_A = UCS2ToUTF8StringAlloc(parameters); + peerUTMRoomA(peer, roomType, command_A, parameters_A, authenticate); + gsifree(parameters_A); + gsifree(command_A); +} +#endif + +void peerSetPasswordA +( + PEER peer, + RoomType roomType, + const char password[PEER_PASSWORD_LEN] +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + + // Check room type. + //////////////////// + assert(roomType == StagingRoom); + if(roomType != StagingRoom) + return; + + // NULL password is the same as empty password. + /////////////////////////////////////////////// + if(!password) + password = ""; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return; + + // Check if we're in or entering. + ///////////////////////////////// + if(!ENTERING_ROOM && !IN_ROOM) + return; + + // Make sure we're hosting. + /////////////////////////// + assert(connection->hosting); + if(!connection->hosting) + return; + + // Set/clear the password. + ////////////////////////// + if(password[0]) + chatSetChannelPasswordA(connection->chat, ROOM, CHATTrue, password); + else + chatSetChannelPasswordA(connection->chat, ROOM, CHATFalse, "x"); + + // Set the passworded flag. + /////////////////////////// + connection->passwordedRoom = password[0]?PEERTrue:PEERFalse; + + // Send a state-changed. + //////////////////////// + piSendStateChanged(peer); +} +#ifdef GSI_UNICODE +void peerSetPasswordW +( + PEER peer, + RoomType roomType, + const unsigned short password[PEER_PASSWORD_LEN] +) +{ + char* password_A = UCS2ToUTF8StringAlloc(password); + peerSetPasswordA(peer, roomType, password_A); + gsifree(password_A); +} +#endif + +void peerSetRoomNameA +( + PEER peer, + RoomType roomType, + const char * name +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + + assert(roomType == StagingRoom); + + // NULL name is the same as empty name. + /////////////////////////////////////// + if(!name) + name = ""; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return; + + // Check if we're in or entering. + ///////////////////////////////// + if(!ENTERING_ROOM && !IN_ROOM) + return; + + // Make sure we're hosting. + /////////////////////////// + assert(connection->hosting); + if(!connection->hosting) + return; + + // Set it. + ////////// + chatSetChannelTopicA(connection->chat, ROOM, name); +} +#ifdef GSI_UNICODE +void peerSetRoomNameW +( + PEER peer, + RoomType roomType, + const unsigned short * name +) +{ + char* name_A = UCS2ToUTF8StringAlloc(name); + peerSetRoomNameA(peer, roomType, name_A); + gsifree(name_A); +} +#endif + +const char * peerGetRoomNameA +( + PEER peer, + RoomType roomType +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + assert(IN_ROOM); + if(!IN_ROOM) + return NULL; + + return NAME; +} +#ifdef GSI_UNICODE +const unsigned short * peerGetRoomNameW +( + PEER peer, + RoomType roomType +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + assert(IN_ROOM); + if(!IN_ROOM) + return NULL; + + return NAME_W; +} +#endif + +const char * peerGetRoomChannelA +( + PEER peer, + RoomType roomType +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + assert(IN_ROOM || ENTERING_ROOM); + if(!IN_ROOM && ! ENTERING_ROOM) + return NULL; + + return ROOM; +} +#ifdef GSI_UNICODE +const unsigned short * peerGetRoomChannelW +( + PEER peer, + RoomType roomType +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + assert(IN_ROOM || ENTERING_ROOM); + if(!IN_ROOM && ! ENTERING_ROOM) + return NULL; + + return ROOM_W; +} +#endif + +PEERBool peerInRoom +( + PEER peer, + RoomType roomType +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + + return IN_ROOM; +} + +void peerSetTitleRoomChannelA +( + PEER peer, + const char * channel +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return; + + // Check for no channel. + //////////////////////// + if(!channel) + channel = ""; + + // Copy it. + /////////// + strzcpy(connection->titleRoomChannel, channel, PI_ROOM_MAX_LEN); +} +#ifdef GSI_UNICODE +void peerSetTitleRoomChannelW +( + PEER peer, + const unsigned short * channel +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + peerSetTitleRoomChannelA(peer, channel_A); + gsifree(channel_A); +} +#endif + +SBServer peerGetHostServer(PEER peer) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + return connection->hostServer; +} + +/************ +** PLAYERS ** +************/ +typedef struct piEnumPlayersData +{ + peerEnumPlayersCallback callback; + void * param; +} piEnumPlayersData; + +static void piEnumPlayersEnumRoomPlayersCallback +( + PEER peer, + RoomType roomType, + piPlayer * player, + int index, + void *param +) +{ + piEnumPlayersData * data = (piEnumPlayersData *)param; + const char * nick; + + int flags; + + if(player) + { + nick = player->nick; + flags = player->flags[roomType]; + } + else + { + nick = NULL; + flags = 0; + } + + // Call the callback. + ///////////////////// +#ifndef GSI_UNICODE + data->callback(peer, PEERTrue, roomType, index, nick, flags, data->param); +#else + if (nick == NULL) + data->callback(peer, PEERTrue, roomType, index, NULL, flags, data->param); + else + { + unsigned short nick_W[512]; + UTF8ToUCS2String(nick, nick_W); + data->callback(peer, PEERTrue, roomType, index, nick_W, flags, data->param); + } +#endif +} + +void peerEnumPlayers +( + PEER peer, + RoomType roomType, + peerEnumPlayersCallback callback, + void * param +) +{ + PEERBool success = PEERTrue; + piEnumPlayersData data; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + ASSERT_ROOMTYPE(roomType); + + // Check that we're in this room. + ///////////////////////////////// + assert(IN_ROOM); + if(success && !IN_ROOM) + success = PEERFalse; + + // Check for failure. + ///////////////////// + if(!success) + { + // Call the callback. + ///////////////////// + callback(peer, PEERFalse, roomType, -1, NULL, PEERFalse, param); + return; + } + + // Enumerate through the players, using a local copy. + ///////////////////////////////////////////////////// + data.callback = callback; + data.param = param; + piEnumRoomPlayers(peer, roomType, piEnumPlayersEnumRoomPlayersCallback, &data); +} + +void peerMessagePlayerA +( + PEER peer, + const char * nick, + const char * message, + MessageType messageType +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_MESSAGETYPE(messageType); + assert(nick); + assert(nick[0]); + + // Check for connection succeeded. + ////////////////////////////////// + if(!connection->connected) + return; + + // Check for no message. + //////////////////////// + if(!message || !message[0]) + return; + + // Send the message to this player. + /////////////////////////////////// + chatSendUserMessageA(connection->chat, nick, message, (int)messageType); +} +#ifdef GSI_UNICODE +void peerMessagePlayerW +( + PEER peer, + const unsigned short * nick, + const unsigned short * message, + MessageType messageType +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char* message_A = UCS2ToUTF8StringAlloc(message); + peerMessagePlayerA(peer, nick_A, message_A, messageType); + gsifree(nick_A); + gsifree(message_A); +} +#endif + +void peerUTMPlayerA +( + PEER peer, + const char * nick, + const char * command, + const char * parameters, + PEERBool authenticate +) +{ + // Send it. + /////////// + piSendPlayerUTM(peer, nick, command, parameters, authenticate); +} +#ifdef GSI_UNICODE +void peerUTMPlayerW +( + PEER peer, + const unsigned short * nick, + const unsigned short * command, + const unsigned short * parameters, + PEERBool authenticate +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char* command_A = UCS2ToUTF8StringAlloc(command); + char* parameters_A = UCS2ToUTF8StringAlloc(parameters); + peerUTMPlayerA(peer, nick_A, command_A, parameters_A, authenticate); + gsifree(parameters_A); + gsifree(command_A); + gsifree(nick_A); +} +#endif + +void peerKickPlayerA +( + PEER peer, + RoomType roomType, + const char * nick, + const char * reason +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + assert(IN_ROOM || ENTERING_ROOM); + assert(nick); + assert(nick[0]); + + // Check for connection succeeded. + ////////////////////////////////// + if(!connection->connected) + return; + + // Kick the player. + /////////////////// + chatKickUserA(connection->chat, ROOM, nick, reason); +} +#ifdef GSI_UNICODE +void peerKickPlayerW +( + PEER peer, + RoomType roomType, + const unsigned short * nick, + const unsigned short * reason +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char* reason_A = UCS2ToUTF8StringAlloc(reason); + peerKickPlayerA(peer, roomType, nick_A, reason_A); + gsifree(reason_A); + gsifree(nick_A); +} +#endif + +PEERBool peerGetPlayerPingA +( + PEER peer, + const char * nick, + int * ping +) +{ + piPlayer * player; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(nick); + assert(nick[0]); + assert(ping); + + // Get the player. + ////////////////// + player = piGetPlayer(peer, nick); + if(!player) + return PEERFalse; + + // Is it the local player? + ////////////////////////// + if(player->local) + { + *ping = 0; + } + else + { + // Check if there's a ping. + /////////////////////////// + if(!player->numPings) + return PEERFalse; + + *ping = player->pingAverage; + } + + return PEERTrue; +} +#ifdef GSI_UNICODE +PEERBool peerGetPlayerPingW +( + PEER peer, + const unsigned short * nick, + int * ping +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + PEERBool result = peerGetPlayerPingA(peer, nick_A, ping); + gsifree(nick_A); + return result; +} +#endif + +PEERBool peerGetPlayersCrossPingA +( + PEER peer, + const char * nick1, + const char * nick2, + int * crossPing +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + assert(nick1); + assert(nick1[0]); + assert(nick2); + assert(nick2[0]); + assert(crossPing); + + // Do it. + ///////// + return piGetXping(peer, nick1, nick2, crossPing); +} +#ifdef GSI_UNICODE +PEERBool peerGetPlayersCrossPingW +( + PEER peer, + const unsigned short * nick1, + const unsigned short * nick2, + int * crossPing +) +{ + char* nick1_A = UCS2ToUTF8StringAlloc(nick1); + char* nick2_A = UCS2ToUTF8StringAlloc(nick2); + PEERBool result = peerGetPlayersCrossPingA(peer, nick1_A, nick2_A, crossPing); + gsifree(nick2_A); + gsifree(nick1_A); + return result; +} +#endif + +PEERBool peerPingPlayerA +( + PEER peer, + const char * nick +) +{ + piPlayer * player; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(nick); + assert(nick[0]); + + // Get the player. + ////////////////// + player = piGetPlayer(peer, nick); + if(!player) + return PEERFalse; + + // Is it the local player? + ////////////////////////// + if(player->local) + return PEERFalse; + + // Do we have the IP? + ///////////////////// + if(!player->gotIPAndProfileID) + return PEERFalse; + + // Is the player already being pinged? + ////////////////////////////////////// + if(player->waitingForPing) + return PEERTrue; + + // Set this player as a must ping. + ////////////////////////////////// + player->mustPing = PEERTrue; + + // Set this player as a one-time ping. + ////////////////////////////////////// + if(!player->inPingRoom) + player->pingOnce = PEERTrue; + + return PEERTrue; +} +#ifdef GSI_UNICODE +PEERBool peerPingPlayerW +( + PEER peer, + const unsigned short * nick +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + PEERBool result = peerPingPlayerA(peer, nick_A); + gsifree(nick_A); + return result; +} +#endif + +PEERBool peerGetPlayerInfoNoWaitA +( + PEER peer, + const char * nick, + unsigned int * IP, + int * profileID +) +{ + piPlayer * player; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(nick); + + player = piGetPlayer(peer, nick); + if(!player || !player->gotIPAndProfileID) + { + const char * info; + unsigned int locIP; + int locProfileID; + + // Can we get it from chat? + /////////////////////////// + if(chatGetBasicUserInfoNoWaitA(connection->chat, nick, &info, NULL) && piDemangleUser(info, &locIP, &locProfileID)) + { + if(player) + piSetPlayerIPAndProfileID(peer, nick, locIP, locProfileID); + + if(IP) + *IP = locIP; + if(profileID) + *profileID = locProfileID; + + return PEERTrue; + } + return PEERFalse; + } + + if(IP) + *IP = player->IP; + if(profileID) + *profileID = player->profileID; + + return PEERTrue; +} +#ifdef GSI_UNICODE +PEERBool peerGetPlayerInfoNoWaitW +( + PEER peer, + const unsigned short * nick, + unsigned int * IP, + int * profileID +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + PEERBool result = peerGetPlayerInfoNoWaitA(peer, nick_A, IP, profileID); + gsifree(nick_A); + return result; +} +#endif + +void peerGetPlayerInfoA +( + PEER peer, + const char * nick, + peerGetPlayerInfoCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + piPlayer * player; + PI_OP_ID; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + assert(nick); + assert(nick[0]); + assert(callback); + + // Find the player. + /////////////////// + player = piGetPlayer(peer, nick); + + // Check if chat has it. + //////////////////////// + if(player && !player->gotIPAndProfileID) + { + const char * info; + unsigned int IP; + int profileID; + + if(chatGetBasicUserInfoNoWaitA(connection->chat, nick, &info, NULL) && piDemangleUser(info, &IP, &profileID)) + { + piSetPlayerIPAndProfileID(peer, nick, IP, profileID); + } + } + + // See if we already have it. + ///////////////////////////// + if(player && player->gotIPAndProfileID) + { + piAddGetPlayerInfoCallback(peer, PEERTrue, nick, player->IP, player->profileID, callback, param, opID); + } + else + { + // Start an op to get it. + ///////////////////////// + if(!piNewGetPlayerInfoOperation(peer, nick, callback, param, opID)) + success = PEERFalse; + } + + // If failed, add the callback. + /////////////////////////////// + if(!success) + piAddGetPlayerInfoCallback(peer, PEERFalse, nick, 0, 0, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerGetPlayerInfoW +( + PEER peer, + const unsigned short * nick, + peerGetPlayerInfoCallback callback, + void * param, + PEERBool blocking +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + peerGetPlayerInfoA(peer, nick_A, callback, param, blocking); + gsifree(nick_A); +} +#endif + +void peerGetPlayerProfileIDA +( + PEER peer, + const char * nick, + peerGetPlayerProfileIDCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + piPlayer * player; + PI_OP_ID; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + assert(nick); + assert(nick[0]); + assert(callback); + + // Find the player. + /////////////////// + player = piGetPlayer(peer, nick); + + // Check if chat has it. + //////////////////////// + if(player && !player->gotIPAndProfileID) + { + const char * info; + unsigned int IP; + int profileID; + + if(chatGetBasicUserInfoNoWaitA(connection->chat, nick, &info, NULL) && piDemangleUser(info, &IP, &profileID)) + { + piSetPlayerIPAndProfileID(peer, nick, IP, profileID); + } + } + + // See if we already have it. + ///////////////////////////// + if(player && player->gotIPAndProfileID) + { + piAddGetPlayerProfileIDCallback(peer, PEERTrue, nick, player->profileID, callback, param, opID); + } + else + { + // Start an op to get it. + ///////////////////////// + if(!piNewGetProfileIDOperation(peer, nick, callback, param, opID)) + success = PEERFalse; + } + + // If failed, add the callback. + /////////////////////////////// + if(!success) + piAddGetPlayerProfileIDCallback(peer, PEERFalse, nick, 0, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerGetPlayerProfileIDW +( + PEER peer, + const unsigned short * nick, + peerGetPlayerProfileIDCallback callback, + void * param, + PEERBool blocking +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + peerGetPlayerProfileIDA(peer, nick_A, callback, param, blocking); + gsifree(nick_A); +} +#endif + +void peerGetPlayerIPA +( + PEER peer, + const char * nick, + peerGetPlayerIPCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + piPlayer * player; + PI_OP_ID; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + assert(nick); + assert(nick[0]); + assert(callback); + + // Find the player. + /////////////////// + player = piGetPlayer(peer, nick); + + // Check if chat has it. + //////////////////////// + if(player && !player->gotIPAndProfileID) + { + const char * info; + unsigned int IP; + int profileID; + + if(chatGetBasicUserInfoNoWaitA(connection->chat, nick, &info, NULL) && piDemangleUser(info, &IP, &profileID)) + { + piSetPlayerIPAndProfileID(peer, nick, IP, profileID); + } + } + + // Check if we already have it. + /////////////////////////////// + if(player && player->gotIPAndProfileID) + { + piAddGetPlayerIPCallback(peer, PEERTrue, nick, player->IP, callback, param, opID); + } + else + { + // Start an op to get it. + ///////////////////////// + if(!piNewGetIPOperation(peer, nick, callback, param, opID)) + success = PEERFalse; + } + + // If failed, add the callback. + /////////////////////////////// + if(!success) + piAddGetPlayerIPCallback(peer, PEERFalse, nick, 0, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerGetPlayerIPW +( + PEER peer, + const unsigned short * nick, + peerGetPlayerIPCallback callback, + void * param, + PEERBool blocking +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + peerGetPlayerIPA(peer, nick_A, callback, param, blocking); + gsifree(nick_A); +} +#endif + +PEERBool peerIsPlayerHostA +( + PEER peer, + const char * nick, + RoomType roomType +) +{ + piPlayer * player; + + PEER_CONNECTION; + PEER_CONNECTED; + + // Are we in this type of room? + /////////////////////////////// + assert(IN_ROOM); + if(!IN_ROOM) + return PEERFalse; + + // Get the player. + ////////////////// + player = piGetPlayer(peer, nick); + if(!player) + return PEERFalse; + + // If it's the local player, return the value we store. + /////////////////////////////////////////////////////// + if(player->local) + return connection->hosting; + + // Is he host? + ////////////// + return piIsPlayerHost(player); +} +#ifdef GSI_UNICODE +PEERBool peerIsPlayerHostW +( + PEER peer, + const unsigned short * nick, + RoomType roomType +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + PEERBool result = peerIsPlayerHostA(peer, nick_A, roomType); + gsifree(nick_A); + return result; +} +#endif + +PEERBool peerGetPlayerFlagsA +( + PEER peer, + const char * nick, + RoomType roomType, + int * flags +) +{ + piPlayer * player; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(flags); + if(!flags) + return PEERFalse; + + // Are we in this type of room? + /////////////////////////////// + assert(IN_ROOM); + if(!IN_ROOM) + return PEERFalse; + + // Get the player. + ////////////////// + player = piGetPlayer(peer, nick); + if(!player) + return PEERFalse; + + // Is he in? + //////////// + if(!player->inRoom[roomType]) + return PEERFalse; + + // Get the flags. + ///////////////// + *flags = player->flags[roomType]; + + return PEERTrue; +} +#ifdef GSI_UNICODE +PEERBool peerGetPlayerFlagsW +( + PEER peer, + const unsigned short * nick, + RoomType roomType, + int * flags +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + PEERBool result = peerGetPlayerFlagsA(peer, nick_A, roomType, flags); + gsifree(nick_A); + return result; +} +#endif + +/********* +** GAME ** +*********/ +void peerSetReady +( + PEER peer, + PEERBool ready +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return; + + // Are we in a staging room? + //////////////////////////// + assert(connection->inRoom[StagingRoom]); + if(!connection->inRoom[StagingRoom]) + return; + + // Don't do anything if the state isn't changing. + ///////////////////////////////////////////////// + if(connection->ready == ready) + return; + + // Set it. + ////////// + connection->ready = ready; + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); + +#if 1 + // Send an old-style ready notice. + // THIS IS ONLY NEEDED FOR BACKWARDS COMPATIBILITY AND SHOULD BE REMOVED AT SOME POINT IN THE FUTURE. + //////////////////////////////////////////////////////////////////////////////////////////////////// + { + char buffer[32]; + //IN_ADDR addr; + //addr.s_addr = connection->publicIP; + strcpy(buffer, "@@@NFO \\$flags$\\"); + if(ready) + strcat(buffer, "r"); + strcat(buffer, "X\\"); // Flag to indicate this was sent by a new client. + peerMessageRoomA(peer, StagingRoom, buffer, NormalMessage); + } +#endif +} + +PEERBool peerGetReadyA +( + PEER peer, + const char * nick, + PEERBool * ready +) +{ + piPlayer * player; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(nick); + assert(nick[0]); + assert(ready); + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return PEERFalse; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return PEERFalse; + + // Are we in a staging room? + //////////////////////////// + assert(connection->inRoom[StagingRoom]); + if(!connection->inRoom[StagingRoom]) + return PEERFalse; + + // Get the player. + ////////////////// + player = piGetPlayer(peer, nick); + if(!player || !player->inRoom[StagingRoom]) + return PEERFalse; + + *ready = (PEERBool)((player->flags[StagingRoom] & PEER_FLAG_READY) != 0); + + return PEERTrue; +} +#ifdef GSI_UNICODE +PEERBool peerGetReadyW +( + PEER peer, + const unsigned short * nick, + PEERBool * ready +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + PEERBool result = peerGetReadyA(peer, nick_A, ready); + gsifree(nick_A); + return result; +} +#endif + +static void piAreAllReadyEnumRoomPlayersCallback +( + PEER peer, + RoomType roomType, + piPlayer * player, + int index, + void *param +) +{ + if(player) + { + PEERBool * allReadyPtr = (PEERBool *)param; + + // If this player's not ready, set the flag. + //////////////////////////////////////////// + if(!(player->flags[StagingRoom] & PEER_FLAG_READY)) + *allReadyPtr = PEERFalse; + } + + GSI_UNUSED(peer); + GSI_UNUSED(roomType); + GSI_UNUSED(index); +} + +PEERBool peerAreAllReady +( + PEER peer +) +{ + PEERBool allReady; + + PEER_CONNECTION; + PEER_CONNECTED; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return PEERFalse; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return PEERFalse; + + // Are we in a staging room? + //////////////////////////// + assert(connection->inRoom[StagingRoom]); + if(!connection->inRoom[StagingRoom]) + return PEERFalse; + + // Enum through all the room's players. + /////////////////////////////////////// + allReady = PEERTrue; + piEnumRoomPlayers(peer, StagingRoom, piAreAllReadyEnumRoomPlayersCallback, &allReady); + + return allReady; +} + +void peerStartGameA +( + PEER peer, + const char * message, + int reportingOptions +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return; + + // Check that we're in a staging room. + ////////////////////////////////////// + assert(connection->inRoom[StagingRoom]); + if(!connection->inRoom[StagingRoom]) + return; + + // Make sure we're the host. + //////////////////////////// + assert(connection->hosting); + if(!connection->hosting) + return; + + // Change NULL messages to empty messages. + ////////////////////////////////////////// + if(!message) + message = ""; + + // Send the launch UTM. + /////////////////////// + piSendChannelUTM(peer, connection->rooms[StagingRoom], PI_UTM_LAUNCH, message, PEERFalse); + +#if 1 + // Send an old-style launch command. + // THIS IS ONLY NEEDED FOR BACKWARDS COMPATIBILITY AND SHOULD BE REMOVED AT SOME POINT IN THE FUTURE. + //////////////////////////////////////////////////////////////////////////////////////////////////// + { + char buffer[32]; + IN_ADDR addr; + addr.s_addr = connection->publicIP; + sprintf(buffer, "@@@GML %s/OLD", inet_ntoa(addr)); + peerMessageRoomA(peer, StagingRoom, buffer, NormalMessage); + } +#endif + + // We're playing. + ///////////////// + connection->playing = PEERTrue; + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); + + // If we're AutoMatching, we're now done. + ///////////////////////////////////////// + if(peerIsAutoMatching(peer)) + { + piSetAutoMatchStatus(peer, PEERComplete); + } + else if(connection->queryReporting) + { + // Check if we should stop GOA reporting. + ///////////////////////////////////////// + if(reportingOptions & PEER_STOP_REPORTING) + { + // Stop. + //////// + piStopReporting(peer); + } + else + { + // Set the options. + /////////////////// + connection->reportingOptions = reportingOptions; + + // Send a state-changed. + //////////////////////// + piSendStateChanged(peer); + } + } +} +#ifdef GSI_UNICODE +void peerStartGameW +( + PEER peer, + const unsigned short * message, + int reportingOptions +) +{ + char* message_A = UCS2ToUTF8StringAlloc(message); + peerStartGameA(peer, message_A, reportingOptions); + gsifree(message_A); +} +#endif + +PEERBool peerStartReporting +( + PEER peer +) +{ + return peerStartReportingWithSocket(peer, INVALID_SOCKET, 0); +} + +PEERBool peerStartReportingWithSocket +( + PEER peer, + SOCKET socket, + unsigned short port +) +{ + PEER_CONNECTION; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return PEERFalse; + + // Start. + ///////// + if(!piStartReporting(peer, socket, port)) + return PEERFalse; + + return PEERTrue; +} + +void peerStartPlaying +( + PEER peer +) +{ + PEER_CONNECTION; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Mark us as playing. + ////////////////////// + connection->playing = PEERTrue; + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); +} + +PEERBool peerIsPlaying +( + PEER peer +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return PEERFalse; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return PEERFalse; + + return connection->playing; +} + +void peerStopGame +( + PEER peer +) +{ + PEER_CONNECTION; + + // We're done playing. + ////////////////////// + connection->playing = PEERFalse; + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); + + // Are we reporting? + //////////////////// + if(connection->queryReporting) + { + // Are we still in the staging room? + //////////////////////////////////// + if(connection->inRoom[StagingRoom]) + piSendStateChanged(peer); + else + piStopReporting(peer); + } +} + +void peerStateChanged +( + PEER peer +) +{ + PEER_CONNECTION; + + // We should be reporting. + ////////////////////////// + assert(connection->queryReporting); + + // Send a state-changed. + //////////////////////// + piSendStateChanged(peer); +} + +void piSendChannelUTM +( + PEER peer, + const char * channel, + const char * command, + const char * parameters, + PEERBool authenticate +) +{ + char buffer[512]; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(channel && channel[0]); + assert(command && command[0]); + assert(parameters); + + // Check for connection succeeded. + ////////////////////////////////// + if(!connection->connected) + return; + + // Check for no channel. + //////////////////////// + if(!channel || !channel[0]) + return; + + // Check for no command. + //////////////////////// + if(!command || !command[0]) + return; + + // Check for no parameters. + /////////////////////////// + if(!parameters) + parameters = ""; + + // Make sure this UTM isn't too long. + ///////////////////////////////////// + if((strlen(command) + strlen(parameters) + 5) > sizeof(buffer)) + return; + + // Form the message. + //////////////////// + sprintf(buffer, "%s %s", command, parameters); + + // Send it. + /////////// + chatSendChannelMessageA(connection->chat, channel, buffer, authenticate?CHAT_ATM:CHAT_UTM); +} + +void piSendPlayerUTM +( + PEER peer, + const char * nick, + const char * command, + const char * parameters, + PEERBool authenticate +) +{ + char buffer[512]; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(nick && nick[0]); + assert(command && command[0]); + assert(parameters); + + // Check for connection succeeded. + ////////////////////////////////// + if(!connection->connected) + return; + + // Check for no nick. + ///////////////////// + if(!nick || !nick[0]) + return; + + // Check for no command. + //////////////////////// + if(!command || !command[0]) + return; + + // Check for no parameters. + /////////////////////////// + if(!parameters) + parameters = ""; + + // Make sure this UTM isn't too long. + ///////////////////////////////////// + if((strlen(command) + strlen(parameters) + 5) > sizeof(buffer)) + return; + + // Form the message. + //////////////////// + sprintf(buffer, "%s %s", command, parameters); + + // Send it. + /////////// + chatSendUserMessageA(connection->chat, nick, buffer, authenticate?CHAT_ATM:CHAT_UTM); +} + +/********* +** KEYS ** +*********/ +void peerSetGlobalKeysA +( + PEER peer, + int num, + const char ** keys, + const char ** values +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + assert(keys); + assert(values); + assert(num > 0); + + // Check for connection succeeded. + ////////////////////////////////// + if(!connection->connected) + return; + + // Set the keys. + //////////////// + chatSetGlobalKeysA(connection->chat, num, keys, values); +} +#ifdef GSI_UNICODE +void peerSetGlobalKeysW +( + PEER peer, + int num, + const unsigned short ** keys, + const unsigned short ** values +) +{ + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + char** values_A = UCS2ToUTF8StringArrayAlloc(values, num); + int i; + peerSetGlobalKeysA(peer, num, (const char**)keys_A, (const char**)values_A); + for (i=0; iconnected) + return; + + if(!nick || !nick[0]) + nick = connection->nick; + + assert(callback); + + // Start the operation. + /////////////////////// + if(!piNewGetGlobalKeysOperation(peer, nick, num, keys, callback, param, opID)) + success = PEERFalse; + + // Check for failure. + ///////////////////// + if(!success) + piAddGetGlobalKeysCallback(peer, PEERFalse, nick, 0, NULL, NULL, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerGetPlayerGlobalKeysW +( + PEER peer, + const unsigned short * nick, + int num, + const unsigned short ** keys, + peerGetGlobalKeysCallback callback, + void * param, + PEERBool blocking +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + int i; + peerGetPlayerGlobalKeysA(peer, nick_A, num, (const char**)keys_A, callback, param, blocking); + gsifree(nick_A); + for (i=0; iconnected) + return; + + ASSERT_ROOMTYPE(roomType); + assert(callback); + + // Check that we're in or entering this room. + ///////////////////////////////////////////// + if(!ENTERING_ROOM && !IN_ROOM) + return; + + // Start the operation. + /////////////////////// + if(!piNewGetGlobalKeysOperation(peer, ROOM, num, keys, callback, param, opID)) + success = PEERFalse; + + // Check for failure. + ///////////////////// + if(!success) + piAddGetGlobalKeysCallback(peer, PEERFalse, "", 0, NULL, NULL, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerGetRoomGlobalKeysW +( + PEER peer, + RoomType roomType, + int num, + const unsigned short ** keys, + peerGetGlobalKeysCallback callback, + void * param, + PEERBool blocking +) +{ + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + int i; + peerGetRoomGlobalKeysA(peer, roomType, num, (const char**)keys_A, callback, param, blocking); + for (i=0; i 0); + ASSERT_ROOMTYPE(roomType); + + // Check for connection succeeded. + ////////////////////////////////// + if(!connection->connected) + return; + + // Check that we're in or entering this room. + ///////////////////////////////////////////// + if(!ENTERING_ROOM && !IN_ROOM) + return; + + // Set the keys. + //////////////// + chatSetChannelKeysA(connection->chat, ROOM, nick, num, keys, values); +} +#ifdef GSI_UNICODE +void peerSetRoomKeysW +( + PEER peer, + RoomType roomType, + const unsigned short * nick, + int num, + const unsigned short ** keys, + const unsigned short ** values +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + char** values_A = UCS2ToUTF8StringArrayAlloc(values, num); + int i; + peerSetRoomKeysA(peer, roomType, nick_A, num, (const char**)keys_A, (const char**)values_A); + gsifree(nick_A); + for (i=0; iconnected) + return; + + ASSERT_ROOMTYPE(roomType); + assert(callback); + + // Check that we're in or entering this room. + ///////////////////////////////////////////// + if(!ENTERING_ROOM && !IN_ROOM) + return; + + // Start the operation. + /////////////////////// + if(!piNewGetRoomKeysOperation(peer, roomType, nick, num, keys, callback, param, opID)) + success = PEERFalse; + + // Check for failure. + ///////////////////// + if(!success) + piAddGetRoomKeysCallback(peer, PEERFalse, roomType, nick, 0, NULL, NULL, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerGetRoomKeysW +( + PEER peer, + RoomType roomType, + const unsigned short * nick, + int num, + const unsigned short ** keys, + peerGetRoomKeysCallback callback, + void * param, + PEERBool blocking +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + int i; + peerGetRoomKeysA(peer, roomType, nick_A, num, (const char**)keys_A, callback, param, blocking); + gsifree(nick_A); + for (i=0; i= 2); + + // Check the params. + //////////////////// + if(!filter) + filter = ""; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + goto failed; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + goto failed; + + // Check for an AutoMatch in progress. + ////////////////////////////////////// + assert(!peerIsAutoMatching(peer)); + if(peerIsAutoMatching(peer)) + goto failed; + + // If entering a staging room, leave. + ///////////////////////////////////// + if(connection->enteringRoom[StagingRoom]) + piLeaveRoom(peer, StagingRoom, ""); + + // Stop any reporting. + ////////////////////// + piStopReporting(peer); + + // Stop any game listing. + ///////////////////////// + piSBStopListingGames(peer); + + // Store some parameters. + ///////////////////////// + connection->maxPlayers = maxPlayers; + connection->autoMatchFilter = goastrdup(filter); + if(!connection->autoMatchFilter) + goto failed; + + // Initialize the AutoMatch status. + /////////////////////////////////// + connection->autoMatchStatus = PEERFailed; + + // Clear the SB and QR failed flags. + //////////////////////////////////// + connection->autoMatchSBFailed = PEERFalse; + connection->autoMatchQRFailed = PEERFalse; + + // Start the AutoMatch. + /////////////////////// + if(!piNewAutoMatchOperation(peer, socket, port, statusCallback, rateCallback, param, opID)) + { + gsifree(connection->autoMatchFilter); + goto failed; + } + + PI_DO_BLOCKING; + + return; + +failed: + // Failed to start the attempt. + /////////////////////////////// + connection->autoMatchStatus = PEERFailed; + piAddAutoMatchStatusCallback(peer); +} +#ifdef GSI_UNICODE +void peerStartAutoMatchWithSocketW +( + PEER peer, + int maxPlayers, + const unsigned short * filter, + SOCKET socket, + unsigned short port, + peerAutoMatchStatusCallback statusCallback, + peerAutoMatchRateCallback rateCallback, + void * param, + PEERBool blocking +) +{ + char* filter_A = UCS2ToUTF8StringAlloc(filter); + peerStartAutoMatchWithSocketA(peer, maxPlayers, filter_A, socket, port, statusCallback, rateCallback, param, blocking); + gsifree(filter_A); +} +#endif + +void peerStopAutoMatch(PEER peer) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return; + + // Stop the AutoMatch. + ////////////////////// + piStopAutoMatch(peer); +} + +PEERBool peerIsAutoMatching(PEER peer) +{ + PEER_CONNECTION; + + // If the status is Failed or Done, then we're not matching. + //////////////////////////////////////////////////////////// + if(connection->autoMatchStatus == PEERFailed) + return PEERFalse; + if(connection->autoMatchStatus == PEERComplete) + return PEERFalse; + + return PEERTrue; +} + +PEERAutoMatchStatus peerGetAutoMatchStatus(PEER peer) +{ + PEER_CONNECTION; + + return connection->autoMatchStatus; +} + +void peerSetStagingRoomMaxPlayers(PEER peer, int maxPlayers) +{ + PEER_CONNECTION; + GS_ASSERT(connection->inRoom[StagingRoom]); + if (connection->inRoom[StagingRoom]) + { + // Let QR2 report the new max players, and set the new chat channel limit + connection->maxPlayers = maxPlayers; + piSendStateChanged(peer); + + chatSetChannelLimitA(connection->chat, connection->rooms[StagingRoom], maxPlayers); + } +} diff --git a/code/gamespy/Peer/peerMain.h b/code/gamespy/Peer/peerMain.h new file mode 100644 index 00000000..5b459f81 --- /dev/null +++ b/code/gamespy/Peer/peerMain.h @@ -0,0 +1,223 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERMAIN_H_ +#define _PEERMAIN_H_ + +/************* +** INCLUDES ** +*************/ +#include "peer.h" +#include "../darray.h" +#include "../hashtable.h" +#include "../pinger/pinger.h" +#include "../Chat/chatASCII.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#if 0 +piConnection * connection; // This is to fool Visual Assist. +#endif + +/************ +** DEFINES ** +************/ +#define PI_ROOM_MAX_LEN 257 +#define PI_NICK_MAX_LEN 64 +#define PI_NAME_MAX_LEN 512 +#define PI_TITLE_MAX_LEN 32 +#define PI_AWAY_MAX_LEN 128 +#define PI_SB_LEN 32 // from sb_internal.h + +#ifndef PI_CHAT_PING_TIME + #ifndef _NITRO + #define PI_CHAT_PING_TIME (5 * 60000) + #else + #define PI_CHAT_PING_TIME (20000) + #endif +#endif + +#define PEER_CONNECTION piConnection * connection;\ + assert(peer);\ + connection = (piConnection *)peer;\ + GSI_UNUSED(connection); + +#define ASSERT_ROOMTYPE(type) assert((type == TitleRoom) || (type == GroupRoom) || (type == StagingRoom)) +#define ASSERT_MESSAGETYPE(type) assert((type == NormalMessage) || (type == ActionMessage) || (type == NoticeMessage)) + +#define ROOM (connection->rooms[roomType]) +#define ROOM_W (connection->rooms_W[roomType]) +#define ROOMS (connection->rooms) +#define NAME (connection->names[roomType]) +#define NAME_W (connection->names_W[roomType]) +#define NAMES (connection->names) +#define IN_ROOM (connection->inRoom[roomType]) +#define ENTERING_ROOM (connection->enteringRoom[roomType]) + +#define strzcpy(dest, src, len) { strncpy(dest, src, (len)); (dest)[(len) - 1] = '\0'; } +#define strzcat(dest, src, len) { strncat(dest, src, (len) - strlen(dest)); (dest)[(len) - 1] = '\0'; } + +#if defined(_PS3) +#define PEERCBType void* // Note: ANSI function pointers should int rather than void* +#else +#define PEERCBType int // Note: ANSI function pointers should int rather than void* +#endif + +/********** +** TYPES ** +**********/ +typedef struct piConnection +{ + // Chat. + //////// + CHAT chat; // The chat connection. + char nick[PI_NICK_MAX_LEN]; // The local nick. + PEERBool connecting; + PEERBool connected; + peerNickErrorCallback nickErrorCallback; + gsi_time lastChatPing; + + // Game. + //////// + unsigned int publicIP; + unsigned int privateIP; + int profileID; + char title[PI_TITLE_MAX_LEN]; + +#ifdef GSI_UNICODE + unsigned short title_W[PI_TITLE_MAX_LEN]; + unsigned short nick_W[PI_NICK_MAX_LEN]; + unsigned short names_W[NumRooms][PI_NAME_MAX_LEN]; + unsigned short rooms_W[NumRooms][PI_ROOM_MAX_LEN]; +#endif + + // Rooms. + ///////// + char rooms[NumRooms][PI_ROOM_MAX_LEN]; + PEERBool enteringRoom[NumRooms]; + PEERBool inRoom[NumRooms]; + char names[NumRooms][PI_NAME_MAX_LEN]; + int oldFlags[NumRooms]; + int groupID; + char titleRoomChannel[PI_ROOM_MAX_LEN]; + PEERBool stayInTitleRoom; + + // Players. + /////////// + HashTable players; + int numPlayers[NumRooms]; + PEERBool alwaysRequestPlayerInfo; + + // Ping. + //////// + PEERBool doPings; + int lastPingTimeMod; + PEERBool pingRoom[NumRooms]; + PEERBool xpingRoom[NumRooms]; + HashTable xpings; + unsigned int lastXpingSend; + + // Reporting. + ///////////// + qr2_t queryReporting; + char qrSecretKey[64]; // i ripped the length from qr2.c + PEERBool natNegotiate; + int reportingOptions; + int reportingGroupID; // might be different than groupID if left group room after started reporting + + // Hosting. + /////////// + PEERBool hosting; + PEERBool playing; + int maxPlayers; + PEERBool passwordedRoom; + + // Staging room. + //////////////// + SBServer hostServer; + PEERBool ready; + + // SB. + ////// + char sbName[PI_SB_LEN]; + char sbSecretKey[PI_SB_LEN]; + int sbGameVersion; + int sbMaxUpdates; + PEERBool sbInitialized; + SBServerList gameList; + SBServerList groupList; + SBQueryEngine gameEngine; + peerListingGamesCallback gameListCallback; + void * gameListParam; + PEERBool initialGameList; + struct piOperation * listingGroupsOperation; + + // ID. + ////// + int nextID; + + // Operations. + ////////////// + DArray operationList; + int operationsStarted; + int operationsFinished; + + // Callbacks. + ///////////// + PEERCallbacks callbacks; + DArray callbackList; + int callbacksQueued; + int callbacksCalled; + int callbackDepth; + + // Away. + //////// + PEERBool away; + char awayReason[PI_AWAY_MAX_LEN]; + + // Keys. + //////// + HashTable globalWatchKeys[NumRooms]; + HashTable roomWatchKeys[NumRooms]; + HashTable globalWatchCache; + HashTable roomWatchCache[NumRooms]; + + // AutoMatch. + ///////////// + PEERAutoMatchStatus autoMatchStatus; + SBServerList autoMatchList; + SBQueryEngine autoMatchEngine; + PEERBool autoMatchBrowsing; + struct piOperation * autoMatchOperation; + qr2_t autoMatchReporting; + char * autoMatchFilter; + PEERBool autoMatchSBFailed; + PEERBool autoMatchQRFailed; + + // Misc. + //////// + PEERBool disconnect; + PEERBool shutdown; +} piConnection; + +void piSendChannelUTM(PEER peer, const char * channel, const char * command, const char * parameters, PEERBool authenticate); +void piSendPlayerUTM(PEER peer, const char * nick, const char * command, const char * parameters, PEERBool authenticate); +PEERBool piConnectTitle(PEER peer); +void piDisconnectTitle(PEER peer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerMangle.c b/code/gamespy/Peer/peerMangle.c new file mode 100644 index 00000000..dbe02426 --- /dev/null +++ b/code/gamespy/Peer/peerMangle.c @@ -0,0 +1,292 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/* +** +** Title Room +** #GSP! +** +** Group Room +** #GPG! +** +** Staging Room +** #GSP!!XX +** +** User +** XX| +** +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include "peerMain.h" +#include "peerMangle.h" + +/************ +** DEFINES ** +************/ +#define PI_SEPERATOR "!" + +/************ +** GLOBALS ** +************/ +PEERBool piOldMangleStagingRooms; +static const char digits_hex[] = "0123456789abcdef"; +static const char digits_crypt[] = "aFl4uOD9sfWq1vGp"; +static const char new_digits_crypt[] = "qJ1h4N9cP3lzD0Ka"; +static const unsigned int ip_xormask = 0xc3801dc7; +static char cryptbuffer[32]; + +/************** +** FUNCTIONS ** +**************/ +//originally ripped from Aphex's ipencode.h +static const char * EncodeIP(unsigned int ip, char * buffer, PEERBool newCrypt) +{ + const char * crypt = newCrypt?new_digits_crypt:digits_crypt; + int i; + char * str; + int digit_idx; + + // XOR the IP address. + ip ^= ip_xormask; + + // Print out the ip addr in hex form. + sprintf(cryptbuffer, "%08x", ip); + + // Translate chars in positions 0 through 7 from hex digits to "crypt" digits. + for(i = 0 ; i < 8 ; i++) + { + str = strchr(digits_hex, cryptbuffer[i]); + digit_idx = (str - digits_hex); + + if((digit_idx < 0) || (digit_idx > 15)) // sanity check + { + strcpy(cryptbuffer, "14saFv19"); // equivalent to 0.0.0.0 + break; + } + + cryptbuffer[i] = crypt[digit_idx]; + } + + if(buffer) + { + strcpy(buffer, cryptbuffer); + return buffer; + } + + return cryptbuffer; +} + +//originally ripped from Aphex's ipencode.h +static unsigned int DecodeIP(const char * buffer, PEERBool newCrypt) +{ + const char * crypt = newCrypt?new_digits_crypt:digits_crypt; + unsigned int ip; + char * str; + int digit_idx; + int i; + + if(!buffer) + return 0; + + // Translate chars from hex digits to "crypt" digits. + for(i = 0 ; i < 8 ; i++) + { + str = strchr(crypt, buffer[i]); + digit_idx = (str - crypt); + + if((digit_idx < 0) || (digit_idx > 15)) + return 0; + + cryptbuffer[i] = digits_hex[digit_idx]; + } + + // Cap the buffer. + cryptbuffer[i] = '\0'; + + // Convert the string to an unsigned long (the XORd ip addr). + sscanf(cryptbuffer, "%x", &ip); + + // re-XOR the IP address. + ip ^= ip_xormask; + + return ip; +} + +static const char * piStagingRoomHash(unsigned int publicIP, unsigned int privateIP, unsigned short port, char * buffer) +{ + unsigned int result; + + publicIP = ntohl(publicIP); + privateIP = ntohl(privateIP); + + result = (((privateIP >> 24) & 0xFF) | ((privateIP >> 8) & 0xFF00) | ((privateIP << 8) & 0xFF0000) | ((privateIP << 24) & 0xFF000000)); + result ^= publicIP; + result ^= (port | (port << 16)); + + return EncodeIP(result, buffer, PEERTrue); +} + +void piMangleTitleRoom +( + char buffer[PI_ROOM_MAX_LEN], + const char * title +) +{ + assert(buffer); + assert(title); + assert(title[0]); + + sprintf(buffer, "#GSP" PI_SEPERATOR "%s", + title); +} + +void piMangleGroupRoom +( + char buffer[PI_ROOM_MAX_LEN], + int groupID +) +{ + assert(buffer); + assert(groupID); + + sprintf(buffer, "#GPG" PI_SEPERATOR "%d", groupID); +} + +void piMangleStagingRoom +( + char buffer[PI_ROOM_MAX_LEN], + const char * title, + unsigned int publicIP, + unsigned int privateIP, + unsigned short privatePort +) +{ + char encodeBuffer[9]; + int borderChar; + + assert(buffer); + assert(title); + assert(title[0]); + + if(piOldMangleStagingRooms) + { + EncodeIP(publicIP, encodeBuffer, PEERFalse); + borderChar = 'X'; + } + else + { + piStagingRoomHash(publicIP, privateIP, privatePort, encodeBuffer); + borderChar = 'M'; + } + + sprintf(buffer, "#GSP" PI_SEPERATOR "%s" PI_SEPERATOR "%c%s%c", title, borderChar, encodeBuffer, borderChar); +} + +void piMangleUser +( + char buffer[PI_USER_MAX_LEN], + unsigned int IP, + int profileID +) +{ + assert(buffer); + assert(IP != 0); + assert(profileID >= 0); + + sprintf(buffer, "X%sX|%d", + EncodeIP(IP, NULL, PEERFalse), + profileID); +} + +PEERBool piDemangleUser +( + const char buffer[PI_USER_MAX_LEN], + unsigned int * IP, + int * profileID +) +{ + unsigned int decodedIP; + int scannedProfileID; + + assert(buffer); + if(buffer == NULL) + return PEERFalse; + + // Check the length. + //////////////////// + if(strlen(buffer) < 12) + return PEERFalse; + + // Check for the Xs. + //////////////////// + if((buffer[0] != 'X') && (buffer[9] != 'X')) + return PEERFalse; + + // Get the IP. + ////////////// + decodedIP = DecodeIP(buffer + 1, PEERFalse); + if(!decodedIP) + return PEERFalse; + + // Check the profile ID. + //////////////////////// + if(!isdigit(buffer[11])) + return PEERFalse; + + // Get the pid. + /////////////// + scannedProfileID = atoi(buffer + 11); + + // Check what is wanted. + //////////////////////// + if(IP) + *IP = decodedIP; + if(profileID) + *profileID = scannedProfileID; + + + return PEERTrue; +} + +void piMangleIP +( + char buffer[11], + unsigned int IP +) +{ + assert(buffer); + assert(IP != 0); + + EncodeIP(IP, buffer + 1, PEERFalse); + buffer[0] = 'X'; + buffer[9] = 'X'; + buffer[10] = '\0'; +} + +unsigned int piDemangleIP +( + const char buffer[11] +) +{ + assert(buffer); + if(!buffer) + return 0; + + // Check for the Xs. + //////////////////// + if((buffer[0] != 'X') && (buffer[9] != 'X')) + return 0; + + return DecodeIP(buffer + 1, PEERFalse); +} diff --git a/code/gamespy/Peer/peerMangle.h b/code/gamespy/Peer/peerMangle.h new file mode 100644 index 00000000..4d826b60 --- /dev/null +++ b/code/gamespy/Peer/peerMangle.h @@ -0,0 +1,46 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERMANGLE_H_ +#define _PEERMANGLE_H_ + +/************* +** INCLUDES ** +*************/ +#include "peer.h" +#include "peerMain.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/************ +** DEFINES ** +************/ +#define PI_USER_MAX_LEN 128 + +/************** +** FUNCTIONS ** +**************/ +void piMangleTitleRoom(char buffer[PI_ROOM_MAX_LEN], const char * title); +void piMangleGroupRoom(char buffer[PI_ROOM_MAX_LEN], int groupID); +void piMangleStagingRoom(char buffer[PI_ROOM_MAX_LEN], const char * title, + unsigned int publicIP, unsigned int privateIP, unsigned short privatePort); +void piMangleUser(char buffer[PI_USER_MAX_LEN], unsigned int IP, int profileID); +PEERBool piDemangleUser(const char buffer[PI_USER_MAX_LEN], unsigned int * IP, int * profileID); +void piMangleIP(char buffer[11], unsigned int IP); +unsigned int piDemangleIP(const char buffer[11]); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerOperations.c b/code/gamespy/Peer/peerOperations.c new file mode 100644 index 00000000..975e09d6 --- /dev/null +++ b/code/gamespy/Peer/peerOperations.c @@ -0,0 +1,1844 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include +#include +#include +#include "peerOperations.h" +#include "peerCallbacks.h" +#include "peerGlobalCallbacks.h" +#include "peerMangle.h" +#include "peerRooms.h" +#include "peerPlayers.h" +#include "peerKeys.h" +#include "peerSB.h" +#include "peerHost.h" +#include "peerAutoMatch.h" +#include "peerQR.h" +#include "../md5.h" + +/************ +** DEFINES ** +************/ +#define PI_CHAT_SERVER_ADDRESS "peerchat." GSI_DOMAIN_NAME +#define PI_CHAT_SERVER_PORT 6667 + +#define PEER_CONNECTION_OP piOperation * operation = (piOperation *)param;\ + piConnection * connection = NULL;\ + PEER peer = NULL;\ + assert(operation && operation->peer);\ + if(operation && operation->peer)\ + peer = operation->peer;\ + connection = (piConnection *)peer;\ + GSI_UNUSED(connection); +#if 0 +// for Visual Assist +PEER peer; +piOperation * operation; +#endif + +/********** +** TYPES ** +**********/ +typedef struct piOperationContainer +{ + piOperation * operation; +} piOperationContainer; + +/************** +** FUNCTIONS ** +**************/ +#ifndef GSI_UNICODE +#define piGetGlobalKeysCallback piGetGlobalKeysCallbackA +#define piGetChannelKeysCallback piGetChannelKeysCallbackA +#define piGetPlayerInfoCallback piGetPlayerInfoCallbackA +#define piConnectNickErrorCallback piConnectNickErrorCallbackA +#define piConnectFillInUserCallback piConnectFillInUserCallbackA +#define piCreateStagingRoomEnumUsersCallback piCreateStagingRoomEnumUsersCallbackA +#define piCreateStagingRoomEnterChannelCallback piCreateStagingRoomEnterChannelCallbackA +#define piJoinRoomEnumUsersCallback piJoinRoomEnumUsersCallbackA +#define piJoinRoomEnterChannelCallback piJoinRoomEnterChannelCallbackA +#define piChangeNickCallback piChangeNickCallbackA +#define piAuthenticateCDKeyCallback piAuthenticateCDKeyCallbackA +#else +#define piGetGlobalKeysCallback piGetGlobalKeysCallbackW +#define piGetChannelKeysCallback piGetChannelKeysCallbackW +#define piGetPlayerInfoCallback piGetPlayerInfoCallbackW +#define piConnectNickErrorCallback piConnectNickErrorCallbackW +#define piConnectFillInUserCallback piConnectFillInUserCallbackW +#define piCreateStagingRoomEnumUsersCallback piCreateStagingRoomEnumUsersCallbackW +#define piCreateStagingRoomEnterChannelCallback piCreateStagingRoomEnterChannelCallbackW +#define piJoinRoomEnumUsersCallback piJoinRoomEnumUsersCallbackW +#define piJoinRoomEnterChannelCallback piJoinRoomEnterChannelCallbackW +#define piChangeNickCallback piChangeNickCallbackW +#define piAuthenticateCDKeyCallback piAuthenticateCDKeyCallbackW +#endif + +static void piOperationsListFree(void *elem1) +{ + piOperationContainer * container = (piOperationContainer *)elem1; + piOperation * operation; + + assert(container); + assert(container->operation); + + operation = container->operation; + + // Free the name. + ///////////////// + gsifree(operation->name); + + // Free the password. + ///////////////////// + gsifree(operation->password); + + // Free the user data. + ////////////////////// + gsifree(operation->data); + + // Close the socket if needed. + ////////////////////////////// + if(operation->socketClose) + { + closesocket(operation->socket); + SocketShutDown(); + } + + // Free the operation. + ////////////////////// + gsifree(operation); +} + +PEERBool piOperationsInit(PEER peer) +{ + PEER_CONNECTION; + + assert(!connection->operationList); + + // Init data. + ///////////// + connection->operationsStarted = 0; + connection->operationsFinished = 0; + + // Init the list. + ///////////////// + connection->operationList = ArrayNew(sizeof(piOperationContainer), 0, piOperationsListFree); + if(!connection->operationList) + return PEERFalse; + + return PEERTrue; +} + +void piOperationsReset(PEER peer) +{ + PEER_CONNECTION; + + // Clear the list. + ////////////////// + if(connection->operationList) + ArrayClear(connection->operationList); + + // Clear the listingGroupsOperation + if (connection->listingGroupsOperation) + connection->listingGroupsOperation = NULL; +} + +void piOperationsCleanup(PEER peer) +{ + PEER_CONNECTION; + + // Cleanup the list. + //////////////////// + if(connection->operationList) + ArrayFree(connection->operationList); + connection->operationList = NULL; +} + +void piRemoveOperation(PEER peer, piOperation * operation) +{ + piOperationContainer * container; + piOperation * op; + int i; + int count; + + PEER_CONNECTION; + + // Make sure there is a list. + ///////////////////////////// + if(!connection->operationList) + return; + + // Loop through the operations. + /////////////////////////////// + count = ArrayLength(connection->operationList); + for(i = 0 ; i < count ; i++) + { + // Get the operation. + ///////////////////// + container = (piOperationContainer *)ArrayNth(connection->operationList, i); + op = container->operation; + + // Check the op. + //////////////// + if(op == operation) + { + // Remove it. + ///////////// + ArrayDeleteAt(connection->operationList, i); + + // One more finished. + ///////////////////// + connection->operationsFinished++; + + return; + } + } +} + +PEERBool piIsOperationFinished(PEER peer, int opID) +{ + piOperationContainer * container; + piOperation * op; + int i; + int count; + + PEER_CONNECTION; + + // Make sure there is a list. + ///////////////////////////// + if(!connection->operationList) + return PEERTrue; + + // Loop through the operations. + /////////////////////////////// + count = ArrayLength(connection->operationList); + for(i = 0 ; i < count ; i++) + { + // Get the operation. + ///////////////////// + container = (piOperationContainer *)ArrayNth(connection->operationList, i); + op = container->operation; + + // Check the ID. + //////////////// + if(op->ID == opID) + return PEERFalse; + } + + return PEERTrue; +} + +void piCancelJoinOperation(PEER peer, RoomType roomType) +{ + piOperationContainer * container; + piOperation * operation; + int i; + int count; + + PEER_CONNECTION; + + // Make sure there is a list. + ///////////////////////////// + if(!connection->operationList) + return; + + // Loop through the operations. + /////////////////////////////// + count = ArrayLength(connection->operationList); + for(i = 0 ; i < count ; i++) + { + // Get the operation. + ///////////////////// + container = (piOperationContainer *)ArrayNth(connection->operationList, i); + operation = container->operation; + + // Is it a join or create? + ////////////////////////// + if((operation->type == PI_JOIN_ROOM_OPERATION) || (operation->type == PI_CREATE_ROOM_OPERATION)) + { + // Is it the same room? + /////////////////////// + if(operation->roomType == roomType) + { + // Cancel the operation. + //////////////////////// + operation->cancel = PEERTrue; + + return; + } + } + } +} + +int piGetNextID(PEER peer) +{ + int ID; + + PEER_CONNECTION; + + // Get the ID. + ////////////// + ID = connection->nextID; + + // Increment. + ///////////// + connection->nextID++; + if(connection->nextID < 0) + connection->nextID = 0; + + return ID; +} + +static piOperation * piAddOperation +( + PEER peer, + piOperationType type, + void * data, + PEERCBType callback, + void * callbackParam, + int opID +) +{ + piOperation * operation; + piOperationContainer container; + + PEER_CONNECTION; + + //assert(type >= 0); + assert(type < PI_NUM_OPERATION_TYPES); + + // Make sure there is a list. + ///////////////////////////// + if(!connection->operationList) + return NULL; + + // Alloc the operaiton. + /////////////////////// + operation = (piOperation *)gsimalloc(sizeof(piOperation)); + if(!operation) + return NULL; + + // Fill in the operation. + ///////////////////////// + memset(operation, 0, sizeof(piOperation)); + operation->peer = peer; + operation->type = type; + operation->data = data; + operation->ID = opID; + operation->callback = callback; + operation->callbackParam = callbackParam; + operation->name = NULL; + operation->cancel = PEERFalse; + + // Add the operation to the list. + ///////////////////////////////// + container.operation = operation; + ArrayAppend(connection->operationList, &container); + + // One more op. + /////////////// + connection->operationsStarted++; + + return operation; +} + +/*************** +** OPERATIONS ** +***************/ + +/* Connect. +**********/ +static void piConnectConnectCallback +( + CHAT chat, + CHATBool success, + int failureReason, + void *param +) +{ + PEER_CONNECTION_OP; + + // If successful, try and do the connect title stuff. + ///////////////////////////////////////////////////// + if(success) + { + if(!piConnectTitle(peer)) + { + piDisconnectTitle(peer); + success = CHATFalse; + } + } + + // Connection attempt finished. + /////////////////////////////// + connection->connecting = PEERFalse; + connection->connected = (PEERBool)success; + + if(success) + { + const char * nick; + + // Setup server pinging. + //////////////////////// + connection->lastChatPing = current_time(); + + // Check the nick. + ////////////////// + nick = chatGetNickA(chat); + if(strcasecmp(connection->nick, nick) != 0) + { + strcpy(connection->nick, nick); + +#ifdef GSI_UNICODE + UTF8ToUCS2String(connection->nick, connection->nick_W); +#endif + } + } + else + { + // Set the disconnect flag. + /////////////////////////// + connection->disconnect = PEERTrue; + } + + // Add the callback. + //////////////////// + piAddConnectCallback(peer, (PEERBool)success, failureReason, (peerConnectCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + piRemoveOperation(peer, operation); +} + +static void piConnectNickErrorCallbackA +( + CHAT chat, + int type, + const char * nick, + int numSuggestedNicks, + const char ** suggestedNicks, + void *param +) +{ + PEER_CONNECTION_OP; + + // Add the peer callback. + ///////////////////////// + piAddNickErrorCallback(peer, type, nick, numSuggestedNicks, suggestedNicks, operation->callbackParam, operation->ID); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piConnectNickErrorCallbackW +( + CHAT chat, + int type, + const unsigned short * nick, + int numSuggestedNicks, + const unsigned short ** suggestedNicks, + void *param +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char** suggestedNicks_A = UCS2ToUTF8StringArrayAlloc(suggestedNicks, numSuggestedNicks); + int i; + piConnectNickErrorCallbackA(chat, type, nick_A, numSuggestedNicks, (const char**)suggestedNicks_A, param); + gsifree(nick_A); + if(suggestedNicks_A) + { + for (i=0; ipublicIP = IP; + + // If chat has a pid, override ours. + //////////////////////////////////// + pid = chatGetProfileID(connection->chat); + if(pid) + connection->profileID = pid; + + // Mangle the IP and pid into the user. + /////////////////////////////////////// + piMangleUser(user, connection->publicIP, connection->profileID); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piConnectFillInUserCallbackW +( + CHAT chat, + unsigned int IP, + unsigned short user[128], // OUT parameter!! + void * param +) +{ + // This one is reversed! Call the callback, THEN convert + char user_A[256]; + piConnectFillInUserCallbackA(chat, IP, user_A, param); + UTF8ToUCS2String(user_A, user); +} +#endif + +PEERBool piNewConnectOperation +( + PEER peer, + piConnectType connectType, + const char * nick, + int namespaceID, + const char * email, + const char * profilenick, + const char * uniquenick, + const char * password, + const char * authtoken, + const char * partnerchallenge, + peerConnectCallback callback, + void * callbackParam, + int opID +) +{ + piOperation * operation; + chatGlobalCallbacks globalCallbacks; + const char * uniqueID; + char encodedUniqueID[33]; + peerNickErrorCallback nickErrorCallback; + + PEER_CONNECTION; + + assert(callback); + +#ifdef _DEBUG + if(connectType == PI_CONNECT) + { + assert(nick && nick[0]); + } + else if(connectType == PI_CONNECT_UNIQUENICK_LOGIN) + { + assert(namespaceID > 0); + assert(uniquenick && uniquenick[0]); + assert(password && password[0]); + } + else if(connectType == PI_CONNECT_PROFILENICK_LOGIN) + { + assert(namespaceID >= 0); + assert(email && email[0]); + assert(profilenick && profilenick[0]); + assert(password && password[0]); + } + else if(connectType == PI_CONNECT_PREAUTH) + { + assert(authtoken && authtoken[0]); + assert(partnerchallenge && partnerchallenge[0]); + } +#endif + + // Add an operation. + //////////////////// + operation = piAddOperation(peer, PI_CONNECT_OPERATION, NULL, (PEERCBType)callback, callbackParam, opID); + if(!operation) + return PEERFalse; + + // Setup the global callbacks. + ////////////////////////////// + memset(&globalCallbacks, 0, sizeof(chatGlobalCallbacks)); + globalCallbacks.disconnected = piChatDisconnected; + globalCallbacks.privateMessage = piChatPrivateMessage; + globalCallbacks.param = peer; + + // Encode the unique ID. + //////////////////////// + uniqueID = GOAGetUniqueID(); + MD5Digest((unsigned char *)uniqueID, strlen(uniqueID), encodedUniqueID); + + // Connect to chat. + /////////////////// + nickErrorCallback = (connection->nickErrorCallback ? piConnectNickErrorCallback : NULL); + if(connectType == PI_CONNECT) + { + connection->chat = chatConnectSecureA( + PI_CHAT_SERVER_ADDRESS, + PI_CHAT_SERVER_PORT, + nick, + encodedUniqueID, + connection->sbName, + connection->sbSecretKey, + &globalCallbacks, + nickErrorCallback, + piConnectFillInUserCallback, + piConnectConnectCallback, + operation, + CHATFalse); + } + else if((connectType == PI_CONNECT_UNIQUENICK_LOGIN) || (connectType == PI_CONNECT_PROFILENICK_LOGIN)) + { + connection->chat = chatConnectLoginA( + PI_CHAT_SERVER_ADDRESS, + PI_CHAT_SERVER_PORT, + namespaceID, + email, + profilenick, + uniquenick, + password, + encodedUniqueID, + connection->sbName, + connection->sbSecretKey, + &globalCallbacks, + nickErrorCallback, + piConnectFillInUserCallback, + piConnectConnectCallback, + operation, + CHATFalse); + } + else if(connectType == PI_CONNECT_PREAUTH) + { + connection->chat = chatConnectPreAuthA( + PI_CHAT_SERVER_ADDRESS, + PI_CHAT_SERVER_PORT, + authtoken, + partnerchallenge, + encodedUniqueID, + connection->sbName, + connection->sbSecretKey, + &globalCallbacks, + nickErrorCallback, + piConnectFillInUserCallback, + piConnectConnectCallback, + operation, + CHATFalse); + } + if(!connection->chat) + { + piRemoveOperation(peer, operation); + return PEERFalse; + } + + return PEERTrue; +} + +/* Create Staging Room. +**********************/ +static PEERJoinResult piEnterResultToJoinResult +( + CHATEnterResult result +) +{ + switch(result) + { + case CHATEnterSuccess: + return PEERJoinSuccess; + case CHATChannelIsFull: + return PEERFullRoom; + case CHATInviteOnlyChannel: + return PEERInviteOnlyRoom; + case CHATBannedFromChannel: + return PEERBannedFromRoom; + case CHATBadChannelPassword: + return PEERBadPassword; + default: + break; + } + + return PEERJoinFailed; +} + +static void piCreateStagingRoomEnumUsersCallbackA +( + CHAT chat, + CHATBool success, + const char * channel, + int numUsers, + const char ** users, + int * modes, + void *param +) +{ + PEER_CONNECTION_OP; + + // Check if this was cancelled. + /////////////////////////////// + if(operation->cancel) + { + piRemoveOperation(peer, operation); + return; + } + + // Start hosting/reporting. + /////////////////////////// + if(success) + { + if(!peerIsAutoMatching(peer)) + { + if(!piStartHosting(peer, operation->socket, operation->port)) + success = CHATFalse; + else + { + // If we created the socket, hand over responsibility for it to qr2. + //////////////////////////////////////////////////////////////////// + if(operation->socketClose) + { + operation->socketClose = PEERFalse; + connection->queryReporting->read_socket = 1; + } + } + } + else + { + connection->hosting = PEERTrue; + } + } + + // Do stuff based on success. + ///////////////////////////// + if(success) + { + int i; + + // Done entering. + ///////////////// + piFinishedEnteringRoom(peer, StagingRoom, operation->name); + + // Add everyone to the room. + //////////////////////////// + for(i = 0 ; i < numUsers ; i++) + piPlayerJoinedRoom(peer, users[i], StagingRoom, modes[i]); + + // Set the name. + //////////////// + chatSetChannelTopicA(connection->chat, channel, operation->name); + + // Set a limit on the room. + /////////////////////////// +#ifndef PI_NO_STAGING_ROOM_LIMIT + if(connection->maxPlayers) + chatSetChannelLimitA(connection->chat, channel, connection->maxPlayers); +#endif + + // If this is AutoMatch, and we created a socket, hand it over. + /////////////////////////////////////////////////////////////// + if(operation->socketClose && peerIsAutoMatching(peer)) + { + connection->autoMatchOperation->socket = operation->socket; + connection->autoMatchOperation->port = operation->port; + connection->autoMatchOperation->socketClose = PEERTrue; + operation->socketClose = PEERFalse; + } + } + else + { + // Leave the room. + ////////////////// + piLeaveRoom(peer, StagingRoom, NULL); + } + + // Add the callback. + //////////////////// + piAddJoinRoomCallback(peer, (PEERBool)success, success?PEERJoinSuccess:PEERJoinFailed, StagingRoom, (peerJoinRoomCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + piRemoveOperation(peer, operation); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piCreateStagingRoomEnumUsersCallbackW +( + CHAT chat, + CHATBool success, + const unsigned short * channel, + int numUsers, + const unsigned short ** users, + int * modes, + void *param +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char** users_A = UCS2ToUTF8StringArrayAlloc(users, numUsers); + int i; + piCreateStagingRoomEnumUsersCallbackA(chat, success, channel_A, numUsers, (const char**)users_A, modes, param); + gsifree(channel_A); + for (i=0; iLimit = connection->maxPlayers; + mode->OpsObeyChannelLimit = CHATTrue; + + // Don't let ops bypass channel limit on the number of players in room + chatSetChannelMode(chat, channel, mode); + } + +} + + +static void piCreateStagingRoomEnterChannelCallbackA +( + CHAT chat, + CHATBool success, + CHATEnterResult result, + const char * channel, + void *param +) +{ + PEER_CONNECTION_OP; + + assert(channel); + assert(channel[0]); + + // Check if this was cancelled. + /////////////////////////////// + if(operation->cancel) + { + piRemoveOperation(peer, operation); + return; + } + + if(success) + { + // If passworded, set it. + ///////////////////////// + if(operation->password) + chatSetChannelPasswordA(connection->chat, channel, CHATTrue, operation->password); + + + // get the channel modes so we can set the limits on our staging channel + chatGetChannelMode(connection->chat, peerGetRoomChannel(peer, StagingRoom), piCreateStagingRoomGetChannelModeCallback, + (void *)connection, CHATFalse); + + + // How many users? + ////////////////// + chatEnumUsersA(chat, channel, piCreateStagingRoomEnumUsersCallback, operation, CHATFalse); + } + else + { + PEERJoinResult joinResult; + + // Not entering. + //////////////// + piLeaveRoom(peer, StagingRoom, NULL); + + // Get the peer result. + /////////////////////// + if(result == CHATEnterSuccess) + joinResult = PEERJoinFailed; + else + joinResult = piEnterResultToJoinResult(result); + + // Add the callback. + //////////////////// + piAddJoinRoomCallback(peer, PEERFalse, joinResult, StagingRoom, (peerJoinRoomCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + piRemoveOperation(peer, operation); + } +} +#ifdef GSI_UNICODE +static void piCreateStagingRoomEnterChannelCallbackW +( + CHAT chat, + CHATBool success, + CHATEnterResult result, + const unsigned short * channel, + void *param +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + piCreateStagingRoomEnterChannelCallbackA(chat, success, result, channel_A, param); + gsifree(channel_A); +} +#endif + +// internal QR2 function +qr2_error_t qr2_create_socket(/*[out]*/SOCKET *sock, const char *ip, /*[in/out]*/int * port); + +PEERBool piNewCreateStagingRoomOperation +( + PEER peer, + const char * name, + const char * password, + int maxPlayers, + SOCKET socket, + unsigned short port, + peerJoinRoomCallback callback, + void * callbackParam, + int opID +) +{ + piOperation * operation; + chatChannelCallbacks channelCallbacks; + char room[PI_ROOM_MAX_LEN]; + PEERBool createdSocket = PEERFalse; + + PEER_CONNECTION; + + assert(name); + if(!name) + name = ""; + + assert(callback); + if(!callback) + return PEERFalse; + + // Save off the maxplayers. + /////////////////////////// + connection->maxPlayers = maxPlayers; + + // If we don't have a socket, create one to use. + //////////////////////////////////////////////// + if(socket == INVALID_SOCKET) + { + IN_ADDR addr; + qr2_error_t rcode; + int privatePort; + + addr.s_addr = 0;//crt -- don't bind to privateIP (for clients that have both public and private) connection->privateIP; + if(port) + privatePort = port; + else + privatePort = PI_QUERYPORT; + + rcode = qr2_create_socket(&socket, inet_ntoa(addr), &privatePort); + if(rcode != e_qrnoerror) + return PEERFalse; + + port = (unsigned short)privatePort; + createdSocket = PEERTrue; + } + + // Get the room name. + ///////////////////// + piMangleStagingRoom(room, connection->title, connection->publicIP, connection->privateIP, port); + assert(room[0]); + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_CREATE_ROOM_OPERATION, NULL, (PEERCBType)callback, callbackParam, opID); + if(!operation) + return PEERFalse; + operation->socketClose = createdSocket; + operation->name = goastrdup(name); + if(!operation->name) + { + piRemoveOperation(peer, operation); + return PEERFalse; + } + operation->socket = socket; + operation->port = port; + operation->roomType = StagingRoom; + if(password[0]) + operation->password = goastrdup(password); + + // Set the callbacks. + ///////////////////// + piSetChannelCallbacks(peer, &channelCallbacks); + + // Create the room. + /////////////////// + piStartedEnteringRoom(peer, StagingRoom, room); + chatEnterChannelA(connection->chat, room, NULL, &channelCallbacks, piCreateStagingRoomEnterChannelCallback, operation, CHATFalse); + + // Store passworded flag if needed. + /////////////////////////////////// + if(password[0]) + connection->passwordedRoom = PEERTrue; + + return PEERTrue; +} + +/* Join Room. +************/ +static void piJoinRoomEnumUsersCallbackA +( + CHAT chat, + CHATBool success, + const char * channel, + int numUsers, + const char ** users, + int * modes, + void * param +) +{ + PEER_CONNECTION_OP; + + // Check if this was cancelled. + /////////////////////////////// + if(operation->cancel) + { + piRemoveOperation(peer, operation); + return; + } + + // Check for success. + ///////////////////// + if(success) + { + int i; + + // Finished entering the room. + ////////////////////////////// + piFinishedEnteringRoom(peer, operation->roomType, ""); + + // Add all these people to the room. + //////////////////////////////////// + for(i = 0 ; i < numUsers ; i++) + piPlayerJoinedRoom(peer, users[i], operation->roomType, modes[i]); + } + else + { + // Leave the room. + ////////////////// + piLeaveRoom(peer, operation->roomType, NULL); + } + + // Add the callback. + //////////////////// + piAddJoinRoomCallback(peer, (PEERBool)success, success?PEERJoinSuccess:PEERJoinFailed, operation->roomType, (peerJoinRoomCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + piRemoveOperation(peer, operation); + + GSI_UNUSED(chat); + GSI_UNUSED(channel); +} +#ifdef GSI_UNICODE +static void piJoinRoomEnumUsersCallbackW +( + CHAT chat, + CHATBool success, + const unsigned short * channel, + int numUsers, + const unsigned short ** users, + int * modes, + void * param +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char** users_A = UCS2ToUTF8StringArrayAlloc(users, numUsers); + int i; + piJoinRoomEnumUsersCallbackA(chat, success, channel_A, numUsers, (const char**)users_A, modes, param); + gsifree(channel_A); + for (i=0; icancel) + { + piSBFreeHostServer(peer); + piRemoveOperation(peer, operation); + return; + } + + if(success) + { + // How many users? + ////////////////// + chatEnumUsersA(chat, channel, piJoinRoomEnumUsersCallback, operation, CHATFalse); + } + else + { + PEERJoinResult joinResult; + + // Not entering. + //////////////// + piLeaveRoom(peer, operation->roomType, NULL); + + // Get the peer result. + /////////////////////// + if(result == CHATEnterSuccess) + joinResult = PEERJoinFailed; + else + joinResult = piEnterResultToJoinResult(result); + + // Add the callback. + //////////////////// + piAddJoinRoomCallback(peer, PEERFalse, joinResult, operation->roomType, (peerJoinRoomCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + piRemoveOperation(peer, operation); + } +} +#ifdef GSI_UNICODE +static void piJoinRoomEnterChannelCallbackW +( + CHAT chat, + CHATBool success, + CHATEnterResult result, + const unsigned short * channel, + void *param +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + piJoinRoomEnterChannelCallbackA(chat, success, result, channel_A, param); + gsifree(channel_A); +} +#endif + +PEERBool piNewJoinRoomOperation +( + PEER peer, + RoomType roomType, + const char * channel, + const char * password, + peerJoinRoomCallback callback, + void * callbackParam, + int opID +) +{ + piOperation * operation; + chatChannelCallbacks channelCallbacks; + + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + assert(callback); + + // Check the name. + ////////////////// + assert(channel); + assert(channel[0]); + if(!channel || !channel[0]) + return PEERFalse; + + // Check password. + ////////////////// + if(!password) + password = ""; + + // Check that the name isn't too long. + ////////////////////////////////////// + assert(strlen(channel) < PI_ROOM_MAX_LEN); + if(strlen(channel) >= PI_ROOM_MAX_LEN) + return PEERFalse; + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_JOIN_ROOM_OPERATION, NULL, (PEERCBType)callback, callbackParam, opID); + if(!operation) + return PEERFalse; + operation->roomType = roomType; + + // Set the callbacks. + ///////////////////// + piSetChannelCallbacks(peer, &channelCallbacks); + + // Join the room. + ///////////////// + piStartedEnteringRoom(peer, roomType, channel); + chatEnterChannelA(connection->chat, channel, password, &channelCallbacks, piJoinRoomEnterChannelCallback, operation, CHATFalse); + + return PEERTrue; +} + +/* List Group Rooms. +*******************/ +PEERBool piNewListGroupRoomsOperation +( + PEER peer, + const char * fields, + peerListGroupRoomsCallback callback, + void * param, + int opID +) +{ + piOperation * operation; + + PEER_CONNECTION; + + assert(callback); + + // Add the operation. + ///////////////////// + operation = connection->listingGroupsOperation = piAddOperation(peer, PI_LIST_GROUP_ROOMS_OPERATION, NULL, (PEERCBType)callback, param, opID); + if(!operation) + return PEERFalse; + + // Start the listing. + ///////////////////// + return piSBStartListingGroups(peer, fields); +} + +/* Get Player Info. +*******************/ +static void piGetPlayerInfoCallbackA +( + CHAT chat, + CHATBool success, + const char * nick, + const char * user, + const char * address, + void * param +) +{ + int profileID = 0; + unsigned int IP = 0; + + PEER_CONNECTION_OP; + + assert(nick); + assert(nick[0]); + + // Check for success. + ///////////////////// + if(success) + { + assert(user); + assert(user[0]); + + // Get the info. + //////////////// + if(!piDemangleUser(user, &IP, &profileID)) + success = CHATFalse; + + // Cache the info. + ////////////////// + if(success) + piSetPlayerIPAndProfileID(peer, nick, IP, profileID); + } + if(!success) + { + profileID = 0; + IP = 0; + } + + // Add the callback. + //////////////////// + if(operation->callback) + { + if(operation->type == PI_GET_PLAYER_INFO_OPERATION) + piAddGetPlayerInfoCallback(peer, (PEERBool)success, nick, IP, profileID, (peerGetPlayerInfoCallback)operation->callback, operation->callbackParam, operation->ID); + else if(operation->type == PI_GET_PROFILE_ID_OPERATION) + piAddGetPlayerProfileIDCallback(peer, (PEERBool)success, nick, profileID, (peerGetPlayerProfileIDCallback)operation->callback, operation->callbackParam, operation->ID); + else if(operation->type == PI_GET_IP_OPERATION) + piAddGetPlayerIPCallback(peer, (PEERBool)success, nick, IP, (peerGetPlayerIPCallback)operation->callback, operation->callbackParam, operation->ID); + else + assert(0); + } + + // Remove the operation. + //////////////////////// + piRemoveOperation(peer, operation); + + GSI_UNUSED(chat); + GSI_UNUSED(address); +} +#ifdef GSI_UNICODE +static void piGetPlayerInfoCallbackW +( + CHAT chat, + CHATBool success, + const unsigned short * nick, + const unsigned short * user, + const unsigned short * address, + void * param +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char* user_A = UCS2ToUTF8StringAlloc(user); + char* address_A = UCS2ToUTF8StringAlloc(address); + piGetPlayerInfoCallbackA(chat, success, nick_A, user_A, address_A, param); + gsifree(nick_A); + gsifree(user_A); + gsifree(address_A); +} +#endif + +PEERBool piNewGetPlayerInfoOperation +( + PEER peer, + const char * nick, + peerGetPlayerInfoCallback callback, + void * param, + int opID +) +{ + piOperation * operation; + + PEER_CONNECTION; + + assert(nick); + assert(nick[0]); + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_GET_PLAYER_INFO_OPERATION, NULL, (PEERCBType)callback, param, opID); + if(!operation) + return PEERFalse; + + // Get the user's info. + /////////////////////// + chatGetBasicUserInfoA(connection->chat, nick, piGetPlayerInfoCallback, operation, CHATFalse); + + return PEERTrue; +} + +PEERBool piNewGetProfileIDOperation +( + PEER peer, + const char * nick, + peerGetPlayerProfileIDCallback callback, + void * param, + int opID +) +{ + piOperation * operation; + + PEER_CONNECTION; + + assert(nick); + assert(nick[0]); + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_GET_PROFILE_ID_OPERATION, NULL, (PEERCBType)callback, param, opID); + if(!operation) + return PEERFalse; + + // Get the user's info. + /////////////////////// + chatGetBasicUserInfoA(connection->chat, nick, piGetPlayerInfoCallback, operation, CHATFalse); + + return PEERTrue; +} + +PEERBool piNewGetIPOperation +( + PEER peer, + const char * nick, + peerGetPlayerIPCallback callback, + void * param, + int opID +) +{ + piOperation * operation; + + PEER_CONNECTION; + + assert(nick); + assert(nick[0]); + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_GET_IP_OPERATION, NULL, (PEERCBType)callback, param, opID); + if(!operation) + return PEERFalse; + + // Get the user's info. + /////////////////////// + chatGetBasicUserInfoA(connection->chat, nick, piGetPlayerInfoCallback, operation, CHATFalse); + + return PEERTrue; +} + +/* Change Nick. +**************/ +static void piChangeNickCallbackA +( + CHAT chat, + CHATBool success, + const char * oldNick, + const char * newNick, + void * param +) +{ + PEER_CONNECTION_OP; + + // Check for success. + ///////////////////// + if(success) + { + // Update the nick locally. + /////////////////////////// + strcpy(connection->nick, newNick); + +#ifdef GSI_UNICODE + UTF8ToUCS2String(connection->nick, connection->nick_W); +#endif + } + + // Add the callback. + //////////////////// + if(operation->callback) + piAddChangeNickCallback(peer, (PEERBool)success, oldNick, newNick, (peerChangeNickCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + piRemoveOperation(peer, operation); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piChangeNickCallbackW +( + CHAT chat, + CHATBool success, + const unsigned short * oldNick, + const unsigned short * newNick, + void * param +) +{ + char* oldNick_A = UCS2ToUTF8StringAlloc(oldNick); + char* newNick_A = UCS2ToUTF8StringAlloc(newNick); + piChangeNickCallbackA(chat, success, oldNick_A, newNick_A, param); + gsifree(oldNick_A); + gsifree(newNick_A); +} +#endif + +PEERBool piNewChangeNickOperation +( + PEER peer, + const char * newNick, + peerChangeNickCallback callback, + void * param, + int opID +) +{ + piOperation * operation; + + PEER_CONNECTION; + + assert(newNick); + assert(newNick[0]); + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_CHANGE_NICK_OPERATION, NULL, (PEERCBType)callback, param, opID); + if(!operation) + return PEERFalse; + + // Change the nick. + /////////////////// + chatChangeNickA(connection->chat, newNick, piChangeNickCallback, operation, CHATFalse); + + return PEERTrue; +} + +/* Get Global Keys. +******************/ +static void piGetGlobalKeysCallbackA +( + CHAT chat, + CHATBool success, + const char * user, + int num, + const char ** keys, + const char ** values, + void * param +) +{ + int i; + + PEER_CONNECTION_OP; + + // Update the watch keys. + ///////////////////////// + if(success && user) + { + for(i = 0 ; i < num ; i++) + piGlobalKeyChanged(peer, user, keys[i], values[i]); + } + + // Add the callback. + //////////////////// + if(operation->callback) + piAddGetGlobalKeysCallback(peer, (PEERBool)success, user, num, keys, values, (peerGetGlobalKeysCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + if(!success || !user || !operation->num) + piRemoveOperation(peer, operation); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piGetGlobalKeysCallbackW +( + CHAT chat, + CHATBool success, + const unsigned short * user, + int num, + const unsigned short ** keys, + const unsigned short ** values, + void * param +) +{ + char* user_A = UCS2ToUTF8StringAlloc(user); + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + char** values_A = UCS2ToUTF8StringArrayAlloc(values, num); + int i; + piGetGlobalKeysCallbackA(chat, success, user_A, num, (const char**)keys_A, (const char**)values_A, param); + gsifree(user_A); + for (i=0; i 0); + assert(keys); + + if(!target || !target[0]) + return PEERFalse; + if(num <= 0) + return PEERFalse; + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_GET_GLOBAL_KEYS_OPERATION, NULL, (PEERCBType)callback, param, opID); + if(!operation) + return PEERFalse; + + // Use num as a flag for getting a whole channel. + ///////////////////////////////////////////////// + operation->num = (target[0] == '#'); + + // Get the keys. + //////////////// + chatGetGlobalKeysA(connection->chat, target, num, keys, piGetGlobalKeysCallback, operation, CHATFalse); + + return PEERTrue; +} + +/* Get Room Keys. +****************/ +static void piGetChannelKeysCallbackA +( + CHAT chat, + CHATBool success, + const char * channel, + const char * user, + int num, + const char ** keys, + const char ** values, + void * param +) +{ + int i; + + PEER_CONNECTION_OP; + + // Update the watch keys. + ///////////////////////// + if(success && user) + { + for(i = 0 ; i < num ; i++) + piRoomKeyChanged(peer, operation->roomType, user, keys[i], values[i]); + } + + // Add the callback. + //////////////////// + if(operation->callback) + piAddGetRoomKeysCallback(peer, (PEERBool)success, operation->roomType, user, num, keys, values, (peerGetRoomKeysCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + if(!success || !user || !operation->num) + piRemoveOperation(peer, operation); + + GSI_UNUSED(chat); + GSI_UNUSED(channel); +} +#ifdef GSI_UNICODE +static void piGetChannelKeysCallbackW +( + CHAT chat, + CHATBool success, + const unsigned short * channel, + const unsigned short * user, + int num, + const unsigned short ** keys, + const unsigned short ** values, + void * param +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* user_A = UCS2ToUTF8StringAlloc(user); + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + char** values_A = UCS2ToUTF8StringArrayAlloc(values, num); + int i; + piGetChannelKeysCallbackA(chat, success, channel_A, user_A, num, (const char**)keys_A, (const char**)values_A, param); + gsifree(channel_A); + gsifree(user_A); + for (i=0; i= 0); + assert(!num || keys); + + if(num < 0) + return PEERFalse; + if((num > 0) && !keys) + return PEERFalse; + + if(!ENTERING_ROOM && !IN_ROOM) + return PEERFalse; + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_GET_ROOM_KEYS_OPERATION, NULL, (PEERCBType)callback, param, opID); + if(!operation) + return PEERFalse; + operation->roomType = roomType; + + // Use num as a flag for getting a whole channel. + ///////////////////////////////////////////////// + if(nick) + operation->num = (strcmp(nick, "*") == 0); + else + operation->num = 0; + + // Get the keys. + //////////////// + chatGetChannelKeysA(connection->chat, ROOM, nick, num, keys, piGetChannelKeysCallback, operation, CHATFalse); + + return PEERTrue; +} + +/* Authenticate CD Key +*********************/ +static void piAuthenticateCDKeyCallbackA +( + CHAT chat, + int result, + const char * message, + void * param +) +{ + + PEER_CONNECTION_OP; + + // Add the callback. + //////////////////// + if(operation->callback) + piAddAuthenticateCDKeyCallback(peer, result, message, (peerAuthenticateCDKeyCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + piRemoveOperation(peer, operation); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piAuthenticateCDKeyCallbackW +( + CHAT chat, + int result, + const unsigned short * message, + void * param +) +{ + char* message_A = UCS2ToUTF8StringAlloc(message); + piAuthenticateCDKeyCallbackA(chat, result, message_A, param); + gsifree(message_A); +} +#endif + +PEERBool piNewAuthenticateCDKeyOperation +( + PEER peer, + const char * cdkey, + peerAuthenticateCDKeyCallback callback, + void * param, + int opID +) +{ + piOperation * operation; + + PEER_CONNECTION; + + assert(cdkey); + assert(cdkey[0]); + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_AUTHENTICATE_CDKEY_OPERATION, NULL, (PEERCBType)callback, param, opID); + if(!operation) + return PEERFalse; + + // Check the CD key. + //////////////////// + chatAuthenticateCDKeyA(connection->chat, cdkey, piAuthenticateCDKeyCallback, operation, CHATFalse); + + return PEERTrue; +} + +/* AutoMatch +***********/ +PEERBool piNewAutoMatchOperation +( + PEER peer, + SOCKET socket, + unsigned short port, + peerAutoMatchStatusCallback statusCallback, + peerAutoMatchRateCallback rateCallback, + void * param, + int opID +) +{ + piOperation * operation; + PEERAutoMatchStatus status; + + PEER_CONNECTION; + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_AUTO_MATCH_OPERATION, NULL, (PEERCBType)statusCallback, param, opID); + if(!operation) + return PEERFalse; + + // Store the second callback. + ///////////////////////////// + operation->callback2 = (PEERCBType)rateCallback; + + // Store the socket and port. + ///////////////////////////// + operation->socket = socket; + operation->port = port; + + // Track this op. + ///////////////// + connection->autoMatchOperation = operation; + + // Figure out which status to use. + ////////////////////////////////// +/* if(connection->inRoom[StagingRoom]) + { + if(connection->numPlayers[StagingRoom] <= 1) + { + if(connection->hosting) + status = PEERWaiting; + else + status = PEERStaging; + } + else if(connection->numPlayers[StagingRoom] >= connection->maxPlayers) + { + status = PEERReady; + } + else + { + status = PEERStaging; + } + } + else*/ + { + status = PEERSearching; + } + + // Set the status. + ////////////////// + piSetAutoMatchStatus(peer, status); + + return PEERTrue; +} diff --git a/code/gamespy/Peer/peerOperations.h b/code/gamespy/Peer/peerOperations.h new file mode 100644 index 00000000..b0675ccd --- /dev/null +++ b/code/gamespy/Peer/peerOperations.h @@ -0,0 +1,221 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEEROPERATIONS_H_ +#define _PEEROPERATIONS_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/********** +** TYPES ** +**********/ +typedef enum piOperationType +{ + PI_CONNECT_OPERATION, + PI_CREATE_ROOM_OPERATION, + PI_JOIN_ROOM_OPERATION, + PI_ENUM_PLAYERS_OPERATION, + PI_LIST_GROUP_ROOMS_OPERATION, + PI_LIST_STAGING_ROOMS_OPERATION, + PI_GET_PLAYER_INFO_OPERATION, + PI_GET_PROFILE_ID_OPERATION, + PI_GET_IP_OPERATION, + PI_CHANGE_NICK_OPERATION, + PI_GET_GLOBAL_KEYS_OPERATION, + PI_GET_ROOM_KEYS_OPERATION, + PI_AUTHENTICATE_CDKEY_OPERATION, + PI_AUTO_MATCH_OPERATION, + PI_NUM_OPERATION_TYPES +} piOperationType; + +typedef enum piConnectType +{ + PI_CONNECT, + PI_CONNECT_UNIQUENICK_LOGIN, + PI_CONNECT_PROFILENICK_LOGIN, + PI_CONNECT_PREAUTH +} piConnectType; + +typedef struct piOperation +{ + PEER peer; // the peer object + piOperationType type; // PI__OPERATION + void * data; // operation-specific data + int ID; // unique ID for this operation + PEERCBType callback; // the callback for this operation + PEERCBType callback2; // second callback if needed + void * callbackParam; // user-data for the callback + RoomType roomType; // lots of operations need this + char * name; // general purpose name + char * password; // general purpose password + int num; // general purpose integer + SOCKET socket; // general purpose socket + unsigned short port; // general purpose port + PEERBool socketClose; // close the socket when done + PEERBool cancel; // this op has been cancelled +} piOperation; + +/************** +** FUNCTIONS ** +**************/ +PEERBool piOperationsInit(PEER peer); +void piOperationsReset(PEER peer); +void piOperationsCleanup(PEER peer); +PEERBool piIsOperationFinished(PEER peer, int opID); +void piRemoveOperation(PEER peer, piOperation * operation); +void piCancelJoinOperation(PEER peer, RoomType roomType); +piOperation * piGetOperation(PEER peer, int opID); +int piGetNextID(PEER peer); + +/*************** +** OPERATIONS ** +***************/ +PEERBool piNewConnectOperation +( + PEER peer, + piConnectType connectType, + const char * nick, + int namespaceID, + const char * email, + const char * profilenick, + const char * uniquenick, + const char * password, + const char * authtoken, + const char * partnerchallenge, + peerConnectCallback callback, + void * callbackParam, + int opID +); + +PEERBool piNewCreateStagingRoomOperation +( + PEER peer, + const char * name, + const char * password, + int maxPlayers, + SOCKET socket, + unsigned short port, + peerJoinRoomCallback callback, + void * callbackParam, + int opID +); + +PEERBool piNewJoinRoomOperation +( + PEER peer, + RoomType roomType, + const char * channel, + const char * password, + peerJoinRoomCallback callback, + void * callbackParam, + int opID +); + +PEERBool piNewListGroupRoomsOperation +( + PEER peer, + const char * fields, + peerListGroupRoomsCallback callback, + void * param, + int opID +); + +PEERBool piNewGetPlayerInfoOperation +( + PEER peer, + const char * nick, + peerGetPlayerInfoCallback callback, + void * param, + int opID +); + +PEERBool piNewGetProfileIDOperation +( + PEER peer, + const char * nick, + peerGetPlayerProfileIDCallback callback, + void * param, + int opID +); + +PEERBool piNewGetIPOperation +( + PEER peer, + const char * nick, + peerGetPlayerIPCallback callback, + void * param, + int opID +); + +PEERBool piNewChangeNickOperation +( + PEER peer, + const char * newNick, + peerChangeNickCallback callback, + void * param, + int opID +); + +PEERBool piNewGetGlobalKeysOperation +( + PEER peer, + const char * target, + int num, + const char ** keys, + peerGetGlobalKeysCallback callback, + void * param, + int opID +); + +PEERBool piNewGetRoomKeysOperation +( + PEER peer, + RoomType roomType, + const char * nick, + int num, + const char ** keys, + peerGetRoomKeysCallback callback, + void * param, + int opID +); + +PEERBool piNewAuthenticateCDKeyOperation +( + PEER peer, + const char * cdkey, + peerAuthenticateCDKeyCallback callback, + void * param, + int opID +); + +PEERBool piNewAutoMatchOperation +( + PEER peer, + SOCKET socket, + unsigned short port, + peerAutoMatchStatusCallback statusCallback, + peerAutoMatchRateCallback rateCallback, + void * param, + int opID +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerPing.c b/code/gamespy/Peer/peerPing.c new file mode 100644 index 00000000..65330e79 --- /dev/null +++ b/code/gamespy/Peer/peerPing.c @@ -0,0 +1,1118 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include +#include "peerPlayers.h" +#include "peerPing.h" +#include "peerGlobalCallbacks.h" +#include "peerCallbacks.h" +#include "peerMangle.h" + +/************ +** DEFINES ** +************/ +#define PI_PING_TIMEOUT 5000 +#define PI_PINGS_PER_SEC 25 +#define PI_PING_INTERVAL (1000 / PI_PINGS_PER_SEC) +#define PI_XPING_INTERVAL 2000 +#define PI_XPING_PLAYER_INTERVAL 5000 +#define PI_PINGER_PORT 13139 +#define PI_MAX_PING_PLAYERS 12 +#define PI_XPING_NUM_BUCKETS 32 +#define PI_DONT_PING_FLAGS (PEER_FLAG_PLAYING | PEER_FLAG_AWAY) +#define PI_MAX_XPING_NUM_PLAYERS 32 + +#define PEER_CONNECTION_DATA piConnection * connection;\ + assert(data);\ + assert(data->peer);\ + connection = (piConnection *)data->peer;\ + GSI_UNUSED(connection); + + +/********** +** TYPES ** +**********/ +typedef struct piXping +{ + char nicks[2][PI_NICK_MAX_LEN]; + int ping; +} piXping; + +/************** +** FUNCTIONS ** +**************/ +static int piXpingTableHashFn +( + const void *param, + int numBuckets +) +{ + piXping * xping = (piXping *)param; + int i; + int c; + const char * str; + unsigned int hash = 0; + const char * nicks[2]; + + assert(xping); + assert(xping->nicks[0][0]); + assert(xping->nicks[1][0]); + + nicks[0] = xping->nicks[0]; + nicks[1] = xping->nicks[1]; + + // Reverse the order if the second is smaller. + ////////////////////////////////////////////// + if(strcmp(nicks[1], nicks[0]) < 0) + { + const char * temp = nicks[0]; + nicks[0] = nicks[1]; + nicks[1] = temp; + } + + // Get the hash. + //////////////// + for(i = 0 ; i < 2 ; i++) + { + str = nicks[i]; + while((c = *str++) != '\0') + hash += (unsigned int)tolower(c); + hash %= (unsigned int)numBuckets; + } + + return (int)hash; +} + +static int GS_STATIC_CALLBACK piXpingTableCompareFn +( + const void *param1, + const void *param2 +) +{ + piXping * xping1 = (piXping *)param1; + piXping * xping2 = (piXping *)param2; + + int i; + int rcode; + const char * nicks[2][2]; + + assert(xping1); + assert(xping1->nicks[0][0]); + assert(xping1->nicks[1][0]); + assert(xping2); + assert(xping2->nicks[0][0]); + assert(xping2->nicks[1][0]); + + nicks[0][0] = xping1->nicks[0]; + nicks[0][1] = xping1->nicks[1]; + nicks[1][0] = xping2->nicks[0]; + nicks[1][1] = xping2->nicks[1]; + + // Reverse the order if the second is smaller. + ////////////////////////////////////////////// + for(i = 0 ; i < 2 ; i++) + { + if(strcmp(nicks[i][1], nicks[i][0]) < 0) + { + const char * temp = nicks[i][0]; + nicks[i][0] = nicks[i][1]; + nicks[i][1] = temp; + } + } + + for(i = 0 ; i < 2 ; i++) + { + rcode = strcasecmp(nicks[0][i], nicks[1][i]); + if(rcode != 0) + return rcode; + } + + return 0; +} + +static void piXpingTableElementFreeFn +( + void *param +) +{ + piXping * xping = (piXping *)param; + assert(xping); + assert(xping->nicks[0][0]); + assert(xping->nicks[1][0]); + GSI_UNUSED(xping); +} + +static void piProcessPing +( + PEER peer, + piPlayer * player, + int ping +) +{ + int i; + int total; + + //PEER_CONNECTION; + + // One more returned. + ///////////////////// + player->pingsReturned++; + player->pingsLostConsecutive = 0; + + // We have one more ping for this player. + ///////////////////////////////////////// + player->numPings++; + + //Update last ping received. + //////////////////////////// + player->lastPingRecv = current_time(); + + // Update the ping history. + /////////////////////////// + if(player->pingHistoryNum > 0) + memmove(player->pingHistory + 1, player->pingHistory, min(player->pingHistoryNum, PI_PING_HISTORY_LEN - 1) * sizeof(int)); + player->pingHistory[0] = ping; + if(player->pingHistoryNum < PI_PING_HISTORY_LEN) + player->pingHistoryNum++; + + // Recompute the ping average. + ////////////////////////////// + total = 0; + for(i = 0 ; i < player->pingHistoryNum ; i++) + total += player->pingHistory[i]; + player->pingAverage = (total / player->pingHistoryNum); + + // Add a ping callback. + /////////////////////// + piAddPingCallback(peer, player->nick, ping); + + // New ping, no xping yet. + ////////////////////////// + player->xpingSent = PEERFalse; + + // If this is a one-timer, stop pinging. + //////////////////////////////////////// + if(player->pingOnce) + player->pingOnce = PEERFalse; +} + +static void piPinged +( + unsigned int IP, + unsigned short port, + int ping, + const char * data, + int len, + PEER peer +) +{ + piPlayer * player; + + // Find the player. + /////////////////// + player = piFindPlayerByIP(peer, IP); + if(!player) + return; + + // Process the ping. + //////////////////// + piProcessPing(peer, player, ping); + + GSI_UNUSED(port); + GSI_UNUSED(data); + GSI_UNUSED(len); +} + +PEERBool piPingInit +( + PEER peer +) +{ + static PEERBool noPings[NumRooms]; + + PEER_CONNECTION; + + // If there are no ping rooms, skip this. + ///////////////////////////////////////// + if(memcmp(connection->pingRoom, noPings, sizeof(noPings)) == 0) + return PEERTrue; + + // Init the xping table. + //////////////////////// + connection->xpings = TableNew(sizeof(piXping), PI_XPING_NUM_BUCKETS, piXpingTableHashFn, piXpingTableCompareFn, piXpingTableElementFreeFn); + if(!connection->xpings) + return PEERFalse; + + // Init pinger. + /////////////// + if(!pingerInit(NULL, PI_PINGER_PORT, piPinged, peer, NULL, NULL)) + return PEERFalse; + + // No pings yet. + //////////////// + connection->lastPingTimeMod = 0; + connection->lastXpingSend = 0; + + // Init the random number generator. + //////////////////////////////////// + srand(current_time()); + + // We're doing pings. + ///////////////////// + connection->doPings = PEERTrue; + + return PEERTrue; +} + +void piPingCleanup +( + PEER peer +) +{ + PEER_CONNECTION; + + // Nothing to do if we weren't pinging. + /////////////////////////////////////// + if(!connection->doPings) + return; + + // If we're staying in the title room, get out. + /////////////////////////////////////////////// + if(connection->stayInTitleRoom) + return; + + // Clear timing stuff. + ////////////////////// + connection->lastPingTimeMod = 0; + connection->lastXpingSend = 0; + + // gsifree the xping table. + //////////////////////// + if(connection->xpings) + TableFree(connection->xpings); + connection->xpings = NULL; + + // Cleanup pinger. + ////////////////// + pingerShutdown(); + + // Not doing pings. + /////////////////// + connection->doPings = PEERFalse; +} + +typedef struct piPingerReplyData +{ + PEER peer; + unsigned int IP; + int ping; +} piPingerReplyData; + +static void piPingerReplyMapFn +( + void *elem, + void *clientdata +) +{ + piPlayer * player = (piPlayer *)elem; + piPingerReplyData * data = (piPingerReplyData *)clientdata; + + PEER_CONNECTION_DATA; + + assert(player); + + // Check if waiting for a ping. + /////////////////////////////// + if(!player->waitingForPing) + return; + + // Check the IP. + //////////////// + if(!player->gotIPAndProfileID || (player->IP != data->IP)) + return; + + // Not waiting anymore. + /////////////////////// + player->waitingForPing = PEERFalse; + + // Check timeout. + ///////////////// + if(data->ping == PINGER_TIMEOUT) + { + // One more lost. + ///////////////// + player->pingsLostConsecutive++; + + // If a one-timer drops three in a row, give up. + //////////////////////////////////////////////// + if(player->pingOnce && (player->pingsLostConsecutive >= 3)) + { + player->pingsLostConsecutive = 0; + player->pingOnce = PEERFalse; + } + } + else + { + // Process the ping. + //////////////////// + piProcessPing(data->peer, player, data->ping); + } +} + +static void piPingerReply +( + unsigned int IP, + unsigned short port, + int ping, + const char * pingData, + int pingDataLen, + PEER peer +) +{ + piPingerReplyData data; + + PEER_CONNECTION; + + // Find who sent the ping. + ////////////////////////// + data.peer = peer; + data.IP = IP; + data.ping = ping; + TableMap(connection->players, piPingerReplyMapFn, &data); + + GSI_UNUSED(port); + GSI_UNUSED(pingData); + GSI_UNUSED(pingDataLen); + +} + +static void piPingPlayer +( + PEER peer, + piPlayer * player +) +{ + assert(player->gotIPAndProfileID); + + // Make sure we're not already waiting. + /////////////////////////////////////// + assert(!player->waitingForPing); + if(player->waitingForPing) + return; + + // Do the ping. + /////////////// + pingerPing(player->IP, PI_PINGER_PORT, piPingerReply, peer, PINGERFalse, PI_PING_TIMEOUT); + + // Waiting for a ping to return. + //////////////////////////////// + player->waitingForPing = PEERTrue; + + // Last ping send time. + /////////////////////// + player->lastPingSend = current_time(); + + // Clear must ping flag. + //////////////////////// + player->mustPing = PEERFalse; +} + +typedef struct piPickPingPlayersData +{ + PEER peer; + piPlayer ** players; + int max; + int num; +} piPickPingPlayersData; + +static void piPickPingPlayersMap +( + void * elem, + void * clientData +) +{ + piPlayer * player = (piPlayer *)elem; + piPickPingPlayersData * data = (piPickPingPlayersData *)clientData; + PEER peer = (PEER)data->peer; + piPlayer * other; + unsigned long now; + unsigned long delay; + int i; + int j; + + PEER_CONNECTION; + + // Not pinging? + /////////////// + if(!player->inPingRoom && !player->pingOnce) + return; + + // Don't have IP? + ///////////////// + if(!player->gotIPAndProfileID) + return; + + // Waiting for a ping? + ////////////////////// + if(player->waitingForPing) + return; + + // Don't ping the local player. + /////////////////////////////// + if(player->local) + return; + + // Don't ping someone who's playing or away. + //////////////////////////////////////////// + if((player->inRoom[TitleRoom] && (player->flags[TitleRoom] & PI_DONT_PING_FLAGS)) || + (player->inRoom[GroupRoom] && (player->flags[GroupRoom] & PI_DONT_PING_FLAGS)) || + (player->inRoom[StagingRoom] && (player->flags[StagingRoom] & PI_DONT_PING_FLAGS))) + return; + + // Check if we have to ping this player. + //////////////////////////////////////// + if(!player->mustPing) + { + // Get the current time. + //////////////////////// + now = current_time(); + + // Slow pings for no response. + ////////////////////////////// + if((player->pingsLostConsecutive >= 4) && ((now - player->lastPingSend) < 120000)) + return; + + // Pinged recently? + /////////////////// + if(player->inRoom[StagingRoom]) + delay = 2000; + else if(player->pingsReturned < 3) + delay = 5000; + else + delay = 30000; + if((now - player->lastPingSend) < delay) + return; + if((now - player->lastPingRecv) < (delay + 1500)) + return; + } + + // Go through all the spots. + //////////////////////////// + for(i = (data->max - 1) ; i >= 0 ; i--) + { + // Is it empty? + /////////////// + if(!data->players[i]) + continue; + + // The "other" player. + ////////////////////// + other = data->players[i]; + + // Is this player higher priority than us? + ////////////////////////////////////////// + if((!other->numPings && player->numPings) || // Not pinged. + (other->inRoom[StagingRoom] && !player->inRoom[StagingRoom]) || // In staging room. + (piIsPlayerVIP(other, StagingRoom) && !piIsPlayerVIP(player, StagingRoom)) || // Op or voice in the same staging room. + (strcasecmp(other->nick, player->nick) < 0)) // Alphabetical. + { + break; + } + } + + // Bump it up one, so it's set to our spot. + /////////////////////////////////////////// + i++; + + // Did we not find anything? + //////////////////////////// + if(i == data->max) + return; + + // Bump down the other player and all below. + //////////////////////////////////////////// + for(j = (data->max - 1) ; j > i ; j--) + data->players[j] = data->players[j - 1]; + + // Set this player. + /////////////////// + data->players[i] = player; + + // One more. + //////////// + if(data->num < data->max) + data->num++; +} + +// Returns an array of pointers to players to ping, +// or NULL if there's noone to ping. +// The number of players in the array will be no +// larger than min(PI_MAX_PING_PLAYERS, numPings). +/////////////////////////////////////////////////// +static piPlayer ** piPickPingPlayers +( + PEER peer, + int * numPings +) +{ + static piPlayer * players[PI_MAX_PING_PLAYERS]; + piPickPingPlayersData data; + PEER_CONNECTION; + + // Check for no players. + //////////////////////// + if(!connection->players || !(*numPings) || !TableCount(connection->players)) + { + *numPings = 0; + return NULL; + } + + // Setup a list of players to ping. + /////////////////////////////////// + data.peer = peer; + data.players = players; + data.max = min(PI_MAX_PING_PLAYERS, *numPings); + data.num = 0; + memset(players, 0, sizeof(piPlayer *) * data.max); + TableMap(connection->players, piPickPingPlayersMap, &data); + + // Set number of pings. + /////////////////////// + *numPings = data.num; + + // Check for noone to ping. + /////////////////////////// + if(!data.players[0]) + return NULL; + + return data.players; +} + +static void piXpingPlayer +( + PEER peer, + piPlayer * player +) +{ + int roomType; + char message[(PI_NICK_MAX_LEN * 2) + 32]; + char encodedIP[11]; + + PEER_CONNECTION; + + assert(player->inXpingRoom); + if(!player->inXpingRoom) + return; + + // Setup the message. + ///////////////////// + piMangleIP(encodedIP, player->IP); + sprintf(message, "%s %d", encodedIP, player->pingAverage); + + // Figure out which room to send the xping in. + ////////////////////////////////////////////// + for(roomType = 0 ; roomType < NumRooms ; roomType++) + { + if(player->inRoom[roomType] && connection->xpingRoom[roomType]) + { + if(connection->numPlayers[roomType] <= PI_MAX_XPING_NUM_PLAYERS) + { + assert(IN_ROOM || ENTERING_ROOM); + if(IN_ROOM || ENTERING_ROOM) + piSendChannelUTM(peer, ROOM, PI_UTM_XPING, message, PEERFalse); + } + } + } + + // Sent it. + /////////// + player->xpingSent = PEERTrue; + player->lastXping = current_time(); + connection->lastXpingSend = (unsigned int)player->lastXping; +} + +typedef struct piPickXpingPlayerData +{ + PEER peer; + piPlayer * player; +} piPickXpingPlayerData; + +static void piPickXpingPlayerMap +( + void * elem, + void * clientData +) +{ + piPlayer * player = (piPlayer *)elem; + piPickXpingPlayerData * data = (piPickXpingPlayerData *)clientData; + PEER peer = (PEER)data->peer; + unsigned long now; + + PEER_CONNECTION; + + // Not xpinging? + //////////////// + if(!player->inXpingRoom) + return; + + // Don't xping the local player. + //////////////////////////////// + if(player->local) + return; + + // Don't xping if no pings received. + /////////////////////////////////// + if(!player->numPings) + return; + + // Don't xping with the same info. + ////////////////////////////////// + if(player->xpingSent) + return; + + // Don't xping too often. + ///////////////////////// + now = current_time(); + if((now - player->lastXping) < PI_XPING_PLAYER_INTERVAL) + return; + + // First player? + //////////////// + if(!data->player) + { + data->player = player; + } + else if((now - player->lastXping) > (now - data->player->lastXping)) + { + data->player = player; + } +} + +// Returns a player to ping, or NULL if there's noone to ping. +////////////////////////////////////////////////////////////// +static piPlayer * piPickXpingPlayer +( + PEER peer +) +{ + piPickXpingPlayerData data; + PEER_CONNECTION; + + // Check for no players. + //////////////////////// + if(!connection->players || !TableCount(connection->players)) + return NULL; + + // Find someone near the top of the list. + ///////////////////////////////////////// + data.peer = peer; + data.player = NULL; + TableMap(connection->players, piPickXpingPlayerMap, &data); + + return data.player; +} + +void piPingThink +( + PEER peer +) +{ + unsigned long now; + int pingTimeMod; + int numPings; + piPlayer * player; + piPlayer ** players; + int i; + + PEER_CONNECTION; + + // Check if we're not doing pings. + ////////////////////////////////// + if(!connection->doPings) + return; + + // Don't do pings while playing! + //////////////////////////////// + if(connection->playing) + return; + + // Don't do pings while away. + ///////////////////////////// + if(connection->away) + return; + + // Get the current time. + //////////////////////// + now = current_time(); + + // Get the number of pings to send. + /////////////////////////////////// + pingTimeMod = (int)(now / PI_PING_INTERVAL); + if(connection->lastPingTimeMod) + numPings = (pingTimeMod - connection->lastPingTimeMod); + else + numPings = 1; + if(numPings) + connection->lastPingTimeMod = pingTimeMod; + + // Send pings. + ////////////// + players = piPickPingPlayers(peer, &numPings); + if(players) + { + for(i = 0 ; (i < numPings) && players[i] ; i++) + piPingPlayer(peer, players[i]); + } + + // Check for sending a crossping. + ///////////////////////////////// + if((now - connection->lastXpingSend) > PI_XPING_INTERVAL) + { + // Try and pick a player to crossping. + ////////////////////////////////////// + player = piPickXpingPlayer(peer); + if(player) + piXpingPlayer(peer, player); + } + + // Let pinger think. + //////////////////// + pingerThink(); +} + +PEERBool piPingInitPlayer +( + PEER peer, + piPlayer * player +) +{ + int i; + + PEER_CONNECTION; + + assert(player); + + // Check if we're not doing pings. + ////////////////////////////////// + if(!connection->doPings) + return PEERTrue; + + player->lastPingSend = 0; + player->lastPingRecv = 0; + player->lastXping = 0; + player->waitingForPing = PEERFalse; + player->pingsReturned = 0; + player->pingsLostConsecutive = 0; + player->pingAverage = 0; + for(i = 0 ; i < PI_PING_HISTORY_LEN ; i++) + player->pingHistory[i] = 0; + player->pingHistoryNum = 0; + player->numPings = 0; + player->xpingSent = PEERFalse; + player->inPingRoom = PEERFalse; + player->inXpingRoom = PEERFalse; + player->mustPing = PEERFalse; + player->pingOnce = PEERFalse; + + return PEERTrue; +} + +void piPingPlayerJoinedRoom +( + PEER peer, + piPlayer * player, + RoomType roomType +) +{ + PEER_CONNECTION; + + assert(player); + ASSERT_ROOMTYPE(roomType); + + // Check if we're not doing pings. + ////////////////////////////////// + if(!connection->doPings) + return; + + // Is this a ping room? + /////////////////////// + if(connection->pingRoom[roomType]) + player->inPingRoom = PEERTrue; + + // Is this an xping room? + ///////////////////////// + if(connection->xpingRoom[roomType]) + player->inXpingRoom = PEERTrue; + + // Immediately ping players when they join a staging room. + ////////////////////////////////////////////////////////// + if(roomType == StagingRoom) + player->mustPing = PEERTrue; +} + +static void piRemoveXping +( + PEER peer, + piXping * xping +) +{ + PEER_CONNECTION; + + assert(xping); + + TableRemove(connection->xpings, xping); +} + +typedef struct piPingPlayerLeftRoomData +{ + PEER peer; + const char * nick; +} piPingPlayerLeftRoomData; + +static void piPingPlayerLeftRoomTableMapFn +( + void *elem, + void *clientdata +) +{ + piXping * xping = (piXping *)elem; + piPingPlayerLeftRoomData * data = (piPingPlayerLeftRoomData *)clientdata; + assert(xping); + assert(data); + + // Check if this player is part of this xping. + ////////////////////////////////////////////// + if((strcmp(xping->nicks[0], data->nick) == 0) || (strcmp(xping->nicks[1], data->nick) == 0)) + piRemoveXping(data->peer, xping); +} + +void piPingPlayerLeftRoom +( + PEER peer, + piPlayer * player +) +{ + PEER_CONNECTION; + + assert(player); + + // Check if we're not doing pings. + ////////////////////////////////// + if(!connection->doPings) + return; + + // Was this player in a ping room? + ////////////////////////////////// + if(player->inPingRoom) + { + int i; + PEERBool inPingRoom = PEERFalse; + + // Is he still in a ping room? + ////////////////////////////// + for(i = 0 ; i < NumRooms ; i++) + { + if(player->inRoom[i] && connection->pingRoom[i]) + inPingRoom = PEERTrue; + } + player->inPingRoom = inPingRoom; + } + + // Was this player in an xping room? + //////////////////////////////////// + if(player->inXpingRoom) + { + int i; + PEERBool inXpingRoom = PEERFalse; + + // Is he still in a ping room? + ////////////////////////////// + for(i = 0 ; i < NumRooms ; i++) + { + if(player->inRoom[i] && connection->xpingRoom[i]) + inXpingRoom = PEERTrue; + } + player->inXpingRoom = inXpingRoom; + + if(!player->inXpingRoom) + { + piPingPlayerLeftRoomData data; + data.peer = peer; + data.nick = player->nick; + + // Get rid of all this players xpings. + ////////////////////////////////////// + TableMapSafe(connection->xpings, piPingPlayerLeftRoomTableMapFn, &data); + } + } +} + +static piXping * piFindXping +( + PEER peer, + const char * nick1, + const char * nick2 +) +{ + piXping xpingMatch; + + PEER_CONNECTION; + + assert(nick1); + assert(nick1[0]); + assert(piGetPlayer(peer, nick1)); + assert(nick2); + assert(nick2[0]); + assert(piGetPlayer(peer, nick2)); + + // Setup the xping match. + ///////////////////////// + strzcpy(xpingMatch.nicks[0], nick1, PI_NICK_MAX_LEN); + _strlwr(xpingMatch.nicks[0]); + strzcpy(xpingMatch.nicks[1], nick2, PI_NICK_MAX_LEN); + _strlwr(xpingMatch.nicks[0]); + + // Find the xping. + ////////////////// + return (piXping *)TableLookup(connection->xpings, &xpingMatch); +} + +static piXping * piAddXping +( + PEER peer, + const char * nick1, + const char * nick2 +) +{ + piXping xpingMatch; + piXping * xping; + + PEER_CONNECTION; + + assert(nick1); + assert(nick1[0]); + assert(piGetPlayer(peer, nick1)); + assert(nick2); + assert(nick2[0]); + assert(piGetPlayer(peer, nick2)); + + // Setup the one to add. + //////////////////////// + xping = &xpingMatch; + strzcpy(xping->nicks[0], nick1, PI_NICK_MAX_LEN); + _strlwr(xping->nicks[0]); + strzcpy(xping->nicks[1], nick2, PI_NICK_MAX_LEN); + _strlwr(xping->nicks[1]); + + // Add it. + ////////// + TableEnter(connection->xpings, xping); + + // Get it. + ////////// + xping = (piXping *)TableLookup(connection->xpings, &xpingMatch); + + // Return it. + ///////////// + return xping; +} + +void piUpdateXping +( + PEER peer, + const char * nick1, + const char * nick2, + int ping +) +{ + piPlayer * player1; + piPlayer * player2; + piXping * xping; + + PEER_CONNECTION; + + assert(nick1); + assert(nick1[0]); + assert(piGetPlayer(peer, nick1)); + assert(nick2); + assert(nick2[0]); + assert(piGetPlayer(peer, nick2)); + assert(ping >= 0); + + // Check if we're not doing pings. + ////////////////////////////////// + if(!connection->doPings) + return; + + player1 = piGetPlayer(peer, nick1); + if(!player1) + return; + if(!player1->inXpingRoom) + return; + player2 = piGetPlayer(peer, nick2); + if(!player2) + return; + if(!player2->inXpingRoom) + return; + + // Add it. + ////////// + xping = piAddXping(peer, nick1, nick2); + assert(xping); + if(!xping) + return; + + // Update. + ////////// + xping->ping = ping; +} + +PEERBool piGetXping +( + PEER peer, + const char * nick1, + const char * nick2, + int * ping +) +{ + piXping * xping; + + PEER_CONNECTION; + + assert(nick1); + assert(nick1[0]); + assert(piGetPlayer(peer, nick1)); + assert(nick2); + assert(nick2[0]); + assert(piGetPlayer(peer, nick2)); + assert(ping); + + // Check if we're not doing pings. + ////////////////////////////////// + if(!connection->doPings) + return PEERFalse; + + // Get the xping. + ///////////////// + xping = piFindXping(peer, nick1, nick2); + assert(xping); + if(!xping) + return PEERFalse; + + // Return the ping. + /////////////////// + *ping = xping->ping; + + return PEERTrue; +} diff --git a/code/gamespy/Peer/peerPing.h b/code/gamespy/Peer/peerPing.h new file mode 100644 index 00000000..99757acd --- /dev/null +++ b/code/gamespy/Peer/peerPing.h @@ -0,0 +1,41 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERPING_H_ +#define _PEERPING_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/************** +** FUNCTIONS ** +**************/ +PEERBool piPingInit(PEER peer); +void piPingCleanup(PEER peer); +void piPingThink(PEER peer); +PEERBool piPingInitPlayer(PEER peer, piPlayer * player); +void piPingPlayerJoinedRoom(PEER peer, piPlayer * player, RoomType roomType); +void piPingPlayerLeftRoom(PEER peer, piPlayer * player); +void piUpdateXping(PEER peer, const char * nick1, const char * nick2, int ping); +PEERBool piGetXping(PEER peer, const char * nick1, const char * nick2, int * ping); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerPlayers.c b/code/gamespy/Peer/peerPlayers.c new file mode 100644 index 00000000..0d5db959 --- /dev/null +++ b/code/gamespy/Peer/peerPlayers.c @@ -0,0 +1,824 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include "peerPlayers.h" +#include "peerPing.h" +#include "peerCallbacks.h" +#include "peerKeys.h" + +/************ +** DEFINES ** +************/ +#define PI_PLAYERS_NUM_BUCKETS 32 +#define PI_PLAYER_INFO_SIZE (sizeof(piPlayer) - PI_NICK_MAX_LEN) +#define PI_PLAYER_INFO(player) ((char *)(player) + PI_NICK_MAX_LEN) + +/************** +** FUNCTIONS ** +**************/ +static int piPlayersTableHashFn +( + const void *elem, + int numBuckets +) +{ + piPlayer * player = (piPlayer *)elem; + int c; + const char * str; + unsigned int hash; + + assert(player); + assert(player->nick[0]); + + // Get the hash. + //////////////// + str = player->nick; + hash = 0; + while((c = *str++) != '\0') + hash += (unsigned int)tolower(c); + hash %= (unsigned int)numBuckets; + + return (int)hash; +} + +static int GS_STATIC_CALLBACK piPlayersTableCompareFn +( + const void *elem1, + const void *elem2 +) +{ + piPlayer * player1 = (piPlayer *)elem1; + piPlayer * player2 = (piPlayer *)elem2; + assert(player1); + assert(player1->nick[0]); + assert(player2); + assert(player2->nick[0]); + + return strcasecmp(player1->nick, player2->nick); +} + +static void piPlayersTableElementFreeFn +( + void *elem +) +{ + piPlayer * player = (piPlayer *)elem; + assert(player); + assert(player->nick); + GSI_UNUSED(player); +} + +PEERBool piPlayersInit +( + PEER peer +) +{ + int i; + + PEER_CONNECTION; + + if(connection->stayInTitleRoom) + return PEERTrue; + + // Setup the player table. + ////////////////////////// + connection->players = TableNew(sizeof(piPlayer), PI_PLAYERS_NUM_BUCKETS, piPlayersTableHashFn, piPlayersTableCompareFn, piPlayersTableElementFreeFn); + if(!connection->players) + return PEERFalse; + + // No players in any room. + ////////////////////////// + for(i = 0 ; i < NumRooms ; i++) + connection->numPlayers[i] = 0; + + return PEERTrue; +} + +void piPlayersCleanup +( + PEER peer +) +{ + int i; + + PEER_CONNECTION; + + if(connection->stayInTitleRoom) + return; + + // gsifree the player table. + ///////////////////////// + if(connection->players) + TableFree(connection->players); + connection->players = NULL; + for(i = 0 ; i < NumRooms ; i++) + connection->numPlayers[i] = 0; +} + +static piPlayer * piAddPlayer +( + PEER peer, + const char * nick, + PEERBool initialize +) +{ + piPlayer * player; + piPlayer playerMatch; + + PEER_CONNECTION; + + assert(!piGetPlayer(peer, nick)); + + // Setup the player. + //////////////////// + player = &playerMatch; + memset(player, 0, sizeof(piPlayer)); + strzcpy(player->nick, nick, PI_NICK_MAX_LEN); + if(initialize) + { + int roomType; + + player->local = (PEERBool)(strcasecmp(nick, connection->nick) == 0); + + for(roomType = 0 ; roomType < NumRooms ; roomType++) + { + player->inRoom[roomType] = PEERFalse; + player->flags[roomType] = 0; + } + + player->IP = 0; + player->profileID = 0; + player->gotIPAndProfileID = PEERFalse; + + if(!piPingInitPlayer(peer, player)) + return NULL; + } + + // Add the player. + ////////////////// + TableEnter(connection->players, player); + + player = piGetPlayer(peer, nick); + assert(player); + + return player; +} + +static void piRemovePlayer +( + PEER peer, + piPlayer * player +) +{ + PEER_CONNECTION; + + assert(player); + + // Remove it. + ///////////// + TableRemove(connection->players, player); +} + +piPlayer * piPlayerJoinedRoom +( + PEER peer, + const char * nick, + RoomType roomType, + int mode +) +{ + piPlayer * player; + + PEER_CONNECTION; + + assert(nick); + assert(nick[0]); + ASSERT_ROOMTYPE(roomType); + assert(IN_ROOM || ENTERING_ROOM); + + // Check that we're in/entering this room. + ////////////////////////////////////////// + if(!ENTERING_ROOM && !IN_ROOM) + return NULL; + + // Find the player. + /////////////////// + player = piGetPlayer(peer, nick); + + // Is it a new player? + ////////////////////// + if(!player) + { + // Add the player. + ////////////////// + player = piAddPlayer(peer, nick, PEERTrue); + } + + // In this room. + //////////////// + assert(!player->inRoom[roomType]); + player->inRoom[roomType] = PEERTrue; + connection->numPlayers[roomType]++; + player->flags[roomType] = 0; + if(mode & CHAT_OP) + player->flags[roomType] |= PEER_FLAG_OP; + if(mode & CHAT_VOICE) + player->flags[roomType] |= PEER_FLAG_VOICE; + + // Do ping stuff. + ///////////////// + piPingPlayerJoinedRoom(peer, player, roomType); + + return player; +} + +void piPlayerLeftRoom +( + PEER peer, + const char * nick, + RoomType roomType +) +{ + piPlayer * player; + + PEER_CONNECTION; + + assert(nick); + assert(nick[0]); + ASSERT_ROOMTYPE(roomType); + + // Find the player. + /////////////////// + player = piGetPlayer(peer, nick); + assert(player); + if(!player) + return; + + // Leave the room. + ////////////////// + assert(player->inRoom[roomType]); + player->inRoom[roomType] = PEERFalse; + connection->numPlayers[roomType]--; + player->flags[roomType] = 0; + + // Do ping stuff. + ///////////////// + piPingPlayerLeftRoom(peer, player); + + // Are we not in any rooms now? + /////////////////////////////// + if(!player->inRoom[0] && !player->inRoom[1] && !player->inRoom[2]) + { + // This player is out. + ////////////////////// + piRemovePlayer(peer, player); + } + + // Cleanse the key cache. + ///////////////////////// + piKeyCacheCleanse(peer); +} + +void piPlayerChangedNick +( + PEER peer, + const char * oldNick, + const char * newNick +) +{ + char playerInfoBuffer[PI_PLAYER_INFO_SIZE]; + piPlayer * player; + + assert(oldNick); + assert(oldNick[0]); + assert(newNick); + assert(newNick[0]); + + // Find the player. + /////////////////// + player = piGetPlayer(peer, oldNick); + + // Check if we can't find this player + // (we could have already done the nick change + // if we're in more than one channel together). + /////////////////////////////////////////////// + if(!player) + return; + + // Save off the player info. + //////////////////////////// + memcpy(playerInfoBuffer, PI_PLAYER_INFO(player), PI_PLAYER_INFO_SIZE); + + // Remove the old nick. + /////////////////////// + piRemovePlayer(peer, player); + + // Add the new one. + /////////////////// + player = piAddPlayer(peer, newNick, PEERFalse); + assert(player); + if(!player) + return; + + // Copy the player info back in. + //////////////////////////////// + memcpy(PI_PLAYER_INFO(player), playerInfoBuffer, PI_PLAYER_INFO_SIZE); + + // Update the key cache. + //////////////////////// + piKeyCachePlayerChangedNick(peer, oldNick, newNick); +} + +typedef struct piLeftRoomData +{ + PEER peer; + RoomType roomType; +} piLeftRoomData; + +static void piLeftRoomMapFn +( + void *elem, + void *clientdata +) +{ + piPlayer * player = (piPlayer *)elem; + piLeftRoomData * data = (piLeftRoomData *)clientdata; + assert(player); + assert(player->nick); + assert(data); + + // Is the player in this room? + ////////////////////////////// + if(player->inRoom[data->roomType]) + { + // Leave the room. + ////////////////// + piPlayerLeftRoom(data->peer, player->nick, data->roomType); + } +} + +void piClearRoomPlayers +( + PEER peer, + RoomType roomType +) +{ + piLeftRoomData data; + + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + + // Go through all the players. + ////////////////////////////// + data.peer = peer; + data.roomType = roomType; + TableMapSafe(connection->players, piLeftRoomMapFn, &data); +} + +piPlayer * piGetPlayer +( + PEER peer, + const char * nick +) +{ + piPlayer playerMatch; + piPlayer * player; + + PEER_CONNECTION; + + assert(nick); + assert(nick[0]); + + // Check for no table. + ////////////////////// + if(!connection->players) + return NULL; + + // Lookup this player. + ////////////////////// + strzcpy(playerMatch.nick, nick, PI_NICK_MAX_LEN); + player = (piPlayer *)TableLookup(connection->players, &playerMatch); + + return player; +} + +typedef struct piEnumRoomPlayersData +{ + PEER peer; + RoomType roomType; + int count; + piEnumRoomPlayersCallback callback; + void * param; +} piEnumRoomPlayersData; + +static void piEnumRoomPlayersMap +( + void *elem, + void *clientdata +) +{ + piPlayer * player = (piPlayer *)elem; + piEnumRoomPlayersData * data = (piEnumRoomPlayersData *)clientdata; + // Is this player in the room? + ////////////////////////////// + if(player->inRoom[data->roomType]) + { + // Call the callback. + ///////////////////// + data->callback(data->peer, data->roomType, player, data->count, data->param); + + // One more player. + /////////////////// + data->count++; + } +} + +void piEnumRoomPlayers +( + PEER peer, + RoomType roomType, + piEnumRoomPlayersCallback callback, + void * param +) +{ + piEnumRoomPlayersData data; + + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + assert(callback); + + // Init the data. + ///////////////// + data.peer = peer; + data.roomType = roomType; + data.count = 0; + data.callback = callback; + data.param = param; + + // Enum through the players. + //////////////////////////// + TableMap(connection->players, piEnumRoomPlayersMap, &data); + + // Call the callback once to terminate the enum. + //////////////////////////////////////////////// + callback(peer, roomType, NULL, -1, param); +} + +static int piFindPlayersByIPMap +( + void * elem, + void * clientdata +) +{ + piPlayer * player = (piPlayer *)elem; + unsigned int IP; + + IP = *(unsigned int *)clientdata; + + // Is this the same IP? + /////////////////////// + if(player->gotIPAndProfileID && (player->IP == IP)) + return 0; + + return 1; +} + +piPlayer * piFindPlayerByIP +( + PEER peer, + unsigned int IP +) +{ + PEER_CONNECTION; + + return (piPlayer *)TableMap2(connection->players, piFindPlayersByIPMap, &IP); +} + +void piSetPlayerIPAndProfileID +( + PEER peer, + const char * nick, + unsigned int IP, + int profileID +) +{ + piPlayer * player; + + assert(nick); + assert(nick[0]); + + if(!nick) + return; + + // Cache the info. + ////////////////// + player = piGetPlayer(peer, nick); + if(player) + { + player->IP = IP; + player->profileID = profileID; + + if(!player->gotIPAndProfileID) + { + player->gotIPAndProfileID = PEERTrue; + + // The IP is now available, send a ping + //////////////////////////////////////// + if(player->inRoom[TitleRoom]) + piPingPlayerJoinedRoom(peer, player, TitleRoom); + if(player->inRoom[GroupRoom]) + piPingPlayerJoinedRoom(peer, player, GroupRoom); + if(player->inRoom[StagingRoom]) + piPingPlayerJoinedRoom(peer, player, StagingRoom); + } + + } +} + +static void piSetNewPlayerFlags +( + PEER peer, + const char * nick, + RoomType roomType, + int flags +) +{ + piPlayer * player; + int oldFlags; + + assert(nick); + assert(nick[0]); + + if(!nick) + return; + + // Find the player. + /////////////////// + player = piGetPlayer(peer, nick); + if(!player || !player->inRoom[roomType]) + return; + + // Copy off the old flags. + ////////////////////////// + oldFlags = player->flags[roomType]; + + // Check for no change. + /////////////////////// + if(flags == oldFlags) + return; + + // Set the new flags. + ///////////////////// + player->flags[roomType] = flags; + + // If ready changed in the staging room, call a callback. + ///////////////////////////////////////////////////////// + if((roomType == StagingRoom) && ((oldFlags & PEER_FLAG_READY) != (flags & PEER_FLAG_READY))) + piAddReadyChangedCallback(peer, player->nick, (PEERBool)((player->flags[roomType] & PEER_FLAG_READY) != 0)); + + // Call the flags changed callback. + /////////////////////////////////// + piAddPlayerFlagsChangedCallback(peer, roomType, nick, oldFlags, flags); +} + +int piParseFlags +( + const char * flags +) +{ + int nFlags = 0; + + if(strchr(flags, 's')) + nFlags |= PEER_FLAG_STAGING; + if(strchr(flags, 'r')) + nFlags |= PEER_FLAG_READY; + if(strchr(flags, 'g')) + nFlags |= PEER_FLAG_PLAYING; + if(strchr(flags, 'a')) + nFlags |= PEER_FLAG_AWAY; + if(strchr(flags, 'h')) + nFlags |= PEER_FLAG_HOST; + + return nFlags; +} + +void piSetPlayerRoomFlags +( + PEER peer, + const char * nick, + RoomType roomType, + const char * flags +) +{ + piPlayer * player; + int nFlags; + + assert(nick); + assert(nick[0]); + + if(!nick) + return; + + // Find the player. + /////////////////// + player = piGetPlayer(peer, nick); + if(!player || !player->inRoom[roomType]) + return; + + // Get the mode. + //////////////// + nFlags = (player->flags[roomType] & (PEER_FLAG_OP | PEER_FLAG_VOICE)); + + // Add the new flags. + ///////////////////// + nFlags |= piParseFlags(flags); + + // Set them. + //////////// + piSetNewPlayerFlags(peer, nick, roomType, nFlags); +} + +void piSetPlayerModeFlags +( + PEER peer, + const char * nick, + RoomType roomType, + int mode +) +{ + piPlayer * player; + int nFlags; + + assert(nick); + assert(nick[0]); + + if(!nick) + return; + + // Find the player. + /////////////////// + player = piGetPlayer(peer, nick); + if(!player || !player->inRoom[roomType]) + return; + + // Get the non-mode flags. + ////////////////////////// + nFlags = (player->flags[roomType] & (~(PEER_FLAG_OP | PEER_FLAG_VOICE))); + + // Add the new flags. + ///////////////////// + if(mode & CHAT_OP) + nFlags |= PEER_FLAG_OP; + if(mode & CHAT_VOICE) + nFlags |= PEER_FLAG_VOICE; + + // Set them. + //////////// + piSetNewPlayerFlags(peer, nick, roomType, nFlags); +} + +PEERBool piIsPlayerVIP +( + piPlayer * player, + RoomType roomType +) +{ + if(!player) + return PEERFalse; + + if(!player->inRoom[roomType]) + return PEERFalse; + + return (PEERBool)((player->flags[roomType] & (PEER_FLAG_OP | PEER_FLAG_VOICE)) != 0); +} + +PEERBool piIsPlayerHost(piPlayer * player) +{ + if(!player) + return PEERFalse; + + if(!player->inRoom[StagingRoom]) + return PEERFalse; + + return (PEERBool)((player->flags[StagingRoom] & (PEER_FLAG_OP | PEER_FLAG_HOST)) == (PEER_FLAG_OP | PEER_FLAG_HOST)); +} + +PEERBool piIsPlayerOp(piPlayer * player) +{ + if(!player) + return PEERFalse; + + if(!player->inRoom[StagingRoom]) + return PEERFalse; + + return (PEERBool)(player->flags[StagingRoom] & PEER_FLAG_OP); +} + +typedef struct piFindPlayerByIndexData +{ + int index; + int count; + RoomType roomType; +} piFindPlayerByIndexData; + +static int piFindPlayerByIndexMap +( + void * elem, + void * clientdata +) +{ + piPlayer * player = (piPlayer *)elem; + piFindPlayerByIndexData * data = (piFindPlayerByIndexData *)clientdata; + + // check if the player is in the room + if(player->inRoom[data->roomType]) + { + // check if this is the one we're looking for + if(data->index == data->count) + return 0; + + // one more player found + data->count++; + } + + return 1; +} + +piPlayer * piFindPlayerByIndex +( + PEER peer, + RoomType roomType, + int index +) +{ + piFindPlayerByIndexData data; + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + + // setup the data + data.index = index; + data.count = 0; + data.roomType = roomType; + + // enum through the players + return TableMap2(connection->players, piFindPlayerByIndexMap, &data); +} + +typedef struct piCountRoomOpsMapData +{ + int count; + RoomType roomType; + const char * exclude; +} piCountRoomOpsMapData; + +static void piCountRoomOpsMap +( + void * elem, + void * clientdata +) +{ + piPlayer * player = (piPlayer *)elem; + piCountRoomOpsMapData * data = (piCountRoomOpsMapData *)clientdata; + + // check the exlude nick + if(data->exclude && (strcasecmp(data->exclude, player->nick) == 0)) + return; + + // is this player an op? + if(player->flags[data->roomType] & PEER_FLAG_OP) + data->count++; +} + +int piCountRoomOps(PEER peer, RoomType roomType, const char * exclude) +{ + piCountRoomOpsMapData data; + + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + + // setup the data struct + data.count = 0; + data.roomType = roomType; + data.exclude = exclude; + + // enum through the players + TableMap(connection->players, piCountRoomOpsMap, &data); + + // return the count + return data.count; +} diff --git a/code/gamespy/Peer/peerPlayers.h b/code/gamespy/Peer/peerPlayers.h new file mode 100644 index 00000000..9820e821 --- /dev/null +++ b/code/gamespy/Peer/peerPlayers.h @@ -0,0 +1,92 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERPLAYERS_H_ +#define _PEERPLAYERS_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/************ +** DEFINES ** +************/ +#define PI_PING_HISTORY_LEN 4 + +/********** +** TYPES ** +**********/ +typedef struct piPlayer +{ + char nick[PI_NICK_MAX_LEN]; // this has to be first + PEERBool inRoom[NumRooms]; // true if in the room + PEERBool local; // true for the local player + + unsigned int IP; // the IP in network byte order + int profileID; // gp profile id + PEERBool gotIPAndProfileID; // true if we have the IP and profile ID + + int flags[NumRooms]; // player's room flags + + // Ping stuff + unsigned long lastPingSend; // last time a ping was sent + unsigned long lastPingRecv; // last time a ping was received + unsigned long lastXping; // last time a xping was sent + PEERBool waitingForPing; // if true we're waiting for a ping to return + int pingsReturned; // number of pings returned + int pingsLostConsecutive; // number of pings lost in a row + int pingAverage; // the average ping time + int pingHistory[PI_PING_HISTORY_LEN]; // history of pings + int pingHistoryNum; // the number of pings in the ping history + int numPings; // number of pings for this player + PEERBool xpingSent; // if true, we already sent a xping with the current average + PEERBool inPingRoom; // true if the player is in a room getting pinged + PEERBool inXpingRoom; // true if the player is in a room getting xpinged + PEERBool mustPing; // if true, ping this player as soon as possible + PEERBool pingOnce; // a one-time ping has been triggered with peerPingPlayer +} piPlayer; + +/************** +** FUNCTIONS ** +**************/ +PEERBool piPlayersInit(PEER peer); +void piPlayersCleanup(PEER peer); +piPlayer * piPlayerJoinedRoom(PEER peer, const char * nick, RoomType roomType, int mode); +void piPlayerLeftRoom(PEER peer, const char * nick, RoomType roomType); +void piPlayerChangedNick(PEER peer, const char * oldNick, const char * newNick); +void piClearRoomPlayers(PEER peer, RoomType roomType); +piPlayer * piGetPlayer(PEER peer, const char * nick); +typedef void (* piEnumRoomPlayersCallback)(PEER peer, RoomType roomType, piPlayer * player, int index, void * param); +void piEnumRoomPlayers(PEER peer, RoomType roomType, piEnumRoomPlayersCallback callback, void * param); +piPlayer * piFindPlayerByIP(PEER peer, unsigned int IP); +void piSetPlayerIPAndProfileID(PEER peer, const char * nick, unsigned int IP, int profileID); +int piParseFlags(const char * flags); +void piSetPlayerRoomFlags(PEER peer, const char * nick, RoomType roomType, const char * flags); +void piSetPlayerModeFlags(PEER peer, const char * nick, RoomType roomType, int mode); +PEERBool piIsPlayerVIP(piPlayer * player, RoomType roomType); +PEERBool piIsPlayerHost(piPlayer * player); +PEERBool piIsPlayerOp(piPlayer * player); +piPlayer * piFindPlayerByIndex(PEER peer, RoomType roomType, int index); +piPlayer * piFindRoomHost(PEER peer, RoomType roomType); +piPlayer * piFindRoomOp(PEER peer, RoomType roomType); +int piCountRoomOps(PEER peer, RoomType roomType, const char * exclude); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerQR.c b/code/gamespy/Peer/peerQR.c new file mode 100644 index 00000000..d70ab916 --- /dev/null +++ b/code/gamespy/Peer/peerQR.c @@ -0,0 +1,494 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include "peerQR.h" +#include "peerGlobalCallbacks.h" +#include "peerPlayers.h" +#include "peerAutoMatch.h" +#include "peerOperations.h" + +/************** +** CALLBACKS ** +**************/ +#ifndef GSI_UNICODE +#define piQRAddErrorCallback piQRAddErrorCallbackA +#else +#define piQRAddErrorCallback piQRAddErrorCallbackW +#endif + +static void piQRServerKeyCallback +( + int key, + qr2_buffer_t buffer, + PEER peer +) +{ + PEER_CONNECTION; + + // check if we should report it + if(connection->inRoom[StagingRoom] && (!connection->playing || (connection->reportingOptions & PEER_REPORT_INFO))) + { + // check if it's one of our keys + switch(key) + { + case HOSTNAME_KEY: + qr2_buffer_addA(buffer, NAMES[StagingRoom]); + return; + case NUMPLAYERS_KEY: + qr2_buffer_add_int(buffer, connection->numPlayers[StagingRoom]); + return; + case MAXPLAYERS_KEY: + if(!connection->maxPlayers) + break; + qr2_buffer_add_int(buffer, connection->maxPlayers); + return; + case GAMEMODE_KEY: + if(connection->playing) + break; + qr2_buffer_addA(buffer, "openstaging"); + return; + case PASSWORD_KEY: + qr2_buffer_add_int(buffer, connection->passwordedRoom?1:0); + return; + } + } + + // we always report groupid + if(key == GROUPID_KEY) + { + qr2_buffer_add_int(buffer, connection->reportingGroupID); + return; + } + + // pass it to the app + if(connection->callbacks.qrServerKey) + connection->callbacks.qrServerKey(peer, key, buffer, connection->callbacks.param); +} + +static void piQRPlayerKeyCallback +( + int key, + int index, + qr2_buffer_t buffer, + PEER peer +) +{ + piPlayer * player; + PEER_CONNECTION; + + // check if we should report it + if(connection->inRoom[StagingRoom] && (!connection->playing || (connection->reportingOptions & PEER_REPORT_PLAYERS))) + { + // check if it's a key we report + if((key == PLAYER__KEY) || (key == PING__KEY)) + { + // convert the index to a player + player = piFindPlayerByIndex(peer, StagingRoom, index); + if(!player) + qr2_buffer_addA(buffer, ""); + else if(key == PLAYER__KEY) + qr2_buffer_addA(buffer, player->nick); + else if(key == PING__KEY) + qr2_buffer_add_int(buffer, player->pingAverage); + + return; + } + } + + // pass it to the app + if(connection->callbacks.qrPlayerKey) + connection->callbacks.qrPlayerKey(peer, key, index, buffer, connection->callbacks.param); +} + +static void piQRTeamKeyCallback +( + int key, + int index, + qr2_buffer_t buffer, + PEER peer +) +{ + PEER_CONNECTION; + + // pass it to the app + if(connection->callbacks.qrTeamKey) + connection->callbacks.qrTeamKey(peer, key, index, buffer, connection->callbacks.param); +} + +static void piQRKeyListCallback +( + qr2_key_type type, + qr2_keybuffer_t keyBuffer, + PEER peer +) +{ + PEER_CONNECTION; + + // add our own keys + switch(type) + { + case key_server: + if(!connection->autoMatchReporting) + { + qr2_keybuffer_add(keyBuffer, HOSTNAME_KEY); + qr2_keybuffer_add(keyBuffer, GAMEMODE_KEY); + if(connection->passwordedRoom) + qr2_keybuffer_add(keyBuffer, PASSWORD_KEY); + if(connection->reportingGroupID) + qr2_keybuffer_add(keyBuffer, GROUPID_KEY); + } + qr2_keybuffer_add(keyBuffer, NUMPLAYERS_KEY); + if(connection->maxPlayers) + qr2_keybuffer_add(keyBuffer, MAXPLAYERS_KEY); + break; + case key_player: + qr2_keybuffer_add(keyBuffer, PLAYER__KEY); + qr2_keybuffer_add(keyBuffer, PING__KEY); + break; + default: + break; + } + + // pass it to the app + if(connection->callbacks.qrKeyList) + connection->callbacks.qrKeyList(peer, type, keyBuffer, connection->callbacks.param); +} + +static int piQRCountCallback +( + qr2_key_type type, + PEER peer +) +{ + PEER_CONNECTION; + + // we only handle player counts + if(type == key_player) + { + // check if we should report it + if(connection->inRoom[StagingRoom] && (!connection->playing || (connection->reportingOptions & PEER_REPORT_PLAYERS))) + return connection->numPlayers[StagingRoom]; + } + + // pass it to the app + if(connection->callbacks.qrCount) + return connection->callbacks.qrCount(peer, type, connection->callbacks.param); + + // app's not handling it + return 0; +} + +static void piQRAddErrorCallbackA +( + qr2_error_t error, + char * errorString, + PEER peer +) +{ + PEER_CONNECTION; + + // handle this if automatching + if(peerIsAutoMatching(peer)) + { + PEERAutoMatchStatus status; + + // track the error + connection->autoMatchQRFailed = PEERTrue; + + // switch to a new state + if(connection->autoMatchSBFailed && (connection->autoMatchStatus != PEERStaging)) + status = PEERFailed; + else + status = PEERSearching; + piSetAutoMatchStatus(peer, status); + + return; + } + + // pass it to the app + if(connection->callbacks.qrAddError) + { +#ifndef GSI_UNICODE + connection->callbacks.qrAddError(peer, error, errorString, connection->callbacks.param); +#else + unsigned short* errorString_W = UTF8ToUCS2StringAlloc(errorString); + connection->callbacks.qrAddError(peer, error, errorString_W, connection->callbacks.param); + gsifree(errorString_W); +#endif + } +} +#ifdef GSI_UNICODE +static void piQRAddErrorCallbackW +( + qr2_error_t error, + unsigned short * errorString, + PEER peer +) +{ + char* errorString_A = UCS2ToUTF8StringAlloc(errorString); + piQRAddErrorCallbackA(error, errorString_A, peer); + gsifree(errorString_A); +} +#endif + +static void piQRNatNegotiateCallback +( + int cookie, + PEER peer +) +{ + PEER_CONNECTION; + + // pass it to the app + if(connection->callbacks.qrNatNegotiateCallback) + connection->callbacks.qrNatNegotiateCallback(peer, cookie, connection->callbacks.param); +} + +static void piQRPublicAddressCallback +( + unsigned int ip, + unsigned short port, + PEER peer +) +{ + PEER_CONNECTION; + + // pass it to the app + if(connection->callbacks.qrPublicAddressCallback) + connection->callbacks.qrPublicAddressCallback(peer, ip, port, connection->callbacks.param); +} + +/************** +** FUNCTIONS ** +**************/ +PEERBool piStartReporting +( + PEER peer, + SOCKET socket, + unsigned short port +) +{ + int rcode; + + PEER_CONNECTION; + + // Check that we're not reporting. + ////////////////////////////////// + assert(!connection->queryReporting); + if(connection->queryReporting) + piStopReporting(peer); + + // Init qr2. + //////////// + if(socket == INVALID_SOCKET) + { + rcode = qr2_initA(&connection->queryReporting, NULL, PI_QUERYPORT, connection->title, connection->qrSecretKey, + 1, connection->natNegotiate, + piQRServerKeyCallback, + piQRPlayerKeyCallback, + piQRTeamKeyCallback, + piQRKeyListCallback, + piQRCountCallback, + piQRAddErrorCallback, + peer); + } + else + { + rcode = qr2_init_socketA(&connection->queryReporting, socket, port, connection->title, connection->qrSecretKey, + 1, connection->natNegotiate, + piQRServerKeyCallback, + piQRPlayerKeyCallback, + piQRTeamKeyCallback, + piQRKeyListCallback, + piQRCountCallback, + piQRAddErrorCallback, + peer); + } + + if(rcode != 0) + return PEERFalse; + + // Store the ID of the group room we're in. + /////////////////////////////////////////// + connection->reportingGroupID = connection->groupID; + + // Setup the nat-negotiate callback. + //////////////////////////////////// + qr2_register_natneg_callback(connection->queryReporting, piQRNatNegotiateCallback); + + // Set the public address callback. + /////////////////////////////////// + qr2_register_publicaddress_callback(connection->queryReporting, piQRPublicAddressCallback); + + // No options. + ////////////// + connection->reportingOptions = 0; + + return PEERTrue; +} + +void piStopReporting +( + PEER peer +) +{ + PEER_CONNECTION; + + // Check that we're reporting. + ////////////////////////////// + if(!connection->queryReporting) + return; + + // Clean up query-reporting. + //////////////////////////// + qr2_shutdown(connection->queryReporting); + connection->queryReporting = NULL; +} + +void piSendStateChanged +( + PEER peer +) +{ + PEER_CONNECTION; + + // Check that we're reporting. + ////////////////////////////// + if(!connection->queryReporting) + return; + + // Send the statechanged. + ///////////////////////// + qr2_send_statechanged(connection->queryReporting); +} + +void piQRThink +( + PEER peer +) +{ + PEER_CONNECTION; + + if(connection->queryReporting) + qr2_think(connection->queryReporting); + if(connection->autoMatchReporting) + qr2_think(connection->autoMatchReporting); +} + +PEERBool piStartAutoMatchReporting +( + PEER peer +) +{ + piOperation * operation; + char autoMatchTitle[PI_TITLE_MAX_LEN]; + int rcode; + + PEER_CONNECTION; + + // Check that we're not reporting. + ////////////////////////////////// + assert(!connection->autoMatchReporting); + if(connection->autoMatchReporting) + piStopAutoMatchReporting(peer); + + // Get the operation. + ///////////////////// + operation = connection->autoMatchOperation; + assert(operation); + + // Setup the AutoMatch gamename. + //////////////////////////////// + strzcpy(autoMatchTitle, connection->title, sizeof(autoMatchTitle)); + strzcat(autoMatchTitle, "am", sizeof(autoMatchTitle)); + + // Init qr2. + //////////// + if(operation->socket == INVALID_SOCKET) + { + rcode = qr2_initA(&connection->autoMatchReporting, NULL, PI_QUERYPORT, autoMatchTitle, connection->qrSecretKey, + 1, connection->natNegotiate, + piQRServerKeyCallback, + piQRPlayerKeyCallback, + piQRTeamKeyCallback, + piQRKeyListCallback, + piQRCountCallback, + piQRAddErrorCallback, + peer); + } + else + { + rcode = qr2_init_socketA(&connection->autoMatchReporting, operation->socket, operation->port, autoMatchTitle, connection->qrSecretKey, + 1, connection->natNegotiate, + piQRServerKeyCallback, + piQRPlayerKeyCallback, + piQRTeamKeyCallback, + piQRKeyListCallback, + piQRCountCallback, + piQRAddErrorCallback, + peer); + + // If we created the socket, hand over responsibility for it to qr2. + //////////////////////////////////////////////////////////////////// + if(operation->socketClose) + { + operation->socketClose = PEERFalse; + connection->autoMatchReporting->read_socket = 1; + } + } + + // Set the error flag. + ////////////////////// + connection->autoMatchQRFailed = (rcode == 0)?PEERTrue:PEERFalse; + + // Return false if there was an error. + ////////////////////////////////////// + if(rcode != 0) + return PEERFalse; + + // Setup the nat-negotiate callback. + //////////////////////////////////// + qr2_register_natneg_callback(connection->autoMatchReporting, piQRNatNegotiateCallback); + + // Set the public address callback. + /////////////////////////////////// + qr2_register_publicaddress_callback(connection->autoMatchReporting, piQRPublicAddressCallback); + + return PEERTrue; +} + +void piStopAutoMatchReporting +( + PEER peer +) +{ + PEER_CONNECTION; + + // Check that we're reporting. + ////////////////////////////// + if(!connection->autoMatchReporting) + return; + + // Clean up query-reporting. + //////////////////////////// + qr2_shutdown(connection->autoMatchReporting); + connection->autoMatchReporting = NULL; + + // This socket should have been cleared after qr2_shutdown + // but it's a copy of connection->autoMatchReporting->hbsock + // Thus it needs to be cleared if it hasn't been already + if (connection->autoMatchOperation->socket != INVALID_SOCKET) + { + connection->autoMatchOperation->socket = INVALID_SOCKET; + } +} diff --git a/code/gamespy/Peer/peerQR.h b/code/gamespy/Peer/peerQR.h new file mode 100644 index 00000000..0805a656 --- /dev/null +++ b/code/gamespy/Peer/peerQR.h @@ -0,0 +1,44 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERQR_H_ +#define _PEERQR_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/************ +** DEFINES ** +************/ +#define PI_QUERYPORT 6500 + +/************** +** FUNCTIONS ** +**************/ +PEERBool piStartReporting(PEER peer, SOCKET socket, unsigned short port); +void piStopReporting(PEER peer); +void piSendStateChanged(PEER peer); +void piQRThink(PEER peer); +PEERBool piStartAutoMatchReporting(PEER peer); +void piStopAutoMatchReporting(PEER peer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerRooms.c b/code/gamespy/Peer/peerRooms.c new file mode 100644 index 00000000..d7128e8d --- /dev/null +++ b/code/gamespy/Peer/peerRooms.c @@ -0,0 +1,416 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include +#include +#include "peerRooms.h" +#include "peerPlayers.h" +#include "peerMangle.h" +#include "peerCallbacks.h" +#include "peerKeys.h" +#include "peerQR.h" +#include "peerHost.h" +#include "peerOperations.h" +#include "peerSB.h" + +/************** +** FUNCTIONS ** +**************/ +PEERBool piRoomsInit +( + PEER peer +) +{ + int roomType; + + PEER_CONNECTION; + + // Init rooms. + ////////////// + for(roomType = 0 ; roomType < NumRooms ; roomType++) + { + if(connection->stayInTitleRoom && ((RoomType)roomType == TitleRoom)) + continue; + + ROOM[0] = '\0'; + NAME[0] = '\0'; + ENTERING_ROOM = PEERFalse; + IN_ROOM = PEERFalse; + connection->oldFlags[roomType] = 0; + +#ifdef GSI_UNICODE + ROOM_W[0] = '\0'; +#endif + } + connection->groupID = 0; + connection->titleRoomChannel[0] = '\0'; + + return PEERTrue; +} + +void piRoomsCleanup +( + PEER peer +) +{ + int roomType; + + PEER_CONNECTION; + + // Check all the rooms. + /////////////////////// + for(roomType = 0 ; roomType < NumRooms ; roomType++) + { + if(connection->stayInTitleRoom && ((RoomType)roomType == TitleRoom)) + continue; + + // Are we in or entering the room? + ////////////////////////////////// + if(IN_ROOM || ENTERING_ROOM) + { + // Leave it. + //////////// + piLeaveRoom(peer, (RoomType)roomType, NULL); + } + ROOM[0] = '\0'; + NAME[0] = '\0'; + ENTERING_ROOM = PEERFalse; + IN_ROOM = PEERFalse; + } + connection->titleRoomChannel[0] = '\0'; +} + +void piStartedEnteringRoom +( + PEER peer, + RoomType roomType, + const char * room +) +{ + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + assert(room); + assert(room[0]); + assert(strlen(room) < PI_ROOM_MAX_LEN); + if(strlen(room) >= PI_ROOM_MAX_LEN) + return; + + // Check that we're not entering, or in, this room. + /////////////////////////////////////////////////// + assert(!ROOM[0]); + assert(!ENTERING_ROOM); + assert(!IN_ROOM); + + // Start entering. + ////////////////// + strcpy(ROOM, room); + ENTERING_ROOM = PEERTrue; + connection->oldFlags[roomType] = 0; + +#ifdef GSI_UNICODE + UTF8ToUCS2String(ROOM, ROOM_W); +#endif +} + + +void piFinishedEnteringRoom +( + PEER peer, + RoomType roomType, + const char * name +) +{ + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + + if(!name) + name = ""; + + // Check that we're entering. + ///////////////////////////// + assert(ROOM[0]); + assert(ENTERING_ROOM); + assert(!IN_ROOM); + assert(strlen(name) < PI_ROOM_MAX_LEN); + + // We're in. + //////////// + IN_ROOM = PEERTrue; + ENTERING_ROOM = PEERFalse; + strzcpy(NAME, name, PI_NAME_MAX_LEN); + +#ifdef GSI_UNICODE + UTF8ToUCS2String(NAME, NAME_W); +#endif + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); + + // Refresh the watch keys for this room. + //////////////////////////////////////// + piKeyCacheRefreshRoom(peer, roomType); +} + +void piLeaveRoom +( + PEER peer, + RoomType roomType, + const char * reason +) +{ + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + + // Check that we're in/entering this room. + ////////////////////////////////////////// + if(!ENTERING_ROOM && !IN_ROOM) + return; + + assert(ROOM[0]); + + // Are we entering? + /////////////////// + if(ENTERING_ROOM) + { + // Cancel the operation. + //////////////////////// + piCancelJoinOperation(peer, roomType); + } + + // Leave the channel. + ///////////////////// + if(connection->connected) + chatLeaveChannelA(connection->chat, ROOM, reason); + + // Clear all the players out of this room. + ////////////////////////////////////////// + piClearRoomPlayers(peer, roomType); + + // Reset in/entering states. + //////////////////////////// + if(IN_ROOM) + { + assert(!ENTERING_ROOM); + IN_ROOM = PEERFalse; + } + else + { + assert(ENTERING_ROOM); + ENTERING_ROOM = PEERFalse; + } + + // Clear the room/name. + /////////////////////// + ROOM[0] = '\0'; + NAME[0] = '\0'; + +#ifdef GSI_UNICODE + ROOM_W[0] = '\0'; +#endif + + // Clear the flags. + /////////////////// + connection->oldFlags[roomType] = 0; + + // Do roomtype specific stuff. + ////////////////////////////// + if(roomType == StagingRoom) + { + // Stop hosting. + //////////////// + piStopHosting(peer, PEERFalse); + + // Stop reporting as long as we're not playing. + /////////////////////////////////////////////// + if(!connection->playing) + piStopReporting(peer); + + // No host server. + ////////////////// + piSBFreeHostServer(peer); + + // Turn ready off. + ////////////////// + connection->ready = PEERFalse; + + // Clear passworded flag. + ///////////////////////// + connection->passwordedRoom = PEERFalse; + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); + } + else if(roomType == GroupRoom) + { + // Clear the group ID. + ////////////////////// + connection->groupID = 0; + } + + // Cleanse the key cache. + ///////////////////////// + piKeyCacheCleanse(peer); +} + +PEERBool piRoomToType +( + PEER peer, + const char * room, + RoomType * roomType +) +{ + int i; + + PEER_CONNECTION; + + for(i = 0 ; i < NumRooms ; i++) + { + if(strcasecmp(room, ROOMS[i]) == 0) + { + *roomType = (RoomType)i; + return PEERTrue; + } + } + + return PEERFalse; +} + +void piSetLocalFlags +( + PEER peer +) +{ + char buffer[NumRooms][128]; + char * titleRoom; + char * groupRoom; + char * stagingRoom; + const char * key = "b_flags"; + int nFlags; + + PEER_CONNECTION; + + if(!connection->connected) + return; + + if(connection->inRoom[TitleRoom] || connection->enteringRoom[TitleRoom]) + titleRoom = buffer[TitleRoom]; + else + titleRoom = NULL; + if(connection->inRoom[GroupRoom] || connection->enteringRoom[GroupRoom]) + groupRoom = buffer[GroupRoom]; + else + groupRoom = NULL; + if(connection->inRoom[StagingRoom] || connection->enteringRoom[StagingRoom]) + stagingRoom = buffer[StagingRoom]; + else + stagingRoom = NULL; + + // Check for staging room. + ////////////////////////// + if(connection->inRoom[StagingRoom]) + { + if(titleRoom) + *titleRoom++ = 's'; + if(groupRoom) + *groupRoom++ = 's'; + *stagingRoom++ = 's'; + + // Check for ready. + /////////////////// + if(connection->ready) + *stagingRoom++ = 'r'; + + // Check for hosting. + ///////////////////// + if(connection->hosting) + *stagingRoom++ = 'h'; + } + + // Check for playing. + ///////////////////// + if(connection->playing) + { + if(titleRoom) + *titleRoom++ = 'g'; + if(groupRoom) + *groupRoom++ = 'g'; + if(stagingRoom) + *stagingRoom++ = 'g'; + } + + // Check for away. + ////////////////// + if(connection->away) + { + if(titleRoom) + *titleRoom++ = 'a'; + if(groupRoom) + *groupRoom++ = 'a'; + if(stagingRoom) + *stagingRoom++ = 'a'; + } + + // Cap it off. + ////////////// + if(titleRoom) + { + *titleRoom = '\0'; + titleRoom = buffer[TitleRoom]; + } + if(groupRoom) + { + *groupRoom = '\0'; + groupRoom = buffer[GroupRoom]; + } + if(stagingRoom) + { + *stagingRoom = '\0'; + stagingRoom = buffer[StagingRoom]; + } + + // Set the keys. + //////////////// + if(titleRoom) + { + nFlags = piParseFlags(titleRoom); + if(nFlags != connection->oldFlags[TitleRoom]) + { + chatSetChannelKeysA(connection->chat, connection->rooms[TitleRoom], connection->nick, 1, &key, (const char **)&titleRoom); + connection->oldFlags[TitleRoom] = nFlags; + } + } + if(groupRoom) + { + nFlags = piParseFlags(groupRoom); + if(nFlags != connection->oldFlags[GroupRoom]) + { + chatSetChannelKeysA(connection->chat, connection->rooms[GroupRoom], connection->nick, 1, &key, (const char **)&groupRoom); + connection->oldFlags[GroupRoom] = nFlags; + } + } + if(stagingRoom) + { + nFlags = piParseFlags(stagingRoom); + if(nFlags != connection->oldFlags[StagingRoom]) + { + chatSetChannelKeysA(connection->chat, connection->rooms[StagingRoom], connection->nick, 1, &key, (const char **)&stagingRoom); + connection->oldFlags[StagingRoom] = nFlags; + } + } +} diff --git a/code/gamespy/Peer/peerRooms.h b/code/gamespy/Peer/peerRooms.h new file mode 100644 index 00000000..cc6388b4 --- /dev/null +++ b/code/gamespy/Peer/peerRooms.h @@ -0,0 +1,40 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERROOMS_H_ +#define _PEERROOMS_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/************** +** FUNCTIONS ** +**************/ +PEERBool piRoomsInit(PEER peer); +void piRoomsCleanup(PEER peer); +void piStartedEnteringRoom(PEER peer, RoomType roomType, const char * room); +void piFinishedEnteringRoom(PEER peer, RoomType roomType, const char * name); +void piLeaveRoom(PEER peer, RoomType roomType, const char * reason); +PEERBool piRoomToType(PEER peer, const char * room, RoomType * roomType); +void piSetLocalFlags(PEER peer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerSB.c b/code/gamespy/Peer/peerSB.c new file mode 100644 index 00000000..cb5a1b75 --- /dev/null +++ b/code/gamespy/Peer/peerSB.c @@ -0,0 +1,1112 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include +#include +#include "peerMain.h" +#include "peerOperations.h" +#include "peerSB.h" +#include "peerCallbacks.h" +#include "peerMangle.h" +#include "peerRooms.h" +#include "peerAutoMatch.h" +#include "peerQR.h" + +/************ +** GLOBALS ** +************/ +int piSBQueryVersion = QVERSION_QR2; + +/********** +** GAMES ** +**********/ +static void piSBGamesListCallback +( + SBServerListPtr serverlist, + SBListCallbackReason reason, + SBServer server, + void * instance +) +{ + PEER peer = (PEER)instance; + PEER_CONNECTION; + +#if 0 + { + static const char reasonStrings[][32] = + { + "serveradded", + "serverupdated", + "serverdeleted", + "initiallistcomplete", + "disconnected", + "queryerror", + "publicipdetermined" + }; + char buffer[256]; + sprintf(buffer, "GAMES | LIST | %s", reasonStrings[reason]); + if(server) + sprintf(buffer + strlen(buffer), " | %s:%d", SBServerGetPublicAddress(server), SBServerGetPublicQueryPort(server)); + strcat(buffer, "\n"); + OutputDebugString(buffer); + } +#endif + + // handle based on the callback reason + switch(reason) + { + case slc_serveradded: + // if it's added call the callback and get it updated + piAddListingGamesCallback(peer, PEERTrue, server, PEER_ADD); + if(!SBServerHasBasicKeys(server)) + { + SBBool usequerychallenge = SBFalse; + if (serverlist->backendgameflags & QR2_USE_QUERY_CHALLENGE) + usequerychallenge = SBTrue; + SBQueryEngineUpdateServer(&connection->gameEngine, server, 0, QTYPE_BASIC, usequerychallenge); + } + break; + + case slc_serverupdated: + // if it's updated let the app know + piAddListingGamesCallback(peer, PEERTrue, server, PEER_UPDATE); + break; + + case slc_serverdeleted: + // if it's deleted, call the callback + if ((server->state & (STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY|STATE_PENDINGICMPQUERY)) != 0) + SBQueryEngineRemoveServerFromFIFOs(&connection->gameEngine, server); + piAddListingGamesCallback(peer, PEERTrue, server, PEER_REMOVE); + break; + + case slc_initiallistcomplete: + // done with the initial list + connection->initialGameList = PEERFalse; + + // let the app know + piAddListingGamesCallback(peer, PEERTrue, NULL, PEER_COMPLETE); + + break; + + case slc_queryerror: + // call the failed callback + piAddListingGamesCallback(peer, PEERFalse, NULL, 0); + break; + + case slc_publicipdetermined: + // let the engine know the IP + connection->publicIP = serverlist->mypublicip; + SBQueryEngineSetPublicIP(&connection->gameEngine, serverlist->mypublicip); + break; + + default: + break; + } +} + +static void piSBGamesEngineCallback +( + SBQueryEnginePtr engine, + SBQueryEngineCallbackReason reason, + SBServer server, + void * instance +) +{ + PEER peer = (PEER)instance; + //PEER_CONNECTION; + +#if 0 + { + static const char reasonStrings[][32] = + { + "updatesuccess", + "updatefailed", + "engineidle" + }; + char buffer[256]; + sprintf(buffer, "GAMES | ENGINE | %s", reasonStrings[reason]); + if(server) + sprintf(buffer + strlen(buffer), " | %s:%d", SBServerGetPublicAddress(server), SBServerGetPublicQueryPort(server)); + strcat(buffer, "\n"); + OutputDebugString(buffer); + } +#endif + + // if this is an updated server, call the callback + if(reason == qe_updatesuccess) + piAddListingGamesCallback(peer, PEERTrue, server, PEER_UPDATE); + + GSI_UNUSED(engine); +} + +PEERBool piSBStartListingGames +( + PEER peer, + const unsigned char * fields, + int numFields, + const char * filter +) +{ + char groupFilter[32]; + char smartSpyFilter[MAX_FILTER_LEN]; + char fullFields[MAX_FIELD_LIST_LEN]; + int listLen; + int keyLen; + int i; + + PEER_CONNECTION; + + // check that we're initialized. + assert(connection->sbInitialized); + if(!connection->sbInitialized) + return PEERFalse; + + // Stop it if it's going. + ///////////////////////// + piSBStopListingGames(peer); + + // Clear the list. + ////////////////// + SBServerListClear(&connection->gameList); + + // Filter by group if in one. + ///////////////////////////// + if(connection->groupID) + sprintf(groupFilter, "groupid=%d", connection->groupID); + else + strcpy(groupFilter, "groupid is null"); + + // Setup the actual filter. + /////////////////////////// + if(filter) + { + sprintf(smartSpyFilter, "(%s) AND (", groupFilter); + strzcat(smartSpyFilter, filter, sizeof(smartSpyFilter) - 1); + strcat(smartSpyFilter, ")"); + } + else + { + strcpy(smartSpyFilter, groupFilter); + } + + // Clear this in case it was set. + ///////////////////////////////// + connection->gameEngine.numserverkeys = 0; + + // We always set these keys. + //////////////////////////// + strcpy(fullFields, "\\hostname\\gamemode"); + listLen = (int)strlen(fullFields); + SBQueryEngineAddQueryKey(&connection->gameEngine, HOSTNAME_KEY); + SBQueryEngineAddQueryKey(&connection->gameEngine, GAMEMODE_KEY); + + // Build the key list. + ////////////////////// + for(i = 0 ; i < numFields ; i++) + { + // Check that we have the space to add the key. + /////////////////////////////////////////////// + keyLen = (int)strlen(qr2_registered_key_list[fields[i]]); + if((listLen + keyLen + 1) >= MAX_FIELD_LIST_LEN) + break; + + // Add the key. + /////////////// + listLen += sprintf(fullFields + listLen, "\\%s", qr2_registered_key_list[fields[i]]); + + // Add to the engine query list. + //////////////////////////////// + SBQueryEngineAddQueryKey(&connection->gameEngine, fields[i]); + } + + // Start updating the list. + /////////////////////////// + if(SBServerListConnectAndQuery(&connection->gameList, fullFields, smartSpyFilter, PUSH_UPDATES, 0) != sbe_noerror) + { + piSBStopListingGames(peer); + return PEERFalse; + } + + // Doing the initial listing. + ///////////////////////////// + connection->initialGameList = PEERTrue; + + // Clear the list before they start getting servers. + //////////////////////////////////////////////////// + piAddListingGamesCallback(peer, PEERTrue, NULL, PEER_CLEAR); + + return PEERTrue; +} + +void piSBStopListingGames +( + PEER peer +) +{ + PEER_CONNECTION; + + // check that we're initialized. + assert(connection->sbInitialized); + if(!connection->sbInitialized) + return; + + // Stop the listing. + //////////////////// + SBServerListDisconnect(&connection->gameList); + + // Stop the engine. + /////////////////// + SBEngineHaltUpdates(&connection->gameEngine); + + // Remove all pending game list callbacks. + ////////////////////////////////////////// + piClearCallbacks(peer, PI_LISTING_GAMES_CALLBACK); +} + +/*********** +** GROUPS ** +***********/ +static void piSBGroupsListCallback +( + SBServerListPtr serverlist, + SBListCallbackReason reason, + SBServer server, + void * instance +) +{ + piOperation * operation; + PEER peer = (PEER)instance; + PEER_CONNECTION; + + operation = connection->listingGroupsOperation; + +#if 0 + { + static const char reasonStrings[][32] = + { + "serveradded", + "serverupdated", + "serverdeleted", + "initiallistcomplete", + "disconnected", + "queryerror", + "publicipdetermined" + }; + char buffer[256]; + sprintf(buffer, "GROUPS | LIST | %s", reasonStrings[reason]); + if(server) + sprintf(buffer + strlen(buffer), " | %d", ntohl(SBServerGetPublicInetAddress(server))); + strcat(buffer, "\n"); + OutputDebugString(buffer); + } +#endif + + // handle based on the callback reason + switch(reason) + { + // if it's added call the callback + case slc_serveradded: + { + int groupID = (int)ntohl(SBServerGetPublicInetAddress(server)); +//#ifndef GSI_PEER_UNICODE + const char * name = SBServerGetStringValueA(server, "hostname", "(No Name)"); + int numWaiting = SBServerGetIntValueA(server, "numwaiting", 0); + int maxWaiting = SBServerGetIntValueA(server, "maxwaiting", 0); + int numGames = SBServerGetIntValueA(server, "numservers", 0); + int numPlaying = SBServerGetIntValueA(server, "numplayers", 0); +//#else +// const unsigned short * name = SBServerGetStringValue(server, L"hostname", L"(No Name)"); +// int numWaiting = SBServerGetIntValue(server, L"numwaiting", 0); +// int maxWaiting = SBServerGetIntValue(server, L"maxwaiting", 0); +// int numGames = SBServerGetIntValue(server, L"numservers", 0); +// int numPlaying = SBServerGetIntValue(server, L"numplayers", 0); +//#endif + piAddListGroupRoomsCallback(peer, PEERTrue, groupID, server, name, numWaiting, maxWaiting, numGames, numPlaying, (peerListGroupRoomsCallback)operation->callback, operation->callbackParam, operation->ID); + break; + } + + // initial list completion + case slc_initiallistcomplete: + // calling the terminating callback + piAddListGroupRoomsCallback(peer, PEERTrue, 0, NULL, NULL, 0, 0, 0, 0, (peerListGroupRoomsCallback)operation->callback, operation->callbackParam, operation->ID); + piRemoveOperation(peer, operation); + connection->listingGroupsOperation = NULL; + break; + + // check for a query error + case slc_queryerror: + // call the failed callback + piAddListGroupRoomsCallback(peer, PEERFalse, 0, NULL, NULL, 0, 0, 0, 0, (peerListGroupRoomsCallback)operation->callback, operation->callbackParam, operation->ID); + piRemoveOperation(peer, operation); + connection->listingGroupsOperation = NULL; + break; + + default: + break; + } + + GSI_UNUSED(serverlist); +} + +PEERBool piSBStartListingGroups +( + PEER peer, + const char * fields +) +{ + char fullFields[MAX_FIELD_LIST_LEN + 1]; + int len; + + PEER_CONNECTION; + + // check that we're initialized. + assert(connection->sbInitialized); + if(!connection->sbInitialized) + return PEERFalse; + + // Stop it if it's going. + ///////////////////////// + piSBStopListingGroups(peer); + + // Clear the list. + ////////////////// + SBServerListClear(&connection->groupList); + + // Setup the fields list. + ///////////////////////// + strcpy(fullFields, "\\hostname\\numwaiting\\maxwaiting\\numservers\\numplayers"); + len = (int)strlen(fullFields); + strzcpy(fullFields + len, fields, (unsigned int)MAX_FIELD_LIST_LEN - len); + + // Start updating the list. + /////////////////////////// + if(SBServerListConnectAndQuery(&connection->groupList, fullFields, "", SEND_GROUPS, 0) != sbe_noerror) + { + piSBStopListingGroups(peer); + return PEERFalse; + } + + return PEERTrue; +} + +void piSBStopListingGroups +( + PEER peer +) +{ + PEER_CONNECTION; + + // check that we're initialized. + assert(connection->sbInitialized); + if(!connection->sbInitialized) + return; + + // Stop the listing. + //////////////////// + SBServerListDisconnect(&connection->groupList); +} + +/************** +** AUTOMATCH ** +**************/ +static PEERBool piIsLocalServer(PEER peer, SBServer server) +{ + PEER_CONNECTION; + + // check the public ip + if(SBServerGetPublicInetAddress(server) != connection->publicIP) + return PEERFalse; + + // check the private ip and port + if(SBServerHasPrivateAddress(server)) + { + if(SBServerGetPrivateInetAddress(server) != connection->privateIP) + return PEERFalse; + + if(SBServerGetPrivateQueryPort(server) != connection->autoMatchOperation->port) + return PEERFalse; + } + // check the public port + else + { + if(SBServerGetPublicQueryPort(server) != connection->autoMatchOperation->port) + return PEERFalse; + } + + return PEERTrue; +} + +static int piSBAutoMatchGetServerRating(PEER peer, SBServer server) +{ + PEER_CONNECTION; + + // ignore the local machine + if(connection->autoMatchReporting && piIsLocalServer(peer, server)) + return 0; + + // check if it got the keys. + if(!SBServerHasFullKeys(server)) + return 0; + + // ignore full servers +#ifndef GSI_UNICODE + if(SBServerGetIntValue(server, "numplayers", 0) >= SBServerGetIntValue(server, "maxplayers", 0)) + return 0; +#else + if(SBServerGetIntValue(server, L"numplayers", 0) >= SBServerGetIntValue(server, L"maxplayers", 0)) + return 0; +#endif + + // return the rating for this server + return piCallAutoMatchRateCallback(peer, server); +} + +static void piSBAutoMatchCheckUpdatedServer(PEER peer, SBServer server) +{ + int rating; + + PEER_CONNECTION; + + // only do this if we're waiting + if(connection->autoMatchStatus != PEERWaiting) + return; + + // if we're already joining a staging room, ignore this + if(connection->enteringRoom[StagingRoom]) + return; + + // get the rating for this server + rating = piSBAutoMatchGetServerRating(peer, server); + + // if it's not acceptable, ignore + if(rating <= 0) + return; + + // stop reporting and leave the room + piStopAutoMatchReporting(peer); + piLeaveRoom(peer, StagingRoom, ""); + + // join the match + if(!piJoinAutoMatchRoom(peer, server)) + { + // it's bad news if it failed to start the op + piSetAutoMatchStatus(peer, PEERFailed); + } +} + +static void piSBAutoMatchCheckPushedServer(PEER peer, SBServer server) +{ + SortInfo info; + int aRating; + PEER_CONNECTION; + + // We must be in the waiting or searching phase to rate a server this way + if(connection->autoMatchStatus != PEERWaiting && connection->autoMatchStatus != PEERSearching) + return; + + // if we're already joining a staging room, ignore this + if(connection->enteringRoom[StagingRoom]) + return; + + // Check if this is the only server in our list + if (SBServerListCount(&connection->autoMatchList) == 1) + { + // get the rating for this server + aRating = piSBAutoMatchGetServerRating(peer, server); + + // if it's not acceptable, ignore + if(aRating <= 0) + return; + + // stop reporting and leave the room + piStopAutoMatchReporting(peer); + piLeaveRoom(peer, StagingRoom, ""); + + // join the match + if(!piJoinAutoMatchRoom(peer, server)) + { + // it's bad news if it failed to start the op + piSetAutoMatchStatus(peer, PEERFailed); + } + } + + // otherwise we'll need to run through all the servers + // rate each of them and join the one with the highest rating. + else if (SBServerListCount(&connection->autoMatchList) > 1) + { + int count, i; + SBServer aServer; + + // loop through all the servers. + count = SBServerListCount(&connection->autoMatchList); + for(i = (count - 1) ; i >= 0 ; i--) + { + // get the server. + aServer = SBServerListNth(&connection->autoMatchList, i); + + // get the rating for this server + aRating = piSBAutoMatchGetServerRating(peer, aServer); + if(aRating <= 0) + { + SBServerListRemoveAt(&connection->autoMatchList, i); + continue; + } + + // store this server's rating + SBServerAddIntKeyValue(aServer, PI_AUTOMATCH_RATING_KEY, aRating); + } + + // recount + count = SBServerListCount(&connection->autoMatchList); + + // no matches? + if(!count) + { + piSetAutoMatchStatus(peer, PEERWaiting); + return; + } + + // sort by rating +#ifdef GSI_UNICODE + _tcscpy(info.sortkey, (const unsigned short *)PI_AUTOMATCH_RATING_KEY); +#else + _tcscpy(info.sortkey, PI_AUTOMATCH_RATING_KEY); +#endif + info.comparemode = sbcm_int; + SBServerListSort(&connection->autoMatchList, SBTrue, info); + + piStopAutoMatchReporting(peer); + piLeaveRoom(peer, StagingRoom, ""); + + // join the top rated match + if(!piJoinAutoMatchRoom(peer, SBServerListNth(&connection->autoMatchList, 0))) + { + // it's bad news if it failed to start the op + piSetAutoMatchStatus(peer, PEERFailed); + } + } + +} + +static void piSBAutoMatchListCallback +( + SBServerListPtr serverlist, + SBListCallbackReason reason, + SBServer server, + void * instance +) +{ + PEER peer = (PEER)instance; + PEER_CONNECTION; + +#if 0 + { + static const char reasonStrings[][32] = + { + "serveradded", + "serverupdated", + "serverdeleted", + "initiallistcomplete", + "disconnected", + "queryerror", + "publicipdetermined" + }; + char buffer[256]; + sprintf(buffer, "AUTOMATCH | LIST | %s", reasonStrings[reason]); + if(server) + sprintf(buffer + strlen(buffer), " | %s:%d", SBServerGetPublicAddress(server), SBServerGetPublicQueryPort(server)); + strcat(buffer, "\n"); + OutputDebugString(buffer); + } +#endif + + // handle based on the callback reason + switch(reason) + { + case slc_serveradded: + // If we have the server info, or we already requested it + // just bail + if(server->state & (STATE_FULLKEYS|STATE_PENDINGFULLQUERY)) + break; + + // If it's behind a firewall, get the keys from the master + if (!SBServerDirectConnect(server)) + SBGetServerRulesFromMaster(&connection->autoMatchList, SBServerGetPublicInetAddress(server), SBServerGetPublicQueryPortNBO(server)); + else // send normal update + { + SBBool usequerychallenge = SBFalse; + if (serverlist->backendgameflags & QR2_USE_QUERY_CHALLENGE) + usequerychallenge = SBTrue; + SBQueryEngineUpdateServer(&connection->autoMatchEngine, server, 0, QTYPE_FULL, usequerychallenge); + } + break; + + case slc_serverupdated: + // If we don't have the full keys, request them + if(!SBServerHasFullKeys(server) && SBServerDirectConnect(server) && !(server->state & STATE_PENDINGFULLQUERY)) + { + // Send a normal query, if the server is firewalled we wouldn't be here + // assert(SBServerDirectConnect(server)) + SBBool usequerychallenge = SBFalse; + if (serverlist->backendgameflags & QR2_USE_QUERY_CHALLENGE) + usequerychallenge = SBTrue; + SBQueryEngineUpdateServer(&connection->autoMatchEngine, server, 0, QTYPE_FULL, usequerychallenge); + } + else if(!SBServerHasFullKeys(server) && !SBServerDirectConnect(server)) + { + SBGetServerRulesFromMaster(&connection->autoMatchList, SBServerGetPublicInetAddress(server), SBServerGetPublicQueryPortNBO(server)); + } + else + piSBAutoMatchCheckPushedServer(peer, server); + break; + + case slc_serverdeleted: + // make sure it's not in the update queue + if ((server->state & (STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY)) != 0) + SBQueryEngineRemoveServerFromFIFOs(&connection->autoMatchEngine, server); + break; + + case slc_initiallistcomplete: + // if no servers were found, start Waiting + // or if servers were found but all were behind a firewall, start Waiting + if(!SBServerListCount(&connection->autoMatchList) || + (0==connection->autoMatchEngine.querylist.count)) + { + piSetAutoMatchStatus(peer, PEERWaiting); + } + break; + case slc_queryerror: + // browsing failed + connection->autoMatchSBFailed = PEERTrue; + if(connection->autoMatchStatus == PEERSearching) + piSetAutoMatchStatus(peer, connection->autoMatchQRFailed?PEERFailed:PEERWaiting); + break; + + case slc_publicipdetermined: + // let the engine know the IP + connection->publicIP = serverlist->mypublicip; + SBQueryEngineSetPublicIP(&connection->gameEngine, serverlist->mypublicip); + break; + + default: + break; + } +} + +static void piSBAutoMatchEngineCallback +( + SBQueryEnginePtr engine, + SBQueryEngineCallbackReason reason, + SBServer server, + void * instance +) +{ + PEER peer = (PEER)instance; + int i; + int count; + int rating; + SortInfo info; + + PEER_CONNECTION; + +#if 0 + { + static const char reasonStrings[][32] = + { + "updatesuccess", + "updatefailed", + "engineidle" + }; + char buffer[256]; + sprintf(buffer, "AUTOMATCH | ENGINE | %s", reasonStrings[reason]); + if(server) + sprintf(buffer + strlen(buffer), " | %s:%d", SBServerGetPublicAddress(server), SBServerGetPublicQueryPort(server)); + strcat(buffer, "\n"); + OutputDebugString(buffer); + } +#endif + + // handle based on the callback reason + switch(reason) + { + case qe_updatesuccess: + // check the server + piSBAutoMatchCheckUpdatedServer(peer, server); + break; + + case qe_updatefailed: + if(!SBServerListCount(&connection->autoMatchList)) + piSetAutoMatchStatus(peer, PEERWaiting); + break; + + // check if the querying is complete. + case qe_engineidle: + // make sure we're searching + if(connection->autoMatchStatus != PEERSearching) + return; + + // if we're already in or joining a staging room, ignore this + if(connection->inRoom[StagingRoom] || connection->enteringRoom[StagingRoom]) + return; + + // loop through all the servers. + count = SBServerListCount(&connection->autoMatchList); + for(i = (count - 1) ; i >= 0 ; i--) + { + // get the server. + server = SBServerListNth(&connection->autoMatchList, i); + + // get the rating for this server + rating = piSBAutoMatchGetServerRating(peer, server); + if(rating <= 0) + { + SBServerListRemoveAt(&connection->autoMatchList, i); + continue; + } + + // store this server's rating + SBServerAddIntKeyValue(server, PI_AUTOMATCH_RATING_KEY, rating); + } + + // recount + count = SBServerListCount(&connection->autoMatchList); + + // no matches? + if(!count) + { + piSetAutoMatchStatus(peer, PEERWaiting); + return; + } + + // sort by rating +#ifdef GSI_UNICODE + _tcscpy(info.sortkey, (const unsigned short *)PI_AUTOMATCH_RATING_KEY); +#else + _tcscpy(info.sortkey, PI_AUTOMATCH_RATING_KEY); +#endif + + info.comparemode = sbcm_int; + SBServerListSort(&connection->autoMatchList, SBTrue, info); + + // join the top rated match + if(!piJoinAutoMatchRoom(peer, SBServerListNth(&connection->autoMatchList, 0))) + { + // it's bad news if it failed to start the op + piSetAutoMatchStatus(peer, PEERFailed); + } + + break; + + default: + break; + } + + GSI_UNUSED(engine); +} + +PEERBool piSBStartListingAutoMatches +( + PEER peer +) +{ + PEER_CONNECTION; + + // check that we're initialized. + assert(connection->sbInitialized); + if(!connection->sbInitialized) + { + connection->autoMatchSBFailed = PEERTrue; + return PEERFalse; + } + + // stop it if it's going. + piSBStopListingAutoMatches(peer); + + // clear the list. + SBServerListClear(&connection->autoMatchList); + + // start updating the list. + if(SBServerListConnectAndQuery(&connection->autoMatchList, NULL, connection->autoMatchFilter, PUSH_UPDATES, 0) != sbe_noerror) + { + piSBStopListingAutoMatches(peer); + connection->autoMatchSBFailed = PEERTrue; + return PEERFalse; + } + + // clear the error flag + connection->autoMatchSBFailed = PEERFalse; + + // we're browsing + connection->autoMatchBrowsing = PEERTrue; + + return PEERTrue; +} + +void piSBStopListingAutoMatches +( + PEER peer +) +{ + PEER_CONNECTION; + + // check that we're initialized. + assert(connection->sbInitialized); + if(!connection->sbInitialized) + return; + + // we're done browsing + connection->autoMatchBrowsing = PEERFalse; + + // stop the listing. + SBServerListDisconnect(&connection->autoMatchList); + + // stop the engine. + SBEngineHaltUpdates(&connection->autoMatchEngine); +} + +/************** +** FUNCTIONS ** +**************/ +PEERBool piSBInit +( + PEER peer +) +{ + char autoMatchTitle[PI_TITLE_MAX_LEN]; + + PEER_CONNECTION; + + // init the game list and engine + memset(&connection->gameList, 0, sizeof(connection->gameList)); + SBServerListInit(&connection->gameList, connection->title, connection->sbName, connection->sbSecretKey, connection->sbGameVersion, SBFalse, piSBGamesListCallback, peer); + SBQueryEngineInit(&connection->gameEngine, connection->sbMaxUpdates, piSBQueryVersion, SBFalse, piSBGamesEngineCallback, peer); + + // init the group list + memset(&connection->groupList, 0, sizeof(connection->groupList)); + SBServerListInit(&connection->groupList, connection->title, connection->sbName, connection->sbSecretKey, connection->sbGameVersion, SBFalse, piSBGroupsListCallback, peer); + + // init the AutoMatch list and engine + strzcpy(autoMatchTitle, connection->title, sizeof(autoMatchTitle)); + strzcat(autoMatchTitle, "am", sizeof(autoMatchTitle)); + memset(&connection->autoMatchList, 0, sizeof(connection->autoMatchList)); + SBServerListInit(&connection->autoMatchList, autoMatchTitle, connection->sbName, connection->sbSecretKey, connection->sbGameVersion, SBFalse, piSBAutoMatchListCallback, peer); + SBQueryEngineInit(&connection->autoMatchEngine, connection->sbMaxUpdates, piSBQueryVersion, SBFalse, piSBAutoMatchEngineCallback, peer); + + // no host server yet + connection->hostServer = NULL; + + // initialized + connection->sbInitialized = PEERTrue; + + return PEERTrue; +} + +void piSBCleanup +( + PEER peer +) +{ + PEER_CONNECTION; + + // stop listing + piSBStopListingGames(peer); + piSBStopListingGroups(peer); + + // cleanup the SB + if(connection->sbInitialized) + { + SBServerListCleanup(&connection->gameList); + SBEngineCleanup(&connection->gameEngine); + SBServerListCleanup(&connection->groupList); + SBServerListCleanup(&connection->autoMatchList); + SBEngineCleanup(&connection->autoMatchEngine); + } + + // AutoMatch cleanup + connection->autoMatchBrowsing = PEERFalse; +} + + +void piSBUpdateGame +( + PEER peer, + SBServer server, + PEERBool fullUpdate, + PEERBool forceUpdateByMaster, + PEERBool icmpEcho +) +{ + PEER_CONNECTION; + + // check that we're initialized. + assert(connection->sbInitialized); + + if(!connection->sbInitialized) + return; + + // Changed 08-26-2004 + // Saad Nader + // Added force update by master server parameter + // for internal update function + ///////////////////// + if (forceUpdateByMaster) + { + // remove from the existing update lists if present + if(server->flags & UNSOLICITED_UDP_FLAG) + SBQueryEngineRemoveServerFromFIFOs(&connection->gameEngine, server); + + // Obtain the information via the master server + SBGetServerRulesFromMaster(&connection->gameList, server->publicip, server->publicport); + return; + } + + if (icmpEcho && (server->flags & ICMP_IP_FLAG)) + { + SBBool usequerychallenge = SBFalse; + if (connection->gameList.backendgameflags & QR2_USE_QUERY_CHALLENGE) + usequerychallenge = SBTrue; + + if (server->flags & UNSOLICITED_UDP_FLAG) + SBQueryEngineRemoveServerFromFIFOs(&connection->gameEngine, server); + + SBQueryEngineUpdateServer(&connection->gameEngine, server, 1, QTYPE_ICMP, usequerychallenge); + return; + } + // if the server supports unsolicited UDP, query it directly + if(server->flags & UNSOLICITED_UDP_FLAG) + { + SBBool usequerychallenge = SBFalse; + if (connection->gameList.backendgameflags & QR2_USE_QUERY_CHALLENGE) + usequerychallenge = SBTrue; + + // remove from the existing update lists if present + SBQueryEngineRemoveServerFromFIFOs(&connection->gameEngine, server); + + // query the server + SBQueryEngineUpdateServer(&connection->gameEngine, server, 1, fullUpdate?QTYPE_FULL:QTYPE_BASIC, usequerychallenge); + } + else + { + // get the values from the master server + SBGetServerRulesFromMaster(&connection->gameList, server->publicip, server->publicport); + } +} + +void piSBThink +( + PEER peer +) +{ + PEER_CONNECTION; + + // check that we're initialized. + if(!connection->sbInitialized) + return; + + SBListThink(&connection->gameList); + SBListThink(&connection->groupList); + SBQueryEngineThink(&connection->gameEngine); + SBListThink(&connection->autoMatchList); + SBQueryEngineThink(&connection->autoMatchEngine); +} + +void piSendNatNegotiateCookie +( + PEER peer, + unsigned int ip, + unsigned short port, + int cookie +) +{ + PEER_CONNECTION; + + // check that we're initialized. + if(!connection->sbInitialized) + return; + + // send the cookie + if (peerIsAutoMatching(peer)) + { + SBSendNatNegotiateCookieToServer(&connection->autoMatchList, ip, htons(port), cookie); + } + else + SBSendNatNegotiateCookieToServer(&connection->gameList, ip, htons(port), cookie); +} + +void piSendMessageToServer +( + PEER peer, + unsigned int ip, + unsigned short port, + const char * data, + int len +) +{ + PEER_CONNECTION; + + // check that we're initialized. + if(!connection->sbInitialized) + return; + + // send the message + if(peerIsAutoMatching(peer)) + { + SBSendMessageToServer(&connection->autoMatchList, ip, port, data, len); + } + else + { + SBSendMessageToServer(&connection->gameList, ip, port, data, len); + } +} + +static void piSBCloneServerTableMap(void * elem, void * clientData) +{ + SBKeyValuePair * kvPair = (SBKeyValuePair *)elem; + SBServer clone = (SBServer)clientData; + + SBServerAddKeyValue(clone, kvPair->key, kvPair->value); +} + +SBServer piSBCloneServer(SBServer server) +{ + SBServer clone; + HashTable table; + + // allocate the clone + clone = SBAllocServer(NULL, server->publicip, server->publicport); + + // save off the table + table = clone->keyvals; + + // copy the source server + memcpy(clone, server, sizeof(struct _SBServer)); + clone->keyvals = table; + clone->next = NULL; + + // copy all the values from the original table + TableMap(server->keyvals, piSBCloneServerTableMap, clone); + + return clone; +} + +void piSBFreeHostServer(PEER peer) +{ + PEER_CONNECTION; + + if(!connection->hostServer) + return; + + SBServerFree(&connection->hostServer); + connection->hostServer = NULL; +} diff --git a/code/gamespy/Peer/peerSB.h b/code/gamespy/Peer/peerSB.h new file mode 100644 index 00000000..ed97057c --- /dev/null +++ b/code/gamespy/Peer/peerSB.h @@ -0,0 +1,46 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERSB_H_ +#define _PEERSB_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/************** +** FUNCTIONS ** +**************/ +PEERBool piSBInit(PEER peer); +void piSBCleanup(PEER peer); +PEERBool piSBStartListingGames(PEER peer, const unsigned char * fields, int numFields, const char * filter); +void piSBStopListingGames(PEER peer); +void piSBUpdateGame(PEER peer, SBServer server, PEERBool fullUpdate, PEERBool forceUpdateByMaster, PEERBool icmpEcho); +PEERBool piSBStartListingGroups(PEER peer, const char * fields); +void piSBStopListingGroups(PEER peer); +void piSBThink(PEER peer); +void piSendNatNegotiateCookie(PEER peer, unsigned int ip, unsigned short port, int cookie); +void piSendMessageToServer(PEER peer, unsigned int ip, unsigned short port, const char * data, int len); +PEERBool piSBStartListingAutoMatches(PEER peer); +void piSBStopListingAutoMatches(PEER peer); +SBServer piSBCloneServer(SBServer server); +void piSBFreeHostServer(PEER peer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerc/peerc.c b/code/gamespy/Peer/peerc/peerc.c new file mode 100644 index 00000000..46ca28a2 --- /dev/null +++ b/code/gamespy/Peer/peerc/peerc.c @@ -0,0 +1,773 @@ +// GameSpy Peer SDK C Test App +// Dan "Mr. Pants" Schoenblum +// dan@gamespy.com + +/********* +INCLUDES +*********/ +#include "../peer.h" +#include "../../common/gsStringUtil.h" +#include "../../common/gsAvailable.h" + +/******** +DEFINES +********/ +#define TITLE _T("gmtest") + +// ensure cross-platform compatibility for printf +#ifdef UNDER_CE + void RetailOutputA(CHAR *tszErr, ...); + #define printf RetailOutputA +#elif defined(_NITRO) + #include "../../common/nitro/screen.h" + #define printf Printf + #define vprintf VPrintf +#endif + +#define LOOP_SECONDS 60 + +#define NICK_SIZE 32 // size of nick array, below + +/************* +GLOBAL VARS +*************/ +gsi_char nick[NICK_SIZE]; // our nickname for peerConnect + +// RoomType to string +gsi_char RtoS[3][16] = +{ + _T("TitleRoom"), + _T("GroupRoom"), + _T("StagingRoom") +}; + +PEERBool groupRoomCallbackDone = PEERFalse; + + +static const gsi_char * ResultToString (PEERJoinResult result) +{ + switch(result) + { + case PEERJoinSuccess: + return _T("Success"); + case PEERFullRoom: + return _T("Full"); + case PEERInviteOnlyRoom: + return _T("Invite Only"); + case PEERBannedFromRoom: + return _T("Banned"); + case PEERBadPassword: + return _T("Bad Password"); + case PEERAlreadyInRoom: + return _T("Already in room"); + case PEERNoTitleSet: + return _T("No Title"); + case PEERNoConnection: + return _T("No Connection"); + case PEERAutoMatching: + return _T("Auto Matching"); + case PEERJoinFailed: + return _T("Join Failed"); + } + + //ASSERT(0); + return _T(""); +} + +static void JoinRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + void * param + ) +{ + if(!success) + { + _tprintf(_T("Join failure: %s"), ResultToString(result)); + return; + } + else + { + _tprintf(_T("Joined %s.\n"), RtoS[roomType]); + /*dlg->FillPlayerList(roomType); + if(roomType == StagingRoom) + { + dlg->m_name = peerGetRoomName(peer, StagingRoom); + dlg->UpdateData(FALSE); + }*/ + } + + GSI_UNUSED(param); + GSI_UNUSED(roomType); + GSI_UNUSED(peer); +} + +static void CreateStagingRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + void * param + ) +{ + if (success) + _tprintf(_T("Staging Room Created\n")); + + GSI_UNUSED(param); + GSI_UNUSED(roomType); + GSI_UNUSED(result); + GSI_UNUSED(peer); +} + +static void DisconnectedCallback +( + PEER peer, + const gsi_char * reason, + void * param +) +{ + _tprintf(_T("Disconnected: %s\n"), reason); + + GSI_UNUSED(peer); + GSI_UNUSED(param); +} + +static void RoomMessageCallback +( + PEER peer, + RoomType roomType, + const gsi_char * nick, + const gsi_char * message, + MessageType messageType, + void * param +) +{ + _tprintf(_T(" (%s) %s: %s\n"), RtoS[roomType], nick, message); + //if(strcasecmp(message, _T("quit")) == 0) + // stop = PEERTrue; + //else if((strlen(message) > 5) && (strncasecmp(message, _T("echo"), 4) == 0)) + // peerMessageRoom(peer, roomType, message + 5, messageType); + + GSI_UNUSED(param); + GSI_UNUSED(peer); + GSI_UNUSED(messageType); +} + +static void RoomUTMCallback +( + PEER peer, + RoomType roomType, + const gsi_char * nick, + const gsi_char * command, + const gsi_char * parameters, + PEERBool authenticated, + void * param +) +{ + GSI_UNUSED(peer); + GSI_UNUSED(roomType); + GSI_UNUSED(nick); + GSI_UNUSED(command); + GSI_UNUSED(parameters); + GSI_UNUSED(authenticated); + GSI_UNUSED(param); +} + +static void PlayerMessageCallback +( + PEER peer, + const gsi_char * nick, + const gsi_char * message, + MessageType messageType, + void * param +) +{ + _tprintf(_T("(PRIVATE) %s: %s\n"), nick, message); + + GSI_UNUSED(peer); + GSI_UNUSED(messageType); + GSI_UNUSED(param); +} + +static void ReadyChangedCallback +( + PEER peer, + const gsi_char * nick, + PEERBool ready, + void * param +) +{ + if(ready) + _tprintf(_T("%s is ready\n"), nick); + else + _tprintf(_T("%s is not ready\n"), nick); + + GSI_UNUSED(peer); + GSI_UNUSED(param); +} + +static void GameStartedCallback +( + PEER peer, + SBServer server, + const gsi_char * message, + void * param +) +{ + _tprintf(_T("The game is starting at %s\n"), SBServerGetPublicAddress(server)); + if(message && message[0]) + _tprintf(_T(": %s\n"), message); + else + _tprintf(_T("\n")); + + GSI_UNUSED(peer); + GSI_UNUSED(param); +} + +static void PlayerJoinedCallback +( + PEER peer, + RoomType roomType, + const gsi_char * nick, + void * param +) +{ + _tprintf(_T("%s joined the %s\n"), nick, RtoS[roomType]); + + GSI_UNUSED(peer); + GSI_UNUSED(param); +} + +static void PlayerLeftCallback +( + PEER peer, + RoomType roomType, + const gsi_char * nick, + const gsi_char * reason, + void * param +) +{ + _tprintf(_T("%s left the %s\n"), nick, RtoS[roomType]); + + GSI_UNUSED(peer); + GSI_UNUSED(param); + GSI_UNUSED(reason); +} + +static void PlayerChangedNickCallback +( + PEER peer, + RoomType roomType, + const gsi_char * oldNick, + const gsi_char * newNick, + void * param +) +{ + _tprintf(_T("%s in %s changed nicks to %s\n"), oldNick, RtoS[roomType], newNick); + + GSI_UNUSED(peer); + GSI_UNUSED(param); +} + +PEERBool connectSuccess; +static void ConnectCallback +( + PEER peer, + PEERBool success, + int failureReason, + void * param +) +{ + connectSuccess = success; + + GSI_UNUSED(peer); + GSI_UNUSED(success); + GSI_UNUSED(failureReason); + GSI_UNUSED(param); +} + +static void EnumPlayersCallback +( + PEER peer, + PEERBool success, + RoomType roomType, + int index, + const gsi_char * nick, + int flags, + void * param +) +{ + if(!success) + { + _tprintf(_T("Enum %s players failed\n"), RtoS[roomType]); + return; + } + + if(index == -1) + { + _tprintf(_T("--------------------\n")); + return; + } + + _tprintf(_T("%d: %s\n"), index, nick); + /*if(flags & PEER_FLAG_OP) + _tprintf(_T(" (host)\n")); + else + _tprintf(_T("\n"));*/ + + + GSI_UNUSED(peer); + GSI_UNUSED(param); + GSI_UNUSED(flags); +} +/* +int gStartListing; +static void ListingGamesCallback +( + PEER peer, + PEERBool success, + const gsi_char * name, + SBServer server, + PEERBool staging, + int msg, + int progress, + void * param +) +{ + if(success) + { + gsi_char *msgname; + switch (msg) + { + case PEER_CLEAR: + msgname = _T("PEER_CLEAR"); + break; + case PEER_ADD: + msgname = _T("PEER_ADD"); + if (SBServerHasBasicKeys(server)) + _tprintf(_T(" firewall server name: %s numplayers: %d ping: %d firewall: %s\n"), SBServerGetStringValue(server,_T("hostname"),_T("UNKNOWN")), SBServerGetIntValue(server,_T("numplayers"),0), SBServerGetPing(server), SBServerDirectConnect(server)==SBTrue?_T("NO"):_T("YES")); + if (SBServerHasFullKeys(server)) + _tprintf(_T(" server gametype: %s \n"), SBServerGetStringValue(server, _T("gametype"), _T("unknown"))); + break; + case PEER_UPDATE: + msgname = _T("PEER_UPDATE"); + if(server) + { + _tprintf(_T(" update server name: %s numplayers: %d ping: %d firewall: %s\n"), SBServerGetStringValue(server,_T("hostname"),_T("UNKNOWN")), SBServerGetIntValue(server,_T("numplayers"),0), SBServerGetPing(server), SBServerDirectConnect(server)==SBTrue?_T("NO"):_T("YES")); + _tprintf(_T(" server gametype: %s \n"), SBServerGetStringValue(server, _T("gametype"), _T("unknown"))); + } + break; + case PEER_REMOVE: + msgname = _T("PEER_REMOVE"); + break; + case PEER_COMPLETE: + msgname = _T("PEER_COMPLETE"); + gStartListing = 1; + break; + default: + msgname = _T("ERROR!"); + } + //_tprintf("game: %s\n", msgname); + //if(server) + // _tprintf(" server name: %s numplayers: %d ping: %d\n", SBServerGetStringValue(server,_T("hostname"),_T("UNKNOWN")), SBServerGetIntValue(server,_T("numplayers"),0), SBServerGetPing(server)); + } + + GSI_UNUSED(peer); + GSI_UNUSED(name); + GSI_UNUSED(staging); + GSI_UNUSED(progress); + GSI_UNUSED(param); +} +*/ +static void ListGroupRoomsCallback +( + PEER peer, + PEERBool success, + int groupID, + SBServer server, + const gsi_char * name, + int numWaiting, + int maxWaiting, + int numGames, + int numPlaying, + void * param +) +{ + if(success && (groupID > 0)) + { + _tprintf(_T(" %s\n"), name); + _tprintf(_T(" Players in room: %d\n"), numWaiting); + _tprintf(_T(" Max players in room: %d\n"), maxWaiting); + _tprintf(_T(" Games: %d\n"), numGames); + _tprintf(_T(" Players in games: %d\n"), numPlaying); + } + else + groupRoomCallbackDone = PEERTrue; // if groupID is set to 0 it means all group rooms have been listed + + GSI_UNUSED(peer); + GSI_UNUSED(server); + GSI_UNUSED(numWaiting); + GSI_UNUSED(maxWaiting); + GSI_UNUSED(numGames); + GSI_UNUSED(numPlaying); + GSI_UNUSED(param); +} + +PEERBool joinSuccess; +static void JoinCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + void * param +) +{ + joinSuccess = success; + + GSI_UNUSED(peer); + GSI_UNUSED(result); + GSI_UNUSED(roomType); + GSI_UNUSED(param); +} + +static void NickErrorCallback +( + PEER peer, + int type, + const gsi_char * badNick, + int numSuggestedNicks, + const gsi_char ** suggestedNicks, + void * param +) +{ + static int errCount; + + if(errCount < 20) + { + _tsnprintf(nick,NICK_SIZE,_T("peerc%u"),(unsigned int)current_time()); + nick[NICK_SIZE - 1] = '\0'; + peerRetryWithNick(peer, nick); + } + else + { + //peerDisconnect(peer); + peerRetryWithNick(peer, NULL); + } + errCount++; + + GSI_UNUSED(type); + GSI_UNUSED(badNick); + GSI_UNUSED(suggestedNicks); + GSI_UNUSED(numSuggestedNicks); + GSI_UNUSED(param); +} +/* +static void AutoMatchStatusCallback(PEER thePeer, PEERAutoMatchStatus theStatus, void* theParam) +{ + _tprintf(_T("Automatch status: %d\r\n"), theStatus); + GSI_UNUSED(thePeer); + GSI_UNUSED(theStatus); + GSI_UNUSED(theParam); +} + +static int AutoMatchRateCallback(PEER thePeer, SBServer theServer, void* theParam) +{ + GSI_UNUSED(thePeer); + GSI_UNUSED(theServer); + GSI_UNUSED(theParam); + return 0; +} +*/ +static void RoomKeyChangedCallback(PEER thePeer, RoomType theType, const gsi_char* theNick, const gsi_char* theKey, const gsi_char* theValue, void* theParam) +{ + GSI_UNUSED(thePeer); + GSI_UNUSED(theType); + GSI_UNUSED(theNick); + GSI_UNUSED(theKey); + GSI_UNUSED(theValue); + GSI_UNUSED(theParam); +} + +static void QRServerKeyCallback +( + PEER peer, + int key, + qr2_buffer_t buffer, + void * param +) +{ +#if VERBOSE + gsi_char verbose[256]; + _stprintf(verbose, _T("QR_SERVER_KEY | %d\n"), key); + OutputDebugString(verbose); +#endif + + switch(key) + { + case HOSTNAME_KEY: + _tprintf(_T(" Server Key callback is being called for HOSTNAME_KEY\n")); + qr2_buffer_add(buffer, _T("My Game")); + break; + case NUMPLAYERS_KEY: + _tprintf(_T(" Server Key callback is being called for NUMPLAYERS_KEY\n")); + qr2_buffer_add_int(buffer, 1); + break; + case GAMEMODE_KEY: + _tprintf(_T(" Server Key callback is being called for GAMEMODE_KEY\n")); + qr2_buffer_add(buffer, _T("openplaying")); + break; + case HOSTPORT_KEY: + _tprintf(_T(" Server Key callback is being called for HOSTPORT_KEY\n")); + qr2_buffer_add_int(buffer, 15151); + break; + case MAPNAME_KEY: + _tprintf(_T(" Server Key callback is being called for MAPNAME_KEY\n")); + qr2_buffer_add(buffer, _T("Big Crate Room")); + break; + case GAMETYPE_KEY: + _tprintf(_T(" Server Key callback is being called for GAMETYPE_KEY\n")); + qr2_buffer_add(buffer, _T("Friendly")); + break; + case TIMELIMIT_KEY: + _tprintf(_T(" Server Key callback is being called for TIMELIMIT_KEY\n")); + qr2_buffer_add_int(buffer, 100); + break; + case FRAGLIMIT_KEY: + _tprintf(_T(" Server Key callback is being called for FRAGLIMIT_KEY\n")); + qr2_buffer_add_int(buffer, 0); + break; + case TEAMPLAY_KEY: + _tprintf(_T(" Server Key callback is being called for TEAMPLAY_KEY\n")); + qr2_buffer_add_int(buffer, 0); + break; + default: + qr2_buffer_add(buffer, _T("")); + break; + } + + GSI_UNUSED(peer); + GSI_UNUSED(param); +} + +static void QRKeyListCallback +( + PEER peer, + qr2_key_type type, + qr2_keybuffer_t keyBuffer, + void * param +) +{ + // register the keys we use + switch(type) + { + case key_server: + _tprintf(_T(" Key List Callback is being called for server keys\n")); + if(!peerIsAutoMatching(peer)) + { + qr2_keybuffer_add(keyBuffer, HOSTPORT_KEY); + qr2_keybuffer_add(keyBuffer, MAPNAME_KEY); + qr2_keybuffer_add(keyBuffer, GAMETYPE_KEY); + qr2_keybuffer_add(keyBuffer, TIMELIMIT_KEY); + qr2_keybuffer_add(keyBuffer, FRAGLIMIT_KEY); + qr2_keybuffer_add(keyBuffer, TEAMPLAY_KEY); + } + break; + case key_player: + _tprintf(_T(" Key List Callback is being called for player keys\n")); + // no custom player keys + break; + case key_team: + _tprintf(_T(" Key List Callback is being called for team keys\n")); + // no custom team keys + break; + default: break; + } + + GSI_UNUSED(param); +} + + + + +#ifdef __MWERKS__ // CodeWarrior will warn if not prototyped + int test_main(int argc, char **argv); +#endif + +int test_main(int argc, char **argv) +{ + PEER peer; // peer object (initialized with peerInitialize + + /* peerInitialize */ + PEERCallbacks callbacks; // we will need to add all of the supported callbacks + + /* peerSetTitle parameters */ + gsi_char secret_key[9]; // your title's assigned secret key + int maxUpdates = 10; // max server queries the SDK will send out at a time + PEERBool natNeg = PEERFalse; // nat negotiation will not be supported + PEERBool pingRooms[NumRooms]; + PEERBool crossPingRooms[NumRooms]; + + /* peerConnect parameters */ + int profileID = 0; // we will not be using gp accounts, so this will be ignored + void * userData = NULL; // optional data passed to peerConnectCallback + PEERBool blocking = PEERTrue; // true means function runs synchronously + PEERBool non_blocking = PEERFalse; // false means function runs asynchronously + + int newbiesGroupID = 2; + + gsi_char * serverName = _T("UberNoobServer"); + int maxPlayers = 2; + gsi_char * noPassword = _T(""); + + GSIACResult result; // used for backend availability check + gsi_time startTime; + + // set our nickname to a random string so that we'll have different nicks when running multiple instances of peerc + _tsnprintf(nick,NICK_SIZE,_T("peerc%u"),(unsigned int)current_time()); + nick[NICK_SIZE - 1] = '\0'; + + // set the secret key, in a semi-obfuscated manner + secret_key[0] = 'H'; + secret_key[1] = 'A'; + secret_key[2] = '6'; + secret_key[3] = 'z'; + secret_key[4] = 'k'; + secret_key[5] = 'S'; + secret_key[6] = '\0'; + + // check that the game's backend is available + GSIStartAvailableCheck(_T("gmtest")); + while((result = GSIAvailableCheckThink()) == GSIACWaiting) + msleep(5); + if(result != GSIACAvailable) + { + _tprintf(_T("The backend is not available\n")); + return 1; + } + + // set the callbacks + memset(&callbacks, 0, sizeof(PEERCallbacks)); + callbacks.disconnected = DisconnectedCallback; + //callbacks.qrNatNegotiateCallback + callbacks.gameStarted = GameStartedCallback; + callbacks.playerChangedNick = PlayerChangedNickCallback; + callbacks.playerJoined = PlayerJoinedCallback; + callbacks.playerLeft = PlayerLeftCallback; + callbacks.readyChanged = ReadyChangedCallback; + callbacks.roomMessage = RoomMessageCallback; + callbacks.playerMessage = PlayerMessageCallback; + callbacks.roomUTM = RoomUTMCallback; + callbacks.roomKeyChanged = RoomKeyChangedCallback; + callbacks.qrKeyList = QRKeyListCallback; + callbacks.qrServerKey = QRServerKeyCallback; + callbacks.param = NULL; + + // initialize peer object, specifying the supported callbacks + peer = peerInitialize(&callbacks); + if(!peer) + { + _tprintf(_T("Failed to init\n")); + return 1; + } + + // ping/cross-ping in every room + pingRooms[TitleRoom] = PEERTrue; + pingRooms[GroupRoom] = PEERTrue; + pingRooms[StagingRoom] = PEERTrue; + crossPingRooms[TitleRoom] = PEERTrue; + crossPingRooms[GroupRoom] = PEERTrue; + crossPingRooms[StagingRoom] = PEERTrue; + + // set the title + if(!peerSetTitle(peer, TITLE, secret_key, TITLE, secret_key, 0, maxUpdates, natNeg, pingRooms, crossPingRooms)) + { + peerShutdown(peer); + _tprintf(_T("Failed to set the title\n")); + return 1; + } + + // connect to the chat server + _tprintf(_T("Connecting as %s..."), nick); + peerConnect(peer, nick, profileID, NickErrorCallback, ConnectCallback, userData, blocking); + if(!connectSuccess) + { + peerShutdown(peer); + _tprintf(_T("Failed to connect\n")); + return 1; + } + printf("Connected\n\n"); + + // join the title room + printf("Joining title room..."); + peerJoinTitleRoom(peer, NULL, JoinCallback, NULL, PEERTrue); + if(!joinSuccess) + { + peerDisconnect(peer); + peerShutdown(peer); + _tprintf(_T("Failed to join the title room\n")); + return 1; + } + printf("Joined\n\n"); + + // list the group rooms + printf("Listing group rooms:\n"); + peerListGroupRooms(peer, _T(""), ListGroupRoomsCallback, userData, non_blocking); + + while (!groupRoomCallbackDone) + { + peerThink(peer); + msleep(10); + } + + // send a chat message to the room + printf("\nSending message to the Title Room...\n"); + peerMessageRoom(peer, TitleRoom, _T("Hi everyone in the Title Room!\n"), NormalMessage); + + // Loop for a while + startTime = current_time(); + while((current_time() - startTime) < (1000)) + { + peerThink(peer); + msleep(10); + } + + _tprintf(_T("\nJoining Group Room 'Newbies'...")); + peerJoinGroupRoom(peer, newbiesGroupID, JoinRoomCallback, userData, blocking); + + _tprintf(_T("\nPlayers in Group Room: \n")); + peerEnumPlayers(peer, GroupRoom, EnumPlayersCallback, NULL); + + _tprintf(_T("Hosting Staging Room...\n")); + peerCreateStagingRoom(peer, serverName, maxPlayers, noPassword, CreateStagingRoomCallback, userData, blocking); + + // Loop for a while + startTime = current_time(); + while((current_time() - startTime) < (1000)) + { + peerThink(peer); + msleep(10); + } + + //peerStartAutoMatch(peer, 3, _T(""), AutoMatchStatusCallback, AutoMatchRateCallback, NULL, PEERFalse); + + //peerStopListingGames(peer); + + // Leave the title room + peerLeaveRoom(peer, TitleRoom, NULL); + + // Stop auto match if it's in progress + //peerStopAutoMatch(peer); + + // disconnect local client from chat server (peerShutdown must still be called) + peerDisconnect(peer); + + peerShutdown(peer); // clean up (free internal sdk memory) + + GSI_UNUSED(argc); + GSI_UNUSED(argv); + return 0; +} diff --git a/code/gamespy/Peer/peerc/peernitrocw/Nitro.lcf b/code/gamespy/Peer/peerc/peernitrocw/Nitro.lcf new file mode 100644 index 00000000..998f6b00 --- /dev/null +++ b/code/gamespy/Peer/peerc/peernitrocw/Nitro.lcf @@ -0,0 +1,493 @@ +#--------------------------------------------------------------------------- +# Project: NitroSDK - tools - makelcf +# File: ARM9-TS.lcf.template +# +# Copyright 2003-2006 Nintendo. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# proprietary information of Nintendo of America Inc. and/or Nintendo +# Company Ltd., and are protected by Federal copyright law. They may +# not be disclosed to third parties or copied or duplicated in any form, +# in whole or in part, without the prior written consent of Nintendo. +# +# $Log: ARM9-TS.lcf.template,v $ +# Revision 1.34 04/06/2006 09:02:36 kitase_hirotake +# support for .itcm.bss and .dtcm.bss +# +# Revision 1.33 03/30/2006 23:59:22 AM yasu +# changed creation year +# +# Revision 1.32 03/29/2006 13:14:22 AM yasu +# support for overlays in CWVER 2.x +# +# Revision 1.31 11/24/2005 01:16:47 yada +# change start address of mainEX arena from 0x2400000 to 0x23e0000 +# +# Revision 1.30 09/02/2005 04:14:22 AM yasu +# Old symbols were redefined so they can be used even under SDK2.2 +# +# Revision 1.29 08/31/2005 09:34:57 AM yasu +# Corrected a problem where code would not function normally when using section names such as section_BSS +# +# Revision 1.28 08/26/2005 11:22:16 AM yasu +# overlay support for ITCM/DTCM +# +# Revision 1.27 06/20/2005 12:29:20 AM yasu +# Changed Surffix to Suffix +# +# Revision 1.26 06/14/2005 09:03:42 yada +# fix around minus value of SDK_STACKSIZE +# +# Revision 1.25 04/13/2005 12:51:00 terui +# Change SDK_AUTOLOAD.DTCM.START 0x027c0000 -> 0x027e0000 +# +# Revision 1.24 03/30/2005 00:02:14 yosizaki +# fix copyright header. +# +# Revision 1.23 03/25/2005 12:54:59 AM yasu +# Include .version section +# +# Revision 1.22 10/03/2004 02:00:56 AM yasu +# Output component file list for compstatic tool +# +# Revision 1.21 09/27/2004 05:28:21 AM yasu +# Support .sinit +# +# Revision 1.20 09/09/2004 11:49:20 AM yasu +# Support compstatic in default +# +# Revision 1.19 09/06/2004 06:40:00 AM yasu +# Add labels for digest +# +# Revision 1.18 08/20/2004 06:19:59 AM yasu +# DTCM moves to 0x027c0000 at default +# +# Revision 1.17 08/02/2004 10:38:53 AM yasu +# Add autoload-done callback address in overlaydefs +# +# Revision 1.16 07/26/2004 02:22:32 AM yasu +# Change DTCM address to 0x023c0000 +# +# Revision 1.15 07/26/2004 00:08:27 AM yasu +# Fix label of exception table +# +# Revision 1.14 07/24/2004 05:42:25 AM yasu +# Set default values for SDK_AUTOGEN_xTCM_START +# +# Revision 1.13 07/23/2004 11:32:14 AM yasu +# Define labels for __exception_table_start__ and _end__ +# +# Revision 1.12 07/12/2004 12:21:08 AM yasu +# Check size of ITCM/DTCM +# +# Revision 1.11 07/10/2004 04:10:26 AM yasu +# Support command 'Library' +# +# Revision 1.10 07/02/2004 08:13:02 AM yasu +# Support OBJECT( ) +# +# Revision 1.9 07/01/2004 12:54:38 yasu +# support ITCM/DTCM/WRAM autoload +# +# Revision 1.8 07/01/2004 10:41:46 yasu +# support autoload +# +# Revision 1.7 06/02/2004 07:35:37 yasu +# Set libsyscall.a in FORCE_ACTIVE +# Put NitroMain at the top of ROM image +# +# Revision 1.6 06/02/2004 04:56:28 yasu +# Change to fit to new ROM map of TS +# +# Revision 1.5 2004/06/01 06:12:00 miya +# add padding at top of ROM image. +# +# Revision 1.4 04/26/2004 12:16:48 yasu +# add KEEP_SECTIONS +# +# Revision 1.3 04/20/2004 07:41:32 yasu +# Set STATICINIT instead of .ctor temporarily +# +# Revision 1.2 04/14/2004 07:16:42 yasu +# add ALIGN(32) for convenience to handle cache line +# +# Revision 1.1 04/06/2004 01:59:54 yasu +# newly added +# +# $NoKeywords: $ +#--------------------------------------------------------------------------- +MEMORY +{ + main (RWX) : ORIGIN = 0x02000000, LENGTH = 0x0 > main.sbin + ITCM (RWX) : ORIGIN = 0x01ff8000, LENGTH = 0x0 >> main.sbin + DTCM (RWX) : ORIGIN = 0x027e0000, LENGTH = 0x0 >> main.sbin + binary.AUTOLOAD_INFO (RWX) : ORIGIN = 0, LENGTH = 0x0 >> main.sbin + binary.STATIC_FOOTER (RWX) : ORIGIN = 0, LENGTH = 0x0 >> main.sbin + + main_defs (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 > main_defs.sbin + main_table (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 > main_table.sbin + dummy.MAIN_EX (RW) : ORIGIN = 0x023e0000, LENGTH = 0x0 + arena.MAIN (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 + arena.MAIN_EX (RW) : ORIGIN = AFTER(dummy.MAIN_EX), LENGTH = 0x0 + arena.ITCM (RW) : ORIGIN = AFTER(ITCM), LENGTH = 0x0 + arena.DTCM (RW) : ORIGIN = AFTER(DTCM), LENGTH = 0x0 + binary.MODULE_FILES (RW) : ORIGIN = 0x0, LENGTH = 0x0 > component.files + check.ITCM (RWX) : ORIGIN = 0x0, LENGTH = 0x08000 > itcm.check + check.DTCM (RW) : ORIGIN = 0x0, LENGTH = 0x04000 > dtcm.check +} + +FORCE_ACTIVE +{ + SVC_SoftReset +} + +KEEP_SECTION +{ + .sinit +} + +SECTIONS +{ + ############################ STATIC ################################# + .main: + { + ALIGNALL(4); . = ALIGN(32); # Fit to cache line + + # + # TEXT BLOCK: READ ONLY + # + SDK_STATIC_START =.; + SDK_STATIC_TEXT_START =.; + #:::::::::: text/rodata + libsyscall.a (.text) + crt0.o (.text) + crt0.o (.rodata) + * (.version) + OBJECT(NitroMain,*) + GROUP(ROOT) (.text) + . = ALIGN(4); + * (.exception) + . = ALIGN(4); + SDK_STATIC_ETABLE_START =.; + EXCEPTION + SDK_STATIC_ETABLE_END =.; + . = ALIGN(4); + GROUP(ROOT) (.init) + . = ALIGN(4); + GROUP(ROOT) (.rodata) + . = ALIGN(4); + + SDK_STATIC_SINIT_START =.; + #:::::::::: ctor + GROUP(ROOT) (.ctor) + GROUP(ROOT) (.sinit) + WRITEW 0; + #:::::::::: ctor + SDK_STATIC_SINIT_END =.; + + #:::::::::: text/rodata + . = ALIGN(32); + SDK_STATIC_TEXT_END =.; + + # + # DATA BLOCK: READ WRITE + # + SDK_STATIC_DATA_START =.; + #:::::::::: data + GROUP(ROOT) (.sdata) + . = ALIGN(4); + GROUP(ROOT) (.data) + . = ALIGN(4); + SDK_OVERLAY_DIGEST =.; + # NO DIGEST + SDK_OVERLAY_DIGEST_END =.; + #:::::::::: data + . = ALIGN(32); + SDK_STATIC_DATA_END =.; + SDK_STATIC_END =.; + + SDK_STATIC_TEXT_SIZE = SDK_STATIC_TEXT_END - SDK_STATIC_TEXT_START; + SDK_STATIC_DATA_SIZE = SDK_STATIC_DATA_END - SDK_STATIC_DATA_START; + SDK_STATIC_SIZE = SDK_STATIC_END - SDK_STATIC_START; + __sinit__ = SDK_STATIC_SINIT_START; # for static initializer + __exception_table_start__ = SDK_STATIC_ETABLE_START; # for exception table + __exception_table_end__ = SDK_STATIC_ETABLE_END; # for exception table + } > main + + .main.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_STATIC_BSS_START =.; + #:::::::::: bss + GROUP(ROOT) (.sbss) + . = ALIGN(4); + GROUP(ROOT) (.bss) + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_STATIC_BSS_END = .; + SDK_STATIC_BSS_SIZE = SDK_STATIC_BSS_END - SDK_STATIC_BSS_START; + + } >> main + + + ############################ AUTOLOADS ############################## + SDK_AUTOLOAD.ITCM.START = 0x01ff8000; + SDK_AUTOLOAD.ITCM.END = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD.ITCM.BSS_END = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD.ITCM.SIZE = 0; + SDK_AUTOLOAD.ITCM.BSS_SIZE = 0; + SDK_AUTOLOAD.DTCM.START = 0x027e0000; + SDK_AUTOLOAD.DTCM.END = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD.DTCM.BSS_END = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD.DTCM.SIZE = 0; + SDK_AUTOLOAD.DTCM.BSS_SIZE = 0; + SDK_AUTOLOAD_START = SDK_STATIC_END; + SDK_AUTOLOAD_SIZE = 0; + SDK_AUTOLOAD_NUMBER = 2; + + .ITCM: + { + ALIGNALL(4); . = ALIGN(32); + + # + # TEXT BLOCK: READ ONLY + # + SDK_AUTOLOAD_ITCM_ID =0; + SDK_AUTOLOAD.ITCM.ID =0; + SDK_AUTOLOAD.ITCM.START =.; + SDK_AUTOLOAD.ITCM.TEXT_START =.; + #:::::::::: text/rodata + . = ALIGN(4); + * (.itcm) + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: text/rodata + SDK_AUTOLOAD.ITCM.TEXT_END =.; + + # + # DATA BLOCK: READ WRITE BLOCK + # + SDK_AUTOLOAD.ITCM.DATA_START =.; + #:::::::::: data + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: data + . = ALIGN(32); + SDK_AUTOLOAD.ITCM.DATA_END =.; + SDK_AUTOLOAD.ITCM.END =.; + + SDK_AUTOLOAD.ITCM.TEXT_SIZE = SDK_AUTOLOAD.ITCM.TEXT_END - SDK_AUTOLOAD.ITCM.TEXT_START; + SDK_AUTOLOAD.ITCM.DATA_SIZE = SDK_AUTOLOAD.ITCM.DATA_END - SDK_AUTOLOAD.ITCM.DATA_START; + SDK_AUTOLOAD.ITCM.SIZE = SDK_AUTOLOAD.ITCM.END - SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SDK_AUTOLOAD.ITCM.SIZE; + + } > ITCM + + .ITCM.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_AUTOLOAD.ITCM.BSS_START = .; + #:::::::::: bss + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + * (.itcm.bss) + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_AUTOLOAD.ITCM.BSS_END = .; + + SDK_AUTOLOAD.ITCM.BSS_SIZE = SDK_AUTOLOAD.ITCM.BSS_END - SDK_AUTOLOAD.ITCM.BSS_START; + + } >> ITCM + + .DTCM: + { + ALIGNALL(4); . = ALIGN(32); + + # + # TEXT BLOCK: READ ONLY + # + SDK_AUTOLOAD_DTCM_ID =1; + SDK_AUTOLOAD.DTCM.ID =1; + SDK_AUTOLOAD.DTCM.START =.; + SDK_AUTOLOAD.DTCM.TEXT_START =.; + #:::::::::: text/rodata + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: text/rodata + SDK_AUTOLOAD.DTCM.TEXT_END =.; + + # + # DATA BLOCK: READ WRITE BLOCK + # + SDK_AUTOLOAD.DTCM.DATA_START =.; + #:::::::::: data + . = ALIGN(4); + . = ALIGN(4); + * (.dtcm) + . = ALIGN(4); + #:::::::::: data + . = ALIGN(32); + SDK_AUTOLOAD.DTCM.DATA_END =.; + SDK_AUTOLOAD.DTCM.END =.; + + SDK_AUTOLOAD.DTCM.TEXT_SIZE = SDK_AUTOLOAD.DTCM.TEXT_END - SDK_AUTOLOAD.DTCM.TEXT_START; + SDK_AUTOLOAD.DTCM.DATA_SIZE = SDK_AUTOLOAD.DTCM.DATA_END - SDK_AUTOLOAD.DTCM.DATA_START; + SDK_AUTOLOAD.DTCM.SIZE = SDK_AUTOLOAD.DTCM.END - SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SDK_AUTOLOAD.DTCM.SIZE; + + } > DTCM + + .DTCM.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_AUTOLOAD.DTCM.BSS_START = .; + #:::::::::: bss + . = ALIGN(4); + . = ALIGN(4); + * (.dtcm.bss) + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_AUTOLOAD.DTCM.BSS_END = .; + + SDK_AUTOLOAD.DTCM.BSS_SIZE = SDK_AUTOLOAD.DTCM.BSS_END - SDK_AUTOLOAD.DTCM.BSS_START; + + } >> DTCM + + + SDK_AUTOLOAD_ITCM_START = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD_ITCM_END = SDK_AUTOLOAD.ITCM.END; + SDK_AUTOLOAD_ITCM_BSS_END = SDK_AUTOLOAD.ITCM.BSS_END; + SDK_AUTOLOAD_ITCM_SIZE = SDK_AUTOLOAD.ITCM.SIZE; + SDK_AUTOLOAD_ITCM_BSS_SIZE = SDK_AUTOLOAD.ITCM.BSS_SIZE; + SDK_AUTOLOAD_DTCM_START = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD_DTCM_END = SDK_AUTOLOAD.DTCM.END; + SDK_AUTOLOAD_DTCM_BSS_END = SDK_AUTOLOAD.DTCM.BSS_END; + SDK_AUTOLOAD_DTCM_SIZE = SDK_AUTOLOAD.DTCM.SIZE; + SDK_AUTOLOAD_DTCM_BSS_SIZE = SDK_AUTOLOAD.DTCM.BSS_SIZE; + + ############################ AUTOLOAD_INFO ########################## + .binary.AUTOLOAD_INFO: + { + WRITEW ADDR(.ITCM); + WRITEW SDK_AUTOLOAD.ITCM.SIZE; + WRITEW SDK_AUTOLOAD.ITCM.BSS_SIZE; + WRITEW ADDR(.DTCM); + WRITEW SDK_AUTOLOAD.DTCM.SIZE; + WRITEW SDK_AUTOLOAD.DTCM.BSS_SIZE; + } > binary.AUTOLOAD_INFO + + SDK_AUTOLOAD_LIST = SDK_AUTOLOAD_START + SDK_AUTOLOAD_SIZE; + SDK_AUTOLOAD_LIST_END = SDK_AUTOLOAD_START + SDK_AUTOLOAD_SIZE + SIZEOF(.binary.AUTOLOAD_INFO); + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SIZEOF(.binary.AUTOLOAD_INFO); + + ############################ STATIC_FOOTER ########################## + .binary.STATIC_FOOTER: + { + WRITEW 0xdec00621; # LE(0x2106C0DE) = NITRO CODE + WRITEW _start_ModuleParams - ADDR(.main); + WRITEW 0; # NO DIGEST + } > binary.STATIC_FOOTER + + ############################ OVERLAYS ############################### + SDK_OVERLAY_NUMBER = 0; + + + ############################ MAIN EX ################################## + # MAIN EX Area + .dummy.MAIN_EX: + { + . = ALIGN(32); + } > dummy.MAIN_EX + + ############################ ARENA ################################## + .arena.MAIN: + { + . = ALIGN(32); + SDK_SECTION_ARENA_START =.; + } > arena.MAIN + + .arena.MAIN_EX: + { + . = ALIGN(32); + SDK_SECTION_ARENA_EX_START =.; + } > arena.MAIN_EX + + .arena.ITCM: + { + . = ALIGN(32); + SDK_SECTION_ARENA_ITCM_START =.; + } > arena.ITCM + + .arena.DTCM: + { + . = ALIGN(32); + SDK_SECTION_ARENA_DTCM_START =.; + } > arena.DTCM + + ############################ OVERLAYDEFS ############################ + .main_defs: + { + ### main module information + WRITEW ADDR(.main); # load address + WRITEW _start; # entry address + WRITEW SDK_STATIC_SIZE + SDK_AUTOLOAD_SIZE; # size of module + WRITEW _start_AutoloadDoneCallback; # callback autoload done + + ### overlay filename + + } > main_defs + + + ############################ OVERLAYTABLE ########################### + .main_table: + { + + } > main_table + + + ############################ OTHERS ################################# + SDK_MAIN_ARENA_LO = SDK_SECTION_ARENA_START; + SDK_IRQ_STACKSIZE = 4096; # allocated in DTCM + SDK_SYS_STACKSIZE = 0; # when 0 means all remains of DTCM + + # Module filelist + .binary.MODULE_FILES: + { + WRITES ("main.sbin"); + WRITES ("main_defs.sbin"); + WRITES ("main_table.sbin"); + } > binary.MODULE_FILES + + # ITCM/DTCM size checker => check AUTOLOAD_ITCM/DTCM + .check.ITCM: + { + . = . + SDK_AUTOLOAD_ITCM_SIZE + SDK_AUTOLOAD_ITCM_BSS_SIZE; + } > check.ITCM + + SDK_SYS_STACKSIZE_SIGN = (SDK_SYS_STACKSIZE < 0x80000000) * 2 - 1; + .check.DTCM: + { + . = . + SDK_AUTOLOAD_DTCM_SIZE + SDK_AUTOLOAD_DTCM_BSS_SIZE; + . = . + SDK_IRQ_STACKSIZE + SDK_SYS_STACKSIZE * SDK_SYS_STACKSIZE_SIGN; + } > check.DTCM + +} diff --git a/code/gamespy/Peer/peerc/peernitrocw/ROM-TS.rsf b/code/gamespy/Peer/peerc/peernitrocw/ROM-TS.rsf new file mode 100644 index 00000000..cec9e1db --- /dev/null +++ b/code/gamespy/Peer/peerc/peernitrocw/ROM-TS.rsf @@ -0,0 +1,116 @@ +#---------------------------------------------------------------------------- +# Project: NitroSDK - include +# File: ROM-TS.lsf +# +# Copyright 2003-2005 Nintendo. All rights reserved. +# +# These coded insructions, statements, and computer programs contain +# proprietary information of Nintendo of America Inc. and/or Nintendo +# Company Ltd., and are protected by Federal copyright law. They may +# not be disclosed to third parties or copied or duplicated in any form, +# in whole or in part, without the prior written consent of Nintendo. +# +# $Log: ROM-TS.rsf,v $ +# Revision 1.6 2005/04/05 23:52:58 yosizaki +# fix copyright date. +# +# Revision 1.5 2005/04/05 12:16:10 yosizaki +# support RomSpeedType parameter. +# +# Revision 1.4 2004/09/21 02:18:49 yasu +# Add default banner +# +# Revision 1.3 2004/09/09 11:39:09 yasu +# Unified ROM-TS and ROM-TS-C, also ROM-TEG and ROM-TEG-C +# +# Revision 1.2 2004/05/26 12:03:38 yasu +# add :r option to get basename for supporting IDE with makerom +# +# Revision 1.1 2004/04/06 01:59:59 yasu +# newly added +# +# $NoKeywords: $ +#---------------------------------------------------------------------------- +# +# Nitro ROM SPEC FILE +# + +Arm9 +{ + Static "$(MAKEROM_ARM9:r).sbin$(COMPSUFFIX9)" + OverlayDefs "$(MAKEROM_ARM9:r)_defs.sbin$(COMPSUFFIX9)" + OverlayTable "$(MAKEROM_ARM9:r)_table.sbin$(COMPSUFFIX9)" + Elf "$(MAKEROM_ARM9:r).nef" +} + +Arm7 +{ + Static "$(MAKEROM_ARM7:r).sbin$(COMPSUFFIX7)" + OverlayDefs "$(MAKEROM_ARM7:r)_defs.sbin$(COMPSUFFIX7)" + OverlayTable "$(MAKEROM_ARM7:r)_table.sbin$(COMPSUFFIX7)" + Elf "$(MAKEROM_ARM7:r).nef" +} + +Property +{ + ### + ### Settings for FinalROM + ### + #### BEGIN + # + # TITLE NAME: Your product name within 12bytes + # + #TitleName "YourAppName" + + # + # MAKER CODE: Your company ID# in 2 ascii words + # issued by NINTENDO + # + #MakerCode "00" + + # + # REMASTER VERSION: Mastering version + # + #RomVersion 0 + + # + # ROM SPEED TYPE: [MROM/1TROM/UNDEFINED] + # + RomSpeedType $(MAKEROM_ROMSPEED) + + # + # ROM SIZE: in bit [64M/128M/256M/512M/1G/2G] + # + #RomSize 128M + #RomSize 256M + + # + # ROM PADDING: TRUE if finalrom + # + #RomFootPadding TRUE + + # + # ROM HEADER TEMPLATE: Provided to every product by NINTENDO + # + #RomHeaderTemplate ./etc/rom_header.template.sbin + + # + # BANNER FILE: generated from Banner Spec File + # + #BannerFile ./etc/myGameBanner.bnr + BannerFile $(NITROSDK_ROOT)/include/nitro/specfiles/default.bnr + + ### + ### + ### + #### END +} + +RomSpec +{ + Offset 0x00000000 + Segment ALL + HostRoot $(MAKEROM_ROMROOT) + Root / + File $(MAKEROM_ROMFILES) +} diff --git a/code/gamespy/Peer/peerc/peerps2prodg/peerps2prodg.dsp b/code/gamespy/Peer/peerc/peerps2prodg/peerps2prodg.dsp new file mode 100644 index 00000000..056ed2b6 --- /dev/null +++ b/code/gamespy/Peer/peerc/peerps2prodg/peerps2prodg.dsp @@ -0,0 +1,564 @@ +# Microsoft Developer Studio Project File - Name="peerps2prodg" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=peerps2prodg - Win32 Release_Insock +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "peerps2prodg.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "peerps2prodg.mak" CFG="peerps2prodg - Win32 Release_Insock" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "peerps2prodg - Win32 Debug_EENet" (based on "Win32 (x86) Console Application") +!MESSAGE "peerps2prodg - Win32 Debug_Insock" (based on "Win32 (x86) Console Application") +!MESSAGE "peerps2prodg - Win32 Debug_SNSystems" (based on "Win32 (x86) Console Application") +!MESSAGE "peerps2prodg - Win32 Release_EENet" (based on "Win32 (x86) Console Application") +!MESSAGE "peerps2prodg - Win32 Release_Insock" (based on "Win32 (x86) Console Application") +!MESSAGE "peerps2prodg - Win32 Release_SNSystems" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/Gamespy/GOA/Peer/peerc/peerps2prodg", NTEDAAAA" +# PROP Scc_LocalPath "." +CPP=snCl.exe +RSC=rc.exe + +!IF "$(CFG)" == "peerps2prodg - Win32 Debug_EENet" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug_EENet" +# PROP BASE Intermediate_Dir "Debug_EENet" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_EENet" +# PROP Intermediate_Dir "Debug_EENet" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Debug/" /FD /debug /c +# ADD CPP /nologo /W4 /WX /Od /I "c:\usr\local\sce\ee\include\libeenet" /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "EENET" /D "_DEBUG" /D "SN_TARGET_PS2" /D "UNIQUEID" /FD /debug /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"PS2_EE_Debug\peerps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 libc.a eenetctl.a ent_smap.a ent_eth.a ent_ppp.a libeenet.a libscf.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /nodefaultlib /out:"Debug_EENet\peerps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "peerps2prodg - Win32 Debug_Insock" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "peerps2prodg___Win32_Debug_Insock" +# PROP BASE Intermediate_Dir "peerps2prodg___Win32_Debug_Insock" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_Insock" +# PROP Intermediate_Dir "Debug_Insock" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "_DEBUG" /D "SN_SYSTEMS" /D "SN_TARGET_PS2" /D "UNIQUEID" /FD /debug /c +# ADD CPP /nologo /W4 /WX /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "_DEBUG" /D "SN_TARGET_PS2" /D "INSOCK" /D "UNIQUEID" /FD /debug /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libc.a sneetcp.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /nodefaultlib /out:"Debug_SNSystems\peerps2prodg.elf" /D:SN_TARGET_PS2 -fshort-wchar +# ADD LINK32 libinsck.a libnet.a libmrpc.a libkernl.a libcdvd.a libnetif.a netcnfif.a /nologo /pdb:none /debug /machine:IX86 /nodefaultlib /out:"Debug_Insock\peerps2prodg.elf" /D:SN_TARGET_PS2 -fshort-wchar + +!ELSEIF "$(CFG)" == "peerps2prodg - Win32 Debug_SNSystems" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug_SNSystems" +# PROP BASE Intermediate_Dir "Debug_SNSystems" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_SNSystems" +# PROP Intermediate_Dir "Debug_SNSystems" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Debug/" /FD /debug /c +# ADD CPP /nologo /W4 /WX /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "_DEBUG" /D "SN_SYSTEMS" /D "SN_TARGET_PS2" /D "UNIQUEID" /FD /debug /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"PS2_EE_Debug\peerps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 libc.a sneetcp.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /nodefaultlib /out:"Debug_SNSystems\peerps2prodg.elf" /D:SN_TARGET_PS2 -fshort-wchar + +!ELSEIF "$(CFG)" == "peerps2prodg - Win32 Release_EENet" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Release_EENet" +# PROP BASE Intermediate_Dir "Release_EENet" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Release_EENet" +# PROP Intermediate_Dir "Release_EENet" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /O2 /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Release/" /FD /c +# ADD CPP /nologo /W4 /WX /Od /I "c:\usr\local\sce\ee\include\libeenet" /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "EENET" /D "SN_TARGET_PS2" /D "UNIQUEID" /FD /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"PS2_EE_Release\peerps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 eenetctl.a ent_smap.a ent_eth.a ent_ppp.a libeenet.a libscf.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"Release_EENet\peerps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "peerps2prodg - Win32 Release_Insock" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "peerps2prodg___Win32_Release_Insock" +# PROP BASE Intermediate_Dir "peerps2prodg___Win32_Release_Insock" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Release_Insock" +# PROP Intermediate_Dir "Release_Insock" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W4 /WX /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_SYSTEMS" /D "SN_TARGET_PS2" /D "UNIQUEID" /FD /c +# ADD CPP /nologo /W4 /WX /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /D "INSOCK" /D "UNIQUEID" /FD /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 sneetcp.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"Release_SNSystems\peerps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 libinsck.a libnet.a libmrpc.a libkernl.a libcdvd.a libnetif.a netcnfif.a /nologo /pdb:none /machine:IX86 /out:"Release_Insock\peerps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "peerps2prodg - Win32 Release_SNSystems" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Release_SNSystems" +# PROP BASE Intermediate_Dir "Release_SNSystems" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Release_SNSystems" +# PROP Intermediate_Dir "Release_SNSystems" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /O2 /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Release/" /FD /c +# ADD CPP /nologo /W4 /WX /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_SYSTEMS" /D "SN_TARGET_PS2" /D "UNIQUEID" /FD /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"PS2_EE_Release\peerps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 sneetcp.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"Release_SNSystems\peerps2prodg.elf" /D:SN_TARGET_PS2 + +!ENDIF + +# Begin Target + +# Name "peerps2prodg - Win32 Debug_EENet" +# Name "peerps2prodg - Win32 Debug_Insock" +# Name "peerps2prodg - Win32 Debug_SNSystems" +# Name "peerps2prodg - Win32 Release_EENet" +# Name "peerps2prodg - Win32 Release_Insock" +# Name "peerps2prodg - Win32 Release_SNSystems" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\peerc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\ps2\ps2common.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\..\common\ps2\prodg\PS2_in_VC.h +# End Source File +# End Group +# Begin Group "Peer" + +# PROP Default_Filter "" +# Begin Group "ServerBrowsingSDK" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\serverbrowsing\sb_crypt.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\serverbrowsing\sb_crypt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\serverbrowsing\sb_internal.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\serverbrowsing\sb_queryengine.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\serverbrowsing\sb_server.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\serverbrowsing\sb_serverbrowsing.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\serverbrowsing\sb_serverbrowsing.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\serverbrowsing\sb_serverlist.c +# End Source File +# End Group +# Begin Group "QueryReportingSDK" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\qr2\qr2.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\qr2\qr2.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\qr2\qr2regkeys.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\qr2\qr2regkeys.h +# End Source File +# End Group +# Begin Group "ChatSDK" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Chat\chat.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatCallbacks.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatCallbacks.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatChannel.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatChannel.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatCrypt.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatCrypt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatHandlers.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatHandlers.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatMain.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatMain.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatSocket.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatSocket.h +# End Source File +# End Group +# Begin Group "Pinger" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\pinger\pinger.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\pinger\pingerMain.c +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\peer.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerAutoMatch.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerAutoMatch.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerCallbacks.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerCallbacks.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerGlobalCallbacks.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerGlobalCallbacks.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerHost.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerHost.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerKeys.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerKeys.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerMain.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerMain.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerMangle.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerMangle.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerOperations.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerOperations.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerPing.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerPing.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerPlayers.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerPlayers.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerQR.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerQR.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerRooms.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerRooms.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerSB.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerSB.h +# End Source File +# End Group +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\darray.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\darray.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAssert.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAssert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAvailable.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAvailable.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsCommon.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsDebug.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsDebug.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsMemory.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsMemory.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatform.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatform.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformSocket.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformSocket.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformThread.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformThread.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformUtil.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformUtil.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsStringUtil.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsStringUtil.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\hashtable.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\hashtable.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\md5.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\md5c.c +# End Source File +# End Group +# Begin Source File + +SOURCE=c:\usr\local\sce\ee\lib\app.cmd +# End Source File +# Begin Source File + +SOURCE=c:\usr\local\sce\ee\lib\crt0.s +# End Source File +# Begin Source File + +SOURCE=..\..\..\ps2common\prodg\ps2.lk +# End Source File +# End Target +# End Project diff --git a/code/gamespy/Peer/peerc/peerps2prodg/peerps2prodg.dsw b/code/gamespy/Peer/peerc/peerps2prodg/peerps2prodg.dsw new file mode 100644 index 00000000..7c1581af --- /dev/null +++ b/code/gamespy/Peer/peerc/peerps2prodg/peerps2prodg.dsw @@ -0,0 +1,37 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "peerps2prodg"=.\peerps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/Peer/peerc/peerps2prodg", NTEDAAAA + . + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ + begin source code control + "$/gamespy/goa/peer/peerc/peerps2prodg", NTEDAAAA + . + end source code control +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/LoginDlg.cpp b/code/gamespy/Voice2/Voice2BuddyMFC/LoginDlg.cpp new file mode 100644 index 00000000..4938f083 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/LoginDlg.cpp @@ -0,0 +1,107 @@ +// LoginDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "Voice2BuddyMFC.h" +#include "LoginDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + + +CLoginDlg::CLoginDlg(CWnd* pParent /*=NULL*/) + : CDialog(CLoginDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CLoginDlg) + m_Email = _T(""); + m_Nickname = _T(""); + m_Password = _T(""); + //}}AFX_DATA_INIT +} + + +void CLoginDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CLoginDlg) + DDX_Text(pDX, IDC_EMAIL, m_Email); + DDX_Text(pDX, IDC_NICKNAME, m_Nickname); + DDX_Text(pDX, IDC_PASSWORD, m_Password); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CLoginDlg, CDialog) + //{{AFX_MSG_MAP(CLoginDlg) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg message handlers + +void CLoginDlg::OnOK() +{ + // Retrieve settings from the view + UpdateData(TRUE); + + // Save settings to the registry + HKEY aRegKey = NULL; + LONG aResult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\GameSpy\\Voice2BuddyMFC", 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &aRegKey, NULL); + if (aResult == ERROR_SUCCESS) + { + RegSetValue(aRegKey, "LastLoginNickname", REG_SZ, m_Nickname, m_Nickname.GetLength()); + RegSetValue(aRegKey, "LastLoginEmail", REG_SZ, m_Email, m_Email.GetLength()); + RegCloseKey(aRegKey); + } + + + CDialog::OnOK(); +} + +void CLoginDlg::OnCancel() +{ + + CDialog::OnCancel(); +} + +BOOL CLoginDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Load settings from the registry + char aStringBuf[255]; + LONG aLongBuf; + + HKEY aRegKey = NULL; + LONG aResult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\GameSpy\\Voice2BuddyMFC", 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &aRegKey, NULL); + if (aResult == ERROR_SUCCESS) + { + // Load last session username + aLongBuf = 255; + aResult = RegQueryValue(aRegKey, "LastLoginNickname", aStringBuf, &aLongBuf); + if (aResult == ERROR_SUCCESS) + m_Nickname = aStringBuf; + + // Load last session email + aLongBuf = 255; + aResult = RegQueryValue(aRegKey, "LastLoginEmail", aStringBuf, &aLongBuf); + if (aResult == ERROR_SUCCESS) + m_Email = aStringBuf; + + RegCloseKey(aRegKey); + } + + // Reflect new settings into the view + UpdateData(FALSE); + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/LoginDlg.h b/code/gamespy/Voice2/Voice2BuddyMFC/LoginDlg.h new file mode 100644 index 00000000..dd0d2490 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/LoginDlg.h @@ -0,0 +1,50 @@ +#if !defined(AFX_LOGINDLG_H__0E2CE143_57CF_4FE7_9DFB_9C63388C0680__INCLUDED_) +#define AFX_LOGINDLG_H__0E2CE143_57CF_4FE7_9DFB_9C63388C0680__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// LoginDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + +class CLoginDlg : public CDialog +{ +// Construction +public: + CLoginDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CLoginDlg) + enum { IDD = IDD_LOGIN }; + CString m_Email; + CString m_Nickname; + CString m_Password; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CLoginDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CLoginDlg) + virtual void OnOK(); + virtual void OnCancel(); + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_LOGINDLG_H__0E2CE143_57CF_4FE7_9DFB_9C63388C0680__INCLUDED_) diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/SetupDlg.cpp b/code/gamespy/Voice2/Voice2BuddyMFC/SetupDlg.cpp new file mode 100644 index 00000000..bf54bc68 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/SetupDlg.cpp @@ -0,0 +1,236 @@ +// SetupDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "Voice2BuddyMFC.h" +#include "SetupDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define VOICE_THINK_TIMER_ID 101 +#define VOICE_THINK_TIMER_DELAY 10 + +enum SetupMessage +{ + V2B_STARTDEVICES = 0 +}; + +///////////////////////////////////////////////////////////////////////////// +// CSetupDlg dialog + + +CSetupDlg::CSetupDlg(CWnd* pParent /*=NULL*/) + : CDialog(CSetupDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CSetupDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void CSetupDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CSetupDlg) + DDX_Control(pDX, IDC_ACTIVATELEVEL, m_ActivateLevel); + DDX_Control(pDX, IDC_VOICELEVEL, m_VoiceLevelCtrl); + DDX_Control(pDX, IDC_ISSPEAKING, m_IsSpeakingCtrl); + DDX_Control(pDX, IDC_PLAYBACKCOMBO, m_PlaybackCombo); + DDX_Control(pDX, IDC_CAPTURECOMBO, m_CaptureCombo); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CSetupDlg, CDialog) + //{{AFX_MSG_MAP(CSetupDlg) + ON_CBN_SELCHANGE(IDC_CAPTURECOMBO, OnSelChangeCaptureCombo) + ON_CBN_SELCHANGE(IDC_PLAYBACKCOMBO, OnSelChangePlaybackCombo) + ON_WM_TIMER() + ON_WM_HSCROLL() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CSetupDlg message handlers + +BOOL CSetupDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Begin Enumerate devices + int aNumDevices = gvListDevices(m_SetupInfo->m_DeviceInfoArray, MAX_DEVICES, GV_CAPTURE_AND_PLAYBACK); + + // Populate the combo boxes + for (int aDeviceNum = 0; aDeviceNum < aNumDevices; aDeviceNum++) + { + // Add capture devices to display list + if (GV_CAPTURE & m_SetupInfo->m_DeviceInfoArray[aDeviceNum].m_deviceType) + { + // Set the item data to the device number (this may differ from the row due to sorting) + m_CaptureCombo.InsertString(0, m_SetupInfo->m_DeviceInfoArray[aDeviceNum].m_name); + m_CaptureCombo.SetItemData(0, aDeviceNum); + if (GV_CAPTURE & m_SetupInfo->m_DeviceInfoArray[aDeviceNum].m_defaultDevice) + m_CaptureCombo.SetCurSel(0); + } + + // Add playback devices to display list + if (GV_PLAYBACK & m_SetupInfo->m_DeviceInfoArray[aDeviceNum].m_deviceType) + { + // Set the item data to the device number (this may differ from the row due to sorting) + m_PlaybackCombo.InsertString(0, m_SetupInfo->m_DeviceInfoArray[aDeviceNum].m_name); + m_PlaybackCombo.SetItemData(0, aDeviceNum); + if (GV_PLAYBACK & m_SetupInfo->m_DeviceInfoArray[aDeviceNum].m_defaultDevice) + m_PlaybackCombo.SetCurSel(0); + } + } + + // If no device has been set, try setting to the first one + if (m_CaptureCombo.GetCurSel() == -1) + m_CaptureCombo.SetCurSel(0); + if (m_PlaybackCombo.GetCurSel() == -1) + m_PlaybackCombo.SetCurSel(0); + + // Try to start the capture and playback device + StartSelCaptureDevice(); + StartSelPlaybackDevice(); + + UpdateData(FALSE); + + SetTimer(VOICE_THINK_TIMER_ID, VOICE_THINK_TIMER_DELAY, NULL); + + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + + +// Switch the active device +void CSetupDlg::OnSelChangeCaptureCombo() +{ + StartSelCaptureDevice(); +} + +// Switch the active device +void CSetupDlg::OnSelChangePlaybackCombo() +{ + StartSelPlaybackDevice(); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void CSetupDlg::StartSelCaptureDevice() +{ + // Stop the previous device + if (m_SetupInfo->m_CaptureDevice != NULL) + { + gvStopDevice(m_SetupInfo->m_CaptureDevice, GV_CAPTURE); + gvFreeDevice(m_SetupInfo->m_CaptureDevice); + } + + // Get the selected device id + int aSelDevice = m_CaptureCombo.GetCurSel(); + if (aSelDevice == -1) + return; + + int aDeviceNum = m_CaptureCombo.GetItemData(aSelDevice); + if (aDeviceNum == CB_ERR) + return; + + // Create and start the new device + m_SetupInfo->m_CaptureDevice = gvNewDevice(m_SetupInfo->m_DeviceInfoArray[aDeviceNum].m_id, GV_CAPTURE); + if (m_SetupInfo->m_CaptureDevice != NULL) + gvStartDevice(m_SetupInfo->m_CaptureDevice, GV_CAPTURE); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void CSetupDlg::StartSelPlaybackDevice() +{ + // Stop the previous device + if (m_SetupInfo->m_PlaybackDevice != NULL) + { + gvStopDevice(m_SetupInfo->m_PlaybackDevice, GV_PLAYBACK); + gvFreeDevice(m_SetupInfo->m_PlaybackDevice); + } + + // Get the selected device id + int aSelDevice = m_PlaybackCombo.GetCurSel(); + if (aSelDevice == -1) + return; + + int aDeviceNum = m_PlaybackCombo.GetItemData(aSelDevice); + if (aDeviceNum == CB_ERR) + return; + + // Create and start the new device + m_SetupInfo->m_PlaybackDevice = gvNewDevice(m_SetupInfo->m_DeviceInfoArray[aDeviceNum].m_id, GV_PLAYBACK); + if (m_SetupInfo->m_PlaybackDevice != NULL) + gvStartDevice(m_SetupInfo->m_PlaybackDevice, GV_PLAYBACK); +} + +static GVBool gMute = GVFalse; + +void CSetupDlg::OnTimer(UINT nIDEvent) +{ + gvThink(); + + // Check for voice data, play as local echo + int aBytesAvailable = gvGetAvailableCaptureBytes(m_SetupInfo->m_CaptureDevice); + if (aBytesAvailable > 0) + { + GVByte aBuffer[1024]; + int aLength = 1024; + GVFrameStamp aFrameStamp; + GVScalar aVolume = 0; + + GVBool gotPacket = gvCapturePacket(m_SetupInfo->m_CaptureDevice, aBuffer, &aLength, &aFrameStamp, &aVolume); + if (gotPacket == GVTrue) + { + gvPlayPacket(m_SetupInfo->m_PlaybackDevice, aBuffer, aLength, 0, aFrameStamp, gMute); + m_IsSpeakingCtrl.ShowWindow(SW_SHOW); + } + else + { + m_IsSpeakingCtrl.ShowWindow(SW_HIDE); + } + + // Display the volume level + m_VoiceLevelCtrl.SetPos( (int)(aVolume*100) ); + } + + + CDialog::OnTimer(nIDEvent); +} + +void CSetupDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) +{ + if (pScrollBar == (CScrollBar*)&m_ActivateLevel) + { + double aThreshold = (float)m_ActivateLevel.GetPos()/100; + if (m_SetupInfo->m_CaptureDevice != NULL) + gvSetCaptureThreshold(m_SetupInfo->m_CaptureDevice, aThreshold); + } + CDialog::OnHScroll(nSBCode, nPos, pScrollBar); +} + +BOOL CSetupDlg::DestroyWindow() +{ + // Stop the devices + if (m_SetupInfo->m_CaptureDevice != NULL) + gvStopDevice(m_SetupInfo->m_CaptureDevice, GV_CAPTURE); + if (m_SetupInfo->m_PlaybackDevice != NULL) + gvStopDevice(m_SetupInfo->m_PlaybackDevice, GV_PLAYBACK); + + + // Kill the think timer + KillTimer(VOICE_THINK_TIMER_ID); + + + return CDialog::DestroyWindow(); +} diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/SetupDlg.h b/code/gamespy/Voice2/Voice2BuddyMFC/SetupDlg.h new file mode 100644 index 00000000..a832760b --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/SetupDlg.h @@ -0,0 +1,81 @@ +#if !defined(AFX_SETUPDLG_H__36B5B6D7_690D_4628_92E4_96B2CAE34B82__INCLUDED_) +#define AFX_SETUPDLG_H__36B5B6D7_690D_4628_92E4_96B2CAE34B82__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// SetupDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CSetupDlg dialog +#include "../../voice2/gv.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +const unsigned int MAX_DEVICES = 10; + +struct VoiceSetupInfo +{ + GVDevice m_PlaybackDevice; + GVDevice m_CaptureDevice; + GVDeviceInfo m_DeviceInfoArray[MAX_DEVICES]; +}; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +class CSetupDlg : public CDialog +{ +public: + VoiceSetupInfo* m_SetupInfo; + + void StartSelCaptureDevice(); + void StartSelPlaybackDevice(); + + + // MFC STUFF BELOW + +// Construction +public: + CSetupDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CSetupDlg) + enum { IDD = IDD_SETUPDIALOG }; + CSliderCtrl m_ActivateLevel; + CProgressCtrl m_VoiceLevelCtrl; + CStatic m_IsSpeakingCtrl; + CComboBox m_PlaybackCombo; + CComboBox m_CaptureCombo; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CSetupDlg) + public: + virtual BOOL DestroyWindow(); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CSetupDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnSelChangeCaptureCombo(); + afx_msg void OnSelChangePlaybackCombo(); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_SETUPDLG_H__36B5B6D7_690D_4628_92E4_96B2CAE34B82__INCLUDED_) diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/StdAfx.cpp b/code/gamespy/Voice2/Voice2BuddyMFC/StdAfx.cpp new file mode 100644 index 00000000..16c74863 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// Voice2BuddyMFC.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/StdAfx.h b/code/gamespy/Voice2/Voice2BuddyMFC/StdAfx.h new file mode 100644 index 00000000..19665974 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/StdAfx.h @@ -0,0 +1,27 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__FCEAFCA1_440C_4599_B89A_56952F1DB30C__INCLUDED_) +#define AFX_STDAFX_H__FCEAFCA1_440C_4599_B89A_56952F1DB30C__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__FCEAFCA1_440C_4599_B89A_56952F1DB30C__INCLUDED_) diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.cpp b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.cpp new file mode 100644 index 00000000..fca45c65 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.cpp @@ -0,0 +1,74 @@ +// Voice2BuddyMFC.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "Voice2BuddyMFC.h" +#include "Voice2BuddyMFCDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CVoice2BuddyMFCApp + +BEGIN_MESSAGE_MAP(CVoice2BuddyMFCApp, CWinApp) + //{{AFX_MSG_MAP(CVoice2BuddyMFCApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CVoice2BuddyMFCApp construction + +CVoice2BuddyMFCApp::CVoice2BuddyMFCApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CVoice2BuddyMFCApp object + +CVoice2BuddyMFCApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CVoice2BuddyMFCApp initialization + +BOOL CVoice2BuddyMFCApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. +/* +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif +*/ + CVoice2BuddyMFCDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + // TODO: Place code here to handle when the dialog is + // dismissed with OK + } + else if (nResponse == IDCANCEL) + { + // TODO: Place code here to handle when the dialog is + // dismissed with Cancel + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.h b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.h new file mode 100644 index 00000000..d8196222 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.h @@ -0,0 +1,49 @@ +// Voice2BuddyMFC.h : main header file for the VOICE2BUDDYMFC application +// + +#if !defined(AFX_VOICE2BUDDYMFC_H__B124CD13_D911_4447_AF83_E1503E9147BF__INCLUDED_) +#define AFX_VOICE2BUDDYMFC_H__B124CD13_D911_4447_AF83_E1503E9147BF__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CVoice2BuddyMFCApp: +// See Voice2BuddyMFC.cpp for the implementation of this class +// + +class CVoice2BuddyMFCApp : public CWinApp +{ +public: + CVoice2BuddyMFCApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CVoice2BuddyMFCApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CVoice2BuddyMFCApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_VOICE2BUDDYMFC_H__B124CD13_D911_4447_AF83_E1503E9147BF__INCLUDED_) diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.rc b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.rc new file mode 100644 index 00000000..9feae435 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.rc @@ -0,0 +1,300 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\Voice2BuddyMFC.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\Voice2BuddyMFC.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ABOUTBOX DIALOG DISCARDABLE 0, 0, 235, 55 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About Voice2BuddyMFC" +FONT 8, "MS Sans Serif" +BEGIN + ICON IDR_MAINFRAME,IDC_STATIC,11,17,20,20 + LTEXT "Voice2BuddyMFC Version 1.0",IDC_STATIC,40,10,119,8, + SS_NOPREFIX + LTEXT "Copyright (C) 2004",IDC_STATIC,40,25,119,8 + DEFPUSHBUTTON "OK",IDOK,178,7,50,14,WS_GROUP +END + +IDD_VOICE2BUDDYMFC_DIALOG DIALOGEX 0, 0, 150, 126 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "Voice2 Buddy MFC" +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "Exit",IDCANCEL,7,105,50,14 + PUSHBUTTON "Setup",ID_SETUP,7,78,50,14,WS_DISABLED + PUSHBUTTON "Voice Chat",ID_VOICECHAT,7,59,50,14,WS_DISABLED + LISTBOX IDC_BUDDYLIST,64,7,79,112,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_HSCROLL | + WS_TABSTOP + LTEXT "Select a buddy to chat with --->",IDC_STATIC,7,7,50,46 +END + +IDD_LOGIN DIALOG DISCARDABLE 0, 0, 143, 95 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Login" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,75,74,50,14 + PUSHBUTTON "Cancel",IDCANCEL,18,74,50,14 + EDITTEXT IDC_EMAIL,48,7,88,14,ES_AUTOHSCROLL + EDITTEXT IDC_NICKNAME,48,28,88,14,ES_AUTOHSCROLL + EDITTEXT IDC_PASSWORD,48,49,88,14,ES_PASSWORD | ES_AUTOHSCROLL + RTEXT "Email:",IDC_STATIC,7,7,36,14,SS_CENTERIMAGE + RTEXT "Nickname:",IDC_STATIC,7,28,36,14,SS_CENTERIMAGE + RTEXT "Password:",IDC_STATIC,7,49,36,14,SS_CENTERIMAGE +END + +IDD_SESSION DIALOG DISCARDABLE 0, 0, 121, 79 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Session" +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "Exit",IDCANCEL,35,58,50,14 + CTEXT "",IDC_DISPLAYTEXT,7,7,107,23 + CONTROL 146,IDC_SPEAKING1,"Static",SS_BITMAP | SS_SUNKEN | NOT + WS_VISIBLE,25,36,28,14 + CONTROL 134,IDC_SPEAKING2,"Static",SS_BITMAP | SS_SUNKEN | NOT + WS_VISIBLE,68,36,28,14 +END + +IDD_SETUPDIALOG DIALOG DISCARDABLE 0, 0, 197, 207 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Voice Setup" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,73,186,50,14 + COMBOBOX IDC_PLAYBACKCOMBO,43,82,136,105,CBS_DROPDOWNLIST | + CBS_SORT | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_CAPTURECOMBO,43,116,136,96,CBS_DROPDOWNLIST | + CBS_SORT | WS_VSCROLL | WS_TABSTOP + LTEXT "Capture Device:",IDC_STATIC,43,103,98,12 + LTEXT "Playback Device:",IDC_STATIC,43,68,117,12 + CONTROL 136,IDC_SPEAKERBITMAP,"Static",SS_BITMAP,13,74,21,20 + CONTROL 135,IDC_MICROPHONEBITMAP,"Static",SS_BITMAP,13,106,21,20 + GROUPBOX "",IDC_STATIC,7,58,183,82,BS_CENTER + CONTROL "Slider1",IDC_ACTIVATELEVEL,"msctls_trackbar32",TBS_BOTH | + TBS_NOTICKS | WS_TABSTOP,48,146,105,12 + CONTROL "Progress1",IDC_VOICELEVEL,"msctls_progress32",WS_BORDER, + 51,160,97,11 + GROUPBOX "",IDC_STATIC,7,138,183,40 + LTEXT "Activate",IDC_STATIC,155,147,27,10 + LTEXT "Volume",IDC_STATIC,155,161,27,10 + CONTROL 146,IDC_ISSPEAKING,"Static",SS_BITMAP,16,153,27,12 + CONTROL 142,IDC_STATIC,"Static",SS_BITMAP | SS_SUNKEN,7,7,185,52 +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "Voice2BuddyMFC MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "Voice2BuddyMFC\0" + VALUE "LegalCopyright", "Copyright (C) 2004\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "Voice2BuddyMFC.EXE\0" + VALUE "ProductName", "Voice2BuddyMFC Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_ABOUTBOX, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 228 + TOPMARGIN, 7 + BOTTOMMARGIN, 48 + END + + IDD_VOICE2BUDDYMFC_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 143 + TOPMARGIN, 7 + BOTTOMMARGIN, 119 + END + + IDD_LOGIN, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 136 + TOPMARGIN, 7 + BOTTOMMARGIN, 88 + END + + IDD_SESSION, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 114 + TOPMARGIN, 7 + BOTTOMMARGIN, 72 + END + + IDD_SETUPDIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 190 + TOPMARGIN, 7 + BOTTOMMARGIN, 200 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +IDB_SPEAKER BITMAP DISCARDABLE "res\\microsoft-speaker.bmp" +IDB_MICROPHONE BITMAP DISCARDABLE "res\\microsoft-microphone.bmp" +IDB_GAMESPYLOGO BITMAP DISCARDABLE "res\\logo270x83.bmp" +IDB_SPEAKING BITMAP DISCARDABLE "res\\speaking.bmp" +IDB_GAMESPYLOGO1 BITMAP DISCARDABLE "res\\gamespyl.bmp" +IDB_SPEAKING2 BITMAP DISCARDABLE "res\\speaking2.bmp" + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDS_ABOUTBOX "&About Voice2BuddyMFC..." +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\Voice2BuddyMFC.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFCDlg.cpp b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFCDlg.cpp new file mode 100644 index 00000000..54281b3c --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFCDlg.cpp @@ -0,0 +1,600 @@ +// Voice2BuddyMFCDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "Voice2BuddyMFC.h" +#include "Voice2BuddyMFCDlg.h" + +#include "LoginDlg.h" +#include "SetupDlg.h" +#include "VoiceSessionDlg.h" + + +#include "../../common/gsAvailable.h" +#include "../../voice2/gv.h" + + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +enum V2BuddyMessage +{ + V2B_MSG_DOSETUP, + V2B_MSG_DOLOGIN, + V2B_MSG_DOVOICECHAT, +}; + +#define THINK_TIMER_ID 100 +#define THINK_TIMER_DELAY 10 +#define GAME_NAME "gmtest" +#define NAMESPACE_GAMESPY_SHARED 0 +#define PRODUCTID_GMTEST 1 // can't use 0! + +#define V2B_GP_LOCATION "Voice2BuddyMFC" +#define V2B_GP_STATUS_IDLE "Idle" // free to invite +#define V2B_GP_STATUS_CHATTING "Chatting" // actively chatting +#define V2B_GP_INVITE_DECLINED "Voice invitation declined." +#define V2B_GP_INVITE_ACCEPTED "Voice invitation accepted." + +// Utility to check buddy status and make sure they're running V2B +BOOL IsBuddyUsingV2B(GPConnection* theConnection, GPProfile theProfileId, GPBuddyStatus* theStatus); + + +///////////////////////////////////////////////////////////////////////////// +// CAboutDlg dialog used for App About + +class CAboutDlg : public CDialog +{ +public: + CAboutDlg(); + +// Dialog Data + //{{AFX_DATA(CAboutDlg) + enum { IDD = IDD_ABOUTBOX }; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CAboutDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + //{{AFX_MSG(CAboutDlg) + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) +{ + //{{AFX_DATA_INIT(CAboutDlg) + //}}AFX_DATA_INIT +} + +void CAboutDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CAboutDlg) + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) + //{{AFX_MSG_MAP(CAboutDlg) + // No message handlers + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CVoice2BuddyMFCDlg dialog + +CVoice2BuddyMFCDlg::CVoice2BuddyMFCDlg(CWnd* pParent /*=NULL*/) + : CDialog(CVoice2BuddyMFCDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CVoice2BuddyMFCDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT + // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); +} + +void CVoice2BuddyMFCDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CVoice2BuddyMFCDlg) + DDX_Control(pDX, ID_SETUP, m_SetupButton); + DDX_Control(pDX, ID_VOICECHAT, m_VoiceChatButton); + DDX_Control(pDX, IDC_BUDDYLIST, m_BuddyList); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CVoice2BuddyMFCDlg, CDialog) + //{{AFX_MSG_MAP(CVoice2BuddyMFCDlg) + ON_WM_SYSCOMMAND() + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_BN_CLICKED(ID_SETUP, OnSetup) + ON_BN_CLICKED(ID_VOICECHAT, OnVoiceChat) + ON_BN_CLICKED(IDCANCEL, OnExit) + ON_WM_TIMER() + ON_WM_DESTROY() + ON_LBN_SELCHANGE(IDC_BUDDYLIST, OnSelchangeBuddylist) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CVoice2BuddyMFCDlg message handlers + +BOOL CVoice2BuddyMFCDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Add "About..." menu item to system menu. + + // IDM_ABOUTBOX must be in the system command range. + ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); + ASSERT(IDM_ABOUTBOX < 0xF000); + + CMenu* pSysMenu = GetSystemMenu(FALSE); + if (pSysMenu != NULL) + { + CString strAboutMenu; + strAboutMenu.LoadString(IDS_ABOUTBOX); + if (!strAboutMenu.IsEmpty()) + { + pSysMenu->AppendMenu(MF_SEPARATOR); + pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); + } + } + + // Set the icon for this dialog. The framework does this automatically + // when the application's main window is not a dialog + SetIcon(m_hIcon, TRUE); // Set big icon + SetIcon(m_hIcon, FALSE); // Set small icon + + // We're not connected yet + m_GP = NULL; + m_Initialized = FALSE; + m_Connected = FALSE; + m_InvitedPlayer = 0; + m_MyProfileId = 0; + + // Start network + SocketStartUp(); + + // Init voice SDK (pre-init, do not start devices yet) + GVBool aResult = gvStartup(m_hWnd); + if (aResult != GVTrue) + { + MessageBox("Failed on gvStartup!"); + PostQuitMessage(0); + } + + // Set the codec (this sample hard codes it) + gvSetSampleRate(GVRate_16KHz); + aResult = gvSetCodec(GVCodecSuperHighQuality); + if (aResult != GVTrue) + { + MessageBox("Failed on gvSetCodec!"); + PostQuitMessage(0); + } + + // Disable the "voice chat" button until a buddy is selected + m_VoiceChatButton.EnableWindow(FALSE); + + // Clear the setup info + memset(&m_SetupInfo, 0, sizeof(m_SetupInfo)); + + // Show the setup dialog + PostMessage(WM_USER+1, V2B_MSG_DOSETUP, 0); + + return TRUE; // return TRUE unless you set the focus to a control +} + +void CVoice2BuddyMFCDlg::OnSysCommand(UINT nID, LPARAM lParam) +{ + if ((nID & 0xFFF0) == IDM_ABOUTBOX) + { + CAboutDlg dlgAbout; + dlgAbout.DoModal(); + } + else + { + CDialog::OnSysCommand(nID, lParam); + } +} + +// If you add a minimize button to your dialog, you will need the code below +// to draw the icon. For MFC applications using the document/view model, +// this is automatically done for you by the framework. + +void CVoice2BuddyMFCDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CDialog::OnPaint(); + } +} + +// The system calls this to obtain the cursor to display while the user drags +// the minimized window. +HCURSOR CVoice2BuddyMFCDlg::OnQueryDragIcon() +{ + return (HCURSOR) m_hIcon; +} + +void CVoice2BuddyMFCDlg::OnSetup() +{ + // Setup and calibrate the voice hardware + CSetupDlg aSetupDlg; + aSetupDlg.m_SetupInfo = &m_SetupInfo; + aSetupDlg.DoModal(); +} + +void CVoice2BuddyMFCDlg::OnVoiceChat() +{ + // Get the selected buddy + int selIndex = m_BuddyList.GetCurSel(); + if (selIndex == -1) + return; + + // Get the buddy profile + GPProfile aProfile = m_BuddyList.GetItemData(selIndex); + + // Make sure the buddy is using V2B + GPBuddyStatus aStatus; + if (FALSE == IsBuddyUsingV2B(&m_GP, aProfile, &aStatus)) + { + MessageBox("Buddy is not running V2B."); + return; + } + + // Make sure this buddy isn't chatting already + if (strcmp(V2B_GP_STATUS_CHATTING, aStatus.locationString) == 0) + { + MessageBox("Buddy is currently involved in a voice chat."); + return; + } + + // Invite the buddy to voice chat + gpInvitePlayer(&m_GP, aProfile, PRODUCTID_GMTEST, NULL); + m_InvitedPlayer = aProfile; // remember who we invited +} + +void CVoice2BuddyMFCDlg::OnExit() +{ + // TODO: Add your control notification handler code here + CDialog::OnCancel(); +} + +LRESULT CVoice2BuddyMFCDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) +{ + if (message == (WM_USER+1)) + { + // Process custom message + switch(wParam) + { + case V2B_MSG_DOSETUP: + { + // Show the setup dialog + OnSetup(); + + // Pop up the login dialog + PostMessage(WM_USER+1, V2B_MSG_DOLOGIN, 0); + + // Start the app timer + SetTimer(THINK_TIMER_ID, THINK_TIMER_DELAY, NULL); + return 0; + } + case V2B_MSG_DOLOGIN: + { + // Show the login dialog + CLoginDlg aDlg; + int result = aDlg.DoModal(); + if (result == IDCANCEL) + PostQuitMessage(0); + else + DoLogin(aDlg.m_Email, aDlg.m_Nickname, aDlg.m_Password); + return 0; + } + case V2B_MSG_DOVOICECHAT: + { + // Create the voice session dialog + CVoiceSessionDlg aVoiceSessionDlg; + aVoiceSessionDlg.m_NNCookie = lParam; + aVoiceSessionDlg.m_SetupInfo = m_SetupInfo; + + // We use the profileId of the host as the NNCookie + // now, check to see if we're the host + if (m_MyProfileId == aVoiceSessionDlg.m_NNCookie) + aVoiceSessionDlg.m_IsHost = TRUE; + else + aVoiceSessionDlg.m_IsHost = FALSE; + + // Run the dialog + aVoiceSessionDlg.DoModal(); + return 0; + } + }; + } // end custom messages + return CDialog::WindowProc(message, wParam, lParam); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Utility to check if a buddy is using this app +BOOL IsBuddyUsingV2B(GPConnection* theConnection, GPProfile theProfileId, GPBuddyStatus* theStatus) +{ + GPBuddyStatus aTempStatus; + GPBuddyStatus* aStatus; + if (theStatus != NULL) + aStatus = theStatus; // store location in return value + else + aStatus = &aTempStatus; // store location in temp + + // get the internal buddy index + int aBuddyIndex; + GPResult aResult = gpGetBuddyIndex(theConnection, theProfileId, &aBuddyIndex); + if (aResult != GP_NO_ERROR) + return FALSE; // not a buddy + + // Get the buddy status + aResult = gpGetBuddyStatus(theConnection, aBuddyIndex, aStatus); + if (aResult != GP_NO_ERROR) + return FALSE; // couldn't get status + + // Is the buddy using V2B? + if (strcmp(V2B_GP_LOCATION, aStatus->locationString) != 0) + return FALSE; // no using V2B + + return TRUE; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void OnGPError(GPConnection* theConnection, GPErrorArg* theArg, void* theParam) +{ + CVoice2BuddyMFCDlg* aDialog = (CVoice2BuddyMFCDlg*)theParam; + + char buf[1024]; + sprintf(buf, "GP ERROR: %s", theArg->errorString); + aDialog->MessageBox(buf); + + if (theArg->fatal == GP_FATAL) + aDialog->m_Connected = FALSE; + GSI_UNUSED(theConnection); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void OnGPBuddyInfo(GPConnection* theConnection, GPGetInfoResponseArg* theArg, void* theParam) +{ + CVoice2BuddyMFCDlg* aDialog = (CVoice2BuddyMFCDlg*)theParam; + + // Make sure this name is in the list + int anIndex = aDialog->m_BuddyList.FindString(0, theArg->nick); + if (anIndex == -1) + { + int anIndex = aDialog->m_BuddyList.AddString(theArg->nick); + if (anIndex != -1) + aDialog->m_BuddyList.SetItemData(anIndex, theArg->profile); + } + + GSI_UNUSED(theConnection); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void OnGPBuddyStatus(GPConnection* theConnection, GPRecvBuddyStatusArg* theArg, void* theParam) +{ + CVoice2BuddyMFCDlg* aDialog = (CVoice2BuddyMFCDlg*)theParam; + + // Retrieve info for the buddy (nickname etc.) + // will return with file cache information, if available + gpGetInfo(theConnection, theArg->profile, GP_CHECK_CACHE, GP_NON_BLOCKING, (GPCallback)OnGPBuddyInfo, theParam); + GSI_UNUSED(aDialog); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void OnGPBuddyMessage(GPConnection* theConnection, GPRecvBuddyMessageArg* theArg, void* theParam) +{ + CVoice2BuddyMFCDlg* aDialog = (CVoice2BuddyMFCDlg*)theParam; + + // Ignore messages that are not from V2B + if (FALSE == IsBuddyUsingV2B(theConnection, theArg->profile, NULL)) + return; + + // If this wasn't the guy we invited, ignore it + if (theArg->profile != aDialog->m_InvitedPlayer) + return; + + // Is this an invite accept? + if (strcmp(V2B_GP_INVITE_ACCEPTED, theArg->message)==0) + aDialog->PostMessage(WM_USER+1, V2B_MSG_DOVOICECHAT, aDialog->m_MyProfileId); + + // Is this an invite decline? + if (strcmp(V2B_GP_INVITE_DECLINED, theArg->message)==0) + aDialog->MessageBox(theArg->message); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void OnGPInvite(GPConnection* theConnection, GPRecvGameInviteArg* theArg, void* theParam) +{ + CVoice2BuddyMFCDlg* aDialog = (CVoice2BuddyMFCDlg*)theParam; + + // Make sure the invite is from V2B + if (FALSE == IsBuddyUsingV2B(theConnection, theArg->profile, NULL)) + return; + + // Find the nickname of the buddy, for display + // (may not be the selected buddy) + CString aBuddyName = ""; + int anIndex = 0; + while(anIndex < aDialog->m_BuddyList.GetCount()) + { + // If this is the buddy, store off the name + if (aDialog->m_BuddyList.GetItemData(anIndex) == (unsigned int)theArg->profile) + { + aDialog->m_BuddyList.GetText(anIndex, aBuddyName); + break; + } + anIndex++; + } + if (aBuddyName.IsEmpty()) + return; // buddy not found, ignore the invite + + // Ask the user if they want to accept + char buf[255]; + sprintf(buf, "%s has invited you to voice chat. Accept?", (LPCSTR)aBuddyName); + int aDialogResult = aDialog->MessageBox(buf, "Voice Chat", MB_YESNO); + if (aDialogResult == IDYES) + { + // begin voice session + gpSendBuddyMessage(&aDialog->m_GP, theArg->profile, V2B_GP_INVITE_ACCEPTED); + aDialog->PostMessage(WM_USER+1, V2B_MSG_DOVOICECHAT, theArg->profile); + } + else + { + // send rejection + gpSendBuddyMessage(&aDialog->m_GP, theArg->profile, V2B_GP_INVITE_DECLINED); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void OnGPConnect(GPConnection* theConnection, GPConnectResponseArg* theArg, void* theParam) +{ + CVoice2BuddyMFCDlg* aDialog = (CVoice2BuddyMFCDlg*)theParam; + + if (theArg->result == GP_NO_ERROR) + { + aDialog->m_Connected = TRUE; + aDialog->m_SetupButton.EnableWindow(TRUE); + aDialog->m_MyProfileId = theArg->profile; + } + else + { + aDialog->MessageBox("Failed to connect!"); + PostQuitMessage(0); + } + GSI_UNUSED(theConnection); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void CVoice2BuddyMFCDlg::DoLogin(const CString& theEmail, const CString& theNickname, const CString& thePassword) +{ + // Begin the availability check + GSIACResult aResult = GSIACWaiting; + GSIStartAvailableCheck(GAME_NAME); + while(aResult == GSIACWaiting) + { + aResult = GSIAvailableCheckThink(); + Sleep(20); + } + + // Initialize the Presence SDK + GPResult result = gpInitialize(&m_GP, PRODUCTID_GMTEST, NAMESPACE_GAMESPY_SHARED, GP_PARTNERID_GAMESPY); + if (result != GP_NO_ERROR) + { + MessageBox("Failed on gpInitialize\r\n"); + PostQuitMessage(0); + } + + gpSetCallback(&m_GP, GP_ERROR, (GPCallback)OnGPError, this); + gpSetCallback(&m_GP, GP_RECV_BUDDY_STATUS, (GPCallback)OnGPBuddyStatus, this); + gpSetCallback(&m_GP, GP_RECV_GAME_INVITE, (GPCallback)OnGPInvite, this); + gpSetCallback(&m_GP, GP_RECV_BUDDY_MESSAGE, (GPCallback)OnGPBuddyMessage,this); + m_Initialized = TRUE; + + // Connect to the presence server (goes to callback even though it's blocking) + gpConnect(&m_GP, theNickname, theEmail, thePassword, GP_NO_FIREWALL, GP_BLOCKING, (GPCallback)OnGPConnect, this); + + // Set the think timer if we connected + // (m_Connected is set from the callback function above) + if (m_Connected) + SetTimer(THINK_TIMER_ID, THINK_TIMER_DELAY, NULL); + + // Set our location + gpSetStatus(&m_GP, GP_ONLINE, V2B_GP_STATUS_IDLE, V2B_GP_LOCATION); +} + +void CVoice2BuddyMFCDlg::OnTimer(UINT nIDEvent) +{ + // Win32 timers can be called concurrently by the OS. + // (you can get a second timer callback before the first finishes!) + // CRITICAL_SECTION will not prevent this as Win32 allows concurrent access + static bool inTimer = false; + if (inTimer) + return; + + // Prevent windows from entering the timer again + inTimer = true; + + if (m_Connected) + gpProcess(&m_GP); + + // Allow windows to enter the timer again + inTimer = false; + + CDialog::OnTimer(nIDEvent); +} + +void CVoice2BuddyMFCDlg::OnDestroy() +{ + CDialog::OnDestroy(); + + // Kill the think timer + KillTimer(THINK_TIMER_ID); + + // Disconnect from GP + if (m_Connected) + gpDisconnect(&m_GP); + + // Destruct GP + if (m_Initialized) + gpDestroy(&m_GP); + + gvCleanup(); +} + +void CVoice2BuddyMFCDlg::OnSelchangeBuddylist() +{ + int aSel = m_BuddyList.GetCurSel(); + if (aSel == -1) + m_VoiceChatButton.EnableWindow(FALSE); + else + m_VoiceChatButton.EnableWindow(TRUE); +} diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFCDlg.h b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFCDlg.h new file mode 100644 index 00000000..fdd4d25b --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFCDlg.h @@ -0,0 +1,74 @@ +// Voice2BuddyMFCDlg.h : header file +// + +#if !defined(AFX_VOICE2BUDDYMFCDLG_H__F4BA3DE6_2C65_4B7D_A1BC_A9C3BAF53DEE__INCLUDED_) +#define AFX_VOICE2BUDDYMFCDLG_H__F4BA3DE6_2C65_4B7D_A1BC_A9C3BAF53DEE__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +///////////////////////////////////////////////////////////////////////////// +// CVoice2BuddyMFCDlg dialog +#include "../../gp/gp.h" +#include "SetupDlg.h" + +class CVoice2BuddyMFCDlg : public CDialog +{ + // Non MFC +public: + GPConnection m_GP; + BOOL m_Initialized; + BOOL m_Connected; + unsigned int m_NNCookie; + GPProfile m_InvitedPlayer; // the last player we invited + GPProfile m_MyProfileId; // the local player's profile id + + VoiceSetupInfo m_SetupInfo; + + void DoLogin(const CString& theEmail, const CString& theNickname, const CString& thePassword); + + +// Construction +public: + CVoice2BuddyMFCDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CVoice2BuddyMFCDlg) + enum { IDD = IDD_VOICE2BUDDYMFC_DIALOG }; + CButton m_SetupButton; + CButton m_VoiceChatButton; + CListBox m_BuddyList; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CVoice2BuddyMFCDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam); + //}}AFX_VIRTUAL + +// Implementation +protected: + HICON m_hIcon; + + // Generated message map functions + //{{AFX_MSG(CVoice2BuddyMFCDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnSysCommand(UINT nID, LPARAM lParam); + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + afx_msg void OnSetup(); + afx_msg void OnVoiceChat(); + afx_msg void OnExit(); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnDestroy(); + afx_msg void OnSelchangeBuddylist(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_VOICE2BUDDYMFCDLG_H__F4BA3DE6_2C65_4B7D_A1BC_A9C3BAF53DEE__INCLUDED_) diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/VoiceSessionDlg.cpp b/code/gamespy/Voice2/Voice2BuddyMFC/VoiceSessionDlg.cpp new file mode 100644 index 00000000..cdbcccc6 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/VoiceSessionDlg.cpp @@ -0,0 +1,397 @@ +// VoiceSessionDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "Voice2BuddyMFC.h" +#include "VoiceSessionDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define THINK_TIMER_ID 100+2 +#define THINK_TIMER_DELAY 20 + + +///////////////////////////////////////////////////////////////////////////// +// CVoiceSessionDlg dialog +struct VoicePacket +{ + GVFrameStamp mFrameStamp; + int mDataLength; + GVByte mBuffer[1024]; // most likely get < 256 bytes +}; +const int gVoicePacketHeaderSize = sizeof(GVFrameStamp)+sizeof(int); + + +CVoiceSessionDlg::CVoiceSessionDlg(CWnd* pParent /*=NULL*/) + : CDialog(CVoiceSessionDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CVoiceSessionDlg) + m_DisplayText = _T(""); + //}}AFX_DATA_INIT +} + + +void CVoiceSessionDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CVoiceSessionDlg) + DDX_Control(pDX, IDC_SPEAKING2, m_RemoteSpeaking); + DDX_Control(pDX, IDC_SPEAKING1, m_LocalSpeaking); + DDX_Text(pDX, IDC_DISPLAYTEXT, m_DisplayText); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CVoiceSessionDlg, CDialog) + //{{AFX_MSG_MAP(CVoiceSessionDlg) + ON_WM_TIMER() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CVoiceSessionDlg message handlers +void CVoiceSessionDlg::OnTimer(UINT nIDEvent) +{ + // Win32 timers can be called concurrently by the OS. + // (you can get a second timer callback before the first finishes!) + // CRITICAL_SECTION will not prevent this as Win32 allows concurrent access + static bool inTimer = false; + if (inTimer) + return; + + // Prevent windows from entering the timer again + inTimer = true; + + // Process voice data + gvThink(); + + // Process NN + NNThink(); + + // Process network traffic + if (m_Socket) + gt2Think(m_Socket); + + // If we're not connected we don't need to do the rest + if (!m_Connection || (gt2GetConnectionState(m_Connection) != GT2Connected)) + { + inTimer = false; + return; + } + + // Show a bitmap if the sources are speaking + static bool wasLocalSpeaking = false; + static bool wasRemoteSpeaking = false; + + bool isLocalSpeaking = (GVTrue == gvIsSourceTalking(m_SetupInfo.m_PlaybackDevice, 0)); + bool isRemoteSpeaking = (GVTrue == gvIsSourceTalking(m_SetupInfo.m_PlaybackDevice, m_RemoteAddr)); + + if (isLocalSpeaking != wasLocalSpeaking) + { + wasLocalSpeaking = isLocalSpeaking; + m_LocalSpeaking.ShowWindow(isLocalSpeaking ? SW_SHOW:SW_HIDE); + } + if (isRemoteSpeaking != wasRemoteSpeaking) + { + wasRemoteSpeaking = isRemoteSpeaking; + m_RemoteSpeaking.ShowWindow(isRemoteSpeaking ? SW_SHOW:SW_HIDE); + } + + // Check for microphone activity + int aBytesAvailable = gvGetAvailableCaptureBytes(m_SetupInfo.m_CaptureDevice); + if (aBytesAvailable > 0) + { + // Voice packet struct, so we don't have to do a buffer copy + // We just record the data directly into the packet + // (the actual packet may be smaller + + VoicePacket aVoicePacket; + memset(&aVoicePacket, 0, sizeof(aVoicePacket)); + aVoicePacket.mDataLength = sizeof(aVoicePacket.mBuffer); + + GVScalar aVolume; // this doesn't need to be sent + + // Read in a voice packet + GVBool gotPacket = gvCapturePacket(m_SetupInfo.m_CaptureDevice, aVoicePacket.mBuffer, &aVoicePacket.mDataLength, &aVoicePacket.mFrameStamp, &aVolume); + if (gotPacket == GVTrue) + { + // Playback for local echo + //gvPlayPacket(m_SetupInfo.m_PlaybackDevice, aVoicePacket.mBuffer, aVoicePacket.mDataLength, 0, aVoicePacket.mFrameStamp); + + // Contruct a message for the buddy + int aTotalPacketSize = gVoicePacketHeaderSize + aVoicePacket.mDataLength; + gt2Send(m_Connection, (GT2Byte*)&aVoicePacket, aTotalPacketSize, GT2False); + } + } + + // Allow new timers to be triggered + inTimer = false; + + CDialog::OnTimer(nIDEvent); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Callbacks for GT2 +void GT2SocketErrorCallback(GT2Socket theSocket) +{ + CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)gt2GetSocketData(theSocket); + aDialog->m_DisplayText = "GT2SocketErrorCallback. Connection Closed"; + aDialog->UpdateData(FALSE); +} + +void GT2ConnectedCallback(GT2Connection theConnection, GT2Result theResult, unsigned char* theMessage, int theLength) +{ + GT2Socket aSocket = gt2GetConnectionSocket(theConnection); + CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)gt2GetSocketData(aSocket); + + // Was there an error? + if (theResult != GT2Success) + { + aDialog->MessageBox("GT2ConnectedCallback returned failure!"); + aDialog->PostMessage(WM_CLOSE, 0, 0); + } + else + { + // Remember our connection + aDialog->m_Connection = theConnection; + aDialog->m_DisplayText = "Session established.\r\n(You may begin speaking)"; + aDialog->UpdateData(FALSE); + + // Start the voice devices + if (aDialog->m_SetupInfo.m_CaptureDevice != NULL) + gvStartDevice(aDialog->m_SetupInfo.m_CaptureDevice, GV_CAPTURE); + if (aDialog->m_SetupInfo.m_PlaybackDevice != NULL) + gvStartDevice(aDialog->m_SetupInfo.m_PlaybackDevice, GV_PLAYBACK); + } + GSI_UNUSED(theMessage); + GSI_UNUSED(theLength); + GSI_UNUSED(theResult); +} + +void GT2ReceivedCallback(GT2Connection theConnection, unsigned char* theBuffer, int theLength, int wasReliable) +{ + // Get the ptr to the dialog + CVoiceSessionDlg* aDlg = (CVoiceSessionDlg*)gt2GetSocketData(gt2GetConnectionSocket(theConnection)); + + // Cast to a voice packet so we can extract data + VoicePacket* aVoicePacket = (VoicePacket*)theBuffer; + + // Sanity check the packet + assert(theLength >= gVoicePacketHeaderSize); + assert(theLength == (gVoicePacketHeaderSize + aVoicePacket->mDataLength)); + + // Play the voice data + GVSource aSource = gt2GetRemoteIP(theConnection); + gvPlayPacket(aDlg->m_SetupInfo.m_PlaybackDevice, aVoicePacket->mBuffer, aVoicePacket->mDataLength, aSource, aVoicePacket->mFrameStamp, 0); + OutputDebugString("Playing voice packet\r\n"); + GSI_UNUSED(wasReliable); + GSI_UNUSED(theLength); +} + +GT2Bool GT2UnrecognizedMessageCallback(GT2Socket theSocket, unsigned int theIp, unsigned short thePort, GT2Byte* theData, int theLength) +{ + static unsigned char aNNHeader[NATNEG_MAGIC_LEN] = + { NN_MAGIC_0, NN_MAGIC_1, NN_MAGIC_2, + NN_MAGIC_3, NN_MAGIC_4, NN_MAGIC_5 }; + + // Bail out if it's too short + if (theLength < NATNEG_MAGIC_LEN) + { + OutputDebugString("\tDiscarded message (too small)\r\n"); + return GT2False; + } + + // Check if it matches the NN Magic bytes + if (0==memcmp(theData, aNNHeader,NATNEG_MAGIC_LEN)) + { + // Hand off to the NN SDK + sockaddr_in anAddr; + anAddr.sin_family = AF_INET; + anAddr.sin_port = htons(thePort); + anAddr.sin_addr.s_addr = theIp; + NNProcessData((char*)theData, theLength, &anAddr); + OutputDebugString("\tProcessed NN message\r\n"); + return GT2True; + } + + // Not handled by us + OutputDebugString("\tDiscarded message (not recognized)\r\n"); + GSI_UNUSED(theSocket); + return GT2False; +} + +void GT2ClosedCallback(GT2Connection theConnection, GT2CloseReason theReason) +{ + GT2Socket aSocket = gt2GetConnectionSocket(theConnection); + CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)gt2GetSocketData(aSocket); + + aDialog->m_DisplayText = "Connection closed. (GT2ClosedCallback)"; + aDialog->UpdateData(FALSE); + GSI_UNUSED(theReason); +} + +void GT2ConnectAttemptCallback(GT2Socket theSocket, GT2Connection theConnection, unsigned int theIp, unsigned short thePort, int theLatency, GT2Byte * theMessage, int theLength) +{ + CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)gt2GetSocketData(theSocket); + + // Set the callbacks so we can receive data + GT2ConnectionCallbacks aCallbackList; + aCallbackList.received = GT2ReceivedCallback; + aCallbackList.connected = GT2ConnectedCallback; // we're connected + aCallbackList.closed = GT2ClosedCallback; + + // Accept the connection + aDialog->m_Connection = theConnection; + gt2Accept(theConnection, &aCallbackList); + + // Store off the remote addr + aDialog->m_RemoteAddr = gt2GetRemoteIP(theConnection); + + // Start the voice devices + if (aDialog->m_SetupInfo.m_CaptureDevice != NULL) + gvStartDevice(aDialog->m_SetupInfo.m_CaptureDevice, GV_CAPTURE); + if (aDialog->m_SetupInfo.m_PlaybackDevice != NULL) + gvStartDevice(aDialog->m_SetupInfo.m_PlaybackDevice, GV_PLAYBACK); + + aDialog->m_DisplayText = "Session established.\r\n(You may begin speaking)"; + aDialog->UpdateData(FALSE); + GSI_UNUSED(theMessage); + GSI_UNUSED(theLength); + GSI_UNUSED(theIp); + GSI_UNUSED(thePort); + GSI_UNUSED(theLatency); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Callbacks for the NN SDK +void NNProgressCallback(NegotiateState theState, void* theParam) +{ + CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)theParam; + aDialog->m_DisplayText.Format("NNProgressCallback: %d\r\n", theState); + aDialog->UpdateData(FALSE); +} + +void NNCompletedCallback(NegotiateResult theResult, SOCKET theSocket, sockaddr_in* theAddr, void* theParam) +{ + CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)theParam; + + // Check the result, are we connected at the socket layer? + if (theResult == nr_success) + { + // Convert sockaddr_in to a string + CString aAddrString; + aAddrString.Format("%s:%d", inet_ntoa(theAddr->sin_addr), ntohs(theAddr->sin_port)); + aDialog->m_DisplayText = "NN Successful"; + + // If hosting, listen for the client... + if (aDialog->m_IsHost) + { + gt2Listen(aDialog->m_Socket, GT2ConnectAttemptCallback); + aDialog->m_DisplayText = "Listening for GT2 connection"; + } + // ...otherwise connect to the host + else + { + // Store off the remote addr + aDialog->m_RemoteAddr = theAddr->sin_addr.s_addr; + + // Set our gt2 callbacks, so we can receive messages + GT2ConnectionCallbacks aCallbackList; + aCallbackList.connected = GT2ConnectedCallback; // we're connected + aCallbackList.received = GT2ReceivedCallback; // we've received data + aCallbackList.closed = GT2ClosedCallback; + + int aTimeout = 0; + GT2Result aResult = gt2Connect(aDialog->m_Socket, &aDialog->m_Connection, aAddrString, NULL, 0, aTimeout, &aCallbackList, GT2False); + if (aResult != GT2Success) + { + // Error, bail out + aDialog->m_DisplayText = "Failed on gt2Connect"; + //aDialog->PostMessage(WM_CLOSE, 0, 0); + } + aDialog->m_DisplayText = "Connecting with GT2"; + } + } + else + { + aDialog->m_DisplayText = "Failed to negotiate a connection."; + } + + // Draw the new display text + aDialog->UpdateData(FALSE); + GSI_UNUSED(theSocket); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// MFC Event handlers +BOOL CVoiceSessionDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Init some variables + m_Socket = NULL; + m_Connection = NULL; + + // Set the gt2Callbacks + // Create our gt2 socket (we'll need it for NN) + // We use NULL for the local addr because we don't care which + // port we're bound to. We could specify one if we wanted by + // using ":5000" as the addr, for example + GT2Result aResult = gt2CreateSocket(&m_Socket, NULL, 0, 0, GT2SocketErrorCallback); + if (aResult != GT2Success) + { + // We're hosed, bail out by closing the dialog + MessageBox("Failed on gt2CreateSocket"); + PostMessage(WM_CLOSE, 0, 0); + return TRUE; + } + + // Set the socket data (the user param) to our dialog + gt2SetSocketData(m_Socket, this); + gt2SetUnrecognizedMessageCallback(m_Socket, GT2UnrecognizedMessageCallback); + + // Begin nat negotiation (use the gt2 socket) + // m_NNCookie and m_IsHost are set before the dialog is run + SOCKET aWinSocket = gt2GetSocketSOCKET(m_Socket); + NNBeginNegotiationWithSocket(aWinSocket, m_NNCookie, m_IsHost, NNProgressCallback, NNCompletedCallback, this); + + m_DisplayText = "Establishing connection..."; + UpdateData(FALSE); + + // Set the think timer + SetTimer(THINK_TIMER_ID, THINK_TIMER_DELAY, NULL); + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + +BOOL CVoiceSessionDlg::DestroyWindow() +{ + // Clean up NN SDK + NNFreeNegotiateList(); + + // Close the gt2 socket (will close the connection if connected) + if (m_Socket) + gt2CloseSocket(m_Socket); + + // Stop the voice devices + if (m_SetupInfo.m_CaptureDevice != NULL) + gvStopDevice(m_SetupInfo.m_CaptureDevice, GV_CAPTURE); + if (m_SetupInfo.m_PlaybackDevice != NULL) + gvStopDevice(m_SetupInfo.m_PlaybackDevice, GV_PLAYBACK); + + // Kill the timer + KillTimer(THINK_TIMER_ID); + + + return CDialog::DestroyWindow(); +} diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/VoiceSessionDlg.h b/code/gamespy/Voice2/Voice2BuddyMFC/VoiceSessionDlg.h new file mode 100644 index 00000000..86751671 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/VoiceSessionDlg.h @@ -0,0 +1,66 @@ +#if !defined(AFX_VOICESESSIONDLG_H__4D78C45C_9CB3_4F4B_81DF_8BF65316598E__INCLUDED_) +#define AFX_VOICESESSIONDLG_H__4D78C45C_9CB3_4F4B_81DF_8BF65316598E__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// VoiceSessionDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CVoiceSessionDlg dialog +#include "SetupDlg.h" +#include "../../gt2/gt2.h" +#include "../../natneg/natneg.h" + +class CVoiceSessionDlg : public CDialog +{ +public: + int m_NNCookie; // used to connect the two buddies + BOOL m_IsHost; // TRUE = I invited the buddy, FALSE = Buddy invited me + + VoiceSetupInfo m_SetupInfo; // device info + + GT2Socket m_Socket; + GT2Connection m_Connection; + unsigned int m_RemoteAddr; // IP addr of the buddy (used for identification) + + // MFC STUFF BELOW + +// Construction +public: + CVoiceSessionDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CVoiceSessionDlg) + enum { IDD = IDD_SESSION }; + CStatic m_RemoteSpeaking; + CStatic m_LocalSpeaking; + CString m_DisplayText; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CVoiceSessionDlg) + public: + virtual BOOL DestroyWindow(); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CVoiceSessionDlg) + afx_msg void OnTimer(UINT nIDEvent); + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_VOICESESSIONDLG_H__4D78C45C_9CB3_4F4B_81DF_8BF65316598E__INCLUDED_) diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/res/Voice2BuddyMFC.ico b/code/gamespy/Voice2/Voice2BuddyMFC/res/Voice2BuddyMFC.ico new file mode 100644 index 0000000000000000000000000000000000000000..7eef0bcbe6580a6f464d688906172c2d9de44262 GIT binary patch literal 1078 zcmc&zF>b>!3}jLb9s)T}@Kod(893@u8ajANzT`op9^o+)S?=nU(FD@%0s)Sg^oyC8{H z9myetc;MEP)59v(LMa~xK8Yu^jIR*H22uCFiq5%C{s7(PJi>o15i^bmX4(vPxWAio z9ryY#AU_jfnd047-@`)XzL?%iS$gQyFP{44kS9X)fN{{QoL~hO-&=q&20Zr*cxFAt PkaNE{wR~2C$NfnjhSXWT literal 0 HcmV?d00001 diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/res/Voice2BuddyMFC.rc2 b/code/gamespy/Voice2/Voice2BuddyMFC/res/Voice2BuddyMFC.rc2 new file mode 100644 index 00000000..de338e06 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/res/Voice2BuddyMFC.rc2 @@ -0,0 +1,13 @@ +// +// VOICE2BUDDYMFC.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/res/gamespyl.bmp b/code/gamespy/Voice2/Voice2BuddyMFC/res/gamespyl.bmp new file mode 100644 index 0000000000000000000000000000000000000000..01142a32b949be9f8199ca9e796c5e6c0a0a6549 GIT binary patch literal 67450 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 literal 0 HcmV?d00001 diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/res/microsoft-speaker.bmp b/code/gamespy/Voice2/Voice2BuddyMFC/res/microsoft-speaker.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b98071f3ca296210e80aa599661276f9a76e02c2 GIT binary patch literal 3126 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 + GVIEncodedFrameSize = info->m_encodedFrameSize; + + // extra info + GVIBytesPerFrame = (GVISamplesPerFrame * (int)GV_BYTES_PER_SAMPLE); + + // frames needs to be initialized with codec info + gviFramesStartup(); +} + +void gviSetSampleRate(GVRate sampleRate) +{ + //Save the sample rate. + GVISampleRate = sampleRate; + GVIBytesPerSecond = GVISampleRate * GV_BYTES_PER_SAMPLE; +} + +GVRate gviGetSampleRate(void) +{ + return GVISampleRate; +} + + +GVBool gviNewDecoder(GVDecoderData * data) +{ + if(!GVICodecInfo.m_newDecoderCallback) + { + *data = NULL; + return GVTrue; + } + + return GVICodecInfo.m_newDecoderCallback(data); +} + +void gviFreeDecoder(GVDecoderData data) +{ + if(GVICodecInfo.m_freeDecoderCallback) + GVICodecInfo.m_freeDecoderCallback(data); +} + +void gviEncode(GVByte * out, const GVSample * in) +{ + GVICodecInfo.m_encodeCallback(out, in); +} + +void gviDecodeAdd(GVSample * out, const GVByte * in, GVDecoderData data) +{ + GVICodecInfo.m_decodeAddCallback(out, in, data); +} + +void gviDecodeSet(GVSample * out, const GVByte * in, GVDecoderData data) +{ + if(GVICodecInfo.m_decodeSetCallback) + { + GVICodecInfo.m_decodeSetCallback(out, in, data); + } + else + { + memset(out, 0, GVIBytesPerFrame); + GVICodecInfo.m_decodeAddCallback(out, in, data); + } +} + +void gviResetEncoder(void) +{ +#if !defined(GV_NO_DEFAULT_CODEC) && !defined(_PS2) && !defined(_PSP) + + //If we are using the RawCodec, we have never set up Speex. + if (!GVIRawCodec) + gviSpeexResetEncoder(); +#endif +} diff --git a/code/gamespy/Voice2/gvCodec.h b/code/gamespy/Voice2/gvCodec.h new file mode 100644 index 00000000..3e9f446a --- /dev/null +++ b/code/gamespy/Voice2/gvCodec.h @@ -0,0 +1,47 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_CODEC_H_ +#define _GV_CODEC_H_ + +#include "gvMain.h" + +/************ +** GLOBALS ** +************/ +extern int GVISamplesPerFrame; +extern int GVIBytesPerFrame; +extern int GVIEncodedFrameSize; +extern GVRate GVISampleRate; //In samples per second. +extern int GVIBytesPerSecond; + +/************** +** FUNCTIONS ** +**************/ +void gviCodecsInitialize(void); +void gviCodecsCleanup(void); + +GVBool gviSetCodec(GVCodec codec); +void gviSetCustomCodec(GVCustomCodecInfo * info); + +void gviSetSampleRate(GVRate sampleRate); +GVRate gviGetSampleRate(void); + +GVBool gviNewDecoder(GVDecoderData * data); +void gviFreeDecoder(GVDecoderData data); + +void gviEncode(GVByte * out, const GVSample * in); +void gviDecodeAdd(GVSample * out, const GVByte * in, GVDecoderData data); +void gviDecodeSet(GVSample * out, const GVByte * in, GVDecoderData data); + +void gviResetEncoder(void); + +#endif diff --git a/code/gamespy/Voice2/gvCustomDevice.c b/code/gamespy/Voice2/gvCustomDevice.c new file mode 100644 index 00000000..b3b869c0 --- /dev/null +++ b/code/gamespy/Voice2/gvCustomDevice.c @@ -0,0 +1,385 @@ +#include "gvCustomDevice.h" +#include "gvSource.h" +#include "gvCodec.h" +#include "gvUtil.h" + +#define GVI_DEFAULT_THRESHOLD ((GVScalar)0.0) + +#if defined(_WIN32) +static GVDeviceID GVICustomDeviceID = {0}; +#else +static GVDeviceID GVICustomDeviceID = 0; +#endif + +typedef struct +{ + GVBool m_captureStarted; + GVScalar m_captureThreshold; + GVFrameStamp m_captureClock; + GVFrameStamp m_captureLastCrossedThresholdTime; + GVScalar m_captureVolume; + + GVBool m_playbackStarted; + GVISourceList m_playbackSources; + GVFrameStamp m_playbackClock; + GVScalar m_playbackVolume; +} GVICustomData; + +static void gviCustomFreeDevice(GVIDevice * device) +{ + GVICustomData * data = (GVICustomData *)device->m_data; + + if(device->m_types & GV_PLAYBACK) + { + data->m_playbackStarted = GVFalse; + gviFreeSourceList(data->m_playbackSources); + } + if(device->m_types & GV_CAPTURE) + { + data->m_captureStarted = GVFalse; + } + + gviFreeDevice(device); +} + +static GVBool gviCustomStartDevice(GVIDevice * device, GVDeviceType type) +{ + GVICustomData * data = (GVICustomData *)device->m_data; + + if(type & GV_PLAYBACK) + { + data->m_playbackStarted = GVTrue; + data->m_playbackClock = 0; + } + if(type & GV_CAPTURE) + { + data->m_captureStarted = GVTrue; + } + return GVTrue; +} + +static void gviCustomStopDevice(GVIDevice * device, GVDeviceType type) +{ + GVICustomData * data = (GVICustomData *)device->m_data; + + if(type & GV_PLAYBACK) + { + data->m_playbackStarted = GVFalse; + gviClearSourceList(data->m_playbackSources); + } + if(type & GV_CAPTURE) + { + data->m_captureStarted = GVFalse; + data->m_captureClock++; + } +} + +static GVBool gviCustomIsDeviceStarted(GVIDevice * device, GVDeviceType type) +{ + GVICustomData * data = (GVICustomData *)device->m_data; + + if(type == GV_PLAYBACK) + return data->m_playbackStarted; + else if(type == GV_CAPTURE) + return data->m_captureStarted; + return (data->m_playbackStarted && data->m_captureStarted); +} + +static void gviCustomSetDeviceVolume(GVIDevice * device, GVDeviceType type, GVScalar volume) +{ + GVICustomData * data = (GVICustomData *)device->m_data; + + if(type & GV_PLAYBACK) + data->m_playbackVolume = volume; + if(type & GV_CAPTURE) + data->m_captureVolume = volume; +} + +static GVScalar gviCustomGetDeviceVolume(GVIDevice * device, GVDeviceType type) +{ + GVICustomData * data = (GVICustomData *)device->m_data; + + if(type & GV_PLAYBACK) + return data->m_playbackVolume; + return data->m_captureVolume; +} + +static void gviCustomSetCaptureThreshold(GVIDevice * device, GVScalar threshold) +{ + GVICustomData * data = (GVICustomData *)device->m_data; + + data->m_captureThreshold = threshold; +} + +static GVScalar gviCustomGetCaptureThreshold(GVIDevice * device) +{ + GVICustomData * data = (GVICustomData *)device->m_data; + + return data->m_captureThreshold; +} + +static int gviCustomGetAvailableCaptureBytes(GVDevice device) +{ + // not supported with custom devices + assert(0); + GSI_UNUSED(device); + return 0; +} + +static GVBool gviCustomCapturePacket(GVDevice device, GVByte * packet, int * len, GVFrameStamp * frameStamp, GVScalar * volume) +{ + // not supported with custom devices + assert(0); + GSI_UNUSED(device); + GSI_UNUSED(packet); + GSI_UNUSED(len); + GSI_UNUSED(frameStamp); + GSI_UNUSED(volume); + return GVFalse; +} + +static void gviCustomPlayPacket(GVIDevice * device, const GVByte * packet, int len, GVSource source, GVFrameStamp frameStamp, GVBool mute) +{ + GVICustomData * data = (GVICustomData *)device->m_data; + + // don't do anythying if we're not playing + if(!data->m_playbackStarted) + return; + + gviAddPacketToSourceList(data->m_playbackSources, packet, len, source, frameStamp, mute, data->m_playbackClock); +} + +static GVBool gviCustomIsSourceTalking(GVDevice device, GVSource source) +{ + GVICustomData * data = (GVICustomData *)device->m_data; + + // don't do anythying if we're not playing + if(!data->m_playbackStarted) + return GVFalse; + + return gviIsSourceTalking(data->m_playbackSources, source); +} + +static int gviCustomListTalkingSources(GVDevice device, GVSource sources[], int maxSources) +{ + GVICustomData * data = (GVICustomData *)device->m_data; + + // don't do anythying if we're not playing + if(!data->m_playbackStarted) + return GVFalse; + + return gviListTalkingSources(data->m_playbackSources, sources, maxSources); +} + +GVDevice gviCustomNewDevice(GVDeviceType type) +{ + GVIDevice * device; + GVICustomData * data; + + // create a new device + device = gviNewDevice(GVICustomDeviceID, GVHardwareCustom, type, sizeof(GVICustomData)); + if(!device) + return NULL; + + // get a pointer to the data + data = (GVICustomData *)device->m_data; + + // store the pointers + device->m_methods.m_freeDevice = gviCustomFreeDevice; + device->m_methods.m_startDevice = gviCustomStartDevice; + device->m_methods.m_stopDevice = gviCustomStopDevice; + device->m_methods.m_isDeviceStarted = gviCustomIsDeviceStarted; + device->m_methods.m_setDeviceVolume = gviCustomSetDeviceVolume; + device->m_methods.m_getDeviceVolume = gviCustomGetDeviceVolume; + device->m_methods.m_setCaptureThreshold = gviCustomSetCaptureThreshold; + device->m_methods.m_getCaptureThreshold = gviCustomGetCaptureThreshold; + device->m_methods.m_getAvailableCaptureBytes = gviCustomGetAvailableCaptureBytes; + device->m_methods.m_capturePacket = gviCustomCapturePacket; + device->m_methods.m_playPacket = gviCustomPlayPacket; + device->m_methods.m_isSourceTalking = gviCustomIsSourceTalking; + device->m_methods.m_listTalkingSources = gviCustomListTalkingSources; + + // init the data + if(type & GV_PLAYBACK) + { + // create the array of sources + data->m_playbackSources = gviNewSourceList(); + if(!data->m_playbackSources) + return GVFalse; + + // setup the struct + data->m_playbackStarted = GVFalse; + data->m_playbackClock = 0; + data->m_playbackVolume = 1.0; + } + if(type & GV_CAPTURE) + { + data->m_captureStarted = GVFalse; + data->m_captureThreshold = GVI_DEFAULT_THRESHOLD; + data->m_captureClock = 0; + data->m_captureLastCrossedThresholdTime = (GVFrameStamp)(data->m_captureClock - GVI_HOLD_THRESHOLD_FRAMES - 1); + data->m_captureVolume = 1.0; + } + + return device; +} + +GVBool gviGetCustomPlaybackAudio(GVIDevice * device, GVSample * audio, int numSamples) +{ + GVICustomData * data = (GVICustomData *)device->m_data; + GVBool wroteToBuffer; + int numFrames; + int i; + GVBool result = GVFalse; + + // don't do anythying if we're not playing + if(!data->m_playbackStarted) + return GVFalse; + + // the len must be a multiple of the frame size + assert(!(numSamples % GVISamplesPerFrame)); + + // calc the number of frames + numFrames = (numSamples / GVISamplesPerFrame); + + // fill it + wroteToBuffer = gviWriteSourcesToBuffer(data->m_playbackSources, data->m_playbackClock, + audio, numFrames); + + // check if anything was written + if(!wroteToBuffer) + { + // no, so clear it + memset(audio, 0, numSamples * GV_BYTES_PER_SAMPLE); + } + else + { + // check if we need to adjust the volume + if(data->m_playbackVolume != 1.0) + { + for(i = 0 ; i < numSamples ; i++) + audio[i] = (GVSample)(audio[i] * data->m_playbackVolume); + } + // set the output flag to true because samples were decoded this pass + result = GVTrue; + } + + // filter + if(device->m_playbackFilterCallback) + { + for(i = 0 ; i < numFrames ; i++) + device->m_playbackFilterCallback(device, audio + (GVISamplesPerFrame * i), (GVFrameStamp)(data->m_playbackClock + i)); + } + + // update the clock + // Expanded to remove warnings in VS2K5 + data->m_playbackClock = data->m_playbackClock + (GVFrameStamp)numFrames; + + return result; +} + +GVBool gviSetCustomCaptureAudio(GVDevice device, const GVSample * audio, int numSamples, + GVByte * packet, int * packetLen, GVFrameStamp * frameStamp, GVScalar * volume) +{ + GVICustomData * data = (GVICustomData *)device->m_data; + int numBytes; + GVBool overThreshold = GVFalse; + int numFrames; + int i; + int len; + + // store the len + len = *packetLen; + + // clear the len and volume + *packetLen = 0; + if(volume) + *volume = 0; + + // don't do anything if we're not capturing + if(!data->m_captureStarted) + return GVFalse; + + // the len must be a multiple of the frame size + assert(!(numSamples % GVISamplesPerFrame)); + + // set the frameStamp + *frameStamp = data->m_captureClock; + + // figure out the number of frames + numFrames = (numSamples / GVISamplesPerFrame); + + // if audio is NULL, this is simply telling us to advance the clock + if(audio) + { + // get the number of new bytes + numBytes = (int)(numSamples * GV_BYTES_PER_SAMPLE); + + // make sure they have enough space in the packet buffer + assert(len >= numBytes); + if(len < numBytes) + return GVFalse; + + // get the volume if requested + if(volume) + *volume = gviGetSamplesVolume(audio, numSamples); + + // check against the threshold + if(volume) + { + // we already got the volume, so use that to check + overThreshold = (*volume >= data->m_captureThreshold); + } + else + { + // we didn't get a volume, so check the samples directly + overThreshold = gviIsOverThreshold(audio, numSamples, data->m_captureThreshold); + } + + // did the audio cross the threshold? + if(overThreshold) + { + // update the time at which we crossed + data->m_captureLastCrossedThresholdTime = data->m_captureClock; + } + else + { + // check if we are still within the hold time + overThreshold = ((GVFrameStamp)(data->m_captureClock - data->m_captureLastCrossedThresholdTime) < GVI_HOLD_THRESHOLD_FRAMES); + } + + if(overThreshold) + { + // check if we need to adjust the volume + if(data->m_captureVolume != 1.0) + { + for(i = 0 ; i < numSamples ; i++) + ((GVSample *)audio)[i] = (GVSample)(audio[i] * data->m_captureVolume); + } + + // handle the data one frame at a time + for(i = 0 ; i < numFrames ; i++) + { + // filter + if(device->m_captureFilterCallback) + device->m_captureFilterCallback(device, (GVSample *)audio + (i * GVISamplesPerFrame), (GVFrameStamp)(data->m_captureClock + i)); + + // encode the buffer into the packet + gviEncode(packet + (i * GVIEncodedFrameSize), audio + (i * GVISamplesPerFrame)); + } + } + + // set the len + *packetLen = (numFrames * GVIEncodedFrameSize); + } + + // increment the clock + // Expanded to remove warnings in VS2K5 + data->m_captureClock = data->m_captureClock + (GVFrameStamp)numFrames; + + // return false if we didn't get a packet + if(!overThreshold) + return GVFalse; + + return GVTrue; +} diff --git a/code/gamespy/Voice2/gvCustomDevice.h b/code/gamespy/Voice2/gvCustomDevice.h new file mode 100644 index 00000000..d0818844 --- /dev/null +++ b/code/gamespy/Voice2/gvCustomDevice.h @@ -0,0 +1,25 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_CUSTOM_DEVICE_H_ +#define _GV_CUSTOM_DEVICE_H_ + +#include "gvMain.h" +#include "gvDevice.h" + +GVDevice gviCustomNewDevice(GVDeviceType type); + +GVBool gviGetCustomPlaybackAudio(GVIDevice * device, GVSample * audio, int numSamples); + +GVBool gviSetCustomCaptureAudio(GVDevice device, const GVSample * audio, int numSamples, + GVByte * packet, int * packetLen, GVFrameStamp * frameStamp, GVScalar * volume); + +#endif diff --git a/code/gamespy/Voice2/gvDevice.c b/code/gamespy/Voice2/gvDevice.c new file mode 100644 index 00000000..792f26de --- /dev/null +++ b/code/gamespy/Voice2/gvDevice.c @@ -0,0 +1,114 @@ +#include "gvDevice.h" + +/*********** +** DEVICE ** +***********/ +GVIDevice * gviNewDevice(GVDeviceID deviceID, GVHardwareType hardwareType, GVDeviceType types, int dataSize) +{ + GVIDevice * device; + + device = (GVIDevice *)gsimalloc(sizeof(GVIDevice)); + if(!device) + return NULL; + + memset(device, 0, sizeof(GVIDevice)); + memcpy(&device->m_deviceID, &deviceID, sizeof(GVDeviceID)); + device->m_hardwareType = hardwareType; + device->m_types = types; + device->m_data = gsimalloc((unsigned int)dataSize); + if(!device->m_data) + { + gsifree(device); + return NULL; + } + memset(device->m_data, 0, (unsigned int)dataSize); + + return device; +} + +void gviFreeDevice(GVIDevice * device) +{ + gsifree(device->m_data); + gsifree(device); +} + +void gviDeviceUnplugged(GVIDevice * device) +{ + // if they don't have a callback, we can't free the device without them knowing + if(!device->m_unpluggedCallback) + return; + + // let them know it was unplugged... + device->m_unpluggedCallback(device); + + // ...then free the device + device->m_methods.m_freeDevice(device); +} + +/**************** +** DEVICE LIST ** +****************/ +static int gviFindDeviceIndex(GVIDeviceList devices, GVIDevice * device) +{ + int len; + int i; + + assert(devices); + + len = ArrayLength(devices); + for(i = 0 ; i < len ; i++) + { + if(device == gviGetDevice(devices, i)) + return i; + } + + return -1; +} + +GVIDeviceList gviNewDeviceList(ArrayElementFreeFn elemFreeFn) +{ + return ArrayNew(sizeof(GVIDevice *), 2, elemFreeFn); +} + +void gviFreeDeviceList(GVIDeviceList devices) +{ + assert(devices); + + ArrayFree(devices); +} + +void gviAppendDeviceToList(GVIDeviceList devices, GVIDevice * device) +{ + assert(devices); + + ArrayAppend(devices, &device); +} + +void gviDeleteDeviceFromList(GVIDeviceList devices, GVIDevice * device) +{ + int index; + + assert(devices); + + // find the device + index = gviFindDeviceIndex(devices, device); + if(index == -1) + return; + + // delete it from the array + ArrayDeleteAt(devices, index); +} + +int gviGetNumDevices(GVIDeviceList devices) +{ + assert(devices); + + return ArrayLength(devices); +} + +GVIDevice * gviGetDevice(GVIDeviceList devices, int index) +{ + assert(devices); + + return *(GVIDevice **)ArrayNth(devices, index); +} diff --git a/code/gamespy/Voice2/gvDevice.h b/code/gamespy/Voice2/gvDevice.h new file mode 100644 index 00000000..9832a203 --- /dev/null +++ b/code/gamespy/Voice2/gvDevice.h @@ -0,0 +1,90 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_DEVICE_H_ +#define _GV_DEVICE_H_ + +#include "gvMain.h" + +// amount of time to keep capturing even after voice drops below the capture threshold +// makes sure that speech that trails off, or slightly dips in volume, is still captured +#define GVI_HOLD_THRESHOLD_FRAMES 20 + +// buffer sizes for capture and playback +#define GVI_PLAYBACK_BUFFER_MILLISECONDS 200 +#define GVI_CAPTURE_BUFFER_MILLISECONDS 1000 + +/*********** +** DEVICE ** +***********/ +typedef struct +{ + void (* m_freeDevice)(GVDevice device); + + GVBool (* m_startDevice)(GVDevice device, GVDeviceType type); + void (* m_stopDevice)(GVDevice device, GVDeviceType type); + GVBool (* m_isDeviceStarted)(GVDevice device, GVDeviceType type); + + void (* m_setDeviceVolume)(GVDevice device, GVDeviceType type, GVScalar volume); + GVScalar (* m_getDeviceVolume)(GVDevice device, GVDeviceType type); + + void (* m_setCaptureThreshold)(GVDevice device, GVScalar threshold); + GVScalar (* m_getCaptureThreshold)(GVDevice device); + + GVBool (*m_capturePacket)(GVDevice device, GVByte * packet, int * len, GVFrameStamp * frameStamp, GVScalar * volume); + int (* m_getAvailableCaptureBytes)(GVDevice device); + + void (* m_playPacket)(GVDevice device, const GVByte * packet, int len, GVSource source, GVFrameStamp frameStamp, GVBool mute); + + GVBool (* m_isSourceTalking)(GVDevice device, GVSource source); + int (* m_listTalkingSources)(GVDevice device, GVSource sources[], int maxSources); + + int (* m_getNumChannels)(GVDevice device, GVDeviceType type); + void (* m_getChannelName)(GVDevice device, GVDeviceType type, int channel, gsi_char name[GV_CHANNEL_NAME_LEN]); + + void (* m_setChannel)(GVDevice device, GVDeviceType type, int channel); + int (* m_getChannel)(GVDevice device, GVDeviceType type); +} GVIDeviceMethods; + +typedef struct GVIDevice +{ + GVDeviceID m_deviceID; + GVHardwareType m_hardwareType; + GVDeviceType m_types; + GVIDeviceMethods m_methods; + gvUnpluggedCallback m_unpluggedCallback; + gvFilterCallback m_captureFilterCallback; + gvFilterCallback m_playbackFilterCallback; + void * m_data; + GVCaptureMode m_captureMode; + GVScalar m_savedCaptureThreshold; +} GVIDevice; + +GVIDevice * gviNewDevice(GVDeviceID deviceID, GVHardwareType hardwareType, GVDeviceType types, int dataSize); +void gviFreeDevice(GVIDevice * device); + +void gviDeviceUnplugged(GVIDevice * device); + +/**************** +** DEVICE LIST ** +****************/ +typedef DArray GVIDeviceList; + +GVIDeviceList gviNewDeviceList(ArrayElementFreeFn elemFreeFn); +void gviFreeDeviceList(GVIDeviceList devices); + +void gviAppendDeviceToList(GVIDeviceList devices, GVIDevice * device); +void gviDeleteDeviceFromList(GVIDeviceList devices, GVIDevice * device); + +int gviGetNumDevices(GVIDeviceList devices); +GVIDevice * gviGetDevice(GVIDeviceList devices, int index); + +#endif diff --git a/code/gamespy/Voice2/gvDirectSound.c b/code/gamespy/Voice2/gvDirectSound.c new file mode 100644 index 00000000..86ca0b2b --- /dev/null +++ b/code/gamespy/Voice2/gvDirectSound.c @@ -0,0 +1,1067 @@ +#include +#include +#include "gvDirectSound.h" +#include "gvDevice.h" +#include "gvCodec.h" +#include "gvSource.h" +#include "gvUtil.h" +#pragma warning(disable:4201) +#include +#include +#if (DIRECTSOUND_VERSION == 0x0800) +#include +#endif + +#if !defined(_WIN32) +#error This file should only be used with Windows +#endif + +/************ +** DEFINES ** +************/ +// GUID utility macros +#define GVI_GUID_COPY(dest, src) memcpy((dest), (src), sizeof(GUID)) + +// these were copied from dsound.h +const GUID GVDefaultPlaybackDeviceID = {0xdef00002, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03}; +const GUID GVDefaultCaptureDeviceID = {0xdef00003, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03}; + +/********** +** TYPES ** +**********/ +typedef struct +{ + GVBool m_playing; + LPDIRECTSOUND m_playback; + LPDIRECTSOUNDBUFFER m_playbackBuffer; + DWORD m_playbackBufferSize; + DWORD m_playbackBufferHalfSize; + DWORD m_playbackBufferPosition; + GVFrameStamp m_playbackClock; + GVISourceList m_playbackSources; + gsi_time m_playbackLastThink; + + GVBool m_capturing; + LPDIRECTSOUNDCAPTURE m_capture; + LPDIRECTSOUNDCAPTUREBUFFER m_captureBuffer; + DWORD m_captureBufferSize; + DWORD m_captureBufferPosition; + GVScalar m_captureVolume; + GVFrameStamp m_captureClock; + GVScalar m_captureThreshold; + GVFrameStamp m_captureLastCrossedThresholdTime; +} GVIHardwareData; + +typedef struct +{ + GVDeviceInfo * m_devices; + int m_len; + int m_num; + GVBool m_enumeratingCapture; +} GVIEnumDevicesInfo; + +/************ +** GLOBALS ** +************/ +static HWND GVIHwnd; +static GVIDeviceList GVIDevices; +static WAVEFORMATEX GVIWaveFormat; +static GVBool GVICleanupCOM; + +/************** +** FUNCTIONS ** +**************/ +static void gviFreeArrayDevice(void * elem) +{ + GVIDevice * device = *(GVIDevice **)elem; + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + if(device->m_types == GV_PLAYBACK) + { + if(data->m_playbackSources) + gviFreeSourceList(data->m_playbackSources); + if(data->m_playbackBuffer) + IDirectSoundBuffer_Release(data->m_playbackBuffer); + if(data->m_playback) + IDirectSound_Release(data->m_playback); + } + else + { + if(data->m_captureBuffer) + { + IDirectSoundCaptureBuffer_Stop(data->m_captureBuffer); + IDirectSoundCaptureBuffer_Release(data->m_captureBuffer); + } + if(data->m_capture) + IDirectSoundCapture_Release(data->m_capture); + } + + gviFreeDevice(device); +} + +GVBool gviHardwareStartup(HWND hWnd) +{ + HRESULT result; + + // check the hwnd + if(!hWnd) + { + hWnd = GetForegroundWindow(); + if(!hWnd) + hWnd = GetDesktopWindow(); + } + + // store it + GVIHwnd = hWnd; + + // create the devices array + GVIDevices = gviNewDeviceList(gviFreeArrayDevice); + if(!GVIDevices) + return GVFalse; + + // init COM + assert(GVICleanupCOM == GVFalse); + result = CoInitialize(NULL); + if(SUCCEEDED(result)) + GVICleanupCOM = GVTrue; + + return GVTrue; +} + +void gviHardwareCleanup(void) +{ + // cleanup the devices + if(GVIDevices) + { + gviFreeDeviceList(GVIDevices); + GVIDevices = NULL; + } + + // cleanup COM + if(GVICleanupCOM == GVTrue) + { + CoUninitialize(); + GVICleanupCOM = GVFalse; + } +} + +static GVBool gviPlaybackDeviceThink(GVIDevice * device) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + HRESULT result; + DWORD lockPosition; + LPVOID audioPtr1, audioPtr2; + DWORD audioLen1, audioLen2; + GVBool wroteToBuffer; + DWORD newBytes; + DWORD rawPlayCursor; + DWORD playCursor; + DWORD writeCursor; + gsi_time now; + int diff; + int numFrames; + int i; + + // don't do anythying if we're not playing + if(!data->m_playing) + return GVTrue; + + // get the current position + result = IDirectSoundBuffer_GetCurrentPosition(data->m_playbackBuffer, &rawPlayCursor, &writeCursor); + if(FAILED(result)) + return GVFalse; + + // we only want to deal with whole frames + playCursor = (DWORD)gviRoundDownToNearestMultiple((int)rawPlayCursor, GVIBytesPerFrame); + + // get the number of new bytes + newBytes = (((playCursor + data->m_playbackBufferSize) - data->m_playbackBufferPosition) % data->m_playbackBufferSize); + + // before we store the new position, save the old one + lockPosition = data->m_playbackBufferPosition; + + // store the new position + data->m_playbackBufferPosition = playCursor; + + // figure out how long it has been since out last think + now = current_time(); + diff = (int)(now - data->m_playbackLastThink); + data->m_playbackLastThink = now; + + // adjust the time based on the number of new samples we know about + diff -= gviDivideByBytesPerMillisecond(newBytes); + + // adjust again based on the raw play cursor + // because newBytes is based on an adjusted play cursor + diff -= (rawPlayCursor - playCursor); + + // diff should now be approximately 0 + // if it is closer to a multiple of the buffer length (in ms), + // that is because we have missed at least one loop through the buffer + // we use half of the buffer size because it is half way between + // what we expect and what we are checking for + if(diff >= gviDivideByBytesPerMillisecond((int)data->m_playbackBufferHalfSize)) + { + int numMissedLoops; + int numMissedFrames; + int msPerBuffer = gviDivideByBytesPerMillisecond((int)data->m_playbackBufferSize); + + // estimate how many loops we missed + numMissedLoops = (gviRoundToNearestMultiple(diff, msPerBuffer) / msPerBuffer); + + // convert to frames + numMissedFrames = (numMissedLoops * (data->m_playbackBufferSize / GVIBytesPerFrame)); + + // adjust the clock + // Expanded to remove warnings in VS2K5 + data->m_playbackClock = data->m_playbackClock + (GVFrameStamp)(numMissedFrames); + } + + // if we don't have any new bytes, there's nothing to do + if(newBytes == 0) + return GVTrue; + + // lock the appropriate half of the playback buffer + result = IDirectSoundBuffer_Lock(data->m_playbackBuffer, lockPosition, newBytes, &audioPtr1, &audioLen1, &audioPtr2, &audioLen2, 0); + if(FAILED(result)) + return GVFalse; + + // fill it + numFrames = (audioLen1 / GVIBytesPerFrame); + wroteToBuffer = gviWriteSourcesToBuffer(data->m_playbackSources, data->m_playbackClock, (GVSample *)audioPtr1, numFrames); + if(!wroteToBuffer) + memset(audioPtr1, 0, audioLen1); + + // filter + if(device->m_playbackFilterCallback) + { + for(i = 0 ; i < numFrames ; i++) + device->m_playbackFilterCallback(device, (GVSample *)audioPtr1 + (GVISamplesPerFrame * i), (GVFrameStamp)(data->m_playbackClock + i)); + } + + // update the clock + // Expanded to remove warnings in VS2K5 + data->m_playbackClock = data->m_playbackClock + (GVFrameStamp)numFrames; + + // do the same for the second pointer, if it is set + if(audioPtr2) + { + // fill it + numFrames = (audioLen2 / GVIBytesPerFrame); + wroteToBuffer = gviWriteSourcesToBuffer(data->m_playbackSources, data->m_playbackClock, (GVSample *)audioPtr2, numFrames); + if(!wroteToBuffer) + memset(audioPtr2, 0, audioLen2); + + // filter + if(device->m_playbackFilterCallback) + { + for(i = 0 ; i < numFrames ; i++) + device->m_playbackFilterCallback(device, (GVSample *)audioPtr2 + (GVISamplesPerFrame * i), (GVFrameStamp)(data->m_playbackClock + i)); + } + + // update the clock + // Expanded to remove warnings in VS2K5 + data->m_playbackClock = data->m_playbackClock + (GVFrameStamp)numFrames; + } + + // unlock the buffer + result = IDirectSoundBuffer_Unlock(data->m_playbackBuffer, audioPtr1, audioLen1, audioPtr2, audioLen2); + if(FAILED(result)) + return GVFalse; + + return GVTrue; +} + +void gviHardwareThink(void) +{ + GVIDevice * device; + GVBool rcode; + int num; + int i; + + if(!GVIDevices) + return; + + // loop through backwards so that we can remove devices as we go + num = gviGetNumDevices(GVIDevices); + for(i = (num - 1) ; i >= 0 ; i--) + { + // get the device + device = gviGetDevice(GVIDevices, i); + + // check if this is a playback device + if(device->m_types == GV_PLAYBACK) + { + // let it think + rcode = gviPlaybackDeviceThink(device); + + // check if the device was unplugged + if(!rcode) + gviDeviceUnplugged(device); + } + } +} + +#if !defined(GSI_UNICODE) + static BOOL CALLBACK gviEnumDevicesCallback(LPGUID lpGuid, LPCSTR desc, LPCSTR module, LPVOID lpContext) +#else + static BOOL CALLBACK gviEnumDevicesCallback(LPGUID lpGuid, LPCWSTR desc, LPCWSTR module, LPVOID lpContext) +#endif +{ + if(lpGuid != NULL) + { + GVIEnumDevicesInfo * info = (GVIEnumDevicesInfo *)lpContext; + GVDeviceInfo * device = NULL; + int i; + + // first check if it is already in the list + for(i = 0 ; i < info->m_num ; i++) + { + if(IsEqualGUID(&info->m_devices[i].m_id, lpGuid)) + device = &info->m_devices[i]; + } + + if(!device) + { + // if not, add it + device = &info->m_devices[info->m_num++]; + + // clear it + memset(device, 0, sizeof(GVDeviceInfo)); + + // store the ID and name + GVI_GUID_COPY(&device->m_id, lpGuid); + _tcsncpy(device->m_name, desc, GV_DEVICE_NAME_LEN); + device->m_name[GV_DEVICE_NAME_LEN - 1] = '\0'; + } + + // store if it is a cap or playback device + if(info->m_enumeratingCapture) + device->m_deviceType = GV_CAPTURE; + else + device->m_deviceType = GV_PLAYBACK; + + // directsound device + device->m_hardwareType = GVHardwareDirectSound; + + // check for full list + if(info->m_num == info->m_len) + return FALSE; + } + GSI_UNUSED(module); + return TRUE; +} + +int gviHardwareListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types) +{ +#if DIRECTSOUND_VERSION >= 0x0800 + GUID defaultCaptureDevice; + GUID defaultPlaybackDevice; +#endif + GVIEnumDevicesInfo info; + int i; + + // make sure there is space for at least one device + if(maxDevices < 1) + return 0; + + // enumerate capture and playback devices + info.m_devices = devices; + info.m_len = maxDevices; + info.m_num = 0; + if(types & GV_CAPTURE) + { + info.m_enumeratingCapture = GVTrue; +#if !defined(GSI_UNICODE) + DirectSoundCaptureEnumerateA(gviEnumDevicesCallback, &info); +#else + DirectSoundCaptureEnumerateW(gviEnumDevicesCallback, &info); +#endif + } + if(types & GV_PLAYBACK) + { + info.m_enumeratingCapture = GVFalse; +#if !defined(GSI_UNICODE) + DirectSoundEnumerateA(gviEnumDevicesCallback, &info); +#else + DirectSoundEnumerateW(gviEnumDevicesCallback, &info); +#endif + } + +#if DIRECTSOUND_VERSION >= 0x0800 + // check for the default capture and playback devices + GetDeviceID(&DSDEVID_DefaultVoiceCapture, &defaultCaptureDevice); + GetDeviceID(&DSDEVID_DefaultVoicePlayback, &defaultPlaybackDevice); + for(i = 0 ; i < info.m_num ; i++) + { + if(IsEqualGUID(&devices[i].m_id, &defaultCaptureDevice)) + devices[i].m_defaultDevice = GV_CAPTURE; + else if(IsEqualGUID(&devices[i].m_id, &defaultPlaybackDevice)) + devices[i].m_defaultDevice = GV_PLAYBACK; + } +#else + for(i = 0 ; i < info.m_num ; i++) + { + devices[i].m_defaultDevice = (GVDeviceType)0; + } +#endif + + return info.m_num; +} + +static void gviHardwareFreeDevice(GVIDevice * device) +{ + // delete it from the array + // it will clear out internal data in the array's free function + gviDeleteDeviceFromList(GVIDevices, device); +} + +static GVBool gviStartPlaybackDevice(GVIHardwareData * data) +{ + HRESULT result; + LPVOID audioPtr1, audioPtr2; + DWORD audioLen1, audioLen2; + + // make sure the device is stopped + IDirectSoundBuffer_Stop(data->m_playbackBuffer); + + // lock the buffer + result = IDirectSoundBuffer_Lock(data->m_playbackBuffer, 0, 0, &audioPtr1, &audioLen1, &audioPtr2, &audioLen2, DSBLOCK_ENTIREBUFFER); + if(FAILED(result)) + return GVFalse; + + // clear it + memset(audioPtr1, 0, audioLen1); + + // unlock the buffer + result = IDirectSoundBuffer_Unlock(data->m_playbackBuffer, audioPtr1, audioLen1, audioPtr2, audioLen2); + if(FAILED(result)) + return GVFalse; + + // reset the position + result = IDirectSoundBuffer_SetCurrentPosition(data->m_playbackBuffer, 0); + if(FAILED(result)) + return GVFalse; + + // start the buffer + result = IDirectSoundBuffer_Play(data->m_playbackBuffer, 0, 0, DSBPLAY_LOOPING); + if(FAILED(result)) + return GVFalse; + + // clear the playback clocks + data->m_playbackClock = 0; + + // init the think time + data->m_playbackLastThink = current_time(); + + // started playing + data->m_playing = GVTrue; + + return GVTrue; +} + +static GVBool gviStartCaptureDevice(GVIHardwareData * data) +{ + // started capturing + data->m_capturing = GVTrue; + + return GVTrue; +} + +static GVBool gviHardwareStartDevice(GVIDevice * device, GVDeviceType type) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + if(type == GV_PLAYBACK) + return gviStartPlaybackDevice(data); + return gviStartCaptureDevice(data); +} + +static void gviStopPlaybackDevice(GVIHardwareData * data) +{ + // stop the playback buffer + IDirectSoundBuffer_Stop(data->m_playbackBuffer); + + // stopped playing + data->m_playing = GVFalse; + + // clear any pending sources & buffers + gviClearSourceList(data->m_playbackSources); +} + +static void gviStopCaptureDevice(GVIHardwareData * data) +{ + // stopped capturing + data->m_capturing = GVFalse; + + // reset the encoder's state + gviResetEncoder(); +} + +static void gviHardwareStopDevice(GVIDevice * device, GVDeviceType type) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + if(type == GV_PLAYBACK) + gviStopPlaybackDevice(data); + else + gviStopCaptureDevice(data); +} + +static GVBool gviIsPlaybackDeviceStarted(GVIHardwareData * data) +{ + HRESULT result; + DWORD status; + + result = IDirectSoundBuffer_GetStatus(data->m_playbackBuffer, &status); + if(FAILED(result)) + return GVFalse; + + if(status & DSBSTATUS_PLAYING) + return GVTrue; + return GVFalse; +} + +static GVBool gviIsCaptureDeviceStarted(GVIHardwareData * data) +{ + return data->m_capturing; +} + +static GVBool gviHardwareIsDeviceStarted(GVIDevice * device, GVDeviceType type) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + if(type == GV_PLAYBACK) + return gviIsPlaybackDeviceStarted(data); + return gviIsCaptureDeviceStarted(data); +} + +static void gviSetPlaybackDeviceVolume(GVIHardwareData * data, GVScalar volume) +{ + LONG vol; + + // convert from our scale to DS's scale + if(volume == 0.0) + vol = -10000; + else + vol = (LONG)(log(volume) * 2000); + + // set the volume + IDirectSoundBuffer_SetVolume(data->m_playbackBuffer, vol); +} + +static void gviSetCaptureDeviceVolume(GVIHardwareData * data, GVScalar volume) +{ + data->m_captureVolume = volume; +} + +static void gviHardwareSetDeviceVolume(GVIDevice * device, GVDeviceType type, GVScalar volume) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + if(type == GV_PLAYBACK) + gviSetPlaybackDeviceVolume(data, volume); + else + gviSetCaptureDeviceVolume(data, volume); +} + +static GVScalar gviGetPlaybackDeviceVolume(GVIHardwareData * data) +{ + HRESULT result; + LONG vol; + + // get the volume + result = IDirectSoundBuffer_GetVolume(data->m_playbackBuffer, &vol); + if(FAILED(result)) + return (GVScalar)0; + + // convert it to our format + return (GVScalar)(exp(vol / 2000.0)); +} + +static GVScalar gviGetCaptureDeviceVolume(GVIHardwareData * data) +{ + return data->m_captureVolume; +} + +static GVScalar gviHardwareGetDeviceVolume(GVIDevice * device, GVDeviceType type) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + if(type == GV_PLAYBACK) + return gviGetPlaybackDeviceVolume(data); + return gviGetCaptureDeviceVolume(data); +} + +static void gviHardwareSetCaptureThreshold(GVIDevice * device, GVScalar threshold) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + data->m_captureThreshold = threshold; +} + +static GVScalar gviHardwareGetCaptureThreshold(GVIDevice * device) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + return data->m_captureThreshold; +} + +static int gviHardwareGetAvailableCaptureBytes(GVDevice device) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + HRESULT result; + DWORD readPosition; + DWORD newBytes; + int numFrames; + + // get the current position + result = IDirectSoundCaptureBuffer_GetCurrentPosition(data->m_captureBuffer, NULL, &readPosition); + if(FAILED(result)) + { + gviDeviceUnplugged(device); + return 0; + } + + // get the number of new bytes + newBytes = (((readPosition + data->m_captureBufferSize) - data->m_captureBufferPosition) % data->m_captureBufferSize); + + // figure out how many frames this is + numFrames = (newBytes / GVIBytesPerFrame); + + // calculate how many bytes this is once encoded + newBytes = (numFrames * GVIEncodedFrameSize); + + return newBytes; +} + +static GVBool gviHardwareCapturePacket(GVDevice device, GVByte * packet, int * len, GVFrameStamp * frameStamp, GVScalar * volume) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + HRESULT result; + DWORD readPosition; + DWORD newBytes; + LPVOID audioPtr1, audioPtr2; + GVSample * audioPtr; + DWORD audioLen, audioLen1, audioLen2; + GVBool overThreshold = GVFalse; + int numFrames; + int i, j; + int lenAvailable; + int framesAvailable; + + // figure out how many encoded bytes they can handle + lenAvailable = *len; + + // clear the len and volume + *len = 0; + if(volume) + *volume = 0; + + // get the current position + result = IDirectSoundCaptureBuffer_GetCurrentPosition(data->m_captureBuffer, NULL, &readPosition); + if(FAILED(result)) + { + gviDeviceUnplugged(device); + return GVFalse; + } + + // get the number of new bytes + newBytes = (((readPosition + data->m_captureBufferSize) - data->m_captureBufferPosition) % data->m_captureBufferSize); + + // figure out how many frames this is + numFrames = (newBytes / GVIBytesPerFrame); + + // figure out how many frames they can handle + framesAvailable = (lenAvailable / GVIEncodedFrameSize); + + // don't give them more frames than they can handle + numFrames = min(numFrames, framesAvailable); + if(!numFrames) + return GVFalse; + + // round off the bytes to a frame boundary + newBytes = (numFrames * GVIBytesPerFrame); + + // only do this part if we are capturing + if(data->m_capturing) + { + // lock the buffer + result = IDirectSoundCaptureBuffer_Lock(data->m_captureBuffer, data->m_captureBufferPosition, newBytes, &audioPtr1, &audioLen1, &audioPtr2, &audioLen2, 0); + if(FAILED(result)) + { + gviDeviceUnplugged(device); + return GVFalse; + } + + // get the volume if requested + if(volume) + { + GVScalar vol1 = gviGetSamplesVolume(audioPtr1, audioLen1 / GV_BYTES_PER_SAMPLE); + GVScalar vol2 = gviGetSamplesVolume(audioPtr2, audioLen2 / GV_BYTES_PER_SAMPLE); + *volume = max(vol1, vol2); + } + + // check against the threshold + if(volume) + { + // we already got the volume, so use that to check + overThreshold = (*volume >= data->m_captureThreshold); + } + else + { + // we didn't get a volume, so check the samples directly + overThreshold = gviIsOverThreshold(audioPtr1, audioLen1 / GV_BYTES_PER_SAMPLE, data->m_captureThreshold); + + // if not over threshold, and there is a second portion of audio, check it + if(!overThreshold && audioPtr2) + overThreshold = gviIsOverThreshold(audioPtr2, audioLen2 / GV_BYTES_PER_SAMPLE, data->m_captureThreshold); + } + + // did the audio cross the threshold? + if(overThreshold) + { + // update the time at which we crossed + data->m_captureLastCrossedThresholdTime = data->m_captureClock; + } + else + { + // check if we are still within the hold time + overThreshold = ((GVFrameStamp)(data->m_captureClock - data->m_captureLastCrossedThresholdTime) < GVI_HOLD_THRESHOLD_FRAMES); + } + + if(overThreshold) + { + // store the frameStamp + *frameStamp = data->m_captureClock; + + // setup the starting audio pointer and len + audioPtr = audioPtr1; + audioLen = audioLen1; + + // handle the data one frame at a time + for(i = 0 ; i < numFrames ; i++) + { + // scale the data + if(data->m_captureVolume < 1.0) + { + for(j = 0 ; j < GVISamplesPerFrame ; j++) + audioPtr[j] = (GVSample)(audioPtr[j] * data->m_captureVolume); + } + + // filter + if(device->m_captureFilterCallback) + device->m_captureFilterCallback(device, audioPtr, (GVFrameStamp)(data->m_captureClock + i)); + + // encode the buffer into the packet + gviEncode(packet + (GVIEncodedFrameSize * i), audioPtr); + + // update the loop info as needed + if(audioLen > (DWORD)GVIBytesPerFrame) + { + audioPtr += GVISamplesPerFrame; + audioLen -= GVIBytesPerFrame; + } + else + { + audioPtr = (GVSample*)audioPtr2; + audioLen = audioLen2; + } + } + } + + // unlock the buffer + result = IDirectSoundCaptureBuffer_Unlock(data->m_captureBuffer, audioPtr1, audioLen1, audioPtr2, audioLen2); + if(FAILED(result)) + { + gviDeviceUnplugged(device); + return GVFalse; + } + } + + // set the new position + data->m_captureBufferPosition += newBytes; + data->m_captureBufferPosition %= data->m_captureBufferSize; + + // increment the clock + // Expanded to remove warnings in VS2K5 + data->m_captureClock = data->m_captureClock + (GVFrameStamp)numFrames; + + // set the len + *len = (numFrames * GVIEncodedFrameSize); + + // return false if we didn't get a packet + if(!overThreshold) + return GVFalse; + + return GVTrue; +} + +static void gviHardwarePlayPacket(GVIDevice * device, const GVByte * packet, int len, GVSource source, GVFrameStamp frameStamp, GVBool mute) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + // don't do anythying if we're not playing + if(!data->m_playing) + return; + + //add it + gviAddPacketToSourceList(data->m_playbackSources, packet, len, source, frameStamp, mute, data->m_playbackClock); +} + +static GVBool gviHardwareIsSourceTalking(GVDevice device, GVSource source) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + // don't do anythying if we're not playing + if(!data->m_playing) + return GVFalse; + + return gviIsSourceTalking(data->m_playbackSources, source); +} + +static int gviHardwareListTalkingSources(GVDevice device, GVSource sources[], int maxSources) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + // don't do anythying if we're not playing + if(!data->m_playing) + return GVFalse; + + return gviListTalkingSources(data->m_playbackSources, sources, maxSources); +} + +static GVBool gviInitPlaybackDevice(GVIHardwareData * data) +{ + HRESULT result; + DSBUFFERDESC bufferDescriptor; + + // set the cooperative level + result = IDirectSound_SetCooperativeLevel(data->m_playback, GVIHwnd, DSSCL_NORMAL); + if(FAILED(result)) + return GVFalse; + + // setup the buffer size + data->m_playbackBufferSize = gviMultiplyByBytesPerMillisecond(GVI_PLAYBACK_BUFFER_MILLISECONDS); + + // make sure it is a multiple of twice the raw frame size + // it needs to be twice the size because it will be split in two + data->m_playbackBufferSize = gviRoundUpToNearestMultiple(data->m_playbackBufferSize, GVIBytesPerFrame * 2); + + // we use this a lot, so calc it here + data->m_playbackBufferHalfSize = (data->m_playbackBufferSize / 2); + + // set up the buffer descriptor + memset(&bufferDescriptor, 0, sizeof(DSBUFFERDESC)); + bufferDescriptor.dwSize = sizeof(DSBUFFERDESC); + bufferDescriptor.dwFlags = DSBCAPS_CTRLVOLUME|DSBCAPS_GETCURRENTPOSITION2; +#if !defined(GV_NO_GLOBAL_FOCUS) + bufferDescriptor.dwFlags |= DSBCAPS_GLOBALFOCUS; +#endif + bufferDescriptor.dwBufferBytes = (DWORD)data->m_playbackBufferSize; + bufferDescriptor.lpwfxFormat = &GVIWaveFormat; + + // create the buffer + result = IDirectSound_CreateSoundBuffer(data->m_playback, &bufferDescriptor, &data->m_playbackBuffer, NULL); + if(FAILED(result)) + return GVFalse; + + return GVTrue; +} + +static GVBool gviInitCaptureDevice(GVIHardwareData * data) +{ + HRESULT result; + DSCBUFFERDESC bufferDescriptor; + + // setup the buffer size + data->m_captureBufferSize = gviMultiplyByBytesPerMillisecond(GVI_CAPTURE_BUFFER_MILLISECONDS); + + // make sure it is a multiple of the raw frame size + data->m_captureBufferSize = gviRoundUpToNearestMultiple(data->m_captureBufferSize, GVIBytesPerFrame); + + // setup the buffer descriptor + memset(&bufferDescriptor, 0, sizeof(bufferDescriptor)); + bufferDescriptor.dwSize = sizeof(DSCBUFFERDESC); + bufferDescriptor.dwBufferBytes = (DWORD)data->m_captureBufferSize; + bufferDescriptor.lpwfxFormat = &GVIWaveFormat; + + // create the buffer + result = IDirectSoundCapture_CreateCaptureBuffer(data->m_capture, &bufferDescriptor, &data->m_captureBuffer, NULL); + if(FAILED(result)) + return GVFalse; + + // start the buffer + result = IDirectSoundCaptureBuffer_Start(data->m_captureBuffer, DSCBSTART_LOOPING); + if(FAILED(result)) + { + IDirectSoundCaptureBuffer_Release(data->m_captureBuffer); + data->m_captureBuffer = NULL; + return GVFalse; + } + + return GVTrue; +} + +GVDevice gviHardwareNewDevice(GVDeviceID deviceID, GVDeviceType type) +{ + GVIDevice * device; + GVIHardwareData * data; + HRESULT result; + + // DS can only handle one type or the other + if((type != GV_CAPTURE) && (type != GV_PLAYBACK)) + return NULL; + + //setup the wave format + memset(&GVIWaveFormat, 0, sizeof(WAVEFORMATEX)); + GVIWaveFormat.wFormatTag = WAVE_FORMAT_PCM; + GVIWaveFormat.nChannels = 1; + GVIWaveFormat.nSamplesPerSec = GVISampleRate; + GVIWaveFormat.wBitsPerSample = GV_BITS_PER_SAMPLE; + GVIWaveFormat.nBlockAlign = ((GVIWaveFormat.nChannels * GVIWaveFormat.wBitsPerSample) / 8); + GVIWaveFormat.nAvgBytesPerSec = (GVIWaveFormat.nSamplesPerSec * GVIWaveFormat.nBlockAlign); + + + // create a new device + device = gviNewDevice(deviceID, GVHardwareDirectSound, type, sizeof(GVIHardwareData)); + if(!device) + return NULL; + + // get a pointer to the data + data = (GVIHardwareData *)device->m_data; + + // store the pointers + device->m_methods.m_freeDevice = gviHardwareFreeDevice; + device->m_methods.m_startDevice = gviHardwareStartDevice; + device->m_methods.m_stopDevice = gviHardwareStopDevice; + device->m_methods.m_isDeviceStarted = gviHardwareIsDeviceStarted; + device->m_methods.m_setDeviceVolume = gviHardwareSetDeviceVolume; + device->m_methods.m_getDeviceVolume = gviHardwareGetDeviceVolume; + device->m_methods.m_setCaptureThreshold = gviHardwareSetCaptureThreshold; + device->m_methods.m_getCaptureThreshold = gviHardwareGetCaptureThreshold; + device->m_methods.m_getAvailableCaptureBytes = gviHardwareGetAvailableCaptureBytes; + device->m_methods.m_capturePacket = gviHardwareCapturePacket; + device->m_methods.m_playPacket = gviHardwarePlayPacket; + device->m_methods.m_isSourceTalking = gviHardwareIsSourceTalking; + device->m_methods.m_listTalkingSources = gviHardwareListTalkingSources; + + // check if they want to init playback + if(type == GV_PLAYBACK) + { + // create the array of sources + data->m_playbackSources = gviNewSourceList(); + if(!data->m_playbackSources) + { + gviFreeDevice(device); + return NULL; + } + + // create the interface + result = DirectSoundCreate(&deviceID, &data->m_playback, NULL); + if(FAILED(result)) + { + gviFreeSourceList(data->m_playbackSources); + gviFreeDevice(device); + return NULL; + } + + // setup the playback device + if(!gviInitPlaybackDevice(data)) + { + gviFreeSourceList(data->m_playbackSources); + IDirectSound_Release(data->m_playback); + gviFreeDevice(device); + return NULL; + } + } + // check if they want to init capture + else if(type == GV_CAPTURE) + { + // create the interface + result = DirectSoundCaptureCreate(&deviceID, &data->m_capture, NULL); + if(FAILED(result)) + { + gviFreeDevice(device); + return NULL; + } + + // setup the capture device + if(!gviInitCaptureDevice(data)) + { + IDirectSoundCapture_Release(data->m_capture); + gviFreeDevice(device); + return NULL; + } + + // set some data vars + data->m_captureVolume = 1.0; + data->m_captureClock = 0; + data->m_captureLastCrossedThresholdTime = (data->m_captureClock - GVI_HOLD_THRESHOLD_FRAMES - 1); + } + + // add it to the list + gviAppendDeviceToList(GVIDevices, device); + + return device; +} + +#if (DIRECTSOUND_VERSION == 0x0800) +static LPDIRECTPLAYVOICETEST GetVoiceTestInstance(void) +{ + LPDIRECTPLAYVOICETEST voiceTest; + HRESULT result; + + // create the IDirectPlayVoiceTest object + result = CoCreateInstance(&CLSID_DirectPlayVoiceTest, NULL, CLSCTX_INPROC_SERVER, &IID_IDirectPlayVoiceTest, (LPVOID*)&voiceTest); + + if(FAILED(result)) + return NULL; + + return voiceTest; +} + +GVBool gvRunSetupWizard(GVDeviceID captureDeviceID, GVDeviceID playbackDeviceID) +{ + LPDIRECTPLAYVOICETEST voiceTest; + HRESULT result; + + // get the voice test instance + voiceTest = GetVoiceTestInstance(); + if(!voiceTest) + return GVFalse; + + // run the dialog + result = IDirectPlayVoiceTest_CheckAudioSetup(voiceTest, &playbackDeviceID, &captureDeviceID, GVIHwnd, 0); + + // releate the instance + IDirectPlayVoiceTest_Release(voiceTest); + + // check for success + if((result == DV_OK) || (result == DV_FULLDUPLEX) || (result == DV_HALFDUPLEX)) + return GVTrue; + + return GVFalse; +} + +GVBool gvAreDevicesSetup(GVDeviceID captureDevice, GVDeviceID playbackDevice) +{ + LPDIRECTPLAYVOICETEST voiceTest; + HRESULT result; + + // get the voice test instance + voiceTest = GetVoiceTestInstance(); + if(!voiceTest) + return GVFalse; + + // run the dialog + result = IDirectPlayVoiceTest_CheckAudioSetup(voiceTest, &playbackDevice, &captureDevice, GVIHwnd, DVFLAGS_QUERYONLY); + + // releate the instance + IDirectPlayVoiceTest_Release(voiceTest); + + // check for success + if((result == DV_FULLDUPLEX) || (result == DV_HALFDUPLEX)) + return GVTrue; + + return GVFalse; +} +#endif diff --git a/code/gamespy/Voice2/gvDirectSound.h b/code/gamespy/Voice2/gvDirectSound.h new file mode 100644 index 00000000..fb141e87 --- /dev/null +++ b/code/gamespy/Voice2/gvDirectSound.h @@ -0,0 +1,25 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_DIRECT_SOUND_H_ +#define _GV_DIRECT_SOUND_H_ + +#include "gvMain.h" + +GVBool gviHardwareStartup(HWND hWnd); +void gviHardwareCleanup(void); +void gviHardwareThink(void); + +int gviHardwareListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types); + +GVDevice gviHardwareNewDevice(GVDeviceID deviceID, GVDeviceType type); + +#endif diff --git a/code/gamespy/Voice2/gvFrame.c b/code/gamespy/Voice2/gvFrame.c new file mode 100644 index 00000000..0d9addc7 --- /dev/null +++ b/code/gamespy/Voice2/gvFrame.c @@ -0,0 +1,107 @@ +#include "gvFrame.h" +#include "gvCodec.h" + +// packets are only accepted if they are frameStamped to be played +// within this many frames from the current play clock +#define GVI_INCOMING_PACKET_TIMEFRAME_FRAMES (GVI_FRAMESTAMP_MAX / 2) + +#define GVI_PREALLOCATED_FRAMES 200 + +#if defined(_PS2) || defined(_PSP) +#define GVI_DYNAMICALLY_ALLOCATE_FRAMES 0 +#else +#define GVI_DYNAMICALLY_ALLOCATE_FRAMES 1 +#endif + +// list of available frames +static GVIPendingFrame * GVIAvailableFrames; + +GVBool gviIsFrameStampGT(GVFrameStamp a, GVFrameStamp b) +{ + return ((GVFrameStamp)(b - a) > GVI_INCOMING_PACKET_TIMEFRAME_FRAMES); +} + +GVBool gviIsFrameStampGTE(GVFrameStamp a, GVFrameStamp b) +{ + return ((GVFrameStamp)(b - a - 1) > GVI_INCOMING_PACKET_TIMEFRAME_FRAMES); +} + +static void gviFreePendingFrame(GVIPendingFrame * frame) +{ + gsifree(frame); +} + +static GVIPendingFrame * gviNewPendingFrame(void) +{ + GVIPendingFrame * frame; + + // allocate a new frame +#if GVI_PRE_DECODE + frame = (GVIPendingFrame *)gsimalloc(sizeof(GVIPendingFrame) + GVIBytesPerFrame - sizeof(GVSample)); +#else + frame = (GVIPendingFrame *)gsimalloc(sizeof(GVIPendingFrame) + GVIEncodedFrameSize - 1); +#endif + + // return it + return frame; +} + +void gviPutPendingFrame(GVIPendingFrame * frame) +{ + // put the frame back in the available frames list + frame->m_next = GVIAvailableFrames; + GVIAvailableFrames = frame; +} + +GVIPendingFrame * gviGetPendingFrame(void) +{ + GVIPendingFrame * frame; + + // check the available frames list + if(GVIAvailableFrames) + { + frame = GVIAvailableFrames; + GVIAvailableFrames = frame->m_next; + return frame; + } + +#if GVI_DYNAMICALLY_ALLOCATE_FRAMES + // allocate a new frame + return gviNewPendingFrame(); +#else + // we can't dynamically allocate frames + return NULL; +#endif +} + +void gviFramesStartup(void) +{ + GVIPendingFrame * frame; + int i; + + if(GVIAvailableFrames) + gviFramesCleanup(); + + GVIAvailableFrames = NULL; + + for(i = 0 ; i < GVI_PREALLOCATED_FRAMES ; i++) + { + frame = gviNewPendingFrame(); + if(!frame) + return; + frame->m_next = GVIAvailableFrames; + GVIAvailableFrames = frame; + } +} + +void gviFramesCleanup(void) +{ + GVIPendingFrame * next; + + while(GVIAvailableFrames) + { + next = GVIAvailableFrames->m_next; + gviFreePendingFrame(GVIAvailableFrames); + GVIAvailableFrames = next; + } +} diff --git a/code/gamespy/Voice2/gvFrame.h b/code/gamespy/Voice2/gvFrame.h new file mode 100644 index 00000000..9bdf85ed --- /dev/null +++ b/code/gamespy/Voice2/gvFrame.h @@ -0,0 +1,51 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_FRAME_H_ +#define _GV_FRAME_H_ + +#include "gvMain.h" + +// max value for a framestamp +#define GVI_FRAMESTAMP_MAX 0xFFFF + +#if defined(_MACOSX) + #define GVI_PRE_DECODE 1 +#else + #define GVI_PRE_DECODE 0 +#endif + +// when allocated, enough memory is allocated to fit an entire +// frame into the m_frame array +typedef struct GVIPendingFrame +{ + GVFrameStamp m_frameStamp; + struct GVIPendingFrame * m_next; + // m_frame must be the last member of this struct +#if GVI_PRE_DECODE + GVSample m_frame[1]; +#else + GVByte m_frame[1]; +#endif +} GVIPendingFrame; + +void gviFramesStartup(void); +void gviFramesCleanup(void); + +GVIPendingFrame * gviGetPendingFrame(void); +void gviPutPendingFrame(GVIPendingFrame * frame); + +// a > b +GVBool gviIsFrameStampGT(GVFrameStamp a, GVFrameStamp b); +// a >= b +GVBool gviIsFrameStampGTE(GVFrameStamp a, GVFrameStamp b); + +#endif diff --git a/code/gamespy/Voice2/gvGSM.c b/code/gamespy/Voice2/gvGSM.c new file mode 100644 index 00000000..20230322 --- /dev/null +++ b/code/gamespy/Voice2/gvGSM.c @@ -0,0 +1,97 @@ +#include "gvGSM.h" +#include + +// these are standard for GSM +#define GVI_SAMPLES_PER_FRAME 160 +#define GVI_ENCODED_FRAME_SIZE 33 + +static GVBool gviGSMInitialized; +static gsm gviGSMEncoderState; + +GVBool gviGSMInitialize(void) +{ + const int gsm_ltp = 1; + + // we shouldn't already be initialized + if(gviGSMInitialized) + return GVFalse; + + // create a new encoder state + gviGSMEncoderState = gsm_create(); + if(!gviGSMEncoderState) + return GVFalse; + + // set the LTP cut option + gsm_option(gviGSMEncoderState, GSM_OPT_LTP_CUT, &gsm_ltp); + + // we're now initialized + gviGSMInitialized = GVTrue; + + return GVTrue; +} + +void gviGSMCleanup(void) +{ + // make sure there is something to cleanup + if(!gviGSMInitialized) + return; + + // destroy the encoder state + gsm_destroy(gviGSMEncoderState); + gviGSMEncoderState = NULL; + + // no longer initialized + gviGSMInitialized = GVFalse; +} + +int gviGSMGetSamplesPerFrame(void) +{ + return GVI_SAMPLES_PER_FRAME; +} + +int gviGSMGetEncodedFrameSize(void) +{ + return GVI_ENCODED_FRAME_SIZE; +} + +GVBool gviGSMNewDecoder(GVDecoderData * data) +{ + gsm decoder; + + // create a new decoder state + decoder = gsm_create(); + if(!decoder) + return GVFalse; + + *data = decoder; + return GVTrue; +} + +void gviGSMFreeDecoder(GVDecoderData data) +{ + // destory the decoder state + gsm_destroy((gsm)data); +} + +void gviGSMEncode(GVByte * out, const GVSample * in) +{ + gsm_encode((gsm)gviGSMEncoderState, (gsm_signal*)in, (gsm_byte*)out); +} + +void gviGSMDecodeAdd(GVSample * out, const GVByte * in, GVDecoderData data) +{ + GVSample temp[GVI_SAMPLES_PER_FRAME]; + int i; + + // decode it + gsm_decode((gsm)data, (gsm_byte*)in, (gsm_signal*)temp); + + // add the output + for(i = 0 ; i < GVI_SAMPLES_PER_FRAME ; i++) + out[i] += temp[i]; +} + +void gviGSMDecodeSet(GVSample * out, const GVByte * in, GVDecoderData data) +{ + gsm_decode((gsm)data, (gsm_byte*)in, (gsm_signal*)out); +} diff --git a/code/gamespy/Voice2/gvGSM.h b/code/gamespy/Voice2/gvGSM.h new file mode 100644 index 00000000..74d9c102 --- /dev/null +++ b/code/gamespy/Voice2/gvGSM.h @@ -0,0 +1,39 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +18002 Skypark Circle +Irvine, California 92614 +949.798.4200 (Tel) +949.798.4299 (Fax) +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_GSM_H_ +#define _GV_GSM_H_ + +#include "gvMain.h" + +/* +quality: samplesPerFrame encodedFrameSize bitsPerSecond (at 11025hz) +All: 160 33 18200 +*/ + +GVBool gviGSMInitialize(void); +void gviGSMCleanup(void); + +int gviGSMGetSamplesPerFrame(void); +int gviGSMGetEncodedFrameSize(void); + +GVBool gviGSMNewDecoder(GVDecoderData * data); +void gviGSMFreeDecoder(GVDecoderData data); + +void gviGSMEncode(GVByte * out, const GVSample * in); +void gviGSMDecodeAdd(GVSample * out, const GVByte * in, GVDecoderData data); +void gviGSMDecodeSet(GVSample * out, const GVByte * in, GVDecoderData data); + +#endif diff --git a/code/gamespy/Voice2/gvLogitechPS2Codecs.c b/code/gamespy/Voice2/gvLogitechPS2Codecs.c new file mode 100644 index 00000000..83d34f65 --- /dev/null +++ b/code/gamespy/Voice2/gvLogitechPS2Codecs.c @@ -0,0 +1,118 @@ +#include "gvLogitechPS2Codecs.h" +#include + +#if !defined(_PS2) +#error This file should only be used with the PlayStation2 +#endif + +static GVBool GVILGCodecInitialized; +static int GVILGCodecHandle; +static int GVILGCodecBytesPerFrame; +static int GVILGCodecSamplesPerFrame; +static int GVILGCodecEncodedFrameSize; +static GVSample * GVILGCodecDecodeBuffer; + +GVBool gviLGCodecInitialize(const char * name) +{ + lgCodecDesc * codecDesc; + int i; + int rcode; + + // check if the lib hasn't been initialized + if(!GVILGCodecInitialized) + { + // initialize it + rcode = lgCodecInit(); + if(LGCODEC_FAILED(rcode)) + return GVFalse; + + // we're now initialized + GVILGCodecInitialized = GVTrue; + } + + // find the codec + for(i = 0 ; (codecDesc = lgCodecEnumerate(i)) ; i++) + { + // check if the name matches + if(strcmp(codecDesc->name, name) == 0) + break; + } + + // check if we didn't find it + if(!codecDesc) + return GVFalse; + + // allocate memory for the decode buffer + GVILGCodecDecodeBuffer = (GVSample *)gsimalloc((unsigned int)codecDesc->bytes_per_pcm_frame); + if(!GVILGCodecDecodeBuffer) + return GVFalse; + + // open a handle to the codec + rcode = lgCodecOpen(codecDesc->id, &GVILGCodecHandle); + if(LGCODEC_FAILED(rcode)) + { + gsifree(GVILGCodecDecodeBuffer); + return GVFalse; + } + + // store the codec info + GVILGCodecBytesPerFrame = codecDesc->bytes_per_pcm_frame; + GVILGCodecSamplesPerFrame = (int)(GVILGCodecBytesPerFrame / GV_BYTES_PER_SAMPLE); + GVILGCodecEncodedFrameSize = codecDesc->bytes_per_enc_frame; + + return GVTrue; +} + +void gviLGCodecCleanup(void) +{ + gsifree(GVILGCodecDecodeBuffer); + GVILGCodecDecodeBuffer = NULL; + lgCodecClose(GVILGCodecHandle); +} + +int gviLGCodecGetSamplesPerFrame(void) +{ + return GVILGCodecSamplesPerFrame; +} + +int gviLGCodecGetEncodedFrameSize(void) +{ + return GVILGCodecEncodedFrameSize; +} + +void gviLGCodecEncode(GVByte * out, const GVSample * in) +{ + int destSize = GVILGCodecEncodedFrameSize; + lgCodecEncode(GVILGCodecHandle, in, GVILGCodecBytesPerFrame, out, &destSize); + assert(destSize == GVILGCodecEncodedFrameSize); +} + +void gviLGCodecDecodeAdd(GVSample * out, const GVByte * in, GVDecoderData data) +{ + int i; + int destSize; + + destSize = GVILGCodecBytesPerFrame; + lgCodecDecode(GVILGCodecHandle, in, GVILGCodecEncodedFrameSize, GVILGCodecDecodeBuffer, &destSize); + assert(destSize == GVILGCodecBytesPerFrame); + + for(i = 0 ; i < GVILGCodecSamplesPerFrame ; i++) + out[i] += GVILGCodecDecodeBuffer[i]; + + GSI_UNUSED(data); +} + +void gviLGCodecDecodeSet(GVSample * out, const GVByte * in, GVDecoderData data) +{ + int i; + int destSize; + + destSize = GVILGCodecBytesPerFrame; + lgCodecDecode(GVILGCodecHandle, in, GVILGCodecEncodedFrameSize, GVILGCodecDecodeBuffer, &destSize); + assert(destSize == GVILGCodecBytesPerFrame); + + for(i = 0 ; i < GVILGCodecSamplesPerFrame ; i++) + out[i] = GVILGCodecDecodeBuffer[i]; + + GSI_UNUSED(data); +} diff --git a/code/gamespy/Voice2/gvLogitechPS2Codecs.h b/code/gamespy/Voice2/gvLogitechPS2Codecs.h new file mode 100644 index 00000000..31b2b7c5 --- /dev/null +++ b/code/gamespy/Voice2/gvLogitechPS2Codecs.h @@ -0,0 +1,41 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +18002 Skypark Circle +Irvine, California 92614 +949.798.4200 (Tel) +949.798.4299 (Fax) +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_LOGITECH_PS2_CODECS_H_ +#define _GV_LOGITECH_PS2_CODECS_H_ + +#include "gvMain.h" + +/* +name: samplesPerFrame encodedFrameSize bitsPerSecond +LPC10: 180 7 2489 +SPEEX: 160 20 8000 +GSM: 160 33 13200 +G723.24: 160 60 24000 +uLaw: 160 160 64000 +PCM: 160 320 128000 +*/ + +GVBool gviLGCodecInitialize(const char * name); +void gviLGCodecCleanup(void); + +int gviLGCodecGetSamplesPerFrame(void); +int gviLGCodecGetEncodedFrameSize(void); + +void gviLGCodecEncode(GVByte * out, const GVSample * in); +void gviLGCodecDecodeAdd(GVSample * out, const GVByte * in, GVDecoderData data); +void gviLGCodecDecodeSet(GVSample * out, const GVByte * in, GVDecoderData data); + +#endif diff --git a/code/gamespy/Voice2/gvMain.c b/code/gamespy/Voice2/gvMain.c new file mode 100644 index 00000000..a9eb6bab --- /dev/null +++ b/code/gamespy/Voice2/gvMain.c @@ -0,0 +1,318 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +devsupport@gamespy.com +http://gamespy.net +*/ + +#include "gvMain.h" +#include "gvCodec.h" +#include "gvSource.h" +#include "gvFrame.h" +#include "gvCustomDevice.h" +#if !defined(GV_NO_DEFAULT_HARDWARE) + #if defined(_WIN32) + #include "gvDirectSound.h" + #elif defined(_PS2) + #include "gvPS2Audio.h" + #elif defined(_MACOSX) + #include "gvOSXAudio.h" + #elif defined(_PSP) + #include "gvPSPAudio.h" + #elif defined(_PS3) + #include "gvPS3Audio.h" + #else + #error There is no default hardware support on this platform. Define GV_NO_DEFAULT_HARDWARE to compile without default hardware support. + #endif +#endif + + +/************ +** GENERAL ** +************/ +#if defined(_WIN32) +GVBool gvStartup(HWND hWnd) +#else +GVBool gvStartup(void) +#endif +{ + // init codecs + gviCodecsInitialize(); + + GVIGlobalMute = GVFalse; + + // startup the devices +#if !defined(GV_NO_DEFAULT_HARDWARE) + #if defined(_WIN32) + if(!gviHardwareStartup(hWnd)) + #else + if(!gviHardwareStartup()) + #endif + { + return GVFalse; + } +#endif + return GVTrue; +} + +void gvCleanup(void) +{ +#if !defined(GV_NO_DEFAULT_HARDWARE) + gviHardwareCleanup(); +#endif + gviCodecsCleanup(); + gviFramesCleanup(); +} + +void gvThink(void) +{ +#if !defined(GV_NO_DEFAULT_HARDWARE) + gviHardwareThink(); +#endif +} + +/********** +** CODEC ** +**********/ +GVBool gvSetCodec(GVCodec codec) +{ + return gviSetCodec(codec); +} + +void gvSetCustomCodec(GVCustomCodecInfo * info) +{ + gviSetCustomCodec(info); +} + +void gvGetCodecInfo(int * samplesPerFrame, int * encodedFrameSize, int * bitsPerSecond) +{ + if(samplesPerFrame) + *samplesPerFrame = GVISamplesPerFrame; + if(encodedFrameSize) + *encodedFrameSize = GVIEncodedFrameSize; + if(bitsPerSecond) + *bitsPerSecond = (int)(8 * GVIBytesPerSecond * GVIEncodedFrameSize / GVIBytesPerFrame); +} + +/**************** +** Sample Rate ** +****************/ +void gvSetSampleRate(GVRate sampleRate) +{ + gviSetSampleRate(sampleRate); +} + +GVRate gvGetSampleRate(void) +{ + return gviGetSampleRate(); +} + +/************ +** DEVICES ** +************/ +#if !defined(GV_NO_DEFAULT_HARDWARE) +int gvListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types) +{ + return gviHardwareListDevices(devices, maxDevices, types); +} + +GVDevice gvNewDevice(GVDeviceID deviceID, GVDeviceType type) +{ + return gviHardwareNewDevice(deviceID, type); +} +#endif + +void gvFreeDevice(GVDevice device) +{ + device->m_methods.m_freeDevice(device); +} + +GVBool gvStartDevice(GVDevice device, GVDeviceType type) +{ + return device->m_methods.m_startDevice(device, type); +} + +void gvStopDevice(GVDevice device, GVDeviceType type) +{ + device->m_methods.m_stopDevice(device, type); +} + +GVBool gvIsDeviceStarted(GVDevice device, GVDeviceType type) +{ + return device->m_methods.m_isDeviceStarted(device, type); +} + +void gvSetDeviceVolume(GVDevice device, GVDeviceType type, GVScalar volume) +{ + volume = max(volume, 0.0); + volume = min(volume, 1.0); + + device->m_methods.m_setDeviceVolume(device, type, volume); +} + +GVScalar gvGetDeviceVolume(GVDevice device, GVDeviceType type) +{ + return device->m_methods.m_getDeviceVolume(device, type); +} + +void gvSetUnpluggedCallback(GVDevice device, gvUnpluggedCallback unpluggedCallback) +{ + device->m_unpluggedCallback = unpluggedCallback; +} + +void gvSetFilter(GVDevice device, GVDeviceType type, gvFilterCallback callback) +{ + if(type & GV_CAPTURE) + device->m_captureFilterCallback = callback; + if(type & GV_PLAYBACK) + device->m_playbackFilterCallback = callback; +} + +/************ +** CAPTURE ** +************/ +GVBool gvCapturePacket(GVDevice device, GVByte * packet, int * len, GVFrameStamp * frameStamp, GVScalar * volume) +{ + return device->m_methods.m_capturePacket(device, packet, len, frameStamp, volume); +} + +int gvGetAvailableCaptureBytes(GVDevice device) +{ + return device->m_methods.m_getAvailableCaptureBytes(device); +} + +void gvSetCaptureThreshold(GVDevice device, GVScalar threshold) +{ + device->m_methods.m_setCaptureThreshold(device, threshold); +} + +GVScalar gvGetCaptureThreshold(GVDevice device) +{ + return device->m_methods.m_getCaptureThreshold(device); +} + +void gvSetCaptureMode(GVDevice device, GVCaptureMode captureMode) +{ + //This only works with Capture devices. + assert(device->m_types & GV_CAPTURE); + + //See if we are switching from Threshold to PTT. + if ((device->m_captureMode == GVCaptureModeThreshold) && (captureMode == GVCaptureModePushToTalk)) + { + device->m_savedCaptureThreshold = gvGetCaptureThreshold(device); + gvSetCaptureThreshold(device, 0.0f); + + //Stop the capture device. + if (gvIsDeviceStarted(device, GV_CAPTURE)) + gvStopDevice(device, GV_CAPTURE); + } + //See if we are switching from PTT to Threshold. + if ((device->m_captureMode == GVCaptureModePushToTalk) && (captureMode == GVCaptureModeThreshold)) + { + gvSetCaptureThreshold(device, device->m_savedCaptureThreshold); + + //Turn on the capture device. + gvStartDevice(device, GV_CAPTURE); + } + + device->m_captureMode = captureMode; +} + +GVCaptureMode gvGetCaptureMode(GVDevice device) +{ + return device->m_captureMode; +} + +void gvSetPushToTalk(GVDevice device, GVBool talkOn) +{ + if (talkOn) + gvStartDevice(device, GV_CAPTURE); + else + gvStopDevice(device, GV_CAPTURE); +} + +GVBool gvGetPushToTalk(GVDevice device) +{ + return gvIsDeviceStarted(device, GV_CAPTURE); +} + +/************* +** PLAYBACK ** +*************/ +void gvPlayPacket(GVDevice device, const GVByte * data, int len, GVSource source, GVFrameStamp frameStamp, GVBool mute) +{ + device->m_methods.m_playPacket(device, data, len, source, frameStamp, mute); +} + +GVBool gvIsSourceTalking(GVDevice device, GVSource source) +{ + return device->m_methods.m_isSourceTalking(device, source); +} + +int gvListTalkingSources(GVDevice device, GVSource sources[], int maxSources) +{ + return device->m_methods.m_listTalkingSources(device, sources, maxSources); +} + +void gvSetGlobalMute(GVBool mute) +{ + gviSetGlobalMute(mute); +} + +GVBool gvGetGlobalMute(void) +{ + return gviGetGlobalMute(); +} + + +/****************** +** CUSTOM DEVICE ** +******************/ +GVDevice gvNewCustomDevice(GVDeviceType type) +{ + return gviCustomNewDevice(type); +} + +GVBool gvGetCustomPlaybackAudio(GVDevice device, GVSample * audio, int numSamples) +{ + return gviGetCustomPlaybackAudio(device, audio, numSamples); +} + +GVBool gvSetCustomCaptureAudio(GVDevice device, const GVSample * audio, int numSamples, + GVByte * packet, int * packetLen, GVFrameStamp * frameStamp, GVScalar * volume) +{ + return gviSetCustomCaptureAudio(device, audio, numSamples, packet, packetLen, frameStamp, volume); +} + +/************* +** CHANNELS ** +*************/ +int gvGetNumChannels(GVDevice device, GVDeviceType type) +{ + if(device->m_methods.m_getNumChannels) + return device->m_methods.m_getNumChannels(device, type); + return 0; +} + +void gvGetChannelName(GVDevice device, GVDeviceType type, int channel, gsi_char name[GV_CHANNEL_NAME_LEN]) +{ + if(device->m_methods.m_getNumChannels) + device->m_methods.m_getChannelName(device, type, channel, name); +} + +void gvSetChannel(GVDevice device, GVDeviceType type, int channel) +{ + if(device->m_methods.m_setChannel) + device->m_methods.m_setChannel(device, type, channel); +} + +int gvGetChannel(GVDevice device, GVDeviceType type) +{ + if(device->m_methods.m_getChannel) + return device->m_methods.m_getChannel(device, type); + return 0; +} diff --git a/code/gamespy/Voice2/gvMain.h b/code/gamespy/Voice2/gvMain.h new file mode 100644 index 00000000..8df1bd20 --- /dev/null +++ b/code/gamespy/Voice2/gvMain.h @@ -0,0 +1,18 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_MAIN_H_ +#define _GV_MAIN_H_ + +#include "gv.h" +#include "../darray.h" + +#endif diff --git a/code/gamespy/Voice2/gvOSXAudio.c b/code/gamespy/Voice2/gvOSXAudio.c new file mode 100644 index 00000000..48ebfbe7 --- /dev/null +++ b/code/gamespy/Voice2/gvOSXAudio.c @@ -0,0 +1,1465 @@ +#include "gvOSXAudio.h" +#include "gvDevice.h" +#include "gvCodec.h" +#include "gvSource.h" +#include "gvUtil.h" +#include +#include +#include + +#if !defined(_MACOSX) +#error This file should only be used with MacOS X +#endif + +#if !defined(GVI_VOLUME_IN_SOFTWARE) + #define GVI_VOLUME_IN_SOFTWARE 1 +#endif + +/********** +** TYPES ** +**********/ +// when allocated, enough memory is allocated to fit an entire +// captured frame into the m_frame array +typedef struct GVICapturedFrame +{ + GVFrameStamp m_frameStamp; + struct GVICapturedFrame * m_next; + // m_frame must be the last member of this struct + GVSample m_frame[1]; +} GVICapturedFrame; + +typedef struct +{ + GVBool m_playing; + AudioConverterRef m_playbackConverter; + AudioStreamBasicDescription m_playbackStreamDescriptor; + GVFrameStamp m_playbackClock; + GVSample * m_playbackBuffer; + GVISourceList m_playbackSources; + int m_playbackChannel; +#if GVI_VOLUME_IN_SOFTWARE + GVScalar m_playbackVolume; +#endif + + GVBool m_capturing; + AudioConverterRef m_captureConverter; + AudioStreamBasicDescription m_captureStreamDescriptor; + GVFrameStamp m_captureClock; + GVSample * m_captureBuffer; + GVICapturedFrame m_capturedFrames; + GVICapturedFrame m_captureAvailableFrames; + GVScalar m_captureThreshold; + GVFrameStamp m_captureLastCrossedThresholdTime; + int m_captureChannel; +#if GVI_VOLUME_IN_SOFTWARE + GVScalar m_captureVolume; +#endif + + pthread_mutex_t m_mutex; + GVBool m_unplugged; +} GVIHardwareData; + +typedef struct +{ + GVIDevice * m_device; + AudioBufferList * m_capturedAudio; + GVBool m_used; +} GVICaptureConverterData; + +/************ +** GLOBALS ** +************/ +static GVIDeviceList GVIDevices; +static AudioStreamBasicDescription GVIVoiceFormat; + +/************** +** FUNCTIONS ** +**************/ +static void gviFreeCapturedFrames(GVICapturedFrame * frameHead) +{ + GVICapturedFrame * frame; + while(frameHead->m_next) + { + frame = frameHead->m_next; + frameHead->m_next = frame->m_next; + gsifree(frame); + } +} + +static GVICapturedFrame * gviPopFirstFrame(GVICapturedFrame * frameHead) +{ + GVICapturedFrame * frame = frameHead->m_next; + if(frame) + { + frameHead->m_next = frame->m_next; + frame->m_next = NULL; + } + return frame; +} + +static void gviPushFirstFrame(GVICapturedFrame * frameHead, GVICapturedFrame * frame) +{ + frame->m_next = frameHead->m_next; + frameHead->m_next = frame; +} + +static void gviPushLastFrame(GVICapturedFrame * frameHead, GVICapturedFrame * frame) +{ + GVICapturedFrame * lastFrame; + + // find the last frame in the list + lastFrame = frameHead; + while(lastFrame->m_next) + lastFrame = lastFrame->m_next; + + // add this frame to the end of the capture list + lastFrame->m_next = frame; +} + +static GVBool gviLockDevice(GVIHardwareData * data) +{ + int rcode; + rcode = pthread_mutex_lock(&data->m_mutex); + return (rcode == 0)?GVTrue:GVFalse; +} + +static void gviUnlockDevice(GVIHardwareData * data) +{ + pthread_mutex_unlock(&data->m_mutex); +} + +static void gviCleanupPlayback(GVIHardwareData * data) +{ + if(data->m_playbackSources) + gviFreeSourceList(data->m_playbackSources); + if(data->m_playbackConverter) + AudioConverterDispose(data->m_playbackConverter); + gsifree(data->m_playbackBuffer); +} + +static void gviCleanupCapture(GVIHardwareData * data) +{ + if(data->m_captureConverter) + AudioConverterDispose(data->m_captureConverter); + gviFreeCapturedFrames(&data->m_captureAvailableFrames); + gviFreeCapturedFrames(&data->m_capturedFrames); + gsifree(data->m_captureBuffer); +} + +static void gviFreeArrayDevice(void * elem) +{ + GVIDevice * device = *(GVIDevice **)elem; + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + // stop the device + device->m_methods.m_stopDevice(device, device->m_types); + + // free the mutex + pthread_mutex_destroy(&data->m_mutex); + + // playback specific cleanup + if(device->m_types & GV_PLAYBACK) + { + gviCleanupPlayback(data); + } + + // capture specific cleanup + if(device->m_types & GV_CAPTURE) + { + gviCleanupCapture(data); + } + + gviFreeDevice(device); +} + +GVBool gviHardwareStartup(void) +{ + // create the devices array + GVIDevices = gviNewDeviceList(gviFreeArrayDevice); + if(!GVIDevices) + return GVFalse; + + // setup the format descriptor for the GV format + memset(&GVIVoiceFormat, 0, sizeof(AudioStreamBasicDescription)); + GVIVoiceFormat.mSampleRate = (Float64)GV_SAMPLES_PER_SECOND; + GVIVoiceFormat.mFormatID = kAudioFormatLinearPCM; + GVIVoiceFormat.mFormatFlags = kAudioFormatFlagsNativeEndian|kAudioFormatFlagIsSignedInteger|kAudioFormatFlagIsPacked; + GVIVoiceFormat.mBytesPerPacket = GV_BYTES_PER_SAMPLE; + GVIVoiceFormat.mFramesPerPacket = 1; + GVIVoiceFormat.mBytesPerFrame = GV_BYTES_PER_SAMPLE; + GVIVoiceFormat.mChannelsPerFrame = 1; + GVIVoiceFormat.mBitsPerChannel = GV_BITS_PER_SAMPLE; + + return GVTrue; +} + +void gviHardwareCleanup(void) +{ + // cleanup the devices + if(GVIDevices) + { + gviFreeDeviceList(GVIDevices); + GVIDevices = NULL; + } +} + +void gviHardwareThink(void) +{ + GVIDevice * device; + int num; + int i; + + if(!GVIDevices) + return; + + // loop through backwards so that we can remove devices as we go + num = gviGetNumDevices(GVIDevices); + for(i = (num - 1) ; i >= 0 ; i--) + { + // get the device + device = gviGetDevice(GVIDevices, i); + + // has it been unplugged? + if(((GVIHardwareData*)device->m_data)->m_unplugged) + { + gviDeviceUnplugged(device); + } + } +} + +static GVBool gviCFStringToString(CFStringRef ref, gsi_char str[], int strLen) +{ +#if !defined(GSI_UNICODE) + + Boolean result = CFStringGetCString(ref, str, strLen, kCFStringEncodingASCII); + return (result == true)?GVTrue:GVFalse; + +#else + + CFRange range; + + range.location = 0; + range.length = CFStringGetLength(ref); + range.length = min(range.length, strLen - 1); + + CFStringGetCharacters(ref, range, str); + str[range.length] = '\0'; + + return GVTrue; + +#endif +} + +static int gviCountChannels(AudioDeviceID deviceID, bool input) +{ + OSStatus result; + UInt32 size; + AudioBufferList * list; + int numChannels; + int i; + + // get the size of the buffer list + result = AudioDeviceGetPropertyInfo(deviceID, 0, input, kAudioDevicePropertyStreamConfiguration, &size, NULL); + if(result != noErr) + return 0; + + // allocate the buffer list + list = (AudioBufferList *)gsimalloc(size); + if(list == NULL) + return 0; + + // fill the buffer list + result = AudioDeviceGetProperty(deviceID, 0, input, kAudioDevicePropertyStreamConfiguration, &size, list); + if(result != noErr) + { + gsifree(list); + return 0; + } + + // count the number of channels + numChannels = 0; + for(i = 0 ; i < list->mNumberBuffers ; i++) + numChannels += list->mBuffers[i].mNumberChannels; + + // free the list + gsifree(list); + + return numChannels; +} + +static GVBool gviFillDeviceInfo(AudioDeviceID deviceID, GVDeviceInfo * device, GVDeviceType types) +{ + OSStatus result; + UInt32 size; + UInt32 isAlive; + GVDeviceType supportedTypes; + CFStringRef nameRef; + + // make sure the device is alive + size = sizeof(UInt32); + result = AudioDeviceGetProperty(deviceID, 0, true, kAudioDevicePropertyDeviceIsAlive, &size, &isAlive); + if((result != noErr) || (isAlive != 1)) + return GVFalse; + + // set the hardware type + device->m_hardwareType = GVHardwareMacOSX; + + // store the id + device->m_id = deviceID; + + // figure out what types it supports + supportedTypes = (GVDeviceType)0; + if(gviCountChannels(deviceID, true) > 0) + supportedTypes |= GV_CAPTURE; + if(gviCountChannels(deviceID, false) > 0) + supportedTypes |= GV_PLAYBACK; + + // if there's nothing in common, return + if(!(supportedTypes & types)) + return GVFalse; + + // store the supported types + device->m_deviceType = supportedTypes; + + // get the name + size = sizeof(nameRef); + result = AudioDeviceGetProperty(deviceID, 0, (supportedTypes & GV_CAPTURE)?true:false, kAudioDevicePropertyDeviceNameCFString, &size, &nameRef); + if(result != noErr) + return GVFalse; + + // if there's a blank name, we'll supply our own + if(CFStringGetLength(nameRef) <= 0) + { + CFRelease(nameRef); + nameRef = NULL; + } + + // if there's no name, give a default + if(nameRef == NULL) + { + if(supportedTypes == GV_CAPTURE) + nameRef = CFSTR("Capture Device"); + else if(supportedTypes == GV_PLAYBACK) + nameRef = CFSTR("Playback Device"); + else + nameRef = CFSTR("Capture & Playback Device"); + } + + // convert it to an array of gsi_char + gviCFStringToString(nameRef, device->m_name, GV_DEVICE_NAME_LEN); + + // release the CFString + CFRelease(nameRef); + + return GVTrue; +} + +int gviHardwareListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types) +{ + OSStatus result; + UInt32 size; + int numDevices; + int deviceCount; + AudioDeviceID * deviceIDs; + AudioDeviceID defaultCaptureDeviceID; + AudioDeviceID defaultPlaybackDeviceID; + GVDeviceType defaultTypes; + int i; + + // get the default device ids + size = sizeof(AudioDeviceID); + result = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &size, &defaultCaptureDeviceID); + if(result != noErr) + defaultCaptureDeviceID = kAudioDeviceUnknown; + result = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &size, &defaultPlaybackDeviceID); + if(result != noErr) + defaultPlaybackDeviceID = kAudioDeviceUnknown; + + // get the size of the device ids array + result = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &size, NULL); + if(result != noErr) + return 0; + + // calc the number of devices + numDevices = (size / sizeof(AudioDeviceID)); + + // allocate the array of device ids + deviceIDs = (AudioDeviceID *)gsimalloc(size); + if(deviceIDs == NULL) + return 0; + + // get the device ids + result = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &size, deviceIDs); + if(result != noErr) + { + gsifree(deviceIDs); + return 0; + } + + // fill the array of devices with info + deviceCount = 0; + for(i = 0 ; (i < maxDevices) && (deviceCount < numDevices) ; i++) + { + // try to add the device to the list + GVBool addedDevice = gviFillDeviceInfo(deviceIDs[i], &devices[deviceCount], types); + + // check if it was added successfully + if(addedDevice) + { + // check it against the defaults + defaultTypes = (GVDeviceType)0; + if(deviceIDs[i] == defaultCaptureDeviceID) + defaultTypes |= GV_CAPTURE; + if(deviceIDs[i] == defaultPlaybackDeviceID) + defaultTypes |= GV_PLAYBACK; + devices[deviceCount].m_defaultDevice = defaultTypes; + + // increment the count + deviceCount++; + } + } + + // free the array + gsifree(deviceIDs); + + return deviceCount; +} + +static void gviHardwareFreeDevice(GVDevice device) +{ + // delete it from the array + // it will clear out internal data in the array's free function + gviDeleteDeviceFromList(GVIDevices, device); +} + +static OSStatus gviAudioConverterCaptureProc(AudioConverterRef inAudioConverter, + UInt32 * ioNumberDataPackets, + AudioBufferList * ioData, + AudioStreamPacketDescription ** outDataPacketDescription, + void * inUserData) +{ + GVICaptureConverterData * converterData = (GVICaptureConverterData *)inUserData; + GVIDevice * device = converterData->m_device; + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + AudioBufferList * bufferList = converterData->m_capturedAudio; + + // check if this data has already been used + if(converterData->m_used) + return (OSStatus)1; + + // it only wants one buffer + ioData->mBuffers[0].mNumberChannels = bufferList->mBuffers[data->m_captureChannel].mNumberChannels; + ioData->mBuffers[0].mData = bufferList->mBuffers[data->m_captureChannel].mData; + ioData->mBuffers[0].mDataByteSize = bufferList->mBuffers[data->m_captureChannel].mDataByteSize; + + // fill in the number of samples we provided + *ioNumberDataPackets = (ioData->mBuffers[0].mDataByteSize / data->m_captureStreamDescriptor.mBytesPerPacket); + + // we've used the buffer + converterData->m_used = GVTrue; + + return noErr; +} + +static OSStatus gviHardwareCaptureIOProc(AudioDeviceID inDevice, + const AudioTimeStamp * inNow, + const AudioBufferList * inInputData, + const AudioTimeStamp * inInputTime, + AudioBufferList * outOutputData, + const AudioTimeStamp * inOutputTime, + void * inClientData) +{ + GVIDevice * device = (GVIDevice *)inClientData; + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + OSStatus result; + UInt32 size; + GVICaptureConverterData converterData; + AudioBufferList bufferList; + GVICapturedFrame * frame; + + // get a lock on the device + if(!gviLockDevice(data)) + return (OSStatus)1; + + // make sure we are capturing + if(data->m_capturing) + { + // setup the buffer list + bufferList.mNumberBuffers = 1; + bufferList.mBuffers[0].mNumberChannels = 1; + bufferList.mBuffers[0].mDataByteSize = GVIBytesPerFrame; + bufferList.mBuffers[0].mData = data->m_captureBuffer; + + // setup the converter data struct + converterData.m_device = device; + converterData.m_capturedAudio = (AudioBufferList *)inInputData; + converterData.m_used = GVFalse; + + // loop while it is converting data + do + { + // request one frame + size = GVISamplesPerFrame; + + // convert the captured data into our format + result = AudioConverterFillComplexBuffer(data->m_captureConverter, gviAudioConverterCaptureProc, &converterData, &size, &bufferList, NULL); + + // was there enough to fill a buffer + if(result == noErr) + { + // get a frame + frame = gviPopFirstFrame(&data->m_captureAvailableFrames); + + // if there aren't any available frames, repurpose the oldest captured frame + if(!frame) + { + frame = gviPopFirstFrame(&data->m_capturedFrames); + assert(frame); + if(!frame) + break; + } + + // setup the frame + memcpy(frame->m_frame, data->m_captureBuffer, GVIBytesPerFrame); + frame->m_frameStamp = data->m_captureClock; + + // increment the capture clock + data->m_captureClock++; + + // add this frame to the end of the capture list + gviPushLastFrame(&data->m_capturedFrames, frame); + } + } + while(result == noErr); + } + + // release the device lock + gviUnlockDevice(data); + + return noErr; +} + +static GVBool gviHardwareStartCapture(GVDevice device) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + OSStatus result; + + // now capturing + data->m_capturing = GVTrue; + + // add the IO proc + result = AudioDeviceAddIOProc((AudioDeviceID)device->m_deviceID, gviHardwareCaptureIOProc, device); + if(result != noErr) + { + data->m_capturing = GVFalse; + return GVFalse; + } + + // start it + result = AudioDeviceStart((AudioDeviceID)device->m_deviceID, gviHardwareCaptureIOProc); + if(result != noErr) + { + data->m_capturing = GVFalse; + return GVFalse; + } + + return GVTrue; +} + +static OSStatus gviAudioConverterPlaybackProc(AudioConverterRef inAudioConverter, + UInt32 * ioNumberDataPackets, + AudioBufferList * ioData, + AudioStreamPacketDescription ** outDataPacketDescription, + void * inUserData) +{ + GVIDevice * device = (GVIDevice *)inUserData; + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + GVBool wroteToBuffer = GVFalse; + + // make sure we are playing + if(data->m_playing) + { + // write sources to the playback buffer + wroteToBuffer = gviWriteSourcesToBuffer(data->m_playbackSources, data->m_playbackClock, data->m_playbackBuffer, 1); + } + + // clear it if nothing was written + if(!data->m_playing || !wroteToBuffer) + { + memset(data->m_playbackBuffer, 0, (size_t)GVIBytesPerFrame); + } + // check if we need to adjust the volume + else if(data->m_playbackVolume < 1.0) + { + int i; + for(i = 0 ; i < GVISamplesPerFrame ; i++) + data->m_playbackBuffer[i] = (GVSample)(data->m_playbackBuffer[i] * data->m_playbackVolume); + } + + // filter + if(device->m_playbackFilterCallback) + device->m_playbackFilterCallback(device, data->m_playbackBuffer, data->m_playbackClock); + + // setup the output data + ioData->mBuffers[0].mNumberChannels = 1; + ioData->mBuffers[0].mDataByteSize = GVIBytesPerFrame; + ioData->mBuffers[0].mData = (void *)data->m_playbackBuffer; + + // we wrote one frame + *ioNumberDataPackets = GVISamplesPerFrame; + + // update the clock + data->m_playbackClock++; + + return noErr; +} + +static OSStatus gviHardwarePlaybackIOProc(AudioDeviceID inDevice, + const AudioTimeStamp * inNow, + const AudioBufferList * inInputData, + const AudioTimeStamp * inInputTime, + AudioBufferList * outOutputData, + const AudioTimeStamp * inOutputTime, + void * inClientData) +{ + GVIDevice * device = (GVIDevice *)inClientData; + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + AudioBufferList bufferList; + OSStatus result; + UInt32 size; + + // get a lock on the device + if(!gviLockDevice(data)) + return (OSStatus)1; + + // calculate the number of samples the proc wants + size = (outOutputData->mBuffers[0].mDataByteSize / data->m_playbackStreamDescriptor.mBytesPerFrame); + + // setup our own buffer list, pointing at the channel (buffer) we want + bufferList.mNumberBuffers = 1; + bufferList.mBuffers[0] = outOutputData->mBuffers[data->m_playbackChannel]; + + // fill the buffer using the callback + result = AudioConverterFillComplexBuffer(data->m_playbackConverter, gviAudioConverterPlaybackProc, device, &size, &bufferList, NULL); + if(result != noErr) + return (OSStatus)1; + + // release the device lock + gviUnlockDevice(data); + + return noErr; +} + +static GVBool gviHardwareStartPlayback(GVDevice device) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + OSStatus result; + + // now playing + data->m_playing = GVTrue; + + // reset the playback clock + data->m_playbackClock = 0; + + // add the IO proc + result = AudioDeviceAddIOProc((AudioDeviceID)device->m_deviceID, gviHardwarePlaybackIOProc, device); + if(result != noErr) + { + data->m_playing = GVFalse; + return GVFalse; + } + + // start it + result = AudioDeviceStart((AudioDeviceID)device->m_deviceID, gviHardwarePlaybackIOProc); + if(result != noErr) + { + data->m_playing = GVFalse; + return GVFalse; + } + + return GVTrue; +} + +static GVBool gviHardwareStartDevice(GVDevice device, GVDeviceType type) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + GVBool result; + + // get a lock on the device + if(!gviLockDevice(data)) + return GVFalse; + + // capture + if((type & GV_CAPTURE) && !data->m_capturing) + { + result = gviHardwareStartCapture(device); + if(!result) + { + gviUnlockDevice(data); + return GVFalse; + } + } + + // playback + if((type & GV_PLAYBACK) && !data->m_playing) + { + result = gviHardwareStartPlayback(device); + if(!result) + { + if(type & GV_CAPTURE) + device->m_methods.m_stopDevice(device, GV_CAPTURE); + gviUnlockDevice(data); + return GVFalse; + } + } + + // release the device lock + gviUnlockDevice(data); + + return GVTrue; +} + +static void gviHardwareStopDevice(GVDevice device, GVDeviceType type) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + GVICapturedFrame * frame; + + // lock the device + gviLockDevice(data); + + // capture + if((type & GV_CAPTURE) && data->m_capturing) + { + // unlock so the ioproc can stop + gviUnlockDevice(data); + + // stop io + AudioDeviceStop((AudioDeviceID)device->m_deviceID, gviHardwareCaptureIOProc); + + // relock + gviLockDevice(data); + + // remove the IO proc + AudioDeviceRemoveIOProc((AudioDeviceID)device->m_deviceID, gviHardwareCaptureIOProc); + + // move captured frames back to the available frames list + while((frame = gviPopFirstFrame(&data->m_capturedFrames)) != NULL) + gviPushFirstFrame(&data->m_captureAvailableFrames, frame); + + // no longer capturing + data->m_capturing = GVFalse; + } + + // playback + if((type & GV_PLAYBACK) && data->m_playing) + { + // unlock so the ioproc can stop + gviUnlockDevice(data); + + // stop io + AudioDeviceStop((AudioDeviceID)device->m_deviceID, gviHardwarePlaybackIOProc); + + // relock + gviLockDevice(data); + + // remove the IO proc + AudioDeviceRemoveIOProc((AudioDeviceID)device->m_deviceID, gviHardwarePlaybackIOProc); + + // clear any pending sources & buffers + gviClearSourceList(data->m_playbackSources); + + // no longer playing + data->m_playing = GVFalse; + } + + // unlock the device + gviUnlockDevice(data); +} + +static GVBool gviHardwareIsDeviceStarted(GVDevice device, GVDeviceType type) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + GVBool result = GVFalse; + + // get a lock on the device + if(!gviLockDevice(data)) + return GVFalse; + + // figure out the result + if(type == GV_PLAYBACK) + result = data->m_playing; + else if(type == GV_CAPTURE) + result = data->m_capturing; + else if (type == GV_CAPTURE_AND_PLAYBACK) + result = (data->m_playing && data->m_capturing)?GVTrue:GVFalse; + + // release the device lock + gviUnlockDevice(data); + + return result; +} + +#if GVI_VOLUME_IN_SOFTWARE + + static void gviSetDeviceVolume(GVDevice device, GVBool isInput, GVScalar volume) + { + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + if(isInput) + data->m_captureVolume = volume; + else + data->m_playbackVolume = volume; + } + + static GVScalar gviGetDeviceVolume(GVDevice device, GVBool isInput) + { + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + if(isInput) + return data->m_captureVolume; + else + return data->m_playbackVolume; + } + +#else + + static void gviSetDeviceVolume(GVDevice device, GVBool isInput, GVScalar volume) + { + Float32 vol = volume; + int channel; + + // set the volume on all three channels + // this covers mono and stereo devices + for(channel = 0 ; channel < 3 ; channel++) + AudioDeviceSetProperty(device->m_deviceID, NULL, channel, isInput, kAudioDevicePropertyVolumeScalar, sizeof(Float32), &vol); + } + + static GVBool gviGetChannelVolume(GVDevice device, GVBool isInput, int channel, Float32 * volume) + { + OSStatus result; + UInt32 size = sizeof(Float32); + + // get the volume for a specific channel + result = AudioDeviceGetProperty(device->m_deviceID, channel, isInput, kAudioDevicePropertyVolumeScalar, &size, volume); + + return (result == noErr)?GVTrue:GVFalse; + } + + static GVScalar gviGetDeviceVolume(GVDevice device, GVBool isInput) + { + GVBool result; + Float32 channels[2]; + + // check for mono + result = gviGetChannelVolume(device, isInput, 0, &channels[0]); + if(result) + return (GVScalar)channels[0]; + + // get left + result = gviGetChannelVolume(device, isInput, 1, &channels[0]); + if(!result) + return 0; + + // get right + result = gviGetChannelVolume(device, isInput, 2, &channels[1]); + if(!result) + return (GVScalar)channels[0]; + + // return a mix of left and right + return (GVScalar)((channels[0] + channels[1]) / 2); + } + +#endif + +static void gviHardwareSetDeviceVolume(GVDevice device, GVDeviceType type, GVScalar volume) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + // get a lock on the device + if(!gviLockDevice(data)) + return; + + // set the volume + if(type & GV_PLAYBACK) + gviSetDeviceVolume(device, GVFalse, volume); + if(type & GV_CAPTURE) + gviSetDeviceVolume(device, GVTrue, volume); + + // release the device lock + gviUnlockDevice(data); +} + +static GVScalar gviHardwareGetDeviceVolume(GVDevice device, GVDeviceType type) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + GVScalar volume = 0; + + // get a lock on the device + if(!gviLockDevice(data)) + return 0; + + // get the volume + if(type & GV_PLAYBACK) + volume = gviGetDeviceVolume(device, GVFalse); + else if(type & GV_CAPTURE) + volume = gviGetDeviceVolume(device, GVTrue); + + // release the device lock + gviUnlockDevice(data); + + return volume; +} + +static void gviHardwareSetCaptureThreshold(GVDevice device, GVScalar threshold) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + // get a lock on the device + if(!gviLockDevice(data)) + return; + + // store the threshold + data->m_captureThreshold = threshold; + + // release the device lock + gviUnlockDevice(data); +} + +static GVScalar gviHardwareGetCaptureThreshold(GVDevice device) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + GVScalar threshold; + + // get a lock on the device + if(!gviLockDevice(data)) + return GVFalse; + + // store the threshold + threshold = data->m_captureThreshold; + + // release the device lock + gviUnlockDevice(data); + + return threshold; +} + +static GVBool gviHardwareCapturePacket(GVDevice device, GVByte * packet, int * len, GVFrameStamp * frameStamp, GVScalar * volume) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + int numFrames; + GVICapturedFrame * frame; + GVScalar frameVolume; + GVBool overThreshold; + + // get a lock on the device + if(!gviLockDevice(data)) + return GVFalse; + + // calculate the number of frames we can put in the packet + numFrames = (*len / GVIEncodedFrameSize); + + // clear the len and volume + *len = 0; + if(volume) + *volume = 0; + + // don't do anything if we're not capturing + if(!data->m_capturing) + { + gviUnlockDevice(data); + return GVFalse; + } + + // don't do anything if there isn't room for a frame + if(numFrames < 1) + { + gviUnlockDevice(data); + return GVFalse; + } + + do + { + // get a frame + frame = gviPopFirstFrame(&data->m_capturedFrames); + if(!frame) + { + gviUnlockDevice(data); + return GVFalse; + } + + // calculate the volume + frameVolume = gviGetSamplesVolume(frame->m_frame, GVISamplesPerFrame); + + // check against the threshold + overThreshold = (frameVolume >= data->m_captureThreshold); + if(overThreshold) + { + // update the time at which we crossed the threshold + data->m_captureLastCrossedThresholdTime = frame->m_frameStamp; + } + else + { + // check if we are still within the hold time + overThreshold = ((GVFrameStamp)(frame->m_frameStamp - data->m_captureLastCrossedThresholdTime) < GVI_HOLD_THRESHOLD_FRAMES); + } + + // check if this should be captured + if(overThreshold) + { + // scale the data + if(data->m_captureVolume < 1.0) + { + int j; + for(j = 0 ; j < GVISamplesPerFrame ; j++) + frame->m_frame[j] = (GVSample)(frame->m_frame[j] * data->m_captureVolume); + } + + // filter + if(device->m_captureFilterCallback) + device->m_captureFilterCallback(device, frame->m_frame, frame->m_frameStamp); + + // encode the frame into the packet + gviEncode(packet, frame->m_frame); + *len = GVIEncodedFrameSize; + *frameStamp = frame->m_frameStamp; + if(volume) + *volume = frameVolume; + } + + // put the frame back in the available list + gviPushFirstFrame(&data->m_captureAvailableFrames, frame); + } + while(!overThreshold); + + // release the device lock + gviUnlockDevice(data); + + return GVTrue; +} + +static int gviHardwareGetAvailableCaptureBytes(GVDevice device) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + GVICapturedFrame * frame; + int count = 0; + + // don't do anything if we're not capturing + if(!data->m_capturing) + return 0; + + // count the number of captured frames + for(frame = data->m_capturedFrames.m_next ; frame ; frame = frame->m_next) + count++; + + // return the number of bytes + return (count * GVIEncodedFrameSize); +} + +static void gviHardwarePlayPacket(GVDevice device, const GVByte * packet, int len, GVSource source, GVFrameStamp frameStamp, GVBool mute) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + // get a lock on the device + if(!gviLockDevice(data)) + return; + + // check if we're not playing + if(data->m_playing) + { + //add it + gviAddPacketToSourceList(data->m_playbackSources, packet, len, source, frameStamp, mute, data->m_playbackClock); + } + + // release the device lock + gviUnlockDevice(data); +} + +static GVBool gviHardwareIsSourceTalking(GVDevice device, GVSource source) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + GVBool isTalking = GVFalse; + + // get a lock on the device + if(!gviLockDevice(data)) + return GVFalse; + + // check if we're playing + if(data->m_playing) + { + // check if the source is talking + isTalking = gviIsSourceTalking(data->m_playbackSources, source); + } + + // release the device lock + gviUnlockDevice(data); + + return isTalking; +} + +static int gviHardwareListTalkingSources(GVDevice device, GVSource sources[], int maxSources) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + int numTalking = 0; + + // get a lock on the device + if(!gviLockDevice(data)) + return 0; + + // check if we're not playing + if(data->m_playing) + { + // list them + numTalking = gviListTalkingSources(data->m_playbackSources, sources, maxSources); + } + + // release the device lock + gviUnlockDevice(data); + + return numTalking; +} + +static int gviHardwareGetNumChannels(GVDevice device, GVDeviceType type) +{ + OSStatus result; + UInt32 size; + int numStreams; + + result = AudioDeviceGetPropertyInfo(device->m_deviceID, 0, (type & GV_CAPTURE)?true:false, kAudioDevicePropertyStreams, &size, NULL); + if(result != noErr) + return 1; + + numStreams = (size / sizeof(AudioStreamID)); + + return numStreams; +} + +static void gviHardwareGetChannelName(GVDevice device, GVDeviceType type, int index, gsi_char name[GV_CHANNEL_NAME_LEN]) +{ + OSStatus result; + UInt32 size; + CFStringRef nameRef; + AudioStreamID * streamIDs; + int num; + + // clear the name in case we abort + name[0] = '\0'; + + // figure out how many channels there are + num = gviHardwareGetNumChannels(device, type); + + // make sure the index we have is valid + if((index < 0) || (index >= num)) + return; + + // allocate memory for the stream IDs + size = (num * sizeof(AudioStreamID)); + streamIDs = (AudioStreamID*)gsimalloc(size); + + // get the stream IDs + result = AudioDeviceGetProperty(device->m_deviceID, 0, (type & GV_CAPTURE)?true:false, kAudioDevicePropertyStreams, &size, streamIDs); + if(result == noErr) + { + // get the CFString for this stream + size = sizeof(CFStringRef); + result = AudioStreamGetProperty(streamIDs[index], 0, kAudioDevicePropertyDeviceNameCFString, &size, &nameRef); + if(result != noErr) + nameRef = NULL; + } + + // if there's a blank name, we'll supply our own + if(nameRef && (CFStringGetLength(nameRef) <= 0)) + { + CFRelease(nameRef); + nameRef = NULL; + } + + // if there's no name, give a default + if(nameRef == NULL) + { + CFStringRef format; + + if(type & GV_CAPTURE) + format = CFSTR("Input Channel %d"); + else + format = CFSTR("Output Channel %d"); + + nameRef = CFStringCreateWithFormat(NULL, NULL, format, index); + + CFRelease(format); + } + + // convert it to an array of gsi_char + gviCFStringToString(nameRef, name, GV_CHANNEL_NAME_LEN); + + // release the CFString + CFRelease(nameRef); + + // free the streamIDs + gsifree(streamIDs); +} + +static void gviHardwareSetChannel(GVDevice device, GVDeviceType type, int index) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + int num; + + num = gviHardwareGetNumChannels(device, type); + + if((index < 0) || (index >= num)) + return; + + if(type & GV_CAPTURE) + data->m_captureChannel = index; + else + data->m_playbackChannel = index; +} + +static int gviHardwareGetChannel(GVDevice device, GVDeviceType type) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + if(type & GV_CAPTURE) + return data->m_captureChannel; + else + return data->m_playbackChannel; +} + +OSStatus gviPropertyListener(AudioDeviceID inDevice, + UInt32 inChannel, + Boolean isInput, + AudioDevicePropertyID inPropertyID, + void* inClientData) +{ + GVIDevice * device = (GVIDevice*)inClientData; + GVIHardwareData * data = (GVIHardwareData*)device->m_data; + UInt32 value; + UInt32 len = sizeof(UInt32); + OSStatus result; + + result = AudioDeviceGetProperty(device->m_deviceID, 0, isInput, kAudioDevicePropertyDeviceIsAlive, &len, &value); + if((result == noErr) && (value == 0)) + { + // the device has been unplugged + gviLockDevice(device->m_data); + data->m_unplugged = GVTrue; + gviUnlockDevice(device->m_data); + } + + return (OSStatus)0; +} + +static GVBool gviHardwareInitCapture(GVIDevice * device) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + UInt32 size; + OSStatus result; + GVICapturedFrame * frame; + int numCaptureBufferBytes; + int numCaptureBufferFrames; + int i; + + // get the capture format + size = sizeof(AudioStreamBasicDescription); + result = AudioDeviceGetProperty(device->m_deviceID, 0, true, kAudioDevicePropertyStreamFormat, &size, &data->m_captureStreamDescriptor); + if(result != noErr) + return GVFalse; + + // create a converter from the capture format to the GV format + result = AudioConverterNew(&data->m_captureStreamDescriptor, &GVIVoiceFormat, &data->m_captureConverter); + if(result != noErr) + return GVFalse; + + // allocate a capture buffer + data->m_captureBuffer = (GVSample *)gsimalloc(GVIBytesPerFrame); + if(!data->m_captureBuffer) + { + AudioConverterDispose(data->m_captureConverter); + return GVFalse; + } + + // allocate space for holding captured frames + numCaptureBufferBytes = gviMultiplyByBytesPerMillisecond(GVI_CAPTURE_BUFFER_MILLISECONDS); + numCaptureBufferBytes = gviRoundUpToNearestMultiple(numCaptureBufferBytes, GVIBytesPerFrame); + numCaptureBufferFrames = (numCaptureBufferBytes / GVIBytesPerFrame); + for(i = 0 ; i < numCaptureBufferFrames ; i++) + { + frame = (GVICapturedFrame *)gsimalloc(sizeof(GVICapturedFrame) + GVIBytesPerFrame - sizeof(GVSample)); + if(!frame) + { + gviFreeCapturedFrames(&data->m_captureAvailableFrames); + gsifree(data->m_captureBuffer); + AudioConverterDispose(data->m_captureConverter); + return GVFalse; + } + gviPushFirstFrame(&data->m_captureAvailableFrames, frame); + } + + // init the last crossed time + data->m_captureLastCrossedThresholdTime = (data->m_captureClock - GVI_HOLD_THRESHOLD_FRAMES - 1); + + // add property listener + AudioDeviceAddPropertyListener(device->m_deviceID, 0, true, kAudioDevicePropertyDeviceIsAlive, gviPropertyListener, device); + +#if GVI_VOLUME_IN_SOFTWARE + // init volume + data->m_captureVolume = (GVScalar)1.0; +#endif + + return GVTrue; +} + +static GVBool gviHardwareInitPlayback(GVIDevice * device) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + UInt32 size; + OSStatus result; + UInt32 primeMethod; + SInt32 channelMap[100]; + int i; + + // create the array of sources + data->m_playbackSources = gviNewSourceList(); + if(!data->m_playbackSources) + return GVFalse; + + // get the playback format + size = sizeof(AudioStreamBasicDescription); + result = AudioDeviceGetProperty(device->m_deviceID, 0, false, kAudioDevicePropertyStreamFormat, &size, &data->m_playbackStreamDescriptor); + if(result != noErr) + { + gviFreeSourceList(data->m_playbackSources); + return GVFalse; + } + + // create a converter from the GV format to the playback format + result = AudioConverterNew(&GVIVoiceFormat, &data->m_playbackStreamDescriptor, &data->m_playbackConverter); + if(result != noErr) + { + gviFreeSourceList(data->m_playbackSources); + return GVFalse; + } + + // set it to do no priming + primeMethod = kConverterPrimeMethod_None; + result = AudioConverterSetProperty(data->m_playbackConverter, kAudioConverterPrimeMethod, sizeof(UInt32), &primeMethod); + if(result != noErr) + { + AudioConverterDispose(data->m_playbackConverter); + gviFreeSourceList(data->m_playbackSources); + return GVFalse; + } + + // setup the converter to map the input channel to all output channels + result = AudioConverterGetPropertyInfo(data->m_playbackConverter, kAudioConverterChannelMap, &size, NULL); + if(result == noErr) + { + result = AudioConverterGetProperty(data->m_playbackConverter, kAudioConverterChannelMap, &size, channelMap); + if(result == noErr) + { + for(i = 0 ; i < (size / sizeof(SInt32)) ; i++) + channelMap[i] = 0; + + AudioConverterSetProperty(data->m_playbackConverter, kAudioConverterChannelMap, size, channelMap); + } + } + + // allocate the playback buffer + data->m_playbackBuffer = (GVSample *)gsimalloc(GVIBytesPerFrame); + if(!data->m_playbackBuffer) + { + AudioConverterDispose(data->m_playbackConverter); + gviFreeSourceList(data->m_playbackSources); + return GVFalse; + } + + // add property listener + AudioDeviceAddPropertyListener(device->m_deviceID, 0, false, kAudioDevicePropertyDeviceIsAlive, gviPropertyListener, device); + +#if GVI_VOLUME_IN_SOFTWARE + // init volume + data->m_playbackVolume = (GVScalar)1.0; +#endif + + return GVTrue; +} + +static GVBool gviHardwareInitDevice(GVIDevice * device) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + int result; + + // init the mutex + result = pthread_mutex_init(&data->m_mutex, NULL); + if(result != 0) + return GVFalse; + + // handle playback specific stuff + if(device->m_types & GV_PLAYBACK) + { + if(!gviHardwareInitPlayback(device)) + { + pthread_mutex_destroy(&data->m_mutex); + return GVFalse; + } + } + + // handle capture specific stuff + if(device->m_types & GV_CAPTURE) + { + if(!gviHardwareInitCapture(device)) + { + pthread_mutex_destroy(&data->m_mutex); + gviCleanupPlayback(data); + return GVFalse; + } + } + + return GVTrue; +} + +GVDevice gviHardwareNewDevice(GVDeviceID deviceID, GVDeviceType type) +{ + GVIDevice * device; + GVBool result; + + // check for no type + if(!(type & GV_CAPTURE_AND_PLAYBACK)) + return NULL; + + // create a new device + device = gviNewDevice(deviceID, GVHardwareMacOSX, type, sizeof(GVIHardwareData)); + if(!device) + return NULL; + + // init the device + result = gviHardwareInitDevice(device); + if(result == GVFalse) + { + gviFreeDevice(device); + return NULL; + } + + // store the pointers + device->m_methods.m_freeDevice = gviHardwareFreeDevice; + device->m_methods.m_startDevice = gviHardwareStartDevice; + device->m_methods.m_stopDevice = gviHardwareStopDevice; + device->m_methods.m_isDeviceStarted = gviHardwareIsDeviceStarted; + device->m_methods.m_setDeviceVolume = gviHardwareSetDeviceVolume; + device->m_methods.m_getDeviceVolume = gviHardwareGetDeviceVolume; + device->m_methods.m_setCaptureThreshold = gviHardwareSetCaptureThreshold; + device->m_methods.m_getCaptureThreshold = gviHardwareGetCaptureThreshold; + device->m_methods.m_getAvailableCaptureBytes = gviHardwareGetAvailableCaptureBytes; + device->m_methods.m_capturePacket = gviHardwareCapturePacket; + device->m_methods.m_playPacket = gviHardwarePlayPacket; + device->m_methods.m_isSourceTalking = gviHardwareIsSourceTalking; + device->m_methods.m_listTalkingSources = gviHardwareListTalkingSources; + device->m_methods.m_getNumChannels = gviHardwareGetNumChannels; + device->m_methods.m_getChannelName = gviHardwareGetChannelName; + device->m_methods.m_setChannel= gviHardwareSetChannel; + device->m_methods.m_getChannel = gviHardwareGetChannel; + + // add it to the list + gviAppendDeviceToList(GVIDevices, device); + + return device; +} diff --git a/code/gamespy/Voice2/gvOSXAudio.h b/code/gamespy/Voice2/gvOSXAudio.h new file mode 100644 index 00000000..f1ae2b2d --- /dev/null +++ b/code/gamespy/Voice2/gvOSXAudio.h @@ -0,0 +1,29 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +18002 Skypark Circle +Irvine, California 92614 +949.798.4200 (Tel) +949.798.4299 (Fax) +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_OSX_AUDIO_H_ +#define _GV_OSX_AUDIO_H_ + +#include "gvMain.h" + +GVBool gviHardwareStartup(void); +void gviHardwareCleanup(void); +void gviHardwareThink(void); + +int gviHardwareListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types); + +GVDevice gviHardwareNewDevice(GVDeviceID deviceID, GVDeviceType type); + +#endif diff --git a/code/gamespy/Voice2/gvPS2Audio.c b/code/gamespy/Voice2/gvPS2Audio.c new file mode 100644 index 00000000..fd79b0c7 --- /dev/null +++ b/code/gamespy/Voice2/gvPS2Audio.c @@ -0,0 +1,107 @@ +#include "gvPS2Audio.h" +#include "gvPS2Spu2.h" +#include "gvPS2Headset.h" +#include "gvPS2Eyetoy.h" + +#if !defined(_PS2) +#error This file should only be used with the PlayStation2 +#endif + +/************** +** FUNCTIONS ** +**************/ +GVBool gviHardwareStartup(void) +{ + // initialize hardware libraries + #if !defined(GV_NO_PS2_SPU2) + if(gviPS2Spu2Startup()) + #endif + { + #if !defined(GV_NO_PS2_HEADSET) + if(gviPS2HeadsetStartup()) + #endif + { + #if !defined(GV_NO_PS2_EYETOY) + if(gviPS2EyetoyStartup()) + #endif + { + return GVTrue; + } + #if !defined(GV_NO_PS2_HEADSET) + gviPS2HeadsetCleanup(); + #endif + } + #if !defined(GV_NO_PS2_SPU2) + gviPS2Spu2Cleanup(); + #endif + } + + return GVFalse; +} + +void gviHardwareCleanup(void) +{ + // cleanup hardware libraries + #if !defined(GV_NO_PS2_SPU2) + gviPS2Spu2Cleanup(); + #endif + #if !defined(GV_NO_PS2_HEADSET) + gviPS2HeadsetCleanup(); + #endif + #if !defined(GV_NO_PS2_EYETOY) + gviPS2EyetoyCleanup(); + #endif +} + +void gviHardwareThink(void) +{ + // let hardware libraries that support playback think + #if !defined(GV_NO_PS2_SPU2) + gviPS2Spu2Think(); + #endif + #if !defined(GV_NO_PS2_HEADSET) + gviPS2HeadsetThink(); + #endif +} + +int gviHardwareListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types) +{ + int numDevices = 0; + + // enumerate hardware devices + #if !defined(GV_NO_PS2_SPU2) + numDevices += gviPS2Spu2ListDevices(devices + numDevices, maxDevices - numDevices, types); + #endif + #if !defined(GV_NO_PS2_HEADSET) + numDevices += gviPS2HeadsetListDevices(devices + numDevices, maxDevices - numDevices, types); + #endif + #if !defined(GV_NO_PS2_EYETOY) + numDevices += gviPS2EyetoyListDevices(devices + numDevices, maxDevices - numDevices, types); + #endif + + // return the number of devices we found + return numDevices; +} + +GVDevice gviHardwareNewDevice(GVDeviceID deviceID, GVDeviceType type) +{ + // check if this is the SPU2 device + #if !defined(GV_NO_PS2_SPU2) + if(deviceID == GVPS2Spu2DeviceID) + return gviPS2Spu2NewDevice(deviceID, type); + #endif + + // check if this has the eyetoy bit set + #if !defined(GV_NO_PS2_EYETOY) + if(deviceID & GVI_EYETOY_DEVICEID_BIT) + return gviPS2EyetoyNewDevice(deviceID, type); + #endif + + // this is a headset device + #if !defined(GV_NO_PS2_HEADSET) + return gviPS2HeadsetNewDevice(deviceID, type); + #endif + + // this didn't match anything + return NULL; +} diff --git a/code/gamespy/Voice2/gvPS2Audio.h b/code/gamespy/Voice2/gvPS2Audio.h new file mode 100644 index 00000000..504032e3 --- /dev/null +++ b/code/gamespy/Voice2/gvPS2Audio.h @@ -0,0 +1,29 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +18002 Skypark Circle +Irvine, California 92614 +949.798.4200 (Tel) +949.798.4299 (Fax) +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_PS2_AUDIO_H_ +#define _GV_PS2_AUDIO_H_ + +#include "gvMain.h" + +GVBool gviHardwareStartup(void); +void gviHardwareCleanup(void); +void gviHardwareThink(void); + +int gviHardwareListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types); + +GVDevice gviHardwareNewDevice(GVDeviceID deviceID, GVDeviceType type); + +#endif diff --git a/code/gamespy/Voice2/gvPS2Eyetoy.c b/code/gamespy/Voice2/gvPS2Eyetoy.c new file mode 100644 index 00000000..2e5904b9 --- /dev/null +++ b/code/gamespy/Voice2/gvPS2Eyetoy.c @@ -0,0 +1,560 @@ +#include "gvPS2Eyetoy.h" +#if !defined(GV_NO_PS2_EYETOY) +#include "gvDevice.h" +#include "gvCodec.h" +#include "gvSource.h" +#include "gvUtil.h" +#include + +#if !defined(_PS2) +#error This file should only be used with the PlayStation2 +#endif + +/********** +** TYPES ** +**********/ +typedef struct +{ + int m_handle; + + GVBool m_capturing; + GVFrameStamp m_captureClock; + GVScalar m_captureThreshold; + GVFrameStamp m_captureLastCrossedThresholdTime; + GVSample * m_captureBuffer; + int m_captureBufferBytes; +} GVIPS2EyetoyData; + +/************ +** GLOBALS ** +************/ +static GVIDeviceList GVIDevices; +static GVBool GVILGVidInitialized; + +/************** +** FUNCTIONS ** +**************/ +static void gviFreeArrayDevice(void * elem) +{ + GVIDevice * device = *(GVIDevice **)elem; + GVIPS2EyetoyData * data = (GVIPS2EyetoyData *)device->m_data; + + // close the device + lgVidClose(data->m_handle); + + // cleanup the buffer + gsifree(data->m_captureBuffer); + + // free the device + gviFreeDevice(device); +} + +GVBool gviPS2EyetoyStartup(void) +{ + int result; + + // create the array of devices + GVIDevices = gviNewDeviceList(gviFreeArrayDevice); + if(!GVIDevices) + return GVFalse; + + // initialize the eyetoy library + if(GVILGVidInitialized) + { + result = lgVidReInit(); + } + else + { + lgVidCustomAllocator anAllocator; + anAllocator.pfnMalloc = gsimalloc; + anAllocator.pfnMemAlign = gsimemalign; + anAllocator.pfnFree = gsifree; +#ifdef GVI_LGVID_OLD_DRIVER + result = lgVidInit(NULL); +#else + result = lgVidInit(&anAllocator, NULL, 0); +#endif + + } + if(LGVID_FAILED(result)) + { + gviFreeDeviceList(GVIDevices); + GVIDevices = NULL; + return GVFalse; + } + + GVILGVidInitialized = GVTrue; + + return GVTrue; +} + +void gviPS2EyetoyCleanup(void) +{ + // free the device array + if(GVIDevices) + { + gviFreeDeviceList(GVIDevices); + GVIDevices = NULL; + } + + // cleanup the eyetoy library + lgVidUnloadIOPModule(); +} + +int gviPS2EyetoyListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types) +{ + lgVidDeviceDesc eyetoyDesc; + GVDeviceType supportedTypes; + int result; + int index; + int numDevices = 0; + int modeIndex; + + if(!(types & GV_CAPTURE)) + return 0; + + // find all the eyetoy devices +enumerate: + for(index = 0 ; numDevices < maxDevices ; index++) + { + // get the indexed device + result = lgVidEnumerate(index, &eyetoyDesc); + + // check for the unplugged device error + if(result == LGVID_ERR_DEVICE_LOST) + { + // as recommended by the docs, restart the enumeration + numDevices = 0; + goto enumerate; + } + + // check for any other error + if(LGVID_FAILED(result)) + break; + + // check what this device supports + supportedTypes = (GVDeviceType)0; + for(modeIndex = 0 ; modeIndex < eyetoyDesc.SupportedModesCount ; modeIndex++) + { + if(eyetoyDesc.SupportedModes[modeIndex].OperatingMode.AudioRate == LGVID_AUD_8000) + { + supportedTypes = GV_CAPTURE; + break; + } + } + + if(supportedTypes == GV_CAPTURE) + { +#if defined(GSI_UNICODE) + char name[GV_DEVICE_NAME_LEN]; +#endif + + // store this device's info in the array + devices[numDevices].m_id = (int)(index | GVI_EYETOY_DEVICEID_BIT); +#if defined(GSI_UNICODE) + strncpy(name, eyetoyDesc.FriendlyName, GV_DEVICE_NAME_LEN); + name[GV_DEVICE_NAME_LEN - 1] = '\0'; + AsciiToUCS2String(name, devices[numDevices].m_name); +#else + strncpy(devices[numDevices].m_name, eyetoyDesc.FriendlyName, GV_DEVICE_NAME_LEN); + devices[numDevices].m_name[GV_DEVICE_NAME_LEN - 1] = '\0'; +#endif + devices[numDevices].m_deviceType = supportedTypes; + devices[numDevices].m_defaultDevice = (GVDeviceType)0; // ps2 doesn't support default devices + devices[numDevices].m_hardwareType = GVHardwarePS2Eyetoy; + + // one more device + numDevices++; + } + } + + // return the number of devices we found + return numDevices; +} + +static void gviPS2EyetoyFreeDevice(GVIDevice * device) +{ + // delete it from the array + // it will clear out internal data in the array's free function + gviDeleteDeviceFromList(GVIDevices, device); +} + +static GVBool gviPS2EyetoyStartDevice(GVIDevice * device, GVDeviceType type) +{ + GVIPS2EyetoyData * data = (GVIPS2EyetoyData *)device->m_data; + int result; + + if(type != GV_CAPTURE) + return GVFalse; + + // start streaming + result = lgVidStartStreaming(data->m_handle); + if(LGVID_FAILED(result)) + return GVFalse; + + // no data in the capture buffer + data->m_captureBufferBytes = 0; + + // started capturing + data->m_capturing = GVTrue; + + return GVTrue; +} + +static void gviPS2EyetoyStopDevice(GVIDevice * device, GVDeviceType type) +{ + GVIPS2EyetoyData * data = (GVIPS2EyetoyData *)device->m_data; + + if(!(type & GV_CAPTURE)) + return; + + // stop streaming + lgVidStopStreaming(data->m_handle); + + // stopped capturing + data->m_capturing = GVFalse; + + // so a stop then start isn't continuous + data->m_captureClock++; +} + +static GVBool gviPS2EyetoyIsDeviceStarted(GVIDevice * device, GVDeviceType type) +{ + GVIPS2EyetoyData * data = (GVIPS2EyetoyData *)device->m_data; + + if(!(type & GV_CAPTURE)) + return GVFalse; + + return data->m_capturing; +} + +static void gviPS2EyetoySetDeviceVolume(GVIDevice * device, GVDeviceType type, GVScalar volume) +{ + GVIPS2EyetoyData * data = (GVIPS2EyetoyData *)device->m_data; + lgVidCameraControl cameraControl; + u_char gain; + + if(!(type & GV_CAPTURE)) + return; + + // the gain varies from 0 (silence) to 8 (full gain) + gain = (u_char)(volume * 8); + gain = (unsigned char)min(gain, 8); + + // setup the camera control struct + cameraControl.Flags = LGVID_FLAG_AUDIO_GAIN; + cameraControl.AudioGain = gain; + + // set it + lgVidSetCameraControl(data->m_handle, &cameraControl); +} + +static GVScalar gviPS2EyetoyGetDeviceVolume(GVIDevice * device, GVDeviceType type) +{ + GVIPS2EyetoyData * data = (GVIPS2EyetoyData *)device->m_data; + lgVidCameraControl cameraControl; + int result; + + if(!(type & GV_CAPTURE)) + return 0; + + cameraControl.Flags = LGVID_FLAG_AUDIO_GAIN; + result = lgVidGetCameraControl(data->m_handle, &cameraControl); + if(LGVID_FAILED(result) || !(cameraControl.Flags & LGVID_FLAG_AUDIO_GAIN)) + return 1.0; + + return (GVScalar)(cameraControl.AudioGain / 8.0); +} + +static void gviPS2EyetoySetCaptureThreshold(GVIDevice * device, GVScalar threshold) +{ + GVIPS2EyetoyData * data = (GVIPS2EyetoyData *)device->m_data; + data->m_captureThreshold = threshold; +} + +static GVScalar gviPS2EyetoyGetCaptureThreshold(GVIDevice * device) +{ + GVIPS2EyetoyData * data = (GVIPS2EyetoyData *)device->m_data; + return data->m_captureThreshold; +} + +static int gviPS2EyetoyGetAvailableCaptureBytes(GVDevice device) +{ + GVIPS2EyetoyData * data = (GVIPS2EyetoyData *)device->m_data; + + // don't do anything if we're not capturing + if(!data->m_capturing) + return 0; + + // eyetoy's don't provide this info + // so, let the app think something is available + return 1; +} + +static void gviProcessCapturedFrame(GVDevice device, GVByte * frameOut, GVSample * frameIn, GVScalar * volume, GVBool * threshold) +{ + GVIPS2EyetoyData * data = (GVIPS2EyetoyData *)device->m_data; + GVScalar frameVolume; + + // get the volume if requested + if(volume) + { + frameVolume = gviGetSamplesVolume(frameIn, GVISamplesPerFrame); + if(frameVolume > *volume) + *volume = frameVolume; + } + + // check against the threshold + if(threshold && !*threshold) + { + if(volume) + { + // we already got the volume, so use that to check + *threshold = (*volume >= data->m_captureThreshold); + } + else + { + // we didn't get a volume, so check the samples directly + *threshold = gviIsOverThreshold(frameIn, GVISamplesPerFrame, data->m_captureThreshold); + } + } + + // filter + if(device->m_captureFilterCallback) + device->m_captureFilterCallback(device, frameIn, data->m_captureClock); + + // increment the capture clock + data->m_captureClock++; + + // encode the buffer into the packet + gviEncode(frameOut, frameIn); +} + +static GVBool gviPS2EyetoyCapturePacket(GVDevice device, GVByte * packet, int * len, GVFrameStamp * frameStamp, GVScalar * volume) +{ + GVIPS2EyetoyData * data = (GVIPS2EyetoyData *)device->m_data; + lgVidAudioDesc audioDesc; + GVBool overThreshold; + int result; + int numFrames; + int lenAvailable; + int framesAvailable; + int framesCaptured; + GVSample * frameIn; + GVByte * frameOut; + + // figure out how many encoded bytes they can handle + lenAvailable = *len; + + // clear the len and volume + *len = 0; + if(volume) + *volume = 0; + + // don't do anything if we're not capturing + if(!data->m_capturing) + return GVFalse; + + // set the frameStamp + *frameStamp = data->m_captureClock; + + // figure out how many frames they can handle + framesAvailable = (lenAvailable / GVIEncodedFrameSize); + + // how many frames have we already captured? + framesCaptured = (data->m_captureBufferBytes / GVIBytesPerFrame); + + // handle the data one frame at a time + overThreshold = GVFalse; + frameIn = data->m_captureBuffer; + frameOut = packet; + for(numFrames = 0 ; (numFrames < framesAvailable) && (numFrames < framesCaptured) ; numFrames++) + { + // process the frame + gviProcessCapturedFrame(device, frameOut, frameIn, volume, &overThreshold); + + // update the frame pointers + frameIn += GVISamplesPerFrame; + frameOut += GVIEncodedFrameSize; + } + + // adjust buffer based on what we delivered + if(numFrames) + { + data->m_captureBufferBytes -= (numFrames * GVIBytesPerFrame); + if(data->m_captureBufferBytes) + memmove(data->m_captureBuffer, data->m_captureBuffer + (numFrames * GVIBytesPerFrame), (unsigned int)data->m_captureBufferBytes); + } + + // read from the device if they still have available frames + if(numFrames < framesAvailable) + { + // if it got here, any pending frames should have already been handled + assert(data->m_captureBufferBytes < GVIBytesPerFrame); + + // setup the audio descriptor + audioDesc.Samples = (((u_char *)data->m_captureBuffer) + data->m_captureBufferBytes); + + // read the audio + result = lgVidReadAudio(data->m_handle, &audioDesc); + if(LGVID_FAILED(result)) + { + gviDeviceUnplugged(device); + return GVFalse; + } + + // check that we got something + if(audioDesc.TimeStamp != -1) + { + // add to our total + data->m_captureBufferBytes += audioDesc.TotalBytes; + + // set the frame buffer pointer + frameIn = data->m_captureBuffer; + + for( ; numFrames < framesAvailable ; numFrames++) + { + // check if we have a full frame + if(data->m_captureBufferBytes < GVIBytesPerFrame) + break; + + // process the frame + gviProcessCapturedFrame(device, frameOut, frameIn, volume, &overThreshold); + + // update the capture buffer count + data->m_captureBufferBytes -= GVIBytesPerFrame; + + // update the frame pointers + frameIn += GVISamplesPerFrame; + frameOut += GVIEncodedFrameSize; + } + + // move what we have to the front of the buffer + if(data->m_captureBufferBytes) + memmove(data->m_captureBuffer, frameIn, (unsigned)data->m_captureBufferBytes); + } + } + + // check if this packet crossed the threshold + if(overThreshold) + { + // store the time we crossed it + data->m_captureLastCrossedThresholdTime = data->m_captureClock; + } + else + { + // check if we are still on the overhang from a previous crossing + overThreshold = ((GVFrameStamp)(*frameStamp - data->m_captureLastCrossedThresholdTime) < GVI_HOLD_THRESHOLD_FRAMES); + } + + // set the len + *len = (numFrames * GVIEncodedFrameSize); + + // return false if we didn't get a packet + if(!overThreshold || (*len == 0)) + return GVFalse; + + return GVTrue; +} + +static GVBool gviPS2EyetoyInitDevice(GVIDevice * device, int deviceIndex, GVDeviceType type) +{ + lgVidDeviceDesc eyetoyDesc; + lgVidOpenParam openParam; + GVIPS2EyetoyData * data; + int result; + int size; + int i; + + // we only support capture + if(type != GV_CAPTURE) + return GVFalse; + + // enum the device to get a list of supported modes + result = lgVidEnumerate(deviceIndex, &eyetoyDesc); + if(LGVID_FAILED(result)) + return GVFalse; + + // find a supported device mode + //TODO: make this better at choosing the video mode that uses the least CPU and memory (ideally none at all) + for(i = 0 ; i < eyetoyDesc.SupportedModesCount ; i++) + { + if(eyetoyDesc.SupportedModes[i].OperatingMode.AudioRate == LGVID_AUD_8000) + break; + } + if(i == eyetoyDesc.SupportedModesCount) + return GVFalse; + + // setup the open param + memcpy(&openParam.OperatingMode, &eyetoyDesc.SupportedModes[i].OperatingMode, sizeof(lgVidOperatingMode)); + openParam.AudioBufferDuration = GVI_CAPTURE_BUFFER_MILLISECONDS; + + // get a pointer to the data + data = (GVIPS2EyetoyData *)device->m_data; + + // open the device + result = lgVidOpen(deviceIndex, &openParam, &data->m_handle); + if(LGVID_FAILED(result)) + return GVFalse; + + // set some data vars + data->m_captureLastCrossedThresholdTime = (GVFrameStamp)(data->m_captureClock - GVI_HOLD_THRESHOLD_FRAMES - 1); + + // allocate the buffer + size = (GVI_CAPTURE_BUFFER_MILLISECONDS * GV_BYTES_PER_SECOND / 1000); + size += GVIBytesPerFrame; + data->m_captureBuffer = (GVSample *)gsimalloc((unsigned)size); + if(!data->m_captureBuffer) + { + lgVidClose(data->m_handle); + return GVFalse; + } + + return GVTrue; +} + +GVDevice gviPS2EyetoyNewDevice(GVDeviceID deviceID, GVDeviceType type) +{ + GVIDevice * device; + GVBool result; + + // check for wrong type + if(type != GV_CAPTURE) + return NULL; + + // check for the eyetoy bit + if(!(deviceID & GVI_EYETOY_DEVICEID_BIT)) + return NULL; + + // create a new device + device = gviNewDevice(deviceID, GVHardwarePS2Eyetoy, type, sizeof(GVIPS2EyetoyData)); + if(!device) + return NULL; + + // init the device + result = gviPS2EyetoyInitDevice(device, (int)(deviceID & ~GVI_EYETOY_DEVICEID_BIT), type); + if(result == GVFalse) + { + gviFreeDevice(device); + return NULL; + } + + // store the pointers + device->m_methods.m_freeDevice = gviPS2EyetoyFreeDevice; + device->m_methods.m_startDevice = gviPS2EyetoyStartDevice; + device->m_methods.m_stopDevice = gviPS2EyetoyStopDevice; + device->m_methods.m_isDeviceStarted = gviPS2EyetoyIsDeviceStarted; + device->m_methods.m_setDeviceVolume = gviPS2EyetoySetDeviceVolume; + device->m_methods.m_getDeviceVolume = gviPS2EyetoyGetDeviceVolume; + device->m_methods.m_setCaptureThreshold = gviPS2EyetoySetCaptureThreshold; + device->m_methods.m_getCaptureThreshold = gviPS2EyetoyGetCaptureThreshold; + device->m_methods.m_getAvailableCaptureBytes = gviPS2EyetoyGetAvailableCaptureBytes; + device->m_methods.m_capturePacket = gviPS2EyetoyCapturePacket; + + // add it to the list + gviAppendDeviceToList(GVIDevices, device); + + return device; +} + +#endif //!defined(GV_NO_PS2_EYETOY) diff --git a/code/gamespy/Voice2/gvPS2Eyetoy.h b/code/gamespy/Voice2/gvPS2Eyetoy.h new file mode 100644 index 00000000..6b4983cf --- /dev/null +++ b/code/gamespy/Voice2/gvPS2Eyetoy.h @@ -0,0 +1,32 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +18002 Skypark Circle +Irvine, California 92614 +949.798.4200 (Tel) +949.798.4299 (Fax) +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_PS2_EYETOY_H_ +#define _GV_PS2_EYETOY_H_ + +#include "gvMain.h" + +// this is set on all eyetoy GVDeviceID's +// it prevents a conflict with headset id's, which also use a 0-based index +#define GVI_EYETOY_DEVICEID_BIT 0x80000000 + +GVBool gviPS2EyetoyStartup(void); +void gviPS2EyetoyCleanup(void); + +int gviPS2EyetoyListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types); + +GVDevice gviPS2EyetoyNewDevice(GVDeviceID deviceID, GVDeviceType type); + +#endif diff --git a/code/gamespy/Voice2/gvPS2Headset.c b/code/gamespy/Voice2/gvPS2Headset.c new file mode 100644 index 00000000..39aa74ab --- /dev/null +++ b/code/gamespy/Voice2/gvPS2Headset.c @@ -0,0 +1,751 @@ +#include "gvPS2Headset.h" +#if !defined(GV_NO_PS2_HEADSET) +#include "gvDevice.h" +#include "gvCodec.h" +#include "gvSource.h" +#include "gvUtil.h" +#include + +#if !defined(_PS2) +#error This file should only be used with the PlayStation2 +#endif + +/************ +** DEFINES ** +************/ +#define GVI_PLAYBACK_STAYAHEAD_MILLISECONDS 50 + +/********** +** TYPES ** +**********/ +typedef struct +{ + int m_handle; + + GVBool m_playing; + GVScalar m_playbackVolume; + GVFrameStamp m_playbackClock; + GVISourceList m_playbackSources; + GVSample * m_playbackBuffer; + + GVBool m_capturing; + GVScalar m_captureVolume; + GVFrameStamp m_captureClock; + GVScalar m_captureThreshold; + GVFrameStamp m_captureLastCrossedThresholdTime; + GVSample * m_captureBuffer; + int m_captureBufferBytes; +} GVIPS2HeadsetData; + +/************ +** GLOBALS ** +************/ +static GVIDeviceList GVIDevices; + +/************** +** FUNCTIONS ** +**************/ +static GVIDevice * gviFindDeviceByID(GVDeviceID deviceID) +{ + GVIDevice * device; + int num; + int i; + + num = gviGetNumDevices(GVIDevices); + for(i = 0 ; i < num ; i++) + { + device = gviGetDevice(GVIDevices, i); + if(device->m_deviceID == deviceID) + return device; + } + + return NULL; +} + +static void gviFreeArrayDevice(void * elem) +{ + GVIDevice * device = *(GVIDevice **)elem; + GVIPS2HeadsetData * data = (GVIPS2HeadsetData *)device->m_data; + + // close the device + lgAudClose(data->m_handle); + + // playback specific cleanup + if(device->m_types & GV_PLAYBACK) + { + if(data->m_playbackSources) + gviFreeSourceList(data->m_playbackSources); + gsifree(data->m_playbackBuffer); + } + + // capture specific cleanup + if(device->m_types & GV_CAPTURE) + { + gsifree(data->m_captureBuffer); + } + + // free the device + gviFreeDevice(device); +} + +GVBool gviPS2HeadsetStartup(void) +{ + int result; + + // create the array of devices + GVIDevices = gviNewDeviceList(gviFreeArrayDevice); + if(!GVIDevices) + return GVFalse; + + // initialize the headset library + result = lgAudInit(gsimalloc, gsifree); + if(LGAUD_FAILED(result)) + { + gviFreeDeviceList(GVIDevices); + GVIDevices = NULL; + return GVFalse; + } + + return GVTrue; +} + +void gviPS2HeadsetCleanup(void) +{ + // free the device array + if(GVIDevices) + { + gviFreeDeviceList(GVIDevices); + GVIDevices = NULL; + } + + // cleanup the headset library + lgAudDeInit(); +} + +static GVBool gviPlaybackDeviceThink(GVIDevice * device) +{ + GVIPS2HeadsetData * data = (GVIPS2HeadsetData *)device->m_data; + int result; + int remainingBytes; + int numFrames; + int newBytes; + GVBool wroteToBuffer; + int size; + int i; + + // don't do anything if we're not playing + if(!data->m_playing) + return GVTrue; + + // get the remaining bytes + result = lgAudGetRemainingPlaybackBytes(data->m_handle, &remainingBytes); + if(LGAUD_FAILED(result)) + return GVFalse; + + // figure out how many bytes we need to write to stay ahead + newBytes = (gviMultiplyByBytesPerMillisecond(GVI_PLAYBACK_STAYAHEAD_MILLISECONDS) - remainingBytes); + newBytes = gviRoundUpToNearestMultiple(newBytes, GVIBytesPerFrame); + + // figure out the number of frames that is + numFrames = (newBytes / GVIBytesPerFrame); + + // write the frames + for(i = 0 ; i < numFrames ; i++) + { + // write a frame of sources to our buffer + wroteToBuffer = gviWriteSourcesToBuffer(data->m_playbackSources, data->m_playbackClock, data->m_playbackBuffer, 1); + + // clear it if nothing was written + if(!wroteToBuffer) + memset(data->m_playbackBuffer, 0, (unsigned int)GVIBytesPerFrame); + + // filter + if(device->m_playbackFilterCallback) + device->m_playbackFilterCallback(device, data->m_playbackBuffer, data->m_playbackClock); + + // send it to the device + size = GVIBytesPerFrame; + result = lgAudWrite(data->m_handle, LGAUD_BLOCKMODE_BLOCKING, (u_char *)data->m_playbackBuffer, &size); + if(LGAUD_FAILED(result)) + return GVFalse; + + // update the clock + data->m_playbackClock++; + } + + return GVTrue; +} + +void gviPS2HeadsetThink(void) +{ + GVIDevice * device; + GVBool rcode; + int num; + int i; + + if(!GVIDevices) + return; + + // loop through the devices backwards to that we can remove devices as we go + num = gviGetNumDevices(GVIDevices); + for(i = (num - 1) ; i >= 0 ; i--) + { + // get the device + device = gviGetDevice(GVIDevices, i); + + // // check if playback is setup on the device + if(device->m_types & GV_PLAYBACK) + { + // let it think + rcode = gviPlaybackDeviceThink(device); + + // check if the device was unplugged + if(!rcode) + gviDeviceUnplugged(device); + } + } +} + +static GVBool gviDoesSupportOurFormat(lgAudSamplingFormat * formats, int count) +{ + int i; + + for(i = 0 ; i < count ; i++) + { + if(formats[i].Channels == 1) + if(formats[i].BitResolution == 16) + if(formats[i].LowerSamplingRate <= GV_SAMPLES_PER_SECOND) + if(formats[i].HigherSamplingRate >= GV_SAMPLES_PER_SECOND) + return GVTrue; + } + + return GVFalse; +} + +int gviPS2HeadsetListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types) +{ + lgAudDeviceDesc headsetDesc; + GVDeviceType supportedTypes; + int result; + int index; + int numDevices = 0; + +enumerate: + for(index = 0 ; numDevices < maxDevices ; index++) + { + // get the indexed device + result = lgAudEnumerate(index, &headsetDesc); + + // check for the unplugged device error + if(result == LGAUD_ERR_DEVICE_LOST) + { + // as recommended by the docs, restart the enumeration + numDevices = 0; + goto enumerate; + } + + // check for any other error + if(LGAUD_FAILED(result)) + break; + + // check what this device supports + supportedTypes = (GVDeviceType)0; + if(gviDoesSupportOurFormat(headsetDesc.RecordingFormats, headsetDesc.RecordingFormatsCount)) + supportedTypes |= GV_CAPTURE; + if(gviDoesSupportOurFormat(headsetDesc.PlaybackFormats, headsetDesc.PlaybackFormatsCount)) + supportedTypes |= GV_PLAYBACK; + + // only add it if it supports a format for a type that the app requested + // this library doesn't support device names or default devices + if(supportedTypes & types) + { + // store this device's info in the array + devices[numDevices].m_id = index; + devices[numDevices].m_deviceType = supportedTypes; + devices[numDevices].m_defaultDevice = (GVDeviceType)0; // ps2 doesn't support default devices + + // set the name and the hardware type based on the capabilities + if(supportedTypes == GV_CAPTURE) + { + _tcscpy(devices[numDevices].m_name, _T("USB Microphone")); + devices[numDevices].m_hardwareType = GVHardwarePS2Microphone; + } + else if(supportedTypes == GV_PLAYBACK) + { + _tcscpy(devices[numDevices].m_name, _T("USB Speakers")); + devices[numDevices].m_hardwareType = GVHardwarePS2Speakers; + } + else + { + _tcscpy(devices[numDevices].m_name, _T("USB Headset")); + devices[numDevices].m_hardwareType = GVHardwarePS2Headset; + } + + // one more device + numDevices++; + } + } + + // return the number of devices we found + return numDevices; +} + +static void gviPS2HeadsetFreeDevice(GVIDevice * device) +{ + // delete it from the array + // it will clear out internal data in the array's free function + gviDeleteDeviceFromList(GVIDevices, device); +} + +static GVBool gviStartPlaybackDevice(GVIPS2HeadsetData * data) +{ + int result; + + // start the buffer + result = lgAudStartPlayback(data->m_handle); + if(LGAUD_FAILED(result)) + return GVFalse; + + // clear the clock + data->m_playbackClock = 0; + + // started playing + data->m_playing = GVTrue; + + return GVTrue; +} + +static GVBool gviStartCaptureDevice(GVIPS2HeadsetData * data) +{ + int result; + + // start the buffer + result = lgAudStartRecording(data->m_handle); + if(LGAUD_FAILED(result)) + return GVFalse; + + // no data in the capture buffer + data->m_captureBufferBytes = 0; + + // started capturing + data->m_capturing = GVTrue; + + return GVTrue; +} + +static GVBool gviPS2HeadsetStartDevice(GVIDevice * device, GVDeviceType type) +{ + GVIPS2HeadsetData * data = (GVIPS2HeadsetData *)device->m_data; + + if(type == GV_PLAYBACK) + return gviStartPlaybackDevice(data); + if(type == GV_CAPTURE) + return gviStartCaptureDevice(data); + if(type == GV_CAPTURE_AND_PLAYBACK) + { + if(!gviStartPlaybackDevice(data)) + return GVFalse; + if(!gviStartCaptureDevice(data)) + { + device->m_methods.m_stopDevice(device, GV_PLAYBACK); + return GVFalse; + } + return GVTrue; + } + return GVFalse; +} + +static void gviPS2HeadsetStopDevice(GVIDevice * device, GVDeviceType type) +{ + GVIPS2HeadsetData * data = (GVIPS2HeadsetData *)device->m_data; + + if(type & GV_PLAYBACK) + { + // stop the playback buffer + lgAudStopPlayback(data->m_handle); + + // stopped playing + data->m_playing = GVFalse; + + // clear any pending sources & buffers + gviClearSourceList(data->m_playbackSources); + } + if(type & GV_CAPTURE) + { + // stop the capture buffer + lgAudStopRecording(data->m_handle); + + // stopped capturing + data->m_capturing = GVFalse; + + // so a stop then start isn't continuous + data->m_captureClock++; + } +} + +static GVBool gviPS2HeadsetIsDeviceStarted(GVIDevice * device, GVDeviceType type) +{ + GVIPS2HeadsetData * data = (GVIPS2HeadsetData *)device->m_data; + + if(type == GV_PLAYBACK) + return data->m_playing; + if(type == GV_CAPTURE) + return data->m_capturing; + if(type == GV_CAPTURE_AND_PLAYBACK) + return (data->m_playing && data->m_capturing); + return GVFalse; +} + +static void gviPS2HeadsetSetDeviceVolume(GVIDevice * device, GVDeviceType type, GVScalar volume) +{ + GVIPS2HeadsetData * data = (GVIPS2HeadsetData *)device->m_data; + + if(type & GV_PLAYBACK) + { + lgAudSetPlaybackVolume(data->m_handle, LGAUD_CH_BOTH, (u_char)(volume * 100)); + data->m_playbackVolume = volume; + } + if(type & GV_CAPTURE) + { + lgAudSetRecordingVolume(data->m_handle, LGAUD_CH_BOTH, (u_char)(volume * 100)); + data->m_captureVolume = volume; + } +} + +static GVScalar gviPS2HeadsetGetDeviceVolume(GVIDevice * device, GVDeviceType type) +{ + GVIPS2HeadsetData * data = (GVIPS2HeadsetData *)device->m_data; + + if(type & GV_PLAYBACK) + return data->m_playbackVolume; + return data->m_captureVolume; +} + +static void gviPS2HeadsetSetCaptureThreshold(GVIDevice * device, GVScalar threshold) +{ + GVIPS2HeadsetData * data = (GVIPS2HeadsetData *)device->m_data; + data->m_captureThreshold = threshold; +} + +static GVScalar gviPS2HeadsetGetCaptureThreshold(GVIDevice * device) +{ + GVIPS2HeadsetData * data = (GVIPS2HeadsetData *)device->m_data; + return data->m_captureThreshold; +} + +static int gviPS2HeadsetGetAvailableCaptureBytes(GVDevice device) +{ + GVIPS2HeadsetData * data = (GVIPS2HeadsetData *)device->m_data; + int result; + int newBytes; + int numFrames; + + // don't do anything if we're not capturing + if(!data->m_capturing) + return 0; + + // get the number of new bytes + result = lgAudGetAvailableRecordingBytes(data->m_handle, &newBytes); + if(LGAUD_FAILED(result)) + { + gviDeviceUnplugged(device); + return 0; + } + + // figure out how many frames this is + numFrames = (newBytes / GVIBytesPerFrame); + + // calculate how many bytes this is once encoded + newBytes = (numFrames * GVIEncodedFrameSize); + + return newBytes; +} + +static void gviProcessCapturedFrame(GVDevice device, GVByte * frameOut, GVSample * frameIn, GVScalar * volume, GVBool * threshold) +{ + GVIPS2HeadsetData * data = (GVIPS2HeadsetData *)device->m_data; + GVScalar frameVolume; + + // get the volume if requested + if(volume) + { + frameVolume = gviGetSamplesVolume(frameIn, GVISamplesPerFrame); + if(frameVolume > *volume) + *volume = frameVolume; + } + + // check against the threshold + if(threshold && !*threshold) + { + if(volume) + { + // we already got the volume, so use that to check + *threshold = (*volume >= data->m_captureThreshold); + } + else + { + // we didn't get a volume, so check the samples directly + *threshold = gviIsOverThreshold(frameIn, GVISamplesPerFrame, data->m_captureThreshold); + } + } + + // filter + if(device->m_captureFilterCallback) + device->m_captureFilterCallback(device, frameIn, data->m_captureClock); + + // increment the capture clock + data->m_captureClock++; + + // encode the buffer into the packet + gviEncode(frameOut, frameIn); +} + +static GVBool gviPS2HeadsetCapturePacket(GVDevice device, GVByte * packet, int * len, GVFrameStamp * frameStamp, GVScalar * volume) +{ + GVIPS2HeadsetData * data = (GVIPS2HeadsetData *)device->m_data; + GVBool overThreshold; + int result; + int numFrames; + int readSize; + int lenAvailable; + int framesAvailable; + GVSample * frameIn; + GVByte * frameOut; + + // figure out how many encoded bytes they can handle + lenAvailable = *len; + + // clear the len and volume + *len = 0; + if(volume) + *volume = 0; + + // don't do anything if we're not capturing + if(!data->m_capturing) + return GVFalse; + + // set the frameStamp + *frameStamp = data->m_captureClock; + + // figure out how many frames they can handle + framesAvailable = (lenAvailable / GVIEncodedFrameSize); + + // handle the data one frame at a time + overThreshold = GVFalse; + frameIn = data->m_captureBuffer; + frameOut = packet; + for(numFrames = 0 ; numFrames < framesAvailable ; numFrames++) + { + // read this frame + readSize = (GVIBytesPerFrame - data->m_captureBufferBytes); + result = lgAudRead(data->m_handle, LGAUD_BLOCKMODE_NOT_BLOCKING, ((u_char *)data->m_captureBuffer) + data->m_captureBufferBytes, &readSize); + if(LGAUD_FAILED(result)) + { + gviDeviceUnplugged(device); + return GVFalse; + } + + // check if we have a full frame + if(readSize != (GVIBytesPerFrame - data->m_captureBufferBytes)) + { + // add to our count of how many bytes we have + data->m_captureBufferBytes += readSize; + break; + } + + // process the frame + gviProcessCapturedFrame(device, frameOut, frameIn, volume, &overThreshold); + + // we got a full frame, so there's no leftover + data->m_captureBufferBytes = 0; + + // update the frame pointer + frameOut += GVIEncodedFrameSize; + } + + // check if this packet crossed the threshold + if(overThreshold) + { + // store the time we crossed it + data->m_captureLastCrossedThresholdTime = data->m_captureClock; + } + else + { + // check if we are still on the overhang from a previous crossing + overThreshold = ((GVFrameStamp)(*frameStamp - data->m_captureLastCrossedThresholdTime) < GVI_HOLD_THRESHOLD_FRAMES); + } + + // set the len + *len = (numFrames * GVIEncodedFrameSize); + + // return false if we didn't get a packet + if(!overThreshold || (*len == 0)) + return GVFalse; + + return GVTrue; +} + +static void gviPS2HeadsetPlayPacket(GVIDevice * device, const GVByte * packet, int len, GVSource source, GVFrameStamp frameStamp, GVBool mute) +{ + GVIPS2HeadsetData * data = (GVIPS2HeadsetData *)device->m_data; + + // don't do anything if we're not playing + if(!data->m_playing) + return; + + //add it + gviAddPacketToSourceList(data->m_playbackSources, packet, len, source, frameStamp, mute, data->m_playbackClock); +} + +static GVBool gviPS2HeadsetIsSourceTalking(GVDevice device, GVSource source) +{ + GVIPS2HeadsetData * data = (GVIPS2HeadsetData *)device->m_data; + + // don't do anything if we're not playing + if(!data->m_playing) + return GVFalse; + + return gviIsSourceTalking(data->m_playbackSources, source); +} + +static int gviPS2HeadsetListTalkingSources(GVDevice device, GVSource sources[], int maxSources) +{ + GVIPS2HeadsetData * data = (GVIPS2HeadsetData *)device->m_data; + + // don't do anything if we're not playing + if(!data->m_playing) + return GVFalse; + + return gviListTalkingSources(data->m_playbackSources, sources, maxSources); +} + +static GVBool gviPS2HeadsetInitDevice(GVIDevice * device, int deviceIndex, GVDeviceType type) +{ + GVIPS2HeadsetData * data; + lgAudOpenParam openParam; + int result; + + // setup the open param + openParam.Mode = 0; + if(type & GV_CAPTURE) + { + openParam.Mode |= LGAUD_MODE_RECORDING; + openParam.RecordingFormat.Channels = 1; + openParam.RecordingFormat.BitResolution = 16; + openParam.RecordingFormat.SamplingRate = GV_SAMPLES_PER_SECOND; + openParam.RecordingFormat.BufferMilliseconds = GVI_CAPTURE_BUFFER_MILLISECONDS; + } + if(type & GV_PLAYBACK) + { + openParam.Mode |= LGAUD_MODE_PLAYBACK; + openParam.PlaybackFormat.Channels = 1; + openParam.PlaybackFormat.BitResolution = 16; + openParam.PlaybackFormat.SamplingRate = GV_SAMPLES_PER_SECOND; + openParam.PlaybackFormat.BufferMilliseconds = GVI_PLAYBACK_BUFFER_MILLISECONDS; + } + + // get a pointer to the data + data = (GVIPS2HeadsetData *)device->m_data; + + // open the device + result = lgAudOpen(deviceIndex, &openParam, &data->m_handle); + if(LGAUD_FAILED(result)) + return GVFalse; + + // handle playback specific stuff + if(type & GV_PLAYBACK) + { + // create the array of sources + data->m_playbackSources = gviNewSourceList(); + if(!data->m_playbackSources) + { + lgAudClose(data->m_handle); + return GVFalse; + } + + // allocate the buffer + data->m_playbackBuffer = (GVSample *)gsimalloc((unsigned int)GVIBytesPerFrame); + if(!data->m_playbackBuffer) + { + gviFreeSourceList(data->m_playbackSources); + lgAudClose(data->m_handle); + return GVFalse; + } + + // set data + //data->m_playbackVolume = gviGetMixerVolume(data, GV_PLAYBACK); + data->m_playbackVolume = 1.0; + } + + // handle capture specific stuff + if(type & GV_CAPTURE) + { + // set some data vars + data->m_captureVolume = 1.0; + data->m_captureLastCrossedThresholdTime = (GVFrameStamp)(data->m_captureClock - GVI_HOLD_THRESHOLD_FRAMES - 1); + + // allocate the buffer + data->m_captureBuffer = (GVSample *)gsimalloc((unsigned int)GVIBytesPerFrame); + if(!data->m_captureBuffer) + { + if(type & GV_PLAYBACK) + { + gviFreeSourceList(data->m_playbackSources); + gsifree(data->m_playbackBuffer); + } + lgAudClose(data->m_handle); + return GVFalse; + } + } + + return GVTrue; +} + +GVDevice gviPS2HeadsetNewDevice(GVDeviceID deviceID, GVDeviceType type) +{ + GVIDevice * device; + GVBool result; + + // check for no type + if(!(type & GV_CAPTURE_AND_PLAYBACK)) + return NULL; + + // check if the device already exists + if(gviFindDeviceByID(deviceID)) + return NULL; + + // create a new device + device = gviNewDevice(deviceID, GVHardwarePS2Headset, type, sizeof(GVIPS2HeadsetData)); + if(!device) + return NULL; + + // init the device + result = gviPS2HeadsetInitDevice(device, deviceID, type); + if(result == GVFalse) + { + gviFreeDevice(device); + return NULL; + } + + // store the pointers + device->m_methods.m_freeDevice = gviPS2HeadsetFreeDevice; + device->m_methods.m_startDevice = gviPS2HeadsetStartDevice; + device->m_methods.m_stopDevice = gviPS2HeadsetStopDevice; + device->m_methods.m_isDeviceStarted = gviPS2HeadsetIsDeviceStarted; + device->m_methods.m_setDeviceVolume = gviPS2HeadsetSetDeviceVolume; + device->m_methods.m_getDeviceVolume = gviPS2HeadsetGetDeviceVolume; + device->m_methods.m_setCaptureThreshold = gviPS2HeadsetSetCaptureThreshold; + device->m_methods.m_getCaptureThreshold = gviPS2HeadsetGetCaptureThreshold; + device->m_methods.m_getAvailableCaptureBytes = gviPS2HeadsetGetAvailableCaptureBytes; + device->m_methods.m_capturePacket = gviPS2HeadsetCapturePacket; + device->m_methods.m_playPacket = gviPS2HeadsetPlayPacket; + device->m_methods.m_isSourceTalking = gviPS2HeadsetIsSourceTalking; + device->m_methods.m_listTalkingSources = gviPS2HeadsetListTalkingSources; + + // add it to the list + gviAppendDeviceToList(GVIDevices, device); + + return device; +} + +#endif //!defined(GV_NO_PS2_HEADSET) diff --git a/code/gamespy/Voice2/gvPS2Headset.h b/code/gamespy/Voice2/gvPS2Headset.h new file mode 100644 index 00000000..6f6481ed --- /dev/null +++ b/code/gamespy/Voice2/gvPS2Headset.h @@ -0,0 +1,29 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +18002 Skypark Circle +Irvine, California 92614 +949.798.4200 (Tel) +949.798.4299 (Fax) +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_PS2_HEADSET_H_ +#define _GV_PS2_HEADSET_H_ + +#include "gvMain.h" + +GVBool gviPS2HeadsetStartup(void); +void gviPS2HeadsetCleanup(void); +void gviPS2HeadsetThink(void); + +int gviPS2HeadsetListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types); + +GVDevice gviPS2HeadsetNewDevice(GVDeviceID deviceID, GVDeviceType type); + +#endif diff --git a/code/gamespy/Voice2/gvPS2Spu2.c b/code/gamespy/Voice2/gvPS2Spu2.c new file mode 100644 index 00000000..b2f157c9 --- /dev/null +++ b/code/gamespy/Voice2/gvPS2Spu2.c @@ -0,0 +1,681 @@ +#include "gvPS2Spu2.h" +#if !defined(GV_NO_PS2_SPU2) +#include "gvDevice.h" +#include "gvCodec.h" +#include "gvSource.h" +#include "gvUtil.h" +#include +#include + +#if !defined(_PS2) +#error This file should only be used with the PlayStation2 +#endif + +// Thanks to Martin Jajam at Coresoft for original SPU2 code +//////////////////////////////////////////////////////////// + +/* +The input block we are writing to takes data in the form + +---1st half of buffer----- +256 sample Left channel = 512 bytes ( 1 block ) +256 sample Right channel = 512 bytes + +---2nd half of buffer----- +256 sample Left channel = 512 bytes +256 sample Right channel = 512 bytes + +*/ + +/************ +** DEFINES ** +************/ +#define bss_align(val) \ + __attribute__ ((aligned(val))) __attribute__ ((section (".bss"))) +#define LIMIT(x, minx, maxx) ((x) < (minx) ? (minx) : ((x) > (maxx) ? (maxx) : (x))) + +#define AUTODMA_CH 1 + +#define SPU_BLOCK_SIZE 512 // in samples +#define IOP_BUFF_SIZE 12288 // 24 blocks +#define SPU_BUFF_SIZE 2048 // 4 blocks + +#define CORE0_INPUT_L (0x2000)// * 2) +#define CORE0_INPUT_R (0x2200)// * 2) +#define CORE1_INPUT_L (0x2400)// * 2) +#define CORE1_INPUT_R (0x2600)// * 2) +#if (AUTODMA_CH == 0) +#define CORE_INPUT_L CORE0_INPUT_L +#define CORE_INPUT_R CORE0_INPUT_R +#else +#define CORE_INPUT_L CORE1_INPUT_L +#define CORE_INPUT_R CORE1_INPUT_R +#endif + +// each DMA is 4 blocks = 256 samples * 2 bytes * 4 blocks = 2048 bytes +#define EEBUFFER_SAMPLE_COUNT (IOP_BUFF_SIZE/4) // 2 bytes sample + +/********** +** TYPES ** +**********/ +typedef struct +{ + GVBool m_playing; + GVScalar m_playbackVolume; + GVFrameStamp m_playbackClock; + GVISourceList m_playbackSources; + GVSample * m_playbackBuffer; + int m_playbackBufferPos; +} GVIPS2Spu2Data; + +/************ +** GLOBALS ** +************/ +const GVDeviceID GVPS2Spu2DeviceID = -1; +static GVIDevice * GVIPS2Spu2Device; + +static gsi_u16 EEBuffer[IOP_BUFF_SIZE] bss_align(64); +static int IOPBuffer = 0; +static int gEEStreanIntr = 0; // flag for when an endpoint is reached by the audio play head. +static int gEEDataSampleCount = 0; // how many samples do we have buffered up? ( 0 - 256 ) + +/************** +** FUNCTIONS ** +**************/ +static int DMA_EE_To_IOP(int dst, u_char *src, int size) +{ + sceSifDmaData transData; + int did; + + assert((((gsi_u32)src) & 0x3f) == 0); // assert 64 byte aligned + + if (size <= 0) { + return 0; + } + + transData.data = (u_int)src; + transData.addr = (u_int)dst; + transData.size = (unsigned int)size; + transData.mode = 0; // caution + FlushCache(0); + + did = (int)sceSifSetDma( &transData, 1 ); + + while (sceSifDmaStat((unsigned int)did) >= 0) + ; + + return size; +} + +static int cbEEStreamTransfer( void* common ) +{ + gEEStreanIntr = 1; + GSI_UNUSED(common); + return 0; +} + +static void EEStreamClearSPUBuffer() +{ + memset (EEBuffer, 0, IOP_BUFF_SIZE); + FlushCache(0); + DMA_EE_To_IOP( IOPBuffer, // IOP side destination address + (u_char *)EEBuffer, // EE side source address + IOP_BUFF_SIZE); + + DMA_EE_To_IOP( IOPBuffer + IOP_BUFF_SIZE, // IOP side destination address + (u_char *)EEBuffer, // EE side source address + IOP_BUFF_SIZE); + + // Clear out SPU2 input area + sceSdRemote(1, rSdVoiceTrans, AUTODMA_CH, SD_TRANS_MODE_WRITE | SD_TRANS_BY_DMA, + IOPBuffer, CORE_INPUT_L, 0x800 ); + sceSdRemote(1, rSdVoiceTrans, AUTODMA_CH, SD_TRANS_MODE_WRITE | SD_TRANS_BY_DMA, + IOPBuffer + IOP_BUFF_SIZE, CORE_INPUT_R, 0x800 ); + + sceSdRemote (1, rSdVoiceTransStatus, AUTODMA_CH, SD_TRANS_STATUS_WAIT); +} + +GVBool gviPS2Spu2Startup(void) +{ + // clear the device pointer + GVIPS2Spu2Device = NULL; + + // do general initialization + if(sceSdRemoteInit() != 0) + return GVFalse; + if(sceSdRemote(1, rSdInit, SD_INIT_COLD) != 0) + return GVFalse; + if(sceSifInitIopHeap() != 0) + return GVFalse; + + return GVTrue; +} + +void gviPS2Spu2Cleanup(void) +{ + // free the device array + if(GVIPS2Spu2Device) + gviFreeDevice(GVIPS2Spu2Device); +} + +static void AudioUpSample(gsi_i16* Dest48Khz, const gsi_i16* Src8Khz, int num8Khzsamples) +// 16 bit audio upsample from 8khz to 48khz +// numsamples = Src samples (8khz ) +{ +#if(0) + // Point sample + while (num8Khzsamples) + { + int a = Src8Khz[0]; + + + // bad alias upsampling code here.... + Dest48Khz[0] = a; + Dest48Khz[1] = a; + Dest48Khz[2] = a; + Dest48Khz[3] = a; + Dest48Khz[4] = a; + Dest48Khz[5] = a; + + Dest48Khz +=6; + Src8Khz ++; + num8Khzsamples--; + } +#elif (1) + // linear interpolate + int a=0,b=0; + while (num8Khzsamples - 1) + { + + a = Src8Khz[0]; + b = Src8Khz[1]; + + + // bad alias upsampling code here.... + Dest48Khz[0] = (gsi_i16)a; + Dest48Khz[1] = (gsi_i16)((5*a)/6 +(1*b)/6); + Dest48Khz[2] = (gsi_i16)((4*a)/6 +(2*b)/6); + Dest48Khz[3] = (gsi_i16)((3*a)/6 +(3*b)/6); + Dest48Khz[4] = (gsi_i16)((2*a)/6 +(4*b)/6); + Dest48Khz[5] = (gsi_i16)((1*a)/6 +(5*b)/6); + + Dest48Khz +=6; + Src8Khz ++; + num8Khzsamples--; + } + Dest48Khz[0] = (gsi_i16)b; + Dest48Khz[1] = (gsi_i16)b; + Dest48Khz[2] = (gsi_i16)b; + Dest48Khz[3] = (gsi_i16)b; + Dest48Khz[4] = (gsi_i16)b; + Dest48Khz[5] = (gsi_i16)b; + +#else + + // use catmull-rom spline to interpolate + + + // multiply tr vector by abcd matrix to get point + + float a = (float)Src8Khz[ 0]; + float b = (float)Src8Khz[ 0]; + float c = (float)Src8Khz[ 1]; + float d = (float)Src8Khz[ 2]; + float v; + + // bad alias upsampling code here.... + Dest48Khz[0] =Src8Khz[ 0]; // t= 0; + Dest48Khz[1] =(gsi_u16)(a * -0.058f + b * 0.938f + c * 0.132f + d * -0.012f ); // t= 1/6; + Dest48Khz[2] =(gsi_u16)(a * -0.074f + b * 0.778f + c * 0.333f + d * -0.037f ); // t= 2/6; + Dest48Khz[3] =(gsi_u16)(a * -0.063f + b * 0.563f + c * 0.563f + d * -0.063f ); // t= 3/6; + Dest48Khz[4] =(gsi_u16)(a * -0.037f + b * 0.333f + c * 0.778f + d * -0.074f ); // t= 4/6; + Dest48Khz[5] =(gsi_u16)(a * -0.012f + b * 0.132f + c * 0.938f + d * -0.058f ); // t= 5/6; + + Dest48Khz +=6; + Src8Khz ++; + num8Khzsamples-=3; + + while (num8Khzsamples) + { + a = (float)Src8Khz[-1]; + b = (float)Src8Khz[ 0]; + c = (float)Src8Khz[ 1]; + d = (float)Src8Khz[ 2]; + + // bad alias upsampling code here.... + Dest48Khz[0] =Src8Khz[ 0]; // t= 0; + + v = (a * -0.058f + b * 0.938f + c * 0.132f + d * -0.012f ); + Dest48Khz[1] =(gsi_u16) LIMIT(v,0.0f,65535.0f); // t= 1/6; + + v = (a * -0.074f + b * 0.778f + c * 0.333f + d * -0.037f ); + Dest48Khz[2] =(gsi_u16) LIMIT(v,0.0f,65535.0f); // t= 2/6; + + v = (a * -0.063f + b * 0.563f + c * 0.563f + d * -0.063f ); + Dest48Khz[3] =(gsi_u16) LIMIT(v,0.0f,65535.0f); // t= 3/6; + + v= (a * -0.037f + b * 0.333f + c * 0.778f + d * -0.074f ); + Dest48Khz[4] =(gsi_u16) LIMIT(v,0.0f,65535.0f); // t= 4/6; + + v=(a * -0.012f + b * 0.132f + c * 0.938f + d * -0.058f ); + Dest48Khz[5] =(gsi_u16) LIMIT(v,0.0f,65535.0f); // t= 5/6; + + Dest48Khz +=6; + Src8Khz ++; + num8Khzsamples--; + } + + a = (float)Src8Khz[ 0]; + b = (float)Src8Khz[ 0]; + c = (float)Src8Khz[ 1]; + d = (float)Src8Khz[ 1]; + + // bad alias upsampling code here.... + Dest48Khz[0] =Src8Khz[ 0]; // t= 0; + Dest48Khz[1] =(gsi_u16)(a * -0.058f + b * 0.938f + c * 0.132f + d * -0.012f ); // t= 1/6; + Dest48Khz[2] =(gsi_u16)(a * -0.074f + b * 0.778f + c * 0.333f + d * -0.037f ); // t= 2/6; + Dest48Khz[3] =(gsi_u16)(a * -0.063f + b * 0.563f + c * 0.563f + d * -0.063f ); // t= 3/6; + Dest48Khz[4] =(gsi_u16)(a * -0.037f + b * 0.333f + c * 0.778f + d * -0.074f ); // t= 4/6; + Dest48Khz[5] =(gsi_u16)(a * -0.012f + b * 0.132f + c * 0.938f + d * -0.058f ); // t= 5/6; + + Dest48Khz +=6; + Src8Khz ++; + + a = (float)Src8Khz[ 0]; + b = (float)Src8Khz[ 0]; + c = (float)Src8Khz[ 0]; + d = (float)Src8Khz[ 0]; + + // bad alias upsampling code here.... + Dest48Khz[0] =Src8Khz[ 0]; // t= 0; + Dest48Khz[1] =(gsi_u16)(a * -0.058f + b * 0.938f + c * 0.132f + d * -0.012f ); // t= 1/6; + Dest48Khz[2] =(gsi_u16)(a * -0.074f + b * 0.778f + c * 0.333f + d * -0.037f ); // t= 2/6; + Dest48Khz[3] =(gsi_u16)(a * -0.063f + b * 0.563f + c * 0.563f + d * -0.063f ); // t= 3/6; + Dest48Khz[4] =(gsi_u16)(a * -0.037f + b * 0.333f + c * 0.778f + d * -0.074f ); // t= 4/6; + Dest48Khz[5] =(gsi_u16)(a * -0.012f + b * 0.132f + c * 0.938f + d * -0.058f ); // t= 5/6; + + Dest48Khz +=6; + Src8Khz ++; + + +#endif + +} + +static int EEStreamDataPush(const gsi_u16* data, int num8Khzsamples) +// numsamples is in 8khz format +// copies and converts. returns numsamples read +{ + int remain48Khz = EEBUFFER_SAMPLE_COUNT - gEEDataSampleCount; + + if (num8Khzsamples *6 > remain48Khz) + { + num8Khzsamples = remain48Khz/6; + } + + assert(EEBUFFER_SAMPLE_COUNT >= num8Khzsamples*6); + + AudioUpSample((gsi_i16*)&EEBuffer[gEEDataSampleCount],(const gsi_i16*)data,num8Khzsamples); + + gEEDataSampleCount+=num8Khzsamples*6; + + return num8Khzsamples; +} + +static void EEStreamConvertAndTransfer(GVIDevice * device, int which) +{ + GVIPS2Spu2Data * data = (GVIPS2Spu2Data *)device->m_data; + GVBool wroteToBuffer; + int samplesPushed; + +static int BlockSize= 256; // in samples +static int EEMul = 2; +static int IOPMul = 4; + + int iopmem; + u_char *eemem; + int i; + + // push any pending data + if(data->m_playbackBufferPos) + { + EEStreamDataPush(data->m_playbackBuffer + data->m_playbackBufferPos, GVISamplesPerFrame - data->m_playbackBufferPos); + data->m_playbackBufferPos = 0; + } + + do + { + // write a frame of sources to our buffer + wroteToBuffer = gviWriteSourcesToBuffer(data->m_playbackSources, data->m_playbackClock, data->m_playbackBuffer, 1); + + // clear it if nothing was written + if(!wroteToBuffer) + memset(data->m_playbackBuffer, 0, (unsigned int)GVIBytesPerFrame); + + // filter + if(device->m_playbackFilterCallback) + device->m_playbackFilterCallback(device, data->m_playbackBuffer, data->m_playbackClock); + + // push it + samplesPushed = EEStreamDataPush(data->m_playbackBuffer, GVISamplesPerFrame); + + // update the clock + data->m_playbackClock++; + } + while(gEEDataSampleCount < EEBUFFER_SAMPLE_COUNT); + + // store the position at which the push stopped + if(samplesPushed < GVISamplesPerFrame) + data->m_playbackBufferPos = samplesPushed; + else + data->m_playbackBufferPos = 0; + + gEEDataSampleCount = 0; // reset + + // Do a memcpy from EE to IOP memory using DMA, respecting 256 sample interleave + iopmem = IOPBuffer + which * IOP_BUFF_SIZE; + eemem = (u_char *)(&EEBuffer[0]); + for (i=0; i< EEBUFFER_SAMPLE_COUNT;i+= BlockSize) + { + //left + DMA_EE_To_IOP( iopmem, // IOP side destination address + eemem, // EE side source address + BlockSize*2); + + //right + DMA_EE_To_IOP( iopmem+BlockSize*2, // IOP side destination address + eemem, // EE side source address + BlockSize*2); + + iopmem += BlockSize * IOPMul; // 4 ,2, + eemem += BlockSize * EEMul; // 2 ,2, + } +} + +void gviPS2Spu2Think(void) +{ + GVIPS2Spu2Data * data; + int v; + int which; + + if(!GVIPS2Spu2Device) + return; + + // get the data + data = (GVIPS2Spu2Data *)GVIPS2Spu2Device->m_data; + + // don't do anything if we're not playing + if(!data->m_playing) + return; + + // Check if an interrupt has occured. IF this were a separate thread, it could sleep until this happens. + if(!gEEStreanIntr) + return; + + // IOP_BUFF_SIZE has been played + // clear flag + gEEStreanIntr = 0; + + v = sceSdRemote(1, rSdBlockTransStatus ,AUTODMA_CH,0); + + which = 1 - (v >>24); + + // done transfer, send some more data + EEStreamConvertAndTransfer(GVIPS2Spu2Device, which); +} + +int gviPS2Spu2ListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types) +{ + // check for no room + if(maxDevices < 1) + return 0; + + // check for no playback devices wanted + if(!(types & GV_PLAYBACK)) + return 0; + + // store this device's info in the array + devices[0].m_id = GVPS2Spu2DeviceID; + strcpy(devices[0].m_name, "System Sound"); + devices[0].m_deviceType = GV_PLAYBACK; + devices[0].m_defaultDevice = (GVDeviceType)0; // ps2 doesn't support default devices + devices[0].m_hardwareType = GVHardwarePS2Spu2; + + return 1; +} + +static GVBool gviPS2Spu2StartDevice(GVIDevice * device, GVDeviceType type) +{ + GVIPS2Spu2Data * data = (GVIPS2Spu2Data *)device->m_data; + + if(!(type & GV_PLAYBACK)) + return GVFalse; + + // clear the clock + data->m_playbackClock = 0; + + // started playing + data->m_playing = GVTrue; + + return GVTrue; +} + +static void gviPS2Spu2StopDevice(GVIDevice * device, GVDeviceType type) +{ + GVIPS2Spu2Data * data = (GVIPS2Spu2Data *)device->m_data; + + if(!(type & GV_PLAYBACK)) + return; + + // stopped playing + data->m_playing = GVFalse; + + // clear any pending sources & buffers + gviClearSourceList(data->m_playbackSources); + + // clear the SPU + EEStreamClearSPUBuffer(); +} + +static GVBool gviPS2Spu2IsDeviceStarted(GVIDevice * device, GVDeviceType type) +{ + GVIPS2Spu2Data * data = (GVIPS2Spu2Data *)device->m_data; + + if(type == GV_PLAYBACK) + return data->m_playing; + return GVFalse; +} + +static void gviPS2Spu2SetDeviceVolume(GVIDevice * device, GVDeviceType type, GVScalar volume) +{ + GVIPS2Spu2Data * data = (GVIPS2Spu2Data *)device->m_data; + gsi_i16 sVol; + + if(!(type & GV_PLAYBACK)) + return; + + // store the volume + data->m_playbackVolume = volume; + + // convert it into a signed short + sVol = (gsi_i16)(volume * 0x3FFF); + sceSdRemote(1, rSdSetParam, AUTODMA_CH | SD_P_BVOLL, sVol); + sceSdRemote(1, rSdSetParam, AUTODMA_CH | SD_P_BVOLR, sVol); +} + +static GVScalar gviPS2Spu2GetDeviceVolume(GVIDevice * device, GVDeviceType type) +{ + GVIPS2Spu2Data * data = (GVIPS2Spu2Data *)device->m_data; + + if(!(type & GV_PLAYBACK)) + return 0; + + return data->m_playbackVolume; +} + +static void gviPS2Spu2PlayPacket(GVIDevice * device, const GVByte * packet, int len, GVSource source, GVFrameStamp frameStamp, GVBool mute) +{ + GVIPS2Spu2Data * data = (GVIPS2Spu2Data *)device->m_data; + + // don't do anything if we're not playing + if(!data->m_playing) + return; + + //add it + gviAddPacketToSourceList(data->m_playbackSources, packet, len, source, frameStamp, mute, data->m_playbackClock); +} + +static GVBool gviPS2Spu2IsSourceTalking(GVDevice device, GVSource source) +{ + GVIPS2Spu2Data * data = (GVIPS2Spu2Data *)device->m_data; + + // don't do anything if we're not playing + if(!data->m_playing) + return GVFalse; + + return gviIsSourceTalking(data->m_playbackSources, source); +} + +static int gviPS2Spu2ListTalkingSources(GVDevice device, GVSource sources[], int maxSources) +{ + GVIPS2Spu2Data * data = (GVIPS2Spu2Data *)device->m_data; + + // don't do anything if we're not playing + if(!data->m_playing) + return GVFalse; + + return gviListTalkingSources(data->m_playbackSources, sources, maxSources); +} + +static void gviPS2Spu2FreeDevice(GVIDevice * device) +{ + GVIPS2Spu2Data * data = (GVIPS2Spu2Data *)device->m_data; + + EEStreamClearSPUBuffer(); + + // Free resources + sceSifFreeIopHeap((void *)IOPBuffer); + + IOPBuffer = (int)NULL; + + // Turn off interrupt + sceSdRemoteCallbackQuit(); + sceSdRemote(1, rSdSetTransIntrHandler,AUTODMA_CH,NULL, NULL); + + // kill volume + //changeInputVolume(0x0000); + + if(data->m_playbackSources) + gviFreeSourceList(data->m_playbackSources); + gsifree(data->m_playbackBuffer); + + // free the device + gviFreeDevice(device); + + GVIPS2Spu2Device = NULL; +} + +static GVBool gviPS2Spu2InitDevice(GVIDevice * device, int deviceIndex, GVDeviceType type) +{ + GVIPS2Spu2Data * data = (GVIPS2Spu2Data *)device->m_data; + + // create the array of sources + data->m_playbackSources = gviNewSourceList(); + if(!data->m_playbackSources) + return GVFalse; + + // allocate the buffer + data->m_playbackBuffer = (GVSample *)gsimalloc((unsigned int)GVIBytesPerFrame); + if(!data->m_playbackBuffer) + { + gviFreeSourceList(data->m_playbackSources); + return GVFalse; + } + + // these are the buffers which the audio (SPU) will stream from on the IOP. + // they are double buffered, with one half being read while the other is dma'ed into from the EE + IOPBuffer = (int)sceSifAllocIopHeap(IOP_BUFF_SIZE*2); + assert(IOPBuffer ); + + // Set interrupt to receive mid and end point in buffer + sceSdRemoteCallbackInit(5); + sceSdRemote(1, rSdSetTransIntrHandler,AUTODMA_CH,cbEEStreamTransfer, NULL); + + + // clear interrupt flag. After this each interrupt, this flag will be set to 1. + gEEStreanIntr = 0; + + EEStreamClearSPUBuffer(); + + + // Turn on the auto dma transfer process. From here in the dma continuously + // load data into the sound processor memory, playing it. + // at mid and end points, cbEEStreamTransfer will be called + + sceSdRemote(1, rSdBlockTrans, AUTODMA_CH, + (SD_TRANS_MODE_WRITE| SD_BLOCK_LOOP), + IOPBuffer, // start of buffer + IOP_BUFF_SIZE*2, // size of buffer + IOPBuffer // where in buffer to start + ); + + sceSdRemote(1, rSdSetParam, AUTODMA_CH | SD_P_BVOLL, 0x3FFF); + sceSdRemote(1, rSdSetParam, AUTODMA_CH | SD_P_BVOLR, 0x3FFF); + sceSdRemote(1, rSdSetParam, AUTODMA_CH | SD_P_MVOLL, 0x3FFF); + sceSdRemote(1, rSdSetParam, AUTODMA_CH | SD_P_MVOLR, 0x3FFF); + + // set data + data->m_playbackVolume = 1.0; + GSI_UNUSED(deviceIndex); + GSI_UNUSED(type); + return GVTrue; +} + +GVDevice gviPS2Spu2NewDevice(GVDeviceID deviceID, GVDeviceType type) +{ + GVIDevice * device; + GVBool result; + + // check if the device already exists + if(GVIPS2Spu2Device) + return NULL; + + // check the ID + if(deviceID != GVPS2Spu2DeviceID) + return NULL; + + // check the type + if(type != GV_PLAYBACK) + return NULL; + + // create a new device + device = gviNewDevice(deviceID, GVHardwarePS2Spu2, type, sizeof(GVIPS2Spu2Data)); + if(!device) + return NULL; + + // init the device + result = gviPS2Spu2InitDevice(device, deviceID, type); + if(result == GVFalse) + { + gviFreeDevice(device); + return NULL; + } + + // store the pointers + device->m_methods.m_freeDevice = gviPS2Spu2FreeDevice; + device->m_methods.m_startDevice = gviPS2Spu2StartDevice; + device->m_methods.m_stopDevice = gviPS2Spu2StopDevice; + device->m_methods.m_isDeviceStarted = gviPS2Spu2IsDeviceStarted; + device->m_methods.m_setDeviceVolume = gviPS2Spu2SetDeviceVolume; + device->m_methods.m_getDeviceVolume = gviPS2Spu2GetDeviceVolume; + device->m_methods.m_playPacket = gviPS2Spu2PlayPacket; + device->m_methods.m_isSourceTalking = gviPS2Spu2IsSourceTalking; + device->m_methods.m_listTalkingSources = gviPS2Spu2ListTalkingSources; + + // store a pointer to the device + GVIPS2Spu2Device = device; + + return device; +} + +#endif //!defined(GV_NO_PS2_SPU2) diff --git a/code/gamespy/Voice2/gvPS2Spu2.h b/code/gamespy/Voice2/gvPS2Spu2.h new file mode 100644 index 00000000..576772c8 --- /dev/null +++ b/code/gamespy/Voice2/gvPS2Spu2.h @@ -0,0 +1,29 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +18002 Skypark Circle +Irvine, California 92614 +949.798.4200 (Tel) +949.798.4299 (Fax) +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_PS2_SPU2_H_ +#define _GV_PS2_SPU2_H_ + +#include "gvMain.h" + +GVBool gviPS2Spu2Startup(void); +void gviPS2Spu2Cleanup(void); +void gviPS2Spu2Think(void); + +int gviPS2Spu2ListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types); + +GVDevice gviPS2Spu2NewDevice(GVDeviceID deviceID, GVDeviceType type); + +#endif diff --git a/code/gamespy/Voice2/gvPS3Audio.c b/code/gamespy/Voice2/gvPS3Audio.c new file mode 100644 index 00000000..4050096e --- /dev/null +++ b/code/gamespy/Voice2/gvPS3Audio.c @@ -0,0 +1,48 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gvPS3Audio.h" +#include "gvPS3Headset.h" + +#if !defined(_PS3) +#error This file should only be used with the PlayStation3 +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// starts up the devices +GVBool gviHardwareStartup(void) +{ + return gviPS3HeadsetStartup(); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// cleans up the devices +void gviHardwareCleanup(void) +{ + gviPS3HeadsetCleanup(); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// should be called every once in a while +void gviHardwareThink(void) +{ + gviPS3HeadsetThink(); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// lists the connected devices +int gviHardwareListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types) +{ + return gviPS3HeadsetListDevices(devices, maxDevices, types); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// initializes a new device +GVDevice gviHardwareNewDevice(GVDeviceID deviceID, GVDeviceType type) +{ + return gviPS3HeadsetNewDevice(deviceID, type); +} diff --git a/code/gamespy/Voice2/gvPS3Audio.h b/code/gamespy/Voice2/gvPS3Audio.h new file mode 100644 index 00000000..76b0395a --- /dev/null +++ b/code/gamespy/Voice2/gvPS3Audio.h @@ -0,0 +1,21 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef _GV_PS3_AUDIO_H_ +#define _GV_PS3_AUDIO_H_ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gvMain.h" + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Public Interfaces +GVBool gviHardwareStartup(void); +void gviHardwareCleanup(void); +void gviHardwareThink(void); + +int gviHardwareListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types); + +GVDevice gviHardwareNewDevice(GVDeviceID deviceID, GVDeviceType type); + +#endif // _GV_PS3_AUDIO_H_ diff --git a/code/gamespy/Voice2/gvPS3Headset.c b/code/gamespy/Voice2/gvPS3Headset.c new file mode 100644 index 00000000..7c447215 --- /dev/null +++ b/code/gamespy/Voice2/gvPS3Headset.c @@ -0,0 +1,1141 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gvPS3Headset.h" +#if !defined(GV_NO_PS3_HEADSET) +#include "gvDevice.h" +#include "gvCodec.h" +#include "gvSource.h" +#include "gvUtil.h" +#include +#include +#include +#include +#include +#include + + +#if !defined(_PS3) +#error This file should only be used with the PlayStation3 +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// definitions +#define GVI_PLAYBACK_STAYAHEAD_MILLISECONDS 50 + +// Used as a starting value for the audio port queue key +#define GVI_AUDIO_QUEUE_KEY_BASE 0x0000998877660000ULL + +// Used as a starting value for the capture event queue key +#define GVI_CAPTURE_QUEUE_KEY_BASE 0x0000000072110700UL + +#define GVI_AUDIO_QUEUE_DEPTH 4 +#define GVI_CAPTURE_VOLUME_MAX 241 +#define GVI_PLAYBACK_NUM_BLOCKS CELL_AUDIO_BLOCK_32 +#define GVI_PLAYBACK_BLOCK_SAMPLES CELL_AUDIO_BLOCK_SAMPLES +#define GVI_PLAYBACK_SAMPLE_RATE 48000 //Hz +#define GVI_PS3_MIC_BUFFER_MS 1000 +#define GVI_LOCAL_TALK_MAX 10 +#define GVI_PLAYBACK_SAMPLE_FACTOR (GVI_PLAYBACK_SAMPLE_RATE / gviGetSampleRate()) +#define GVI_NUM_CHANNELS CELL_AUDIO_PORT_2CH + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// structs +typedef struct +{ + sys_event_t m_captureCallbackEvent; + sys_event_queue_t m_captureCallbackQueue; + uint64_t m_captureEventQueueKey; + GVBool m_capturing; + GVScalar m_captureVolume; + GVFrameStamp m_captureClock; + GVScalar m_captureThreshold; + GVFrameStamp m_captureLastCrossedThresholdTime; + float *m_capturePreConvertBuffer; + size_t m_capturePreConvertBufferLen; + GVSample *m_captureBuffer; + + int m_captureBufferBytes; + int m_deviceNum; // used to keep the microphone device number + GVBool m_captureMicOpen; + + GVBool m_playing; + GVScalar m_playbackVolume; + GVFrameStamp m_playbackClock; + GVISourceList m_playbackSources; + GVSample *m_playbackBuffer; + gsi_u32 m_playbackCellAudioPortNum; + CellAudioPortConfig m_playbackCellAudioConfig; + sys_event_queue_t m_playbackQueue; + uint64_t m_playbackQueueKey; + int m_playbackPortPos; +} GVIPS3HeadsetData; + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// globals +static GVIDeviceList GVIDevices; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// gets the device by the deviceID +static GVIDevice * gviFindDeviceByID(GVDeviceID deviceID) +{ + GVIDevice * device; + int num; + int i; + + num = gviGetNumDevices(GVIDevices); + for(i = 0 ; i < num ; i++) + { + device = gviGetDevice(GVIDevices, i); + if(device->m_deviceID == deviceID) + return device; + } + + return NULL; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// frees the device +static void gviFreeArrayDevice(void * elem) +{ + GS_ASSERT(elem); + GVIDevice * device = *(GVIDevice **)elem; + GVIPS3HeadsetData * data = (GVIPS3HeadsetData *)device->m_data; + int result; + + // turn off mic if its a capture device and if the mic port is open + if (data->m_capturing && cellMicIsOpen(data->m_deviceNum)) + result = cellMicClose(data->m_deviceNum); + + // Destroy the callback and playback queues + if (data->m_playbackQueue) + cellAudioRemoveNotifyEventQueue(data->m_playbackQueueKey); + if (data->m_captureCallbackQueue) + cellMicRemoveNotifyEventQueue(data->m_captureEventQueueKey); + if (data->m_playbackQueue) + sys_event_queue_destroy(data->m_playbackQueue, 0); + if (data->m_captureCallbackQueue) + sys_event_queue_destroy(data->m_captureCallbackQueue, 0); + + // close audio port + if (data->m_playing) + cellAudioPortClose(data->m_playbackCellAudioPortNum); + + // playback specific cleanup + if(device->m_types & GV_PLAYBACK) + { + if(data->m_playbackSources) + gviFreeSourceList(data->m_playbackSources); + gsifree(data->m_playbackBuffer); + } + + // capture specific cleanup + if(device->m_types & GV_CAPTURE) + { + gsifree(data->m_captureBuffer); + gsifree(data->m_capturePreConvertBuffer); + } + + // free the device + gviFreeDevice(device); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// starts up the headset +GVBool gviPS3HeadsetStartup(void) +{ + int result; + + // create the array of devices + GVIDevices = gviNewDeviceList(gviFreeArrayDevice); + if(!GVIDevices) + return GVFalse; + + // initialize the mic library + result = cellSysmoduleLoadModule(CELL_SYSMODULE_MIC); + if(result != CELL_OK) + { + gviFreeDeviceList(GVIDevices); + GVIDevices = NULL; + return GVFalse; + } + + result = cellMicInit(); + if(result != CELL_OK) + { + gviFreeDeviceList(GVIDevices); + GVIDevices = NULL; + return GVFalse; + } + + // initialize the audio library + result = cellSysmoduleLoadModule(CELL_SYSMODULE_AUDIO); + if(result != CELL_OK) + { + gviFreeDeviceList(GVIDevices); + GVIDevices = NULL; + return GVFalse; + } + + result = cellAudioInit(); + if(result != CELL_OK && result != CELL_AUDIO_ERROR_ALREADY_INIT) + { + gviFreeDeviceList(GVIDevices); + cellMicEnd(); + GVIDevices = NULL; + return GVFalse; + } + + return GVTrue; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// unloads the headset +void gviPS3HeadsetCleanup(void) +{ + // free the device array + if(GVIDevices) + { + gviFreeDeviceList(GVIDevices); + GVIDevices = NULL; + } + + cellMicEnd(); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// process playback if there is any in the queue +static GVBool gviPlaybackDeviceThink(GVIDevice * device) +{ + GVIPS3HeadsetData * data = (GVIPS3HeadsetData *)device->m_data; + int remainingSamples; + int numFrames; + GVBool wroteToBuffer; + int i, j, k; + sys_event_t playbackQueueEvent; + int result; + int playbackReadPos; + unsigned int currentBlock; + int totalSamples; + + // don't do anything if we're not playing + if(!data->m_playing) + return GVTrue; + + // check the queue + result = sys_event_queue_receive(data->m_playbackQueue, &playbackQueueEvent, 1); + if(result == ETIMEDOUT) + { + return GVTrue; + } + if(result != CELL_OK) + { + return GVFalse; + } + + totalSamples = (GVI_PLAYBACK_NUM_BLOCKS * GVI_PLAYBACK_BLOCK_SAMPLES); + + currentBlock = (unsigned int)*(uint64_t *)(data->m_playbackCellAudioConfig.readIndexAddr); + playbackReadPos = (currentBlock * GVI_PLAYBACK_BLOCK_SAMPLES); + + if(data->m_playbackPortPos == -1) + { + unsigned int nextBlock = (currentBlock + 1) % GVI_PLAYBACK_NUM_BLOCKS; // write target is next block + data->m_playbackPortPos = (nextBlock * GVI_PLAYBACK_BLOCK_SAMPLES); + } + + remainingSamples = (((playbackReadPos + totalSamples) - data->m_playbackPortPos) % totalSamples); + + // figure out the number of frames that we can write + numFrames = ((remainingSamples / GVI_PLAYBACK_SAMPLE_FACTOR) / GVISamplesPerFrame); + + // write the frames + for(i = 0 ; i < numFrames ; i++) + { + // write a frame of sources to our buffer + wroteToBuffer = gviWriteSourcesToBuffer(data->m_playbackSources, data->m_playbackClock, data->m_playbackBuffer, 1); + + // clear it if nothing was written + if(!wroteToBuffer) + memset(data->m_playbackBuffer, 0, (unsigned int)GVIBytesPerFrame); + + // filter + if(device->m_playbackFilterCallback) + device->m_playbackFilterCallback(device, data->m_playbackBuffer, data->m_playbackClock); + + // write to port buffer from m_playbackBuffer + // converting from sample to float + // converting from 8khz or 16KHz to 48khz and mono to stereo (write each sample 6-12 + // times depending on sample rate) + for(j = 0 ; j < GVISamplesPerFrame ; j++) + { + float sample = ((float)data->m_playbackBuffer[j] / (float)SHRT_MAX); + for(k = 0; k < GVI_PLAYBACK_SAMPLE_FACTOR; k++) + { + float *dest = (float *)(data->m_playbackCellAudioConfig.portAddr + + (data->m_playbackPortPos * GVI_NUM_CHANNELS * sizeof(float))); + *dest++ = sample; + *dest = sample; + data->m_playbackPortPos++; + data->m_playbackPortPos %= totalSamples; + } + } + + // update the clock + data->m_playbackClock++; + } + + return GVTrue; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// called every once in a while to process playback +void gviPS3HeadsetThink(void) +{ + GVIDevice * device; + GVBool rcode; + int num; + int i; + + if(!GVIDevices) + return; + + // loop through the devices backwards to that we can remove devices as we go + num = gviGetNumDevices(GVIDevices); + for(i = (num - 1) ; i >= 0 ; i--) + { + // get the device + device = gviGetDevice(GVIDevices, i); + + // // check if playback is setup on the device + if(device->m_types & GV_PLAYBACK) + { + // let it think + rcode = gviPlaybackDeviceThink(device); + + // check if the device was unplugged + if(!rcode) + gviDeviceUnplugged(device); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// gets the devices detected +int gviPS3HeadsetListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types) +{ + int index; + int numDevices = 0; + + maxDevices = min(maxDevices, CELL_MAX_MICS); + + for(index = 0 ; index < maxDevices ; index++) + { + if(cellMicIsAttached(index)) + { + int deviceType; + if (cellMicGetType(index, &deviceType) == CELL_OK) + { + if (deviceType == CELLMIC_TYPE_USBAUDIO) + { + devices[numDevices].m_id = index; + devices[numDevices].m_deviceType = GV_CAPTURE | GV_PLAYBACK; + devices[numDevices].m_defaultDevice = (GVDeviceType)0; + _tcscpy(devices[numDevices].m_name, _T("USB Headset")); + devices[numDevices].m_hardwareType = GVHardwarePS3Headset; + } + else if (deviceType == CELLMIC_TYPE_BLUETOOTH) + { + devices[numDevices].m_id = index; + devices[numDevices].m_deviceType = GV_CAPTURE | GV_PLAYBACK; + devices[numDevices].m_defaultDevice = (GVDeviceType)0; + _tcscpy(devices[numDevices].m_name, _T("Bluetooth Headset")); + devices[numDevices].m_hardwareType = GVHardwarePS3Headset; + } + } + + numDevices++; + } + } + return numDevices; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// unloades the device from the device array +static void gviPS3HeadsetFreeDevice(GVIDevice * device) +{ + // delete it from the array + // it will clear out internal data in the array's free function + gviDeleteDeviceFromList(GVIDevices, device); +} + +static GVBool gviPS3HeadsetInitHeadphone(GVIPS3HeadsetData *data) +{ + int result; + CellAudioPortParam audioParam; + sys_event_queue_attribute_t aQueueAttr; + int aCount; + + audioParam.attr = CELL_AUDIO_PORTATTR_OUT_SECONDARY; + audioParam.nBlock = GVI_PLAYBACK_NUM_BLOCKS; + audioParam.nChannel = GVI_NUM_CHANNELS; + + // set the audio port value to something really abnormal + // so that if the open function fails, the gviPS3HeadsetClose + // will close the port only if a valid port number is assigned + data->m_playbackCellAudioPortNum = 0xFFFFFFFF; + + // Playback + /////////// + + result = cellAudioPortOpen(&audioParam, &data->m_playbackCellAudioPortNum); + if (result != CELL_OK) + { + return GVFalse; + } + + // get the config for the audio port so we can use it to write data to the audio port ring buffer + result = cellAudioGetPortConfig(data->m_playbackCellAudioPortNum, &data->m_playbackCellAudioConfig); + if (result != CELL_OK) + { + return GVFalse; + } + + // Event queue notify tells us when the system is ready to play new data + aCount = 0; + sys_event_queue_attribute_initialize(aQueueAttr); + aQueueAttr.attr_protocol = SYS_SYNC_FIFO; + data->m_playbackQueueKey = GVI_AUDIO_QUEUE_KEY_BASE; + + while (aCount < 10) + { + result = sys_event_queue_create(&data->m_playbackQueue, &aQueueAttr, + data->m_playbackQueueKey, GVI_AUDIO_QUEUE_DEPTH); + if (result == CELL_OK) + { + break; + } + // search unused key + data->m_playbackQueueKey = GVI_AUDIO_QUEUE_KEY_BASE | (rand() & 0x0ffff); + aCount++; + } + + if (result != CELL_OK) + { + return GVFalse; + } + + // register event queue to libaudio + result = cellAudioSetNotifyEventQueue(data->m_playbackQueueKey); + if (result < 0) + { + return GVFalse; + } + + return GVTrue; +} + +GVBool gviPS3HeadsetInitMic(GVIPS3HeadsetData *data) +{ + int result = 0; + int aMsg = 0; + int aDevNum=0; + + int aCount = 0; + sys_event_queue_attribute_t equeue_attr = {SYS_SYNC_FIFO, SYS_PPU_QUEUE, ""}; + + // Event queue key for libmic + // checks for an event occurrence + //create event queue to recv "MicIn" callback from MIOS + data->m_captureEventQueueKey = GVI_CAPTURE_QUEUE_KEY_BASE; + + while ( (aCount++) < 100 ) + { + result = sys_event_queue_create(&data->m_captureCallbackQueue, + &equeue_attr, data->m_captureEventQueueKey, 32); + if (result == CELL_OK) break; + data->m_captureEventQueueKey = GVI_CAPTURE_QUEUE_KEY_BASE | ( rand() & 0xffff); + } + if (result != CELL_OK) + { + return GVFalse; + } + + //install "MicIn" system-callback(with devnum == -1) to recv attach/detach event + result = cellMicSetNotifyEventQueue(data->m_captureEventQueueKey); + if (result != CELL_OK) + { + return GVFalse; + } + + // Wait up to 10 ms before checking if the mike is attached + result = sys_event_queue_receive(data->m_captureCallbackQueue, + &data->m_captureCallbackEvent, 10000); + if(result == ETIMEDOUT) + return GVFalse; + + aMsg = (int)data->m_captureCallbackEvent.data1; + aDevNum = (int)data->m_captureCallbackEvent.data2; + + if (aMsg == CELLMIC_ATTACH && aDevNum == data->m_deviceNum) + { + // start with the default audio device + result = cellMicOpenEx(data->m_deviceNum, GVI_PLAYBACK_SAMPLE_RATE, 1, + GV_SAMPLES_PER_SECOND, GVI_PS3_MIC_BUFFER_MS, CELLMIC_SIGTYPE_DSP); + + if (result != CELL_OK) + { + return GVFalse; + } + data->m_captureMicOpen = GVTrue; + } + return GVTrue; +} + +void gviPS3HeadsetCloseHeadphone(GVIPS3HeadsetData *data) +{ + // Remove and Destroy the callback and playback queues + if (data->m_playbackQueue) + cellAudioRemoveNotifyEventQueue(data->m_playbackQueue); + if (data->m_playbackQueue) + sys_event_queue_destroy(data->m_playbackQueue, 0); + + // the audio port needs to be closed if a device wasn't initialized properly + if (data->m_playbackCellAudioPortNum != 0xFFFFFFFF) + cellAudioPortClose(data->m_playbackCellAudioPortNum); +} + +void gviPS3HeadsetCloseMic(GVIPS3HeadsetData *data) +{ + // Remove and Destroy the callback and playback queues + if (data->m_captureCallbackQueue) + cellAudioRemoveNotifyEventQueue(data->m_captureCallbackQueue); + if (data->m_captureCallbackQueue) + sys_event_queue_destroy(data->m_captureCallbackQueue, 0); + + // The mic should be closed if it is open + if (cellMicIsOpen(data->m_deviceNum)) + cellMicClose(data->m_deviceNum); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// initializes the device for playback +static GVBool gviStartPlaybackDevice(GVIPS3HeadsetData * data) +{ + int result; + + sys_event_queue_drain(data->m_playbackQueue); + + data->m_playbackPortPos = -1; + + result = cellAudioPortStart(data->m_playbackCellAudioPortNum); + if (result != CELL_OK) + { + return GVFalse; + } + + // clear the clock + data->m_playbackClock = 0; + + // started playing + data->m_playing = GVTrue; + + return GVTrue; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// initializes the device for capture +static GVBool gviStartCaptureDevice(GVIPS3HeadsetData * data) +{ + int result; + // start the mic capture + if (data->m_captureMicOpen) + { + result = cellMicStart(data->m_deviceNum); + if (result != CELL_OK) + return GVFalse; + } + else + return GVFalse; + + cellMicReset(data->m_deviceNum); + // no data in the capture buffer + data->m_captureBufferBytes = 0; + data->m_capturePreConvertBufferLen = 0; + + // started capturing + data->m_capturing = GVTrue; + + return GVTrue; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// stops the device +static void gviPS3HeadsetStopDevice(GVIDevice * device, GVDeviceType type) +{ + GVIPS3HeadsetData * data = (GVIPS3HeadsetData *)device->m_data; + + if(type & GV_PLAYBACK) + { + cellAudioPortStop(data->m_playbackCellAudioPortNum); + + // clear the playback buffer + memset(data->m_playbackBuffer, 0, GVIBytesPerFrame); + + // stopped playing + data->m_playing = GVFalse; + + // clear any pending sources & buffers + gviClearSourceList(data->m_playbackSources); + } + if(type & GV_CAPTURE) + { + // stop the capture buffer + cellMicStop(data->m_deviceNum); + + // clear capture buffer + memset(data->m_captureBuffer, 0, GVIBytesPerFrame); + + // stopped capturing + data->m_capturing = GVFalse; + + // so a stop then start isn't continuous + data->m_captureClock++; + } + +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// starts the device +static GVBool gviPS3HeadsetStartDevice(GVIDevice * device, GVDeviceType type) +{ + GVIPS3HeadsetData * data = (GVIPS3HeadsetData *)device->m_data; + + if(type == GV_PLAYBACK) + { + return gviStartPlaybackDevice(data); + } + if(type == GV_CAPTURE) + { + return gviStartCaptureDevice(data); + } + if(type == GV_CAPTURE_AND_PLAYBACK) + { + if(!gviStartPlaybackDevice(data)) + return GVFalse; + if(!gviStartCaptureDevice(data)) + { + gviPS3HeadsetStopDevice(device, GV_PLAYBACK); + return GVFalse; + } + return GVTrue; + } + return GVFalse; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// checks to see if the device is ready +static GVBool gviPS3HeadsetIsDeviceStarted(GVIDevice * device, GVDeviceType type) +{ + // NULL device means not even created or started + GS_ASSERT(device); + if (!device) + return GVFalse; + + GVIPS3HeadsetData * data = (GVIPS3HeadsetData *)device->m_data; + + if(type == GV_PLAYBACK) + return data->m_playing; + if(type == GV_CAPTURE) + return data->m_capturing; + if(type == GV_CAPTURE_AND_PLAYBACK) + return (data->m_playing && data->m_capturing); + return GVFalse; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// sets the volume for the device +static void gviPS3HeadsetSetDeviceVolume(GVIDevice * device, GVDeviceType type, GVScalar volume) +{ + GVIPS3HeadsetData * data = (GVIPS3HeadsetData *)device->m_data; + + if(type & GV_PLAYBACK) + { + cellAudioSetPortLevel(data->m_playbackCellAudioPortNum, volume); + data->m_playbackVolume = volume; + } + if(type & GV_CAPTURE) + { + cellMicSetDeviceAttr(data->m_deviceNum, CELLMIC_DEVATTR_VOLUME, (int)(volume * GVI_CAPTURE_VOLUME_MAX), 0); + data->m_captureVolume = volume; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// gets the device volume +static GVScalar gviPS3HeadsetGetDeviceVolume(GVIDevice * device, GVDeviceType type) +{ + GVIPS3HeadsetData * data = (GVIPS3HeadsetData *)device->m_data; + + if(type & GV_PLAYBACK) + return data->m_playbackVolume; + return data->m_captureVolume; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// sets the capture threshold +static void gviPS3HeadsetSetCaptureThreshold(GVIDevice * device, GVScalar threshold) +{ + GVIPS3HeadsetData * data = (GVIPS3HeadsetData *)device->m_data; + data->m_captureThreshold = threshold; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// gets the capture threshold +static GVScalar gviPS3HeadsetGetCaptureThreshold(GVIDevice * device) +{ + GVIPS3HeadsetData * data = (GVIPS3HeadsetData *)device->m_data; + return data->m_captureThreshold; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// gets the available capture bytes +static int gviPS3HeadsetGetAvailableCaptureBytes(GVDevice device) +{ + GVIPS3HeadsetData * data = (GVIPS3HeadsetData *)device->m_data; + + // don't do anything if we're not capturing + if(!data->m_capturing) + return 0; + + // no call listed in the Sony documentation, so just return 1 + return 1; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +// +GVBool gviPS3HeadsetHandleMicAttach(GVIPS3HeadsetData *data) +{ + int result; + + // start with the default audio device + result = cellMicOpenEx(data->m_deviceNum, GVI_PLAYBACK_SAMPLE_RATE, 1, + GV_SAMPLES_PER_SECOND, GVI_PS3_MIC_BUFFER_MS, CELLMIC_SIGTYPE_DSP); + + if (result != CELL_OK) + { + return GVFalse; + } + data->m_captureMicOpen = GVTrue; + result = cellMicStart(data->m_deviceNum); + if (result != CELL_OK) + { + return GVFalse; + } + result = cellMicReset(data->m_deviceNum); + if (result != CELL_OK) + { + return GVFalse; + } + return GVTrue; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// processes the captured frame +static void gviProcessCapturedFrame(GVDevice device, GVSample *frameIn, GVByte* frameOut, GVScalar *volume, GVBool *threshold) +{ + GVIPS3HeadsetData * data = (GVIPS3HeadsetData *)device->m_data; + GVScalar frameVolume; + + // get the volume if requested + if(volume) + { + frameVolume = gviGetSamplesVolume(frameIn, GVISamplesPerFrame); + if(frameVolume > *volume) + *volume = frameVolume; + } + + // check against the threshold + if(threshold && !*threshold) + { + if(volume) + { + // we already got the volume, so use that to check + *threshold = (*volume >= data->m_captureThreshold); + } + else + { + // we didn't get a volume, so check the samples directly + *threshold = gviIsOverThreshold(frameIn, GVISamplesPerFrame, data->m_captureThreshold); + } + } + + // filter + if(device->m_captureFilterCallback) + device->m_captureFilterCallback(device, frameIn, data->m_captureClock); + + // increment the capture clock + data->m_captureClock++; + + // encode the buffer into the packet + gviEncode(frameOut, frameIn); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// captures a packet +static GVBool gviPS3HeadsetCapturePacket(GVDevice device, GVByte * packet, int * len, GVFrameStamp * frameStamp, GVScalar * volume) +{ + GVIPS3HeadsetData * data = (GVIPS3HeadsetData *)device->m_data; + GVBool overThreshold; + int result; + int numFrames; + int readSize; + int lenAvailable; + int framesAvailable; + GVByte * frameOut; + float *frameIn; + int aCaptureQueueMsg; + int aDeviceIndex; + int sample; + int localtalk; + // figure out how many encoded bytes they can handle + lenAvailable = *len; + + // clear the len and volume + *len = 0; + if(volume) + *volume = 0; + + // don't do anything if we're not capturing + if(!data->m_capturing) + return GVFalse; + + // set the frameStamp + *frameStamp = data->m_captureClock; + + // figure out how many frames they can handle + framesAvailable = (lenAvailable / GVIEncodedFrameSize); + overThreshold = GVFalse; + + frameOut = packet; + //frameIn = data->m_capturePreConvertBuffer; + // handle the data one frame at a time + for(numFrames = 0 ; numFrames < framesAvailable ; numFrames++) + { + // Wait up to 1 us before checking if the mike is attached + result = sys_event_queue_receive(data->m_captureCallbackQueue, + &data->m_captureCallbackEvent, 1); + if(result == ETIMEDOUT) + { + break; + } + + aCaptureQueueMsg = (int)data->m_captureCallbackEvent.data1; + aDeviceIndex = (int)data->m_captureCallbackEvent.data2; + if (aDeviceIndex != data->m_deviceNum) + continue; + + if (aCaptureQueueMsg == CELLMIC_ATTACH) + { + if (!gviPS3HeadsetHandleMicAttach(data)) + { + break; + } + } + else if (aCaptureQueueMsg == CELLMIC_DETACH) + { + gviDeviceUnplugged(device); + return GVFalse; + } + else if (aCaptureQueueMsg == CELLMIC_DATA) + { + // read this frame + //readSize = (GVIBytesPerFrame - data->m_captureBufferBytes); + readSize = ((GVISamplesPerFrame - data->m_capturePreConvertBufferLen) * sizeof(float)); + frameIn = data->m_capturePreConvertBuffer + data->m_capturePreConvertBufferLen; + readSize = cellMicRead(data->m_deviceNum, frameIn, readSize); + + cellMicGetSignalState(0, CELLMIC_SIGSTATE_LOCTALK, &localtalk); + if (localtalk < (int)(GVI_LOCAL_TALK_MAX * data->m_captureThreshold)) + { + return GVFalse; + } + + if (readSize == CELL_MICIN_ERROR_DEVICE_NOT_FOUND) + { + gviDeviceUnplugged(device); + return GVFalse; + } + + data->m_capturePreConvertBufferLen += (readSize / sizeof(float)); + if(data->m_capturePreConvertBufferLen < GVISamplesPerFrame) + { + break; + } + + // convert the data from 32 bit Big Endian Floats [-1.0,1.0] + // to 16 bit short and write the values into the buffer + for (sample = 0; sample < GVISamplesPerFrame; sample++) + { + data->m_captureBuffer[sample] = (GVSample)((SHRT_MAX)*data->m_capturePreConvertBuffer[sample]); + } + + // process the frame + gviProcessCapturedFrame(device, data->m_captureBuffer, frameOut, volume, &overThreshold); + + // we got a full frame, so there's no leftover + data->m_captureBufferBytes = 0; + + // update the frame pointer + frameOut += GVIEncodedFrameSize; + + data->m_capturePreConvertBufferLen = 0; + } + } + + // check if this packet crossed the threshold + if(overThreshold) + { + // store the time we crossed it + data->m_captureLastCrossedThresholdTime = data->m_captureClock; + } + else + { + // check if we are still on the overhang from a previous crossing + overThreshold = ((GVFrameStamp)(*frameStamp - data->m_captureLastCrossedThresholdTime) < GVI_HOLD_THRESHOLD_FRAMES); + } + + // set the len + *len = (numFrames * GVIEncodedFrameSize); + + // return false if we didn't get a packet + if(!overThreshold || (*len == 0)) + return GVFalse; + + return GVTrue; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// sends the packet to the mixer +static void gviPS3HeadsetPlayPacket(GVIDevice * device, const GVByte * packet, int len, GVSource source, GVFrameStamp frameStamp, GVBool mute) +{ + GVIPS3HeadsetData * data = (GVIPS3HeadsetData *)device->m_data; + + // don't do anything if we're not playing + if(!data->m_playing) + return; + + //add it + gviAddPacketToSourceList(data->m_playbackSources, packet, len, source, frameStamp, mute, data->m_playbackClock); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// checks to see if we're talking +static GVBool gviPS3HeadsetIsSourceTalking(GVDevice device, GVSource source) +{ + GVIPS3HeadsetData * data = (GVIPS3HeadsetData *)device->m_data; + + // don't do anything if we're not playing + if(!data->m_playing) + return GVFalse; + + return gviIsSourceTalking(data->m_playbackSources, source); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// lists the talking sources +static int gviPS3HeadsetListTalkingSources(GVDevice device, GVSource sources[], int maxSources) +{ + GVIPS3HeadsetData * data = (GVIPS3HeadsetData *)device->m_data; + + // don't do anything if we're not playing + if(!data->m_playing) + return GVFalse; + + return gviListTalkingSources(data->m_playbackSources, sources, maxSources); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// initializes the device +static GVBool gviPS3HeadsetInitDevice(GVIDevice * device, int deviceIndex, GVDeviceType type) +{ + GVIPS3HeadsetData * data; + + // get a pointer to the data + data = (GVIPS3HeadsetData *)device->m_data; + data->m_deviceNum = deviceIndex; + + // handle playback specific stuff + if(type & GV_PLAYBACK) + { + // create the array of sources + data->m_playbackSources = gviNewSourceList(); + if(!data->m_playbackSources) + { + return NULL; + } + + // allocate the buffer to hold one frame + data->m_playbackBuffer = (GVSample *)gsimemalign(16, (unsigned int)(GVIBytesPerFrame)); + if(!data->m_playbackBuffer) + { + gviFreeSourceList(data->m_playbackSources); + return NULL; + } + if (!gviPS3HeadsetInitHeadphone(data)) + { + gviFreeSourceList(data->m_playbackSources); + gsifree(data->m_playbackBuffer); + gviPS3HeadsetCloseHeadphone(data); + return NULL; + } + data->m_playbackVolume = 1.0; + } + + // handle capture specific stuff + if(type & GV_CAPTURE) + { + // set some data vars + data->m_captureClock = 0; + data->m_captureVolume = 1.0; + data->m_capturePreConvertBufferLen = 0; + + data->m_captureLastCrossedThresholdTime = (GVFrameStamp)(data->m_captureClock - GVI_HOLD_THRESHOLD_FRAMES - 1); + + // allocate the buffer + data->m_captureBuffer = (GVSample *)gsimemalign(16, (unsigned int)(GVIBytesPerFrame)); + if(!data->m_captureBuffer) + { + // Need to free any resources in data for playback + // Also library needs to close audio port and mic + // The library still needs to remain + if(type & GV_PLAYBACK) + { + gviFreeSourceList(data->m_playbackSources); + gsifree(data->m_playbackBuffer); + gviPS3HeadsetCloseHeadphone(data); + } + return NULL; + } + + data->m_capturePreConvertBuffer = (float *)gsimemalign(16,(unsigned int)(GVISamplesPerFrame * sizeof(float))); + if(!data->m_capturePreConvertBuffer) + { + // Need to free any resources in data for playback + // Also library needs to close audio port and mic + // The library still needs to remain + if(type & GV_PLAYBACK) + { + gviFreeSourceList(data->m_playbackSources); + gsifree(data->m_playbackBuffer); + gviPS3HeadsetCloseHeadphone(data); + } + + // Still need to free capture buffer + gsifree(data->m_captureBuffer); + return NULL; + } + + if (!gviPS3HeadsetInitMic(data)) + { + // Need to free any resources in data for playback + // Also library needs to close audio port and mic + // The library still needs to remain + if(type & GV_PLAYBACK) + { + gviFreeSourceList(data->m_playbackSources); + gsifree(data->m_playbackBuffer); + gviPS3HeadsetCloseHeadphone(data); + } + gviPS3HeadsetCloseMic(data); + gsifree(data->m_captureBuffer); + gsifree(data->m_capturePreConvertBuffer); + return NULL; + } + } + + return GVTrue; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// initializes a new device +GVDevice gviPS3HeadsetNewDevice(GVDeviceID deviceID, GVDeviceType type) +{ + GVIDevice * device; + GVBool result; + + // check for no type + if(!(type & GV_CAPTURE_AND_PLAYBACK)) + return NULL; + + // check if the device already exists + if(gviFindDeviceByID(deviceID)) + return NULL; + + // create a new device + device = gviNewDevice(deviceID, GVHardwarePS3Headset, type, sizeof(GVIPS3HeadsetData)); + if(!device) + return NULL; + + // init the device + result = gviPS3HeadsetInitDevice(device, deviceID, type); + if(result == GVFalse) + { + gviFreeDevice(device); + return NULL; + } + + // store the pointers + device->m_methods.m_freeDevice = gviPS3HeadsetFreeDevice; + device->m_methods.m_startDevice = gviPS3HeadsetStartDevice; + device->m_methods.m_stopDevice = gviPS3HeadsetStopDevice; + device->m_methods.m_isDeviceStarted = gviPS3HeadsetIsDeviceStarted; + device->m_methods.m_setDeviceVolume = gviPS3HeadsetSetDeviceVolume; + device->m_methods.m_getDeviceVolume = gviPS3HeadsetGetDeviceVolume; + device->m_methods.m_setCaptureThreshold = gviPS3HeadsetSetCaptureThreshold; + device->m_methods.m_getCaptureThreshold = gviPS3HeadsetGetCaptureThreshold; + device->m_methods.m_getAvailableCaptureBytes = gviPS3HeadsetGetAvailableCaptureBytes; + device->m_methods.m_capturePacket = gviPS3HeadsetCapturePacket; + device->m_methods.m_playPacket = gviPS3HeadsetPlayPacket; + device->m_methods.m_isSourceTalking = gviPS3HeadsetIsSourceTalking; + device->m_methods.m_listTalkingSources = gviPS3HeadsetListTalkingSources; + + // add it to the list + gviAppendDeviceToList(GVIDevices, device); + return device; +} + +#endif //!defined(GV_NO_PS3_HEADSET) diff --git a/code/gamespy/Voice2/gvPS3Headset.h b/code/gamespy/Voice2/gvPS3Headset.h new file mode 100644 index 00000000..459b147b --- /dev/null +++ b/code/gamespy/Voice2/gvPS3Headset.h @@ -0,0 +1,22 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef _GV_PS3_HEADSET_H_ +#define _GV_PS3_HEADSET_H_ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// headers +#include "gvMain.h" + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// public interfaces +GVBool gviPS3HeadsetStartup(void); +void gviPS3HeadsetCleanup(void); +void gviPS3HeadsetThink(void); + +int gviPS3HeadsetListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types); + +GVDevice gviPS3HeadsetNewDevice(GVDeviceID deviceID, GVDeviceType type); + +#endif // _GV_PS3_HEADSET_H_ diff --git a/code/gamespy/Voice2/gvPSPAudio.c b/code/gamespy/Voice2/gvPSPAudio.c new file mode 100644 index 00000000..5b87cd7e --- /dev/null +++ b/code/gamespy/Voice2/gvPSPAudio.c @@ -0,0 +1,503 @@ +#include "gvDevice.h" +#include "gvCodec.h" +#include "gvSource.h" +#include "gvUtil.h" +#include + +#if !defined(_PSP) +#error This file should only be used with the PSP +#endif + +/************ +** DEFINES ** +************/ +// the number of samples to capture at a time +// is a multiple of 64 and 160 +// at 11025hz, corresponds to about 30ms +#define GVI_INPUT_LEN 320 + +#define GVI_CAPTURE_THREAD_STACK_SIZE (1024 * 4) + +// ADC settings +#define GVI_ADC_ALC -6 // -6 dB +#define GVI_ADC_GAIN +30 // +30 dB +#define GVI_ADC_NOIZ -60 // -60 dB +#define GVI_ADC_HOLD 0 // 0 ms +#define GVI_ADC_DECAY 3 // 192 ms +#define GVI_ADC_ATTACK 2 // 24 ms + +/********** +** TYPES ** +**********/ +typedef struct +{ + GVBool m_capturing; + GVScalar m_captureVolume; + GVFrameStamp m_captureClock; + GVScalar m_captureThreshold; + GVFrameStamp m_captureLastCrossedThresholdTime; + GVSample * m_captureBuffer; + size_t m_captureBufferLen; // len in samples (not bytes) + size_t m_captureBufferWritePos; + size_t m_captureBufferReadPos; + SceUID m_captureBufferSemaphore; + SceUID m_captureThreadID; + GVBool m_captureThreadStop; +} GVIHardwareData; + +/************ +** GLOBALS ** +************/ +static GVIDevice * GVIPSPDevice = NULL; + +/************** +** FUNCTIONS ** +**************/ +static void gviHardwareFreeDevice(GVIDevice * device); + +static GVBool gviWaitSemaphore(GVIHardwareData * data) +{ + int rcode = sceKernelWaitSema(data->m_captureBufferSemaphore, 1, NULL); + if(rcode == 0) + return GVTrue; + return GVFalse; +} + +static GVBool gviSignalSemaphore(GVIHardwareData * data) +{ + int rcode = sceKernelSignalSema(data->m_captureBufferSemaphore, 1); + if(rcode == 0) + return GVTrue; + return GVFalse; +} + +GVBool gviHardwareStartup(void) +{ + SceAudioInputParam param; + int rcode; + + // init mic capture + param.alc = GVI_ADC_ALC; + param.gain = GVI_ADC_GAIN; + param.noiz = GVI_ADC_NOIZ; + param.hold = GVI_ADC_HOLD; + param.decay = GVI_ADC_DECAY; + param.attack = GVI_ADC_ATTACK; + rcode = sceAudioInputInitEx(¶m); + if(rcode < 0) + return GVFalse; + + return GVTrue; +} + +void gviHardwareCleanup(void) +{ + if(GVIPSPDevice) + { + gviHardwareFreeDevice(GVIPSPDevice); + GVIPSPDevice = NULL; + } +} + +void gviHardwareThink(void) +{ + // no thinking needed +} + +int gviHardwareListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types) +{ + if(maxDevices < 1) + return 0; + + if(!(types & GV_CAPTURE)) + return 0; + + memset(&devices[0], 0, sizeof(GVDeviceInfo)); + devices[0].m_id = 0; + strcpy(devices[0].m_name, _T("Headset Mic")); + devices[0].m_deviceType = GV_CAPTURE; + devices[0].m_defaultDevice = GV_CAPTURE; + devices[0].m_hardwareType = GVHardwarePSPHeadset; + + return 1; +} + +static void gviHardwareFreeDevice(GVIDevice * device) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + // don't track this device anymore + GVIPSPDevice = NULL; + + // tell the thread to stop + // the thread will free the device after it stops + data->m_captureThreadStop = GVTrue; +} + +static GVBool gviHardwareStartDevice(GVIDevice * device, GVDeviceType type) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + if(type != GV_CAPTURE) + return GVFalse; + + // already capturing? + if(data->m_capturing == GVTrue) + return GVTrue; + + // set vars + data->m_captureBufferWritePos = 0; + data->m_captureBufferReadPos = 0; + + // start capturing + data->m_capturing = GVTrue; + + return GVTrue; +} + +static void gviHardwareStopDevice(GVIDevice * device, GVDeviceType type) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + + if(type != GV_CAPTURE) + return; + + // stop capturing + data->m_capturing = GVFalse; + + // increment the clock so new audio isn't contiguous + data->m_captureClock++; +} + +static GVBool gviHardwareIsDeviceStarted(GVIDevice * device, GVDeviceType type) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + if(type != GV_CAPTURE) + return GVFalse; + return data->m_capturing; +} + +static void gviHardwareSetDeviceVolume(GVIDevice * device, GVDeviceType type, GVScalar volume) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + if(type != GV_CAPTURE) + return; + data->m_captureVolume = volume; +} + +static GVScalar gviHardwareGetDeviceVolume(GVIDevice * device, GVDeviceType type) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + if(type != GV_CAPTURE) + return (GVScalar)0; + return data->m_captureVolume; +} + +static void gviHardwareSetCaptureThreshold(GVIDevice * device, GVScalar threshold) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + data->m_captureThreshold = threshold; +} + +static GVScalar gviHardwareGetCaptureThreshold(GVIDevice * device) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + return data->m_captureThreshold; +} + +static int gviHardwareGetAvailableCaptureBytes(GVDevice device) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + int numSamples; + + if(!data->m_capturing) + return GVFalse; + + // figure out how many samples are available + gviWaitSemaphore(data); + if(data->m_captureBufferWritePos < data->m_captureBufferReadPos) + numSamples = (data->m_captureBufferLen - data->m_captureBufferReadPos); + else + numSamples = (data->m_captureBufferWritePos - data->m_captureBufferReadPos); + gviSignalSemaphore(data); + + return numSamples; +} + +static GVBool gviHardwareCapturePacket(GVDevice device, GVByte * packet, int * len, GVFrameStamp * frameStamp, GVScalar * volume) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + int numBytes; + int numSamples; + int numFrames; + GVBool overThreshold = GVFalse; + int lenAvailable; + int framesAvailable; + int i, j; + GVSample * readPtr; + + if(!data->m_capturing) + return GVFalse; + + // figure out how many encoded bytes they can handle + lenAvailable = *len; + + // clear the len and volume + *len = 0; + if(volume) + *volume = 0; + + // figure out how many bytes can be captured + numSamples = gviHardwareGetAvailableCaptureBytes(device); + numBytes = (numSamples * GV_BYTES_PER_SAMPLE); + + // figure out how many frames that is + numFrames = (numBytes / GVIBytesPerFrame); + + if(numFrames == 0) + return GVFalse; + + // figure out how many frames they can handle + framesAvailable = (lenAvailable / GVIEncodedFrameSize); + + // don't give them more frames than they can handle + numFrames = min(numFrames, framesAvailable); + if(!numFrames) + return GVFalse; + + // figure out how many bytes to capture + numBytes = (numFrames * GVIBytesPerFrame); + numSamples = (numBytes / GV_BYTES_PER_SAMPLE); + + // get the read pointer + readPtr = (data->m_captureBuffer + data->m_captureBufferReadPos); + + // get the volume if they're interested + if(volume) + *volume = gviGetSamplesVolume(readPtr, numSamples); + + // check against the threshold + if(volume) + { + // we already got the volume, so use that to check + overThreshold = (*volume >= data->m_captureThreshold); + } + else + { + // we didn't get a volume, so check the samples directly + overThreshold = gviIsOverThreshold(readPtr, numSamples, data->m_captureThreshold); + } + + // did the audio cross the threshold? + if(overThreshold) + { + // update the time at which we crossed + data->m_captureLastCrossedThresholdTime = data->m_captureClock; + } + else + { + // check if we are still within the hold time + overThreshold = ((GVFrameStamp)(data->m_captureClock - data->m_captureLastCrossedThresholdTime) < GVI_HOLD_THRESHOLD_FRAMES); + } + + if(overThreshold) + { + // store the framestamp + *frameStamp = data->m_captureClock; + + // handle the data one frame at a time + for(i = 0 ; i < numFrames ; i++) + { + // scale the data + if(data->m_captureVolume < 1.0) + { + for(j = 0 ; j < GVISamplesPerFrame ; j++) + readPtr[j] = (GVSample)(readPtr[j] * data->m_captureVolume); + } + + // filter + if(device->m_captureFilterCallback) + device->m_captureFilterCallback(device, readPtr, (GVFrameStamp)(data->m_captureClock + i)); + + // encode the buffer into the packet + gviEncode(packet + (GVIEncodedFrameSize * i), readPtr); + + // update the loop info as needed + readPtr += GVISamplesPerFrame; + } + } + + // advance the read position and clock + data->m_captureBufferReadPos += numSamples; + data->m_captureBufferReadPos %= data->m_captureBufferLen; + data->m_captureClock += numFrames; + + // set the len + *len = (numFrames * GVIEncodedFrameSize); + + // return false if we didn't get a packet + if(!overThreshold) + return GVFalse; + + return GVTrue; +} + +static int gviPSPCaptureThread(SceSize args, void * argp) +{ + GVIDevice * device = *(GVIDevice **)argp; + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + int rcode; + + assert(device); + assert(args == sizeof(GVIDevice*)); + + // loop until we're told to stop + while(!data->m_captureThreadStop) + { + // are we capturing? + if(data->m_capturing) + { + // get some input + rcode = sceAudioInputBlocking(GVI_INPUT_LEN, GV_SAMPLES_PER_SECOND, + data->m_captureBuffer + data->m_captureBufferWritePos); + if(rcode < 0) + { + gviDeviceUnplugged(device); + } + else + { + gviWaitSemaphore(data); + data->m_captureBufferWritePos += GVI_INPUT_LEN; + data->m_captureBufferWritePos %= data->m_captureBufferLen; + gviSignalSemaphore(data); + } + } + else + { + // sleep + msleep(10); + } + } + + // end sampling + // if another device has already been created, don't do this + if(GVIPSPDevice != NULL) + sceAudioInputBlocking(GVI_INPUT_LEN, GV_SAMPLES_PER_SECOND, NULL); + + // free the device and its memory + gsifree(data->m_captureBuffer); + sceKernelDeleteSema(data->m_captureBufferSemaphore); + gviFreeDevice(device); + + sceKernelExitThread(0); + + return 0; +} + +static GVBool gviPSPAudioInitDevice(GVIDevice *device) +{ + GVIHardwareData * data = (GVIHardwareData *)device->m_data; + size_t size; + size_t oldSize; + int rcode; + + // create a semaphore + data->m_captureBufferSemaphore = sceKernelCreateSema("capture buffer", SCE_KERNEL_SA_THFIFO, 1, 1, NULL); + if(data->m_captureBufferSemaphore <= 0) + return GVFalse; + + // figure out the buffer size + size = gviMultiplyByBytesPerMillisecond(GVI_CAPTURE_BUFFER_MILLISECONDS); + size /= GV_BYTES_PER_SAMPLE; // convert from bytes to samples + do + { + // it needs to be a multiple of both the frame size and the input len + oldSize = size; + size = gviRoundUpToNearestMultiple(size, GVISamplesPerFrame); + size = gviRoundUpToNearestMultiple(size, GVI_INPUT_LEN); + } + while(size != oldSize); + data->m_captureBufferLen = size; + + // allocate the buffer + data->m_captureBuffer = (GVSample *)gsimalloc(size * GV_BYTES_PER_SAMPLE); + if(!data->m_captureBuffer) + { + sceKernelDeleteSema(data->m_captureBufferSemaphore); + return GVFalse; + } + + // create capture thread + data->m_captureThreadID = sceKernelCreateThread("capture",gviPSPCaptureThread, + SCE_KERNEL_USER_HIGHEST_PRIORITY, GVI_CAPTURE_THREAD_STACK_SIZE, 0, NULL); + if(data->m_captureThreadID < 0) + { + gsifree(data->m_captureBuffer); + sceKernelDeleteSema(data->m_captureBufferSemaphore); + return GVFalse; + } + + // start capture thread + rcode = sceKernelStartThread(data->m_captureThreadID, sizeof(GVIDevice*), &device); + if(rcode < 0) + { + sceKernelDeleteThread(data->m_captureThreadID); + gsifree(data->m_captureBuffer); + sceKernelDeleteSema(data->m_captureBufferSemaphore); + return GVFalse; + } + + return GVTrue; +} + +GVDevice gviHardwareNewDevice(GVDeviceID deviceID, GVDeviceType type) +{ + GVIDevice * device; + GVIHardwareData * data; + GVBool result; + + // we only do capture + if(type != GV_CAPTURE) + return NULL; + + // there can only be one device at a time + if(GVIPSPDevice != NULL) + return NULL; + + // create the device + device = gviNewDevice(deviceID, GVHardwarePSPHeadset, type, sizeof(GVIHardwareData)); + if(!device) + return NULL; + + // init the device + result = gviPSPAudioInitDevice(device); + if(result == GVFalse) + { + gviFreeDevice(device); + return NULL; + } + + // store the pointers + device->m_methods.m_freeDevice = gviHardwareFreeDevice; + device->m_methods.m_startDevice = gviHardwareStartDevice; + device->m_methods.m_stopDevice = gviHardwareStopDevice; + device->m_methods.m_isDeviceStarted = gviHardwareIsDeviceStarted; + device->m_methods.m_setDeviceVolume = gviHardwareSetDeviceVolume; + device->m_methods.m_getDeviceVolume = gviHardwareGetDeviceVolume; + device->m_methods.m_setCaptureThreshold = gviHardwareSetCaptureThreshold; + device->m_methods.m_getCaptureThreshold = gviHardwareGetCaptureThreshold; + device->m_methods.m_getAvailableCaptureBytes = gviHardwareGetAvailableCaptureBytes; + device->m_methods.m_capturePacket = gviHardwareCapturePacket; + + // get a pointer to the data + data = (GVIHardwareData *)device->m_data; + + // init vars + data->m_captureVolume = (GVScalar)1.0; + data->m_captureClock = 0; + data->m_captureLastCrossedThresholdTime = (data->m_captureClock - GVI_HOLD_THRESHOLD_FRAMES - 1); + + GVIPSPDevice = device; + + return device; +} diff --git a/code/gamespy/Voice2/gvPSPAudio.h b/code/gamespy/Voice2/gvPSPAudio.h new file mode 100644 index 00000000..8b2e7897 --- /dev/null +++ b/code/gamespy/Voice2/gvPSPAudio.h @@ -0,0 +1,29 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +18002 Skypark Circle +Irvine, California 92614 +949.798.4200 (Tel) +949.798.4299 (Fax) +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_PSP_H_ +#define _GV_PSP_H_ + +#include "gvMain.h" + +GVBool gviHardwareStartup(void); +void gviHardwareCleanup(void); +void gviHardwareThink(void); + +int gviHardwareListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types); + +GVDevice gviHardwareNewDevice(GVDeviceID deviceID, GVDeviceType type); + +#endif diff --git a/code/gamespy/Voice2/gvSource.c b/code/gamespy/Voice2/gvSource.c new file mode 100644 index 00000000..4242abf8 --- /dev/null +++ b/code/gamespy/Voice2/gvSource.c @@ -0,0 +1,486 @@ +#include "gvSource.h" +#include "gvCodec.h" +#include "gvFrame.h" +#include "gvUtil.h" +#include "gvMain.h" +#include + +// some code based on: +// "Skew Detection and Compensation for Internet Audio Applications" +// http://csperkins.org/publications/icme2000.pdf + +#define GVI_SYNCHRONIZATION_DELAY 200 + +#define GVI_MAX_SOURCES 8 + +#define GVI_CLOCK_OFFSET_AVERAGING_FACTOR 0.96875 //(31/32) + +#define GVI_DIVERGENCE_HIGH_WATERMARK 10 +#define GVI_DIVERGENCE_LOW_WATERMARK ((GVFrameStamp)-5) + +#define GVI_SHOW_SKEW_CORRECTIONS 0 +#define GVI_SHOW_SOURCELIST_CHANGES 0 + +typedef struct GVISource +{ + GVBool m_inUse; + GVSource m_source; + GVBool m_isTalking; + GVFrameStamp m_finishedTalkingTime; + GVFrameStamp m_clockOffset; + float m_clockOffsetAverage; + GVDecoderData m_decoderData; + GVIPendingFrame m_frameHead; +} GVISource; + +static int GVISynchronizationDelayFrames; +GVBool GVIGlobalMute; + + +static void gviFreeSource(GVISource * source) +{ + GVIPendingFrame * frame = source->m_frameHead.m_next; + GVIPendingFrame * next; + + // make sure it is in use + if(!source->m_inUse) + return; + + // put all of the pending frames back into the list + while(frame) + { + next = frame->m_next; + gviPutPendingFrame(frame); + frame = next; + } + + // free the decoder data + gviFreeDecoder(source->m_decoderData); + +#if GVI_SHOW_SOURCELIST_CHANGES + printf("Freed source\n"); +#endif + + // it is no longer in use + source->m_inUse = GVFalse; +} + +GVISourceList gviNewSourceList(void) +{ + int size; + GVISourceList sourceList; + + if(!GVISynchronizationDelayFrames) + { + int delay; + + delay = gviMultiplyByBytesPerMillisecond(GVI_SYNCHRONIZATION_DELAY); + delay = gviRoundUpToNearestMultiple(delay, GVIBytesPerFrame); + + GVISynchronizationDelayFrames = (delay / GVIBytesPerFrame); + } + + size = (sizeof(GVISource) * GVI_MAX_SOURCES); + sourceList = (GVISourceList)gsimalloc((unsigned int)size); + if(sourceList) + memset(sourceList, 0, (unsigned int)size); + + return sourceList; +} + +void gviFreeSourceList(GVISourceList sourceList) +{ + gviClearSourceList(sourceList); + gsifree(sourceList); +} + +void gviClearSourceList(GVISourceList sourceList) +{ + int i; + + assert(sourceList); + + for(i = 0 ; i < GVI_MAX_SOURCES ; i++) + gviFreeSource(&sourceList[i]); +} + +static GVISource * gviFindSourceInList(GVISourceList sourceList, GVSource source) +{ + GVISource * gviSource; + int i; + + // check if this source is in the list + for(i = 0 ; i < GVI_MAX_SOURCES ; i++) + { + gviSource = &sourceList[i]; + if(gviSource->m_inUse) + { + if(memcmp(&gviSource->m_source, &source, sizeof(GVSource)) == 0) + { + return gviSource; + } + } + } + + return NULL; +} + +GVBool gviIsSourceTalking(GVISourceList sourceList, GVSource source) +{ + GVISource * gviSource = gviFindSourceInList(sourceList, source); + if(!gviSource) + return GVFalse; + + return gviSource->m_isTalking; +} + +int gviListTalkingSources(GVISourceList sourceList, GVSource sources[], int maxSources) +{ + GVISource * gviSource; + int numTalking = 0; + int i; + + // loop through the sources + for(i = 0 ; i < GVI_MAX_SOURCES ; i++) + { + gviSource = &sourceList[i]; + + // check if the source is in use and talking + if(gviSource->m_inUse && gviSource->m_isTalking) + { + // add it to the list + memcpy(&sources[numTalking], &gviSource->m_source, sizeof(GVSource)); + + // one more talker + numTalking++; + + // check for the max + if(numTalking == maxSources) + break; + } + } + + return numTalking; +} + +void gviSetGlobalMute(GVBool mute) + +{ + GVIGlobalMute = mute; +} + +GVBool gviGetGlobalMute(void) +{ + return GVIGlobalMute; +} + +static GVISource * gviAddSourceToList(GVISourceList sourceList, GVSource source) +{ + GVBool result; + GVISource * gviSource = NULL; + int i; + + // loop through the sources + for(i = 0 ; i < GVI_MAX_SOURCES ; i++) + { + // check if this source is available + if(!sourceList[i].m_inUse) + { + gviSource = &sourceList[i]; + break; + } + + // also look for a source without frames + // if we don't find a totally free one, we can take this one over + if(!gviSource && !sourceList[i].m_frameHead.m_next) + gviSource = &sourceList[i]; + } + + // check if we didn't find anything + if(!gviSource) + return NULL; + + // if this source is already in use, free it first + if(gviSource->m_inUse) + gviFreeSource(gviSource); + + // make sure we can get a new decoder before moving on + result = gviNewDecoder(&gviSource->m_decoderData); + if(!result) + return NULL; + + // mark as in use + gviSource->m_inUse = GVTrue; + + // set the rest of the info + memcpy(&gviSource->m_source, &source, sizeof(GVSource)); + gviSource->m_clockOffset = 0; + gviSource->m_clockOffsetAverage = 0; + gviSource->m_isTalking = GVFalse; + gviSource->m_finishedTalkingTime = 0; + gviSource->m_frameHead.m_next = NULL; + +#if GVI_SHOW_SOURCELIST_CHANGES + printf("Added source\n"); +#endif + + return gviSource; +} + +static void gviAddPacketToSource(GVISource * source, GVFrameStamp frameStamp, const GVByte * packet, int len, GVBool mute) +{ + GVIPendingFrame * frame; + GVIPendingFrame * nextFrame; + GVIPendingFrame * newFrame; + GVFrameStamp packetFinishedTime; + int numFrames; + int i; + + // the packet len should be a multiple of the encoded frame size + assert(!(len % GVIEncodedFrameSize)); + + // calculate the number of frames in this packet + numFrames = (len / GVIEncodedFrameSize); + + // use the clock offset to adjust the frameStamp + // Expanded to remove warnings in VS2K5 + frameStamp = frameStamp + (GVFrameStamp)source->m_clockOffset; + + // figure out when this packet will be finished + packetFinishedTime = (GVFrameStamp)(frameStamp + numFrames); + + // update the time at which this source is done talking + if(gviIsFrameStampGT(packetFinishedTime, source->m_finishedTalkingTime)) + source->m_finishedTalkingTime = packetFinishedTime; + + // if muted, don't add. + if(mute || GVIGlobalMute) + { + //Flag that they are currently talking because we skip this step in the hardware. + source->m_isTalking = GVTrue; + return; + } + + // find where to add it + // it must be in chronological order + for(frame = &source->m_frameHead ; frame->m_next ; frame = frame->m_next) + { + // store a pointer to the next frame + // this is the frame we will compare to this time through the loop + nextFrame = frame->m_next; + + // check if the framestamp is the same (a repeated packet) + if(nextFrame->m_frameStamp == frameStamp) + return; + + // check if the new frame should be placed in front of the next frame + if(gviIsFrameStampGT(nextFrame->m_frameStamp, frameStamp)) + { + // check that the packet's finish time doesn't cross over the next frame + assert(!gviIsFrameStampGT(packetFinishedTime, nextFrame->m_frameStamp)); + + // everything is good, break out so we can insert the new frames + break; + } + } + + // loop through the frames in the packet + for(i = 0 ; i < numFrames ; i++) + { + // get a new frame + newFrame = gviGetPendingFrame(); + if(!newFrame) + return; + + // fill it in + newFrame->m_frameStamp = frameStamp; +#if GVI_PRE_DECODE + gviDecodeSet(newFrame->m_frame, packet, source->m_decoderData); +#else + memcpy(newFrame->m_frame, packet, (unsigned int)GVIEncodedFrameSize); +#endif + newFrame->m_next = frame->m_next; + + // setup the previous frame's next pointer to point to this one + frame->m_next = newFrame; + + // adjust vars + frameStamp++; + packet += GVIEncodedFrameSize; + frame = newFrame; + } +} + +void gviAddPacketToSourceList(GVISourceList sourceList, + const GVByte * packet, int len, GVSource source, GVFrameStamp frameStamp, GVBool mute, + GVFrameStamp currentPlayClock) +{ + GVFrameStamp playTime; + GVISource * gviSource; + GVFrameStamp clockOffset; + GVFrameStamp divergence; + int unwrappedClockOffset; + + // add the sync delay to the clock to get the play time + playTime = (GVFrameStamp)(currentPlayClock + GVISynchronizationDelayFrames); + + // calculate the clock offset + clockOffset = (GVFrameStamp)(playTime - frameStamp); + + // check if this source already exists + gviSource = gviFindSourceInList(sourceList, source); + if(!gviSource) + { + // it isn't, so add it + gviSource = gviAddSourceToList(sourceList, source); + + // drop the packet if it couldn't be added + if(!gviSource) + return; + + // store the clock offset + gviSource->m_clockOffset = clockOffset; + gviSource->m_clockOffsetAverage = (float)clockOffset; + + // init the finished talking time + gviSource->m_finishedTalkingTime = (GVFrameStamp)(frameStamp + gviSource->m_clockOffset); + } + else + { + // unwrap the clock offset if needed + if((clockOffset < gviSource->m_clockOffsetAverage) && gviIsFrameStampGT(clockOffset, (GVFrameStamp)gviSource->m_clockOffsetAverage)) + { + unwrappedClockOffset = (clockOffset + GVI_FRAMESTAMP_MAX); + } + else if((gviSource->m_clockOffsetAverage < clockOffset) && gviIsFrameStampGT((GVFrameStamp)gviSource->m_clockOffsetAverage, clockOffset)) + { + unwrappedClockOffset = (clockOffset - GVI_FRAMESTAMP_MAX); + } + else + { + unwrappedClockOffset = clockOffset; + } + + // update the running average of the clock offset + gviSource->m_clockOffsetAverage = + (float)((gviSource->m_clockOffsetAverage * GVI_CLOCK_OFFSET_AVERAGING_FACTOR) + + (unwrappedClockOffset * (1.0 - GVI_CLOCK_OFFSET_AVERAGING_FACTOR))); + if(gviSource->m_clockOffsetAverage < 0) + gviSource->m_clockOffsetAverage += GVI_FRAMESTAMP_MAX; + else if(gviSource->m_clockOffsetAverage >= GVI_FRAMESTAMP_MAX) + gviSource->m_clockOffsetAverage -= GVI_FRAMESTAMP_MAX; + + // calculate the divergence + divergence = (GVFrameStamp)(gviSource->m_clockOffset - (GVFrameStamp)gviSource->m_clockOffsetAverage); + + // check against the high-water mark + if(gviIsFrameStampGT(divergence, GVI_DIVERGENCE_HIGH_WATERMARK)) + { +#if GVI_SHOW_SKEW_CORRECTIONS + static int dropCount; + printf("DROP: %d\n", ++dropCount); +#endif + + // update the clock offset + gviSource->m_clockOffset--; + + // if this is a one frame packet, just drop it + if(len == GVIEncodedFrameSize) + return; + + // otherwise update the params + packet += GVIEncodedFrameSize; + len -= GVIEncodedFrameSize; + } + // check against the low-water mark + else if(gviIsFrameStampGT(GVI_DIVERGENCE_LOW_WATERMARK, divergence)) + { +#if GVI_SHOW_SKEW_CORRECTIONS + static int insertCount; + printf("INSERT: %d\n", ++insertCount); +#endif + + // update the clock offset + // this will basically add a frame of silence + gviSource->m_clockOffset++; + } + + // check if this packet is too old or too far ahead to play + if(gviIsFrameStampGT(currentPlayClock, (GVFrameStamp)(frameStamp + gviSource->m_clockOffset))) + return; + } + + // add the packet to the source + gviAddPacketToSource(gviSource, frameStamp, packet, len, mute); +} + +GVBool gviWriteSourcesToBuffer(GVISourceList sourceList, GVFrameStamp startTime, + GVSample * sampleBuffer, int numFrames) +{ + GVISource * source; + GVFrameStamp timeSliceEnd; + GVIPendingFrame * frame; + int i; + GVBool result = GVFalse; + + // calculate the end of the time slice + timeSliceEnd = (GVFrameStamp)(startTime + numFrames); + + // clear the sample buffer + memset(sampleBuffer, 0, (unsigned int)numFrames * GVIBytesPerFrame); + + // loop through the sources + for(i = 0 ; i < GVI_MAX_SOURCES ; i++) + { + // get the next source + source = &sourceList[i]; + + // check if it is in use + if(!source->m_inUse) + continue; + + // keep going while there are pending frames for this source + while(source->m_frameHead.m_next) + { + // cache a pointer to the first frame + frame = source->m_frameHead.m_next; + + // check if this frame is too far ahead + if(gviIsFrameStampGTE(frame->m_frameStamp, timeSliceEnd)) + break; + + // make sure this buffer's timeslice hasn't already elapsed + if(gviIsFrameStampGTE(frame->m_frameStamp, startTime)) + { + // add the frame to the buffer + GVSample * writePtr = (sampleBuffer + ((GVFrameStamp)(frame->m_frameStamp - startTime) * GVISamplesPerFrame)); +#if GVI_PRE_DECODE + int j; + for(j = 0 ; j < GVISamplesPerFrame ; j++) + writePtr[j] += frame->m_frame[j]; +#else + gviDecodeAdd(writePtr, frame->m_frame, source->m_decoderData); +#endif + + // this source is talking + source->m_isTalking = GVTrue; + + // set the return value to indicate that decoding took place + result = GVTrue; + } + + // update the source to point to the next frame + source->m_frameHead.m_next = frame->m_next; + + // free the frame we just used + gviPutPendingFrame(frame); + } + + // remove the source if it has no more frames and is marked as finished + if(!source->m_frameHead.m_next && gviIsFrameStampGTE(timeSliceEnd, source->m_finishedTalkingTime)) + gviFreeSource(source); + } + + return result; + +} diff --git a/code/gamespy/Voice2/gvSource.h b/code/gamespy/Voice2/gvSource.h new file mode 100644 index 00000000..0184f189 --- /dev/null +++ b/code/gamespy/Voice2/gvSource.h @@ -0,0 +1,40 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_SOURCE_H_ +#define _GV_SOURCE_H_ + +#include "gvMain.h" +/************ +** GLOBALS ** +************/ +extern GVBool GVIGlobalMute; + +typedef struct GVISource * GVISourceList; + +GVISourceList gviNewSourceList(void); +void gviFreeSourceList(GVISourceList sourceList); +void gviClearSourceList(GVISourceList sourceList); + +GVBool gviIsSourceTalking(GVISourceList sourceList, GVSource source); +int gviListTalkingSources(GVISourceList sourceList, GVSource sources[], int maxSources); + +void gviSetGlobalMute(GVBool mute); +GVBool gviGetGlobalMute(void); + +void gviAddPacketToSourceList(GVISourceList sourceList, + const GVByte * packet, int len, GVSource source, GVFrameStamp frameStamp, GVBool mute, + GVFrameStamp currentPlayClock); + +GVBool gviWriteSourcesToBuffer(GVISourceList sourceList, GVFrameStamp startTime, + GVSample * sampleBuffer, int numFrames); + +#endif diff --git a/code/gamespy/Voice2/gvSpeex.c b/code/gamespy/Voice2/gvSpeex.c new file mode 100644 index 00000000..7c7b322c --- /dev/null +++ b/code/gamespy/Voice2/gvSpeex.c @@ -0,0 +1,187 @@ +#include "gvSpeex.h" +#include +#include "gvCodec.h" + + +static GVBool gviSpeexInitialized; +static void * gviSpeexEncoderState; +static SpeexBits gviSpeexBits; +static int gviSpeexEncodedFrameSize; +static int gviSpeexSamplesPerFrame; + +//Encode/Decode buffer. +static float * gviSpeexBuffer; + +GVBool gviSpeexInitialize(int quality, GVRate sampleRate) +{ + int rate; + int bitsPerFrame; + int samplesPerSecond; + + // we shouldn't already be initialized + if(gviSpeexInitialized) + return GVFalse; + + // create a new encoder state + if (sampleRate == GVRate_8KHz) + gviSpeexEncoderState = speex_encoder_init(&speex_nb_mode); + else if (sampleRate == GVRate_16KHz) + gviSpeexEncoderState = speex_encoder_init(&speex_wb_mode); + else + return GVFalse; + + if(!gviSpeexEncoderState) + return GVFalse; + + // set the sampling rate + samplesPerSecond = sampleRate; + speex_encoder_ctl(gviSpeexEncoderState, SPEEX_SET_SAMPLING_RATE, &samplesPerSecond); + + // Get the samples per frame setting. + speex_encoder_ctl(gviSpeexEncoderState, SPEEX_GET_FRAME_SIZE, &gviSpeexSamplesPerFrame); + + // set the quality + speex_encoder_ctl(gviSpeexEncoderState, SPEEX_SET_QUALITY, &quality); + + // initialize the bits struct + speex_bits_init(&gviSpeexBits); + + // get the bitrate + speex_encoder_ctl(gviSpeexEncoderState, SPEEX_GET_BITRATE, &rate); + + // convert to bits per frame + bitsPerFrame = (rate / (sampleRate / gviSpeexSamplesPerFrame)); + + // convert to bytes per frame and store, round up to allocate more space than needed. + gviSpeexEncodedFrameSize = (bitsPerFrame / 8); + if (bitsPerFrame % 8) + gviSpeexEncodedFrameSize++; + + // create our encoding and decoding buffer. + gviSpeexBuffer = (float *)gsimalloc(gviSpeexSamplesPerFrame * sizeof(float)); + + // we're now initialized + gviSpeexInitialized = GVTrue; + + return GVTrue; +} + +void gviSpeexCleanup(void) +{ + // make sure there is something to cleanup + if(!gviSpeexInitialized) + return; + + // free up encoding and decoding buffer. + gsifree(gviSpeexBuffer); + + // destroy the encoder state + speex_encoder_destroy(gviSpeexEncoderState); + gviSpeexEncoderState = NULL; + + // destroy the bits struct + speex_bits_destroy(&gviSpeexBits); + + // no longer initialized + gviSpeexInitialized = GVFalse; +} + +int gviSpeexGetSamplesPerFrame(void) +{ + return gviSpeexSamplesPerFrame; +} + +int gviSpeexGetEncodedFrameSize(void) +{ + return gviSpeexEncodedFrameSize; +} + +GVBool gviSpeexNewDecoder(GVDecoderData * data) +{ + void * decoder; + int perceptualEnhancement = 1; + + // create a new decoder state + if (gviGetSampleRate() == GVRate_8KHz) + decoder = speex_decoder_init(&speex_nb_mode); + else if (gviGetSampleRate() == GVRate_16KHz) + decoder = speex_decoder_init(&speex_wb_mode); + else + return GVFalse; + + if(!decoder) + return GVFalse; + + // turn on the perceptual enhancement + speex_decoder_ctl(decoder, SPEEX_SET_ENH, &perceptualEnhancement); + + *data = decoder; + return GVTrue; +} + +void gviSpeexFreeDecoder(GVDecoderData data) +{ + // destory the decoder state + speex_decoder_destroy((void *)data); +} + +void gviSpeexEncode(GVByte * out, const GVSample * in) +{ + int bytesWritten; + int i; + + // convert the input to floats for encoding + for(i = 0 ; i < gviSpeexSamplesPerFrame ; i++) + gviSpeexBuffer[i] = in[i]; + + // flush the bits + speex_bits_reset(&gviSpeexBits); + + // encode the frame + speex_encode(gviSpeexEncoderState, gviSpeexBuffer, &gviSpeexBits); + + // write the bits to the output + bytesWritten = speex_bits_write(&gviSpeexBits, (char *)out, gviSpeexEncodedFrameSize); + assert(bytesWritten == gviSpeexEncodedFrameSize); +} + +void gviSpeexDecodeAdd(GVSample * out, const GVByte * in, GVDecoderData data) +{ + int rcode; + int i; + + // read the data into the bits + speex_bits_read_from(&gviSpeexBits, (char *)in, gviSpeexEncodedFrameSize); + + // decode it + rcode = speex_decode((void *)data, &gviSpeexBits, gviSpeexBuffer); + assert(rcode == 0); + + // convert the output from floats + for(i = 0 ; i < gviSpeexSamplesPerFrame ; i++) + // Expanded to remove warnings in VS2K5 + out[i] = out[i] + (GVSample)gviSpeexBuffer[i]; +} + +void gviSpeexDecodeSet(GVSample * out, const GVByte * in, GVDecoderData data) +{ + int rcode; + int i; + + // read the data into the bits + speex_bits_read_from(&gviSpeexBits, (char *)in, gviSpeexEncodedFrameSize); + + // decode it + rcode = speex_decode((void *)data, &gviSpeexBits, gviSpeexBuffer); + assert(rcode == 0); + + // convert the output from floats + for(i = 0 ; i < gviSpeexSamplesPerFrame ; i++) + out[i] = (GVSample)gviSpeexBuffer[i]; +} + +void gviSpeexResetEncoder(void) +{ + // reset the encoder's state + speex_encoder_ctl(gviSpeexEncoderState, SPEEX_RESET_STATE, NULL); +} diff --git a/code/gamespy/Voice2/gvSpeex.h b/code/gamespy/Voice2/gvSpeex.h new file mode 100644 index 00000000..b2db0524 --- /dev/null +++ b/code/gamespy/Voice2/gvSpeex.h @@ -0,0 +1,62 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_SPEEX_H_ +#define _GV_SPEEX_H_ + +#include "gvMain.h" + +/* + 8000kHz +quality: samplesPerFrame encodedFrameSize bitsPerSecond +0: 160 6 2150 +1: 160 10 3950 +2: 160 15 5950 +3: 160 20 8000 +4: 160 20 8000 +5: 160 28 11000 +6: 160 28 11000 +7: 160 38 15000 +8: 160 38 15000 +9: 160 46 18200 +10: 160 62 24600 + + 16000kHz +quality: samplesPerFrame encodedFrameSize bitsPerSecond +0: 320 10 3950 +1: 320 15 5750 +2: 320 20 7750 +3: 320 25 9800 +4: 320 32 12800 +5: 320 42 16800 +6: 320 52 20600 +7: 320 60 23800 +8: 320 70 27800 +9: 320 86 34200 +10: 320 106 42200 +*/ + +GVBool gviSpeexInitialize(int quality, GVRate sampleRate); +void gviSpeexCleanup(void); + +int gviSpeexGetSamplesPerFrame(void); +int gviSpeexGetEncodedFrameSize(void); + +GVBool gviSpeexNewDecoder(GVDecoderData * data); +void gviSpeexFreeDecoder(GVDecoderData data); + +void gviSpeexEncode(GVByte * out, const GVSample * in); +void gviSpeexDecodeAdd(GVSample * out, const GVByte * in, GVDecoderData data); +void gviSpeexDecodeSet(GVSample * out, const GVByte * in, GVDecoderData data); + +void gviSpeexResetEncoder(void); + +#endif diff --git a/code/gamespy/Voice2/gvSpeexSpu.c b/code/gamespy/Voice2/gvSpeexSpu.c new file mode 100644 index 00000000..a0ae2f45 --- /dev/null +++ b/code/gamespy/Voice2/gvSpeexSpu.c @@ -0,0 +1,226 @@ +#include "gvSpeexSpu.h" +#include +#include "gvCodec.h" + +#define GVI_SPEEX_ENCODED_BUFFER 128 //dma has trouble when smaller than +struct SpursSpeexTaskOutput gSpeexTaskOutput; +char *gviSpeexEncoderStateBuffer; +static GVBool gviSpeexEncoderInitialized; +static int gviSpeexEncodedFrameSize; +static int gviSpeexSamplesPerFrame; +char *gviSpeexEncodedBuffer; +short *gviSpeexDecodedBuffer; +// used for decoding if SPU decoding isn't used +#if defined(GVI_NOT_USING_SPURS_DECODE_TASK) +static float * gviSpeexBuffer; +static SpeexBits gviSpeexBits; +#endif + + +GVBool gviSpeexInitialize(int quality, GVRate sampleRate) +{ + // we shouldn't already be initialized + if(gviSpeexEncoderInitialized) + return GVFalse; + + // align on a 128 byte boundary to make DMA in spurs task easier + gviSpeexEncoderStateBuffer = (char *)gsimemalign(128,SPEEX_ENCODER_STATE_BUFFER_SIZE); + gSpeexTaskOutput.mSpeexReturnCode = -1; + + // initialize the bits struct +#if defined(GVI_NOT_USING_SPURS_DECODE_TASK) + speex_bits_init(&gviSpeexBits); +#endif + + if (initializeSpursSampleTask() != 0) + return GVFalse; + + // initialize the encoder given the the buffer used to keep track of state + if (issueSampleTaskEncodeInit(quality, sampleRate, &gSpeexTaskOutput,gviSpeexEncoderStateBuffer,SPEEX_ENCODER_STATE_BUFFER_SIZE) != 0) + return GVFalse; + + assert(gSpeexTaskOutput.mSpeexReturnCode == 0); + if (gSpeexTaskOutput.mSpeexInitialized != GVTrue) + { + return GVFalse; + } + + gviSpeexSamplesPerFrame = gSpeexTaskOutput.mSpeexSamplesPerFrame; + gviSpeexEncodedFrameSize = gSpeexTaskOutput.mSpeexEncodedFrameSize; + +#if defined(GVI_NOT_USING_SPURS_DECODE_TASK) + gviSpeexBuffer = (float *)gsimalloc(gviSpeexSamplesPerFrame * sizeof(float)); +#endif + gviSpeexEncodedBuffer = (char *)gsimemalign(128, GVI_SPEEX_ENCODED_BUFFER); + gviSpeexDecodedBuffer = (short *)gsimemalign(128, gviSpeexSamplesPerFrame*sizeof(short)); + // we're now initialized + gviSpeexEncoderInitialized = GVTrue; + + return GVTrue; +} + +void gviSpeexCleanup(void) +{ + // make sure there is something to cleanup + if(!gviSpeexEncoderInitialized) + return; + +#ifdef GVI_NOT_USING_SPURS_DECODE_TASK + // free up encoding and decoding buffer. + gsifree(gviSpeexBuffer); + + // destroy the bits struct + speex_bits_destroy(&gviSpeexBits); +#endif + + // destroy speex state buffer + gsifree(gviSpeexEncoderStateBuffer); + gsifree(gviSpeexEncodedBuffer); + gsifree(gviSpeexDecodedBuffer); + // cleanup spu + shutdownSpursTask(); + + // no longer initialized + gviSpeexEncoderInitialized = GVFalse; +} + +int gviSpeexGetSamplesPerFrame(void) +{ + return gviSpeexSamplesPerFrame; +} + +int gviSpeexGetEncodedFrameSize(void) +{ + return gviSpeexEncodedFrameSize; +} + +GVBool gviSpeexNewDecoder(GVDecoderData * data) +{ +#ifdef GVI_NOT_USING_SPURS_DECODE_TASK + void * decoder; + int perceptualEnhancement = 1; + + // create a new decoder state + if (gviGetSampleRate() == GVRate_8KHz) + decoder = speex_decoder_init(&speex_nb_mode); + else if (gviGetSampleRate() == GVRate_16KHz) + decoder = speex_decoder_init(&speex_wb_mode); + else + return GVFalse; + + if(!decoder) + return GVFalse; + + // turn on the perceptual enhancement + speex_decoder_ctl(decoder, SPEEX_SET_ENH, &perceptualEnhancement); + + *data = decoder; + + return GVTrue; + +#else + char *decoder = (char *)gsimemalign(128, SPEEX_DECODER_STATE_BUFFER_SIZE); + gSpeexTaskOutput.mSpeexReturnCode = -1; + + if (issueSampleTaskDecodeInit(decoder, SPEEX_DECODER_STATE_BUFFER_SIZE, gviGetSampleRate(), &gSpeexTaskOutput) != 0) + return GVFalse; + + if (gSpeexTaskOutput.mSpeexReturnCode != 0) + return GVFalse; + + *data = decoder; + return GVTrue; +#endif // USE SPU ENCODING +} + +void gviSpeexFreeDecoder(GVDecoderData data) +{ +#ifdef GVI_NOT_USING_SPURS_DECODE_TASK + // destroy the decoder state + speex_decoder_destroy((void *)data); +#else + gsifree(data); +#endif +} + + +void gviSpeexEncode(GVByte * out, const GVSample * in) +{ + int immediateReturn = 0; + gSpeexTaskOutput.mSpeexInitialized = 1; + gSpeexTaskOutput.mSpeexReturnCode = -1; + memset(gviSpeexEncodedBuffer, 0, GVI_SPEEX_ENCODED_BUFFER); + immediateReturn = issueSampleTaskEncode((short *)in, gviSpeexSamplesPerFrame, gviSpeexEncodedFrameSize, (char *)gviSpeexEncodedBuffer, + GVI_SPEEX_ENCODED_BUFFER, &gSpeexTaskOutput,gviSpeexEncoderStateBuffer,SPEEX_ENCODER_STATE_BUFFER_SIZE); + assert(immediateReturn == 0); + assert(gSpeexTaskOutput.mSpeexReturnCode == 0); + memcpy(out, gviSpeexEncodedBuffer, gviSpeexEncodedFrameSize); + assert(gSpeexTaskOutput.mSpeexOutBufferSize == gviSpeexEncodedFrameSize); +} + +void gviSpeexDecodeAdd(GVSample * out, const GVByte * in, GVDecoderData data) +{ +#ifdef GVI_NOT_USING_SPURS_DECODE_TASK + int rcode; + int i; + + // read the data into the bits + speex_bits_read_from(&gviSpeexBits, (char *)in, gviSpeexEncodedFrameSize); + + // decode it + rcode = speex_decode((void *)data, &gviSpeexBits, gviSpeexBuffer); + assert(rcode == 0); + + // convert the output from floats + for(i = 0 ; i < gviSpeexSamplesPerFrame ; i++) + // Expanded to remove warnings in VS2K5 + out[i] = out[i] + (GVSample)gviSpeexBuffer[i]; +#else + int immediateReturn = 0, i; + gSpeexTaskOutput.mSpeexInitialized = 1; + gSpeexTaskOutput.mSpeexReturnCode = -1; + memset(gviSpeexEncodedBuffer, 0, GVI_SPEEX_ENCODED_BUFFER); + memcpy(gviSpeexEncodedBuffer, in, gviSpeexEncodedFrameSize); + immediateReturn = issueSampleTaskDecodeAdd(data, SPEEX_DECODER_STATE_BUFFER_SIZE, gviSpeexEncodedBuffer, GVI_SPEEX_ENCODED_BUFFER, + gviSpeexEncodedFrameSize, gviSpeexDecodedBuffer, gviSpeexSamplesPerFrame, &gSpeexTaskOutput); + for (i = 0; i < gviSpeexSamplesPerFrame; i++) + out[i] = out[i] + (GVSample)gviSpeexDecodedBuffer[i]; + assert(immediateReturn == 0); + assert(gSpeexTaskOutput.mSpeexReturnCode == 0); +#endif +} + +void gviSpeexDecodeSet(GVSample * out, const GVByte * in, GVDecoderData data) +{ +#ifdef GVI_USE_SPURS_DECODE_TASK + int rcode; + int i; + + // read the data into the bits + speex_bits_read_from(&gviSpeexBits, (char *)in, gviSpeexEncodedFrameSize); + + // decode it + rcode = speex_decode((void *)data, &gviSpeexBits, gviSpeexBuffer); + + assert(rcode == 0); + + // convert the output from floats + for(i = 0 ; i < gviSpeexSamplesPerFrame ; i++) + out[i] = (GVSample)gviSpeexBuffer[i]; +#else + int immediateReturn = 0; + gSpeexTaskOutput.mSpeexInitialized = 1; + gSpeexTaskOutput.mSpeexReturnCode = -1; + memset(gviSpeexEncodedBuffer, 0, GVI_SPEEX_ENCODED_BUFFER); + memcpy(gviSpeexEncodedBuffer, in, gviSpeexEncodedFrameSize); + immediateReturn = issueSampleTaskDecodeSet(data, SPEEX_DECODER_STATE_BUFFER_SIZE, gviSpeexEncodedBuffer, GVI_SPEEX_ENCODED_BUFFER, + gviSpeexEncodedFrameSize, out, gviSpeexSamplesPerFrame, &gSpeexTaskOutput); + assert(immediateReturn == 0); + assert(gSpeexTaskOutput.mSpeexReturnCode == 0); +#endif +} + +void gviSpeexResetEncoder(void) +{ + speex_encoder_ctl((void *)gviSpeexEncoderStateBuffer, SPEEX_RESET_STATE, NULL); +} diff --git a/code/gamespy/Voice2/gvSpeexSpu.h b/code/gamespy/Voice2/gvSpeexSpu.h new file mode 100644 index 00000000..add14dad --- /dev/null +++ b/code/gamespy/Voice2/gvSpeexSpu.h @@ -0,0 +1,65 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_SPEEXSPU_H_ +#define _GV_SPEEXSPU_H_ + +#include "../common/gsPlatform.h" +#include "gvMain.h" +#include "SpuSpeexTaskOutput.h" +#include "SpursSpeexCInterface.h" + +/* +8000kHz +quality: samplesPerFrame encodedFrameSize bitsPerSecond +0: 160 6 2150 +1: 160 10 3950 +2: 160 15 5950 +3: 160 20 8000 +4: 160 20 8000 +5: 160 28 11000 +6: 160 28 11000 +7: 160 38 15000 +8: 160 38 15000 +9: 160 46 18200 +10: 160 62 24600 + +16000kHz +quality: samplesPerFrame encodedFrameSize bitsPerSecond +0: 320 10 3950 +1: 320 15 5750 +2: 320 20 7750 +3: 320 25 9800 +4: 320 32 12800 +5: 320 42 16800 +6: 320 52 20600 +7: 320 60 23800 +8: 320 70 27800 +9: 320 86 34200 +10: 320 106 42200 +*/ + +GVBool gviSpeexInitialize(int quality, GVRate sampleRate); +void gviSpeexCleanup(void); + +int gviSpeexGetSamplesPerFrame(void); +int gviSpeexGetEncodedFrameSize(void); + +GVBool gviSpeexNewDecoder(GVDecoderData * data); +void gviSpeexFreeDecoder(GVDecoderData data); + +void gviSpeexEncode(GVByte * out, const GVSample * in); +void gviSpeexDecodeAdd(GVSample * out, const GVByte * in, GVDecoderData data); +void gviSpeexDecodeSet(GVSample * out, const GVByte * in, GVDecoderData data); + +void gviSpeexResetEncoder(void); + +#endif diff --git a/code/gamespy/Voice2/gvUtil.c b/code/gamespy/Voice2/gvUtil.c new file mode 100644 index 00000000..d99ba152 --- /dev/null +++ b/code/gamespy/Voice2/gvUtil.c @@ -0,0 +1,78 @@ +#include "gvUtil.h" +#include +#include + +GVScalar gviGetSamplesVolume(const GVSample * samplesPtr, int numSamples) +{ + GVSample value; + GVSample max = 0; + int i; + + for(i = 0 ; i < numSamples ; i++) + { + value = samplesPtr[i]; + if(value < 0) + value = (GVSample)(-value); + if(value > max) + max = value; + } + + return ((GVScalar)max / -SHRT_MIN); +} + +GVBool gviIsOverThreshold(const GVSample * samplesPtr, int numSamples, GVScalar threshold) +{ + int i; + + if(threshold == (GVScalar)0.0) + return GVTrue; + + for(i = 0 ; i < numSamples ; i++) + { + if(labs(samplesPtr[i]) > (threshold * SHRT_MAX)) + return GVTrue; + } + + return GVFalse; +} + +int gviRoundUpToNearestMultiple(int value, int base) +{ + int remainder; + + remainder = (value % base); + if(remainder) + value += (base - remainder); + + return value; +} + +int gviRoundDownToNearestMultiple(int value, int base) +{ + value -= (value % base); + + return value; +} + +int gviRoundToNearestMultiple(int value, int base) +{ + int remainder; + + remainder = (value % base); + if(remainder < (base / 2)) + value -= remainder; + else + value += (base - remainder); + + return value; +} + +int gviMultiplyByBytesPerMillisecond(int value) +{ + return (int)(value * GVISampleRate * GV_BYTES_PER_SAMPLE / 1000); +} + +int gviDivideByBytesPerMillisecond(int value) +{ + return (int)(value * 1000 / (GVISampleRate * GV_BYTES_PER_SAMPLE)); +} diff --git a/code/gamespy/Voice2/gvUtil.h b/code/gamespy/Voice2/gvUtil.h new file mode 100644 index 00000000..e67253d5 --- /dev/null +++ b/code/gamespy/Voice2/gvUtil.h @@ -0,0 +1,35 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +devsupport@gamespy.com +http://gamespy.net +*/ + +#ifndef _GV_UTIL_H_ +#define _GV_UTIL_H_ + +#include "gvMain.h" +#include "gvCodec.h" + +// gets the volume for a set of samples +GVScalar gviGetSamplesVolume(const GVSample * samplesPtr, int numSamples); + +// checks if any samples in the set are above the given threshold +GVBool gviIsOverThreshold(const GVSample * samplesPtr, int numSamples, GVScalar threshold); + +// returns the lowest multiple of base that is >= value +int gviRoundUpToNearestMultiple(int value, int base); +// returns the highest multiple of base that is <= value +int gviRoundDownToNearestMultiple(int value, int base); +// rounds the multiple of base that is closest to value +int gviRoundToNearestMultiple(int value, int base); + +// multiply or divide by bytes per millisecond +int gviMultiplyByBytesPerMillisecond(int value); +int gviDivideByBytesPerMillisecond(int value); + +#endif diff --git a/code/gamespy/Voice2/voice2bench/voice2bench.c b/code/gamespy/Voice2/voice2bench/voice2bench.c new file mode 100644 index 00000000..61511310 --- /dev/null +++ b/code/gamespy/Voice2/voice2bench/voice2bench.c @@ -0,0 +1,261 @@ +#include "../gv.h" +#include "voicesample.h" +#include + +GVByte voice_sample[80000]; + +#if defined(_PS2) + +#include "../gvLogitechPS2Codecs.h" + +static GVBool Init(GVCodec codec) +{ + const char * name; + + // figure out the quality + // the mapping of codec to quality is copied from gvCodec.c + if(codec == GVCodecSuperHighQuality) + name = "uLaw"; + else if(codec == GVCodecHighQuality) + name = "G723.24"; + else if(codec == GVCodecAverage) + name = "GSM"; + else if(codec == GVCodecLowBandwidth) + name = "SPEEX"; + else if(codec == GVCodecSuperLowBandwidth) + name = "LPC10"; + else + return GVFalse; + + return gviLGCodecInitialize(name); +} + +static void Cleanup(void) +{ + gviLGCodecCleanup(); +} + +static int GetSamplesPerFrame(void) +{ + return gviLGCodecGetSamplesPerFrame(); +} + +static GVBool NewDecoder(GVDecoderData * data) +{ + data = NULL; + return GVTrue; +} + +static void FreeDecoder(GVDecoderData data) +{ +} + +static void Encode(GVByte * out, const GVSample * in) +{ + gviLGCodecEncode(out, in); +} + +static void Decode(GVSample * out, const GVByte * in, GVDecoderData data) +{ + gviLGCodecDecodeAdd(out, in, data); +} + +#elif defined(_PSP) || defined(_WIN32) + +#include "../gsm-1.0-pl12/inc/gsm.h" + +gsm EncoderState; + +static GVBool Init(GVCodec codec) +{ + int gsm_ltp = 1; + EncoderState = gsm_create(); + gsm_option(EncoderState, GSM_OPT_LTP_CUT, &gsm_ltp); + + GSI_UNUSED(codec); + return (EncoderState != NULL)?GVTrue:GVFalse; +} + +static void Cleanup(void) +{ + gsm_destroy(EncoderState); +} + +static int GetSamplesPerFrame(void) +{ + return 160; +} + +static GVBool NewDecoder(GVDecoderData * data) +{ + *data = (GVDecoderData)gsm_create(); + return (*data != NULL)?GVTrue:GVFalse; +} + +static void FreeDecoder(GVDecoderData data) +{ + gsm_destroy((gsm)data); +} + +static void Encode(GVByte * out, const GVSample * in) +{ + gsm_encode((gsm)EncoderState, (gsm_signal*)in, (gsm_byte*)out); +} + +static void Decode(GVSample * out, const GVByte * in, GVDecoderData data) +{ + gsm_decode((gsm)data, (gsm_byte*)in, (gsm_signal*)out); +} + +#else + +#include "../gvSpeex.h" + +static GVBool Init(GVCodec codec, GVRate sampleRate) +{ + int quality; + + // figure out the quality + // the mapping of codec to quality is copied from gvCodec.c + if(codec == GVCodecSuperHighQuality) + quality = 10; + else if(codec == GVCodecHighQuality) + quality = 7; + else if(codec == GVCodecAverage) + quality = 4; + else if(codec == GVCodecLowBandwidth) + quality = 2; + else if(codec == GVCodecSuperLowBandwidth) + quality = 1; + else + return GVFalse; + + gvSetSampleRate(sampleRate); + return gviSpeexInitialize(quality, sampleRate); +} + +static void Cleanup(void) +{ + gviSpeexCleanup(); +} + +static int GetSamplesPerFrame(void) +{ + return gviSpeexGetSamplesPerFrame(); +} + +static GVBool NewDecoder(GVDecoderData * data) +{ + return gviSpeexNewDecoder(data); +} + +static void FreeDecoder(GVDecoderData data) +{ + gviSpeexFreeDecoder(data); +} + +static void Encode(GVByte * out, const GVSample * in) +{ + gviSpeexEncode(out, in); +} + +static void Decode(GVSample * out, const GVByte * in, GVDecoderData data) +{ + gviSpeexDecodeAdd(out, in, data); +} + +#endif + +static void TestCodec(GVCodec codec, GVRate sampleRate, const char * name) +{ + int samplesPerFrame; + GVSample * samples; + int numFrames; + GVByte encodeOut[1000]; + GVSample decodeOut[1000]; + int i; + unsigned long startTime; + unsigned long endTime; + GVDecoderData decoderData; + unsigned long sampleTime = 0; + unsigned long totalEncodeTime = 0; + unsigned long totalDecodeTime = 0; + double encodeFraction; + double decodeFraction; + + printf("Testing %s\n", name); + + + if(!Init(codec, sampleRate)) + { + printf("Failed to init\n"); + return; + } + + sampleTime = (unsigned long)((double)sizeof(voice_sample) / ((int)(sampleRate) * GV_BYTES_PER_SAMPLE) * 1000000); + + if(!NewDecoder(&decoderData)) + { + printf("Failed to allocate decoder data\n"); + return; + } + + samplesPerFrame = GetSamplesPerFrame(); + numFrames = (sizeof(voice_sample) / GV_BYTES_PER_SAMPLE / samplesPerFrame); + + samples = (GVSample *)voice_sample; + + totalEncodeTime = 0; + for(i = 0 ; i < numFrames ; i++) + { + startTime = current_time_hires(); + Encode(encodeOut, samples); + endTime = current_time_hires(); + totalEncodeTime += (endTime - startTime); + samples += samplesPerFrame; + msleep(0); + + startTime = current_time_hires(); + Decode(decodeOut, encodeOut, decoderData); + endTime = current_time_hires(); + totalDecodeTime += (endTime - startTime); + msleep(0); + } + + encodeFraction = ((double)totalEncodeTime / sampleTime); + decodeFraction = ((double)totalDecodeTime / sampleTime); + + printf("Encode: %0.1f%%\n", encodeFraction * 100); + printf("Decode: %0.1f%%\n", decodeFraction * 100); + + FreeDecoder(decoderData); + + Cleanup(); +} + +#if defined(_PS2) || defined(_PSP) + #ifdef __MWERKS__ // CodeWarrior will warn if not prototyped + int test_main(int argc, char **argp); + #endif +int test_main(int argc, char **argp) +#else +int main(int argc, char **argp) +#endif // _PS2 +{ + printf("Testing codecs\n"); + + TestCodec(GVCodecSuperLowBandwidth, GVRate_8KHz, "GVCodecSuperLowBandwidth"); +#if !defined(_PS2) + TestCodec(GVCodecLowBandwidth, GVRate_8KHz, "GVCodecLowBandwidth"); +#endif + TestCodec(GVCodecAverage, GVRate_8KHz, "GVCodecAverage"); + TestCodec(GVCodecHighQuality, GVRate_8KHz, "GVCodecHighQuality"); + TestCodec(GVCodecSuperHighQuality, GVRate_8KHz, "GVCodecSuperHighQuality"); + + printf("Testing complete\n"); + + GSI_UNUSED(argc); + GSI_UNUSED(argp); + + return 0; +} diff --git a/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_eenet_debug.h b/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_eenet_debug.h new file mode 100644 index 00000000..0c26e218 --- /dev/null +++ b/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_eenet_debug.h @@ -0,0 +1,4 @@ +#include "PREFIX_PS2_DEBUG_TC296.h" +#include "cw/cwprefix_eenet_debug.h" + +#define GSI_VOICE \ No newline at end of file diff --git a/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_eenet_release.h b/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_eenet_release.h new file mode 100644 index 00000000..aca5ec01 --- /dev/null +++ b/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_eenet_release.h @@ -0,0 +1,4 @@ +#include "PREFIX_PS2_RELEASE_TC296.h" +#include "cw/cwprefix_eenet_release.h" + +#define GSI_VOICE \ No newline at end of file diff --git a/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_insock_debug.h b/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_insock_debug.h new file mode 100644 index 00000000..b9b266c1 --- /dev/null +++ b/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_insock_debug.h @@ -0,0 +1,4 @@ +#include "PREFIX_PS2_DEBUG_TC296.h" +#include "cw/cwprefix_insock_debug.h" + +#define GSI_VOICE \ No newline at end of file diff --git a/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_insock_release.h b/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_insock_release.h new file mode 100644 index 00000000..2a208213 --- /dev/null +++ b/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_insock_release.h @@ -0,0 +1,4 @@ +#include "PREFIX_PS2_DEBUG_TC296.h" +#include "cw/cwprefix_insock_release.h" + +#define GSI_VOICE \ No newline at end of file diff --git a/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_snsystems_debug.h b/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_snsystems_debug.h new file mode 100644 index 00000000..6e5f7cbb --- /dev/null +++ b/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_snsystems_debug.h @@ -0,0 +1,4 @@ +#include "PREFIX_PS2_DEBUG_TC296.h" +#include "cw/cwprefix_snsystems_debug.h" + +#define GSI_VOICE \ No newline at end of file diff --git a/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_snsystems_release.h b/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_snsystems_release.h new file mode 100644 index 00000000..71936c6e --- /dev/null +++ b/code/gamespy/Voice2/voice2bench/voice2benchps2cw/voice2bench_prefix_snsystems_release.h @@ -0,0 +1,4 @@ +#include "PREFIX_PS2_RELEASE_TC296.h" +#include "cw/cwprefix_snsystems_release.h" + +#define GSI_VOICE \ No newline at end of file diff --git a/code/gamespy/Voice2/voice2bench/voice2benchps2prodg/voice2benchps2prodg.dsp b/code/gamespy/Voice2/voice2bench/voice2benchps2prodg/voice2benchps2prodg.dsp new file mode 100644 index 00000000..0f82216a --- /dev/null +++ b/code/gamespy/Voice2/voice2bench/voice2benchps2prodg/voice2benchps2prodg.dsp @@ -0,0 +1,138 @@ +# Microsoft Developer Studio Project File - Name="voice2benchps2prodg" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=voice2benchps2prodg - Win32 Release_EENet +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "voice2benchps2prodg.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "voice2benchps2prodg.mak" CFG="voice2benchps2prodg - Win32 Release_EENet" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "voice2benchps2prodg - Win32 Debug_EENet" (based on "Win32 (x86) Console Application") +!MESSAGE "voice2benchps2prodg - Win32 Release_EENet" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "$/Gamespy/GOA/Voice2/voice2bench/voice2benchps2prodg" +# PROP Scc_LocalPath "." +CPP=snCl.exe +RSC=rc.exe + +!IF "$(CFG)" == "voice2benchps2prodg - Win32 Debug_EENet" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug_EENet" +# PROP BASE Intermediate_Dir "Debug_EENet" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_EENet" +# PROP Intermediate_Dir "Debug_EENet" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Debug/" /FD /debug /c +# ADD CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include\libeenet" /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "EENET" /D "SN_TARGET_PS2" /D "GSI_VOICE" /Fo"PS2_EE_Debug/" /FD /debug /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"PS2_EE_Debug\voice2benchps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 libc.a eenetctl.a ent_smap.a ent_eth.a ent_ppp.a libeenet.a libscf.a liblgaud.a liblgcodec.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"Debug_EENet\voice2benchps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "voice2benchps2prodg - Win32 Release_EENet" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Release_EENet" +# PROP BASE Intermediate_Dir "Release_EENet" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Release_EENet" +# PROP Intermediate_Dir "Release_EENet" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /O2 /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Release/" /FD /c +# ADD CPP /nologo /w /W0 /O2 /I "C:\usr\local\sce\ee\include\libeenet" /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "EENET" /D "SN_TARGET_PS2" /D "GSI_VOICE" /Fo"PS2_EE_Release/" /FD /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"PS2_EE_Release\voice2benchps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 eenetctl.a ent_smap.a ent_eth.a ent_ppp.a libeenet.a libscf.a liblgaud.a liblgcodec.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"Release_EENet\voice2benchps2prodg.elf" /D:SN_TARGET_PS2 + +!ENDIF + +# Begin Target + +# Name "voice2benchps2prodg - Win32 Debug_EENet" +# Name "voice2benchps2prodg - Win32 Release_EENet" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\..\gvLogitechPS2Codecs.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\nonport.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\ps2\ps2common.c +# End Source File +# Begin Source File + +SOURCE=..\..\voice2bench\voice2bench.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\gvLogitechPS2Codecs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\nonport.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\ps2\prodg\PS2_in_VC.h +# End Source File +# Begin Source File + +SOURCE=..\..\voice2bench\voicesample.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\..\..\..\usr\local\sce\ee\lib\app.cmd +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\..\usr\local\sce\ee\lib\crt0.s +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\ps2\prodg\ps2.lk +# End Source File +# End Target +# End Project diff --git a/code/gamespy/Voice2/voice2bench/voice2benchps2prodg/voice2benchps2prodg.dsw b/code/gamespy/Voice2/voice2bench/voice2benchps2prodg/voice2benchps2prodg.dsw new file mode 100644 index 00000000..5c7166a9 --- /dev/null +++ b/code/gamespy/Voice2/voice2bench/voice2benchps2prodg/voice2benchps2prodg.dsw @@ -0,0 +1,37 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "voice2benchps2prodg"=.\voice2benchps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + $/Gamespy/GOA/Voice2/voice2bench/voice2benchps2prodg + . + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ + begin source code control + $/Gamespy/GOA/Voice2/voice2bench/voice2benchps2prodg + . + end source code control +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/code/gamespy/Voice2/voice2bench/voicesample.h b/code/gamespy/Voice2/voice2bench/voicesample.h new file mode 100644 index 00000000..19c3a897 --- /dev/null +++ b/code/gamespy/Voice2/voice2bench/voicesample.h @@ -0,0 +1,6671 @@ +GVByte voice_sample[80000]= +{ + 0x08,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x06,0x00,0x03,0x00, + 0x01,0x00,0x01,0x00,0xFD,0xFF,0xFC,0xFF,0xFD,0xFF,0xFE,0xFF, + 0xFC,0xFF,0xFC,0xFF,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF, + 0xFF,0xFF,0xFE,0xFF,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFD,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFD,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0x02,0x00,0xFF,0xFF,0xFE,0xFF,0x01,0x00,0xFE,0xFF, + 0xFF,0xFF,0x01,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x03,0x00, + 0x03,0x00,0x01,0x00,0x02,0x00,0x03,0x00,0x00,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x01,0x00,0x04,0x00, + 0x02,0x00,0x00,0x00,0x01,0x00,0x04,0x00,0xFF,0xFF,0x00,0x00, + 0x02,0x00,0xFD,0xFF,0x03,0x00,0x01,0x00,0xF1,0xFF,0xF0,0xFF, + 0xFC,0xFF,0xFE,0xFF,0x01,0x00,0x07,0x00,0x05,0x00,0x03,0x00, + 0xF1,0xFF,0xFE,0xFF,0x01,0x00,0x01,0x00,0x12,0x00,0x01,0x00, + 0xFF,0xFF,0xFD,0xFF,0xFA,0xFF,0xFC,0xFF,0xFC,0xFF,0xFB,0xFF, + 0x05,0x00,0x02,0x00,0xF5,0xFF,0xFD,0xFF,0x05,0x00,0x01,0x00, + 0xFE,0xFF,0x0A,0x00,0x10,0x00,0x0D,0x00,0x0A,0x00,0xFA,0xFF, + 0xF5,0xFF,0xFA,0xFF,0x00,0x00,0x04,0x00,0xFF,0xFF,0x03,0x00, + 0xFD,0xFF,0xFD,0xFF,0xEF,0xFF,0x07,0x00,0x07,0x00,0xF3,0xFF, + 0x17,0x00,0x08,0x00,0xF4,0xFF,0xF4,0xFF,0xFF,0xFF,0xFA,0xFF, + 0xFC,0xFF,0xFC,0xFF,0x0A,0x00,0x14,0x00,0x00,0x00,0x10,0x00, + 0xFE,0xFF,0xFA,0xFF,0xFE,0xFF,0x06,0x00,0xF1,0xFF,0xEC,0xFF, + 0x0C,0x00,0xF8,0xFF,0x03,0x00,0x06,0x00,0x01,0x00,0xF6,0xFF, + 0xF4,0xFF,0x07,0x00,0x02,0x00,0x0C,0x00,0x0B,0x00,0x00,0x00, + 0xFB,0xFF,0x07,0x00,0x0B,0x00,0xF9,0xFF,0xFB,0xFF,0x11,0x00, + 0x06,0x00,0xE5,0xFF,0xF4,0xFF,0xFD,0xFF,0xFE,0xFF,0x01,0x00, + 0x07,0x00,0x04,0x00,0xFE,0xFF,0x0E,0x00,0x0E,0x00,0xFF,0xFF, + 0xFA,0xFF,0xFA,0xFF,0x01,0x00,0xF8,0xFF,0xF0,0xFF,0x02,0x00, + 0xFC,0xFF,0x02,0x00,0xFA,0xFF,0xF6,0xFF,0xFC,0xFF,0xFC,0xFF, + 0x10,0x00,0xF8,0xFF,0xFC,0xFF,0x0A,0x00,0x05,0x00,0x0B,0x00, + 0x02,0x00,0xFF,0xFF,0x09,0x00,0x0C,0x00,0xFA,0xFF,0xFF,0xFF, + 0x02,0x00,0xE9,0xFF,0xF6,0xFF,0x04,0x00,0xF8,0xFF,0xF6,0xFF, + 0xFF,0xFF,0x06,0x00,0x00,0x00,0x0C,0x00,0x14,0x00,0x0A,0x00, + 0x08,0x00,0x04,0x00,0xFB,0xFF,0x03,0x00,0xFD,0xFF,0xFA,0xFF, + 0x03,0x00,0xFF,0xFF,0x00,0x00,0xF9,0xFF,0xF7,0xFF,0x00,0x00, + 0xFF,0xFF,0xF5,0xFF,0x05,0x00,0x0B,0x00,0x05,0x00,0x06,0x00, + 0x00,0x00,0x01,0x00,0xF0,0xFF,0x03,0x00,0x0D,0x00,0xF7,0xFF, + 0xFF,0xFF,0xFB,0xFF,0xF0,0xFF,0xEB,0xFF,0x02,0x00,0x10,0x00, + 0x0B,0x00,0x07,0x00,0xFB,0xFF,0x03,0x00,0x05,0x00,0x00,0x00, + 0x02,0x00,0x11,0x00,0x0F,0x00,0xFB,0xFF,0xF8,0xFF,0xED,0xFF, + 0xEB,0xFF,0xF8,0xFF,0x05,0x00,0x0B,0x00,0x0A,0x00,0x09,0x00, + 0x02,0x00,0xF5,0xFF,0xF5,0xFF,0xF8,0xFF,0xF8,0xFF,0x07,0x00, + 0x12,0x00,0x09,0x00,0x01,0x00,0xFF,0xFF,0xF9,0xFF,0xF4,0xFF, + 0xFD,0xFF,0x06,0x00,0x02,0x00,0x0C,0x00,0x0A,0x00,0xFA,0xFF, + 0xF4,0xFF,0xFA,0xFF,0x06,0x00,0xFA,0xFF,0x07,0x00,0x10,0x00, + 0xFE,0xFF,0xFB,0xFF,0xF2,0xFF,0xFD,0xFF,0xFC,0xFF,0xFB,0xFF, + 0x10,0x00,0x18,0x00,0x05,0x00,0x00,0x00,0x01,0x00,0xE2,0xFF, + 0xEA,0xFF,0x02,0x00,0x0E,0x00,0x00,0x00,0x06,0x00,0x0B,0x00, + 0xEE,0xFF,0xF8,0xFF,0xF9,0xFF,0xFC,0xFF,0xFE,0xFF,0x13,0x00, + 0x16,0x00,0xF8,0xFF,0xFE,0xFF,0x03,0x00,0xFF,0xFF,0xF7,0xFF, + 0x03,0x00,0x0E,0x00,0xFC,0xFF,0xFE,0xFF,0x0B,0x00,0xFB,0xFF, + 0xF8,0xFF,0x06,0x00,0x04,0x00,0xFC,0xFF,0xFE,0xFF,0x07,0x00, + 0xF8,0xFF,0xF3,0xFF,0x03,0x00,0x01,0x00,0xF3,0xFF,0xFC,0xFF, + 0x0B,0x00,0x06,0x00,0x02,0x00,0x06,0x00,0xFE,0xFF,0xFB,0xFF, + 0x04,0x00,0x00,0x00,0xFD,0xFF,0x03,0x00,0x06,0x00,0x02,0x00, + 0xF5,0xFF,0xFA,0xFF,0x01,0x00,0xFD,0xFF,0xFC,0xFF,0xFC,0xFF, + 0x03,0x00,0x03,0x00,0xFF,0xFF,0x03,0x00,0x00,0x00,0xFE,0xFF, + 0x03,0x00,0x04,0x00,0x01,0x00,0x01,0x00,0x02,0x00,0xFF,0xFF, + 0x04,0x00,0x03,0x00,0xFE,0xFF,0x02,0x00,0x01,0x00,0x01,0x00, + 0xFF,0xFF,0xFC,0xFF,0x08,0x00,0xFE,0xFF,0xFD,0xFF,0x06,0x00, + 0xF9,0xFF,0xFD,0xFF,0x00,0x00,0x01,0x00,0xFD,0xFF,0xF7,0xFF, + 0x03,0x00,0xFF,0xFF,0xF4,0xFF,0x03,0x00,0x03,0x00,0xF9,0xFF, + 0x01,0x00,0x04,0x00,0x03,0x00,0x02,0x00,0x02,0x00,0x00,0x00, + 0xFF,0xFF,0x06,0x00,0xFD,0xFF,0xFB,0xFF,0x03,0x00,0x02,0x00, + 0xFE,0xFF,0xFE,0xFF,0x01,0x00,0x02,0x00,0x00,0x00,0xFE,0xFF, + 0x01,0x00,0xFC,0xFF,0x01,0x00,0x02,0x00,0x05,0x00,0x06,0x00, + 0xFC,0xFF,0x03,0x00,0x00,0x00,0xFE,0xFF,0x04,0x00,0x02,0x00, + 0xFE,0xFF,0x01,0x00,0x02,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00, + 0x02,0x00,0xFD,0xFF,0xFE,0xFF,0x02,0x00,0x02,0x00,0xFB,0xFF, + 0x02,0x00,0x03,0x00,0xFC,0xFF,0x03,0x00,0xFC,0xFF,0xFC,0xFF, + 0x02,0x00,0xFD,0xFF,0x02,0x00,0x01,0x00,0xFE,0xFF,0x01,0x00, + 0xFE,0xFF,0x01,0x00,0x02,0x00,0xFF,0xFF,0x01,0x00,0x02,0x00, + 0x06,0x00,0x03,0x00,0xFE,0xFF,0xF7,0xFF,0x00,0x00,0x04,0x00, + 0xFD,0xFF,0x01,0x00,0xFE,0xFF,0x01,0x00,0x01,0x00,0x00,0x00, + 0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x04,0x00, + 0xF7,0xFF,0x01,0x00,0x00,0x00,0xFB,0xFF,0x04,0x00,0xFE,0xFF, + 0xFD,0xFF,0x05,0x00,0x02,0x00,0x00,0x00,0x05,0x00,0xFA,0xFF, + 0xFC,0xFF,0xFB,0xFF,0x00,0x00,0xFC,0xFF,0xFB,0xFF,0x04,0x00, + 0xFB,0xFF,0x03,0x00,0x09,0x00,0x07,0x00,0x03,0x00,0x03,0x00, + 0xFB,0xFF,0x00,0x00,0x03,0x00,0x06,0x00,0x01,0x00,0xFF,0xFF, + 0xFE,0xFF,0xF4,0xFF,0x04,0x00,0xFE,0xFF,0x00,0x00,0x02,0x00, + 0xF8,0xFF,0xF6,0xFF,0xFE,0xFF,0x03,0x00,0xFC,0xFF,0xFE,0xFF, + 0x0C,0x00,0x02,0x00,0xF7,0xFF,0x0C,0x00,0xFE,0xFF,0xFD,0xFF, + 0x07,0x00,0xFC,0xFF,0x0D,0x00,0x0A,0x00,0xF6,0xFF,0x06,0x00, + 0x07,0x00,0xF9,0xFF,0xFE,0xFF,0x02,0x00,0xFC,0xFF,0xFF,0xFF, + 0x04,0x00,0xFD,0xFF,0xF6,0xFF,0xFE,0xFF,0xFF,0xFF,0xFC,0xFF, + 0x02,0x00,0x09,0x00,0x02,0x00,0xFF,0xFF,0x04,0x00,0xF6,0xFF, + 0x00,0x00,0xFF,0xFF,0x0B,0x00,0xFC,0xFF,0xF7,0xFF,0xF3,0xFF, + 0xFF,0xFF,0x0C,0x00,0xEF,0xFF,0x00,0x00,0xEF,0xFF,0x11,0x00, + 0x0A,0x00,0xFB,0xFF,0xE7,0xFF,0x12,0x00,0x3D,0x00,0xF8,0xFF, + 0x05,0x00,0xE7,0xFF,0x0D,0x00,0x12,0x00,0xE4,0xFF,0xD8,0xFF, + 0x14,0x00,0x0A,0x00,0x5A,0x00,0x41,0x00,0x66,0xFF,0x17,0x00, + 0xD9,0xFF,0xB0,0xFF,0x00,0x00,0xE0,0xFF,0x16,0x00,0x11,0x00, + 0x19,0x00,0x02,0x00,0x26,0x00,0x21,0x00,0xFC,0xFF,0xF8,0xFF, + 0x08,0x00,0x1F,0x00,0x12,0x00,0xF9,0xFF,0xEA,0xFF,0x00,0x00, + 0x20,0x00,0xE7,0xFF,0xE1,0xFF,0x1F,0x00,0xD8,0xFF,0xE7,0xFF, + 0x0E,0x00,0xEE,0xFF,0x08,0x00,0x0E,0x00,0x09,0x00,0xEC,0xFF, + 0x13,0x00,0x0D,0x00,0xE4,0xFF,0x1B,0x00,0xF2,0xFF,0xED,0xFF, + 0x22,0x00,0xF0,0xFF,0x02,0x00,0x19,0x00,0x09,0x00,0x0C,0x00, + 0xE0,0xFF,0x1C,0x00,0xF7,0xFF,0xFE,0xFF,0xD8,0xFF,0x0E,0x00, + 0x05,0x00,0xAE,0xFF,0x24,0x00,0xED,0xFF,0x1F,0x00,0x18,0x00, + 0xB9,0x00,0x6A,0x00,0x0B,0x00,0x4F,0xFF,0xD9,0xFE,0x67,0x00, + 0xE2,0xFF,0x92,0xFF,0x94,0xFF,0xB8,0xFF,0xDE,0xFF,0x38,0x00, + 0x68,0x00,0xF8,0xFF,0x5D,0x00,0x73,0x00,0x74,0x00,0x94,0x00, + 0x89,0x00,0x12,0x00,0x19,0x00,0x2C,0x00,0xA2,0x00,0x64,0x00, + 0x67,0xFF,0x21,0xFF,0x17,0xFF,0x72,0xFF,0x4E,0xFF,0x99,0xFF, + 0xA0,0xFF,0xBD,0xFF,0x24,0x00,0x51,0x00,0x6A,0x00,0xCF,0xFF, + 0x12,0x00,0x85,0x00,0x86,0x00,0x8E,0x00,0x0B,0x00,0x6D,0x00, + 0xEE,0x00,0xFE,0xFF,0x13,0x00,0xDF,0xFF,0x9E,0xFF,0x81,0xFF, + 0x7E,0xFF,0x2C,0xFF,0x46,0xFF,0xAF,0x00,0x72,0xFF,0xBD,0xFF, + 0x82,0x00,0xE4,0xFF,0x3B,0x00,0x02,0x00,0xAE,0xFF,0x16,0x01, + 0x31,0x01,0x6B,0x00,0x3F,0x00,0x42,0xFE,0xD5,0xFF,0xA7,0x01, + 0x97,0x00,0xFE,0xFE,0x45,0xFD,0x4C,0xFD,0xDF,0xFE,0x65,0x00, + 0x58,0x00,0xA3,0x00,0xCE,0x00,0xB2,0x01,0xD0,0x02,0x6D,0x02, + 0x56,0x01,0x06,0x00,0xD8,0xFF,0x4B,0xFF,0xD2,0xFF,0x5C,0x00, + 0x99,0xFE,0xB8,0xFD,0x00,0xFE,0xB7,0xFE,0xAE,0xFF,0x1F,0x00, + 0xC7,0xFF,0x9C,0xFF,0x68,0x00,0xE0,0x00,0x27,0x01,0x91,0x01, + 0x0F,0x01,0x62,0x00,0x37,0x00,0x06,0x00,0xC6,0xFF,0xBC,0xFF, + 0x6F,0xFF,0x0F,0xFF,0x3F,0xFF,0x5F,0xFF,0x4F,0xFF,0x84,0xFF, + 0xFA,0xFF,0x1D,0x00,0x51,0x00,0xAD,0x00,0xC1,0x00,0xD5,0x00, + 0xE2,0x00,0x95,0x00,0x23,0x00,0x0B,0x00,0xB9,0xFF,0x6E,0xFF, + 0x76,0xFF,0x61,0xFF,0x36,0xFF,0x2C,0xFF,0x6F,0xFF,0x89,0xFF, + 0x92,0xFF,0x13,0x00,0x5A,0x00,0x3D,0x00,0x5A,0x00,0x40,0x00, + 0x1F,0x00,0x52,0x00,0x70,0x00,0x19,0x00,0xCA,0xFF,0x96,0xFF, + 0x3F,0xFF,0xDC,0xFF,0x83,0x00,0x3A,0x00,0x33,0x00,0xB1,0x00, + 0xC9,0x00,0xF2,0x00,0xA5,0x01,0x1D,0x01,0x7B,0x00,0xFF,0x00, + 0xA2,0x00,0x02,0x00,0x4F,0x00,0x5D,0xFF,0x71,0xFE,0x03,0x00, + 0x2A,0x00,0xC5,0x01,0x06,0x06,0x38,0x04,0xD0,0x00,0x4E,0xFF, + 0xD5,0xFC,0x06,0xFC,0x37,0xFC,0xDF,0xFB,0xB5,0xF8,0x25,0xF7, + 0x1F,0xF9,0xCC,0xF9,0x55,0xFC,0xFE,0xFE,0x4A,0xFF,0x9F,0x00, + 0x49,0x03,0x38,0x06,0xAF,0x07,0x8B,0x08,0x5D,0x08,0x39,0x06, + 0xE3,0x05,0xB4,0x05,0xCC,0x03,0xEF,0x01,0x31,0xFF,0x81,0xFC, + 0xE0,0xFA,0x54,0xFB,0x16,0xFC,0x4D,0xFB,0x63,0xFD,0xCF,0xFF, + 0x73,0xFF,0x65,0x00,0x94,0x01,0xC3,0x00,0xCD,0x00,0xF5,0x01, + 0x89,0x01,0xDD,0xFF,0xCD,0xFF,0x89,0xFF,0x09,0xFE,0x6D,0xFE, + 0x52,0xFE,0x32,0xFD,0x60,0xFD,0x3A,0xFE,0xAE,0xFE,0x2F,0xFF, + 0x04,0x00,0x39,0x00,0x4C,0x00,0x2B,0x01,0x30,0x02,0x4F,0x02, + 0x92,0x01,0xCB,0x00,0x57,0x00,0x0C,0x00,0x7A,0x00,0x63,0x00, + 0x18,0x00,0xD6,0x00,0x8B,0x00,0x20,0x00,0x5B,0x02,0xC1,0x01, + 0x7F,0x01,0x6D,0x08,0xD2,0x08,0xE8,0x02,0x2D,0x01,0x63,0xFD, + 0x4D,0xF9,0x8B,0xF9,0xAC,0xFA,0xFA,0xF7,0xB8,0xF3,0x45,0xF5, + 0x45,0xF6,0x2E,0xF7,0xF5,0xFC,0xD2,0xFF,0x07,0x00,0x83,0x02, + 0x2C,0x06,0xB6,0x08,0x54,0x0A,0xA8,0x0C,0x79,0x0B,0xED,0x07, + 0xE3,0x07,0x87,0x06,0x5B,0x03,0x65,0x01,0xD7,0xFE,0x49,0xFB, + 0xD3,0xF8,0x73,0xF9,0xE7,0xF9,0x8D,0xF9,0x6A,0xFB,0x0A,0xFD, + 0x69,0xFE,0x8E,0x01,0xC4,0x03,0xED,0x03,0x58,0x03,0xF2,0x02, + 0x55,0x02,0x89,0x01,0x92,0x01,0x2D,0x00,0xAD,0xFD,0xA1,0xFC, + 0xCA,0xFB,0x1C,0xFB,0x83,0xFB,0x5C,0xFC,0xCF,0xFC,0x80,0xFD, + 0x62,0xFF,0xA7,0x00,0x3B,0x01,0x92,0x02,0xE1,0x02,0x5D,0x02, + 0x81,0x02,0x7B,0x02,0xF2,0x01,0xFD,0x00,0x58,0x00,0x9B,0xFF, + 0xC0,0xFE,0xE8,0xFE,0xEC,0xFE,0x1F,0xFF,0x94,0xFF,0xDD,0xFF, + 0x29,0x00,0xE1,0xFF,0xA1,0x00,0xBF,0x00,0x88,0x02,0xE2,0x06, + 0x2B,0x05,0x3C,0x02,0x47,0x01,0x09,0xFE,0x22,0xFD,0xF5,0xFD, + 0x8F,0xFE,0xFD,0xFC,0x33,0xFA,0x2F,0xFA,0x2B,0xF9,0x33,0xF9, + 0x53,0xFC,0x8E,0xFD,0xE5,0xFD,0xF7,0xFE,0x6C,0x00,0x54,0x02, + 0xE5,0x03,0x6C,0x06,0x2F,0x07,0x04,0x06,0xE0,0x05,0x01,0x05, + 0xF4,0x03,0x81,0x03,0xDE,0x02,0xAC,0x01,0xF9,0xFF,0x29,0xFF, + 0xC9,0xFE,0x3D,0xFE,0xA2,0xFE,0xD4,0xFE,0x8C,0xFE,0xDB,0xFE, + 0x2F,0xFF,0x1D,0xFF,0x07,0xFF,0x31,0xFF,0x3F,0xFF,0x46,0xFF, + 0x83,0xFF,0x86,0xFF,0x51,0xFF,0x0D,0xFF,0xA6,0xFE,0x2D,0xFE, + 0xE6,0xFD,0xF6,0xFD,0x1D,0xFE,0x4F,0xFE,0xA2,0xFE,0xDE,0xFE, + 0x1D,0xFF,0x7E,0xFF,0xDD,0xFF,0x47,0x00,0x85,0x00,0xB1,0x00, + 0xCC,0x00,0xD8,0x00,0xFD,0x00,0xFA,0x00,0x14,0x01,0x2C,0x01, + 0xFA,0x00,0xFA,0x00,0x08,0x01,0xF1,0x00,0xDE,0x00,0xC6,0x00, + 0xB7,0x00,0x61,0x00,0x2C,0x00,0x27,0x00,0xE6,0xFF,0xC1,0xFF, + 0xD5,0xFF,0x0F,0x00,0x28,0x00,0x2C,0x00,0x42,0x00,0x27,0x00, + 0x14,0x00,0x42,0x00,0x5F,0x00,0x6D,0x00,0x5A,0x00,0xFE,0xFF, + 0x70,0xFF,0xEF,0xFE,0xB3,0xFE,0x8C,0xFE,0x79,0xFE,0x6D,0xFE, + 0x3E,0xFE,0x24,0xFE,0x43,0xFE,0x9B,0xFE,0x1D,0xFF,0xA7,0xFF, + 0x1A,0x00,0x59,0x00,0x8B,0x00,0xD4,0x00,0x29,0x01,0x8B,0x01, + 0xDF,0x01,0xFE,0x01,0xFB,0x01,0xF1,0x01,0xE6,0x01,0xE1,0x01, + 0xE8,0x01,0xDC,0x01,0xAA,0x01,0x6D,0x01,0x1D,0x01,0xC6,0x00, + 0x76,0x00,0x32,0x00,0xF4,0xFF,0xA9,0xFF,0x58,0xFF,0x0C,0xFF, + 0xC2,0xFE,0x7E,0xFE,0x4F,0xFE,0x2A,0xFE,0x01,0xFE,0xE1,0xFD, + 0xCE,0xFD,0xC3,0xFD,0xD3,0xFD,0x04,0xFE,0x3F,0xFE,0x75,0xFE, + 0xB6,0xFE,0xF4,0xFE,0x33,0xFF,0x80,0xFF,0xDB,0xFF,0x2F,0x00, + 0x79,0x00,0xBB,0x00,0xED,0x00,0x15,0x01,0x45,0x01,0x72,0x01, + 0x88,0x01,0x91,0x01,0x8C,0x01,0x74,0x01,0x53,0x01,0x3A,0x01, + 0x21,0x01,0xF8,0x00,0xBC,0x00,0x87,0x00,0x52,0x00,0x1D,0x00, + 0xFB,0xFF,0xDC,0xFF,0xBD,0xFF,0x9E,0xFF,0xA3,0xFF,0xC4,0xFF, + 0xCF,0xFF,0xD5,0xFF,0xDD,0xFF,0xDF,0xFF,0xE6,0xFF,0x00,0x00, + 0x1F,0x00,0x24,0x00,0xF9,0xFF,0xB7,0xFF,0x66,0xFF,0x16,0xFF, + 0xE9,0xFE,0xD4,0xFE,0xC9,0xFE,0xB6,0xFE,0xAB,0xFE,0xB8,0xFE, + 0xE0,0xFE,0x35,0xFF,0x9B,0xFF,0xFB,0xFF,0x46,0x00,0x85,0x00, + 0xB7,0x00,0xE5,0x00,0x19,0x01,0x4A,0x01,0x6F,0x01,0x70,0x01, + 0x67,0x01,0x58,0x01,0x42,0x01,0x3B,0x01,0x33,0x01,0x1F,0x01, + 0xFF,0x00,0xD6,0x00,0xA2,0x00,0x66,0x00,0x35,0x00,0x08,0x00, + 0xD9,0xFF,0xA7,0xFF,0x71,0xFF,0x3B,0xFF,0x03,0xFF,0xD5,0xFE, + 0xB4,0xFE,0x92,0xFE,0x77,0xFE,0x65,0xFE,0x58,0xFE,0x5E,0xFE, + 0x74,0xFE,0x9B,0xFE,0xCA,0xFE,0xFB,0xFE,0x36,0xFF,0x6F,0xFF, + 0xA2,0xFF,0xE0,0xFF,0x21,0x00,0x57,0x00,0x8A,0x00,0xB6,0x00, + 0xD8,0x00,0xF1,0x00,0x0D,0x01,0x21,0x01,0x2D,0x01,0x28,0x01, + 0x1B,0x01,0x08,0x01,0xEA,0x00,0xD7,0x00,0xBC,0x00,0x9A,0x00, + 0x74,0x00,0x51,0x00,0x2D,0x00,0x0B,0x00,0xF1,0xFF,0xDA,0xFF, + 0xC1,0xFF,0xAD,0xFF,0xA3,0xFF,0x90,0xFF,0x87,0xFF,0x8A,0xFF, + 0x9C,0xFF,0xBD,0xFF,0xD4,0xFF,0xE2,0xFF,0xF3,0xFF,0xFE,0xFF, + 0x0D,0x00,0x24,0x00,0x37,0x00,0x40,0x00,0x28,0x00,0xFF,0xFF, + 0xD5,0xFF,0xAC,0xFF,0x8C,0xFF,0x79,0xFF,0x6E,0xFF,0x5A,0xFF, + 0x4D,0xFF,0x54,0xFF,0x6A,0xFF,0x90,0xFF,0xC4,0xFF,0xF4,0xFF, + 0x17,0x00,0x38,0x00,0x56,0x00,0x71,0x00,0x8A,0x00,0xA6,0x00, + 0xB9,0x00,0xBC,0x00,0xBC,0x00,0xB3,0x00,0xAF,0x00,0xAD,0x00, + 0xAB,0x00,0xAD,0x00,0x9C,0x00,0x79,0x00,0x5D,0x00,0x39,0x00, + 0x17,0x00,0xFE,0xFF,0xE5,0xFF,0xC3,0xFF,0x92,0xFF,0x63,0xFF, + 0x3B,0xFF,0x1B,0xFF,0x07,0xFF,0x01,0xFF,0xFB,0xFE,0xF6,0xFE, + 0xFB,0xFE,0x0D,0xFF,0x2C,0xFF,0x54,0xFF,0x82,0xFF,0xA6,0xFF, + 0xC9,0xFF,0xE6,0xFF,0x06,0x00,0x28,0x00,0x45,0x00,0x64,0x00, + 0x75,0x00,0x87,0x00,0x8D,0x00,0x90,0x00,0x98,0x00,0x97,0x00, + 0x9B,0x00,0x92,0x00,0x83,0x00,0x73,0x00,0x64,0x00,0x56,0x00, + 0x49,0x00,0x3C,0x00,0x29,0x00,0x1C,0x00,0x10,0x00,0x01,0x00, + 0xF9,0xFF,0xF3,0xFF,0xEA,0xFF,0xE5,0xFF,0xDE,0xFF,0xDB,0xFF, + 0xD9,0xFF,0xDD,0xFF,0xE1,0xFF,0xE4,0xFF,0xED,0xFF,0xF1,0xFF, + 0xF5,0xFF,0xFB,0xFF,0xFD,0xFF,0x03,0x00,0x0B,0x00,0x09,0x00, + 0x05,0x00,0x04,0x00,0x04,0x00,0x02,0x00,0x05,0x00,0x04,0x00, + 0xFB,0xFF,0xF0,0xFF,0xE5,0xFF,0xDD,0xFF,0xD4,0xFF,0xD4,0xFF, + 0xD6,0xFF,0xD0,0xFF,0xD0,0xFF,0xD6,0xFF,0xDA,0xFF,0xE8,0xFF, + 0xF8,0xFF,0x02,0x00,0x0E,0x00,0x15,0x00,0x1D,0x00,0x22,0x00, + 0x27,0x00,0x33,0x00,0x38,0x00,0x39,0x00,0x39,0x00,0x33,0x00, + 0x32,0x00,0x31,0x00,0x2B,0x00,0x29,0x00,0x1F,0x00,0x12,0x00, + 0x05,0x00,0xFA,0xFF,0xEE,0xFF,0xE5,0xFF,0xDC,0xFF,0xD1,0xFF, + 0xC5,0xFF,0xBC,0xFF,0xB7,0xFF,0xB1,0xFF,0xAC,0xFF,0xAF,0xFF, + 0xB0,0xFF,0xAC,0xFF,0xB1,0xFF,0xB6,0xFF,0xBA,0xFF,0xC8,0xFF, + 0xD2,0xFF,0xDA,0xFF,0xE7,0xFF,0xEB,0xFF,0xF6,0xFF,0x04,0x00, + 0x0B,0x00,0x18,0x00,0x1E,0x00,0x25,0x00,0x2E,0x00,0x32,0x00, + 0x3B,0x00,0x42,0x00,0x4A,0x00,0x4B,0x00,0x4A,0x00,0x50,0x00, + 0x52,0x00,0x4C,0x00,0x4B,0x00,0x4B,0x00,0x42,0x00,0x41,0x00, + 0x39,0x00,0x2D,0x00,0x28,0x00,0x1B,0x00,0x16,0x00,0x13,0x00, + 0xFF,0xFF,0xF6,0xFF,0xEC,0xFF,0xE2,0xFF,0xE0,0xFF,0xDB,0xFF, + 0xDB,0xFF,0xD6,0xFF,0xD5,0xFF,0xD0,0xFF,0xCA,0xFF,0xD2,0xFF, + 0xCF,0xFF,0xD3,0xFF,0xD6,0xFF,0xD7,0xFF,0xE1,0xFF,0xE5,0xFF, + 0xEE,0xFF,0xFA,0xFF,0x03,0x00,0x0B,0x00,0x15,0x00,0x19,0x00, + 0x1B,0x00,0x23,0x00,0x26,0x00,0x28,0x00,0x2C,0x00,0x28,0x00, + 0x22,0x00,0x1E,0x00,0x16,0x00,0x14,0x00,0x0E,0x00,0x02,0x00, + 0xFA,0xFF,0xF3,0xFF,0xE9,0xFF,0xDF,0xFF,0xDC,0xFF,0xD2,0xFF, + 0xCD,0xFF,0xCB,0xFF,0xC3,0xFF,0xC7,0xFF,0xC0,0xFF,0xC5,0xFF, + 0xC9,0xFF,0xCA,0xFF,0xD2,0xFF,0xD1,0xFF,0xD9,0xFF,0xE3,0xFF, + 0xE8,0xFF,0xF0,0xFF,0xF8,0xFF,0xFD,0xFF,0x05,0x00,0x0A,0x00, + 0x0E,0x00,0x15,0x00,0x18,0x00,0x1B,0x00,0x20,0x00,0x23,0x00, + 0x29,0x00,0x2A,0x00,0x2C,0x00,0x2C,0x00,0x2C,0x00,0x2E,0x00, + 0x2B,0x00,0x2C,0x00,0x29,0x00,0x28,0x00,0x26,0x00,0x20,0x00, + 0x21,0x00,0x1D,0x00,0x15,0x00,0x16,0x00,0x12,0x00,0x0D,0x00, + 0x09,0x00,0x01,0x00,0x00,0x00,0xFD,0xFF,0xF9,0xFF,0xF5,0xFF, + 0xF4,0xFF,0xF3,0xFF,0xF2,0xFF,0xEF,0xFF,0xF1,0xFF,0xF3,0xFF, + 0xF1,0xFF,0xF4,0xFF,0xF2,0xFF,0xF1,0xFF,0xF7,0xFF,0xF5,0xFF, + 0xF6,0xFF,0xF8,0xFF,0xF8,0xFF,0xFA,0xFF,0xFA,0xFF,0xFC,0xFF, + 0xFF,0xFF,0x00,0x00,0xFC,0xFF,0xFD,0xFF,0x01,0x00,0xFD,0xFF, + 0xFC,0xFF,0xFC,0xFF,0xFE,0xFF,0xFE,0xFF,0xFD,0xFF,0xFC,0xFF, + 0xFC,0xFF,0xFA,0xFF,0xFA,0xFF,0xF8,0xFF,0xF8,0xFF,0xF7,0xFF, + 0xF2,0xFF,0xF6,0xFF,0xF6,0xFF,0xF3,0xFF,0xF5,0xFF,0xF0,0xFF, + 0xF1,0xFF,0xF4,0xFF,0xF4,0xFF,0xF3,0xFF,0xF6,0xFF,0xF9,0xFF, + 0xF8,0xFF,0xF9,0xFF,0xFB,0xFF,0xFE,0xFF,0xFF,0xFF,0x01,0x00, + 0x00,0x00,0x03,0x00,0x04,0x00,0x07,0x00,0x08,0x00,0x06,0x00, + 0x0A,0x00,0x0B,0x00,0x0B,0x00,0x0B,0x00,0x0B,0x00,0x0B,0x00, + 0x0B,0x00,0x0A,0x00,0x0D,0x00,0x0C,0x00,0x0C,0x00,0x0B,0x00, + 0x05,0x00,0x0B,0x00,0x09,0x00,0x09,0x00,0x0A,0x00,0x06,0x00, + 0x07,0x00,0x08,0x00,0x07,0x00,0x06,0x00,0x06,0x00,0x07,0x00, + 0x09,0x00,0x03,0x00,0x02,0x00,0x06,0x00,0x04,0x00,0x04,0x00, + 0x01,0x00,0x01,0x00,0x02,0x00,0xFE,0xFF,0x00,0x00,0xFE,0xFF, + 0xFB,0xFF,0xFC,0xFF,0xFC,0xFF,0xFC,0xFF,0xFC,0xFF,0xFA,0xFF, + 0xFD,0xFF,0xFB,0xFF,0xF8,0xFF,0xFD,0xFF,0xFB,0xFF,0xF8,0xFF, + 0xFA,0xFF,0xFC,0xFF,0xFC,0xFF,0xF9,0xFF,0xFA,0xFF,0xFD,0xFF, + 0xFB,0xFF,0xFB,0xFF,0xFC,0xFF,0xFB,0xFF,0xFC,0xFF,0xFC,0xFF, + 0xFA,0xFF,0xFC,0xFF,0xFD,0xFF,0xFB,0xFF,0xFA,0xFF,0xFC,0xFF, + 0xFC,0xFF,0xFD,0xFF,0xFC,0xFF,0xFC,0xFF,0xFE,0xFF,0xFD,0xFF, + 0xFD,0xFF,0xFE,0xFF,0xFE,0xFF,0xFF,0xFF,0xFE,0xFF,0x00,0x00, + 0x00,0x00,0x01,0x00,0x01,0x00,0xFF,0xFF,0x03,0x00,0x01,0x00, + 0x00,0x00,0x03,0x00,0x02,0x00,0x01,0x00,0x01,0x00,0x01,0x00, + 0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00, + 0x04,0x00,0x06,0x00,0x01,0x00,0x05,0x00,0x05,0x00,0x04,0x00, + 0x07,0x00,0x06,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00, + 0x04,0x00,0x02,0x00,0x04,0x00,0x00,0x00,0x03,0x00,0x02,0x00, + 0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0xFF,0xFF,0xFE,0xFF, + 0xFE,0xFF,0x02,0x00,0x02,0x00,0x00,0x00,0x02,0x00,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0xFF,0xFF,0x00,0x00, + 0xFE,0xFF,0xFD,0xFF,0x00,0x00,0xFD,0xFF,0xFC,0xFF,0xFE,0xFF, + 0xFE,0xFF,0xFF,0xFF,0xFC,0xFF,0xFC,0xFF,0x01,0x00,0xFF,0xFF, + 0xFC,0xFF,0x00,0x00,0xFD,0xFF,0xFD,0xFF,0xFF,0xFF,0xFE,0xFF, + 0xFE,0xFF,0xFE,0xFF,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF, + 0xFE,0xFF,0x00,0x00,0xFF,0xFF,0xFE,0xFF,0xFE,0xFF,0xFC,0xFF, + 0xFC,0xFF,0xFF,0xFF,0x00,0x00,0xFE,0xFF,0xFE,0xFF,0x00,0x00, + 0x01,0x00,0x03,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x01,0x00, + 0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x02,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x02,0x00,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00, + 0x02,0x00,0x01,0x00,0x03,0x00,0x04,0x00,0x01,0x00,0x03,0x00, + 0x01,0x00,0x04,0x00,0xFD,0xFF,0xFE,0xFF,0x09,0x00,0xFF,0xFF, + 0xFF,0xFF,0x01,0x00,0x04,0x00,0x01,0x00,0x00,0x00,0x06,0x00, + 0x02,0x00,0x00,0x00,0x05,0x00,0x06,0x00,0x01,0x00,0x03,0x00, + 0x03,0x00,0x01,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0xFF,0xFF,0xFD,0xFF,0xFC,0xFF,0xFC,0xFF,0xF9,0xFF, + 0xF8,0xFF,0xFB,0xFF,0xF9,0xFF,0xF5,0xFF,0xF9,0xFF,0xF9,0xFF, + 0xF8,0xFF,0xFB,0xFF,0xFC,0xFF,0xFD,0xFF,0xFD,0xFF,0xFD,0xFF, + 0x02,0x00,0x02,0x00,0x02,0x00,0x04,0x00,0x06,0x00,0x08,0x00, + 0x07,0x00,0x04,0x00,0x05,0x00,0x03,0x00,0x05,0x00,0x01,0x00, + 0x02,0x00,0x01,0x00,0xFE,0xFF,0xFF,0xFF,0xFB,0xFF,0x02,0x00, + 0xFD,0xFF,0x00,0x00,0xFA,0xFF,0x00,0x00,0xFC,0xFF,0xFE,0xFF, + 0xF8,0xFF,0xFD,0xFF,0xF8,0xFF,0xFF,0xFF,0x01,0x00,0xF6,0xFF, + 0x10,0x00,0xEE,0xFF,0x32,0x00,0x9C,0xFF,0x61,0x00,0x64,0x00, + 0x8C,0x00,0xDA,0x02,0x98,0x00,0x85,0xFF,0x4B,0x01,0xD3,0xFF, + 0x56,0xFE,0x9F,0x00,0xF0,0xFF,0xEA,0xFA,0x03,0xFE,0x9B,0x04, + 0x36,0x00,0x9C,0xFA,0xA8,0xFB,0xBF,0xFC,0x0E,0xFD,0xBB,0xFF, + 0x95,0x03,0xDA,0x01,0x6B,0xFD,0xC9,0xFD,0xFB,0x01,0x60,0x04, + 0xE2,0x04,0xC2,0x05,0xD2,0x04,0x9B,0x01,0x0F,0x01,0x0D,0x04, + 0x7D,0x05,0x07,0x03,0xE3,0xFE,0x88,0xFC,0x45,0xFB,0x78,0xFA, + 0x2D,0xFC,0x2A,0xFF,0x40,0xFF,0x90,0xFC,0x22,0xFB,0xC2,0xFC, + 0xFD,0xFE,0x05,0x01,0x31,0x03,0x27,0x03,0x98,0x00,0x6F,0xFE, + 0x0C,0xFF,0x9C,0x01,0x9B,0x02,0x26,0x01,0x0C,0xFF,0x35,0xFD, + 0x87,0xFC,0xFE,0xFD,0x70,0x00,0xD6,0x01,0x78,0x00,0x2F,0xFE, + 0xEB,0xFE,0xBA,0x00,0x7A,0x01,0x82,0x02,0xC0,0x02,0xCB,0x00, + 0x39,0x00,0x5A,0x00,0x6D,0x00,0xB1,0x01,0x25,0x01,0x31,0xFF, + 0xAC,0xFE,0xD0,0xFE,0x89,0xFE,0xC1,0xFF,0x3B,0x01,0xA9,0x00, + 0x14,0xFF,0xA2,0xFF,0xA7,0x01,0xDB,0x02,0x0E,0x02,0xAC,0x00, + 0x09,0x00,0xDD,0xFD,0xC2,0xFD,0xCE,0xFE,0xF7,0xFE,0x38,0xFF, + 0x9E,0xFE,0xCE,0xFF,0x8A,0x01,0x3C,0x09,0x51,0x12,0x01,0x0F, + 0x8B,0x04,0xE5,0xFA,0x0C,0xF6,0x2D,0xF5,0xAF,0xF5,0xCC,0xF7, + 0xF9,0xF7,0xAC,0xF1,0x85,0xED,0x9C,0xF1,0xBA,0xF9,0x9A,0x01, + 0x0C,0x06,0x26,0x0A,0x76,0x0A,0x6F,0x07,0x5E,0x08,0xDE,0x0C, + 0xF0,0x0E,0x04,0x0C,0xC3,0x05,0x7F,0xFF,0x99,0xFB,0x89,0xF8, + 0x6D,0xF9,0x6B,0xFC,0x9B,0xFC,0x49,0xFB,0xE2,0xFB,0x21,0xFF, + 0x4D,0x02,0xC6,0x03,0x5C,0x04,0x95,0x04,0x24,0x02,0x9A,0xFF, + 0xC4,0xFF,0x92,0x00,0x2F,0x00,0x55,0xFE,0xD7,0xFC,0xE6,0xFB, + 0x28,0xFB,0x91,0xFB,0xCF,0xFD,0xF9,0xFF,0x43,0x00,0x46,0xFF, + 0x71,0xFE,0x5E,0xFF,0x0A,0x01,0xEE,0x02,0xB6,0x03,0x83,0x02, + 0x12,0x00,0x60,0xFE,0x67,0xFE,0xB3,0xFE,0x5D,0xFF,0x4B,0x00, + 0x1E,0x00,0x74,0x02,0x85,0x10,0x75,0x1C,0x1D,0x16,0x96,0x06, + 0xB8,0xF9,0xCB,0xF3,0x6B,0xF0,0x40,0xEF,0xA6,0xF2,0x23,0xF4, + 0xD6,0xEA,0x08,0xE3,0x7A,0xE8,0x0A,0xF5,0xB7,0x01,0x12,0x09, + 0xAF,0x0F,0x20,0x13,0x19,0x10,0x25,0x0E,0x60,0x13,0x7A,0x18, + 0x63,0x14,0x23,0x09,0x63,0xFD,0x49,0xF7,0xEC,0xF1,0xFB,0xEF, + 0xC1,0xF4,0xC2,0xF9,0xDD,0xF8,0x9B,0xF4,0x5E,0xF6,0x39,0xFE, + 0x2C,0x06,0xCD,0x09,0xB1,0x0C,0x2E,0x0D,0xE4,0x08,0x1B,0x04, + 0x0A,0x03,0x1C,0x05,0xE3,0x02,0x6E,0xFC,0xC1,0xF6,0xB0,0xF4, + 0x89,0xF4,0xFC,0xF5,0x8B,0xF9,0xA5,0xFC,0x92,0xFD,0x00,0xFD, + 0x6F,0xFE,0xC0,0x01,0xBB,0x04,0xD7,0x05,0xFE,0x05,0x68,0x06, + 0x5F,0x03,0x3A,0x00,0x00,0x00,0xBB,0x00,0x4B,0x0F,0xCB,0x21, + 0x27,0x1D,0xC5,0x07,0x34,0xF4,0x89,0xEB,0x7B,0xEB,0xE0,0xEB, + 0x31,0xED,0xD4,0xF0,0x55,0xE9,0xC9,0xDE,0x3C,0xE3,0xD1,0xF3, + 0x0F,0x08,0xC3,0x11,0xB5,0x13,0xC6,0x15,0x40,0x15,0xD7,0x12, + 0x53,0x16,0x17,0x1C,0xC0,0x18,0x93,0x09,0x97,0xF5,0xF9,0xED, + 0x63,0xEE,0x3D,0xEF,0x2E,0xF2,0xA5,0xF5,0x05,0xF7,0xF1,0xF4, + 0x7E,0xF6,0xBF,0x00,0xD3,0x0E,0xA0,0x13,0xED,0x10,0xE2,0x0C, + 0x27,0x09,0x6C,0x06,0xC3,0x02,0x93,0x01,0x6A,0xFF,0x53,0xF8, + 0xA5,0xEF,0x29,0xED,0xE0,0xF1,0xF1,0xF7,0x10,0xFB,0x38,0xFB, + 0xFC,0xFC,0xF7,0xFE,0x69,0x01,0xAB,0x05,0xB6,0x0A,0xF6,0x0C, + 0x28,0x09,0x43,0x03,0x73,0xFD,0x58,0xFB,0x47,0xFE,0xF6,0x0D, + 0xAA,0x23,0xAD,0x21,0xF8,0x07,0x93,0xED,0xCD,0xE6,0x92,0xED, + 0xDA,0xF2,0x5C,0xF3,0x48,0xF1,0x53,0xEA,0x49,0xDD,0x46,0xDF, + 0x3C,0xF3,0x53,0x0C,0x6F,0x17,0x7A,0x12,0x89,0x0D,0xFD,0x0D, + 0x6B,0x12,0x70,0x16,0xC5,0x1B,0xB1,0x17,0x5B,0x08,0x95,0xF3, + 0x53,0xE9,0x3B,0xEF,0x71,0xF6,0x32,0xF9,0xC0,0xF5,0x66,0xF4, + 0x7C,0xF4,0x76,0xF9,0x90,0x03,0xF2,0x0F,0x6B,0x16,0x9D,0x0F, + 0x44,0x07,0x12,0x03,0xF1,0x05,0x93,0x06,0x64,0x02,0xF5,0xFB, + 0x0B,0xF5,0xEF,0xEF,0xFC,0xED,0x76,0xF4,0x27,0xFC,0xC4,0xFF, + 0x35,0xFC,0xAC,0xF9,0xF8,0xFD,0x02,0x04,0x9A,0x08,0xE8,0x0A, + 0xBF,0x0B,0xB9,0x07,0x19,0xFF,0x19,0xF8,0x5C,0xFD,0xB6,0x11, + 0x97,0x27,0x58,0x23,0x85,0x07,0x24,0xED,0xDD,0xE4,0xEB,0xEB, + 0x0A,0xF2,0x89,0xF4,0x16,0xEF,0x73,0xE5,0xBE,0xDA,0x15,0xDF, + 0xDA,0xF6,0x12,0x0F,0xCF,0x19,0x19,0x13,0x9A,0x0C,0x23,0x0D, + 0x02,0x14,0xD0,0x19,0xA6,0x1B,0x52,0x14,0xF6,0x01,0xD1,0xEF, + 0xFD,0xE7,0x8F,0xEF,0x5C,0xF8,0x60,0xFA,0xFA,0xF4,0xCF,0xF1, + 0x13,0xF5,0x95,0xFD,0xDF,0x09,0xCF,0x12,0x8A,0x15,0xA0,0x0D, + 0x28,0x05,0xED,0x02,0x75,0x06,0xAF,0x07,0xE8,0x00,0xF7,0xF7, + 0x82,0xF0,0x3B,0xEF,0x53,0xF1,0x69,0xF7,0x68,0xFD,0x4B,0xFE, + 0x02,0xFB,0x26,0xF9,0x9F,0xFE,0xA9,0x06,0xB5,0x0D,0xB3,0x0E, + 0x18,0x0B,0xE3,0x00,0xAB,0xF8,0x93,0xFD,0x02,0x14,0x9B,0x2D, + 0xC1,0x25,0x2A,0x04,0x54,0xE4,0xDE,0xDF,0xE6,0xEB,0xC3,0xF3, + 0xC7,0xF4,0x30,0xEC,0x65,0xE3,0x3B,0xD9,0x51,0xE1,0xB0,0xFB, + 0x5C,0x17,0x72,0x1F,0x60,0x12,0x89,0x08,0x7D,0x0A,0xDD,0x17, + 0xB6,0x1C,0x5F,0x1A,0xCE,0x0D,0x33,0xFB,0xE5,0xEA,0x5A,0xE5, + 0x5B,0xF1,0x70,0xFC,0xDF,0xFE,0x52,0xF3,0xE6,0xED,0x3C,0xF5, + 0x11,0x04,0x40,0x11,0x34,0x14,0x91,0x12,0x97,0x09,0x27,0x03, + 0x33,0x01,0x9A,0x06,0xFF,0x08,0x9E,0x00,0x18,0xF4,0xD4,0xEB, + 0xB1,0xEF,0x2D,0xF6,0x04,0xFC,0x1D,0xFD,0x44,0xFB,0xE4,0xF8, + 0xF0,0xF8,0xC1,0x00,0x96,0x0B,0x7C,0x13,0x3A,0x0F,0x40,0x00, + 0xA1,0xF8,0x61,0x02,0xDF,0x1D,0x84,0x31,0xF3,0x21,0x89,0xFD, + 0xFA,0xDE,0xFC,0xDC,0xC7,0xE9,0x6F,0xF4,0x42,0xF4,0x91,0xE8, + 0x37,0xDD,0x8C,0xD7,0xE8,0xE7,0x4F,0x05,0x2A,0x1E,0xDB,0x1F, + 0x7D,0x11,0x40,0x07,0xE1,0x0B,0x19,0x1B,0xD1,0x1F,0x55,0x19, + 0xFC,0x04,0xF6,0xF1,0xEB,0xE5,0x43,0xE8,0x1C,0xF6,0x04,0xFF, + 0xA2,0xFD,0x0C,0xEF,0x88,0xEB,0x9B,0xF6,0xBF,0x0B,0x95,0x18, + 0x83,0x15,0x02,0x0D,0xC1,0x03,0xFE,0x02,0xDC,0x04,0xCD,0x09, + 0x85,0x07,0xBD,0xFC,0x9D,0xEF,0xD6,0xE9,0x6A,0xF1,0xC5,0xFA, + 0x65,0xFF,0x0F,0xFA,0x6A,0xF5,0xA1,0xF5,0xE0,0xFD,0x96,0x09, + 0xE1,0x11,0x06,0x10,0x70,0x03,0xC7,0xFD,0xBC,0x08,0x59,0x28, + 0x8A,0x34,0xE3,0x1B,0x1E,0xF2,0x12,0xD9,0xF8,0xE0,0x90,0xED, + 0x1F,0xF4,0xA9,0xEA,0x68,0xE1,0x23,0xDA,0x52,0xDB,0x29,0xF0, + 0xF3,0x0C,0x4C,0x23,0x17,0x1D,0x7E,0x0D,0x44,0x06,0x6D,0x14, + 0x70,0x23,0xD9,0x20,0x30,0x10,0x66,0xF9,0xBF,0xED,0x76,0xE7, + 0xBA,0xEE,0x52,0xF8,0xEC,0xFD,0x9E,0xF5,0x14,0xE8,0x2D,0xEC, + 0xB7,0xFE,0x1C,0x16,0xB1,0x18,0xBF,0x0F,0x5C,0x05,0x47,0x04, + 0x35,0x09,0x13,0x0B,0x30,0x0B,0x0C,0x02,0x16,0xF7,0xCB,0xEB, + 0xC5,0xEC,0x74,0xF6,0x81,0xFD,0x5F,0xFA,0xB9,0xF0,0x62,0xF1, + 0x3A,0xFB,0x81,0x0A,0x0B,0x0F,0xCE,0x09,0x16,0x05,0x24,0x09, + 0x7F,0x1F,0xDA,0x30,0x90,0x28,0x06,0x08,0x7C,0xE8,0x01,0xE1, + 0xB5,0xE8,0xD7,0xF0,0x54,0xEB,0xA1,0xDF,0xAC,0xD7,0x41,0xD9, + 0xA7,0xE8,0x57,0xFF,0xB8,0x14,0x26,0x1A,0x17,0x14,0xE7,0x0C, + 0x70,0x13,0x75,0x21,0x07,0x25,0x12,0x19,0xC4,0x01,0x13,0xF3, + 0x92,0xED,0x6B,0xF1,0x44,0xF5,0x15,0xF5,0xF0,0xF0,0x24,0xE9, + 0x9C,0xEB,0xF2,0xF7,0xCA,0x0A,0xF8,0x13,0xC3,0x10,0x7B,0x09, + 0x0F,0x07,0xF3,0x0C,0x59,0x10,0x17,0x0E,0xF1,0x03,0x6C,0xF9, + 0x4A,0xF2,0x1F,0xF1,0xF4,0xF4,0xAC,0xF7,0xE3,0xF5,0x6B,0xEF, + 0x8F,0xEF,0xC7,0xF8,0x30,0x07,0xB4,0x0B,0x68,0x07,0x31,0x05, + 0x84,0x0D,0x7D,0x26,0x8D,0x31,0xA6,0x22,0xAE,0x01,0x6D,0xEA, + 0x89,0xEB,0x11,0xF2,0x33,0xF3,0xAB,0xE5,0x70,0xDA,0x9C,0xD6, + 0x4A,0xDD,0xBE,0xED,0x31,0x00,0x1B,0x0F,0xB7,0x0F,0x90,0x0C, + 0x14,0x0D,0x05,0x1B,0x12,0x27,0xBB,0x23,0x73,0x12,0xB2,0xFE, + 0x9E,0xF8,0x21,0xF8,0xF7,0xF9,0x23,0xF5,0x6A,0xEF,0x1E,0xEA, + 0xBF,0xE7,0xDA,0xEE,0xA3,0xFA,0x63,0x08,0xB0,0x0A,0x29,0x07, + 0xCD,0x05,0x9E,0x0C,0x63,0x15,0xDA,0x13,0x32,0x0B,0x05,0x00, + 0xB0,0xFB,0x22,0xFA,0x07,0xF9,0x15,0xF7,0x0A,0xF4,0xAD,0xF0, + 0x28,0xED,0x35,0xF1,0x75,0xFB,0x8E,0x05,0x33,0x05,0x42,0x01, + 0xD7,0x03,0x20,0x16,0x5D,0x2D,0xD1,0x2B,0x90,0x13,0xEB,0xF6, + 0x28,0xF1,0x92,0xFA,0xB0,0xFD,0x2F,0xF2,0xEE,0xDE,0xE9,0xD7, + 0x0A,0xDB,0x9F,0xE7,0xDC,0xF4,0xEF,0xFF,0x97,0x04,0x78,0x03, + 0x71,0x06,0x5B,0x11,0x46,0x22,0x28,0x26,0x7A,0x1A,0x45,0x08, + 0x7F,0x00,0x23,0x04,0xFB,0x05,0x95,0xFE,0x3C,0xF0,0x47,0xE9, + 0xA1,0xE9,0xE1,0xEF,0xDF,0xF5,0x52,0xFA,0x99,0xFD,0x35,0xFE, + 0x87,0x01,0xE2,0x07,0x64,0x11,0x0F,0x15,0x7F,0x0F,0x95,0x06, + 0xD2,0x01,0xC9,0x03,0xC0,0x03,0x43,0xFE,0xAC,0xF5,0x45,0xF1, + 0x57,0xF2,0x28,0xF4,0x99,0xF5,0xCB,0xF7,0x94,0xFC,0x8A,0xFD, + 0x7C,0xFD,0x27,0x02,0x00,0x10,0xF6,0x22,0x53,0x24,0x33,0x13, + 0x99,0xFE,0xB0,0xFA,0xBE,0x04,0x7E,0x06,0x36,0xF9,0x75,0xE4, + 0xD2,0xDC,0x33,0xE3,0xB6,0xED,0x40,0xF3,0x16,0xF4,0x18,0xF6, + 0x6F,0xFA,0xFC,0x02,0x66,0x0D,0x68,0x18,0x35,0x1B,0xB1,0x14, + 0xB5,0x0B,0x06,0x0A,0x47,0x0F,0x21,0x0F,0xC6,0x04,0xC3,0xF4, + 0x9F,0xED,0x66,0xF0,0x2C,0xF6,0xAE,0xF7,0x0B,0xF4,0xF7,0xF2, + 0xEF,0xF5,0x70,0xFD,0xEA,0x04,0x1A,0x0A,0xB5,0x0B,0x33,0x09, + 0xB3,0x07,0x8D,0x08,0x31,0x0B,0xE8,0x09,0xA0,0x02,0xD3,0xFA, + 0xA3,0xF7,0x04,0xFA,0xA0,0xFB,0x78,0xF9,0xD0,0xF5,0x64,0xF5, + 0xD2,0xF9,0x16,0xFF,0x73,0x02,0x82,0x02,0x71,0x01,0xC8,0x01, + 0x72,0x04,0xB7,0x07,0xB3,0x07,0x59,0x03,0xBA,0xFC,0xAB,0x00, + 0x24,0x0E,0x82,0x12,0x04,0x06,0x41,0xF3,0xB3,0xF0,0x3E,0xFD, + 0x25,0x06,0x30,0xFE,0x5D,0xEE,0x1E,0xEB,0x34,0xF4,0x78,0xFE, + 0xBE,0xFF,0x35,0xFC,0xE1,0xFB,0x5E,0xFF,0xC2,0x03,0xE7,0x07, + 0x8A,0x0C,0xA9,0x0D,0x17,0x09,0xE6,0x02,0xD3,0x02,0x86,0x08, + 0x74,0x0A,0x62,0x02,0x34,0xF8,0x90,0xF6,0xA0,0xFC,0x8F,0x00, + 0x1A,0xFC,0xDB,0xF5,0xED,0xF5,0x05,0xFC,0xF2,0x00,0x38,0x01, + 0x55,0x00,0x3F,0x01,0x98,0x03,0xEC,0x04,0x01,0x05,0x40,0x05, + 0x79,0x04,0x18,0x02,0x9D,0xFF,0x68,0xFF,0x0D,0x01,0x87,0x00, + 0x1F,0xFD,0x3B,0xFA,0x9F,0xFB,0x3D,0xFF,0x4A,0x00,0x1C,0xFE, + 0xF3,0xFB,0xCE,0xFD,0x87,0x01,0x2D,0x03,0xCF,0x01,0xA4,0xFF, + 0xE2,0x00,0x16,0x04,0x8C,0x05,0x4D,0x03,0x9E,0xFF,0xD3,0xFE, + 0xD7,0xFF,0x47,0x00,0x37,0xFE,0x8A,0xFB,0xAB,0xFA,0xBF,0xFA, + 0x86,0xFB,0x1A,0xFC,0xF9,0xFC,0x2F,0xFE,0xC2,0xFE,0x71,0xFF, + 0xE2,0x00,0x22,0x03,0x55,0x04,0xB9,0x03,0xD6,0x02,0xFD,0x02, + 0x0D,0x04,0xB8,0x03,0x84,0x01,0x0B,0xFF,0x1E,0xFE,0x8A,0xFE, + 0xBE,0xFE,0x4B,0xFD,0xFB,0xFB,0x62,0xFC,0x13,0xFE,0x95,0x00, + 0x3F,0x02,0xB2,0x07,0xF2,0x0D,0x29,0x0E,0xA0,0x07,0xF5,0x00, + 0xEE,0x02,0x83,0x07,0xB3,0x05,0x8B,0xFA,0xAB,0xF0,0x80,0xF1, + 0x5D,0xF7,0xD4,0xF8,0x9D,0xF2,0xE2,0xED,0x1A,0xF2,0x05,0xFB, + 0xBF,0x00,0xA2,0x00,0x60,0x00,0x3A,0x04,0xC0,0x09,0x72,0x0D, + 0x54,0x0D,0x1A,0x0C,0xFB,0x09,0x93,0x07,0xDE,0x05,0x4C,0x04, + 0x95,0x02,0xEB,0xFD,0xDC,0xF8,0xC6,0xF6,0x9E,0xF8,0xF0,0xFA, + 0x76,0xF9,0x06,0xF7,0xA5,0xF7,0xFC,0xFC,0x36,0x02,0xDE,0x02, + 0x4F,0x01,0x45,0x01,0xF8,0x04,0x4B,0x08,0xDF,0x07,0x02,0x05, + 0x8F,0x02,0xC0,0x02,0xFD,0x02,0xCE,0x01,0x40,0xFF,0xCA,0xFC, + 0x03,0xFC,0xB9,0xFB,0xF9,0xFB,0xDE,0xFB,0x89,0xFB,0x87,0xFB, + 0xF4,0xFB,0xBC,0xFD,0x77,0xFF,0x67,0x00,0x59,0x00,0xAB,0x00, + 0x1D,0x02,0xE4,0x03,0xCF,0x04,0x6D,0x03,0xD3,0x01,0xE8,0x01, + 0x08,0x03,0xCF,0x02,0x5E,0x00,0x06,0xFE,0xBD,0xFD,0xF8,0xFE, + 0x05,0xFF,0x3A,0xFD,0xC4,0xFB,0x59,0xFC,0x7F,0xFE,0xAA,0xFF, + 0x2C,0xFF,0xB0,0xFE,0x96,0xFF,0x92,0x01,0xAA,0x02,0x5B,0x02, + 0xA6,0x01,0xBF,0x01,0x71,0x02,0x8E,0x02,0xDE,0x01,0xDC,0x00, + 0x40,0x00,0x12,0x00,0x04,0x00,0xB0,0xFF,0x5D,0xFF,0x53,0xFF, + 0xF7,0xFF,0xA6,0x00,0x3C,0x00,0xA4,0xFF,0x59,0xFF,0x37,0x00, + 0x08,0x01,0x3C,0x00,0xC2,0xFE,0xC3,0xFD,0x7A,0xFE,0x22,0xFF, + 0x95,0xFE,0x37,0xFD,0x65,0xFC,0x77,0xFD,0xAC,0xFE,0x18,0xFF, + 0x89,0xFE,0x5F,0xFE,0xCA,0xFF,0x40,0x01,0xF9,0x01,0xC1,0x01, + 0xB0,0x01,0x61,0x02,0xFE,0x02,0x28,0x03,0x84,0x02,0xE4,0x01, + 0x94,0x01,0x64,0x01,0x2F,0x01,0x7A,0x00,0xC6,0xFF,0x40,0xFF, + 0x1F,0xFF,0x47,0xFF,0x1A,0xFF,0xB9,0xFE,0x66,0xFE,0x90,0xFE, + 0x28,0xFF,0x7B,0xFF,0x53,0xFF,0x1D,0xFF,0x67,0xFF,0x17,0x00, + 0x74,0x00,0x43,0x00,0xF6,0xFF,0x17,0x00,0x99,0x00,0xCB,0x00, + 0x69,0x00,0xE3,0xFF,0xEB,0xFF,0x2A,0x00,0x3D,0x00,0xFA,0xFF, + 0x79,0xFF,0x7F,0xFF,0xC4,0xFF,0xB6,0xFF,0x7B,0xFF,0x72,0xFF, + 0x9D,0xFF,0xA3,0xFF,0x84,0xFF,0x8E,0xFF,0xE5,0xFF,0x1D,0x00, + 0xF2,0xFF,0xD9,0xFF,0x01,0x00,0x65,0x00,0x8C,0x00,0x3F,0x00, + 0x17,0x00,0x68,0x00,0xD1,0x00,0xC1,0x00,0x7A,0x00,0x19,0x00, + 0x66,0x00,0xC6,0x00,0x3A,0x00,0xED,0xFF,0x04,0x00,0x2A,0x00, + 0x0A,0x00,0xBA,0xFF,0xBD,0xFF,0xF1,0xFF,0xEC,0xFF,0xD9,0xFF, + 0x3D,0x00,0xA3,0x00,0xAE,0x00,0x4C,0x00,0xF5,0xFF,0x6E,0x00, + 0x02,0x01,0xC7,0x00,0xE2,0xFF,0x5D,0xFF,0xBC,0xFF,0x20,0x00, + 0xA5,0xFF,0xA2,0xFE,0x3D,0xFE,0xC6,0xFE,0x54,0xFF,0x22,0xFF, + 0xA3,0xFE,0xB5,0xFE,0x6C,0xFF,0xFA,0xFF,0xEF,0xFF,0xB9,0xFF, + 0xE0,0xFF,0x69,0x00,0xBF,0x00,0x9A,0x00,0x6C,0x00,0x82,0x00, + 0xB4,0x00,0xCE,0x00,0xB6,0x00,0x97,0x00,0x9A,0x00,0xA9,0x00, + 0xA8,0x00,0x99,0x00,0x8E,0x00,0x7C,0x00,0x5D,0x00,0x49,0x00, + 0x3E,0x00,0x31,0x00,0x05,0x00,0xD2,0xFF,0xC0,0xFF,0xBA,0xFF, + 0xB4,0xFF,0x8F,0xFF,0x62,0xFF,0x64,0xFF,0x78,0xFF,0x83,0xFF, + 0x71,0xFF,0x59,0xFF,0x6C,0xFF,0x97,0xFF,0xAF,0xFF,0xA8,0xFF, + 0x9D,0xFF,0xB5,0xFF,0xE1,0xFF,0xF7,0xFF,0xF7,0xFF,0xF7,0xFF, + 0x04,0x00,0x1C,0x00,0x34,0x00,0x3B,0x00,0x35,0x00,0x32,0x00, + 0x42,0x00,0x56,0x00,0x50,0x00,0x45,0x00,0x42,0x00,0x43,0x00, + 0x4B,0x00,0x4C,0x00,0x3B,0x00,0x31,0x00,0x34,0x00,0x36,0x00, + 0x2E,0x00,0x1D,0x00,0x16,0x00,0x14,0x00,0x12,0x00,0x08,0x00, + 0xFC,0xFF,0xF9,0xFF,0xF4,0xFF,0xF2,0xFF,0xF0,0xFF,0xEA,0xFF, + 0xE6,0xFF,0xE8,0xFF,0xE8,0xFF,0xE9,0xFF,0xEA,0xFF,0xE7,0xFF, + 0xE4,0xFF,0xE4,0xFF,0xED,0xFF,0xF3,0xFF,0xEC,0xFF,0xE5,0xFF, + 0xEF,0xFF,0xFA,0xFF,0xFC,0xFF,0xFB,0xFF,0xF8,0xFF,0xFF,0xFF, + 0x07,0x00,0x09,0x00,0x06,0x00,0x00,0x00,0x02,0x00,0x09,0x00, + 0x0C,0x00,0x04,0x00,0xFA,0xFF,0x01,0x00,0x06,0x00,0xFF,0xFF, + 0xFA,0xFF,0xF8,0xFF,0xFC,0xFF,0xFB,0xFF,0xFA,0xFF,0xF8,0xFF, + 0xF2,0xFF,0xF2,0xFF,0xF5,0xFF,0xF7,0xFF,0xF3,0xFF,0xEE,0xFF, + 0xF1,0xFF,0xF9,0xFF,0xF7,0xFF,0xF2,0xFF,0xF5,0xFF,0xF8,0xFF, + 0xFC,0xFF,0xFC,0xFF,0xFA,0xFF,0xFC,0xFF,0x02,0x00,0x03,0x00, + 0x03,0x00,0xFF,0xFF,0x02,0x00,0x06,0x00,0x07,0x00,0x0C,0x00, + 0x09,0x00,0x09,0x00,0x0B,0x00,0x0C,0x00,0x0F,0x00,0x0C,0x00, + 0x09,0x00,0x0F,0x00,0x0A,0x00,0x08,0x00,0x12,0x00,0x0A,0x00, + 0x04,0x00,0x0A,0x00,0x0B,0x00,0x09,0x00,0x07,0x00,0x04,0x00, + 0x08,0x00,0x05,0x00,0x02,0x00,0x04,0x00,0x01,0x00,0xFE,0xFF, + 0xFE,0xFF,0xFD,0xFF,0x00,0x00,0xFA,0xFF,0xFA,0xFF,0x00,0x00, + 0xFA,0xFF,0xF9,0xFF,0xFC,0xFF,0xFF,0xFF,0x00,0x00,0xFD,0xFF, + 0x00,0x00,0x00,0x00,0x04,0x00,0x05,0x00,0x02,0x00,0x06,0x00, + 0x00,0x00,0x02,0x00,0x08,0x00,0x01,0x00,0x01,0x00,0x04,0x00, + 0x02,0x00,0x03,0x00,0xFE,0xFF,0xFC,0xFF,0x01,0x00,0xFA,0xFF, + 0xFC,0xFF,0xFE,0xFF,0xF9,0xFF,0xFC,0xFF,0xFB,0xFF,0xFA,0xFF, + 0xFB,0xFF,0xFA,0xFF,0xFA,0xFF,0xFA,0xFF,0xFA,0xFF,0xFC,0xFF, + 0xFC,0xFF,0xFE,0xFF,0xFC,0xFF,0xFA,0xFF,0x00,0x00,0xFF,0xFF, + 0xFC,0xFF,0xFB,0xFF,0xFD,0xFF,0x00,0x00,0xFD,0xFF,0xFB,0xFF, + 0xFF,0xFF,0x02,0x00,0xFF,0xFF,0xFD,0xFF,0x01,0x00,0x02,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0xFF,0xFF,0xFF,0xFF, + 0x04,0x00,0x00,0x00,0xFF,0xFF,0x02,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00,0x04,0x00, + 0x01,0x00,0xFF,0xFF,0x04,0x00,0x03,0x00,0x03,0x00,0x05,0x00, + 0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x04,0x00,0x03,0x00, + 0x02,0x00,0x00,0x00,0x03,0x00,0x06,0x00,0x02,0x00,0x00,0x00, + 0x02,0x00,0x03,0x00,0x06,0x00,0x05,0x00,0x02,0x00,0x02,0x00, + 0x03,0x00,0x05,0x00,0x03,0x00,0x02,0x00,0xFF,0xFF,0x00,0x00, + 0x05,0x00,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0xFE,0xFF,0x02,0x00, + 0xFF,0xFF,0xFD,0xFF,0x00,0x00,0xFD,0xFF,0xFD,0xFF,0xFC,0xFF, + 0xFE,0xFF,0xFD,0xFF,0xFB,0xFF,0xFC,0xFF,0xFE,0xFF,0xFF,0xFF, + 0xFD,0xFF,0xFD,0xFF,0x01,0x00,0x01,0x00,0xFD,0xFF,0xFE,0xFF, + 0x03,0x00,0x02,0x00,0xFD,0xFF,0x02,0x00,0x00,0x00,0xFF,0xFF, + 0x03,0x00,0xFD,0xFF,0xFD,0xFF,0xFF,0xFF,0xFE,0xFF,0x05,0x00, + 0x01,0x00,0xF4,0xFF,0x00,0x00,0x03,0x00,0xF9,0xFF,0x00,0x00, + 0xFD,0xFF,0x00,0x00,0x03,0x00,0xFB,0xFF,0x00,0x00,0x00,0x00, + 0xF9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFD,0xFF,0x01,0x00,0xFC,0xFF, + 0xFE,0xFF,0x01,0x00,0xFE,0xFF,0x00,0x00,0x01,0x00,0x01,0x00, + 0x02,0x00,0x03,0x00,0x03,0x00,0x04,0x00,0x04,0x00,0x04,0x00, + 0x03,0x00,0x02,0x00,0x01,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00, + 0xFE,0xFF,0xFE,0xFF,0xFC,0xFF,0xFD,0xFF,0xFE,0xFF,0xFF,0xFF, + 0x00,0x00,0x01,0x00,0x03,0x00,0x01,0x00,0x01,0x00,0x02,0x00, + 0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00, + 0x01,0x00,0x00,0x00,0x02,0x00,0x02,0x00,0x01,0x00,0x00,0x00, + 0xFF,0xFF,0x01,0x00,0xFF,0xFF,0xFD,0xFF,0xFF,0xFF,0x00,0x00, + 0xFF,0xFF,0xFE,0xFF,0x01,0x00,0x02,0x00,0x01,0x00,0x01,0x00, + 0x02,0x00,0x02,0x00,0x00,0x00,0x02,0x00,0x02,0x00,0x03,0x00, + 0x01,0x00,0x01,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFE,0xFF,0xFF,0xFF,0xFE,0xFF,0xFE,0xFF,0xFF,0xFF,0xFC,0xFF, + 0xFC,0xFF,0x03,0x00,0xFA,0xFF,0x04,0x00,0xFD,0xFF,0xFE,0xFF, + 0x01,0x00,0x00,0x00,0x01,0x00,0xFA,0xFF,0x0D,0x00,0xED,0xFF, + 0x14,0x00,0xE8,0xFF,0x0D,0x00,0xF0,0xFF,0x05,0x00,0xFD,0xFF, + 0x1F,0x00,0xD4,0xFF,0x14,0x00,0x06,0x00,0x0F,0x00,0x18,0x00, + 0x89,0xFF,0xEE,0x00,0x38,0xFD,0xBC,0x07,0x80,0x00,0xC5,0xF4, + 0x1A,0x06,0x36,0xFD,0x41,0xFD,0xA9,0x05,0x43,0xFB,0x24,0x02, + 0x90,0x02,0xDC,0xFB,0xD9,0x03,0x12,0xFF,0x16,0xFF,0xD9,0x03, + 0xE2,0xFC,0xA7,0x01,0x02,0x01,0x17,0xFE,0x6B,0x01,0x43,0xFF, + 0xF9,0xFE,0x98,0x01,0xE5,0xFE,0xC6,0xFE,0x67,0x01,0x4B,0xFE, + 0x79,0x00,0xD7,0x00,0x0C,0xFE,0x93,0x00,0x4B,0x01,0x0D,0xFF, + 0x1B,0x00,0xC1,0x00,0x04,0xFF,0x63,0x01,0x5B,0xFF,0x72,0x00, + 0xE1,0xFF,0x67,0xFF,0x3B,0x01,0x31,0xFD,0xB1,0x06,0xE5,0xFE, + 0x5B,0xFA,0x28,0x04,0xF1,0xFB,0x42,0xFE,0xD8,0x02,0xFE,0xFC, + 0x34,0x02,0xCD,0x01,0xC9,0xFC,0xCC,0x03,0x3F,0xFD,0x5E,0x04, + 0x13,0x03,0x1E,0xF9,0x66,0x04,0x30,0xFD,0xF5,0xFD,0xF8,0x04, + 0x7F,0xF9,0x7A,0x02,0x70,0x05,0x97,0xF7,0x9F,0x06,0x09,0xFE, + 0xA6,0xFB,0x25,0x09,0x6B,0xF9,0xE8,0xFF,0x0B,0x05,0x3A,0xFB, + 0x13,0x01,0x43,0x03,0x51,0xFA,0xFE,0x01,0x7F,0x02,0xA7,0xFA, + 0x7C,0x04,0xFD,0xFC,0xB5,0xFF,0x53,0x03,0xA2,0xFA,0x15,0x05, + 0xB3,0xFC,0xD0,0x01,0xBA,0x02,0xB3,0xF8,0x9D,0x08,0xF6,0xFC, + 0xF2,0xFA,0xF9,0x09,0xAA,0xF7,0x93,0x02,0x14,0x05,0x50,0xF6, + 0xAA,0x08,0x1B,0xFB,0x82,0x00,0x68,0x03,0x5F,0xFA,0x69,0x03, + 0xC1,0xFF,0xF6,0xFF,0x7C,0xFE,0xB6,0x01,0xA1,0xFF,0x05,0xFF, + 0x20,0x01,0xC6,0x00,0x46,0xFC,0xB4,0x03,0x25,0x00,0xDF,0xFB, + 0xAA,0x04,0x2C,0xFD,0xAF,0x01,0xAE,0xFE,0xBB,0x01,0x30,0xFE, + 0x14,0x00,0x14,0x04,0xAE,0xF8,0x40,0x07,0xB6,0xFB,0x4A,0xFF, + 0x6D,0x05,0x3A,0xF9,0xA9,0x03,0x09,0x01,0x8A,0xFC,0x84,0x01, + 0x4B,0x02,0xCA,0xFC,0x3C,0x00,0xCC,0x03,0x67,0xFC,0xE9,0xFD, + 0x45,0x08,0xF3,0xF7,0x0F,0xFF,0x28,0x0B,0xC9,0xF3,0x7B,0x03, + 0x82,0x07,0x0E,0xF1,0xE6,0x0D,0x63,0xFD,0xE0,0xF3,0x79,0x13, + 0xAD,0xEF,0xDD,0x05,0xF4,0x05,0x1E,0xF4,0xEB,0x0A,0xD6,0xF9, + 0xCE,0x01,0x6E,0x00,0x9A,0xFD,0x48,0x03,0x92,0xFD,0x12,0x01, + 0x39,0x02,0x04,0xF8,0xCF,0x07,0x87,0x04,0xDF,0xED,0xD4,0x12, + 0xCE,0xFB,0x4A,0xF1,0xDA,0x15,0x89,0xF3,0xFB,0xFC,0x66,0x0C, + 0x45,0xF4,0x80,0x04,0xBD,0x03,0x94,0xF7,0xFE,0x07,0xB1,0xFC, + 0x63,0xFC,0x82,0x07,0x8C,0xFA,0x1E,0x00,0x11,0x03,0xC2,0xFD, + 0x19,0x01,0x7F,0xFE,0xC6,0x01,0x65,0x00,0xE0,0xFB,0x7F,0x07, + 0x1D,0xF8,0xC7,0x01,0x11,0x09,0x92,0xF0,0xA9,0x09,0x4B,0x02, + 0xAB,0xF5,0xAC,0x08,0xB9,0xFE,0xF4,0xFC,0x7D,0x03,0x23,0xFC, + 0x5B,0x05,0x1B,0xFD,0xDF,0xFC,0x7C,0x08,0x28,0xF6,0x7B,0x06, + 0xB0,0x01,0x57,0xF5,0xF9,0x0A,0x2A,0xFF,0x0A,0xF5,0x52,0x0D, + 0xE5,0xFA,0xC9,0xF9,0x8F,0x09,0x5F,0xFB,0xDC,0x00,0x82,0xFE, + 0x55,0x01,0xD3,0x01,0xB7,0xFD,0x48,0xFF,0xAC,0x03,0x37,0xFB, + 0xE7,0x02,0x57,0x00,0x3B,0xFC,0xAB,0x07,0x0C,0xF8,0x58,0x04, + 0x06,0x00,0xB4,0xFA,0xA8,0x09,0x51,0xF7,0xEC,0xFF,0xAC,0x09, + 0xAE,0xF5,0x27,0x02,0x05,0x04,0x7A,0xFD,0x40,0x00,0x90,0xFB, + 0x51,0x0B,0x9B,0xF5,0x6A,0xFF,0x4B,0x0E,0xBD,0xEB,0x37,0x0D, + 0x6F,0x02,0xD1,0xF1,0x36,0x10,0x15,0xF6,0x2B,0xFE,0xFB,0x0B, + 0x6A,0xF1,0x76,0x08,0x1D,0x02,0x21,0xF5,0x7A,0x0A,0x7F,0xFE, + 0x06,0xFA,0xC9,0x02,0x16,0x07,0xB2,0xF4,0xF7,0x04,0x51,0x06, + 0x10,0xF3,0xF4,0x08,0xCA,0x02,0xBA,0xF5,0x7C,0x05,0x37,0x06, + 0x78,0xF4,0x1B,0x07,0x7B,0x01,0x90,0xF8,0xB0,0x07,0x1A,0xFE, + 0x51,0xFB,0xF1,0x02,0xFD,0x05,0x1B,0xF4,0xE7,0x06,0xA9,0x03, + 0x18,0xF4,0xD8,0x0C,0x74,0xFA,0x5A,0xFB,0x5B,0x07,0xC7,0x00, + 0x12,0xF7,0x0A,0x07,0xAE,0x03,0xAA,0xF0,0x20,0x13,0x1B,0xF9, + 0x97,0xF2,0x7D,0x15,0xCE,0xF8,0x9E,0xF3,0x9B,0x0E,0xE6,0xFF, + 0x10,0xF3,0x6F,0x0B,0xAF,0x00,0xD8,0xF2,0xC9,0x0C,0x47,0x01, + 0x5C,0xF2,0x8B,0x0D,0x80,0xFC,0x8A,0xF8,0xE7,0x06,0x25,0x05, + 0x24,0xF5,0xDB,0xFF,0xD3,0x0F,0xE3,0xEC,0x56,0x0A,0xDA,0x03, + 0x1A,0xF3,0x35,0x08,0xE8,0x00,0xC2,0xFF,0xC2,0xFB,0x16,0x00, + 0x0A,0x07,0x47,0xFB,0x43,0xFA,0xE9,0x07,0x2D,0x00,0xDE,0xF9, + 0x17,0x00,0x3F,0x0A,0x45,0xF6,0x40,0xFE,0xE0,0x0E,0x66,0xEF, + 0x8A,0x03,0x6C,0x0D,0xD8,0xEE,0xAD,0x05,0x0D,0x08,0xE0,0xF5, + 0xB8,0x00,0x1D,0x06,0xB8,0xFB,0xF3,0xFA,0x38,0x0E,0x7D,0xF3, + 0x76,0xFE,0x6C,0x0F,0xBD,0xED,0xD1,0x0A,0x99,0x03,0x99,0xEF, + 0xBA,0x0E,0x96,0x01,0x56,0xF3,0x6C,0x03,0x20,0x0D,0x1E,0xF4, + 0x2F,0xFA,0x51,0x0F,0x60,0xF5,0xB7,0x02,0x88,0x04,0x28,0xF7, + 0x82,0x03,0xE1,0x06,0x66,0xF9,0x10,0xFC,0x50,0x0B,0xA7,0xF7, + 0xC1,0x00,0xF2,0x02,0x75,0xFB,0x77,0x05,0xF1,0xFA,0xB0,0x03, + 0x6F,0xFE,0x6E,0xFC,0xA2,0x0A,0x9F,0xF5,0xEF,0x01,0x38,0x05, + 0x98,0xF6,0x0D,0x0E,0xC2,0xF5,0x04,0xF9,0xF4,0x13,0x08,0xF7, + 0x0F,0xF6,0x68,0x07,0xB0,0x08,0x28,0xF5,0x44,0xFB,0xB5,0x10, + 0x94,0xF2,0x4F,0xFE,0xDC,0x0D,0xFF,0xF2,0xC5,0x02,0xD0,0x08, + 0x96,0xF9,0xF4,0xF6,0xC4,0x0F,0x24,0xFE,0x31,0xF2,0x6B,0x0F, + 0x6A,0xF9,0x16,0xFA,0xB1,0x08,0x68,0x06,0x44,0xEE,0x53,0x04, + 0xC4,0x0F,0xDC,0xED,0x19,0x06,0xD4,0x04,0x73,0xF6,0x3F,0x02, + 0x90,0x0A,0x35,0xF9,0x84,0xF5,0x26,0x0E,0xB0,0x01,0x0F,0xF2, + 0xB9,0x07,0xE6,0x03,0x31,0xF6,0x90,0x09,0x1E,0x00,0xE2,0xF6, + 0xE5,0x04,0xFE,0x04,0xB8,0xFB,0xED,0xFD,0xD1,0x02,0xB8,0xFE, + 0xE6,0xFF,0x01,0xFF,0x04,0x03,0xB0,0xFC,0x86,0xFE,0x66,0x07, + 0x5B,0xFD,0x6D,0xF9,0x1B,0x04,0x50,0x08,0xE8,0xF8,0x37,0xFC, + 0x53,0x02,0x37,0x04,0x34,0xFF,0x18,0xFC,0x09,0x02,0xE3,0xFD, + 0xA4,0x00,0x9C,0x01,0xBD,0x02,0x59,0xF6,0xDE,0x06,0xA6,0x07, + 0x5E,0xEF,0x35,0x05,0xFC,0x09,0x15,0xF9,0xD8,0xFB,0x94,0x07, + 0xDD,0xFE,0xD9,0xFA,0x70,0x01,0x3D,0x03,0xBE,0x01,0x5D,0xFC, + 0x1B,0xFE,0x6D,0x01,0x8E,0x03,0x6E,0xFF,0x65,0xF9,0x15,0x02, + 0xC9,0x09,0xB8,0xF7,0x02,0xF7,0x2B,0x12,0xF4,0xFB,0x49,0xF0, + 0x3E,0x0C,0x67,0x05,0x53,0xF9,0x60,0xFC,0x18,0x06,0x26,0xFE, + 0x22,0xFC,0xA6,0x08,0x0F,0xFB,0xB8,0xFA,0x71,0x07,0x3F,0x01, + 0x2A,0xF9,0x85,0xFF,0x56,0x0A,0x0A,0xFB,0x1F,0xF7,0x62,0x09, + 0x77,0x01,0x92,0xFA,0x5A,0x03,0xF5,0xFE,0xC3,0xFC,0xF4,0x05, + 0xF5,0xFD,0x8C,0xFC,0x0C,0x04,0x84,0xFF,0xDF,0xFD,0x27,0x00, + 0x20,0x00,0xB0,0x00,0xEB,0x00,0x00,0xFF,0xC9,0xFF,0x6D,0xFF, + 0x69,0x03,0xC8,0xFC,0x08,0xFC,0x7E,0x06,0x49,0x01,0xD1,0xFB, + 0xC5,0xFD,0x1E,0x06,0xFB,0xFE,0x06,0xF9,0x56,0x05,0x1B,0x05, + 0x11,0xF9,0x7F,0xFD,0x4D,0x06,0x7D,0xFF,0x5C,0xFD,0x47,0x01, + 0xE6,0x00,0x58,0xFE,0xF0,0x00,0x61,0xFF,0x78,0xFE,0x05,0x04, + 0xAB,0xFF,0x63,0xFA,0xB4,0x02,0xB6,0x03,0x99,0xFC,0x3A,0xFE, + 0xCF,0x02,0x99,0x02,0x54,0xFB,0xB0,0xFF,0x35,0x04,0x15,0xFE, + 0x0A,0xFF,0x35,0x01,0x64,0xFF,0x5E,0x00,0x78,0x00,0xDD,0xFE, + 0x8F,0xFF,0x2B,0x01,0xE9,0x00,0xEF,0xFE,0x43,0xFF,0x59,0x01, + 0x3A,0x00,0x44,0xFE,0x6D,0x00,0x35,0xFF,0x31,0x01,0x9A,0x01, + 0xAC,0xFD,0x93,0xFF,0x65,0x01,0xDA,0x00,0x74,0xFE,0xD0,0xFF, + 0x98,0x01,0x4B,0xFF,0xB4,0xFE,0x0A,0x01,0x96,0x00,0x91,0xFE, + 0x3F,0x00,0x94,0x00,0xDA,0xFF,0x16,0x00,0x34,0xFF,0x18,0x00, + 0x14,0x01,0x54,0x00,0xA5,0xFE,0x8D,0xFF,0xFB,0x01,0x8A,0xFF, + 0xAE,0xFE,0xC9,0x00,0xBC,0x00,0x60,0xFF,0x78,0xFF,0x2E,0x01, + 0x88,0xFF,0x91,0xFF,0x07,0x01,0x5A,0xFF,0x99,0xFF,0x87,0x00, + 0x5D,0x00,0x5F,0xFF,0xC5,0xFF,0x66,0x00,0xB0,0xFF,0xC1,0xFF, + 0x68,0x00,0xC3,0xFF,0x5C,0xFF,0xA1,0x00,0x52,0x00,0x81,0xFF, + 0xB5,0xFF,0xC8,0x00,0x30,0x00,0x51,0xFF,0x57,0x00,0x9C,0x00, + 0xAB,0xFF,0x93,0xFF,0x98,0x00,0x85,0x00,0x8B,0xFF,0xAB,0xFF, + 0x1C,0x00,0x14,0x00,0xF5,0xFF,0xCC,0xFF,0xE4,0xFF,0x04,0x00, + 0x31,0x00,0xA7,0xFF,0x03,0x00,0x1C,0x00,0xFB,0xFF,0x07,0x00, + 0xC2,0xFF,0x37,0x00,0x53,0x00,0xBE,0xFF,0xC7,0xFF,0x59,0x00, + 0x13,0x00,0xC7,0xFF,0xF8,0xFF,0x42,0x00,0xF4,0xFF,0xD5,0xFF, + 0xFE,0xFF,0x28,0x00,0xD6,0xFF,0x09,0x00,0x13,0x00,0xF5,0xFF, + 0x05,0x00,0xD4,0xFF,0x1C,0x00,0x0D,0x00,0xF5,0xFF,0xF7,0xFF, + 0x0B,0x00,0x0B,0x00,0x08,0x00,0xEA,0xFF,0x06,0x00,0x20,0x00, + 0x02,0x00,0xCF,0xFF,0x16,0x00,0x27,0x00,0xDE,0xFF,0xE8,0xFF, + 0x21,0x00,0x12,0x00,0xD4,0xFF,0x05,0x00,0x16,0x00,0x03,0x00, + 0xD1,0xFF,0x14,0x00,0x17,0x00,0xDF,0xFF,0xFE,0xFF,0x0B,0x00, + 0xFC,0xFF,0xF7,0xFF,0x0B,0x00,0xFC,0xFF,0xF6,0xFF,0x08,0x00, + 0x0D,0x00,0xFE,0xFF,0xFD,0xFF,0x02,0x00,0x09,0x00,0x03,0x00, + 0xFC,0xFF,0xFF,0xFF,0x01,0x00,0x0A,0x00,0xFB,0xFF,0xE8,0xFF, + 0x08,0x00,0x10,0x00,0xF4,0xFF,0xF0,0xFF,0x06,0x00,0x0B,0x00, + 0xF6,0xFF,0xF5,0xFF,0x09,0x00,0x0F,0x00,0xF2,0xFF,0xF9,0xFF, + 0x0D,0x00,0x03,0x00,0xF5,0xFF,0xFD,0xFF,0x0A,0x00,0x04,0x00, + 0xFA,0xFF,0x00,0x00,0x06,0x00,0x00,0x00,0x02,0x00,0xFE,0xFF, + 0xFE,0xFF,0x02,0x00,0x03,0x00,0xFD,0xFF,0xFF,0xFF,0x04,0x00, + 0xFC,0xFF,0xFD,0xFF,0x03,0x00,0x01,0x00,0x00,0x00,0x01,0x00, + 0x03,0x00,0xFA,0xFF,0xFF,0xFF,0x0A,0x00,0x03,0x00,0xF7,0xFF, + 0x05,0x00,0x17,0x00,0xF9,0xFF,0xF5,0xFF,0x01,0x00,0x08,0x00, + 0x02,0x00,0xF5,0xFF,0x02,0x00,0x0A,0x00,0xFE,0xFF,0xF4,0xFF, + 0x00,0x00,0x08,0x00,0xFD,0xFF,0xF8,0xFF,0x04,0x00,0x08,0x00, + 0xFC,0xFF,0xF8,0xFF,0x08,0x00,0x06,0x00,0xFC,0xFF,0xFE,0xFF, + 0x03,0x00,0x02,0x00,0xFE,0xFF,0xFE,0xFF,0x03,0x00,0x03,0x00, + 0xFD,0xFF,0xFD,0xFF,0x03,0x00,0x02,0x00,0xFC,0xFF,0xFE,0xFF, + 0x03,0x00,0x01,0x00,0xFD,0xFF,0xFF,0xFF,0x00,0x00,0xFE,0xFF, + 0xFE,0xFF,0xFE,0xFF,0xFE,0xFF,0x00,0x00,0xFF,0xFF,0xFD,0xFF, + 0x02,0x00,0xFF,0xFF,0xFD,0xFF,0xFE,0xFF,0x01,0x00,0x00,0x00, + 0xFC,0xFF,0x00,0x00,0x01,0x00,0xFE,0xFF,0xFF,0xFF,0x02,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0xFF,0xFF,0x01,0x00,0x04,0x00, + 0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x02,0x00,0x00,0x00, + 0x01,0x00,0x01,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x03,0x00, + 0xFE,0xFF,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0xFF,0xFF,0x02,0x00,0x01,0x00,0xFF,0xFF,0xFF,0xFF, + 0x02,0x00,0x02,0x00,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0xFF,0xFF, + 0x01,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0xFF,0xFF,0x02,0x00,0x01,0x00,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0xFE,0xFF,0xFE,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0x01,0x00,0xFF,0xFF,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x03,0x00, + 0xFF,0xFF,0xFF,0xFF,0x02,0x00,0xFE,0xFF,0x01,0x00,0x00,0x00, + 0x00,0x00,0x01,0x00,0xFE,0xFF,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x00,0x00,0xFE,0xFF,0xFF,0xFF,0x01,0x00,0xFF,0xFF,0xFF,0xFF, + 0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0xFF,0xFF, + 0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xFF,0x01,0x00,0x02,0x00, + 0xFD,0xFF,0x02,0x00,0x01,0x00,0x01,0x00,0xFD,0xFF,0x01,0x00, + 0x03,0x00,0x00,0x00,0x03,0x00,0xF8,0xFF,0x09,0x00,0xF9,0xFF, + 0x02,0x00,0xFA,0xFF,0x12,0x00,0xF4,0xFF,0xFE,0xFF,0x06,0x00, + 0xFB,0xFF,0x0A,0x00,0xEC,0xFF,0x06,0x00,0x04,0x00,0x3D,0x00, + 0xB0,0xFF,0x23,0x00,0x13,0x00,0x76,0x00,0x57,0xFF,0x7A,0x00, + 0x47,0xFF,0xC7,0x00,0xB0,0x05,0xF2,0xFB,0x8C,0xF9,0x0E,0x00, + 0x14,0x04,0x65,0x00,0xB3,0xFB,0x13,0x00,0x38,0x04,0xAA,0x01, + 0x0C,0xFD,0x50,0xFF,0x1F,0x03,0x35,0x02,0x28,0xFE,0x8D,0xFE, + 0x6A,0x01,0xB1,0x01,0xAA,0xFE,0x01,0xFE,0x6C,0x00,0x3B,0x01, + 0x45,0xFF,0x47,0xFE,0x9A,0xFF,0xEE,0x00,0x3C,0x00,0xA4,0xFE, + 0xD9,0xFF,0xBB,0x00,0xA1,0x00,0x8E,0xFF,0xB1,0xFF,0xD1,0x00, + 0x4A,0x00,0x8B,0xFF,0xF0,0xFF,0x76,0x00,0x7A,0x00,0xA6,0xFF, + 0x51,0xFF,0x5A,0x00,0x6B,0x00,0x05,0x00,0x4C,0xFF,0x44,0x00, + 0xEA,0x00,0x68,0xFF,0x8A,0xFF,0x6F,0x00,0x23,0x00,0xD2,0xFF, + 0x65,0xFF,0x10,0x00,0xF8,0x00,0xE0,0xFF,0xAF,0xFF,0x8D,0xFF, + 0x8E,0x00,0x26,0x00,0x59,0xFF,0x4B,0x00,0x30,0x00,0xAB,0xFF, + 0x91,0xFF,0xDA,0x00,0x1A,0x00,0x82,0xFF,0xC7,0xFF,0xFC,0xFF, + 0xDD,0x00,0x3A,0x00,0x6F,0xFF,0xF6,0xFF,0x7E,0x00,0x49,0x00, + 0x32,0xFF,0x02,0xFF,0xB0,0x00,0xDC,0xFF,0x0F,0x00,0xFE,0x00, + 0x4F,0xFE,0x15,0x00,0x02,0x01,0x71,0xFF,0x0D,0x00,0xF1,0xFF, + 0xF9,0x00,0x59,0xFF,0xE6,0xFF,0x42,0x01,0x72,0x00,0x53,0xFF, + 0x81,0xFE,0x3F,0x01,0xBA,0x00,0x10,0xFF,0x92,0xFE,0xC2,0x00, + 0xD9,0x01,0x28,0xFF,0x84,0xFE,0xBF,0xFF,0x22,0x00,0xAB,0xFF, + 0xF6,0x00,0x8F,0xFF,0xD4,0xFF,0xEF,0x00,0x33,0x00,0x5E,0xFF, + 0xD5,0x00,0xE4,0x00,0x51,0xFF,0xC7,0x00,0x82,0xFF,0x1D,0xFF, + 0x5E,0xFF,0x2E,0x01,0xDD,0xFF,0x0A,0xFF,0x02,0x00,0xA0,0x00, + 0xB1,0x00,0x15,0xFF,0xBB,0xFF,0x1B,0x00,0xCF,0xFF,0xFE,0xFF, + 0x4C,0x01,0xE3,0xFE,0xF6,0xFF,0xFC,0xFF,0x03,0x00,0xD6,0xFF, + 0x52,0x00,0xE2,0x00,0x23,0xFF,0x92,0x00,0xF8,0xFF,0x6C,0x01, + 0xBE,0xFF,0xCE,0xFE,0xCB,0xFF,0x4F,0x01,0x7F,0x00,0x00,0xFE, + 0x3C,0xFF,0x73,0x00,0xE7,0x00,0x31,0xFF,0xF8,0xFD,0x8B,0x00, + 0xC3,0x01,0x65,0xFF,0x13,0xFF,0x23,0xFF,0xC9,0x01,0x5B,0x01, + 0x75,0xFF,0x03,0x00,0x0C,0x00,0x2E,0x01,0x59,0xFF,0x6A,0x00, + 0x5E,0x00,0x91,0xFF,0xEF,0x00,0xBB,0xFE,0xE0,0xFF,0xBB,0xFF, + 0xDB,0xFE,0x61,0x00,0x10,0x00,0xE1,0xFF,0x9A,0xFF,0xE9,0x00, + 0x18,0x00,0x46,0x00,0xB8,0x00,0x6E,0xFF,0x3B,0xFF,0x74,0xFF, + 0x54,0x01,0xBB,0xFF,0x05,0xFF,0x78,0xFF,0x06,0x01,0x02,0x01, + 0x5A,0xFD,0x4C,0xFF,0x45,0x02,0x7C,0x00,0x55,0xFE,0xAC,0xFF, + 0x53,0x01,0xD0,0x01,0x28,0x00,0x10,0xFF,0x74,0x00,0x76,0x00, + 0x38,0x00,0x04,0xFF,0xE8,0xFE,0xBF,0x00,0x58,0x01,0x50,0xFF, + 0x7B,0xFE,0x5A,0xFF,0xD8,0xFF,0x54,0x00,0x0D,0xFF,0x37,0xFF, + 0x47,0x01,0x4C,0x01,0x4C,0xFF,0xC2,0x00,0xC7,0xFF,0x52,0xFF, + 0x37,0x00,0x73,0x00,0xCA,0x00,0xEC,0xFE,0xCD,0x01,0x5E,0x02, + 0x25,0x00,0xB3,0xFF,0x54,0xFF,0xF7,0x00,0x1A,0x00,0xD4,0xFD, + 0x6D,0xFE,0x47,0xFE,0xC7,0x00,0x63,0x00,0x4F,0xFE,0xA9,0xFE, + 0x5C,0x00,0xEF,0x02,0xED,0x00,0x08,0xFE,0x72,0x00,0x43,0x02, + 0x60,0xFF,0x41,0xFF,0x5E,0xFF,0xAB,0x00,0x7D,0x02,0x98,0xFF, + 0x05,0xFC,0x2B,0xFD,0xE4,0x03,0x64,0x01,0xE7,0xFA,0x2F,0x01, + 0xA7,0x02,0xF7,0x00,0x27,0xFF,0x40,0xFE,0xB1,0x03,0x2A,0x02, + 0x30,0xFE,0x0E,0xFF,0x9D,0x01,0xEA,0xFF,0x57,0xFF,0xB7,0xFF, + 0x68,0xFE,0x83,0x00,0x10,0xFF,0x1C,0xFD,0x65,0x02,0xB8,0x02, + 0x8E,0xFD,0x9C,0xFE,0x45,0x02,0xB4,0x01,0x08,0xFE,0x87,0xFD, + 0xEF,0x01,0x14,0x04,0x7F,0xFE,0xCC,0xFD,0x4D,0x01,0xCC,0x01, + 0xA0,0x01,0x61,0xFD,0x55,0xFC,0x62,0x02,0xE4,0x01,0x24,0xFB, + 0x3E,0xFE,0x05,0x04,0x78,0x01,0xE7,0xFA,0x74,0xFE,0xD5,0x05, + 0x47,0x01,0xBB,0xFD,0xF8,0xFE,0xC8,0x00,0x82,0x03,0x20,0xFF, + 0x1E,0xFE,0x2C,0x02,0xD9,0x01,0x66,0xFE,0xB0,0xFC,0xB9,0x00, + 0xCD,0x01,0xB8,0x01,0x5C,0xFE,0x0C,0xFB,0x98,0xFF,0xA8,0x04, + 0x0E,0x00,0x06,0xFB,0x61,0x01,0x5C,0x03,0xDA,0xFF,0x9E,0xFF, + 0x95,0xFD,0x5D,0x02,0x71,0x04,0x4C,0xFD,0xE0,0xFC,0xE6,0xFE, + 0x0E,0x03,0xC7,0x01,0x3D,0xFD,0x56,0xFD,0xB0,0xFF,0x48,0x05, + 0x16,0x02,0x55,0xFC,0xC7,0xFB,0x3F,0x02,0x8D,0x07,0x35,0xFD, + 0x0A,0xF9,0xE1,0xFE,0x61,0x06,0xB7,0x04,0xB9,0xF8,0x0E,0xFB, + 0x88,0x03,0x29,0x04,0x4C,0x00,0xA7,0xFB,0x20,0xFE,0x05,0x04, + 0x1F,0x03,0xE2,0xFD,0x33,0xFC,0x72,0x02,0x9C,0x04,0xF1,0xFF, + 0x8B,0xFB,0x00,0xFE,0x2C,0x05,0x5D,0x02,0x35,0xFC,0xE7,0xFC, + 0xB3,0xFE,0xAC,0x01,0xED,0x02,0x97,0xFD,0x31,0xFD,0xB9,0x00, + 0xDA,0x05,0xF8,0xFE,0x0C,0xFC,0x57,0x04,0x3E,0x01,0x98,0xFF, + 0xCB,0xFD,0x0D,0x00,0xE0,0x02,0x11,0x00,0x5A,0xFC,0x2E,0xFD, + 0xAB,0x01,0x6F,0x02,0x98,0xFE,0x3C,0xFC,0x8F,0x00,0xAD,0x02, + 0xA2,0x00,0xA7,0xFD,0xA0,0xFF,0xBE,0x01,0x88,0x00,0x7D,0x00, + 0x40,0xFF,0x50,0x00,0x92,0x02,0x4D,0x00,0xF0,0xFF,0x78,0xFE, + 0xAB,0xFF,0x14,0x02,0x96,0xFE,0xCA,0xFF,0x6F,0xFF,0xC1,0xFE, + 0xA1,0xFF,0x42,0x00,0xCC,0x00,0xE4,0xFD,0x05,0x00,0xE5,0x00, + 0x1C,0xFF,0xE5,0x00,0x04,0x01,0xA4,0xFE,0x9C,0xFF,0xE4,0x01, + 0xB1,0xFF,0x20,0xFF,0xAA,0x01,0x59,0x01,0x6C,0xFE,0xFB,0xFF, + 0x97,0x01,0xB8,0x00,0x2E,0xFF,0x43,0xFD,0x38,0x01,0x78,0x01, + 0x0C,0xFF,0x98,0xFE,0x00,0xFE,0xC6,0x01,0x92,0x01,0xCA,0xFF, + 0xEC,0xFE,0x86,0xFF,0x60,0x01,0x27,0x00,0x50,0x02,0x89,0xFF, + 0xB5,0xFD,0x55,0x00,0x6F,0x00,0x32,0x00,0x9D,0xFE,0x37,0x00, + 0x4B,0x00,0x44,0xFF,0x3D,0xFF,0x63,0xFF,0xFA,0x00,0x09,0x02, + 0xBA,0xFE,0x80,0xFE,0x35,0x01,0x4E,0x01,0xE2,0x01,0xAF,0xFE, + 0x31,0x00,0x1E,0x00,0x0C,0x00,0x32,0x02,0xFA,0xFD,0x97,0xFE, + 0x9E,0xFF,0xDB,0x00,0x81,0x00,0x79,0xFD,0x17,0xFF,0x25,0x01, + 0x05,0x02,0x2B,0x00,0x5D,0xFE,0xEE,0xFF,0xA0,0x01,0x2F,0x01, + 0xF4,0xFF,0x78,0xFE,0x17,0xFF,0x53,0x00,0xF7,0xFF,0x4C,0xFF, + 0xC4,0xFD,0xC1,0xFF,0x9D,0x00,0x0F,0xFF,0x27,0xFF,0x54,0xFF, + 0xDF,0x01,0x94,0x00,0xFF,0xFE,0x1E,0x01,0xD2,0x01,0xFF,0x01, + 0xC5,0xFF,0x35,0xFF,0xE9,0xFF,0xAD,0x00,0xC3,0x00,0x22,0xFE, + 0x93,0xFF,0x76,0x01,0x3A,0x00,0xE1,0x00,0x17,0x00,0x95,0xFF, + 0x4F,0x01,0xCB,0x00,0x53,0x00,0x92,0xFE,0x30,0xFF,0x4E,0x00, + 0x5F,0xFE,0xC9,0xFE,0x77,0xFF,0xAF,0x00,0x22,0xFF,0x33,0xFE, + 0xE4,0xFE,0xB6,0xFE,0xC5,0x00,0xE3,0x04,0x05,0x0B,0x3C,0x06, + 0xA8,0xFF,0x79,0x04,0x1D,0x04,0x7B,0xFF,0x17,0xFD,0x5F,0xF8, + 0xA0,0xF8,0x9E,0xF8,0x32,0xF8,0x3D,0xFC,0x41,0xF7,0xB2,0xF7, + 0xBD,0xFC,0xDE,0xFD,0x63,0xFF,0x41,0xFD,0xB4,0x02,0x8F,0x04, + 0xC5,0x02,0x26,0x08,0xAA,0x07,0xE3,0x06,0xEB,0x06,0xBB,0x05, + 0x5F,0x05,0x79,0x01,0x8B,0x01,0xB9,0x01,0x39,0xFF,0x15,0xFF, + 0x08,0xFE,0x58,0xFE,0x6B,0x00,0xB7,0xFF,0xF3,0xFE,0x81,0xFE, + 0x40,0xFF,0x4A,0x03,0xF1,0x01,0x97,0xFD,0x1A,0xFE,0x8B,0x01, + 0x8D,0x01,0x72,0xFD,0x36,0xFC,0x33,0xFE,0x4C,0xFD,0x8A,0xFC, + 0xF5,0xFA,0xE2,0xF8,0x5A,0xFE,0x56,0x08,0xDA,0x13,0xC7,0x0C, + 0xA5,0x00,0xF0,0x05,0xDC,0x0B,0xD7,0x02,0xCF,0xF1,0xD7,0xEF, + 0x63,0xF2,0x0A,0xF1,0xA6,0xF0,0xD4,0xEE,0x2D,0xEE,0x98,0xF2, + 0xBF,0xFC,0x16,0x03,0xE9,0xFE,0x2D,0x02,0x2C,0x0C,0xB9,0x11, + 0x88,0x12,0xB4,0x0D,0x8C,0x0F,0x20,0x12,0xA4,0x0E,0xAE,0x08, + 0xF3,0x01,0x1E,0xFF,0x86,0xFE,0xC9,0xFA,0x96,0xF5,0x50,0xF5, + 0xC9,0xF6,0x5A,0xF9,0x83,0xFC,0xE9,0xFC,0x3B,0xFF,0x16,0x03, + 0x0A,0x06,0x88,0x06,0x66,0x03,0x94,0x02,0x60,0x03,0x02,0x02, + 0x0F,0xFF,0xEA,0xF9,0x26,0xFA,0x9A,0xFE,0x81,0xFD,0xF3,0xF9, + 0x26,0xFA,0x24,0xFD,0x73,0xFE,0xB3,0xFE,0xF5,0xFF,0xD0,0xFF, + 0x92,0x01,0x80,0x01,0x35,0x02,0x9E,0x06,0x65,0x11,0xB6,0x19, + 0xFF,0x04,0x9B,0x01,0xF2,0x0A,0x06,0xFF,0xD0,0xF5,0x1D,0xE9, + 0x04,0xE7,0xDA,0xE9,0xCE,0xEB,0xD0,0xF3,0xFC,0xEC,0xF5,0xEF, + 0xB4,0x00,0x2A,0x05,0x18,0x07,0x49,0x06,0xB6,0x0B,0x56,0x12, + 0x14,0x14,0x0D,0x15,0x5A,0x0C,0x08,0x0A,0xCC,0x0E,0x92,0x09, + 0xC9,0xFC,0x8F,0xFA,0x25,0xFC,0x5E,0xF9,0x2D,0xF9,0xB5,0xF7, + 0x87,0xF6,0x13,0xF9,0x79,0xFF,0xFF,0x01,0xCE,0xFD,0x8E,0xFF, + 0xF7,0x06,0x8E,0x07,0x0C,0x03,0x02,0x01,0xEA,0x00,0x38,0x01, + 0xA4,0xFF,0xBD,0xFC,0xC7,0xFA,0xB6,0xF9,0xC6,0xFA,0x71,0xFD, + 0xE7,0xFE,0x5B,0xFC,0x45,0xFC,0xCA,0x01,0xAC,0x04,0x89,0x00, + 0x97,0x00,0x28,0x02,0xD1,0x02,0xC9,0xF9,0xE1,0x0B,0xA3,0x2E, + 0xD6,0xFF,0xD3,0xF5,0x3A,0x18,0x25,0x04,0x36,0xFA,0x30,0xEB, + 0x8C,0xE6,0x4C,0xEB,0x27,0xE6,0xAA,0xF6,0xC1,0xEC,0x33,0xE1, + 0x55,0x00,0xC1,0x05,0xE4,0x00,0x31,0x04,0xB3,0x07,0x82,0x15, + 0x14,0x15,0xE6,0x12,0x9E,0x0F,0x88,0x09,0x42,0x0F,0x0A,0x0F, + 0xD1,0xFE,0x90,0xFA,0xC6,0xFB,0xE8,0xFA,0xB0,0xFA,0xD7,0xF4, + 0x7E,0xF4,0xC3,0xF8,0x33,0xFF,0x6A,0x01,0xFC,0xFC,0x29,0x00, + 0x58,0x08,0xA3,0x07,0x7E,0x04,0x20,0x03,0x12,0x00,0xC2,0x01, + 0x58,0x03,0x96,0xFC,0x16,0xF8,0x8A,0xFA,0xD8,0xFE,0x4B,0xFA, + 0xAF,0xF5,0x2A,0xFC,0xC1,0xFF,0x8C,0xFF,0x1E,0x01,0x3A,0x02, + 0xBD,0x02,0xE9,0x04,0xB7,0x03,0x94,0xFF,0xB3,0xF7,0xF4,0x1B, + 0x98,0x2B,0x41,0xEE,0x4D,0x02,0x85,0x19,0x0C,0xFA,0x07,0xFB, + 0x0C,0xE9,0x48,0xE3,0xA5,0xED,0xC7,0xF0,0xDF,0xF4,0x70,0xE4, + 0xF8,0xE9,0xBF,0x07,0x60,0x01,0xA0,0xFD,0xF5,0x06,0x7A,0x07, + 0x65,0x13,0xA2,0x19,0x0D,0x0E,0x6C,0x08,0xCC,0x0E,0x1F,0x11, + 0xF4,0x08,0xEC,0xF8,0x86,0xFF,0x3E,0xFF,0xA6,0xF7,0xCD,0xFC, + 0x68,0xF4,0x1A,0xF2,0x0F,0xFE,0x51,0x02,0xB9,0xFB,0xF3,0xFC, + 0xBE,0x03,0xDE,0x09,0x69,0x05,0xDB,0x01,0x57,0x02,0x3C,0x00, + 0x6F,0x04,0x5A,0x01,0x04,0xF9,0x38,0xFA,0xE8,0xFF,0x73,0xFC, + 0xF4,0xF7,0x80,0xF8,0x61,0xFB,0x2B,0xFF,0x4B,0x01,0x59,0xFF, + 0x7F,0xFF,0x0A,0x04,0x40,0x07,0x18,0x03,0xEA,0xFD,0xFA,0xFF, + 0x02,0x13,0x9A,0x21,0xE0,0xFC,0x8A,0xFB,0xBC,0x15,0xCC,0xFE, + 0x95,0xF7,0xA5,0xF2,0x2D,0xE6,0x6D,0xEF,0xF7,0xF7,0xF4,0xEE, + 0xC5,0xEA,0x09,0xF2,0x3E,0xF9,0x7C,0x02,0x27,0xFE,0xF7,0xFD, + 0xA6,0x07,0x49,0x0E,0xF3,0x12,0x6D,0x0E,0xB8,0x07,0x8A,0x0E, + 0x63,0x11,0xF9,0x06,0x0E,0x02,0x14,0x00,0x65,0xFE,0x18,0x00, + 0x31,0xFE,0x32,0xF5,0x8D,0xF6,0x92,0xFD,0xFB,0xFE,0x68,0xFC, + 0xB6,0xFA,0x48,0x01,0x37,0x04,0xB2,0x02,0xC0,0x01,0x32,0x00, + 0x87,0xFF,0x5B,0x04,0x09,0x03,0xB3,0xFB,0xAA,0xFD,0x7E,0x00, + 0xFD,0xFE,0x2B,0xFC,0x21,0xFB,0x45,0xFA,0x5C,0xFC,0x26,0x01, + 0xF9,0xFF,0x81,0xFC,0x67,0x00,0x89,0x05,0xDB,0x01,0x54,0x01, + 0x33,0x01,0x04,0xFE,0x1F,0x03,0xDE,0x0E,0xE0,0x0F,0x43,0xFD, + 0x5A,0x01,0x46,0x10,0x30,0xFF,0xAB,0xF6,0x16,0xFC,0x65,0xF4, + 0x39,0xF2,0xA0,0xFC,0x37,0xF8,0x8B,0xED,0x66,0xF7,0xFD,0xFC, + 0xCF,0xF7,0x43,0xFB,0x6A,0x00,0x01,0x00,0x61,0x03,0xAD,0x09, + 0x4C,0x07,0x40,0x06,0x8E,0x0A,0xAE,0x09,0xFF,0x06,0x02,0x07, + 0x3B,0x06,0xB1,0x03,0x5B,0x02,0xFD,0x01,0x0C,0x00,0x7C,0xFE, + 0x26,0xFF,0x9C,0xFE,0x49,0xFD,0xEA,0xFD,0xBB,0xFE,0xD3,0xFD, + 0xEB,0xFC,0xBF,0xFD,0x51,0xFE,0xDF,0xFD,0x2F,0xFE,0xEE,0xFE, + 0xBA,0xFE,0x4D,0xFF,0x10,0x00,0x2C,0xFF,0xB7,0xFE,0x68,0xFF, + 0x59,0xFF,0x6B,0xFE,0xCE,0xFE,0x39,0xFF,0x0C,0xFF,0x07,0x00, + 0x70,0x00,0xB7,0xFF,0xE7,0xFF,0x28,0x00,0x47,0xFF,0xA7,0xFF, + 0xE3,0x02,0x47,0x07,0x3B,0x07,0x09,0x07,0xC9,0x08,0x2C,0x05, + 0x53,0x02,0x03,0x02,0xF7,0xFF,0xE5,0xFC,0x97,0xFC,0x2A,0xFC, + 0x15,0xF8,0x96,0xF6,0x8E,0xF5,0xE0,0xF4,0x21,0xF6,0xB1,0xF7, + 0x38,0xF8,0x34,0xF9,0xAD,0xFC,0x82,0xFE,0xDE,0x00,0x7B,0x04, + 0xC8,0x05,0x83,0x06,0x49,0x09,0xA3,0x0A,0x43,0x09,0x69,0x09, + 0x67,0x09,0x8C,0x07,0x92,0x06,0x21,0x06,0xFF,0x03,0x02,0x02, + 0x5A,0x01,0xBA,0xFF,0x6C,0xFD,0x51,0xFC,0x46,0xFB,0x13,0xFA, + 0x2F,0xFA,0xCD,0xFA,0x30,0xFB,0x89,0xFB,0xE7,0xFB,0x2C,0xFC, + 0xF5,0xFC,0xAE,0xFD,0x05,0xFE,0x5B,0xFE,0xD9,0xFE,0xE7,0xFF, + 0xAC,0x00,0x2D,0x01,0x3D,0x01,0x71,0x01,0x73,0x01,0x25,0x01, + 0x03,0x01,0xB0,0x00,0x25,0x04,0x59,0x08,0x57,0x07,0x45,0x07, + 0x44,0x08,0x86,0x04,0xA6,0x00,0xF9,0x00,0xED,0xFF,0xB9,0xFB, + 0x36,0xFB,0xE2,0xFA,0x94,0xF7,0x7B,0xF5,0x2D,0xF5,0x65,0xF5, + 0x1D,0xF6,0x09,0xF8,0x08,0xF9,0xB6,0xFA,0xE3,0xFC,0x06,0xFF, + 0x99,0x02,0x54,0x05,0x0F,0x06,0x8E,0x07,0x74,0x0A,0x71,0x0A, + 0x85,0x09,0xBF,0x09,0x7D,0x08,0x7B,0x06,0x4C,0x06,0xB8,0x05, + 0x0C,0x03,0x9B,0x01,0xB0,0x00,0xC9,0xFE,0x11,0xFD,0xD1,0xFB, + 0x5E,0xFA,0xFF,0xF9,0xB4,0xFA,0xEF,0xFA,0x29,0xFB,0x7A,0xFB, + 0xAD,0xFB,0x5D,0xFC,0x5A,0xFD,0x6E,0xFD,0xB5,0xFD,0xC5,0xFE, + 0xA4,0xFF,0x65,0x00,0x4E,0x01,0x94,0x01,0x87,0x01,0xEA,0x01, + 0xD5,0x01,0x4B,0x01,0x86,0x00,0xD7,0x03,0x99,0x08,0x29,0x07, + 0x06,0x07,0xB3,0x08,0x0E,0x05,0xA5,0x00,0x86,0x00,0x23,0x00, + 0xD4,0xFB,0xE3,0xFA,0xE3,0xFA,0xD6,0xF7,0xC8,0xF5,0x13,0xF5, + 0x52,0xF5,0x05,0xF6,0xCF,0xF7,0xE5,0xF8,0xAA,0xFA,0xDF,0xFC, + 0x88,0xFE,0x34,0x02,0x5E,0x05,0x04,0x06,0x97,0x07,0xAB,0x0A, + 0x92,0x0A,0xA8,0x09,0x04,0x0A,0x87,0x08,0x35,0x06,0x34,0x06, + 0xC1,0x05,0x03,0x03,0x6C,0x01,0x8A,0x00,0xCE,0xFE,0x04,0xFD, + 0xEA,0xFB,0x84,0xFA,0x0D,0xFA,0x9D,0xFA,0x0D,0xFB,0x65,0xFB, + 0x64,0xFB,0xB2,0xFB,0x6D,0xFC,0x79,0xFD,0x62,0xFD,0xD0,0xFD, + 0x27,0xFF,0xD4,0xFF,0x5F,0x00,0x7B,0x01,0xDA,0x01,0x21,0x01, + 0xA8,0x01,0xF5,0x01,0xAF,0x00,0xDD,0x01,0xE2,0x07,0x22,0x08, + 0xFA,0x05,0x4B,0x09,0x27,0x07,0x7D,0x01,0x4F,0x00,0xE6,0x00, + 0xF0,0xFC,0xAB,0xFA,0x98,0xFB,0x43,0xF8,0xE6,0xF5,0x6B,0xF5, + 0xE6,0xF4,0x2C,0xF5,0x38,0xF7,0x7E,0xF8,0x78,0xF9,0x6B,0xFC, + 0xFF,0xFD,0x97,0x00,0xA2,0x04,0x3D,0x06,0x25,0x07,0x32,0x0A, + 0x34,0x0B,0xFC,0x09,0x3E,0x0A,0xA2,0x09,0xFB,0x06,0x3F,0x06, + 0x62,0x06,0xB3,0x03,0x9E,0x01,0x03,0x01,0x44,0xFF,0x14,0xFD, + 0x60,0xFC,0x0A,0xFB,0xB9,0xF9,0x63,0xFA,0x06,0xFB,0x1E,0xFB, + 0x32,0xFB,0xB2,0xFB,0xFC,0xFB,0xE4,0xFC,0x31,0xFD,0x96,0xFD, + 0xF3,0xFE,0x86,0xFF,0x47,0x00,0x70,0x01,0xF2,0x01,0x1B,0x01, + 0x81,0x01,0xC7,0x01,0xA2,0x00,0x6D,0x04,0x27,0x09,0xEB,0x06, + 0x18,0x07,0x13,0x0A,0xAA,0x05,0x79,0x00,0x30,0x01,0x0D,0x00, + 0x39,0xFB,0x55,0xFB,0x11,0xFB,0x9B,0xF6,0x0C,0xF5,0x6B,0xF5, + 0x99,0xF4,0x04,0xF5,0x94,0xF7,0x56,0xF8,0xFD,0xF9,0xF7,0xFC, + 0xC7,0xFE,0xD6,0x01,0x27,0x05,0x92,0x06,0x5C,0x08,0x2B,0x0B, + 0x0C,0x0B,0x3A,0x0A,0xA4,0x0A,0x23,0x09,0xCE,0x06,0xA4,0x06, + 0xA4,0x05,0xA0,0x02,0x8C,0x01,0xBE,0x00,0x49,0xFE,0xA6,0xFC, + 0x19,0xFC,0x62,0xFA,0x99,0xF9,0x93,0xFA,0xC5,0xFA,0xBA,0xFA, + 0x4B,0xFB,0xC2,0xFB,0x0D,0xFC,0xCE,0xFC,0x4D,0xFD,0x34,0xFE, + 0x4D,0xFF,0xB7,0xFF,0xD0,0x00,0xF8,0x01,0xBC,0x01,0x37,0x01, + 0xA0,0x01,0x44,0x01,0x51,0x04,0xF4,0x08,0x51,0x07,0x69,0x07, + 0xB6,0x09,0x7A,0x06,0x60,0x01,0x12,0x01,0x34,0x00,0x64,0xFB, + 0x35,0xFB,0x07,0xFB,0xEC,0xF6,0x98,0xF4,0x26,0xF5,0xDC,0xF4, + 0x94,0xF4,0xFC,0xF6,0x3F,0xF8,0x9F,0xF9,0x66,0xFC,0xF2,0xFE, + 0xC4,0x01,0xB5,0x04,0xD9,0x06,0xB3,0x08,0xF1,0x0A,0x52,0x0B, + 0x98,0x0A,0x7C,0x0A,0x53,0x09,0x64,0x07,0x8D,0x06,0x54,0x05, + 0xDC,0x02,0x4D,0x01,0x4F,0x00,0x55,0xFE,0xB1,0xFC,0xF6,0xFB, + 0xB1,0xFA,0xFD,0xF9,0x8B,0xFA,0xD2,0xFA,0xD5,0xFA,0x69,0xFB, + 0xDE,0xFB,0xFC,0xFB,0xC5,0xFC,0xA9,0xFD,0x79,0xFE,0x25,0xFF, + 0xD3,0xFF,0xDD,0x00,0x81,0x01,0x70,0x01,0xD7,0x00,0xDE,0x00, + 0x6C,0x04,0xAC,0x08,0x3A,0x07,0xF2,0x07,0x78,0x0A,0xC6,0x06, + 0xB5,0x02,0xF7,0x01,0x60,0x00,0xE0,0xFB,0x84,0xFB,0x0C,0xFB, + 0xED,0xF6,0xF9,0xF4,0x79,0xF4,0x73,0xF4,0x88,0xF4,0x3D,0xF6, + 0x73,0xF7,0x39,0xF9,0xF5,0xFB,0xE4,0xFD,0x83,0x01,0x96,0x04, + 0x64,0x06,0x86,0x08,0x3F,0x0B,0xAD,0x0B,0xB0,0x0A,0x26,0x0B, + 0xD4,0x09,0xA4,0x07,0xE9,0x06,0xE8,0x05,0x19,0x03,0x47,0x01, + 0x93,0x00,0x5B,0xFE,0xD6,0xFC,0x10,0xFC,0xBD,0xFA,0xF9,0xF9, + 0x75,0xFA,0xC0,0xFA,0xA8,0xFA,0x6E,0xFB,0x9C,0xFB,0xCD,0xFB, + 0xE0,0xFC,0xB5,0xFD,0x46,0xFE,0x08,0xFF,0x0F,0x00,0xA1,0x00, + 0x51,0x01,0x51,0x01,0x31,0x00,0x29,0x02,0xA9,0x07,0x13,0x08, + 0xA7,0x06,0x97,0x0A,0xA6,0x09,0x52,0x04,0xE4,0x02,0x07,0x02, + 0x92,0xFD,0x68,0xFB,0xEC,0xFB,0x39,0xF8,0x5D,0xF5,0xB2,0xF4, + 0x2D,0xF4,0x37,0xF4,0xE3,0xF4,0x0D,0xF6,0xF0,0xF7,0x1F,0xFC, + 0x29,0xFD,0x45,0xFE,0xBA,0x03,0xC9,0x04,0x7B,0x05,0xB8,0x0C, + 0x65,0x0D,0x22,0x08,0x96,0x0C,0xBE,0x0D,0x56,0x07,0x3A,0x08, + 0xC1,0x07,0x64,0x01,0x78,0x02,0xB3,0x03,0x29,0xFB,0x27,0xFB, + 0x5F,0xFE,0x00,0xF9,0xA9,0xF9,0xF3,0xFB,0x3E,0xF8,0xD7,0xF9, + 0x85,0xFE,0x89,0xFA,0x4B,0xFA,0x0D,0xFF,0x78,0xFD,0xB2,0xFE, + 0x49,0x00,0x01,0xFF,0x74,0x00,0x72,0x02,0x89,0xFC,0x1A,0x02, + 0xF6,0x19,0x59,0x08,0x0B,0xFA,0xDE,0x19,0x57,0x0F,0x60,0xFB, + 0x82,0x08,0xAD,0xFB,0xC3,0xEC,0x22,0xFF,0x59,0xF9,0xA3,0xE3, + 0x2B,0xF1,0xC7,0xF3,0x2F,0xEE,0xEB,0xF5,0x42,0xF3,0x46,0xF5, + 0xF8,0x00,0x72,0x05,0x85,0x04,0x2C,0x07,0x00,0x0C,0x40,0x15, + 0xD3,0x13,0x1B,0x0C,0x9A,0x0F,0xB2,0x0E,0x49,0x0B,0xE4,0x08, + 0x76,0xFF,0x89,0xFA,0x0F,0xFE,0xC1,0xFA,0xF4,0xF3,0x40,0xF4, + 0xF3,0xF6,0x9A,0xF8,0x4B,0xFA,0xE1,0xF9,0x4F,0xFA,0x7B,0xFE, + 0x36,0x03,0x5A,0x02,0xE1,0xFE,0x95,0x01,0xF0,0x04,0x2B,0x05, + 0xAC,0x01,0x6B,0x00,0x82,0x00,0xE8,0x00,0xAD,0x02,0x55,0xFD, + 0xAB,0xFB,0xD2,0xF6,0x2D,0x0B,0x35,0x25,0x05,0xF8,0xB9,0xFA, + 0xC2,0x24,0xA6,0x04,0x37,0xFF,0x2F,0x00,0x3E,0xE9,0x01,0xF7, + 0x81,0xFB,0xCC,0xE8,0xBD,0xE3,0x86,0xEA,0x1A,0xF7,0xD0,0xF5, + 0x8A,0xEA,0x35,0xF9,0x12,0x01,0x62,0x05,0x86,0x11,0x98,0x06, + 0xC8,0x08,0x33,0x1D,0x11,0x1A,0x17,0x0F,0x9E,0x0B,0x97,0x0E, + 0x5C,0x0F,0x7E,0x04,0xC2,0xFD,0xEA,0xF6,0xD9,0xF6,0x30,0xFC, + 0x5F,0xF3,0xC5,0xEA,0x1F,0xF6,0xD8,0xFC,0xFA,0xF9,0x4B,0xFB, + 0xCA,0xFB,0x6C,0x02,0x1D,0x06,0xCA,0x06,0xD2,0x01,0xD3,0x00, + 0x9B,0x07,0x9E,0x06,0x81,0xFF,0x76,0xFC,0x4A,0x00,0xE7,0xFE, + 0x8B,0xFD,0x0F,0xFD,0xAD,0xFA,0xBF,0xFD,0x98,0xF8,0xD1,0x04, + 0xD8,0x26,0xBE,0x01,0x99,0xF8,0x66,0x24,0x66,0x0A,0x80,0x01, + 0x71,0xFC,0xE1,0xED,0x4F,0xF8,0xCC,0xEF,0x7F,0xEC,0x61,0xE5, + 0x72,0xE1,0x14,0xF9,0xBD,0xF3,0x05,0xEA,0xF4,0xFB,0x3D,0xFF, + 0xA3,0x09,0x0B,0x10,0x8B,0x09,0x79,0x10,0x05,0x19,0xBA,0x1C, + 0xC5,0x14,0xDF,0x07,0xBC,0x0F,0x70,0x0D,0x50,0x00,0x56,0xFD, + 0xDD,0xF2,0xC8,0xF5,0xA1,0xF7,0xAE,0xF0,0x8E,0xEE,0xD3,0xF2, + 0xAB,0xFC,0xAD,0xFE,0x92,0xFA,0xBD,0xFF,0xF0,0x05,0x4E,0x06, + 0x7D,0x09,0xA8,0x03,0xD2,0x02,0x1B,0x06,0xDC,0x04,0x5D,0x01, + 0xAD,0xFA,0xDC,0xFC,0xBF,0xFE,0xFD,0xFB,0x84,0xFC,0x2C,0xFA, + 0x13,0xFB,0x5B,0xF6,0xD9,0x10,0x97,0x29,0x9E,0xF5,0xE3,0x01, + 0xE9,0x24,0x55,0x08,0x11,0x02,0x18,0xF3,0x86,0xF1,0x3A,0xF5, + 0xD5,0xEC,0xE4,0xEF,0xDA,0xDB,0xDC,0xE3,0xF1,0xFE,0x22,0xEF, + 0x28,0xEE,0x2C,0xFB,0x5F,0x03,0xB0,0x10,0xA6,0x0C,0xE4,0x0E, + 0x7E,0x12,0xA5,0x18,0x27,0x22,0xD4,0x0F,0x83,0x06,0x6C,0x0F, + 0x2D,0x08,0x5D,0x01,0x20,0xF6,0x8C,0xF0,0x33,0xF6,0x0B,0xF3, + 0x9D,0xF3,0x20,0xED,0x81,0xF2,0x43,0x01,0x04,0xFF,0x00,0xFE, + 0x6D,0x01,0x31,0x06,0x00,0x0B,0x71,0x08,0x09,0x05,0xDB,0x02, + 0xCE,0x02,0x29,0x07,0xF1,0xFF,0x09,0xF8,0x97,0xFB,0x41,0xFC, + 0x5A,0xFD,0x7B,0xFA,0x92,0xF8,0xB4,0xFA,0xD9,0xF4,0x06,0x1E, + 0x54,0x26,0x6B,0xEE,0x8B,0x0C,0x81,0x21,0xC3,0x0A,0x1B,0x00, + 0x5A,0xEC,0x40,0xF7,0xBD,0xEF,0x72,0xED,0x21,0xF0,0x25,0xD4, + 0xE0,0xE8,0x94,0xFD,0x86,0xEF,0x37,0xF1,0xAB,0xF6,0x7F,0x0A, + 0x6C,0x12,0x48,0x0C,0xD6,0x13,0x89,0x10,0xED,0x1A,0x75,0x23, + 0xD1,0x0D,0xBD,0x06,0xD2,0x0C,0xF8,0x07,0xAC,0xFF,0xE0,0xF2, + 0x85,0xF1,0x8A,0xF2,0xEC,0xF2,0xB7,0xF4,0xA6,0xEB,0x7E,0xF3, + 0x27,0x01,0xE4,0x01,0x29,0xFF,0xE3,0x00,0xCF,0x08,0x00,0x0B, + 0xBF,0x08,0xC4,0x06,0x09,0x02,0x7F,0x02,0x1E,0x06,0x4B,0xFF, + 0x70,0xF7,0xB8,0xF9,0xBC,0xFC,0xBA,0xFA,0xFF,0xF9,0x64,0xFA, + 0x66,0xFB,0x7F,0xF5,0x96,0x0A,0x0F,0x2F,0x59,0x05,0x51,0xF8, + 0x87,0x1F,0x51,0x14,0x9D,0x07,0xC6,0xF0,0x5F,0xF0,0xA9,0xF5, + 0x32,0xE7,0xC9,0xF3,0xFB,0xDF,0xE2,0xD8,0x1A,0xF5,0x68,0xF6, + 0xE2,0xF5,0x05,0xF1,0x21,0xFF,0x34,0x15,0xBC,0x0E,0x7B,0x13, + 0xB3,0x13,0x0C,0x14,0x21,0x20,0xEE,0x19,0xDA,0x0B,0xB6,0x05, + 0x63,0x07,0xCE,0x05,0x5E,0xF7,0x4A,0xF1,0x2A,0xF0,0x05,0xEF, + 0xDD,0xF5,0x06,0xF2,0xD4,0xEF,0x9A,0xF7,0x7F,0x02,0x3D,0x05, + 0xE5,0xFF,0xCB,0x04,0xCA,0x09,0x6B,0x0A,0xC6,0x09,0xB5,0x04, + 0x11,0x02,0xB3,0x02,0x03,0x02,0x84,0xFC,0xFF,0xF6,0xD0,0xF8, + 0xBE,0xFA,0xE0,0xF8,0xDD,0xFB,0x87,0xFA,0x0D,0xFC,0x87,0xFB, + 0x24,0xFF,0x40,0x2B,0x6F,0x19,0x10,0xF5,0xCF,0x15,0x8E,0x16, + 0xEA,0x0E,0x9E,0xF6,0xAE,0xEB,0xF8,0xF7,0x1F,0xE2,0x98,0xF2, + 0xA6,0xEC,0x53,0xD4,0x48,0xE9,0x68,0xF5,0x33,0xFE,0xD8,0xF2, + 0xFC,0xF4,0x33,0x13,0x67,0x0F,0x99,0x14,0x1B,0x19,0xA4,0x10, + 0x1A,0x1A,0x57,0x1A,0xEA,0x16,0xDB,0x06,0x28,0xFD,0x69,0x0A, + 0x57,0xFC,0xDA,0xF1,0x02,0xF3,0xC7,0xEB,0x5D,0xF2,0x20,0xF3, + 0x2F,0xF6,0x2C,0xF5,0x06,0xF7,0xB6,0x08,0x04,0x05,0x27,0x03, + 0x3D,0x07,0x9B,0x07,0x39,0x0C,0x5C,0x07,0x26,0x05,0x21,0x01, + 0xEF,0xFE,0x57,0x02,0x5D,0xF9,0xCC,0xF5,0xD3,0xF8,0x2D,0xF8, + 0x9F,0xFA,0x2A,0xFA,0xF8,0xFC,0x3A,0xFE,0xE9,0xFD,0x5A,0xFD, + 0x68,0x0D,0xD6,0x2B,0x7C,0x0C,0x2B,0xFE,0xB6,0x17,0x76,0x0F, + 0x26,0x09,0xAE,0xF2,0x47,0xEE,0xA9,0xED,0xA8,0xE4,0x90,0xF6, + 0xC9,0xE3,0x6A,0xD9,0xFD,0xEC,0xC0,0xF7,0x1A,0xFF,0x9F,0xF4, + 0x29,0xFF,0xC0,0x0F,0x2D,0x10,0xC6,0x1B,0x76,0x17,0x76,0x11, + 0xA4,0x17,0xA6,0x18,0xC5,0x14,0xE0,0x03,0xE7,0xFC,0xAA,0x03, + 0xD8,0xF9,0x8A,0xF4,0xBD,0xF0,0x5A,0xEB,0x06,0xF2,0x49,0xF4, + 0xBD,0xF8,0x14,0xF7,0xE8,0xF9,0x50,0x06,0x13,0x07,0x7B,0x07, + 0x52,0x06,0x93,0x06,0x5B,0x0A,0x85,0x08,0xB3,0x05,0x05,0x00, + 0xF9,0xFD,0x39,0x00,0x2A,0xFB,0x5E,0xF6,0xAF,0xF5,0x8E,0xF7, + 0x8B,0xFA,0x60,0xFA,0x88,0xFC,0x89,0xFE,0xDB,0xFE,0x57,0x04, + 0x44,0xFE,0x42,0x0A,0x91,0x29,0x5E,0x11,0x38,0x01,0x05,0x0E, + 0xE8,0x0D,0xC8,0x0B,0x5D,0xF1,0x35,0xF0,0x5F,0xED,0x73,0xE2, + 0xBD,0xF4,0x91,0xE9,0x64,0xE0,0xBA,0xE5,0x2F,0xF4,0xFA,0x05, + 0x9B,0xF7,0x29,0xFE,0xB7,0x0C,0x67,0x0F,0xB6,0x1B,0x5A,0x18, + 0x1C,0x16,0xC3,0x11,0xC0,0x12,0x8D,0x18,0x87,0x06,0x9C,0xFC, + 0x80,0xFC,0x62,0xFB,0x0F,0xF9,0x2A,0xEF,0x95,0xEF,0xC1,0xEF, + 0x31,0xF2,0x6F,0xFB,0xD4,0xF8,0xAE,0xFB,0xA8,0x00,0xDD,0x05, + 0xDD,0x0A,0x05,0x06,0xAE,0x07,0x3F,0x07,0x30,0x07,0x20,0x08, + 0xEF,0x01,0xCB,0xFF,0xD1,0xFC,0x60,0xFC,0xAD,0xFB,0x07,0xF6, + 0xED,0xF5,0xE2,0xF7,0x49,0xFA,0xDE,0xFB,0x80,0xFC,0xBA,0xFE, + 0xFF,0x00,0xA2,0x01,0xA7,0x04,0xE7,0x01,0x8D,0x04,0xA3,0x23, + 0xE3,0x18,0xD0,0xFF,0xB6,0x09,0x3C,0x08,0x32,0x0B,0xA6,0xF8, + 0xC2,0xEF,0x47,0xF0,0xD9,0xE0,0x46,0xF3,0x46,0xF1,0x92,0xE4, + 0xF0,0xE6,0x18,0xEE,0x9E,0x03,0xD6,0xFD,0xD1,0xFE,0x92,0x0A, + 0xA8,0x09,0x31,0x17,0x60,0x1A,0x32,0x18,0x58,0x11,0xFC,0x0B, + 0xC1,0x15,0x44,0x0C,0x03,0x00,0x60,0xFC,0x8C,0xF6,0x8D,0xF9, + 0x3E,0xF6,0x49,0xF2,0x19,0xF1,0x7A,0xEE,0x3A,0xFA,0x36,0xFD, + 0xA8,0xFC,0x47,0xFF,0x31,0x00,0xF3,0x09,0x16,0x09,0xE6,0x07, + 0x94,0x06,0x7F,0x02,0x2A,0x07,0xCA,0x04,0x79,0x01,0x2B,0xFD, + 0x5D,0xF9,0x1E,0xFD,0x44,0xFB,0x90,0xF9,0x40,0xF8,0x64,0xF5, + 0x01,0xFA,0x58,0xFE,0xDE,0xFF,0xEE,0xFD,0xE8,0xFD,0x91,0x03, + 0xFE,0x02,0x82,0x02,0x2A,0x07,0x57,0x01,0x13,0x06,0xE0,0x1F, + 0x6B,0x14,0xFA,0xFF,0x45,0x02,0xED,0x06,0xF6,0x09,0x4B,0xF5, + 0x18,0xF3,0xFB,0xEF,0x53,0xE4,0x15,0xF3,0xD5,0xF2,0x8A,0xEC, + 0x76,0xE5,0x02,0xF0,0x4C,0x05,0xA1,0xFE,0xA4,0x01,0x5A,0x08, + 0xDA,0x0A,0x19,0x13,0x0A,0x17,0xBB,0x1A,0x9C,0x0E,0x0C,0x09, + 0xC8,0x11,0x03,0x0C,0x7F,0x02,0xBF,0xFA,0xB0,0xF7,0xB8,0xF7, + 0x88,0xF5,0xFE,0xF8,0x59,0xF3,0x25,0xEF,0x2A,0xF8,0xFA,0xFD, + 0x67,0x01,0x64,0xFE,0x89,0x00,0x26,0x05,0xD8,0x05,0x49,0x0B, + 0x0B,0x08,0x43,0x02,0x06,0x01,0x4C,0x03,0x47,0x04,0x53,0xFD, + 0x28,0xFA,0xB7,0xF9,0x19,0xF9,0xF6,0xFB,0x80,0xFC,0x04,0xFA, + 0xA0,0xF8,0xC2,0xFB,0x96,0x00,0x03,0x00,0x24,0x00,0x2F,0x01, + 0xCF,0x00,0x7E,0x03,0xE0,0x04,0x1D,0x03,0xCC,0xFE,0x8F,0x00, + 0x27,0x01,0x29,0x0F,0x37,0x1B,0x73,0x04,0x2B,0xFF,0x8B,0x03, + 0x9A,0x09,0xB4,0x06,0x37,0xF4,0x83,0xF6,0xB1,0xEC,0x89,0xED, + 0xA9,0xF7,0xAC,0xF1,0x9F,0xEC,0xF2,0xE6,0x47,0xF7,0x4B,0x02, + 0x95,0xFD,0xEC,0x02,0x72,0x04,0x1B,0x0A,0x77,0x11,0xDB,0x16, + 0xAA,0x17,0x06,0x0B,0x3B,0x0B,0x80,0x10,0x49,0x0B,0x98,0x04, + 0xA4,0xFB,0x75,0xF9,0x72,0xF6,0xBE,0xF5,0xF4,0xFA,0x17,0xF5, + 0x01,0xF2,0xB9,0xF5,0x92,0xFD,0xA4,0x01,0xB9,0xFE,0x0D,0x02, + 0x5D,0x02,0x29,0x04,0xB1,0x08,0x1B,0x08,0x8D,0x04,0x90,0xFF, + 0x89,0x01,0xB8,0x02,0x67,0xFE,0x79,0xFC,0xB6,0xF9,0xB3,0xF8, + 0x3F,0xFA,0x0A,0xFC,0xC6,0xFC,0x4A,0xFA,0x7D,0xFC,0xBB,0xFF, + 0xE5,0x00,0xAD,0x01,0xC4,0x00,0xB1,0x01,0x0E,0x02,0xC2,0x03, + 0x5D,0x03,0xF2,0x00,0xC2,0x00,0x35,0x00,0xB1,0x00,0x4C,0xFC, + 0x25,0xFF,0x9B,0x13,0xAB,0x11,0x0A,0x01,0xCD,0x02,0x7E,0x06, + 0x15,0x09,0xFA,0xFF,0xFC,0xFB,0x87,0xF9,0x6D,0xEB,0xDE,0xF1, + 0x1F,0xF7,0x35,0xF3,0x2D,0xEB,0x56,0xEB,0xB0,0xF9,0xDE,0xF9, + 0x0B,0xFE,0xFB,0x04,0xC1,0x04,0x43,0x07,0x7A,0x0D,0x71,0x18, + 0x9A,0x14,0x5C,0x0C,0x8F,0x0E,0x77,0x0D,0xE9,0x09,0x9B,0x05, + 0xAD,0x01,0x3D,0xFB,0xA0,0xF3,0x9F,0xF7,0x61,0xF9,0xE9,0xF3, + 0x0D,0xF3,0x40,0xF7,0xD3,0xFC,0xA4,0xFB,0xF6,0xFF,0x0D,0x04, + 0x3F,0x01,0x9E,0x03,0xA6,0x06,0x54,0x08,0xF7,0x03,0x24,0x02, + 0xB9,0x03,0xCE,0xFF,0x6C,0xFE,0xC5,0xFD,0xFA,0xFB,0x88,0xF9, + 0x1C,0xF9,0xF2,0xFB,0xFD,0xFA,0x35,0xFB,0x82,0xFD,0x3B,0xFE, + 0xEC,0xFF,0x08,0x01,0xC0,0x02,0x02,0x02,0x41,0x01,0x31,0x03, + 0x9D,0x02,0x2A,0x03,0xD8,0x01,0x34,0x00,0x20,0x00,0xFA,0xFF, + 0x1B,0x00,0x7D,0xFD,0xD2,0xF8,0x4F,0x03,0xE2,0x15,0x79,0x0C, + 0x20,0x03,0x1C,0x03,0xB8,0x05,0xE5,0x07,0xE6,0xFF,0x0C,0x02, + 0x68,0xF5,0xC7,0xEB,0xE9,0xF2,0x44,0xF6,0x33,0xF5,0xD7,0xEA, + 0xDE,0xEF,0x4E,0xF6,0x8A,0xF7,0x49,0x01,0x97,0x05,0xFA,0x05, + 0xBD,0x04,0xF3,0x0C,0xF2,0x16,0xBE,0x12,0xDF,0x0F,0x5A,0x0E, + 0x92,0x0A,0xAC,0x07,0xE2,0x05,0x78,0x05,0x96,0xFB,0x5C,0xF4, + 0x17,0xF7,0x10,0xF7,0x7A,0xF6,0x87,0xF4,0xEB,0xF6,0x66,0xF8, + 0x9D,0xFA,0x00,0x02,0x8F,0x02,0x51,0x02,0x0F,0x02,0x33,0x05, + 0x5C,0x07,0x5C,0x05,0xE5,0x05,0x9B,0x02,0xAF,0xFF,0x03,0xFF, + 0xD7,0xFE,0xB8,0xFD,0xED,0xF9,0xCB,0xF9,0x84,0xF9,0xA1,0xF9, + 0x57,0xFC,0x26,0xFD,0x24,0xFE,0xE5,0xFD,0x43,0x00,0xED,0x02, + 0x1F,0x03,0x6D,0x04,0x71,0x02,0x01,0x02,0x41,0x02,0x05,0x02, + 0xEF,0x02,0x73,0x00,0x75,0xFF,0xC4,0xFE,0x18,0xFE,0xFA,0xFF, + 0x07,0xFC,0xAE,0xF9,0xBF,0x09,0x53,0x12,0x09,0x09,0x93,0x04, + 0x13,0x04,0x61,0x07,0xA8,0x02,0xD8,0x01,0xBF,0x01,0x62,0xF3, + 0xCD,0xEE,0xB1,0xF1,0xF7,0xF6,0xB2,0xF2,0x84,0xEE,0x5D,0xF4, + 0xDB,0xF3,0x03,0xF8,0xBD,0x00,0x5E,0x07,0x71,0x06,0xB4,0x04, + 0x11,0x0E,0x21,0x12,0xFD,0x10,0x01,0x11,0xE5,0x0E,0x15,0x0A, + 0x4B,0x04,0x0D,0x06,0xF9,0x04,0xB6,0xFC,0xAC,0xF8,0x2B,0xF7, + 0x4A,0xF6,0xD0,0xF4,0x2C,0xF7,0x0A,0xFA,0x8E,0xF6,0x5A,0xF8, + 0x36,0xFE,0xE6,0x02,0xFE,0x02,0x72,0x02,0x62,0x05,0x87,0x03, + 0x71,0x04,0xEB,0x06,0xDC,0x05,0xDD,0x01,0x26,0xFE,0x47,0xFF, + 0x90,0xFD,0x32,0xFC,0x02,0xFC,0xE5,0xF9,0x05,0xF9,0x27,0xF9, + 0xCA,0xFC,0x1B,0xFE,0xE2,0xFD,0xAC,0xFF,0x17,0x00,0x46,0x02, + 0x6B,0x03,0xA4,0x04,0xA5,0x04,0x23,0x02,0x59,0x02,0xBD,0x01, + 0xAE,0x01,0x3E,0x01,0xBD,0xFF,0x7C,0xFF,0x12,0xFE,0x94,0xFE, + 0x8C,0xFD,0x90,0xF9,0x34,0x03,0x84,0x0F,0x5D,0x0B,0x57,0x06, + 0xC0,0x03,0x36,0x05,0x4B,0x03,0xFD,0x00,0x1E,0x04,0xCE,0xF9, + 0xBF,0xF1,0x0B,0xF1,0x49,0xF5,0xE7,0xF5,0x92,0xF1,0xC8,0xF5, + 0x86,0xF4,0x1C,0xF5,0xAB,0xFB,0x90,0x03,0x15,0x07,0x69,0x04, + 0x9C,0x09,0x32,0x0D,0x7E,0x0D,0x9A,0x0F,0x2C,0x10,0x3E,0x0D, + 0xDF,0x05,0xD7,0x04,0x98,0x05,0x1F,0x01,0xEC,0xFD,0x4C,0xFB, + 0xAC,0xF8,0x14,0xF5,0xF0,0xF5,0x59,0xFA,0xE7,0xF8,0xA9,0xF8, + 0xCF,0xF9,0xAD,0xFC,0x58,0xFF,0xDA,0x01,0x9C,0x05,0xFA,0x02, + 0xBE,0x02,0xCC,0x03,0x17,0x05,0x01,0x05,0x9B,0x02,0x23,0x02, + 0x05,0xFE,0xFA,0xFC,0x82,0xFD,0xDD,0xFC,0x1C,0xFC,0x25,0xFA, + 0x22,0xFB,0x02,0xFB,0x74,0xFC,0x0A,0xFF,0x65,0xFF,0x75,0x00, + 0xA2,0x00,0x21,0x02,0x19,0x03,0x6B,0x03,0x0D,0x04,0xB0,0x02, + 0x28,0x02,0x49,0x01,0xAF,0x00,0xCE,0x00,0x45,0x00,0x5B,0x00, + 0x0A,0xFF,0xDD,0xFD,0x26,0xFA,0x16,0xFE,0x2B,0x0C,0x48,0x0C, + 0xB5,0x07,0x3A,0x04,0x4D,0x02,0x53,0x02,0xBA,0xFF,0x00,0x04, + 0x0A,0xFF,0x69,0xF6,0xCE,0xF2,0x61,0xF3,0x90,0xF6,0x9A,0xF4, + 0x07,0xF8,0x6A,0xF7,0xD1,0xF4,0xD5,0xF7,0x9C,0xFE,0x6D,0x05, + 0x2D,0x05,0x51,0x08,0x2B,0x0A,0x87,0x09,0xB3,0x0B,0x5D,0x0E, + 0xF0,0x0E,0x28,0x09,0xEC,0x05,0xC1,0x04,0xB1,0x01,0x18,0x00, + 0x3C,0xFF,0x1D,0xFD,0x04,0xF8,0x07,0xF6,0x81,0xF8,0x11,0xF9, + 0xE6,0xF9,0x1D,0xFB,0x69,0xFB,0xF4,0xFB,0x44,0xFE,0x62,0x02, + 0xFE,0x02,0xC4,0x02,0x1E,0x03,0x0F,0x03,0xD3,0x02,0xDD,0x02, + 0x87,0x03,0x55,0x01,0x24,0xFF,0x57,0xFE,0x7A,0xFD,0xF4,0xFC, + 0xF0,0xFC,0x54,0xFD,0x78,0xFC,0x13,0xFC,0x25,0xFD,0x29,0xFE, + 0x78,0xFF,0xAF,0x00,0x4E,0x01,0x39,0x01,0x82,0x01,0x34,0x02, + 0x7F,0x02,0xB9,0x02,0x4E,0x02,0x3C,0x01,0x2E,0x00,0x57,0x00, + 0xCF,0x00,0x87,0x00,0xD9,0xFF,0xA0,0xFC,0x0E,0xFB,0xDD,0x04, + 0xD4,0x0B,0x68,0x09,0xBC,0x06,0xC0,0x02,0x31,0x01,0x50,0xFE, + 0x3C,0x01,0xE6,0x02,0xA7,0xFB,0xA4,0xF6,0xAC,0xF3,0x6F,0xF5, + 0xF9,0xF4,0xC2,0xF7,0x78,0xFA,0x2A,0xF7,0xED,0xF6,0xE4,0xF9, + 0xF0,0x00,0xEB,0x03,0x43,0x07,0x38,0x0A,0x7E,0x08,0x7D,0x08, + 0x49,0x0A,0x65,0x0D,0x83,0x0B,0xCC,0x08,0xBB,0x06,0x8C,0x02, + 0xF5,0xFF,0x8E,0xFF,0xBA,0xFF,0x77,0xFC,0x26,0xF9,0x9B,0xF8, + 0xDE,0xF7,0xAA,0xF8,0x92,0xFA,0xC7,0xFC,0xF6,0xFD,0xD6,0xFC, + 0x1F,0xFE,0x3C,0xFF,0xE1,0x00,0xA2,0x02,0x58,0x03,0x3F,0x03, + 0x2F,0x01,0x56,0x01,0x16,0x01,0x43,0x01,0x03,0x01,0xB9,0xFF, + 0x8F,0xFE,0x24,0xFD,0xBC,0xFD,0x0F,0xFE,0xB7,0xFE,0xB1,0xFE, + 0x26,0xFE,0x3B,0xFE,0xD4,0xFE,0x29,0x00,0xC2,0x00,0x90,0x01, + 0x41,0x01,0xA1,0x00,0x7F,0x00,0xCF,0x00,0x4E,0x01,0x3C,0x01, + 0x67,0x01,0xCB,0x00,0xD7,0xFF,0xAF,0xFE,0x8E,0xFC,0x04,0x01, + 0xCD,0x09,0xDA,0x09,0xA7,0x07,0x73,0x04,0xB6,0x01,0xA6,0xFF, + 0x12,0xFF,0xB0,0x02,0xA4,0xFE,0xCA,0xF9,0x0F,0xF6,0x2F,0xF5, + 0x66,0xF5,0xDE,0xF5,0x06,0xFA,0x9E,0xF8,0xDA,0xF7,0xF3,0xF8, + 0x54,0xFD,0x98,0x01,0x86,0x04,0x2D,0x09,0xC1,0x08,0x43,0x08, + 0x93,0x08,0xBE,0x0A,0x46,0x0B,0xB4,0x09,0xF6,0x08,0xFF,0x04, + 0xB7,0x01,0xC4,0xFF,0xA3,0xFF,0x3C,0xFE,0xC5,0xFB,0xD3,0xFA, + 0xE1,0xF8,0x63,0xF8,0x0F,0xF9,0xC2,0xFB,0x58,0xFE,0x24,0xFD, + 0x63,0xFD,0x8D,0xFD,0x61,0xFE,0x69,0x00,0x2E,0x02,0xA7,0x03, + 0xA5,0x01,0x11,0x01,0x84,0x00,0xA8,0x00,0x55,0x01,0x2A,0x01, + 0xE7,0x00,0xC1,0xFE,0x5F,0xFE,0x0E,0xFE,0xD1,0xFE,0x70,0xFF, + 0x50,0xFF,0x22,0xFF,0x50,0xFE,0x00,0xFF,0x4C,0xFF,0x8F,0x00, + 0x1C,0x01,0x94,0x00,0xFE,0xFF,0x81,0xFF,0x85,0x00,0xD3,0x00, + 0xC0,0x01,0x83,0x01,0x96,0xFF,0x0B,0xFD,0x1F,0xFF,0x84,0x08, + 0x1D,0x0A,0xB0,0x08,0x59,0x06,0xCE,0x01,0x62,0xFF,0xEC,0xFD, + 0x7B,0x02,0x27,0x00,0xC8,0xFB,0x63,0xF8,0x18,0xF5,0xD0,0xF4, + 0xCA,0xF4,0xF1,0xF9,0x68,0xF9,0x9E,0xF8,0x4D,0xF9,0x78,0xFB, + 0x6D,0xFF,0x7C,0x02,0x95,0x08,0x9A,0x08,0x35,0x08,0x00,0x08, + 0x08,0x09,0x26,0x0A,0xB1,0x09,0x8A,0x0A,0x8F,0x06,0x0D,0x03, + 0x5B,0x00,0xA6,0xFF,0x94,0xFE,0x2D,0xFD,0xB0,0xFC,0x42,0xFA, + 0xC1,0xF8,0x16,0xF9,0x30,0xFC,0x84,0xFC,0x70,0xFC,0x32,0xFD, + 0xAE,0xFC,0x38,0xFD,0xDB,0xFE,0xBD,0x01,0xFC,0x01,0x93,0x01, + 0x77,0x01,0x96,0x00,0xA7,0x00,0x35,0x01,0x44,0x02,0x68,0x01, + 0x6A,0x00,0xBB,0xFF,0xCC,0xFE,0x26,0xFF,0x7D,0xFF,0xFD,0xFF, + 0x1C,0xFF,0xAE,0xFE,0x9A,0xFE,0x55,0xFE,0x52,0xFF,0xF1,0xFF, + 0x2D,0x00,0x92,0xFF,0xF8,0xFF,0x2C,0x00,0x40,0x00,0xAA,0x00, + 0x09,0x00,0x45,0xFE,0x45,0x02,0x63,0x09,0xF7,0x08,0xD5,0x07, + 0xF7,0x04,0x80,0x02,0x72,0xFF,0x6F,0xFF,0x45,0x02,0xA3,0xFE, + 0xB1,0xFB,0xED,0xF7,0x98,0xF6,0xA7,0xF4,0x2E,0xF6,0x98,0xF9, + 0x9D,0xF8,0x34,0xF9,0x84,0xF9,0xF1,0xFC,0xE6,0xFE,0x7E,0x03, + 0x6B,0x07,0x8E,0x07,0x0A,0x08,0xE3,0x07,0xB6,0x09,0x23,0x09, + 0x3B,0x0A,0x39,0x09,0x01,0x06,0x02,0x03,0xC6,0x00,0x32,0x00, + 0x27,0xFE,0xCC,0xFD,0x3B,0xFC,0x50,0xFA,0x1B,0xFA,0x28,0xFB, + 0x3E,0xFB,0x85,0xFB,0x48,0xFC,0x4B,0xFC,0x8C,0xFC,0xD3,0xFD, + 0xB3,0xFF,0x1C,0x00,0x24,0x01,0x5F,0x01,0x0D,0x01,0xDA,0x00, + 0x7B,0x01,0x14,0x02,0xB7,0x01,0xB4,0x01,0xF8,0x00,0x26,0x00, + 0x99,0xFF,0xE8,0xFF,0xA3,0xFF,0x1C,0xFF,0xF5,0xFE,0x9D,0xFE, + 0x2E,0xFE,0x4C,0xFE,0x5B,0xFF,0x69,0xFF,0xA1,0xFF,0xA5,0xFF, + 0x16,0x00,0xF5,0xFF,0x92,0xFF,0x08,0xFF,0xF6,0xFF,0x20,0x07, + 0xFF,0x08,0x0A,0x08,0x51,0x06,0x03,0x03,0x64,0x01,0x84,0xFF, + 0x5F,0x02,0x5F,0x00,0x4C,0xFD,0x33,0xFA,0x56,0xF7,0x77,0xF6, + 0xAA,0xF5,0x2A,0xF9,0x8A,0xF8,0xEF,0xF8,0x30,0xF9,0x01,0xFB, + 0xD3,0xFD,0x87,0x00,0x65,0x05,0xC3,0x05,0x5D,0x07,0x81,0x07, + 0xEB,0x08,0x56,0x09,0x93,0x09,0xE8,0x09,0xB9,0x06,0x0D,0x05, + 0x2F,0x02,0x57,0x01,0x65,0xFF,0x87,0xFE,0x6D,0xFD,0x99,0xFA, + 0xF9,0xFA,0x94,0xFB,0x6B,0xFB,0x29,0xFB,0x01,0xFC,0xC3,0xFB, + 0xB7,0xFB,0x6D,0xFD,0xF6,0xFE,0x69,0xFF,0x36,0x00,0xEA,0x00, + 0xC0,0x00,0xD4,0x00,0xBB,0x01,0x1C,0x02,0xDB,0x01,0xDF,0x01, + 0x53,0x01,0xB0,0x00,0x30,0x00,0x76,0x00,0xA9,0xFF,0x2D,0xFF, + 0x08,0xFF,0x95,0xFE,0x57,0xFE,0x64,0xFE,0x1E,0xFF,0xE2,0xFE, + 0x4E,0xFF,0xB4,0xFF,0xA7,0xFF,0x40,0xFF,0x1A,0xFF,0xD2,0xFE, + 0x72,0x04,0xD5,0x08,0x44,0x07,0xAC,0x06,0x93,0x03,0x1B,0x02, + 0x21,0x00,0x2F,0x02,0xF7,0x01,0xF5,0xFD,0xEA,0xFB,0xC5,0xF8, + 0x23,0xF8,0xD2,0xF6,0x84,0xF9,0x4C,0xF9,0x65,0xF8,0x3B,0xF9, + 0x31,0xFA,0x0E,0xFD,0x0F,0xFF,0xA3,0x03,0x4C,0x04,0x61,0x05, + 0xB7,0x06,0xF3,0x07,0xEC,0x08,0xE8,0x08,0x92,0x09,0x9C,0x06, + 0x06,0x05,0x67,0x03,0x1D,0x02,0xAD,0x00,0x30,0xFF,0x3F,0xFE, + 0x09,0xFB,0x61,0xFB,0x20,0xFD,0xE7,0xFB,0xC8,0xFB,0xFD,0xFB, + 0xB2,0xFB,0x62,0xFB,0x62,0xFD,0x7C,0xFF,0xC3,0xFE,0xD2,0xFF, + 0x35,0x00,0x51,0x00,0x89,0x00,0xF9,0x01,0x16,0x02,0x16,0x01, + 0xC1,0x01,0xFA,0x00,0xCA,0x00,0x49,0x00,0xD1,0x00,0x9A,0xFF, + 0x06,0xFF,0x41,0xFF,0x6B,0xFE,0xE7,0xFE,0x22,0xFF,0xD3,0xFF, + 0xEF,0xFE,0x67,0xFF,0x7B,0xFF,0x3D,0xFF,0x95,0xFF,0x3B,0x00, + 0x73,0xFF,0x7D,0x02,0xB0,0x07,0xED,0x05,0x92,0x05,0xA0,0x03, + 0x26,0x02,0x10,0x00,0xDB,0x00,0xE1,0x01,0x5B,0xFD,0x22,0xFC, + 0xE1,0xF9,0x66,0xF9,0x4F,0xF8,0x0F,0xFA,0x80,0xFA,0x9A,0xF8, + 0x70,0xFA,0x75,0xFB,0x06,0xFE,0x88,0xFF,0x10,0x03,0xD0,0x03, + 0x26,0x04,0x9C,0x06,0x59,0x07,0x1B,0x08,0x97,0x07,0xE6,0x07, + 0x53,0x05,0xBC,0x03,0x73,0x03,0x8B,0x01,0x50,0x00,0x23,0xFF, + 0xBC,0xFE,0x6C,0xFD,0x30,0xFC,0x6C,0xFC,0xAF,0xFB,0xB3,0xFB, + 0x6C,0xFC,0xAC,0xFC,0xCB,0xFC,0x50,0xFD,0xCF,0xFE,0x05,0xFF, + 0x23,0x00,0xB8,0x00,0xA4,0x00,0xE0,0x00,0x80,0x01,0xEE,0x01, + 0x4F,0x01,0xDA,0x01,0x23,0x01,0x5B,0x00,0xE1,0xFF,0x2D,0x00, + 0xC1,0xFF,0x0E,0xFF,0x25,0xFF,0xB1,0xFE,0x27,0xFF,0x09,0xFF, + 0x5E,0xFF,0xE1,0xFE,0x1B,0xFF,0x3F,0xFF,0x99,0xFF,0xD0,0xFF, + 0x7A,0x01,0xA3,0x06,0x90,0x05,0x6D,0x04,0x74,0x03,0x83,0x01, + 0xB8,0x00,0x5A,0x00,0x65,0x01,0x6E,0xFD,0x07,0xFC,0x25,0xFB, + 0x50,0xFA,0xB1,0xFA,0xC9,0xFA,0x6A,0xFB,0x8C,0xF9,0x6D,0xFB, + 0xB2,0xFC,0x8B,0xFE,0x8C,0x00,0xE7,0x01,0xD5,0x02,0xDC,0x02, + 0xCD,0x05,0x59,0x06,0xDE,0x06,0x5E,0x06,0x79,0x05,0x0A,0x04, + 0x10,0x03,0x82,0x03,0x96,0x01,0xEB,0xFF,0xB2,0xFE,0xEB,0xFF, + 0xC3,0xFE,0x9D,0xFD,0xE9,0xFD,0x49,0xFC,0x40,0xFC,0xA8,0xFC, + 0xFB,0xFD,0x43,0xFD,0x16,0xFE,0x22,0xFF,0xC8,0xFE,0x53,0x00, + 0xC1,0x00,0xE0,0x00,0x09,0x00,0xF9,0x00,0xCC,0x00,0x79,0x00, + 0x05,0x01,0xD6,0xFF,0x74,0xFF,0x6A,0xFF,0x7F,0x00,0x3D,0x00, + 0xCA,0x00,0xB2,0x00,0xEE,0xFE,0xC4,0xFE,0x54,0xFF,0xFF,0xFF, + 0xFD,0xFF,0x55,0x00,0x61,0xFF,0xF7,0xFE,0x53,0x00,0x5F,0x00, + 0xE9,0x02,0xB7,0x04,0x1D,0x03,0x4A,0x02,0xE5,0x00,0xB9,0x00, + 0xBB,0xFF,0xF0,0xFF,0xEB,0xFE,0x81,0xFC,0x71,0xFC,0xDA,0xFB, + 0x6F,0xFC,0x8D,0xFB,0x23,0xFC,0xCF,0xFB,0x1B,0xFC,0x95,0xFD, + 0xAE,0xFE,0x8C,0x00,0xC3,0x00,0x42,0x02,0xE8,0x02,0x7D,0x04, + 0x40,0x05,0x96,0x05,0x0B,0x05,0xBE,0x03,0xF0,0x03,0x12,0x03, + 0xDE,0x02,0x09,0x01,0xD5,0xFF,0xEA,0x00,0xC2,0x00,0xA8,0xFF, + 0x45,0xFE,0x6B,0xFD,0x64,0xFC,0x97,0xFC,0x7C,0xFE,0x26,0xFE, + 0x64,0xFD,0x28,0xFD,0x2F,0xFE,0x5D,0xFF,0x8D,0x00,0xEA,0x00, + 0x95,0xFF,0xA0,0xFF,0x12,0x00,0x78,0x01,0x25,0x01,0x03,0x01, + 0xD5,0xFF,0x8A,0xFF,0xCE,0x01,0x9D,0x01,0xE5,0x00,0xE9,0xFF, + 0xBC,0xFF,0xE4,0xFE,0x56,0xFF,0xA8,0xFF,0xC2,0xFE,0x1C,0xFF, + 0x14,0xFF,0xB5,0xFF,0x80,0xFF,0x95,0xFF,0xD7,0xFF,0x0B,0x00, + 0x93,0xFF,0x40,0x00,0x90,0x02,0x7A,0x01,0x75,0x01,0x39,0x01, + 0x53,0x00,0x90,0xFF,0xC3,0xFF,0x26,0x00,0x5B,0xFE,0x4D,0xFE, + 0x13,0xFE,0x4C,0xFE,0xFB,0xFD,0x81,0xFE,0x35,0xFE,0x9B,0xFD, + 0x5D,0xFF,0x98,0xFF,0xB0,0x00,0xE5,0x00,0x2C,0x01,0x99,0x01, + 0x61,0x02,0x14,0x03,0x57,0x02,0xD6,0x02,0x0B,0x02,0x6A,0x02, + 0xFF,0x01,0x07,0x01,0xEA,0x00,0x07,0x00,0x4F,0xFF,0x96,0xFE, + 0xC4,0xFE,0x3A,0xFF,0x4C,0xFF,0x5E,0xFE,0x7A,0xFE,0xE1,0xFF, + 0xE6,0xFF,0xB3,0xFF,0x58,0xFF,0xCC,0xFF,0x42,0x00,0xDA,0x00, + 0x2D,0x01,0x42,0xFE,0x0C,0x01,0x5B,0x00,0x80,0xFF,0xC6,0x00, + 0xF3,0xFE,0xE9,0xFE,0x0C,0xFF,0x22,0x02,0xDD,0xFF,0x25,0xFE, + 0x14,0xFF,0x71,0xFF,0x12,0x01,0x89,0xFF,0xED,0xFD,0xEA,0xFF, + 0x86,0xFD,0xE6,0x00,0x32,0x02,0xF8,0xFD,0x71,0x00,0xE6,0xFE, + 0x1D,0x00,0x43,0xFF,0x1E,0x01,0xD4,0x00,0x49,0xFE,0xBB,0x00, + 0x9B,0xFF,0x9B,0x00,0x2D,0x01,0xD0,0xFF,0x44,0xFF,0x97,0x01, + 0xF7,0xFF,0x41,0x00,0xBB,0x01,0xC6,0xFF,0x9E,0xFF,0xB9,0x00, + 0x78,0x00,0x45,0xFF,0x04,0x00,0xD9,0xFF,0x25,0x00,0xF8,0xFF, + 0x3E,0x00,0xA6,0xFF,0x60,0x00,0x4A,0x00,0x9B,0xFF,0x58,0x01, + 0x82,0x00,0xB0,0x00,0xCB,0xFE,0xA0,0x00,0x47,0x01,0xE1,0xFF, + 0x1E,0x02,0xE9,0xFE,0x5B,0xFF,0x8A,0xFF,0x1F,0x00,0xAB,0x00, + 0x19,0xFF,0x20,0xFF,0x2A,0x00,0x69,0x00,0x1F,0x00,0xB8,0x00, + 0x04,0xFF,0x40,0x00,0x8E,0x00,0xC9,0xFE,0x62,0xFE,0xAC,0x01, + 0x71,0x00,0x38,0xFE,0xFA,0x00,0x73,0xFF,0xC1,0xFF,0x7D,0x00, + 0xE2,0xFE,0xC6,0xFF,0xFE,0xFE,0x6A,0x00,0xE2,0x01,0x66,0xFF, + 0xF4,0xFE,0x32,0xFD,0xFC,0x00,0xDE,0x00,0x51,0xFC,0xA0,0x01, + 0x35,0xFF,0xD6,0xFF,0x04,0x03,0x4E,0xFF,0x36,0xFF,0x5B,0x01, + 0x53,0x01,0x73,0xFD,0x61,0x01,0x45,0xFF,0x64,0x00,0x2E,0x01, + 0x6E,0xFD,0x13,0xFF,0xD8,0xFE,0xC2,0x03,0x09,0xFE,0x2C,0xFF, + 0xF4,0x02,0x38,0xFF,0xCD,0x01,0x39,0x01,0xF2,0x00,0x06,0x00, + 0x4C,0x00,0xDA,0x00,0xFD,0xFE,0x08,0x01,0xF6,0xFC,0x90,0xFF, + 0xAF,0x01,0x43,0xFF,0xA3,0xFF,0x03,0xFF,0x57,0x00,0x1E,0x01, + 0x1F,0x02,0x4D,0xFD,0xE2,0x01,0xFD,0xFE,0xB2,0x00,0x25,0x03, + 0x62,0xFE,0x1F,0xFF,0x8C,0xFE,0x86,0x01,0x2A,0x01,0xC0,0x00, + 0xA0,0xFE,0x9D,0xFF,0x4A,0xFF,0x56,0x00,0x73,0xFF,0xD1,0xFD, + 0x0E,0x00,0x51,0xFF,0x9F,0x00,0x83,0xFF,0xE4,0x00,0xE3,0x01, + 0x29,0xFD,0x75,0x00,0x26,0x00,0xA0,0xFF,0xB5,0x00,0x3F,0xFE, + 0x3D,0x00,0x55,0x01,0x8B,0x01,0xD1,0xFE,0xBF,0xFE,0xF2,0xFE, + 0x6F,0x01,0x49,0xFF,0x2B,0x00,0x13,0xFE,0xA1,0xFE,0xB7,0x02, + 0xBB,0xFF,0xA0,0x02,0x20,0x00,0x90,0xFE,0xA4,0x01,0xA8,0x01, + 0x4B,0xFE,0x00,0xFF,0x6B,0xFE,0x6A,0x01,0x79,0x00,0xFC,0xFF, + 0x99,0xFD,0x6D,0x03,0x06,0x02,0xF7,0xFB,0x26,0x02,0xA9,0xFE, + 0xE7,0x00,0x92,0xFD,0xD6,0x00,0xCA,0x02,0x5F,0xFE,0x83,0x00, + 0xAD,0xFE,0x06,0x00,0x45,0x01,0x1A,0xFF,0x57,0x00,0xBE,0xFF, + 0x31,0xFF,0xA2,0x01,0x9C,0x01,0x6C,0xFF,0x22,0x00,0x29,0x00, + 0xC2,0xFE,0x0B,0x01,0x8F,0xFD,0x7B,0xFF,0xBF,0x00,0xDE,0xFE, + 0xAF,0x02,0xB3,0xFE,0xED,0xFF,0x87,0x01,0xC8,0xFF,0x38,0x00, + 0x1F,0x00,0xAD,0xFF,0x3C,0xFF,0x6A,0x01,0xE3,0xFE,0x4E,0xFE, + 0xE8,0x01,0x69,0xFE,0xC0,0xFE,0xB7,0x02,0x7A,0xFE,0x22,0x00, + 0x60,0xFF,0x45,0xFF,0x85,0x01,0x57,0x00,0xF3,0xFF,0x82,0xFE, + 0x69,0x01,0x60,0x01,0xF6,0xFF,0xFC,0xFD,0xC8,0x01,0x60,0xFF, + 0x37,0xFE,0xCF,0x03,0x8C,0xFD,0x75,0x00,0x0A,0x00,0x42,0x00, + 0x60,0xFF,0xE0,0x00,0x1F,0x02,0x19,0xFD,0xD7,0x00,0x5B,0xFE, + 0xD3,0x00,0xE0,0x00,0x9C,0xFE,0xC7,0xFF,0x1B,0x00,0x8F,0xFE, + 0x1C,0x01,0xD5,0x00,0x59,0x00,0x3B,0x00,0xDC,0xFD,0x62,0x00, + 0x01,0x01,0x56,0x01,0xD6,0xFF,0x74,0x00,0x30,0xFF,0xD4,0x01, + 0xE4,0xFE,0xCF,0xFE,0xFB,0x00,0x12,0xFD,0x9B,0xFF,0x4B,0x04, + 0x70,0xFF,0xD4,0xFC,0x11,0x03,0x34,0xFD,0x1D,0x03,0x04,0xFF, + 0x4B,0xFC,0x4D,0x03,0x43,0xFE,0xBE,0x02,0xB6,0xFF,0xA6,0xFD, + 0x28,0x02,0xD6,0xFE,0x4E,0x01,0x18,0x01,0xAD,0xFC,0xDD,0x02, + 0xED,0xFE,0xCA,0xFF,0x3B,0x01,0x43,0xFE,0x73,0x01,0xD6,0xFF, + 0xA8,0xFD,0x8D,0x00,0xBD,0x01,0xC5,0xFD,0xB5,0x01,0x4F,0xFD, + 0x49,0x00,0xE5,0x00,0xC1,0xFF,0xDC,0x01,0x39,0xFD,0xA6,0x02, + 0x64,0xFF,0x14,0x01,0x68,0x01,0xA5,0xFE,0x4D,0xFE,0xD0,0x00, + 0x2C,0x00,0x7C,0x00,0x3D,0x01,0x3A,0xFD,0xF3,0x01,0x7A,0xFF, + 0x2A,0x01,0x67,0xFF,0x4B,0xFF,0xEC,0xFF,0x00,0xFF,0x1B,0x00, + 0x6C,0xFE,0x66,0x01,0xFC,0xFF,0x3E,0x02,0x15,0x00,0x30,0xFE, + 0xB8,0x00,0x47,0xFF,0x80,0x01,0xD3,0xFF,0x38,0xFE,0xAB,0x00, + 0x41,0xFF,0xF8,0xFF,0xDB,0x00,0xCC,0xFF,0xE6,0xFE,0x8C,0xFF, + 0x3E,0x01,0xF9,0xFF,0xF8,0x01,0xE2,0xFF,0xF2,0xFD,0x51,0x00, + 0x0B,0x01,0x3A,0x02,0xC1,0xFF,0xC1,0xFC,0x6D,0x00,0x36,0x01, + 0xBD,0x00,0xC0,0x00,0xFA,0xFC,0x45,0xFF,0x06,0xFF,0xFA,0x00, + 0xEC,0xFF,0x92,0x00,0xAD,0xFE,0x89,0xFE,0x70,0x04,0x61,0xFE, + 0x39,0x01,0x98,0x00,0x4B,0xFC,0x81,0x04,0xB4,0xFE,0xA2,0xFF, + 0x19,0x02,0xBE,0xFC,0x64,0x02,0x0A,0xFF,0x38,0x00,0xA8,0xFE, + 0xF7,0x00,0x52,0x00,0x12,0xFE,0x2E,0x01,0x0C,0xFF,0x8A,0xFE, + 0xC8,0xFF,0x4C,0x02,0xDF,0xFF,0x42,0x00,0x1B,0x01,0x42,0x00, + 0xB4,0xFF,0x20,0x01,0xAE,0xFE,0x7D,0xFF,0xB4,0x01,0x3C,0xFF, + 0xC3,0xFF,0x5F,0x00,0xA5,0x00,0xFC,0xFD,0xF5,0xFD,0x1E,0xFF, + 0x59,0x00,0xB5,0x05,0xFB,0xFB,0xDE,0xFF,0x2A,0x01,0x05,0xFF, + 0x57,0x03,0xF4,0xFB,0x39,0x03,0x12,0xFF,0xDE,0xFF,0x08,0x01, + 0xEE,0xFE,0xBB,0x02,0x17,0xFF,0x5A,0xFD,0x6F,0x00,0xC6,0x00, + 0xF1,0xFF,0xAD,0xFF,0x4A,0xFC,0x7B,0x00,0xA4,0xFF,0x25,0x04, + 0xD6,0xFF,0x07,0xFE,0xF0,0xFF,0x03,0x02,0xC3,0x03,0xD3,0xFA, + 0x89,0x01,0xF4,0xFD,0x49,0x02,0xDE,0x01,0xDF,0xFB,0x92,0x02, + 0xCC,0xFE,0x2A,0x00,0xA4,0xFF,0x64,0x01,0xF2,0xFD,0xC2,0xFF, + 0xCD,0x01,0x4E,0xFD,0x91,0x01,0x05,0x00,0xAE,0xFF,0x99,0xFD, + 0x83,0x01,0x44,0x01,0xF0,0xFD,0x0E,0x03,0x97,0x00,0x2F,0xFF, + 0x46,0xFE,0x6D,0x02,0x9F,0xFF,0x4C,0xFD,0xAE,0x02,0x85,0xFF, + 0xF1,0x01,0x74,0xFE,0xF3,0x01,0x83,0xFD,0xCA,0xFF,0xFE,0x01, + 0xA6,0xFA,0xB9,0x02,0x7A,0xFD,0x7A,0x02,0x28,0xFE,0x54,0x01, + 0x84,0x01,0x05,0xFE,0x79,0x03,0x90,0xFC,0x5E,0x02,0x0C,0x01, + 0x95,0xFE,0x2C,0xFE,0x07,0x02,0x78,0x02,0x66,0xFF,0xE8,0xFE, + 0x62,0xFD,0xDB,0x01,0x71,0x01,0x0A,0xFE,0x63,0xFF,0x71,0xFF, + 0xC6,0xFF,0xFE,0x01,0x95,0xFD,0x56,0x02,0x5B,0xFF,0x33,0xFD, + 0xF9,0x02,0x97,0xFD,0x8A,0x02,0x61,0xFF,0xE7,0xFE,0x63,0x02, + 0xB6,0x00,0xAF,0xFF,0x96,0x01,0x50,0x00,0xEC,0xFB,0x77,0x02, + 0xB5,0xFE,0xD0,0xFF,0x6C,0xFE,0x31,0x00,0xB9,0x00,0x0F,0xFD, + 0x1D,0x03,0xAF,0xFF,0x14,0x00,0x7F,0xFF,0x30,0x00,0x9F,0x01, + 0xAD,0x00,0x2A,0x01,0x3C,0xFD,0x7F,0x00,0x7D,0x00,0xF0,0xFF, + 0xD3,0x01,0x63,0xFD,0xFE,0xFF,0x4A,0xFF,0x50,0x01,0x45,0x01, + 0xE6,0xFD,0x62,0xFF,0x03,0x00,0xA0,0xFF,0x0C,0x01,0x1B,0x01, + 0x07,0xFD,0xCC,0x00,0xE5,0xFD,0x3C,0x04,0x61,0x00,0x1D,0xFD, + 0x9C,0x02,0x75,0xFE,0xCF,0x01,0x9B,0x00,0x3C,0x01,0x5F,0xFC, + 0x54,0xFF,0xB2,0x02,0xBF,0xFF,0x7F,0xFE,0xA7,0x01,0x45,0xFE, + 0x7A,0xFF,0x88,0x03,0xE3,0xFB,0x46,0x01,0x85,0xFE,0x04,0x01, + 0x9A,0xFF,0x98,0xFF,0x9E,0x01,0x8A,0xFE,0x26,0x03,0x1B,0xFD, + 0x0D,0x02,0x1F,0x00,0x11,0xFE,0x16,0x01,0xCA,0xFE,0xA4,0xFF, + 0x49,0x00,0x7D,0x02,0xD8,0xFD,0x57,0x01,0x0C,0xFF,0x27,0xFF, + 0xDF,0x01,0x01,0xFF,0x58,0x01,0xF8,0xFE,0xA9,0x00,0xB2,0x00, + 0x8E,0x01,0xD5,0xFE,0xD4,0x00,0x04,0xFE,0x5E,0xFF,0xAE,0x00, + 0x71,0xFD,0xE8,0xFF,0x7F,0xFE,0xA1,0x04,0x1E,0xFD,0x0C,0x01, + 0xAC,0xFF,0x18,0x00,0x33,0x03,0xBB,0xFF,0xAE,0x00,0xD7,0xFC, + 0x5B,0x01,0x03,0x01,0x4B,0x00,0xAB,0xFB,0x1E,0x00,0xE9,0x01, + 0x9A,0x00,0x35,0xFE,0x9D,0xFF,0x19,0x00,0x9A,0xFF,0xE6,0x01, + 0xCD,0xFF,0x22,0x01,0xC6,0xFE,0x96,0x03,0x08,0x01,0x32,0xFE, + 0x6D,0xFD,0x17,0xFF,0x50,0x00,0x47,0x01,0x8A,0xFF,0xD9,0xFD, + 0x26,0x00,0xCE,0xFF,0x65,0x03,0xBB,0xFF,0xCE,0xFD,0xED,0x01, + 0x8D,0xFF,0x31,0x00,0xB3,0x00,0x94,0xFD,0x81,0xFF,0xF2,0xFE, + 0xDD,0x02,0xDF,0x00,0xE0,0xFE,0x3E,0x01,0xC2,0xFE,0xE7,0x01, + 0x89,0x01,0x55,0xFB,0xE8,0xFF,0x7D,0x02,0xA2,0xFF,0xC2,0x00, + 0xFF,0x00,0xBD,0xFD,0x56,0x01,0xEF,0x00,0x22,0xFD,0x01,0x00, + 0xD7,0xFE,0xBA,0x00,0x46,0x00,0x4F,0x01,0x3F,0xFB,0x17,0x01, + 0x89,0x02,0xF2,0xFE,0x79,0x02,0xE0,0xFE,0x61,0x00,0x2D,0x01, + 0xBF,0xFF,0x79,0x00,0x3A,0x02,0x54,0xFC,0xA9,0x00,0x7D,0xFF, + 0x29,0x00,0x3D,0xFF,0x6B,0xFF,0x59,0x01,0xC5,0xFE,0x31,0x00, + 0x31,0x00,0x17,0x01,0x17,0xFE,0xD3,0x00,0xB7,0xFE,0x89,0x01, + 0xD0,0x01,0x68,0xFE,0x42,0x00,0xBE,0x01,0x13,0xFF,0xE8,0xFE, + 0x18,0x00,0xD9,0xFD,0x1F,0x02,0x0F,0x01,0x11,0xFE,0x65,0x00, + 0x48,0x00,0x9A,0xFF,0xAD,0x01,0x95,0xFF,0x57,0xFC,0x44,0x00, + 0x3E,0x02,0x9C,0x02,0x24,0xFE,0x59,0xFD,0x0E,0x02,0x36,0x00, + 0x15,0x02,0x42,0xFE,0x4F,0xFF,0x22,0x01,0xE7,0xFE,0xA3,0x00, + 0x6A,0x01,0x7A,0xFE,0x37,0xFE,0x25,0x01,0x3A,0x02,0x2D,0x00, + 0x54,0xFC,0xBC,0x02,0xA2,0xFF,0x8D,0xFD,0xE9,0x02,0x1F,0x00, + 0x36,0xFD,0x6B,0x01,0xED,0x00,0xB7,0xFF,0x68,0xFF,0x4F,0xFE, + 0xFC,0x04,0x89,0xFE,0x06,0xFC,0x6B,0x02,0x8A,0xFF,0x1F,0xFF, + 0x6A,0xFF,0x66,0xFE,0x15,0x04,0xF3,0x00,0xEA,0xFE,0x01,0x00, + 0x7B,0x00,0xE7,0xFE,0x47,0xFE,0xA6,0x01,0x8F,0xFC,0xD5,0x05, + 0x3A,0x00,0x3A,0xFD,0x36,0x03,0x57,0xFD,0xB6,0x01,0x75,0xFF, + 0x1A,0xFF,0xA1,0xFE,0xC7,0xFF,0x77,0x01,0xE1,0xFD,0x86,0xFF, + 0xA2,0x00,0xAA,0xFE,0xB5,0x02,0x3A,0x02,0x21,0xFD,0xAB,0x01, + 0x0E,0xFE,0x5F,0x02,0xF1,0xFF,0x45,0xFF,0xDB,0xFF,0x50,0xFE, + 0x20,0x04,0x63,0xFD,0x47,0x02,0x1D,0xFC,0x35,0x01,0x5D,0x02, + 0xD6,0xFD,0x04,0x01,0x27,0xFC,0xD6,0x01,0xA7,0xFD,0x6D,0x03, + 0xC3,0xFE,0xD2,0xFF,0x87,0x02,0x57,0xFE,0xB1,0x03,0x15,0xFE, + 0xA2,0x00,0x2D,0xFD,0xA1,0x04,0xF6,0xFC,0xAC,0xFC,0xD5,0x02, + 0xBE,0xFC,0xCA,0x04,0xA7,0xFD,0x6D,0xFE,0xB6,0x00,0xF0,0xFE, + 0x47,0x01,0x0A,0x02,0x42,0x00,0x56,0x01,0x0A,0xFF,0x13,0x01, + 0xF0,0x01,0x30,0xFC,0xB5,0x00,0x7A,0xFD,0xC2,0xFD,0x3F,0x03, + 0x98,0xFE,0xD5,0x01,0xEC,0x00,0xE7,0xFD,0xF7,0xFE,0x80,0xFF, + 0xF0,0x04,0x73,0xFE,0xDD,0xFD,0xCA,0xFF,0xAA,0x01,0xE2,0x02, + 0x06,0x00,0x68,0xFC,0x52,0xFF,0x11,0x04,0xF3,0xFB,0x0E,0x01, + 0xCC,0xFD,0x36,0x00,0xE9,0x00,0x50,0x00,0x34,0x03,0xE7,0xFB, + 0xC3,0x01,0xB1,0xFC,0x0E,0x04,0xAE,0xFE,0xC1,0xFE,0x4B,0x03, + 0x29,0xFF,0x69,0x04,0xA3,0xFB,0x29,0x02,0x29,0xFF,0x8C,0xFE, + 0x19,0x01,0x18,0x01,0x05,0xFD,0xBA,0xFF,0x53,0x01,0x9A,0xFB, + 0x00,0x03,0x28,0xFE,0x9F,0xFF,0xDC,0xFF,0xC5,0x00,0xC9,0x02, + 0x1D,0xFF,0x9B,0x01,0x38,0x00,0xFA,0xFD,0xA2,0x03,0x67,0xFE, + 0x49,0xFE,0x98,0x00,0x7C,0xFD,0xA9,0x04,0x05,0xFF,0x57,0xFE, + 0x3B,0xFF,0x70,0xFE,0xB1,0x02,0xC1,0x00,0xE6,0xFE,0x89,0xFD, + 0x94,0x02,0xC1,0x01,0x8B,0x00,0x26,0xFF,0x11,0xFC,0xE2,0x03, + 0x1A,0x00,0x5D,0xFD,0x19,0x00,0xA3,0xFF,0x93,0x00,0xFF,0x00, + 0x63,0x00,0xBC,0xFD,0x19,0x02,0xBF,0x01,0xD5,0xFD,0xCB,0xFD, + 0xA6,0x01,0xFD,0x00,0xE0,0xFF,0xA8,0xFF,0xCE,0xFD,0x94,0x04, + 0xF0,0xFE,0xCD,0xFE,0xBB,0x01,0x00,0xFC,0x42,0x00,0xCB,0x01, + 0x7C,0x00,0xF2,0xFF,0xC2,0xFF,0x81,0x00,0x75,0x02,0x39,0xFE, + 0x65,0xFF,0x65,0xFF,0x7F,0x00,0x35,0x01,0x56,0xFC,0xD1,0x04, + 0x94,0xFB,0xFA,0xFF,0x1D,0x01,0x12,0xFD,0x73,0x02,0x55,0xFE, + 0x36,0x02,0xBC,0xFD,0xB6,0x03,0x30,0xFE,0x0C,0x01,0xA1,0x05, + 0xDD,0xFB,0x54,0xFF,0x5C,0x01,0x16,0xFF,0xE2,0xFE,0x4A,0x02, + 0x60,0xFB,0x52,0x00,0x0F,0x01,0x90,0xFC,0xBA,0x02,0xFE,0xFF, + 0x41,0x00,0x9B,0x00,0x9F,0x00,0xED,0xFF,0x40,0xFF,0x5E,0x03, + 0xDF,0xFD,0xD6,0xFF,0x3E,0x02,0xF5,0xFD,0xC0,0xFF,0x82,0xFE, + 0xE4,0xFE,0x87,0x01,0x52,0x00,0x92,0xFE,0xEE,0xFF,0x3A,0x04, + 0x13,0xFF,0x0B,0xFE,0xB3,0x02,0x51,0xFF,0x0E,0xFF,0xFB,0xFE, + 0x6F,0x00,0xB6,0xFE,0x4A,0x01,0x79,0x01,0x96,0xFD,0x5D,0x00, + 0x32,0x00,0x10,0xFE,0x5F,0x01,0xF7,0x00,0xE3,0xFD,0x0D,0x05, + 0xB9,0x02,0x9C,0xF9,0xA2,0x01,0x8A,0xFF,0xB1,0xFD,0xFF,0x01, + 0x9D,0xFE,0xB2,0xFE,0x74,0xFF,0xBF,0x03,0x1F,0xFE,0x57,0x03, + 0xBF,0xFD,0xA0,0xFE,0x80,0x03,0x01,0xFF,0x19,0x00,0x77,0xFD, + 0x13,0x02,0x81,0xFD,0x58,0x03,0xAB,0x00,0x45,0x00,0xC8,0xFF, + 0xB0,0x00,0x4E,0xFE,0x90,0xFD,0x06,0x03,0xD7,0xFA,0xC4,0x01, + 0x5B,0xFE,0x93,0x00,0x26,0x01,0x3C,0xFE,0x73,0x02,0x30,0x02, + 0x21,0x02,0xF4,0xFE,0x00,0xFF,0xAB,0xFE,0xD2,0xFF,0x11,0x02, + 0x8D,0x00,0x3C,0xFB,0x6D,0xFF,0x98,0x01,0xEC,0xFF,0x9C,0x02, + 0x8A,0xFE,0xEF,0xFC,0xA3,0x01,0x0A,0x02,0xE9,0x02,0x75,0x00, + 0x4C,0xFD,0x75,0xFF,0xD8,0xFE,0x12,0x02,0x87,0x00,0x5D,0xFD, + 0xA7,0xFF,0x57,0x00,0xD4,0x00,0x15,0x01,0x9C,0xFD,0xF5,0x00, + 0xF6,0xFF,0x6C,0xFC,0x8F,0x04,0x48,0xFE,0x58,0xFF,0x7C,0x02, + 0x71,0xFE,0xDF,0x01,0x02,0xFF,0x2A,0x00,0xFC,0x01,0x04,0xFF, + 0xAF,0xFD,0x2B,0x02,0x0C,0xFF,0x43,0x00,0xB7,0x00,0x9B,0xFD, + 0x2E,0x01,0x76,0xFE,0x40,0x00,0xB9,0xFF,0x6F,0x02,0xB1,0xFD, + 0x6C,0x00,0xAF,0x00,0xD3,0xFF,0x21,0x02,0x6C,0xFD,0x41,0x02, + 0x23,0xFF,0x80,0x02,0xD5,0xFA,0xC3,0xFF,0x59,0x03,0x72,0x00, + 0x2B,0x01,0xD3,0xFB,0x0A,0x01,0xB0,0xFF,0x3E,0x02,0xF9,0xFE, + 0x4F,0x01,0xAE,0xFD,0x7B,0xFF,0x48,0x03,0x34,0xFF,0x24,0x01, + 0x65,0xFC,0x8C,0xFF,0x48,0x00,0x9B,0x01,0xE9,0x02,0xE5,0xFD, + 0xDC,0xFD,0x3B,0x02,0x5E,0xFE,0x48,0xFF,0xA2,0x02,0xA7,0xFE, + 0x89,0xFF,0x5E,0x00,0x5B,0xFF,0xC8,0x01,0x3E,0xFF,0x9A,0xFD, + 0x50,0x00,0x73,0x00,0xD1,0x02,0xBD,0xFB,0x32,0x00,0x5F,0x01, + 0x38,0x01,0x64,0x03,0x8A,0xFD,0x08,0xFF,0xF4,0xFD,0xCA,0x04, + 0x3A,0xFF,0x4B,0xFF,0x47,0x00,0x59,0xFD,0x9C,0x00,0x6F,0x02, + 0xF0,0xFF,0x4F,0xFB,0xC4,0x03,0xD1,0xFB,0xFD,0x01,0xD4,0x03, + 0xC0,0xFD,0x4B,0x01,0xD1,0xFC,0xD7,0x02,0x0B,0xFF,0x35,0xFF, + 0xF8,0xFE,0x2A,0x01,0xC6,0xFF,0x88,0xFF,0x0B,0x04,0x90,0xFD, + 0x7B,0x01,0x89,0xFC,0xB3,0x02,0x0B,0xFF,0x9C,0xFE,0x45,0x03, + 0xF3,0xFB,0xDD,0x02,0xA1,0xFD,0x44,0x01,0xBF,0x00,0x76,0xFF, + 0xFF,0x00,0xD5,0xFD,0x82,0x01,0x24,0xFD,0xFF,0x02,0x84,0x00, + 0xB6,0xFD,0x50,0x01,0x5A,0xFC,0x55,0x07,0x72,0xFE,0x93,0xFC, + 0x06,0x01,0x7E,0xFD,0x5A,0x05,0x6A,0xFE,0xC6,0xFE,0x47,0xFF, + 0x7C,0xFF,0xF5,0x00,0xAD,0x02,0xD6,0xFD,0x60,0xFD,0x90,0x02, + 0xE0,0x01,0x68,0x01,0xC1,0xFD,0xBA,0xFF,0xDB,0xFD,0xD4,0x00, + 0x4C,0x00,0x8D,0xFF,0xF2,0xFF,0xD8,0xFF,0x08,0x00,0xD3,0x00, + 0x95,0x02,0xCB,0xFD,0x19,0x00,0x44,0xFE,0x02,0x01,0x78,0x00, + 0xDE,0xFF,0x88,0x00,0x07,0xFF,0xDE,0x01,0xC4,0xFE,0xA4,0xFF, + 0xBC,0xFE,0xCA,0x01,0x99,0xFF,0x75,0xFF,0x80,0x00,0x11,0xFF, + 0xB5,0x02,0x31,0xFF,0xEE,0xFF,0x2F,0xFD,0xC1,0xFF,0x03,0x02, + 0xCB,0xFF,0xE2,0x01,0xA9,0xFF,0x47,0x00,0x9C,0xFE,0x41,0x00, + 0x71,0x00,0x89,0xFF,0x64,0xFE,0xFA,0xFF,0x0F,0x04,0xAA,0xFC, + 0xE3,0x00,0xB1,0x00,0x0D,0xFF,0xC7,0x01,0x06,0xFC,0x47,0x01, + 0x18,0x01,0x38,0x00,0x5E,0xFF,0x96,0xFF,0x84,0x00,0x8A,0x00, + 0xE6,0xFF,0x15,0xFD,0x49,0x03,0x17,0xFE,0xDB,0x00,0x49,0x00, + 0xDE,0x01,0x22,0x02,0xCB,0xFC,0x61,0x02,0xEB,0xFC,0x64,0x00, + 0x7A,0xFF,0x4B,0xFF,0xC6,0xFF,0xCD,0xFE,0xC2,0x01,0x1E,0x01, + 0x6F,0xFF,0xE2,0xFF,0xAB,0xFF,0x56,0xFE,0x21,0x02,0xBD,0xFE, + 0x0F,0x01,0xAC,0x02,0xD8,0xFD,0x1D,0x00,0x02,0x02,0x4E,0xFE, + 0x60,0x01,0x45,0xFD,0x70,0xFF,0x7D,0x00,0x99,0xFF,0xAF,0x04, + 0xA1,0xFC,0x27,0xFF,0x75,0xFE,0x57,0x01,0xE8,0x00,0x0C,0x00, + 0xA7,0xFD,0xF7,0x01,0x40,0x03,0x2B,0xFB,0x24,0x05,0x54,0xFB, + 0x44,0xFE,0x50,0x05,0x9C,0xFD,0x72,0x01,0x4D,0xFF,0xA0,0xFD, + 0x02,0x03,0x99,0xFF,0xCC,0xFE,0x73,0x00,0xDB,0xFD,0xBC,0x01, + 0x47,0x02,0xF9,0xFD,0x08,0xFF,0x1C,0x00,0xD0,0x01,0xD6,0xFF, + 0x1D,0xFC,0xBC,0x05,0xF4,0xFC,0xC1,0xFE,0x54,0x03,0xFA,0xFD, + 0x82,0x01,0x0B,0xFF,0xC9,0xFF,0x90,0xFF,0x3F,0x02,0xA3,0xFD, + 0x0D,0x01,0x8F,0x00,0x25,0xFF,0x2D,0x02,0x98,0xFE,0x28,0x00, + 0xDC,0xFE,0xFA,0xFE,0x24,0x00,0xAE,0x01,0x68,0xFE,0x56,0x00, + 0xB9,0xFE,0xC5,0xFF,0x1C,0x04,0xF2,0xFE,0x2D,0x00,0xC2,0xFE, + 0xDE,0xFF,0x35,0x00,0x49,0x01,0xC9,0xFF,0x2C,0xFD,0xC1,0x01, + 0xFF,0xFF,0xCD,0xFF,0x25,0x01,0x4B,0xFF,0x7F,0xFF,0xCF,0xFF, + 0xB0,0x00,0xEF,0x00,0xC4,0xFF,0xDD,0xFD,0x4A,0x01,0x30,0x01, + 0xEB,0xFC,0x53,0x04,0xEE,0xFB,0x56,0x01,0xA0,0x01,0x36,0xFC, + 0xB8,0x04,0x83,0xFC,0x1B,0x02,0x9F,0xFF,0x42,0xFF,0x7C,0x00, + 0x85,0xFF,0x7A,0xFF,0xE1,0xFF,0x19,0x01,0x53,0xFE,0x63,0x03, + 0x0A,0xFD,0xD0,0x01,0x82,0x00,0x75,0xFE,0xFC,0x00,0xFF,0xFD, + 0x7F,0x03,0x42,0xFE,0x4A,0x01,0x1B,0xFE,0x50,0x00,0x17,0x00, + 0x22,0xFE,0x22,0x02,0x6C,0xFB,0x38,0x04,0x70,0x00,0x21,0x00, + 0xF8,0xFF,0x61,0xFD,0x8F,0x02,0xDD,0xFE,0x0E,0x00,0xB2,0xFF, + 0x59,0xFF,0xD9,0x01,0x37,0xFF,0x48,0x00,0x14,0x01,0x8F,0xFF, + 0xED,0x00,0x6A,0x00,0x8B,0xFF,0xB8,0xFD,0x10,0x02,0xC9,0xFE, + 0xF5,0xFF,0xB7,0x00,0xC2,0xFC,0x46,0x03,0x66,0xFD,0x24,0x01, + 0x52,0xFF,0xB7,0x00,0x61,0x02,0xCF,0xFC,0x86,0x04,0xB6,0xFC, + 0x17,0x03,0xA6,0xFD,0x7E,0xFD,0xAA,0x02,0x68,0xFD,0x76,0x03, + 0x0D,0xFE,0xAB,0x00,0x4F,0xFE,0x51,0x00,0xE7,0x00,0x5C,0x00, + 0x5C,0xFF,0xC0,0x01,0x87,0x01,0x2F,0xFD,0xF7,0x01,0xA6,0xFE, + 0xD6,0x00,0x27,0xFF,0xB0,0xFD,0x43,0x01,0xF5,0xFF,0x51,0x00, + 0x18,0x00,0xF6,0xFB,0x16,0x04,0x87,0x01,0x18,0xFE,0x32,0x02, + 0x5B,0xFE,0x57,0x02,0xA5,0xFF,0xB4,0xFB,0xDC,0x00,0xEC,0x03, + 0x1E,0xFF,0xCB,0xFD,0x2C,0xFE,0xF2,0x02,0xEB,0x01,0x2E,0xFE, + 0x62,0xFD,0x41,0xFE,0x4D,0x04,0xFE,0xFE,0xE4,0x00,0x73,0xFF, + 0x60,0xFE,0x15,0x01,0x4F,0x00,0x5C,0x01,0x71,0x00,0x4F,0x01, + 0xF6,0xFC,0x77,0xFF,0xC2,0x02,0xC3,0xFE,0xC4,0x01,0x2F,0xFE, + 0x60,0xFE,0x52,0x01,0x08,0xFE,0xDE,0x01,0xDE,0xFE,0x45,0x01, + 0x97,0xFE,0x9D,0xFE,0x6F,0xFF,0x11,0x01,0x36,0x04,0x2B,0xFD, + 0xA2,0x02,0x13,0xFD,0xCD,0xFF,0xEF,0x03,0xD5,0xFD,0x31,0x01, + 0xA0,0xFD,0xD4,0x00,0x96,0xFF,0xD7,0x00,0xAE,0xFF,0xA0,0xFD, + 0xB9,0x02,0x0F,0xFF,0x08,0x02,0x4F,0xFB,0x67,0x02,0x90,0x01, + 0xD3,0xFC,0xEC,0x01,0x91,0xFD,0xB1,0x03,0xF4,0xFC,0x30,0x01, + 0xD0,0x01,0x40,0xFF,0x0D,0x01,0x36,0xFE,0x5E,0x02,0xE3,0xFC, + 0xD6,0x02,0xD1,0xFE,0x4E,0x00,0xF3,0x00,0xF4,0xFB,0xF9,0x03, + 0xFB,0xFD,0x7C,0x00,0xF2,0xFD,0x24,0x01,0x89,0x00,0x56,0xFF, + 0x9C,0x01,0x32,0xFE,0x06,0x02,0x57,0xFD,0x67,0x03,0xA2,0xFF, + 0x2A,0xFE,0xA6,0xFF,0xC6,0xFF,0x5E,0x02,0xC8,0xFD,0xE8,0x01, + 0xDE,0xFD,0x1A,0xFF,0x55,0x02,0x12,0x00,0x0D,0x00,0x6A,0xFF, + 0x03,0x01,0xE7,0xFE,0xB5,0xFF,0x99,0x00,0xD0,0xFF,0xF3,0x02, + 0x7B,0xFC,0x6E,0xFE,0xDC,0x03,0xEC,0xFF,0xFB,0xFF,0x66,0xFE, + 0xEF,0xFF,0xF2,0xFC,0x75,0x01,0x13,0x01,0x57,0x00,0x79,0x02, + 0x53,0xFB,0x34,0x01,0xBA,0x00,0x2D,0x03,0xB8,0xFE,0x8F,0xFF, + 0xD0,0x00,0xFF,0xFC,0x98,0x03,0x09,0xFC,0xF1,0x00,0xFD,0x01, + 0x5E,0xFD,0x56,0x00,0x5E,0x01,0x57,0x02,0x5A,0xFB,0x3A,0x00, + 0xEC,0x01,0x88,0x00,0xE6,0x02,0x92,0xFB,0x70,0x00,0x68,0x01, + 0x1E,0x00,0x40,0x00,0xE7,0xFD,0x10,0x00,0x7C,0xFF,0x2B,0x02, + 0x39,0xFE,0x26,0x00,0x1C,0x01,0x99,0xFD,0x3F,0x02,0xD3,0x00, + 0x4D,0x01,0x6C,0xFD,0x73,0x00,0x0C,0x00,0x9B,0xFE,0xA4,0x01, + 0xB9,0xFE,0x63,0x00,0x7D,0xFF,0x7B,0x01,0xEB,0xFD,0xD0,0x00, + 0x24,0x00,0x07,0xFF,0x8F,0x02,0x11,0xFE,0x1D,0x01,0x9F,0xFF, + 0x07,0x00,0x6E,0x01,0x1A,0xFF,0x46,0x00,0xF8,0xFE,0x26,0x01, + 0xDB,0xFF,0xDB,0xFD,0xF8,0x00,0x77,0xFF,0x3C,0xFF,0x0B,0x01, + 0xFC,0x00,0xF9,0xFE,0x0D,0x00,0x3F,0x00,0xCB,0xFF,0x8F,0x02, + 0xEE,0x00,0xC5,0xFE,0x03,0xFF,0xBD,0x00,0x13,0xFF,0xA4,0x00, + 0xA5,0x00,0x9C,0xFE,0x36,0x00,0xD8,0xFE,0x81,0x00,0x27,0xFF, + 0xB9,0xFF,0x7F,0x00,0x14,0x00,0x24,0x00,0xE4,0xFE,0x38,0x01, + 0xDB,0xFE,0x4C,0x01,0xCB,0x01,0x52,0xFE,0x65,0x00,0x72,0x00, + 0x37,0x00,0x6F,0xFE,0xB3,0xFF,0x2B,0x01,0x37,0x00,0xCB,0xFF, + 0xD4,0xFF,0x4A,0x00,0x3C,0xFF,0xA5,0x00,0x4D,0x00,0xB5,0x00, + 0x8F,0xFF,0x83,0xFF,0x77,0x00,0x11,0xFF,0x14,0x00,0x0B,0x00, + 0xFE,0x00,0x31,0xFF,0x9A,0xFF,0x02,0x00,0x1C,0xFF,0xDD,0xFF, + 0x0E,0x00,0x38,0x01,0x19,0x00,0xB3,0xFF,0x3E,0x00,0xD4,0xFF, + 0xBE,0xFF,0x5A,0x00,0x19,0x01,0x14,0x00,0x8F,0xFF,0x34,0xFF, + 0x4B,0xFF,0x45,0x00,0xB4,0x00,0xC6,0x00,0x75,0xFF,0x52,0x00, + 0x73,0xFF,0x06,0xFF,0x36,0x01,0x42,0x00,0x35,0x00,0x6A,0x00, + 0xC3,0xFE,0x27,0x00,0x95,0xFF,0x9C,0x00,0xB2,0xFF,0x70,0x00, + 0xFB,0xFF,0x0A,0xFF,0x3E,0x01,0xF4,0xFB,0x32,0x06,0xEE,0xFD, + 0x3E,0xFE,0x17,0x08,0xC7,0xF9,0x9E,0x00,0x7E,0xFF,0x08,0xFB, + 0x37,0x01,0x5F,0x00,0x58,0xFE,0x39,0x00,0xA7,0x00,0xF2,0xFD, + 0x9D,0x00,0x92,0x01,0xD0,0x00,0x9F,0x02,0x7C,0x01,0x59,0x00, + 0x0C,0x00,0x85,0x00,0x6E,0x00,0x3A,0x00,0x37,0x01,0xD2,0xFE, + 0x81,0xFF,0x92,0xFF,0x45,0xFF,0xD9,0xFE,0x5A,0xFE,0x58,0x00, + 0x2E,0xFF,0x6B,0x01,0x3A,0x00,0x48,0xFE,0x4B,0x00,0x91,0x00, + 0x56,0x00,0x3F,0x00,0x4E,0x00,0xB1,0xFF,0x11,0x01,0xD0,0xFF, + 0x0B,0xFF,0xB0,0xFF,0x5B,0x00,0xD8,0xFE,0x4C,0x00,0x76,0x00, + 0xFE,0xFE,0xD7,0xFF,0xD7,0xFF,0xD2,0x00,0xE7,0xFD,0x1B,0x02, + 0xBC,0xFF,0x4B,0xFF,0xCC,0x02,0xB2,0xFE,0xBB,0x01,0x53,0x00, + 0x5F,0xFE,0xF7,0x00,0x46,0x00,0x7C,0x00,0x98,0x00,0xFE,0xFD, + 0xEB,0xFE,0x76,0x00,0x92,0x00,0xF9,0x00,0xE2,0xFF,0xF2,0xFE, + 0x70,0xFE,0x5B,0xFF,0x4C,0x01,0xD7,0x00,0xB1,0x00,0x80,0x00, + 0x61,0xFF,0x48,0xFF,0xDC,0xFF,0x85,0x00,0x88,0x00,0xCD,0x00, + 0x18,0x00,0x70,0xFF,0xF9,0xFF,0xB6,0xFF,0x2A,0xFF,0xFA,0xFF, + 0x92,0x00,0x1D,0x00,0x06,0x00,0xF3,0xFF,0x9B,0xFF,0x5A,0x00, + 0x9E,0xFF,0x75,0xFF,0x87,0x00,0x17,0x00,0xE9,0x00,0x5A,0x00, + 0x28,0xFF,0xB8,0xFF,0xA1,0xFF,0xCC,0xFF,0xAB,0x00,0x90,0xFF, + 0xA9,0xFF,0x1B,0x00,0xAC,0xFE,0xD1,0xFF,0x94,0xFF,0x88,0xFF, + 0x91,0x00,0xC6,0xFF,0xE5,0xFF,0xC6,0xFF,0x99,0xFF,0x29,0x00, + 0xA4,0x00,0xA9,0x00,0x8F,0x00,0x21,0x00,0x9E,0xFF,0xE5,0xFF, + 0x19,0x01,0x6B,0x00,0xE0,0xFF,0x90,0x00,0x34,0x00,0x71,0x00, + 0x03,0x00,0x33,0x00,0x52,0x00,0xD8,0x00,0x7B,0x00,0x9C,0xFF, + 0x9E,0x00,0x21,0x00,0x8F,0xFF,0x5F,0xFF,0x93,0xFF,0x90,0xFF, + 0x40,0xFF,0x2C,0xFF,0x79,0xFF,0x37,0x00,0xF8,0x00,0x33,0x02, + 0x0B,0x03,0xAE,0x03,0xCC,0x02,0xBA,0x00,0x3E,0xFF,0x7C,0xFD, + 0xB3,0xFC,0x24,0xFD,0xF2,0xFC,0x23,0xFD,0x64,0xFD,0x9A,0xFC, + 0x47,0xFC,0x1C,0xFC,0xEB,0xFB,0x21,0xFD,0xC7,0xFE,0x8F,0x00, + 0x8D,0x02,0xAB,0x03,0x79,0x04,0x81,0x04,0x20,0x04,0xD6,0x03, + 0xB8,0x03,0x6E,0x04,0xB5,0x04,0x8C,0x04,0xB1,0x03,0x93,0x01, + 0x55,0xFF,0x53,0xFD,0xC8,0xFB,0x6B,0xFB,0xAF,0xFB,0x7B,0xFC, + 0xB4,0xFD,0x5B,0xFE,0x84,0xFE,0x75,0xFE,0x84,0xFE,0xB3,0xFE, + 0x99,0xFF,0xDD,0x00,0x9E,0x01,0x24,0x02,0xCD,0x01,0x40,0x01, + 0x3A,0x01,0x97,0x00,0x3F,0x00,0x6D,0x00,0x7B,0x00,0x90,0x00, + 0x98,0xFF,0xF7,0xFE,0x6D,0xFE,0x6A,0xFD,0xDA,0xFD,0x7A,0xFF, + 0x26,0x02,0xC6,0x04,0xB4,0x06,0x16,0x07,0xDA,0x04,0xBE,0x01, + 0x42,0xFE,0x17,0xFB,0x81,0xFA,0xFB,0xFA,0x9F,0xFB,0x0B,0xFD, + 0xAE,0xFC,0x70,0xFB,0x3D,0xFA,0xE6,0xF8,0xF0,0xF8,0xAB,0xFA, + 0xB3,0xFD,0xE2,0x00,0x20,0x04,0x5B,0x06,0xD9,0x06,0xEC,0x06, + 0x58,0x06,0x69,0x05,0x9F,0x05,0xB0,0x05,0x67,0x05,0xB8,0x05, + 0xB7,0x04,0xE8,0x02,0x28,0x01,0xAE,0xFE,0x93,0xFC,0x4B,0xFB, + 0x9E,0xFA,0x84,0xFA,0x29,0xFB,0xCE,0xFB,0xFF,0xFB,0xD2,0xFC, + 0x3B,0xFD,0xA1,0xFD,0xF1,0xFE,0xE1,0xFF,0x24,0x01,0x64,0x02, + 0xAD,0x02,0xC7,0x02,0xA3,0x02,0x2F,0x02,0x9D,0x01,0x4F,0x01, + 0xE6,0x00,0x5D,0x00,0x08,0x00,0x1C,0xFF,0x6E,0xFE,0x12,0xFE, + 0x69,0xFD,0x11,0xFD,0x31,0xFD,0x3C,0xFF,0x77,0x02,0x86,0x05, + 0x53,0x08,0x54,0x08,0x3C,0x06,0xBE,0x02,0x87,0xFE,0x85,0xFB, + 0x2E,0xFA,0x9E,0xFA,0x34,0xFB,0x2B,0xFC,0xD3,0xFB,0x31,0xFA, + 0xC8,0xF8,0x4A,0xF7,0x64,0xF7,0x74,0xF9,0xD7,0xFC,0xB7,0x00, + 0x81,0x04,0xF0,0x06,0xBC,0x07,0xBD,0x07,0x15,0x07,0x69,0x06, + 0xAE,0x06,0xCD,0x06,0x0A,0x07,0x63,0x07,0xE9,0x05,0x08,0x04, + 0x40,0x01,0x0C,0xFE,0x8F,0xFB,0xE3,0xF9,0x71,0xF9,0x86,0xF9, + 0xB8,0xFA,0x31,0xFB,0xB4,0xFB,0x5A,0xFC,0x4E,0xFC,0x5B,0xFD, + 0x84,0xFE,0x0C,0x00,0xD7,0x01,0x3F,0x03,0xDB,0x03,0xD3,0x03, + 0x78,0x03,0x87,0x02,0x0C,0x02,0x99,0x01,0xEE,0x00,0x81,0x00, + 0xB2,0xFF,0xA9,0xFE,0xDC,0xFD,0x0F,0xFD,0x33,0xFC,0xBE,0xFB, + 0xD9,0xFD,0x92,0x01,0x31,0x05,0x0B,0x09,0xB4,0x09,0x8A,0x07, + 0xC1,0x03,0xF3,0xFE,0x60,0xFB,0x9B,0xF9,0x77,0xFA,0x25,0xFB, + 0x2B,0xFC,0xF2,0xFB,0x5A,0xF9,0xA3,0xF7,0x97,0xF5,0x94,0xF5, + 0x29,0xF8,0x16,0xFC,0xCF,0x00,0xD3,0x04,0x9E,0x07,0x28,0x08, + 0xE6,0x07,0x7E,0x07,0xD2,0x06,0xBB,0x07,0x71,0x08,0x7C,0x08, + 0xE1,0x08,0xDB,0x06,0xFA,0x03,0xFA,0x00,0x51,0xFD,0xFF,0xFA, + 0x95,0xF9,0x78,0xF9,0xA1,0xF9,0x68,0xFA,0xE5,0xFA,0x82,0xFA, + 0x60,0xFB,0x8B,0xFB,0xAE,0xFC,0xDD,0xFE,0x63,0x00,0x5F,0x02, + 0x9A,0x03,0xD8,0x03,0x8E,0x03,0x25,0x03,0x9A,0x02,0x34,0x02, + 0x55,0x02,0xB3,0x01,0x03,0x01,0x24,0x00,0x7F,0xFE,0x63,0xFD, + 0xA7,0xFC,0xF0,0xFB,0x8B,0xFB,0x35,0xFE,0x47,0x02,0xA1,0x05, + 0xA6,0x09,0x61,0x09,0x9D,0x06,0xEB,0x02,0x05,0xFE,0x78,0xFB, + 0x38,0xFA,0xC6,0xFB,0x2D,0xFC,0x3C,0xFC,0x80,0xFB,0x72,0xF7, + 0x20,0xF6,0x7F,0xF4,0x51,0xF5,0x6A,0xF9,0x46,0xFD,0x13,0x02, + 0xEF,0x04,0xD1,0x06,0xC3,0x06,0x67,0x06,0x46,0x07,0x77,0x07, + 0x7E,0x09,0x98,0x0A,0xA8,0x09,0xAE,0x08,0xD9,0x05,0x3C,0x02, + 0xD0,0xFF,0xBD,0xFD,0x4C,0xFC,0xEC,0xFB,0x52,0xFB,0x7E,0xFA, + 0xB1,0xF9,0x3D,0xF9,0xD2,0xF8,0xAF,0xF9,0xCC,0xFB,0x1B,0xFD, + 0x90,0xFF,0x08,0x01,0x49,0x01,0x5B,0x02,0x43,0x02,0x9B,0x02, + 0x32,0x03,0x91,0x03,0xAF,0x03,0x0D,0x03,0x63,0x02,0x8D,0x00, + 0x61,0xFF,0x6E,0xFE,0x76,0xFD,0x8E,0xFD,0x2F,0xFD,0xA4,0xFC, + 0x21,0xFD,0x8F,0x00,0xA8,0x03,0x01,0x07,0x7F,0x09,0x3C,0x07, + 0xBF,0x04,0x6D,0x00,0x26,0xFD,0xEB,0xFB,0xAD,0xFB,0x57,0xFD, + 0xF9,0xFB,0x92,0xFB,0xAA,0xF8,0x19,0xF5,0x3A,0xF5,0xB6,0xF4, + 0x4E,0xF8,0x70,0xFC,0xF6,0xFF,0x2D,0x03,0x1D,0x04,0x39,0x05, + 0xF0,0x04,0x73,0x06,0x7D,0x08,0xBD,0x09,0xC8,0x0B,0xAE,0x0A, + 0x84,0x08,0x78,0x06,0x25,0x03,0x0D,0x01,0x14,0x00,0xE3,0xFE, + 0xEC,0xFD,0x86,0xFC,0xBB,0xFA,0xE4,0xF8,0xF2,0xF7,0x6F,0xF8, + 0x07,0xF9,0x6A,0xFB,0x40,0xFD,0xE3,0xFD,0x89,0xFF,0x6E,0xFF, + 0x1F,0x00,0xAA,0x01,0x9C,0x02,0x0F,0x04,0x6C,0x04,0x68,0x04, + 0x38,0x03,0x21,0x02,0x2E,0x01,0xEA,0xFF,0xF5,0xFF,0x6A,0xFF, + 0xE0,0xFE,0x2A,0xFE,0xFB,0xFC,0x87,0xFB,0x4B,0xFD,0xB3,0x01, + 0x94,0x04,0xCE,0x08,0xC5,0x08,0x54,0x05,0xC9,0x01,0x1A,0xFD, + 0x18,0xFC,0xE2,0xFB,0x29,0xFE,0xE9,0xFE,0x56,0xFC,0x0C,0xFB, + 0xC4,0xF5,0x08,0xF4,0x55,0xF5,0x09,0xF7,0x6F,0xFC,0x84,0xFF, + 0xF5,0x01,0x2D,0x02,0x0C,0x02,0x25,0x03,0xE6,0x03,0x0A,0x08, + 0x6D,0x0A,0xAC,0x0B,0x67,0x0B,0x2F,0x08,0xF3,0x05,0x50,0x03, + 0x9E,0x02,0x68,0x02,0xB7,0x01,0xBE,0x00,0x9B,0xFD,0x3D,0xFB, + 0xAB,0xF8,0xB0,0xF7,0xD8,0xF8,0x18,0xFA,0xD5,0xFB,0xA0,0xFC, + 0xD1,0xFC,0x7D,0xFC,0xFD,0xFC,0xB2,0xFE,0xA4,0x00,0x12,0x03, + 0x58,0x04,0x2B,0x04,0x8C,0x03,0x5B,0x02,0xB7,0x01,0xE9,0x01, + 0x22,0x02,0x15,0x02,0x44,0x01,0xDE,0xFF,0xF3,0xFD,0xDF,0xFC, + 0xB8,0xFB,0x27,0xFB,0x6E,0x00,0xFB,0x04,0x73,0x06,0x30,0x09, + 0xAA,0x06,0x95,0x00,0x43,0xFD,0x0C,0xFD,0xC1,0xFE,0x94,0x01, + 0x35,0x03,0xED,0xFC,0xCD,0xF5,0xB8,0xF2,0xE8,0xEE,0xB9,0xF3, + 0x50,0xFB,0xE8,0xFC,0xDA,0xFD,0xDC,0xFC,0xD2,0xFB,0x87,0xFE, + 0xA6,0x07,0x46,0x0E,0x0F,0x10,0xEA,0x10,0xD8,0x0A,0xAF,0x05, + 0xC6,0x07,0x72,0x0B,0xA5,0x09,0x3E,0x06,0xC3,0xFF,0xF3,0xF3, + 0xD0,0xF2,0x2C,0xF5,0x23,0xF6,0xB5,0xF9,0x52,0xF8,0xB6,0xF5, + 0x5F,0xF7,0x84,0xFE,0xC3,0x02,0x14,0x07,0x8F,0x09,0x9E,0x03, + 0xE3,0x03,0xC9,0x05,0xE2,0x05,0x56,0x06,0x4A,0x03,0xF5,0xFD, + 0xAF,0xF9,0xE0,0xFB,0xE6,0xFB,0x79,0xFC,0xBC,0xFD,0x34,0xFA, + 0x85,0xF9,0xCD,0xFB,0xC1,0xFD,0xD1,0xFD,0xAB,0x0D,0x88,0x1A, + 0x58,0x10,0x94,0x0F,0x4A,0x09,0xF5,0xFA,0xE3,0xFE,0xC6,0xFF, + 0x48,0xF4,0xD1,0xEB,0xA8,0xE9,0x00,0xE5,0x26,0xEA,0x13,0xF7, + 0x83,0xF3,0x44,0xF5,0x6F,0xFE,0x88,0x01,0xBB,0x0B,0x5E,0x16, + 0x0F,0x16,0x90,0x10,0xD6,0x13,0xF7,0x12,0x55,0x0E,0x06,0x10, + 0x42,0x05,0x9D,0xF8,0x3E,0xF7,0x4C,0xF7,0xB1,0xF2,0x6B,0xF2, + 0x39,0xF3,0x83,0xED,0x57,0xF5,0xE9,0xFD,0xDA,0xFE,0x00,0x05, + 0xD4,0x06,0x42,0x07,0xF5,0x0A,0x69,0x0E,0xBD,0x09,0xBD,0x05, + 0x5B,0x05,0x9A,0xFE,0x8F,0xFE,0x27,0xFE,0xB4,0xF7,0x00,0xF5, + 0x86,0xF5,0x50,0xF6,0x89,0xF8,0xC2,0xFD,0xA3,0xFC,0x9A,0xFD, + 0xAF,0x03,0x8E,0x04,0x38,0x07,0xE4,0x07,0x03,0x05,0xBC,0x00, + 0x1F,0x00,0x89,0x13,0x21,0x1C,0x84,0x09,0xDE,0x02,0x3A,0xFD, + 0xAE,0xF2,0xFA,0xFA,0xC2,0xF9,0xDB,0xE8,0x63,0xE3,0xAD,0xEA, + 0xFC,0xEF,0xD4,0xF5,0x44,0xFD,0xF5,0xF5,0x7B,0xFA,0x96,0x0B, + 0x3A,0x10,0x74,0x12,0x97,0x12,0xE4,0x0E,0x75,0x0C,0xD3,0x13, + 0xE8,0x10,0x62,0x03,0x7E,0xFF,0x77,0xF8,0x3F,0xF8,0x0D,0xFC, + 0xFE,0xF5,0xCF,0xEE,0x72,0xF1,0x28,0xF9,0x54,0xFB,0x25,0x01, + 0x4C,0x01,0x1D,0xFF,0x37,0x07,0xD0,0x0C,0xFB,0x0B,0x05,0x08, + 0x3D,0x05,0xF5,0x01,0xFE,0x02,0x55,0x03,0x57,0xFB,0xF7,0xF7, + 0xB2,0xF7,0x50,0xF7,0x20,0xF9,0x67,0xF9,0xF6,0xF7,0xD3,0xFA, + 0x48,0x01,0x37,0x03,0x08,0x04,0x27,0x05,0x90,0x03,0x20,0x05, + 0x67,0x07,0x79,0x01,0x7C,0xFC,0x86,0x11,0xC9,0x1C,0x2F,0x04, + 0x78,0x00,0x5B,0x02,0x36,0xF4,0x4A,0xF9,0x32,0xFA,0x79,0xE7, + 0x70,0xE3,0x60,0xF1,0x72,0xF3,0x21,0xF2,0xD4,0xF9,0xC8,0xF6, + 0xEE,0xFC,0x02,0x0F,0x5D,0x10,0xEC,0x0A,0xF2,0x0E,0x53,0x13, + 0x6E,0x0F,0xB9,0x12,0xB3,0x0D,0x65,0xFF,0x24,0x00,0xA7,0xFE, + 0x37,0xFA,0x4C,0xF9,0x2A,0xF3,0xF4,0xEE,0x64,0xF6,0xA6,0xFC, + 0xD3,0xF9,0x9A,0xFC,0xB2,0x00,0x10,0x03,0x04,0x09,0xE6,0x0B, + 0xC8,0x07,0x15,0x05,0x84,0x07,0x05,0x05,0x52,0x02,0x70,0xFF, + 0xEE,0xF9,0x6D,0xF9,0xF3,0xFA,0xEC,0xF8,0x28,0xF6,0xA2,0xF7, + 0x90,0xF9,0x29,0xFE,0x02,0x02,0x7E,0x00,0x33,0x02,0x7A,0x05, + 0x89,0x06,0xF4,0x04,0x28,0x05,0xFE,0xFE,0xED,0xFE,0xEE,0x1B, + 0xA0,0x1A,0xFB,0xFA,0x48,0x00,0xFD,0x04,0x35,0xF7,0x27,0xF8, + 0x35,0xF3,0xE5,0xE2,0x0E,0xE7,0x46,0xF7,0x55,0xF1,0x55,0xED, + 0x6E,0xF8,0x5B,0xFA,0x66,0x03,0xA6,0x0F,0x6A,0x0D,0x2C,0x09, + 0xA8,0x12,0xD1,0x18,0x45,0x0F,0xED,0x0E,0x36,0x0A,0x95,0x01, + 0x60,0x02,0xAA,0xFD,0x83,0xF5,0xA8,0xF1,0xCE,0xF7,0xD4,0xF5, + 0xA2,0xF1,0xED,0xF9,0x48,0xFB,0x04,0xFD,0x25,0x04,0x93,0x06, + 0x74,0x05,0x5A,0x09,0x4A,0x0C,0xF0,0x06,0x0F,0x06,0x50,0x04, + 0x66,0xFF,0x7E,0xFE,0xC6,0xFC,0xED,0xF8,0x4A,0xF7,0xDA,0xF8, + 0x75,0xF7,0x0B,0xF7,0x52,0xFB,0x69,0xFE,0xB5,0xFF,0xE0,0x01, + 0x75,0x04,0x30,0x05,0x75,0x06,0x71,0x05,0xC4,0x03,0xE7,0xFE, + 0xE7,0x02,0xFE,0x1D,0x07,0x16,0x89,0xF7,0x23,0x02,0xC8,0x06, + 0x92,0xF8,0x5D,0xF4,0x67,0xEE,0x3F,0xE6,0xA5,0xEB,0xF6,0xF6, + 0xF0,0xEC,0xDB,0xEB,0x4A,0xFB,0x7F,0xFE,0xF5,0x03,0x32,0x0A, + 0xAE,0x0C,0x93,0x0D,0xDD,0x14,0x17,0x17,0x8A,0x0C,0x53,0x0E, + 0x9A,0x0C,0x49,0x04,0xE8,0xFF,0x2D,0xFA,0x89,0xF6,0xE5,0xF6, + 0x9C,0xF5,0x3C,0xEF,0x17,0xF5,0x11,0xFB,0x3C,0xFA,0x3D,0xFE, + 0xCA,0x01,0x99,0x06,0x58,0x08,0xBB,0x0A,0x57,0x09,0xF4,0x06, + 0x2A,0x08,0x8C,0x03,0x2C,0x01,0xC1,0xFD,0xFB,0xFA,0xCE,0xF9, + 0x8F,0xF8,0xDD,0xF7,0x43,0xF5,0xC4,0xF8,0x47,0xFB,0x08,0xFD, + 0x08,0x00,0x5C,0x01,0x06,0x04,0x96,0x05,0x88,0x06,0x45,0x04, + 0xC9,0x04,0xCD,0xFF,0x8B,0x04,0x10,0x20,0x75,0x12,0x45,0xF6, + 0xC1,0x04,0x00,0x08,0x07,0xF8,0x89,0xF1,0xC8,0xED,0x56,0xE9, + 0x5C,0xED,0xA0,0xF4,0x55,0xEA,0x5F,0xEC,0x89,0xFD,0x1F,0xFF, + 0x78,0x01,0x81,0x07,0xF8,0x0D,0xDC,0x10,0xE6,0x13,0xD2,0x14, + 0x25,0x0D,0x32,0x10,0x75,0x0E,0x7F,0x04,0x8A,0xFE,0x8E,0xFA, + 0xA7,0xF8,0xDA,0xF3,0xDB,0xF4,0x7D,0xF4,0x1F,0xF2,0x88,0xF7, + 0xE4,0xFC,0xAB,0xFD,0x8C,0xFF,0xEF,0x06,0x95,0x08,0x65,0x08, + 0x51,0x0A,0x08,0x09,0xE2,0x06,0x88,0x04,0x21,0x02,0x72,0xFD, + 0x19,0xFC,0xDF,0xFA,0xFD,0xF7,0x87,0xF7,0x91,0xF6,0xB6,0xF7, + 0xD1,0xF9,0xAF,0xFD,0x52,0xFF,0xAE,0xFF,0x78,0x04,0x20,0x06, + 0x8D,0x05,0x8B,0x04,0x16,0x05,0x2C,0x00,0x8D,0x05,0x36,0x21, + 0xB8,0x11,0x80,0xF4,0x44,0x07,0x0F,0x09,0x80,0xF7,0xF3,0xF0, + 0x3A,0xEC,0x2C,0xEC,0x17,0xEE,0x7A,0xF2,0xD4,0xE8,0x05,0xEC, + 0xD1,0xFE,0x4A,0xFE,0xA8,0x00,0x38,0x05,0x87,0x0E,0xB0,0x13, + 0x4A,0x12,0x36,0x14,0x04,0x0E,0xF7,0x11,0x7E,0x0F,0xB1,0x04, + 0x69,0xFE,0xAB,0xFA,0x0B,0xFA,0x9B,0xF4,0x75,0xF4,0xFA,0xEE, + 0xC0,0xF2,0x9C,0xFB,0x63,0xF8,0x9D,0xFC,0xE0,0xFF,0x2C,0x07, + 0xDB,0x09,0x7C,0x08,0x32,0x0A,0x2C,0x08,0xAB,0x0A,0xDA,0x04, + 0xE4,0x00,0x2D,0xFF,0xE8,0xFB,0xBC,0xFB,0xEC,0xF6,0x1F,0xF7, + 0x95,0xF6,0xE7,0xF7,0xE5,0xF9,0x3E,0xFA,0xA3,0xFF,0xFE,0xFF, + 0x70,0x02,0xA3,0x05,0x59,0x05,0xBA,0x05,0xC9,0x04,0xD1,0x02, + 0x84,0x00,0xC9,0x15,0xD0,0x1E,0xD3,0xFB,0x58,0xFC,0x59,0x0E, + 0x0F,0xFE,0xE9,0xF3,0x11,0xEE,0x00,0xEC,0x0B,0xEF,0x35,0xF1, + 0x26,0xED,0x6C,0xE6,0xE6,0xF8,0xC0,0xFF,0x42,0xFC,0x9D,0x02, + 0x9D,0x08,0xB0,0x13,0x0D,0x12,0x6E,0x12,0xBE,0x10,0x34,0x10, + 0x4F,0x14,0x5F,0x08,0xAF,0x01,0xB0,0xFC,0x93,0xFC,0xDA,0xF6, + 0x92,0xF0,0xFA,0xF6,0xE9,0xEF,0x28,0xF4,0x6C,0xFB,0x4F,0xF9, + 0xF3,0xFD,0xFB,0x02,0x7C,0x08,0x61,0x07,0xD6,0x09,0xFD,0x0A, + 0xCF,0x07,0xAF,0x08,0x4D,0x03,0x67,0x00,0xDC,0xFE,0xE6,0xFB, + 0xAD,0xF9,0xE2,0xF6,0x8D,0xF7,0x06,0xF7,0x47,0xF8,0x56,0xFA, + 0xE9,0xFB,0xB9,0xFF,0xBD,0x00,0xD2,0x02,0xE8,0x04,0x70,0x06, + 0x39,0x05,0x33,0x03,0xC0,0x02,0xB2,0x03,0xFA,0x1B,0xFC,0x16, + 0xCA,0xF4,0x11,0x05,0x11,0x0C,0x53,0xFA,0x7F,0xF3,0xC5,0xEB, + 0xE6,0xEE,0x66,0xF0,0xC5,0xF1,0x4B,0xE8,0xDF,0xE9,0xC5,0xFE, + 0x11,0xFC,0x8A,0xFD,0x2C,0x02,0xDF,0x0A,0xC5,0x14,0xFF,0x10, + 0xB8,0x11,0xA5,0x0E,0xD4,0x14,0x13,0x12,0x83,0x05,0xA3,0x01, + 0x12,0xFD,0xD6,0xFD,0x34,0xF4,0xCD,0xF0,0xDA,0xF2,0x9B,0xF5, + 0xEA,0xF9,0x30,0xF2,0xF7,0xFA,0x78,0x02,0x53,0x03,0xA2,0x06, + 0x08,0x05,0xF4,0x0A,0x10,0x0B,0x66,0x09,0x1D,0x05,0xBC,0x01, + 0xD7,0x03,0x2E,0xFE,0x76,0xFB,0x41,0xF8,0xA2,0xF7,0x61,0xF9, + 0xFE,0xF6,0x6E,0xF7,0x2C,0xF9,0xAE,0xFE,0xFF,0xFF,0x76,0xFF, + 0xC1,0x02,0x04,0x04,0xAF,0x07,0x4E,0x04,0xC4,0x01,0xBE,0x02, + 0x9C,0x08,0xE9,0x1F,0x7F,0x0E,0xC2,0xF4,0xDF,0x0A,0x54,0x09, + 0x5B,0xF9,0x35,0xF1,0xC3,0xEB,0x2F,0xF1,0x9D,0xF0,0xA1,0xEF, + 0x32,0xE5,0x9B,0xEE,0x96,0xFF,0x4F,0xF9,0x0D,0xFE,0x7D,0x02, + 0x88,0x0D,0x7C,0x14,0x60,0x10,0x0F,0x11,0xF0,0x0F,0xB9,0x17, + 0x00,0x0F,0x74,0x04,0x92,0x02,0xC2,0xFD,0x82,0xFD,0xAA,0xF1, + 0xBF,0xF0,0x34,0xF3,0xE2,0xF5,0x05,0xFA,0x43,0xF1,0xD0,0xFA, + 0x43,0x04,0xF1,0x03,0x0C,0x05,0x45,0x05,0xF8,0x0B,0xC8,0x0B, + 0xB7,0x08,0x42,0x04,0x39,0x02,0x6D,0x04,0x23,0xFE,0xE7,0xF9, + 0x66,0xF8,0x70,0xF8,0x84,0xF9,0xAA,0xF6,0xB7,0xF6,0xF9,0xF9, + 0xCC,0xFE,0x8C,0xFF,0x10,0xFF,0xD4,0x02,0x6A,0x04,0x51,0x07, + 0xD2,0x03,0xF7,0x01,0xB5,0x03,0xED,0x08,0x72,0x20,0x37,0x0D, + 0xDF,0xF4,0xDF,0x0C,0x95,0x07,0x9F,0xF9,0x3A,0xF2,0x6E,0xEB, + 0x47,0xF1,0xD2,0xF0,0xD2,0xEF,0xC2,0xE3,0x76,0xEF,0x60,0xFF, + 0xBD,0xF7,0x8D,0xFE,0x2F,0x02,0x25,0x0D,0x14,0x14,0xED,0x10, + 0xD0,0x10,0xB0,0x0F,0x28,0x19,0xB2,0x0E,0xB1,0x04,0x88,0x03, + 0xBB,0xFD,0xA8,0xFD,0x8A,0xF1,0x41,0xF2,0x98,0xF5,0xDE,0xF1, + 0x54,0xF6,0x56,0xF5,0xE9,0xFA,0xBE,0x00,0x83,0x03,0x68,0x05, + 0x1B,0x06,0x7C,0x0C,0x75,0x0B,0xE5,0x07,0x30,0x06,0xDB,0x03, + 0x47,0x03,0x1B,0xFF,0x80,0xFA,0x96,0xF8,0x16,0xF9,0xD7,0xF8, + 0xE1,0xF5,0xDB,0xF6,0x30,0xFA,0xC3,0xFC,0x8E,0xFE,0xFA,0xFE, + 0x66,0x02,0x81,0x04,0x42,0x06,0x99,0x04,0x9C,0x02,0x9E,0x04, + 0xCC,0x07,0x08,0x1E,0x43,0x12,0x32,0xF5,0x75,0x0A,0x31,0x0A, + 0x1F,0xFA,0xFB,0xF3,0xC1,0xEB,0x58,0xF0,0xD3,0xF0,0xA6,0xF0, + 0x34,0xE4,0xF9,0xEB,0x2F,0xFF,0x4A,0xF7,0xBD,0xFC,0x54,0x02, + 0xA4,0x0A,0x46,0x13,0xC5,0x11,0x85,0x11,0xA3,0x0E,0x21,0x19, + 0x00,0x12,0xF7,0x04,0xE2,0x04,0x2B,0xFF,0xF8,0xFD,0xFC,0xF3, + 0xAA,0xF1,0x67,0xF2,0xC9,0xF3,0xB2,0xFA,0xBD,0xF0,0xEA,0xF6, + 0x0A,0x03,0x53,0x02,0xB3,0x03,0xB9,0x04,0x52,0x0A,0x85,0x0B, + 0x89,0x0A,0x5A,0x06,0x63,0x02,0x20,0x06,0x0E,0x01,0x2F,0xFB, + 0xFC,0xF9,0xBE,0xF8,0x4F,0xF9,0xAB,0xF7,0x2E,0xF6,0xEF,0xF6, + 0xD2,0xFB,0xEB,0xFF,0x88,0xFE,0x62,0xFF,0x5F,0x03,0xBE,0x07, + 0xB3,0x03,0xCB,0x02,0x89,0x05,0x48,0x07,0xE7,0x20,0xDC,0x12, + 0xE0,0xF3,0x89,0x0B,0x37,0x0B,0x2A,0xFB,0x70,0xF3,0xCE,0xEB, + 0xCA,0xEF,0x7D,0xF0,0xF8,0xF1,0xA7,0xE1,0x0F,0xEA,0x7E,0xFF, + 0x27,0xF7,0x97,0xFB,0xC0,0x00,0x04,0x0B,0xC4,0x12,0xD7,0x12, + 0x48,0x12,0x6B,0x0D,0xD7,0x1A,0xFA,0x13,0x30,0x06,0xEB,0x04, + 0xE9,0xFF,0x8B,0xFF,0xD4,0xF3,0x3D,0xF3,0x5E,0xF4,0x17,0xF0, + 0x5C,0xF6,0xCB,0xF3,0x8F,0xF6,0xEB,0xFD,0xD5,0x01,0x81,0x03, + 0xD6,0x04,0xAD,0x0A,0xA1,0x0A,0x74,0x0A,0xC2,0x08,0xB4,0x04, + 0x39,0x05,0xD9,0x02,0x95,0xFD,0x26,0xFA,0xDC,0xF9,0x2D,0xF9, + 0xCB,0xF6,0x5C,0xF6,0x1A,0xF7,0xE0,0xF9,0x37,0xFD,0x2E,0xFE, + 0x76,0xFF,0xAF,0x01,0x0E,0x06,0xB2,0x03,0x59,0x03,0xC8,0x06, + 0xEE,0x04,0x48,0x1D,0xF9,0x17,0x43,0xF7,0x14,0x09,0xBF,0x0B, + 0xDA,0xFD,0x0F,0xF7,0xDB,0xED,0x4F,0xEF,0x41,0xEF,0x3D,0xF4, + 0xA6,0xE4,0x43,0xE6,0x97,0xFB,0xFA,0xF6,0xA3,0xFA,0x17,0xFE, + 0x13,0x07,0x85,0x0F,0xF6,0x11,0xBD,0x13,0x5D,0x0C,0x4B,0x18, + 0x00,0x17,0xF8,0x0A,0xA2,0x07,0xD7,0x01,0x44,0x02,0x25,0xF8, + 0x8A,0xF5,0xCF,0xF2,0xE1,0xF0,0xE2,0xF9,0x2E,0xF1,0xA3,0xF1, + 0xED,0xFB,0x22,0xFF,0x64,0x00,0xAE,0x01,0xF0,0x06,0x44,0x08, + 0xA6,0x0B,0xE6,0x09,0x72,0x04,0x58,0x07,0xFB,0x05,0x4C,0x01, + 0x9D,0xFD,0x87,0xFB,0xC3,0xFA,0xA6,0xF9,0xF1,0xF7,0x2F,0xF5, + 0x83,0xF8,0x28,0xFC,0xFF,0xFB,0xAE,0xFD,0x7E,0xFE,0x78,0x02, + 0xC7,0x03,0x09,0x02,0x76,0x05,0xCD,0x01,0x29,0x13,0x38,0x20, + 0x49,0x01,0xCB,0x01,0x18,0x0E,0xE4,0x03,0x12,0xFF,0x86,0xF3, + 0x38,0xEF,0xDF,0xEF,0x59,0xF6,0x46,0xED,0x2A,0xE1,0xE4,0xF2, + 0x29,0xF6,0x30,0xF7,0xFB,0xFA,0x39,0xFD,0xA0,0x07,0xFC,0x0D, + 0x5E,0x13,0xEB,0x0B,0x19,0x11,0x85,0x19,0x05,0x12,0xE8,0x0E, + 0xA2,0x06,0xFE,0x05,0xF9,0x01,0x16,0xFD,0x6D,0xF9,0xD3,0xF0, + 0xED,0xF8,0x3F,0xF4,0x9A,0xEF,0x8C,0xF5,0xDE,0xF5,0x1D,0xFB, + 0xFD,0xFC,0x46,0x00,0x39,0x01,0x0D,0x06,0xA3,0x0A,0xF0,0x05, + 0x28,0x08,0xE8,0x07,0xAC,0x06,0x8C,0x05,0xD1,0x01,0x60,0xFF, + 0x0B,0xFD,0x12,0xFD,0xE1,0xF8,0xA9,0xF7,0x2C,0xFB,0xEC,0xF9, + 0x68,0xFA,0xEF,0xFB,0x85,0xFD,0x58,0xFF,0xDA,0xFE,0x55,0x02, + 0xEC,0xFF,0x17,0x0B,0x92,0x1B,0x43,0x05,0xE9,0x01,0x90,0x0D, + 0xDD,0x04,0xCE,0x04,0x85,0xFC,0x1C,0xF6,0xAB,0xF3,0x10,0xF9, + 0x6D,0xF7,0x07,0xE8,0x7C,0xF1,0x83,0xF3,0x28,0xF5,0xD3,0xF9, + 0xDA,0xF7,0x8F,0xFD,0xEE,0x01,0x07,0x0C,0x62,0x08,0xE4,0x08, + 0x69,0x10,0x20,0x0E,0x97,0x11,0x4C,0x0C,0xB1,0x09,0xBA,0x07, + 0x1D,0x05,0xC9,0x05,0x24,0x00,0xA4,0xFC,0x87,0xF8,0xEB,0xFA, + 0x1D,0xF9,0x55,0xF6,0x78,0xF7,0xED,0xF5,0x1A,0xFA,0x4A,0xFA, + 0xDA,0xFB,0x33,0xFC,0xA9,0xFE,0xE2,0x02,0x57,0x02,0xE5,0x04, + 0x89,0x03,0xED,0x04,0xAE,0x05,0x57,0x04,0x65,0x03,0x9D,0x00, + 0x0A,0x02,0x5F,0x00,0x68,0xFF,0xEA,0xFE,0x17,0xFD,0x19,0xFE, + 0x4C,0xFD,0x53,0xFD,0xCC,0xFC,0x9B,0xFC,0xF5,0xFD,0x67,0xFD, + 0x44,0x00,0x88,0x06,0xB9,0x01,0xA9,0xFE,0x42,0x04,0xF9,0x01, + 0x8E,0x03,0xA0,0x02,0x63,0x00,0x6D,0xFF,0x41,0xFE,0x5F,0x01, + 0x74,0xFB,0xFD,0xFB,0xEE,0xFB,0xC2,0xFA,0x07,0xFE,0xB5,0xFB, + 0x37,0xFD,0xB4,0xFB,0xCB,0xFE,0x74,0x00,0x5E,0xFF,0x6A,0x02, + 0xD2,0x00,0xAC,0x03,0xCB,0x03,0x1C,0x04,0x3A,0x04,0xE2,0x02, + 0xD7,0x04,0x42,0x03,0x6F,0x04,0x9D,0x02,0xA2,0x02,0x8B,0x02, + 0xF9,0xFF,0xF8,0x01,0x8F,0xFF,0xB0,0xFF,0xAA,0xFE,0xD1,0xFD, + 0x37,0xFE,0xCA,0xFC,0x79,0xFE,0xB9,0xFC,0xBD,0xFD,0x01,0xFE, + 0x05,0xFE,0xF6,0xFE,0x37,0xFE,0x97,0xFF,0x90,0xFE,0x25,0x00, + 0xDF,0xFF,0x56,0xFF,0x86,0x00,0xAC,0xFF,0x96,0x00,0xD4,0xFF, + 0x16,0x00,0xAD,0xFF,0x98,0xFF,0x6F,0x00,0x84,0xFF,0xB6,0xFF, + 0x7B,0xFF,0xC1,0xFF,0x76,0x00,0x8D,0x03,0x44,0x02,0x28,0x00, + 0x2C,0x03,0xA0,0x01,0x0E,0x02,0x7B,0x01,0x39,0x00,0x57,0x00, + 0x99,0xFF,0xF7,0x00,0x2C,0xFE,0x41,0xFE,0xE5,0xFD,0xB0,0xFC, + 0x00,0xFE,0xDE,0xFC,0x78,0xFD,0x8D,0xFC,0xB4,0xFD,0x78,0xFE, + 0x4C,0xFE,0x29,0x00,0x75,0xFF,0xE5,0x00,0x73,0x01,0xD9,0x01, + 0x2D,0x02,0x9C,0x01,0xC8,0x02,0x73,0x02,0xCE,0x02,0x20,0x02, + 0xE8,0x01,0x42,0x02,0xDB,0x01,0x56,0x02,0x45,0x01,0x33,0x01, + 0xFF,0x00,0xC5,0x00,0x86,0x00,0x8A,0xFF,0x66,0xFF,0xD0,0xFE, + 0x15,0xFF,0x93,0xFE,0x3C,0xFE,0x39,0xFE,0xF5,0xFD,0x8C,0xFE, + 0x41,0xFE,0x95,0xFE,0x7E,0xFE,0x8C,0xFE,0x28,0xFF,0x41,0xFF, + 0x6D,0xFF,0x35,0xFF,0xAF,0xFF,0xB5,0xFF,0xCC,0xFF,0xF7,0xFF, + 0xBE,0xFF,0xD1,0xFF,0xC8,0xFF,0xA8,0x01,0x07,0x02,0x8A,0x00, + 0x63,0x01,0x86,0x01,0x7E,0x01,0x2B,0x02,0x61,0x01,0xBA,0x00, + 0xDB,0x00,0x2A,0x01,0x56,0x00,0x5F,0xFF,0x0F,0xFF,0x1D,0xFE, + 0x05,0xFE,0x2E,0xFE,0xA3,0xFD,0x0C,0xFD,0x46,0xFD,0xFA,0xFD, + 0x34,0xFE,0x08,0xFF,0x5A,0xFF,0x78,0xFF,0x5F,0x00,0xFE,0x00, + 0x2B,0x01,0x2A,0x01,0x9E,0x01,0x97,0x01,0xB5,0x01,0x10,0x02, + 0xC3,0x01,0xC7,0x01,0xD1,0x01,0x50,0x02,0xF3,0x01,0x68,0x01, + 0xDB,0x01,0x4A,0x01,0x4A,0x01,0x1F,0x01,0x59,0x00,0x0D,0x00, + 0xB5,0xFF,0x86,0xFF,0xDC,0xFE,0x9E,0xFE,0x4F,0xFE,0xE7,0xFD, + 0x48,0xFE,0x10,0xFE,0xDC,0xFD,0x1F,0xFE,0x4A,0xFE,0x70,0xFE, + 0xD2,0xFE,0x18,0xFF,0x23,0xFF,0x6E,0xFF,0xA4,0xFF,0xD6,0xFF, + 0xE2,0xFF,0x13,0x00,0x1D,0x00,0x25,0x00,0x6F,0x00,0x81,0x00, + 0x6C,0x01,0x78,0x01,0xF3,0x00,0x8A,0x01,0x81,0x01,0xAE,0x01, + 0xE0,0x01,0x60,0x01,0x29,0x01,0xF5,0x00,0xE5,0x00,0x54,0x00, + 0x95,0xFF,0x29,0xFF,0x77,0xFE,0x44,0xFE,0x1C,0xFE,0xAD,0xFD, + 0x6D,0xFD,0x9D,0xFD,0x08,0xFE,0x4D,0xFE,0xBF,0xFE,0x32,0xFF, + 0x9C,0xFF,0x11,0x00,0x94,0x00,0xCD,0x00,0xF4,0x00,0x2F,0x01, + 0x2D,0x01,0x83,0x01,0x85,0x01,0x63,0x01,0x7D,0x01,0x70,0x01, + 0xA0,0x01,0x7F,0x01,0x74,0x01,0x93,0x01,0x37,0x01,0x44,0x01, + 0x4B,0x01,0xAE,0x00,0x6A,0x00,0x70,0x00,0x12,0x00,0xD6,0xFF, + 0xAA,0xFF,0x1C,0xFF,0xD7,0xFE,0xC8,0xFE,0x79,0xFE,0x16,0xFE, + 0x10,0xFE,0x03,0xFE,0xF2,0xFD,0x53,0xFE,0x58,0xFE,0x62,0xFE, + 0xB2,0xFE,0x04,0xFF,0x56,0xFF,0x85,0xFF,0xCE,0xFF,0xEA,0xFF, + 0x2D,0x00,0x7B,0x00,0x82,0x00,0xA4,0x00,0xC8,0x00,0xD4,0x00, + 0xF5,0x00,0x55,0x01,0x99,0x01,0x65,0x01,0x31,0x01,0x76,0x01, + 0x78,0x01,0x73,0x01,0x93,0x01,0x0D,0x01,0xD8,0x00,0xC0,0x00, + 0x67,0x00,0xED,0xFF,0x56,0xFF,0xF7,0xFE,0x60,0xFE,0x3E,0xFE, + 0x13,0xFE,0xB4,0xFD,0xC9,0xFD,0xFA,0xFD,0x51,0xFE,0x90,0xFE, + 0x06,0xFF,0x4F,0xFF,0x89,0xFF,0x21,0x00,0x3D,0x00,0x78,0x00, + 0xD0,0x00,0xCF,0x00,0xE9,0x00,0x16,0x01,0x25,0x01,0x2F,0x01, + 0x3D,0x01,0x43,0x01,0x68,0x01,0x54,0x01,0x52,0x01,0x3F,0x01, + 0x0A,0x01,0x1B,0x01,0xDA,0x00,0xA7,0x00,0x75,0x00,0x32,0x00, + 0x09,0x00,0xD2,0xFF,0xA9,0xFF,0x66,0xFF,0x3A,0xFF,0x32,0xFF, + 0x3B,0xFF,0xEF,0xFE,0xFF,0xFE,0x12,0xFF,0xA6,0xFE,0x2F,0xFF, + 0x23,0xFF,0xDB,0xFE,0x37,0xFF,0x19,0xFF,0x1F,0xFF,0x36,0xFF, + 0x57,0xFF,0x31,0xFF,0x5C,0xFF,0xAB,0xFF,0x84,0xFF,0xE0,0xFF, + 0x00,0x00,0x10,0x00,0x4F,0x00,0x8F,0x00,0xB6,0x00,0xA5,0x00, + 0xF3,0x00,0xE9,0x00,0xE1,0x00,0x00,0x01,0xE6,0x00,0xE5,0x00, + 0xCE,0x00,0xDA,0x00,0xAD,0x00,0x9F,0x00,0x98,0x00,0x60,0x00, + 0x4C,0x00,0x38,0x00,0x22,0x00,0xF0,0xFF,0xF1,0xFF,0xBE,0xFF, + 0xA7,0xFF,0xB4,0xFF,0x8D,0xFF,0x8E,0xFF,0x7B,0xFF,0x88,0xFF, + 0x7D,0xFF,0x81,0xFF,0x8F,0xFF,0x87,0xFF,0x9B,0xFF,0x9D,0xFF, + 0xB9,0xFF,0xB3,0xFF,0xCF,0xFF,0xDC,0xFF,0xE0,0xFF,0x03,0x00, + 0x05,0x00,0x18,0x00,0x17,0x00,0x28,0x00,0x19,0x00,0xFE,0xFF, + 0x26,0x00,0x32,0x00,0xE8,0xFF,0xED,0xFF,0xF5,0xFF,0x09,0x00, + 0x23,0x00,0xC9,0xFF,0x34,0x00,0x47,0x00,0xEE,0xFF,0x4C,0x00, + 0x65,0x00,0x52,0x00,0x4D,0x00,0x60,0x00,0x76,0x00,0x58,0x00, + 0x77,0x00,0x5D,0x00,0x54,0x00,0x74,0x00,0x15,0x00,0x1D,0x00, + 0x34,0x00,0xE2,0xFF,0x08,0x00,0xF9,0xFF,0x98,0xFF,0xE2,0xFF, + 0xA5,0xFF,0x5A,0xFF,0xB3,0xFF,0x7E,0xFF,0x77,0xFF,0xB0,0xFF, + 0xA4,0xFF,0x72,0xFF,0x9D,0xFF,0xCF,0xFF,0xB6,0xFF,0x24,0x00, + 0xA7,0xFF,0x20,0x00,0xE5,0xFF,0x00,0x00,0x17,0x00,0xE9,0xFF, + 0x6E,0xFF,0x2C,0xFF,0x76,0x04,0xF5,0xFD,0xE1,0xFD,0x72,0x02, + 0x01,0xFB,0xEF,0xFF,0xFC,0xFE,0xA8,0xFD,0xB9,0x02,0xE2,0xFE, + 0xF6,0x00,0x28,0x02,0xF6,0xFF,0x41,0x02,0xCF,0x00,0x5E,0x01, + 0x56,0x02,0xC3,0xFF,0xAF,0x01,0xCA,0x00,0xA7,0xFF,0x86,0x01, + 0xF1,0xFF,0xE9,0xFF,0x5C,0x00,0x8E,0xFF,0xE4,0xFF,0xCD,0xFF, + 0x7A,0xFF,0x6B,0xFF,0x6D,0xFF,0xA4,0xFF,0x8C,0xFF,0x4C,0xFF, + 0x47,0xFF,0xB9,0xFF,0x51,0xFF,0x96,0xFF,0xAA,0xFF,0xFA,0xFE, + 0xF2,0xFF,0xCD,0xFF,0x72,0xFF,0x6F,0xFF,0x4F,0x00,0x06,0xFE, + 0x17,0x04,0x8C,0x02,0xC8,0xF9,0x9A,0x02,0xA9,0xFE,0x33,0xFE, + 0x2B,0x02,0xAF,0xFC,0xD4,0x00,0xC7,0x01,0x84,0xFF,0xF6,0x00, + 0x3D,0x00,0xCC,0xFF,0xAF,0x01,0xCA,0x00,0x84,0x01,0x64,0x00, + 0xAF,0xFF,0x5D,0x01,0xC9,0xFF,0xBF,0x00,0x18,0x00,0x97,0xFF, + 0xD7,0xFF,0x93,0xFF,0xEA,0xFF,0x4A,0xFE,0x18,0xFF,0x85,0xFE, + 0xEA,0xFE,0x1D,0x00,0xB8,0xFF,0x4B,0x00,0x01,0x00,0x38,0x01, + 0x9D,0x00,0xA4,0x01,0xFC,0x00,0x4F,0xFF,0xD7,0x00,0xC9,0xFF, + 0xDB,0xFF,0x45,0xFF,0xC9,0xFE,0x3F,0xFF,0x98,0xFF,0x77,0xFF, + 0xDA,0xFF,0x8C,0x00,0x19,0x00,0xF3,0x00,0x27,0x01,0x46,0x01, + 0x0A,0x01,0xBC,0x00,0x70,0x01,0x45,0x00,0xAA,0xFE,0x0C,0xFF, + 0x63,0xFE,0xBE,0xFD,0x9B,0xFE,0xC8,0xFD,0xB1,0xFD,0x8D,0xFF, + 0x4D,0xFF,0x75,0xFF,0x26,0x00,0xC8,0x00,0x44,0x01,0xD5,0x01, + 0x69,0x02,0x74,0x01,0x41,0x02,0x7D,0x01,0x14,0x01,0x58,0x01, + 0xEC,0x00,0x07,0x00,0xF6,0xFE,0x17,0x01,0xD5,0x00,0x8E,0xFF, + 0xEA,0xFF,0xE4,0xFE,0xDA,0x01,0x95,0x02,0x70,0xFE,0x59,0xFF, + 0xEC,0xFD,0xEC,0xFD,0xD6,0xFE,0xC4,0xFC,0x38,0xFC,0xEC,0xFA, + 0x9F,0xFD,0xC6,0xFD,0xB3,0xFD,0x23,0xFF,0x9C,0xFE,0x35,0x01, + 0xFF,0x02,0x8A,0x03,0x70,0x03,0x9F,0x03,0x36,0x04,0x3E,0x04, + 0xDE,0x03,0xA6,0x02,0x96,0x00,0x02,0x00,0xB9,0xFF,0x3D,0xFE, + 0xB0,0xFD,0x92,0xFC,0xA0,0xFC,0x2C,0xFD,0x2E,0xFE,0xB7,0xFE, + 0xD8,0xFE,0x52,0x00,0x25,0x01,0x3F,0x02,0xDB,0x02,0xA7,0x02, + 0x61,0x02,0x4F,0x02,0xED,0x01,0x13,0x01,0xE9,0xFF,0xF0,0xFE, + 0x21,0xFE,0x87,0xFD,0x8A,0xFD,0xF1,0xFC,0x03,0xFD,0x91,0xFD, + 0x2F,0xFE,0x1C,0xFF,0xFB,0xFF,0x95,0x00,0x23,0x01,0xF4,0x01, + 0x61,0x02,0x58,0x02,0x01,0x02,0x9D,0x01,0xDA,0x00,0x79,0x00, + 0xC4,0xFF,0xDC,0xFE,0x71,0xFE,0xE5,0xFD,0x03,0xFE,0x33,0xFE, + 0x74,0xFE,0xF8,0xFE,0x79,0xFF,0x59,0x00,0x0C,0x01,0x8C,0x01, + 0x06,0x02,0x15,0x02,0x12,0x02,0xEC,0x01,0x7C,0x01,0xE9,0x00, + 0x17,0x00,0x82,0xFF,0xEE,0xFE,0x7E,0xFE,0x4C,0xFE,0x0F,0xFE, + 0x41,0xFE,0xA1,0xFE,0x31,0xFF,0xCB,0xFF,0x4E,0x00,0xE0,0x00, + 0x54,0x01,0xA8,0x01,0xC6,0x01,0x98,0x01,0x42,0x01,0xD0,0x00, + 0x49,0x00,0xC3,0xFF,0x2D,0xFF,0xC2,0xFE,0x74,0xFE,0x61,0xFE, + 0x7C,0xFE,0xC2,0xFE,0x2A,0xFF,0x98,0xFF,0x30,0x00,0xB4,0x00, + 0x1B,0x01,0x5E,0x01,0x7D,0x01,0x5A,0x01,0x1C,0x01,0xC7,0x00, + 0x52,0x00,0xBD,0xFF,0x65,0xFF,0xD5,0xFE,0xBF,0xFE,0x72,0xFE, + 0xB6,0xFE,0x53,0xFF,0xAB,0xFE,0xE4,0xFF,0x15,0x00,0x63,0x00, + 0x8E,0x01,0x2D,0x01,0x33,0x01,0x97,0x00,0x89,0x01,0xBC,0x02, + 0x00,0x00,0xFE,0xFE,0x8F,0xFF,0x4C,0xFE,0xF6,0xFE,0xCD,0xFE, + 0x54,0xFE,0x9A,0xFD,0x26,0xFE,0x7D,0x00,0xC8,0xFF,0x97,0x00, + 0x4E,0x01,0xC0,0x00,0x6F,0x01,0x16,0x02,0xB1,0x01,0xA3,0x00, + 0x2C,0x00,0x58,0x00,0x9D,0xFF,0x85,0xFF,0xAB,0xFF,0x46,0xFE, + 0xB8,0xFE,0x46,0xFF,0xCC,0xFF,0xF1,0xFF,0x36,0x00,0xA6,0x00, + 0x72,0x00,0x47,0x01,0x8E,0x01,0xD1,0x00,0x7B,0x00,0x62,0x00, + 0xDF,0xFF,0xAF,0xFF,0x65,0xFF,0x2D,0xFF,0x6E,0xFE,0xB6,0xFE, + 0x4C,0xFF,0x2E,0xFF,0xD6,0xFF,0x08,0x00,0xFA,0xFF,0x35,0x00, + 0xC5,0x00,0xBD,0x00,0x7E,0x00,0xE5,0x00,0xBB,0x00,0xBA,0xFF, + 0xB7,0xFF,0xD9,0xFF,0x6C,0xFF,0xA9,0xFF,0xB4,0xFF,0x75,0xFF, + 0x4D,0xFF,0xF7,0xFF,0x4D,0x00,0x4A,0x00,0x61,0x01,0xF3,0x00, + 0xED,0xFF,0x6D,0x00,0x33,0x00,0x86,0xFF,0x51,0xFF,0x0C,0xFF, + 0xBB,0xFE,0x69,0xFE,0xA9,0xFF,0xF0,0xFF,0x85,0xFF,0x4E,0x00, + 0xB6,0x00,0xCA,0x01,0x25,0x02,0xC2,0x02,0x3E,0x02,0xCF,0x00, + 0x93,0x00,0x8A,0xFF,0xC3,0xFF,0x97,0x01,0xB9,0x00,0xA7,0xFE, + 0x18,0xFF,0x81,0xFE,0x21,0xFE,0x08,0xFF,0x2A,0xFF,0x29,0xFE, + 0x0B,0xFD,0x7A,0xFE,0xFB,0xFE,0x2B,0xFF,0xE4,0x00,0x02,0x00, + 0x4E,0xFF,0x4F,0x00,0x28,0x01,0x6F,0x01,0xE5,0x00,0x0F,0x01, + 0x0C,0x00,0x43,0xFF,0xC3,0x00,0xCD,0x00,0xFC,0xFF,0xF8,0xFF, + 0x91,0xFF,0xEB,0xFF,0x35,0x00,0x26,0x01,0x0B,0x01,0xDE,0xFF, + 0xA5,0x00,0x8A,0x00,0x45,0x00,0xF9,0x00,0xA2,0x00,0x9F,0xFF, + 0x6B,0xFF,0xB5,0xFF,0xD8,0xFF,0x8A,0xFF,0xFD,0xFF,0xAA,0xFF, + 0x16,0xFF,0xFB,0xFF,0x59,0x00,0x3E,0x00,0x3C,0x00,0x3A,0x00, + 0xD4,0xFF,0xB9,0xFF,0x4D,0x00,0x2F,0x00,0x74,0xFF,0x92,0xFF, + 0x6B,0xFF,0x36,0xFF,0xB6,0xFF,0x02,0x00,0xC7,0xFF,0x91,0xFF, + 0xFF,0xFF,0x1A,0x00,0x1A,0x00,0x9B,0x00,0x68,0x00,0xD9,0xFF, + 0x01,0x00,0x18,0x00,0xFA,0xFF,0xFB,0xFF,0x15,0x00,0xC7,0xFF, + 0x80,0xFF,0x09,0x00,0x35,0x00,0x12,0x00,0x52,0x00,0x44,0x00, + 0x0D,0x00,0x2C,0x00,0x73,0x00,0x56,0x00,0x05,0x00,0x0C,0x00, + 0xE2,0xFF,0xA2,0xFF,0xE7,0xFF,0x09,0x00,0xCB,0xFF,0xD4,0xFF, + 0xF3,0xFF,0xF7,0xFF,0x09,0x00,0x4F,0x00,0x4B,0x00,0x00,0x00, + 0x0D,0x00,0x0F,0x00,0xE5,0xFF,0xF9,0xFF,0x07,0x00,0xDF,0xFF, + 0xB0,0xFF,0xE4,0xFF,0xE7,0xFF,0xF8,0xFF,0x2B,0x00,0x01,0x00, + 0x2C,0x00,0x12,0x00,0x21,0x00,0x0F,0x00,0xF7,0xFF,0x1F,0x00, + 0xF7,0xFF,0xB2,0xFF,0xDF,0xFF,0xEB,0xFF,0xD4,0xFF,0x06,0x00, + 0xFB,0xFF,0x0E,0x00,0xED,0xFF,0x1D,0x00,0x30,0x00,0x0F,0x00, + 0x21,0x00,0x0A,0x00,0xDD,0xFF,0xE9,0xFF,0xF4,0xFF,0x01,0x00, + 0xF4,0xFF,0xF1,0xFF,0x0B,0x00,0x11,0x00,0x29,0x00,0x27,0x00, + 0x67,0xFF,0x87,0xFF,0x46,0x05,0x8D,0x07,0x1F,0x00,0x79,0xFB, + 0x06,0xF9,0x2E,0xF7,0x08,0xFC,0xF6,0x00,0x83,0x01,0x85,0xFE, + 0x8F,0xFD,0xD3,0xFF,0x10,0x01,0xB5,0x05,0xCE,0x08,0x34,0x05, + 0x9D,0x02,0x3E,0x01,0x4A,0xFF,0x99,0x00,0xC4,0x04,0xB1,0x04, + 0x69,0xFE,0x39,0xFB,0x7B,0xF9,0xC0,0xF7,0x95,0xFB,0xA1,0xFF, + 0xF1,0xFE,0xED,0xFC,0x9E,0xFD,0x24,0xFF,0xF7,0x00,0x27,0x05, + 0x17,0x07,0x13,0x03,0x5E,0x00,0x88,0xFF,0x6F,0xFE,0x8B,0xFF, + 0xF4,0x00,0xB4,0xFF,0x7E,0xFC,0x0F,0xFC,0x6A,0xFD,0x22,0xFF, + 0xB6,0x02,0xC9,0x04,0xBB,0x02,0xEB,0x00,0x5E,0x00,0xFF,0xFF, + 0x07,0x01,0x49,0x02,0x3F,0x01,0x21,0xFE,0x08,0xFD,0x3C,0xFD, + 0xF2,0xFD,0xB0,0x00,0x49,0x02,0xF8,0x00,0xCF,0xFF,0xB4,0xFF, + 0x9F,0xFF,0x75,0x00,0x1E,0x02,0xB7,0x01,0x56,0xFF,0x93,0xFE, + 0x60,0xFE,0x3F,0xFE,0x08,0x00,0x66,0x01,0x94,0x00,0x64,0xFF, + 0x3F,0xFF,0x04,0xFF,0x49,0xFF,0xD9,0x00,0x3D,0x01,0x10,0x00, + 0x84,0xFF,0x4A,0xFF,0xE9,0xFE,0xDC,0xFF,0x0F,0x01,0xD4,0x00, + 0x09,0x00,0xD0,0xFF,0x64,0xFF,0x3C,0xFF,0x3F,0x00,0xD1,0x00, + 0x6C,0x00,0x1A,0x00,0xEF,0xFF,0x65,0xFF,0xB4,0xFF,0x9C,0x00, + 0xDB,0x00,0xA9,0x00,0x45,0x00,0xAD,0xFF,0x2C,0xFF,0x7D,0xFF, + 0xFF,0xFF,0x38,0x00,0x50,0x00,0x32,0x00,0xCA,0xFF,0xB5,0xFF, + 0x27,0x00,0x86,0x00,0xC9,0x00,0xB3,0x00,0x21,0x00,0x6A,0xFF, + 0x32,0xFF,0x56,0xFF,0xA4,0xFF,0x17,0x00,0x00,0x00,0xF6,0xFF, + 0xB5,0xFF,0xB2,0xFF,0x5E,0x00,0x96,0x00,0xF8,0x00,0xB4,0x00, + 0x12,0x00,0xA9,0xFF,0x5A,0xFF,0x98,0xFF,0xC1,0xFF,0x10,0x00, + 0xFC,0xFF,0xAC,0xFF,0xB6,0xFF,0xB6,0xFF,0x02,0x00,0x80,0x00, + 0x94,0x00,0x76,0x00,0x22,0x00,0xB5,0xFF,0x98,0xFF,0xA8,0xFF, + 0xFE,0xFF,0x1B,0x00,0xFF,0xFF,0xFA,0xFF,0xC2,0xFF,0xB7,0xFF, + 0x0C,0x00,0x3D,0x00,0x65,0x00,0x5F,0x00,0x19,0x00,0xDA,0xFF, + 0xAB,0xFF,0xCB,0xFF,0xF2,0xFF,0x14,0x00,0x2C,0x00,0x04,0x00, + 0xD7,0xFF,0xDF,0xFF,0xC9,0xFF,0x0D,0x00,0x4A,0x00,0x37,0x00, + 0x2B,0x00,0xDD,0xFF,0xCD,0xFF,0xC7,0xFF,0xEF,0xFF,0x21,0x00, + 0x32,0x00,0x1F,0x00,0xF0,0xFF,0xCF,0xFF,0xD5,0xFF,0xF1,0xFF, + 0x10,0x00,0x43,0x00,0x26,0x00,0x1D,0x00,0xF0,0xFF,0xD6,0xFF, + 0xA8,0xFF,0x33,0x00,0x34,0x02,0x76,0x02,0xCA,0x00,0x6A,0xFF, + 0x62,0xFD,0x24,0xFC,0x62,0xFD,0xCF,0xFE,0x5A,0x00,0x66,0x01, + 0xC7,0x00,0x1B,0x00,0xE4,0xFF,0x1E,0x00,0xD8,0x00,0xD6,0x01, + 0x2E,0x02,0xFA,0x00,0xE8,0xFF,0x56,0xFF,0xAB,0xFE,0x78,0xFF, + 0xA7,0x00,0xF7,0x00,0x15,0x01,0x41,0x00,0x26,0xFF,0xCB,0xFE, + 0xEC,0xFE,0xB3,0xFF,0x67,0x00,0xA9,0x00,0x75,0x00,0xA8,0xFF, + 0x58,0xFF,0x6E,0xFF,0xB7,0xFF,0x84,0x00,0xB4,0x00,0x4A,0x00, + 0xC6,0xFF,0x0C,0xFF,0xE9,0xFE,0x66,0xFF,0x00,0x00,0xA5,0x00, + 0xBA,0x00,0x6F,0x00,0xF8,0xFF,0x85,0xFF,0xBD,0xFF,0x05,0x00, + 0x57,0x00,0xAC,0x00,0x40,0x00,0xBD,0xFF,0x7C,0xFF,0x3A,0xFF, + 0x94,0xFF,0x2A,0x00,0x69,0x00,0x7F,0x00,0x46,0x00,0xEC,0xFF, + 0xAC,0xFF,0xC9,0xFF,0x29,0x00,0x49,0x00,0x62,0x00,0x66,0x00, + 0xF4,0xFF,0xC9,0xFF,0xDD,0xFF,0xE6,0xFF,0x59,0x00,0x7F,0x00, + 0x5F,0x00,0x51,0x00,0x00,0x00,0xD9,0xFF,0xF7,0xFF,0x05,0x00, + 0x22,0x00,0x16,0x00,0xF9,0xFF,0xD1,0xFF,0x95,0xFF,0xB2,0xFF, + 0xC2,0xFF,0xCF,0xFF,0xFF,0xFF,0xEB,0xFF,0xF2,0xFF,0xCD,0xFF, + 0x74,0xFF,0xBD,0xFF,0x16,0x00,0x74,0x00,0xDA,0x00,0x91,0x00, + 0x3D,0x00,0xCC,0xFF,0x53,0xFF,0x41,0xFF,0x4A,0xFF,0xB1,0xFF, + 0x1F,0x00,0x48,0x00,0x48,0x00,0xE3,0xFF,0xAF,0xFF,0xFF,0xFF, + 0x93,0x00,0x4B,0x01,0x83,0x01,0x10,0x01,0x28,0x00,0x10,0xFF, + 0x56,0xFE,0x3E,0xFE,0xDA,0xFE,0xB0,0xFF,0x1D,0x00,0x4C,0x00, + 0x3E,0x00,0xEA,0xFF,0xD8,0xFF,0xF4,0xFF,0x20,0x00,0x52,0x00, + 0x37,0x00,0x06,0x00,0xDD,0xFF,0xCA,0xFF,0x09,0x00,0x62,0x00, + 0xB1,0x00,0xA6,0x00,0x3E,0x00,0xDC,0xFF,0x88,0xFF,0xAF,0xFF, + 0x2F,0x00,0x92,0x00,0xED,0x00,0x00,0x01,0xA3,0x00,0x09,0x00, + 0x21,0xFF,0x03,0xFF,0x8D,0xFF,0xF8,0xFF,0x92,0x01,0x8D,0x02, + 0x0F,0x02,0x9C,0x01,0xC4,0xFF,0x89,0xFD,0xFC,0xFD,0xBC,0x00, + 0xFD,0x05,0x0D,0x0B,0x28,0x09,0x36,0x02,0xB6,0xF8,0x7A,0xEE, + 0x02,0xEB,0x84,0xEE,0xE8,0xF6,0x63,0x01,0xD4,0x07,0x76,0x09, + 0x86,0x06,0xC3,0x01,0x84,0xFF,0x46,0x00,0x38,0x04,0xBB,0x07, + 0xA8,0x07,0xCC,0x04,0xDC,0xFE,0x87,0xF9,0x8A,0xF8,0xA8,0xFA, + 0xCF,0xFF,0xF2,0x04,0xBE,0x06,0x7D,0x05,0x85,0x01,0xDB,0xFD, + 0x9B,0xFC,0x12,0xFE,0xBF,0x01,0x7A,0x04,0x28,0x05,0x5C,0x03, + 0x2F,0xFF,0x84,0xFC,0x74,0xFB,0x31,0xFC,0x44,0xFF,0xDE,0x00, + 0x3C,0x01,0xC9,0xFF,0xDB,0xFC,0xC3,0xFB,0x0C,0xFC,0x5A,0xFE, + 0x82,0x01,0x14,0x03,0x63,0x03,0x70,0x01,0xDD,0xFE,0x97,0xFD, + 0xF8,0xFC,0x3E,0xFE,0x02,0x00,0xC5,0x00,0x26,0x01,0x24,0x00, + 0x17,0xFF,0x25,0xFF,0xC9,0xFF,0x5D,0x01,0xB1,0x02,0xEC,0x02, + 0x34,0x02,0x81,0x00,0xD8,0xFE,0xF3,0xFD,0x43,0xFE,0xB0,0xFF, + 0xDC,0x00,0x38,0x01,0xE0,0x00,0x79,0xFF,0x44,0xFE,0x94,0xFD, + 0xAB,0xFD,0x52,0x03,0x64,0x07,0xAF,0x07,0x42,0x0B,0x89,0x06, + 0xD3,0xFF,0x69,0xFC,0x45,0xF4,0xA3,0xF4,0xA7,0xF6,0x83,0xF6, + 0x89,0xFD,0x42,0xFF,0xEA,0xFE,0xEB,0x00,0x6E,0x00,0x88,0x01, + 0x67,0x02,0x62,0x04,0x67,0x05,0x1C,0x03,0xD6,0x02,0x22,0x00, + 0xE5,0xFD,0xA1,0xFF,0xDC,0xFE,0xFA,0x00,0x4E,0x03,0x62,0x01, + 0xF1,0x01,0xC1,0x00,0xD4,0xFE,0xAD,0xFF,0x9D,0x00,0x82,0x02, + 0xB1,0x03,0x30,0x04,0xB3,0x02,0x73,0xFF,0xBC,0xFD,0x39,0xFB, + 0x9F,0xFA,0x9A,0xFC,0xDB,0xFC,0x7B,0xFE,0xD2,0xFF,0xF5,0xFE, + 0x90,0xFF,0xBD,0xFF,0xB6,0xFF,0xFE,0x00,0x94,0x01,0x81,0x01, + 0x36,0x01,0x5B,0x00,0x0E,0xFF,0x90,0xFE,0xD9,0xFE,0xD5,0xFE, + 0x68,0xFF,0xDB,0xFF,0x3B,0xFF,0x2C,0xFF,0x6C,0xFF,0x9D,0xFF, + 0xC9,0x00,0x39,0x02,0x43,0x02,0x08,0x02,0x60,0x01,0x5E,0xFF, + 0xE3,0xFE,0x94,0xFE,0x32,0xFE,0xC8,0xFD,0x38,0x03,0xE4,0x0D, + 0x10,0x10,0xD1,0x0F,0x3B,0x08,0x21,0xF9,0xA3,0xF0,0xC4,0xE6, + 0xB2,0xE7,0x7D,0xF3,0x5E,0xF9,0x06,0x03,0xF4,0x08,0x41,0x05, + 0xBF,0x01,0xD4,0xFE,0x93,0xFF,0xAE,0x02,0xE9,0x06,0x82,0x0A, + 0xFC,0x07,0x4A,0x04,0xC1,0xFE,0xCD,0xF9,0xF4,0xFB,0x82,0xFD, + 0x44,0x01,0x98,0x06,0xCE,0x04,0x84,0x02,0xDE,0xFF,0xE0,0xFB, + 0xD8,0xFC,0x84,0x01,0x0B,0x06,0xCE,0x08,0xD0,0x07,0x5A,0x02, + 0x0C,0xFC,0x81,0xF7,0x99,0xF5,0xD6,0xF7,0x2A,0xFC,0x2F,0xFF, + 0x34,0x01,0x63,0x01,0xE5,0xFE,0xE1,0xFD,0xD5,0xFE,0x17,0x00, + 0x9A,0x02,0x29,0x04,0xE9,0x02,0xB1,0x00,0x13,0xFE,0xB6,0xFB, + 0x17,0xFC,0xD5,0xFD,0x8D,0xFF,0xC0,0x01,0x11,0x02,0xF8,0x00, + 0x12,0x00,0x8F,0xFE,0x4F,0xFE,0x44,0x00,0x04,0x02,0xFA,0x02, + 0xDC,0x02,0xA2,0x01,0xAE,0xFF,0xF7,0xFD,0x1A,0xFB,0x95,0xFC, + 0x73,0x09,0x05,0x11,0xDC,0x12,0xC6,0x10,0xB0,0x00,0xC6,0xF3, + 0xF5,0xE7,0x72,0xE1,0x75,0xEC,0x42,0xF5,0x3E,0xFF,0xCA,0x09, + 0x36,0x08,0x63,0x03,0x41,0xFE,0xCC,0xFC,0xEF,0xFF,0xB6,0x04, + 0xCA,0x0B,0x1D,0x0C,0x2C,0x08,0xAF,0x02,0x69,0xFA,0xC9,0xF9, + 0xE5,0xFB,0xCD,0xFE,0x78,0x06,0x85,0x07,0x94,0x04,0x50,0x01, + 0xEE,0xFB,0xD4,0xFC,0x4E,0x01,0xB7,0x05,0x34,0x09,0xE3,0x06, + 0x3E,0x01,0x83,0xF9,0xF1,0xF3,0xC9,0xF4,0xEF,0xF7,0x1B,0xFE, + 0xB3,0x03,0x5A,0x04,0xC1,0x02,0x1C,0xFE,0x42,0xFB,0x66,0xFC, + 0x81,0xFE,0xFB,0x02,0x95,0x05,0x8B,0x04,0x5C,0x01,0xFE,0xFC, + 0xEE,0xFA,0xF8,0xFA,0x6D,0xFD,0x05,0x01,0xC5,0x02,0x7C,0x02, + 0x15,0x00,0xCF,0xFD,0x28,0xFD,0x28,0xFE,0xEE,0x00,0xF7,0x03, + 0x44,0x05,0xDA,0x03,0x20,0x01,0x9E,0xFD,0x6F,0xF8,0xBC,0xFB, + 0x10,0x0B,0x30,0x14,0x31,0x17,0xA4,0x11,0x06,0xFE,0x70,0xEE, + 0x13,0xDF,0xC3,0xDA,0x85,0xEA,0x04,0xF7,0x4A,0x05,0x4A,0x10, + 0xC7,0x0C,0xBC,0x04,0x93,0xFC,0x1B,0xFB,0xB5,0xFE,0x8A,0x05, + 0x72,0x0D,0x08,0x0D,0xBB,0x08,0x5D,0x00,0xF1,0xF7,0x45,0xF9, + 0xFA,0xFB,0xB9,0x01,0xC1,0x09,0xC8,0x08,0x28,0x04,0x6A,0xFE, + 0x1D,0xF8,0x11,0xF8,0x43,0xFD,0x1A,0x04,0x25,0x0B,0xD8,0x0C, + 0x9C,0x07,0xA9,0x00,0xF2,0xF8,0x03,0xF5,0x24,0xF6,0x81,0xF9, + 0x9E,0xFE,0xFD,0xFF,0x7C,0xFF,0x2E,0xFE,0xAB,0xFC,0x9F,0xFE, + 0xC2,0x00,0x10,0x04,0x7F,0x05,0xA0,0x02,0xC2,0xFF,0x1C,0xFC, + 0x24,0xFA,0xA9,0xFB,0x28,0xFE,0x21,0x01,0x87,0x02,0x6E,0x01, + 0x9A,0xFF,0xD2,0xFD,0x3C,0xFD,0xAD,0xFE,0xBD,0x01,0x04,0x04, + 0x18,0x04,0xD7,0x02,0xAC,0x00,0xA1,0xFE,0xF7,0xFD,0xB8,0xFB, + 0x2F,0xFF,0x9B,0x10,0x9D,0x18,0x0A,0x17,0xF4,0x0F,0xE4,0xF7, + 0xC8,0xE8,0xA1,0xDD,0x47,0xDB,0x7B,0xEF,0xEE,0xFA,0xA6,0x05, + 0x52,0x0D,0x94,0x06,0xBF,0xFF,0x4B,0xFA,0x76,0xFE,0x13,0x05, + 0x39,0x0B,0xCC,0x10,0xEA,0x09,0x8E,0x02,0xB8,0xFA,0xDF,0xF4, + 0x1E,0xFC,0xD4,0x01,0xA5,0x06,0xBC,0x0B,0xA9,0x05,0x7A,0xFE, + 0xFA,0xF9,0x2F,0xF8,0xF8,0xFC,0x6E,0x03,0x5C,0x09,0x98,0x08, + 0x97,0x05,0xC4,0x01,0x29,0xFD,0x60,0x00,0x2D,0xFF,0x85,0xFD, + 0x93,0xFD,0xB9,0xF6,0x57,0xF5,0x0A,0xF6,0x80,0xF8,0x49,0x00, + 0x3B,0x05,0x3E,0x08,0xCD,0x06,0x3B,0x02,0x8B,0xFD,0x6D,0xFA, + 0x41,0xFD,0x04,0xFF,0x14,0x01,0xCD,0x02,0x61,0xFE,0xED,0xFC, + 0xB5,0xFC,0xDF,0xFC,0xF9,0x00,0x84,0x02,0x8A,0x02,0xE4,0x01, + 0x00,0x00,0x1F,0xFF,0x6D,0xFF,0xE0,0x01,0x9D,0x02,0x58,0x03, + 0x04,0x00,0x19,0xF9,0xF0,0x07,0xB3,0x16,0xBD,0x17,0xF9,0x18, + 0xC4,0x02,0x61,0xED,0x64,0xE0,0xA3,0xD4,0x49,0xE6,0x57,0xF7, + 0xA9,0x01,0xB0,0x0C,0xC4,0x08,0xB9,0x00,0xCC,0xF8,0xA3,0xFD, + 0x66,0x06,0xE5,0x0B,0xB4,0x13,0x4E,0x0B,0x21,0x01,0xE1,0xF9, + 0x94,0xF2,0x7F,0xFB,0x5C,0x04,0x19,0x08,0x9C,0x0B,0xD7,0x04, + 0xFB,0xFB,0x8F,0xF7,0x86,0xF9,0x0C,0x00,0xC5,0x05,0x8B,0x0B, + 0x2E,0x07,0x00,0x01,0xF6,0xFC,0x96,0xF8,0x53,0x00,0x96,0x05, + 0x8D,0x08,0x8B,0x07,0x0F,0xFB,0x76,0xF3,0xD7,0xED,0x44,0xF1, + 0x6F,0xFB,0x2E,0x02,0xD7,0x09,0x9F,0x06,0x76,0x01,0xA4,0xFE, + 0xD9,0xFA,0x4A,0xFF,0x87,0x01,0xE5,0x02,0xBD,0x02,0x0B,0xFD, + 0x93,0xFB,0xA3,0xFA,0xE6,0xFC,0x98,0x01,0x22,0x03,0x89,0x04, + 0xDF,0x00,0x67,0xFD,0x90,0xFD,0x72,0xFD,0xB0,0x01,0x3C,0x04, + 0x23,0x05,0xBD,0x04,0x79,0x00,0x40,0xFB,0x4F,0xF9,0x88,0x0B, + 0xD2,0x1A,0xCE,0x1B,0x6F,0x16,0x65,0xFA,0x69,0xE6,0x8F,0xDC, + 0x25,0xD9,0x5F,0xEF,0x7E,0xFD,0x4B,0x03,0xD6,0x05,0xDB,0xFE, + 0xD7,0xFA,0xE3,0xFA,0x75,0x07,0xFE,0x0F,0xED,0x11,0xEE,0x0F, + 0xCF,0x00,0x70,0xF9,0x93,0xF6,0x80,0xF8,0x3B,0x05,0x36,0x09, + 0xAC,0x06,0x61,0x02,0xC8,0xFA,0x70,0xF8,0x4E,0xFC,0x22,0x04, + 0x38,0x08,0x07,0x08,0xB0,0x04,0xCE,0xFC,0xF4,0xFB,0x40,0xFE, + 0x17,0x02,0x9A,0x07,0x05,0x06,0x4A,0x02,0x7A,0xFD,0x57,0xF9, + 0xE1,0xF6,0x0F,0xF8,0x86,0xFB,0x35,0xFB,0xF4,0xFD,0x22,0xFE, + 0xD8,0xFD,0xF8,0x01,0x7C,0x02,0x39,0x04,0x40,0x04,0x69,0x00, + 0xB0,0xFD,0x6B,0xFC,0x93,0xFC,0x3C,0xFE,0x42,0x01,0x03,0x01, + 0xD3,0xFF,0x9A,0xFF,0xB6,0xFD,0xEF,0xFE,0x47,0x01,0xEE,0x00, + 0x67,0x01,0x4B,0x00,0xF3,0xFE,0x92,0x00,0x47,0x02,0x38,0x03, + 0x47,0x03,0xE3,0xFF,0xC4,0xFC,0x7C,0x09,0x51,0x17,0xE3,0x17, + 0x45,0x12,0x83,0xFD,0x0F,0xEB,0xE6,0xE2,0x70,0xE0,0x9A,0xEE, + 0x7C,0xFA,0x8E,0xFD,0x01,0xFE,0x4A,0xFC,0xE2,0xFB,0xF1,0xFE, + 0xE3,0x0A,0xDB,0x10,0x11,0x10,0xD1,0x0B,0x5C,0x00,0xC7,0xFB, + 0x6D,0xFB,0x58,0xFE,0xDD,0x04,0x35,0x05,0xA0,0x00,0xE7,0xFB, + 0x99,0xFA,0xF8,0xFB,0x07,0x01,0x02,0x07,0x5C,0x06,0x17,0x04, + 0xC0,0x00,0x8B,0xFD,0x81,0x00,0xB1,0x03,0xF9,0x05,0x60,0x05, + 0xE0,0xFF,0xEF,0xFB,0x59,0xFA,0xB0,0xFC,0xC9,0xFE,0x19,0xFE, + 0x86,0xFC,0x33,0xF8,0x29,0xF8,0x35,0xFB,0xA1,0xFE,0xC7,0x03, + 0xA1,0x04,0x4B,0x03,0x54,0x01,0x20,0xFF,0xEF,0xFE,0x4A,0xFF, + 0x5F,0x00,0x83,0xFF,0xB1,0xFE,0xB4,0xFD,0x14,0xFD,0xA4,0xFF, + 0xDB,0x00,0xBE,0x01,0x71,0x01,0xBA,0xFF,0x94,0xFE,0x9E,0xFE, + 0x65,0x00,0xBA,0x01,0xE6,0x02,0xC4,0x02,0xA9,0x00,0xD0,0xFF, + 0x96,0xFE,0xFB,0x00,0xA0,0x0E,0xCA,0x17,0xAF,0x14,0x47,0x0A, + 0x0E,0xF8,0x62,0xEC,0xD6,0xE8,0xED,0xEA,0xA9,0xF4,0xB6,0xF8, + 0x50,0xF7,0x25,0xF6,0x93,0xF8,0xCC,0xFC,0x50,0x04,0x12,0x0E, + 0x3A,0x0E,0xFD,0x0B,0xBD,0x06,0x5C,0x01,0xED,0x01,0xE7,0x02, + 0xB2,0x04,0xFA,0x03,0xDC,0xFF,0x54,0xFA,0x2B,0xF9,0x00,0xFC, + 0xE5,0xFE,0x42,0x03,0xD9,0x03,0x8B,0x01,0x4D,0x00,0x14,0x00, + 0xA1,0x02,0xFE,0x05,0x32,0x07,0x4B,0x05,0x74,0x01,0x9E,0xFE, + 0x4F,0xFD,0x55,0xFD,0x76,0xFD,0x26,0xFC,0xBF,0xFA,0x80,0xF8, + 0xE9,0xF7,0x0F,0xFB,0x12,0xFE,0xA6,0x00,0x81,0x02,0x8B,0x02, + 0x77,0x01,0x1E,0x01,0xC3,0x01,0xC3,0x01,0x8E,0x01,0xFD,0xFF, + 0x90,0xFE,0x05,0xFE,0x4F,0xFD,0x5E,0xFE,0x7C,0xFF,0xB5,0xFF, + 0x6D,0xFF,0x44,0xFF,0x6F,0xFF,0xC6,0xFF,0xE4,0x00,0x45,0x01, + 0xCB,0x01,0x44,0x02,0x22,0x02,0xF3,0x01,0x4D,0x01,0x96,0xFF, + 0x09,0xFF,0xEA,0x05,0x17,0x0E,0xAA,0x0F,0x83,0x0A,0x3C,0x01, + 0x07,0xFA,0x75,0xF3,0x92,0xF1,0xCE,0xF5,0x15,0xF7,0x71,0xF6, + 0xEE,0xF5,0x6F,0xF6,0xC6,0xF7,0xC5,0xFB,0x2D,0x02,0xA0,0x06, + 0xAE,0x08,0x3B,0x07,0x7B,0x06,0x55,0x06,0x25,0x06,0x94,0x07, + 0x98,0x07,0xF4,0x04,0x2B,0x01,0xE9,0xFE,0x40,0xFD,0x5D,0xFD, + 0x4A,0xFE,0x62,0xFE,0xC3,0xFE,0x42,0xFE,0x3D,0xFE,0x6E,0xFF, + 0xCF,0x00,0xEE,0x01,0x41,0x03,0x80,0x03,0x9F,0x02,0xCB,0x01, + 0xE4,0xFF,0x4B,0xFF,0x72,0xFF,0x36,0xFE,0xD9,0xFD,0xA9,0xFD, + 0xA4,0xFC,0x43,0xFC,0xB9,0xFC,0xDF,0xFC,0x95,0xFD,0xCE,0xFE, + 0x05,0xFF,0xAF,0xFF,0xE3,0xFF,0x80,0xFF,0x39,0x00,0xA0,0x00, + 0xB0,0x00,0x25,0x01,0x24,0x01,0x5A,0x00,0x3B,0x00,0x36,0x00, + 0xD3,0xFF,0x41,0x00,0x20,0x00,0xC3,0xFF,0xD5,0xFF,0xC8,0xFF, + 0x0F,0x00,0x95,0x00,0xE1,0x00,0xBB,0x00,0x9B,0x00,0x49,0x00, + 0xE4,0xFF,0xD5,0x02,0x2B,0x07,0x09,0x05,0x11,0x03,0xAD,0x04, + 0x4F,0x01,0x48,0xFF,0xDF,0x00,0x14,0x00,0x4E,0xFE,0x05,0xFD, + 0x2B,0xFB,0xEF,0xF8,0xB2,0xF8,0x71,0xF8,0x1C,0xFA,0x33,0xFC, + 0xB0,0xFA,0x81,0xFB,0xE3,0xFC,0x42,0xFD,0xBF,0xFF,0x3B,0x03, + 0x8E,0x04,0xDC,0x04,0x37,0x06,0x5A,0x05,0x92,0x05,0x5B,0x06, + 0xB2,0x05,0x3C,0x06,0xA4,0x05,0xDB,0x03,0x78,0x02,0xA7,0x01, + 0x0E,0x00,0x31,0x00,0x51,0x01,0x40,0xFF,0x4B,0xFE,0xA4,0xFD, + 0x12,0xFC,0x4F,0xFC,0x1B,0xFD,0x1B,0xFD,0xD4,0xFC,0x01,0xFD, + 0x6C,0xFC,0xBE,0xFC,0x44,0xFD,0x3D,0xFD,0x77,0xFE,0xCF,0xFE, + 0xA2,0xFE,0x4D,0xFF,0xBF,0xFF,0xF4,0xFF,0xFE,0x00,0xBF,0x01, + 0x92,0x01,0xB7,0x01,0x46,0x01,0xD2,0x00,0x2B,0x01,0xFF,0x00, + 0xB4,0x00,0xA5,0x00,0x2A,0x00,0x84,0xFF,0xD2,0xFF,0x1E,0x00, + 0xFA,0xFF,0x30,0x00,0xFE,0xFF,0x9C,0xFF,0x19,0x00,0x1E,0x04, + 0x57,0x06,0x9D,0x03,0x4F,0x04,0x46,0x04,0xE5,0x00,0x40,0x00, + 0x93,0x00,0x49,0xFF,0x84,0xFD,0xB0,0xFC,0xB2,0xFA,0x6E,0xF9, + 0x12,0xF9,0xE2,0xF8,0xE8,0xFA,0xAB,0xFA,0x22,0xFA,0xD4,0xFB, + 0x0F,0xFD,0x7E,0xFE,0x4B,0x01,0xCB,0x03,0xD4,0x03,0xE7,0x04, + 0xA3,0x05,0x74,0x05,0x8E,0x06,0x69,0x06,0x24,0x06,0xB3,0x05, + 0x81,0x04,0x11,0x03,0x83,0x02,0xDB,0x01,0xC2,0x00,0x05,0x01, + 0xCE,0xFF,0x9E,0xFE,0x8D,0xFE,0xDD,0xFD,0x5B,0xFD,0x86,0xFD, + 0xA9,0xFD,0xE6,0xFC,0xEA,0xFC,0xBB,0xFC,0x6E,0xFC,0xE7,0xFC, + 0xC0,0xFC,0x28,0xFD,0x84,0xFD,0x78,0xFD,0x06,0xFE,0xE7,0xFE, + 0x5A,0xFF,0xED,0xFF,0x03,0x01,0x17,0x01,0x5A,0x01,0xC2,0x01, + 0x8D,0x01,0xB8,0x01,0xC2,0x01,0x76,0x01,0x39,0x01,0x30,0x01, + 0x78,0x00,0x1E,0x00,0x64,0x00,0x27,0x00,0x3D,0x00,0x14,0x00, + 0x18,0x00,0xD9,0xFF,0x6E,0xFF,0x8C,0xFF,0x7C,0x01,0x51,0x05, + 0x38,0x04,0xE7,0x02,0xA6,0x04,0x52,0x02,0x6D,0x00,0xF7,0x00, + 0x1E,0x00,0xFD,0xFD,0x17,0xFD,0x04,0xFC,0xFA,0xF9,0xF5,0xF9, + 0x32,0xF9,0xCC,0xF9,0xFF,0xFA,0xFB,0xF9,0xF1,0xFA,0xCF,0xFC, + 0x0E,0xFE,0xD6,0xFF,0xBC,0x02,0x6A,0x03,0x99,0x03,0x5D,0x05, + 0x59,0x05,0x21,0x06,0xCD,0x06,0x36,0x06,0xA0,0x05,0xE1,0x04, + 0xAB,0x03,0xBC,0x02,0xCD,0x02,0x80,0x01,0x07,0x01,0x82,0x00, + 0xF7,0xFE,0xA8,0xFE,0xE5,0xFD,0x3D,0xFD,0x88,0xFD,0xC2,0xFD, + 0xD4,0xFC,0xAA,0xFC,0x11,0xFD,0x76,0xFC,0xFF,0xFC,0x59,0xFD, + 0x24,0xFD,0x5A,0xFD,0x6C,0xFD,0xC9,0xFD,0xA8,0xFE,0x46,0xFF, + 0x8F,0xFF,0xA1,0x00,0xE5,0x00,0xCC,0x00,0x8F,0x01,0xAE,0x01, + 0xA4,0x01,0xD0,0x01,0xAC,0x01,0x25,0x01,0x0B,0x01,0xE1,0x00, + 0x52,0x00,0x73,0x00,0x5D,0x00,0x34,0x00,0x2D,0x00,0x0A,0x00, + 0x2B,0x00,0xFE,0xFF,0xD4,0xFF,0xAD,0xFF,0xDC,0xFF,0xE5,0x02, + 0x18,0x05,0x19,0x03,0x6F,0x03,0x0B,0x04,0x45,0x01,0x63,0x00, + 0x8C,0x00,0x06,0xFF,0x5C,0xFD,0xE9,0xFC,0x7D,0xFB,0x50,0xFA, + 0x14,0xFA,0x66,0xF9,0x93,0xFA,0xCF,0xFA,0x88,0xFA,0x2C,0xFC, + 0xC2,0xFD,0xDB,0xFE,0xD2,0x00,0xFF,0x02,0xFB,0x02,0x0F,0x04, + 0x61,0x05,0x81,0x05,0x56,0x06,0x53,0x06,0xAE,0x05,0xEB,0x04, + 0x47,0x04,0x27,0x03,0xE5,0x02,0x7D,0x02,0x2B,0x01,0xE4,0x00, + 0xB1,0xFF,0x82,0xFE,0x60,0xFE,0xF2,0xFD,0x3F,0xFD,0xA4,0xFD, + 0x9C,0xFD,0xAB,0xFC,0xFB,0xFC,0x04,0xFD,0xF0,0xFC,0x41,0xFD, + 0x55,0xFD,0x3F,0xFD,0x68,0xFD,0xA5,0xFD,0x0A,0xFE,0x1C,0xFF, + 0x41,0xFF,0xAB,0xFF,0x82,0x00,0xA3,0x00,0xF3,0x00,0x88,0x01, + 0xBA,0x01,0x89,0x01,0xD2,0x01,0x7D,0x01,0x2C,0x01,0x4F,0x01, + 0xE6,0x00,0xB3,0x00,0xAA,0x00,0x56,0x00,0x0E,0x00,0x40,0x00, + 0x15,0x00,0xFB,0xFF,0x46,0x00,0xF2,0xFF,0xBD,0xFF,0x8C,0xFF, + 0x56,0xFF,0x80,0x02,0x09,0x05,0x8E,0x02,0x36,0x03,0x48,0x04, + 0x10,0x01,0x34,0x00,0xE0,0x00,0x1C,0xFF,0x4C,0xFD,0x7A,0xFD, + 0xCC,0xFB,0x78,0xFA,0x8F,0xFA,0xAD,0xF9,0xBD,0xFA,0x2F,0xFB, + 0xBD,0xFA,0x31,0xFC,0x17,0xFE,0xBB,0xFE,0x86,0x00,0xEC,0x02, + 0x83,0x02,0xA2,0x03,0x4C,0x05,0x4E,0x05,0x01,0x06,0x6C,0x06, + 0x79,0x05,0x93,0x04,0x6D,0x04,0x24,0x03,0xE9,0x02,0xAF,0x02, + 0x35,0x01,0xBB,0x00,0xCB,0xFF,0x4A,0xFE,0xFD,0xFD,0x69,0xFE, + 0x27,0xFD,0x35,0xFD,0xDD,0xFD,0x9E,0xFC,0xEF,0xFC,0x70,0xFD, + 0x28,0xFD,0x47,0xFD,0x95,0xFD,0x43,0xFD,0x7B,0xFD,0x16,0xFE, + 0xFD,0xFD,0x18,0xFF,0x6C,0xFF,0x4F,0xFF,0x3B,0x00,0xAF,0x00, + 0xBF,0x00,0x63,0x01,0xE5,0x01,0x4C,0x01,0x97,0x01,0x83,0x01, + 0xF4,0x00,0x69,0x01,0x35,0x01,0xA8,0x00,0xC3,0x00,0xA7,0x00, + 0xF5,0xFF,0x48,0x00,0x7A,0x00,0xFF,0xFF,0x27,0x00,0x2B,0x00, + 0x06,0x00,0xE0,0xFF,0xDE,0xFF,0x4D,0xFF,0xA6,0xFF,0x16,0x03, + 0x03,0x04,0x12,0x02,0x80,0x03,0x90,0x03,0xA8,0x00,0x5E,0x00, + 0x77,0x00,0x76,0xFE,0x4B,0xFD,0x58,0xFD,0xEA,0xFB,0x07,0xFB, + 0xC5,0xFA,0x32,0xFA,0x0C,0xFB,0x34,0xFB,0x4A,0xFB,0xF9,0xFC, + 0x83,0xFE,0x26,0xFF,0xED,0x00,0x9B,0x02,0x70,0x02,0xCF,0x03, + 0x44,0x05,0x7B,0x05,0xF6,0x05,0x05,0x06,0x0B,0x05,0x58,0x04, + 0x2F,0x04,0x14,0x03,0xD4,0x02,0x55,0x02,0xE1,0x00,0x3E,0x00, + 0x7A,0xFF,0x30,0xFE,0xCE,0xFD,0x20,0xFE,0x05,0xFD,0x01,0xFD, + 0x97,0xFD,0xC9,0xFC,0x1F,0xFD,0xB0,0xFD,0x66,0xFD,0x3A,0xFD, + 0xB4,0xFD,0x89,0xFD,0xC2,0xFD,0x76,0xFE,0x82,0xFE,0x13,0xFF, + 0x63,0xFF,0x8E,0xFF,0x16,0x00,0xF7,0x00,0xC6,0x00,0x92,0x01, + 0x77,0x01,0xD6,0x00,0xE6,0x01,0x48,0x01,0x55,0x01,0x56,0x01, + 0x2E,0x01,0xD1,0x00,0x84,0x00,0xBA,0x00,0x10,0x00,0x35,0x00, + 0x81,0x00,0xFE,0xFF,0xD7,0xFF,0x34,0x00,0x06,0x00,0xCD,0xFF, + 0x11,0x00,0xFB,0xFF,0x78,0xFF,0x99,0xFF,0x30,0x02,0x0D,0x04, + 0x4D,0x02,0xD9,0x02,0xB5,0x03,0x0E,0x01,0xEA,0xFF,0x76,0x00, + 0x05,0xFF,0x15,0xFD,0x76,0xFD,0x75,0xFC,0xDA,0xFA,0x0C,0xFB, + 0xA3,0xFA,0xD4,0xFA,0x6D,0xFB,0xC5,0xFB,0xC7,0xFC,0x91,0xFE, + 0x95,0xFF,0x88,0x00,0x85,0x02,0xBA,0x02,0x55,0x03,0x1B,0x05, + 0x93,0x05,0x88,0x05,0xD4,0x05,0x54,0x05,0xFB,0x03,0xDE,0x03, + 0x52,0x03,0x5E,0x02,0x02,0x02,0x0A,0x01,0xFA,0xFF,0x42,0xFF, + 0x79,0xFE,0x8A,0xFD,0xFF,0xFD,0xAA,0xFD,0x87,0xFC,0x85,0xFD, + 0x82,0xFD,0xE5,0xFC,0xB1,0xFD,0x06,0xFE,0x44,0xFD,0x71,0xFD, + 0x40,0xFE,0xE0,0xFD,0x67,0xFE,0x0D,0xFF,0xE7,0xFE,0x5B,0xFF, + 0xBA,0xFF,0xF3,0xFF,0x97,0x00,0x27,0x01,0xFF,0x00,0x4B,0x01, + 0x7E,0x01,0xFE,0x00,0x3D,0x01,0x57,0x01,0xFF,0x00,0xE9,0x00, + 0xCD,0x00,0x7F,0x00,0x82,0x00,0x9D,0x00,0x36,0x00,0x5B,0x00, + 0x68,0x00,0xF2,0xFF,0x2A,0x00,0x74,0x00,0x24,0x00,0x09,0x00, + 0x4D,0x00,0xE9,0xFF,0x91,0xFF,0x19,0x01,0x6D,0x03,0xE8,0x02, + 0xCC,0x01,0xFD,0x02,0x01,0x02,0xCD,0xFF,0x05,0x00,0xD4,0xFF, + 0xC8,0xFD,0xD3,0xFC,0xEE,0xFC,0x8F,0xFB,0xD8,0xFA,0x35,0xFB, + 0xFF,0xFA,0x4C,0xFB,0xE8,0xFB,0xC6,0xFC,0x32,0xFE,0xC5,0xFF, + 0xBF,0x00,0xD6,0x01,0x0B,0x03,0x22,0x03,0x21,0x04,0x7C,0x05, + 0x87,0x05,0x61,0x05,0x3E,0x05,0x58,0x04,0x35,0x03,0x1D,0x03, + 0x8E,0x02,0x88,0x01,0x05,0x01,0x12,0x00,0x1D,0xFF,0x7D,0xFE, + 0xEF,0xFD,0x79,0xFD,0xA1,0xFD,0xC1,0xFD,0x3F,0xFD,0x5B,0xFD, + 0xC9,0xFD,0xF6,0xFD,0x1C,0xFE,0x39,0xFE,0x17,0xFE,0xF6,0xFD, + 0x69,0xFE,0xC4,0xFE,0x19,0xFF,0x32,0xFF,0x20,0xFF,0x3C,0xFF, + 0x9E,0xFF,0xF2,0xFF,0x50,0x00,0xD2,0x00,0x82,0x00,0x8C,0x01, + 0x93,0x01,0x6A,0x00,0xC1,0x00,0xDF,0x00,0x54,0x01,0xDC,0x00, + 0x2D,0x00,0x70,0x00,0x26,0x00,0x33,0x00,0x37,0x00,0xB9,0xFF, + 0xB1,0xFF,0x0E,0x00,0x7F,0x00,0xE5,0x00,0x47,0x01,0xCE,0x00, + 0x78,0x00,0x64,0xFF,0x10,0x01,0xBF,0x07,0xEC,0x08,0x16,0x07, + 0x3C,0x04,0xD3,0xFF,0xD9,0xFE,0x93,0xFD,0xB7,0xFB,0xE8,0xF7, + 0x36,0xF5,0xAD,0xF3,0x53,0xF5,0x85,0xFA,0x42,0xFA,0xFC,0xFA, + 0xB2,0xFC,0x95,0xFE,0x6B,0x02,0x17,0x07,0x72,0x09,0x54,0x07, + 0x38,0x08,0xA5,0x07,0x87,0x07,0x05,0x08,0xD4,0x04,0x59,0x01, + 0x89,0xFE,0x09,0xFE,0xDE,0xFC,0x9C,0xFD,0x79,0xFC,0x32,0xFA, + 0xD6,0xFB,0x6A,0xFD,0xF2,0xFF,0xDC,0x01,0xE8,0x02,0xD8,0x01, + 0x21,0x02,0x81,0x03,0x22,0x02,0x74,0x03,0x3D,0x03,0x7F,0xFF, + 0xCD,0xFD,0x45,0xFD,0x27,0xFC,0xC4,0xFB,0xFA,0xFA,0xF3,0xF8, + 0xD3,0xF9,0x82,0xFC,0xF9,0xFD,0x44,0x00,0x21,0x01,0x0D,0x00, + 0x8C,0x01,0xC7,0x03,0x9A,0x03,0xF1,0x02,0x77,0x02,0xAA,0x00, + 0x99,0x00,0x67,0x01,0x27,0x00,0x13,0xFF,0x22,0xFE,0xC4,0xFD, + 0xA3,0xFE,0x10,0x00,0xA3,0xFF,0x79,0xFF,0xC6,0x00,0x08,0x01, + 0xCC,0x01,0x34,0x02,0x3C,0x02,0xE6,0x01,0x0B,0x02,0x21,0x02, + 0xBC,0x00,0x44,0x00,0x01,0xFE,0xB4,0xFC,0xA7,0x05,0xE1,0x09, + 0x97,0x04,0x15,0x03,0xE5,0xFD,0xE0,0xFB,0xE6,0xFD,0x7F,0xFB, + 0xD1,0xF6,0xBE,0xF3,0xA7,0xF5,0xF5,0xF6,0x84,0xFD,0x55,0xFF, + 0xE4,0xFB,0x68,0xFF,0x64,0x02,0x03,0x06,0xA6,0x08,0xB6,0x09, + 0xE1,0x05,0x82,0x04,0x6C,0x07,0x33,0x05,0x8C,0x04,0x11,0x01, + 0xF1,0xFC,0x19,0xFC,0xC5,0xFD,0x49,0xFE,0xC3,0xFC,0x78,0xFD, + 0xEA,0xFB,0x13,0xFE,0x43,0x02,0x18,0x03,0xE1,0x02,0xB2,0x02, + 0xC0,0x02,0x1C,0x02,0x5D,0x03,0x6D,0x01,0x45,0xFD,0x35,0xFF, + 0xD8,0xFF,0x01,0xFE,0xB8,0xFD,0x5B,0xFB,0xBD,0xF9,0xCB,0xFA, + 0xDA,0xFC,0x49,0xFC,0x42,0xFD,0x6D,0xFF,0x1A,0xFF,0xD9,0x02, + 0xA1,0x03,0xB1,0x01,0xB0,0x01,0xB5,0x01,0x29,0x01,0xBB,0x00, + 0xAD,0x01,0x0A,0xFF,0xBF,0xFE,0xE7,0xFF,0x5F,0xFF,0xF6,0xFF, + 0xA2,0xFF,0x57,0xFF,0x47,0xFF,0x5A,0x01,0x8E,0x01,0x36,0x01, + 0x1D,0x02,0xF7,0x00,0xE1,0x00,0xAC,0x01,0xF2,0x01,0xC5,0x00, + 0xC1,0x00,0x81,0xFF,0x20,0xFE,0xD8,0xFC,0x24,0x02,0x31,0x0C, + 0xCF,0x06,0xD8,0x03,0xFF,0x00,0xF5,0xFB,0x58,0xFF,0xB9,0xFC, + 0x8C,0xF8,0x7F,0xF2,0x76,0xF3,0x65,0xF6,0xA6,0xF9,0xEC,0xFF, + 0x32,0xFB,0x05,0xFD,0xD5,0x01,0xA9,0x05,0x26,0x09,0x16,0x09, + 0x2D,0x08,0xD5,0x03,0xA1,0x07,0xC4,0x07,0x84,0x04,0x88,0x02, + 0x34,0xFD,0x16,0xFC,0xBB,0xFC,0xCC,0xFE,0xAE,0xFC,0xB6,0xFB, + 0xE1,0xFC,0xE6,0xFC,0x6E,0x01,0x69,0x03,0xC9,0x02,0x5E,0x02, + 0xD2,0x02,0x8A,0x03,0x3B,0x03,0xB8,0x02,0x38,0xFF,0xD7,0xFB, + 0x44,0xFD,0x52,0xFF,0x72,0xFE,0xDF,0xFD,0xA2,0xFB,0xF2,0xF9, + 0x9E,0xFC,0x4B,0xFE,0x1E,0xFE,0xF4,0xFD,0x93,0xFE,0x4B,0xFF, + 0xE7,0x01,0xB3,0x03,0x2B,0x01,0x76,0x00,0x77,0x00,0x1F,0x00, + 0xFB,0x00,0x19,0x01,0xA5,0xFF,0x4E,0xFE,0x0F,0x00,0x4A,0x00, + 0x35,0x00,0x12,0x01,0xED,0xFF,0x26,0x00,0x4C,0x01,0xE4,0x01, + 0x77,0x01,0x67,0x01,0xE3,0x00,0xE3,0xFF,0x9C,0x01,0xA3,0x01, + 0xD9,0xFF,0x23,0x00,0xA8,0xFF,0x51,0xFF,0x77,0xFF,0x5D,0xFF, + 0x6C,0xFD,0xEA,0x01,0x60,0x0A,0x38,0x05,0xA4,0x03,0xD4,0x01, + 0x5A,0xFC,0x83,0xFE,0x23,0xFD,0xC8,0xF9,0x61,0xF4,0x81,0xF6, + 0x41,0xF8,0xE8,0xF9,0xDE,0xFF,0x60,0xFC,0x71,0xFD,0x55,0x01, + 0xE2,0x04,0x16,0x06,0xCE,0x06,0xCB,0x06,0x1D,0x03,0x15,0x07, + 0x27,0x07,0x74,0x04,0x7E,0x02,0x98,0xFF,0x07,0xFE,0xEB,0xFD, + 0xE1,0xFF,0xAE,0xFC,0xEF,0xFB,0x4D,0xFD,0x75,0xFD,0x28,0x00, + 0x8D,0x01,0xF0,0x00,0x69,0x00,0x77,0x02,0xFD,0x02,0x40,0x01, + 0x0F,0x02,0xB3,0x01,0x9A,0x00,0x49,0x00,0x96,0xFD,0x70,0xFC, + 0x36,0xFC,0xDB,0xFB,0xBC,0xFB,0x93,0xFB,0x4A,0xFC,0xB6,0xFC, + 0x64,0xFF,0xE9,0x00,0xA2,0x00,0xDF,0x00,0x73,0x01,0x27,0x02, + 0x34,0x02,0x4E,0x02,0xA3,0x00,0x1A,0x00,0x6F,0x00,0x56,0x00, + 0x55,0x00,0x95,0xFF,0x40,0xFF,0xDC,0xFE,0xFF,0xFF,0xAA,0x00, + 0x76,0x00,0xCA,0x00,0xAF,0x00,0x12,0x01,0xA8,0x01,0xB3,0x01, + 0x07,0x01,0xDF,0x00,0xE3,0x00,0x8F,0x00,0x80,0x00,0xA0,0xFF, + 0xE0,0xFE,0xC6,0xFE,0x44,0xFF,0x39,0xFF,0x3F,0xFF,0x35,0x04, + 0x64,0x04,0x2B,0x02,0xA3,0x02,0x07,0xFF,0x6B,0xFE,0xE8,0xFD, + 0x06,0xFD,0x7D,0xFA,0x3C,0xF9,0x31,0xFB,0xB6,0xFA,0xEE,0xFD, + 0x0F,0xFE,0xA8,0xFD,0x4E,0xFF,0x57,0x01,0x6C,0x03,0x4E,0x03, + 0x0D,0x05,0x2A,0x03,0xC3,0x03,0x8C,0x05,0x11,0x04,0x89,0x03, + 0xCE,0x01,0xA7,0x00,0x90,0xFF,0x3F,0x00,0xD1,0xFF,0xED,0xFD, + 0x6E,0xFE,0x46,0xFE,0xC7,0xFE,0x49,0x00,0x15,0x00,0xE0,0xFF, + 0xFF,0x01,0x7C,0x03,0x4A,0x02,0x42,0x01,0x1E,0x00,0x2F,0xFE, + 0x27,0xFD,0x9D,0xFC,0x3D,0xFB,0x9C,0xFA,0xC7,0xFB,0xB9,0xFC, + 0x42,0xFE,0x58,0xFF,0x07,0xFF,0x8E,0xFF,0x12,0x01,0x83,0x01, + 0x11,0x01,0x92,0x01,0x31,0x01,0x5C,0x01,0x4D,0x02,0xFC,0x01, + 0xF5,0x00,0x63,0x00,0x71,0x00,0x91,0x00,0xE8,0x00,0xCB,0xFF, + 0x84,0xFE,0xF7,0xFE,0xDF,0xFF,0x67,0x00,0xE7,0x00,0xBA,0x00, + 0x13,0x00,0x73,0x00,0xF4,0x00,0xD6,0x00,0x19,0x00,0x48,0xFF, + 0x18,0xFF,0xA1,0xFF,0x47,0x00,0x07,0x00,0x59,0xFF,0xE7,0xFE, + 0x0D,0xFF,0xEA,0xFF,0x26,0x00,0x97,0xFF,0x3A,0xFF,0xD0,0xFF, + 0x48,0x00,0x63,0x00,0x18,0x01,0xE1,0x00,0xAE,0x00,0xC3,0x00, + 0xDF,0x00,0xB0,0x00,0x4A,0x00,0xFD,0xFF,0xCB,0xFE,0x01,0xFF, + 0x64,0xFF,0x80,0xFF,0xA6,0xFF,0x24,0xFF,0x46,0xFF,0x8C,0xFF, + 0x7A,0x00,0x99,0x00,0x9C,0x00,0x01,0x01,0xB3,0x00,0x55,0x01, + 0x78,0x01,0xFC,0x00,0xD3,0x00,0xC3,0x00,0x7C,0x00,0x9C,0x00, + 0x98,0x00,0xA6,0xFF,0x91,0xFF,0x2A,0x00,0x42,0xFF,0x4E,0xFE, + 0x15,0xFF,0xC1,0xFE,0xDE,0xFE,0x8B,0xFF,0xE0,0xFE,0x11,0xFF, + 0x6B,0xFF,0x6D,0xFF,0x69,0xFF,0xD2,0xFF,0x20,0x00,0x20,0xFF, + 0x33,0x00,0x0E,0x00,0xF0,0xFF,0x38,0x01,0xE9,0xFF,0x45,0x00, + 0x6C,0x00,0x79,0x00,0x17,0x00,0x3C,0x00,0xF4,0xFF,0x32,0xFF, + 0xE2,0xFF,0x46,0xFF,0xF2,0xFF,0x48,0xFF,0x67,0xFF,0x37,0x00, + 0x37,0x00,0xCD,0x00,0xB4,0xFF,0x61,0x00,0x59,0x00,0xAD,0x00, + 0x0D,0x01,0x01,0x00,0x63,0x00,0xE2,0xFF,0x6F,0x00,0x70,0x00, + 0xC2,0xFF,0x6B,0xFF,0x59,0xFF,0x96,0xFF,0xAE,0xFF,0x9B,0xFF, + 0xA1,0xFF,0x62,0x00,0x9E,0x00,0xE7,0x00,0x71,0x01,0x3C,0x00, + 0x80,0x00,0x4D,0x00,0x89,0x00,0x90,0x01,0x71,0xFF,0xB2,0xFF, + 0x3D,0xFF,0xF5,0xFF,0xFA,0xFF,0x63,0xFF,0x0A,0xFF,0x5C,0xFF, + 0x73,0x01,0xF6,0x00,0x50,0x00,0x6B,0xFF,0x72,0x00,0x4F,0x00, + 0x4F,0x00,0xA4,0x00,0x8E,0x00,0xDC,0x00,0xF9,0xFE,0x42,0xFF, + 0x90,0xFF,0xFA,0xFF,0x1C,0xFF,0x9E,0xFD,0xC9,0xFF,0x70,0x00, + 0xCC,0xFF,0x9C,0xFF,0xA6,0xFF,0x5F,0xFF,0xF6,0x00,0x0F,0x00, + 0x53,0xFF,0x6D,0x01,0xD6,0xFF,0x09,0xFF,0x1B,0x00,0x9E,0x00, + 0x40,0x00,0xDA,0xFE,0x96,0xFF,0x48,0x00,0xD4,0xFE,0x7F,0x00, + 0x38,0xFF,0x80,0xFF,0xB6,0x00,0x09,0xFE,0x8F,0x01,0xF9,0x01, + 0x72,0xFF,0x5F,0x00,0x9C,0x00,0xA2,0x00,0x3A,0xFF,0x58,0x00, + 0x31,0x00,0x18,0x00,0x40,0xFF,0xF6,0xFD,0x33,0x00,0xF0,0xFF, + 0x6F,0x00,0x90,0xFF,0x4F,0x00,0xD0,0x00,0x1F,0x01,0x74,0xFF, + 0xF7,0xFF,0xEE,0x00,0xAC,0xFE,0xB8,0x02,0x8E,0x00,0x37,0xFF, + 0x25,0x00,0x68,0x00,0xDF,0xFF,0x6B,0xFE,0x9D,0x00,0x23,0xFF, + 0x72,0x00,0x4B,0xFE,0x5C,0xFF,0x2E,0x03,0x12,0xFF,0x89,0xFE, + 0xF4,0x01,0xD4,0x01,0x74,0xFE,0x32,0x01,0x20,0x00,0x2C,0xFF, + 0xC0,0x01,0x65,0xFF,0x28,0xFF,0xBA,0x01,0x52,0xFE,0xAD,0xFE, + 0x55,0x01,0x10,0xFF,0xE4,0x00,0xB0,0xFE,0xAB,0xFF,0x44,0x00, + 0xFF,0xFE,0x4F,0x00,0x1A,0x01,0xAE,0x00,0x95,0xFE,0x48,0x01, + 0xE8,0x00,0x60,0x00,0x85,0xFF,0x0A,0xFF,0x50,0x00,0x2B,0xFE, + 0xCC,0xFF,0x75,0x00,0xCB,0xFE,0x95,0x00,0x64,0x00,0xD8,0xFF, + 0x77,0x00,0xC1,0xFF,0x48,0x00,0x29,0x00,0x8F,0xFF,0x03,0x01, + 0x93,0xFE,0x05,0x01,0xA2,0x01,0xDE,0xFD,0x1B,0x00,0x97,0xFE, + 0x9F,0x01,0x35,0xFE,0xA1,0x00,0x6C,0x01,0x4C,0xFD,0x81,0x01, + 0xBA,0xFF,0xFA,0xFF,0xC7,0xFF,0x66,0x01,0xC1,0xFE,0x71,0x02, + 0xF7,0x00,0xF1,0xFE,0x5C,0x00,0x26,0xFE,0x16,0xFF,0x34,0xFE, + 0xDE,0x01,0x4F,0xFF,0x58,0x02,0x51,0xFD,0xE8,0xFF,0x9A,0x04, + 0x7A,0xFD,0x89,0x01,0xD8,0xFC,0x71,0x01,0x78,0x02,0xE3,0xFD, + 0xC2,0xFF,0x77,0xFE,0xAA,0x01,0xF2,0xFF,0xF7,0x00,0x3E,0xFF, + 0x9A,0xFE,0xCD,0x00,0xDD,0xFF,0x6E,0x01,0x6E,0x00,0xCA,0xFE, + 0xB3,0xFE,0x44,0x02,0x8F,0x00,0x46,0xFF,0xA2,0xFF,0x89,0x00, + 0x83,0x00,0xBA,0xFF,0xF6,0xFF,0x6B,0xFE,0xF1,0x00,0xF5,0xFE, + 0x47,0x01,0x6B,0x00,0xD4,0xFD,0x68,0x02,0xCA,0xFD,0x1C,0x02, + 0xAF,0x00,0xBE,0xFC,0x71,0x02,0x9D,0xFF,0x5D,0x01,0x96,0x00, + 0x69,0xFE,0x7C,0xFE,0xA5,0x01,0x6D,0xFF,0xB8,0xFE,0x02,0x01, + 0xB4,0xFF,0xB6,0xFF,0x71,0x00,0xB5,0x01,0x15,0xFD,0xA9,0x00, + 0x8D,0x00,0xB2,0xFF,0xF3,0x01,0xC4,0xFD,0x14,0x00,0x39,0x01, + 0x2A,0x00,0x73,0x00,0x44,0xFF,0x73,0x00,0xDD,0xFE,0x4B,0x00, + 0x47,0x00,0x36,0xFE,0x5B,0x01,0x4C,0x00,0x45,0xFD,0xCD,0x00, + 0x30,0x02,0x0C,0x00,0xF0,0xFD,0x8B,0xFF,0x4A,0x01,0x75,0x00, + 0x10,0x00,0xE7,0xFD,0x5A,0x02,0x1D,0xFF,0x04,0x01,0xDA,0x00, + 0x47,0xFE,0xCD,0x01,0xB7,0xFF,0xE8,0xFD,0x61,0x00,0xEB,0xFF, + 0x78,0xFE,0xD8,0x02,0x67,0xFE,0x35,0x02,0x0A,0xFF,0x7A,0xFD, + 0x0A,0x01,0x4F,0x00,0x5A,0x00,0xF4,0xFE,0xC3,0x01,0x76,0x00, + 0xAD,0x01,0x15,0xFF,0xBA,0xFF,0x7B,0xFE,0x84,0xFE,0x5B,0x02, + 0x1A,0xFF,0x84,0x00,0x78,0x01,0xC6,0xFE,0x01,0xFF,0x38,0x01, + 0xB2,0xFD,0x6D,0x01,0x29,0x00,0xD9,0xFD,0x79,0x03,0x8C,0xFE, + 0xE4,0x01,0x7C,0xFE,0xB3,0xFF,0x76,0x00,0x59,0x00,0x29,0x01, + 0x8F,0xFE,0xD7,0xFF,0x08,0xFF,0x0D,0x03,0xDD,0xFD,0xBC,0x01, + 0x50,0xFD,0x0A,0x00,0x4F,0x02,0x8E,0xFD,0xCD,0x00,0x65,0xFF, + 0x09,0x01,0xBF,0xFE,0xBA,0x01,0x83,0xFF,0xE4,0xFE,0xBF,0x01, + 0xF6,0xFF,0xA4,0xFE,0xA0,0x00,0xC8,0x00,0x23,0xFF,0xFA,0x01, + 0x8E,0xFE,0xBB,0xFF,0x5D,0x01,0x73,0xFF,0x1C,0x00,0xC9,0xFD, + 0x0B,0x01,0x0B,0x00,0x76,0x00,0x45,0x00,0x12,0xFE,0x53,0x00, + 0xF0,0xFE,0x04,0x02,0x1A,0x01,0x1F,0xFE,0xF3,0xFF,0xE7,0x00, + 0x96,0x00,0x46,0x01,0x68,0xFF,0x17,0xFD,0xC2,0x00,0x8F,0x02, + 0x9A,0x00,0xD6,0xFD,0x73,0xFD,0xC7,0x01,0xD6,0xFE,0x56,0x00, + 0x54,0x01,0xA0,0xFE,0xD8,0xFF,0xCD,0xFF,0x98,0x02,0x90,0xFF, + 0x0A,0x00,0x2A,0xFF,0x34,0x01,0x42,0xFF,0xAE,0x00,0x27,0x01, + 0x14,0xFD,0x89,0x02,0x59,0xFE,0x5A,0x00,0xE1,0xFF,0x08,0xFF, + 0x9B,0x01,0x94,0xFE,0xA1,0xFE,0x7F,0x01,0x0A,0x00,0x50,0x00, + 0xBF,0x00,0x4D,0xFE,0x54,0x01,0x11,0x01,0x3F,0xFF,0x4A,0xFE, + 0x82,0x01,0xC8,0x01,0x17,0xFE,0x9E,0xFF,0x6E,0xFF,0x9B,0x00, + 0x52,0x00,0x1B,0xFF,0x9D,0xFF,0xE1,0xFF,0xFE,0x00,0x4B,0xFF, + 0x5A,0x00,0xC8,0x00,0x2D,0x02,0xAF,0xFE,0x40,0xFD,0xD9,0x01, + 0x4E,0x01,0x2F,0x01,0x68,0xFD,0x34,0x00,0x2F,0x02,0xA9,0xFD, + 0xDA,0xFF,0x1A,0x00,0x2C,0xFE,0xA5,0x00,0xBB,0x00,0x66,0xFF, + 0xC5,0x01,0x92,0xFE,0x46,0x00,0x61,0x01,0xFF,0xFD,0xA5,0x01, + 0x58,0xFF,0x74,0x01,0x0D,0x01,0x54,0xFD,0x33,0x00,0xF4,0xFE, + 0x7E,0x00,0xFC,0x01,0x72,0xFE,0xAD,0x00,0x80,0x00,0xB3,0xFC, + 0x1C,0x02,0xD1,0xFF,0x8C,0x00,0x8E,0xFF,0x68,0xFE,0x18,0x04, + 0xA3,0xFD,0x72,0x00,0xF3,0x01,0x95,0xFD,0xF4,0xFF,0xEE,0xFF, + 0xA6,0xFE,0x4B,0x02,0xEA,0xFF,0x0B,0xFF,0x58,0x00,0x9D,0x00, + 0xCE,0x01,0x23,0xFC,0x7B,0x01,0x32,0x00,0x59,0xFE,0x33,0x03, + 0x2D,0xFE,0x26,0x01,0x3B,0x00,0x25,0xFC,0xC6,0x02,0x2F,0x00, + 0xEF,0xFD,0x6E,0x01,0x3F,0x01,0x38,0x00,0x29,0xFF,0x6D,0xFE, + 0xAA,0x00,0x62,0x00,0xDF,0xFE,0x0E,0x00,0xE9,0x01,0x54,0xFF, + 0x8E,0xFE,0xC4,0x02,0x07,0xFC,0xDC,0x01,0x29,0x03,0xEA,0xFD, + 0x0E,0x00,0x18,0x00,0xB3,0x00,0xEB,0xFF,0x87,0xFF,0x2F,0x00, + 0x01,0x00,0x64,0xFE,0xF2,0x01,0x3C,0xFE,0x67,0x01,0x6F,0xFE, + 0xA0,0xFE,0xA2,0x03,0x29,0xFD,0x87,0xFF,0x27,0x01,0x85,0x01, + 0x45,0xFE,0xCE,0x00,0xE7,0xFF,0x0F,0xFF,0x9E,0x01,0xD6,0xFF, + 0x45,0x01,0xD0,0xFD,0x6C,0x01,0x48,0x00,0xB6,0xFF,0x45,0x00, + 0x2B,0xFE,0x49,0x01,0x63,0xFE,0x51,0x02,0x1D,0xFE,0x28,0xFF, + 0x24,0x02,0x7A,0xFF,0xCB,0x00,0x65,0xFD,0xC2,0x01,0x20,0x00, + 0xB2,0x00,0xD3,0xFF,0x37,0xFE,0xAA,0x00,0x36,0x01,0xCB,0x00, + 0xC3,0xFE,0xAD,0xFE,0x37,0xFF,0xED,0x03,0xDE,0xFD,0x2B,0x00, + 0x06,0x00,0x61,0xFF,0x0F,0x02,0x99,0xFD,0x42,0x01,0xA7,0xFE, + 0x4E,0x00,0xF3,0xFE,0x43,0x02,0xF3,0xFF,0x63,0xFE,0x78,0x01, + 0xFD,0xFE,0xC3,0x00,0xA4,0xFE,0x8F,0x01,0xC1,0xFF,0xF1,0x01, + 0xA6,0xFD,0x41,0x00,0x3C,0x01,0x8C,0xFF,0x82,0x01,0x92,0xFC, + 0x8B,0x01,0xC4,0xFE,0x97,0x00,0xD0,0xFE,0x74,0x00,0xCC,0x00, + 0x98,0xFE,0x61,0x00,0xFA,0xFF,0x99,0x02,0x9D,0xFE,0x9F,0x01, + 0xCA,0xFF,0x14,0xFE,0x1A,0x02,0x16,0xFF,0x50,0x00,0x33,0x00, + 0x12,0xFE,0x20,0x01,0x56,0xFD,0x76,0x00,0x00,0x01,0x01,0x00, + 0x53,0x01,0x3B,0xFD,0xAB,0x01,0xB7,0x01,0x7A,0x01,0xCA,0xFD, + 0x97,0x00,0xEF,0xFE,0xFB,0x00,0x79,0x02,0x9A,0xFC,0x32,0x02, + 0xC5,0xFC,0x75,0xFF,0x2D,0x01,0xA8,0xFF,0x8B,0xFE,0x63,0x01, + 0x88,0x00,0x37,0xFE,0x22,0x04,0x9A,0xFE,0x2C,0x00,0xDA,0xFF, + 0x73,0xFE,0x5B,0x01,0x67,0x00,0xC3,0xFC,0x88,0x01,0x89,0x02, + 0xDD,0xFC,0x10,0x03,0xAD,0xFE,0x35,0x00,0xE9,0x00,0xCA,0xFD, + 0xAC,0x00,0xA4,0xFD,0x6A,0x01,0xEB,0x00,0x05,0x00,0x1D,0xFF, + 0x4C,0x01,0x73,0xFF,0x93,0x00,0x06,0x03,0xC6,0xFB,0xDB,0x01, + 0xED,0xFD,0x2B,0x01,0xC0,0x02,0x39,0xFB,0xDC,0x00,0x1D,0x00, + 0xA6,0x01,0xE8,0xFE,0x75,0x00,0xB9,0xFF,0x15,0x01,0x35,0xFF, + 0xF8,0xFF,0x64,0x03,0x0A,0xFB,0x78,0x02,0xD1,0xFF,0x8D,0x00, + 0xDD,0xFF,0xD6,0xFD,0x61,0x01,0xC8,0xFF,0xA3,0x00,0xCD,0xFE, + 0xC4,0xFE,0x82,0xFF,0x0C,0x02,0x76,0xFF,0xE5,0xFE,0x3B,0x02, + 0xE9,0xFE,0x6A,0x00,0xF7,0x02,0xB0,0xFF,0x9E,0xFE,0xD9,0xFF, + 0x90,0xFF,0x25,0xFF,0x2A,0x02,0x44,0xFD,0x51,0xFF,0x3F,0x00, + 0x4E,0xFE,0x76,0x01,0xB9,0x00,0x54,0x00,0xD0,0x00,0xAF,0x00, + 0x7E,0xFF,0xFB,0x01,0x81,0xFF,0xF9,0xFF,0x1B,0xFF,0x17,0x00, + 0xA0,0x00,0xC5,0xFC,0xF8,0x02,0x95,0xFD,0x6C,0xFE,0x13,0x03, + 0xFD,0xFD,0xBB,0xFF,0xFD,0xFF,0x38,0x02,0x36,0x01,0x6D,0xFF, + 0x40,0xFF,0x0C,0x00,0x13,0x01,0xC4,0x00,0xDA,0xFD,0xB1,0xFF, + 0x90,0xFF,0x6C,0xFF,0xA0,0x01,0xB2,0xFE,0xDC,0x00,0xF5,0xFF, + 0x83,0xFF,0x92,0x00,0xE1,0xFF,0xC5,0xFE,0x1F,0x02,0x5E,0x00, + 0x4C,0xFF,0xA3,0xFE,0xC4,0xFF,0xB4,0x02,0xEC,0x00,0x48,0x00, + 0xC8,0xFC,0x91,0x01,0x64,0xFF,0x3B,0xFE,0x4C,0x01,0x3B,0xFF, + 0xCA,0xFF,0x31,0xFE,0xC9,0x01,0xCB,0x01,0x34,0x02,0xA8,0xFE, + 0x9E,0xFB,0x15,0x02,0xBC,0x01,0xCD,0xFF,0x91,0x00,0x7A,0x00, + 0xD7,0xFA,0x19,0x02,0x22,0x03,0x7B,0xFD,0x17,0x01,0x91,0xFD, + 0x46,0x01,0x78,0x02,0x8E,0x00,0x13,0xFD,0x2F,0x00,0xA2,0x01, + 0xBE,0xFF,0x93,0x00,0x7B,0xFC,0x54,0x01,0x1C,0x00,0x50,0x00, + 0x48,0xFF,0xF9,0xFD,0xE0,0x01,0x83,0x00,0xC6,0x01,0x35,0x00, + 0xF1,0xFE,0x0C,0x02,0xED,0xFD,0xF1,0x00,0x69,0x00,0xBE,0xFD, + 0x4F,0x01,0x2E,0xFE,0x2F,0xFF,0x34,0x01,0x55,0x00,0x75,0xFD, + 0x1F,0x02,0xB0,0xFF,0x4E,0x00,0x56,0x01,0xD6,0x00,0x96,0x01, + 0x14,0xFC,0x56,0x03,0x34,0x00,0xBA,0xFD,0x6E,0x00,0xFB,0xFF, + 0xC0,0xFF,0x01,0xFD,0x87,0x02,0x67,0xFF,0xA4,0xFF,0x63,0x00, + 0x7B,0xFD,0x99,0x03,0xAA,0x00,0xD6,0xFE,0x5C,0xFF,0x15,0x00, + 0x10,0x00,0x6F,0x02,0x43,0xFF,0x68,0xFE,0x06,0x02,0x11,0xFF, + 0xA1,0xFF,0x63,0xFE,0xE2,0x00,0x60,0xFF,0x54,0x01,0xEC,0xFE, + 0xB1,0xFE,0xF3,0x02,0xD1,0xFF,0xED,0xFE,0x0F,0x00,0x78,0xFF, + 0xDA,0x00,0xB0,0x00,0x5B,0xFE,0xD4,0x02,0x10,0xFD,0x46,0x02, + 0x99,0xFF,0x30,0xFF,0xE3,0x02,0x11,0xFC,0x51,0x00,0xC6,0x00, + 0xBB,0x00,0x98,0xFD,0x90,0x02,0x1F,0xFD,0xE2,0xFF,0x92,0x02, + 0xC9,0xFE,0x47,0x01,0x7E,0xFD,0xC4,0x02,0xBD,0x00,0x63,0xFF, + 0x6E,0xFE,0xE7,0x00,0x82,0x00,0xFE,0xFF,0xDA,0x00,0x45,0xFF, + 0x03,0xFF,0x22,0xFE,0xCD,0x01,0xFB,0xFE,0xCE,0xFF,0x7F,0x01, + 0x4D,0xFE,0xC7,0x00,0x64,0x02,0xF9,0xFF,0x05,0x00,0xAA,0xFE, + 0x59,0xFF,0xD6,0x00,0xF2,0xFF,0xEE,0x00,0x67,0xFD,0x14,0x00, + 0x71,0x00,0x79,0x00,0x14,0x01,0x7F,0xFD,0xFE,0x01,0x52,0x00, + 0xA3,0xFF,0x1F,0x00,0x60,0xFF,0xA7,0xFE,0x86,0x01,0x56,0x01, + 0xF4,0xFE,0x6B,0x00,0x87,0xFF,0xD8,0x00,0x15,0x00,0xCF,0x00, + 0xF7,0xFD,0x99,0xFE,0x6D,0x00,0x44,0x00,0x7D,0x00,0xA8,0xFF, + 0xEF,0xFE,0xBB,0x01,0x30,0x01,0x78,0xFE,0x7A,0x00,0x8F,0xFF, + 0x43,0x01,0x37,0xFF,0xDC,0x00,0x8B,0xFF,0x2F,0x02,0x53,0xFE, + 0x02,0xFE,0x8E,0x02,0xDA,0xFC,0x2E,0x01,0xBD,0xFD,0x25,0x02, + 0xEC,0x00,0xF3,0xFD,0x5F,0x01,0x36,0xFE,0x27,0x02,0xBA,0xFF, + 0x84,0xFF,0xA2,0xFF,0x32,0x00,0x64,0x02,0xE0,0xFE,0xF6,0x02, + 0x5A,0xFD,0x92,0xFF,0x94,0x01,0xF7,0xFD,0x9F,0x01,0xF2,0xFE, + 0x0C,0x00,0x1F,0xFF,0xED,0x00,0x31,0xFD,0x7D,0x02,0xC9,0xFF, + 0x1F,0xFD,0xFB,0x03,0x39,0xFC,0x0F,0x01,0x66,0x04,0xFA,0xFF, + 0x59,0xFE,0xB9,0xFF,0xB0,0xFF,0x57,0x00,0xAD,0x00,0x68,0xFD, + 0x60,0xFE,0xF6,0x01,0x90,0xFF,0x95,0x00,0xEE,0x00,0x22,0xFF, + 0xBB,0x00,0x32,0x00,0x0D,0x00,0xB8,0xFD,0xF7,0x00,0x3C,0x00, + 0xC6,0x00,0x42,0x01,0x5D,0x00,0x23,0xFE,0x74,0x00,0x7A,0x01, + 0x9E,0xFC,0xB8,0x02,0x5E,0xFE,0xD8,0xFE,0x8A,0x00,0x94,0xFF, + 0x84,0x01,0xE6,0x00,0xA0,0xFE,0xF9,0xFF,0x09,0x02,0x24,0xFF, + 0x0F,0x01,0xAA,0xFF,0x3B,0xFE,0x61,0x00,0x4E,0x01,0xA1,0xFD, + 0x10,0x02,0xB3,0xFE,0x6F,0xFE,0x85,0x01,0x4C,0xFF,0xD7,0x00, + 0x19,0x00,0xEE,0x00,0xA6,0xFD,0xBB,0x03,0x2F,0x00,0xF0,0xFD, + 0x55,0x00,0x4C,0xFD,0x69,0x02,0x35,0x00,0xDB,0xFF,0x87,0xFE, + 0xC2,0xFF,0x33,0x00,0x33,0x00,0x25,0x02,0x7B,0xFE,0x1D,0x00, + 0x54,0x00,0x63,0x01,0x54,0xFE,0x8D,0x01,0x00,0x00,0x85,0xFD, + 0xFE,0x01,0x33,0xFD,0x11,0x01,0xDE,0x00,0xEF,0xFF,0x08,0x00, + 0xFC,0xFE,0xF6,0xFF,0xE6,0xFF,0x01,0x01,0x30,0x00,0x72,0x01, + 0x23,0xFD,0xC2,0x01,0x2B,0x01,0x80,0xFF,0xED,0x02,0xB9,0xFB, + 0x40,0x01,0xC9,0xFF,0x6A,0xFF,0xFA,0x00,0xDD,0xFC,0x57,0x00, + 0x9F,0xFF,0xA9,0x00,0x88,0xFF,0x9F,0x01,0xB0,0xFF,0x7C,0xFE, + 0xA0,0x03,0x18,0xFE,0x0A,0x00,0x9D,0xFF,0x08,0x00,0x98,0x02, + 0x6C,0xFD,0x8A,0x01,0x3C,0xFF,0xDE,0xFE,0x5E,0x02,0x24,0x00, + 0x0E,0x00,0xA5,0xFE,0x17,0x00,0x1D,0x01,0x56,0x00,0x2D,0xFD, + 0x95,0x01,0x36,0x00,0x86,0xFC,0x94,0x02,0x30,0xFF,0xBB,0x00, + 0x37,0x00,0x63,0xFE,0x25,0x01,0x95,0xFF,0x66,0x00,0xC6,0x00, + 0xB3,0xFF,0x98,0x00,0x44,0x00,0x03,0x01,0x35,0x00,0x74,0xFE, + 0x2C,0x00,0x01,0x00,0x6C,0xFF,0x9A,0xFF,0xCE,0xFE,0x73,0xFF, + 0xED,0x01,0x31,0xFF,0x53,0xFF,0x96,0x00,0x22,0xFF,0x85,0x01, + 0xF5,0x01,0x14,0x00,0x7B,0xFE,0x1A,0xFF,0x41,0x02,0xBB,0xFF, + 0xEE,0xFE,0x1C,0x00,0xCF,0xFF,0x0D,0x00,0xB9,0x00,0x5B,0xFF, + 0xFC,0xFD,0x9F,0x02,0x30,0xFF,0xFE,0xFE,0x37,0x01,0x67,0xFF, + 0x36,0x00,0x18,0x00,0xBC,0xFF,0xA0,0xFF,0xC4,0x00,0x92,0xFF, + 0xFA,0xFF,0x83,0x01,0x98,0x00,0x13,0xFE,0xC5,0x00,0xBA,0x00, + 0xE3,0xFE,0x1C,0x01,0xCC,0xFE,0x2A,0xFF,0xC0,0x00,0x4F,0x00, + 0x9F,0xFF,0x42,0xFF,0x39,0xFF,0xBD,0x01,0x40,0x00,0xB8,0xFE, + 0x13,0x00,0xB8,0x00,0xFD,0x00,0xC3,0xFF,0x75,0x00,0xCE,0xFE, + 0x32,0xFF,0x44,0x01,0x34,0x00,0x5A,0xFF,0x3D,0xFF,0x07,0xFF, + 0x0B,0x01,0x83,0x00,0x7B,0xFF,0xF9,0x00,0x9B,0x00,0xE2,0xFE, + 0xA3,0xFF,0xCA,0x01,0xF3,0xFF,0x0D,0xFF,0x5A,0x01,0x31,0xFF, + 0x3C,0xFF,0x70,0x00,0xDF,0xFF,0x19,0x01,0xDB,0xFF,0x5E,0xFE, + 0x92,0xFE,0xAC,0x01,0x61,0x00,0x68,0xFF,0x36,0x00,0xC3,0xFE, + 0x3A,0xFF,0xAC,0x00,0xB6,0x01,0xC0,0xFF,0xAD,0xFF,0x3C,0xFF, + 0xD9,0xFD,0xC4,0x03,0xEA,0x03,0xD0,0xFF,0x19,0x01,0x5B,0x02, + 0x24,0x02,0xC8,0xFE,0xE6,0xFE,0x94,0xFE,0xCD,0xFC,0xD7,0xFC, + 0xE7,0xFC,0xDE,0xFC,0x0D,0xFC,0x9D,0xFD,0x68,0xFF,0x18,0x00, + 0x68,0x00,0x86,0x01,0x57,0x02,0x17,0x02,0xB9,0x02,0x5E,0x03, + 0xF2,0x02,0x3D,0x02,0x34,0x02,0x41,0x02,0x85,0x01,0xB6,0x00, + 0x0F,0x00,0x75,0xFF,0xC6,0xFE,0xA9,0xFE,0xDA,0xFE,0xB6,0xFE, + 0xDD,0xFE,0xC8,0xFE,0xD6,0xFE,0xA2,0xFE,0xAB,0xFE,0xD5,0xFE, + 0x35,0xFF,0xEA,0xFF,0xC2,0xFF,0x0B,0x00,0x4B,0x00,0xF4,0xFF, + 0xEE,0xFF,0xFD,0xFF,0x26,0x00,0xFE,0xFF,0xC3,0xFF,0x0B,0x00, + 0x77,0x00,0x5D,0x00,0xF1,0xFF,0x08,0x00,0x62,0x00,0x8E,0x00, + 0x1B,0x00,0x39,0x00,0x53,0x00,0x14,0x00,0x3A,0x00,0xF5,0xFF, + 0x1B,0x00,0x0D,0x00,0xD6,0xFF,0x1E,0x00,0x5D,0x00,0x5E,0x00, + 0x3B,0x00,0x42,0x00,0x53,0x00,0x41,0x00,0x4D,0x00,0x7E,0x00, + 0x2F,0x00,0xF4,0xFF,0x18,0x00,0xF8,0xFF,0xC8,0xFF,0xA3,0xFF, + 0xBB,0xFF,0x94,0xFF,0x94,0xFF,0xDB,0xFF,0x7C,0xFF,0x8F,0xFF, + 0xAC,0xFF,0xD7,0xFF,0xE1,0xFF,0xD3,0xFF,0xE1,0xFF,0xB9,0xFF, + 0x37,0x00,0x22,0x00,0xF9,0xFF,0x37,0x00,0x58,0x00,0x1C,0x00, + 0xE9,0xFF,0x30,0x00,0x38,0x00,0x08,0x00,0xFC,0xFF,0x30,0x00, + 0x1E,0x00,0x1D,0x00,0x0D,0x00,0x03,0x00,0x1C,0x00,0xF4,0xFF, + 0xEC,0xFF,0x17,0x00,0x2D,0x00,0xC3,0xFF,0xD5,0xFF,0x2D,0x00, + 0xE1,0xFF,0xE0,0xFF,0x05,0x00,0xF8,0xFF,0xB9,0xFF,0xD2,0xFF, + 0x2C,0x00,0xE8,0xFF,0xEF,0xFF,0x1C,0x00,0xEC,0xFF,0xD4,0xFF, + 0x24,0x00,0x19,0x00,0xB7,0xFF,0xD1,0xFF,0x20,0x00,0x28,0x00, + 0xD9,0xFF,0xFF,0xFF,0xF7,0xFF,0xDD,0xFF,0x3C,0x00,0x1E,0x00, + 0xEB,0xFF,0x37,0x00,0x57,0x00,0x04,0x00,0x10,0x00,0x23,0x00, + 0xE4,0xFF,0x18,0x00,0x2A,0x00,0x04,0x00,0xDE,0xFF,0xF3,0xFF, + 0x12,0x00,0xE9,0xFF,0x04,0x00,0x27,0x00,0x16,0x00,0xCD,0xFF, + 0x0D,0x00,0x29,0x00,0xF3,0xFF,0xEB,0xFF,0xE0,0xFF,0x08,0x00, + 0x01,0x00,0xF7,0xFF,0xDD,0xFF,0xD2,0xFF,0xE3,0xFF,0xFC,0xFF, + 0xFA,0xFF,0xDD,0xFF,0xF5,0xFF,0x2D,0x00,0x36,0x00,0xFD,0xFF, + 0xEE,0xFF,0x04,0x00,0x1E,0x00,0xF5,0xFF,0xDA,0xFF,0xF0,0xFF, + 0x05,0x00,0x1A,0x00,0x1B,0x00,0x15,0x00,0xDF,0xFF,0x0C,0x00, + 0xFC,0xFF,0xE6,0xFF,0x06,0x00,0x0D,0x00,0x18,0x00,0x0B,0x00, + 0x20,0x00,0xD6,0xFF,0xEF,0xFF,0x0C,0x00,0xF4,0xFF,0xFD,0xFF, + 0x20,0x00,0x25,0x00,0xDF,0xFF,0xF4,0xFF,0xF4,0xFF,0xF3,0xFF, + 0xDA,0xFF,0xFA,0xFF,0x21,0x00,0x09,0x00,0x03,0x00,0xF0,0xFF, + 0xF7,0xFF,0xD7,0xFF,0xEB,0xFF,0x06,0x00,0x0C,0x00,0x1D,0x00, + 0x11,0x00,0x1C,0x00,0x04,0x00,0x10,0x00,0x05,0x00,0xE8,0xFF, + 0xFA,0xFF,0xFE,0xFF,0x16,0x00,0xFC,0xFF,0xFA,0xFF,0xF7,0xFF, + 0xF1,0xFF,0x0E,0x00,0x1C,0x00,0x21,0x00,0x1A,0x00,0x1C,0x00, + 0xF5,0xFF,0xDC,0xFF,0xF3,0xFF,0xFB,0xFF,0xF2,0xFF,0x11,0x00, + 0x23,0x00,0x0F,0x00,0xF7,0xFF,0xE7,0xFF,0xDB,0xFF,0xCC,0xFF, + 0xD4,0xFF,0xDE,0xFF,0xF5,0xFF,0xF0,0xFF,0xF7,0xFF,0x0C,0x00, + 0xFB,0xFF,0xEC,0xFF,0xF5,0xFF,0xF5,0xFF,0xE6,0xFF,0x01,0x00, + 0x1B,0x00,0x1A,0x00,0x07,0x00,0x1C,0x00,0x24,0x00,0x09,0x00, + 0x0B,0x00,0x13,0x00,0x14,0x00,0x0D,0x00,0x0F,0x00,0x05,0x00, + 0xFA,0xFF,0x01,0x00,0xF7,0xFF,0xEE,0xFF,0xF9,0xFF,0xF7,0xFF, + 0xFB,0xFF,0x05,0x00,0x18,0x00,0x1E,0x00,0x0A,0x00,0x1C,0x00, + 0x1F,0x00,0x08,0x00,0x00,0x00,0xFE,0xFF,0x07,0x00,0x1D,0x00, + 0x35,0x00,0x2F,0x00,0x38,0x00,0x80,0x00,0xEF,0x00,0x36,0x01, + 0x1E,0x01,0xF9,0x00,0xE4,0x00,0x7A,0x00,0xE9,0xFF,0xA6,0xFF, + 0x48,0xFF,0x96,0xFE,0x22,0xFE,0xEE,0xFD,0x84,0xFD,0x3A,0xFD, + 0x7A,0xFD,0xB5,0xFD,0xC3,0xFD,0x34,0xFE,0xD7,0xFE,0x4A,0xFF, + 0x01,0x00,0xF8,0x00,0x9F,0x01,0x13,0x02,0x92,0x02,0xCE,0x02, + 0xDB,0x02,0xF9,0x02,0xD4,0x02,0x6D,0x02,0x07,0x02,0x86,0x01, + 0xEB,0x00,0x70,0x00,0x0A,0x00,0x99,0xFF,0x4B,0xFF,0x10,0xFF, + 0xB5,0xFE,0x75,0xFE,0x66,0xFE,0x5D,0xFE,0x8A,0xFE,0xE0,0xFE, + 0x15,0xFF,0x55,0xFF,0xC3,0xFF,0xFE,0xFF,0xFB,0xFF,0x2A,0x00, + 0x50,0x00,0x27,0x00,0x25,0x00,0x39,0x00,0xFE,0xFF,0x06,0x00, + 0x69,0x01,0xDE,0x02,0x7A,0x02,0x0F,0x02,0x78,0x02,0x95,0x01, + 0xEC,0xFF,0xCE,0xFF,0xBA,0xFF,0xF4,0xFD,0x00,0xFD,0xED,0xFC, + 0xA4,0xFB,0xA3,0xFA,0x4C,0xFB,0xDB,0xFB,0x9C,0xFB,0x47,0xFC, + 0x40,0xFD,0x97,0xFD,0xC7,0xFE,0xAB,0x00,0xF5,0x01,0xF3,0x02, + 0x1A,0x04,0xB1,0x04,0xD1,0x04,0x54,0x05,0x87,0x05,0x2C,0x05, + 0xEB,0x04,0x66,0x04,0x87,0x03,0x30,0x03,0xCE,0x02,0xC2,0x01, + 0x10,0x01,0x97,0x00,0x54,0xFF,0xEF,0xFD,0x60,0xFD,0x97,0xFC, + 0xC4,0xFB,0xF6,0xFB,0x24,0xFC,0xEA,0xFB,0x0C,0xFC,0x9A,0xFC, + 0xC6,0xFC,0x11,0xFD,0xF1,0xFD,0x6E,0xFE,0xEA,0xFE,0xC2,0xFF, + 0x6A,0x00,0xDC,0x00,0x6D,0x01,0xE3,0x01,0xF7,0x01,0x23,0x02, + 0x2A,0x02,0xAD,0x01,0x47,0x01,0x25,0x01,0xCE,0x00,0x5F,0x00, + 0x40,0x00,0x10,0x00,0xEF,0xFF,0x61,0x01,0x58,0x03,0x17,0x03, + 0x6C,0x02,0xEE,0x02,0x2F,0x02,0x36,0x00,0xFF,0xFF,0x60,0x00, + 0x83,0xFE,0x27,0xFD,0x14,0xFD,0x88,0xFB,0xD0,0xF9,0x3B,0xFA, + 0x0C,0xFB,0xA7,0xFA,0x39,0xFB,0x77,0xFC,0xB4,0xFC,0x99,0xFD, + 0xB9,0xFF,0x5C,0x01,0x6B,0x02,0xE1,0x03,0xD1,0x04,0xDA,0x04, + 0x34,0x05,0x9B,0x05,0x48,0x05,0x3B,0x05,0xB3,0x05,0x0B,0x05, + 0xD6,0x03,0x3B,0x03,0x43,0x02,0xE6,0x00,0x27,0x00,0xAE,0xFF, + 0x47,0xFE,0x01,0xFD,0x83,0xFC,0xB8,0xFB,0x53,0xFB,0xC5,0xFB, + 0x39,0xFC,0x6E,0xFC,0xE5,0xFC,0x4C,0xFD,0x4E,0xFD,0xCA,0xFD, + 0xA0,0xFE,0x35,0xFF,0xFF,0xFF,0xD4,0x00,0xF1,0x00,0x01,0x01, + 0x6B,0x01,0x98,0x01,0x71,0x01,0x94,0x01,0xAD,0x01,0x08,0x01, + 0xB3,0x00,0x9B,0x00,0x31,0x00,0x0D,0x00,0x01,0x01,0xE4,0x02, + 0xA7,0x03,0x18,0x03,0xD4,0x02,0x84,0x02,0x36,0x01,0x11,0x00, + 0x36,0x00,0xB7,0xFF,0x03,0xFE,0x0B,0xFD,0x14,0xFC,0x53,0xFA, + 0x79,0xF9,0x33,0xFA,0xCF,0xFA,0xF0,0xFA,0xC6,0xFB,0x8F,0xFC, + 0xE4,0xFC,0x24,0xFE,0x3A,0x00,0xE9,0x01,0x56,0x03,0xBF,0x04, + 0x6C,0x05,0x5C,0x05,0x6B,0x05,0x84,0x05,0x47,0x05,0x90,0x05, + 0xF1,0x05,0x17,0x05,0xF4,0x03,0x13,0x03,0xB9,0x01,0x4A,0x00, + 0xB8,0xFF,0x3B,0xFF,0xD7,0xFD,0xDB,0xFC,0x44,0xFC,0x36,0xFB, + 0xC1,0xFA,0x4D,0xFB,0xE6,0xFB,0x56,0xFC,0x11,0xFD,0x88,0xFD, + 0x7B,0xFD,0xD9,0xFD,0xA3,0xFE,0x4D,0xFF,0x44,0x00,0x3E,0x01, + 0x82,0x01,0x96,0x01,0xBC,0x01,0xA6,0x01,0x6C,0x01,0x8D,0x01, + 0x9B,0x01,0x40,0x01,0x13,0x01,0xB3,0x00,0x12,0x00,0xEC,0xFF, + 0xA2,0x00,0x52,0x02,0x72,0x03,0x24,0x03,0xBD,0x02,0x5B,0x02, + 0x20,0x01,0xAF,0xFF,0xB3,0xFF,0xC0,0xFF,0x55,0xFE,0x5C,0xFD, + 0x92,0xFC,0xBC,0xFA,0x60,0xF9,0xD8,0xF9,0xC2,0xFA,0xFA,0xFA, + 0xEF,0xFB,0x25,0xFD,0x57,0xFD,0x02,0xFE,0xD6,0xFF,0x71,0x01, + 0xC2,0x02,0x7D,0x04,0xC2,0x05,0xCC,0x05,0x8F,0x05,0x84,0x05, + 0xF9,0x04,0xE2,0x04,0x88,0x05,0x36,0x05,0x4E,0x04,0xA3,0x03, + 0x49,0x02,0x57,0x00,0x4D,0xFF,0xF1,0xFE,0xD2,0xFD,0x05,0xFD, + 0xEC,0xFC,0xFA,0xFB,0xEC,0xFA,0x03,0xFB,0x67,0xFB,0xA1,0xFB, + 0x90,0xFC,0xAA,0xFD,0xE7,0xFD,0x0E,0xFE,0xA6,0xFE,0xEF,0xFE, + 0x60,0xFF,0x89,0x00,0x72,0x01,0xDC,0x01,0x3A,0x02,0x55,0x02, + 0xE2,0x01,0x81,0x01,0x72,0x01,0x32,0x01,0x2A,0x01,0x2D,0x01, + 0xB7,0x00,0x3E,0x00,0x27,0x00,0xE1,0x00,0x3F,0x02,0xDB,0x02, + 0x82,0x02,0x71,0x02,0x02,0x02,0x8D,0x00,0x55,0xFF,0xAB,0xFF, + 0xF2,0xFE,0xAF,0xFD,0x3D,0xFD,0x5A,0xFC,0xB2,0xFB,0xE8,0xF9, + 0x08,0xFA,0xBE,0xFA,0xA2,0xFA,0x64,0xFC,0x68,0xFD,0x83,0xFD, + 0x4B,0xFE,0x9C,0xFF,0xE9,0x00,0xA3,0x02,0x2B,0x05,0x4E,0x07, + 0xBB,0x07,0x7D,0x07,0xBF,0x06,0xCF,0x05,0xD7,0x05,0xFE,0x05, + 0xE7,0x05,0x05,0x05,0xD8,0x02,0x09,0x00,0x0A,0xFD,0x4E,0xFB, + 0xE0,0xFA,0xC7,0xFA,0x1A,0xFB,0xAC,0xFA,0xD4,0xF9,0xA5,0xF9, + 0x6A,0xFA,0xB4,0xFC,0xD3,0xFE,0x9E,0x00,0xE4,0x01,0x8E,0x01, + 0x36,0x01,0x25,0x01,0xE9,0x01,0xC1,0x02,0x93,0x02,0xF5,0x01, + 0x6A,0x00,0xFF,0xFE,0xA9,0xFE,0x76,0xFE,0x92,0xFE,0xE2,0xFE, + 0xFE,0xFE,0x80,0xFE,0x6F,0xFE,0x7E,0xFF,0x92,0x01,0xB3,0x07, + 0xFB,0x0B,0x00,0x0A,0x2F,0x07,0x64,0x02,0x48,0xFE,0x48,0xFD, + 0x45,0xFC,0xBA,0xFB,0x0A,0xF8,0x4F,0xF4,0x16,0xF3,0xD5,0xF2, + 0xB6,0xF6,0x28,0xFA,0xD5,0xFC,0x12,0xFF,0x7B,0xFF,0x35,0x02, + 0xE9,0x05,0xF1,0x09,0xF5,0x0C,0x91,0x0C,0x1F,0x0B,0x47,0x08, + 0xD9,0x05,0x62,0x05,0xBD,0x03,0x27,0x01,0xAD,0xFD,0xD0,0xFA, + 0x03,0xFA,0x7C,0xF9,0x27,0xFB,0xA2,0xFC,0x25,0xFC,0xCC,0xFC, + 0x80,0xFD,0xFC,0xFE,0x5B,0x01,0xEE,0x02,0x6F,0x03,0x6D,0x02, + 0xB6,0x01,0x23,0x01,0xAD,0x00,0x58,0x01,0x91,0x00,0xD4,0xFE, + 0xA9,0xFD,0x85,0xFC,0x75,0xFC,0x12,0xFD,0xF2,0xFD,0x3F,0xFE, + 0xED,0xFD,0x73,0xFE,0x25,0xFF,0x9F,0x00,0x1F,0x02,0x56,0x02, + 0xE8,0x01,0xFE,0x00,0x3A,0x01,0xA5,0x01,0x80,0x01,0xE7,0x01, + 0xF9,0x00,0xFF,0x03,0xD5,0x09,0xA0,0x08,0x7F,0x05,0x0E,0x02, + 0xC4,0xFC,0xCF,0xFA,0xDD,0xF9,0xBE,0xF9,0xC5,0xF7,0x32,0xF4, + 0x4E,0xF4,0x4A,0xF4,0x9A,0xF7,0xE6,0xFB,0xBD,0xFD,0x79,0x00, + 0x39,0x01,0x87,0x03,0xEB,0x06,0xDD,0x09,0x66,0x0C,0x4A,0x0B, + 0xBF,0x09,0xDB,0x07,0x32,0x05,0x67,0x04,0xD0,0x02,0xB7,0xFF, + 0x9C,0xFC,0x2B,0xFB,0x35,0xFB,0x8D,0xFA,0x3C,0xFC,0x68,0xFD, + 0x6F,0xFC,0xB9,0xFD,0x12,0xFF,0x69,0x00,0x09,0x02,0xC5,0x02, + 0xAD,0x02,0x35,0x01,0x06,0x01,0xCD,0x00,0xF8,0xFF,0x4D,0x00, + 0x1B,0xFF,0xA9,0xFD,0x38,0xFD,0xEB,0xFC,0x43,0xFD,0x7D,0xFD, + 0x06,0xFE,0x21,0xFE,0x04,0xFE,0x45,0xFF,0x42,0x00,0x55,0x01, + 0x25,0x02,0xA3,0x01,0x4B,0x01,0xDD,0x00,0xA1,0x01,0xD2,0x01, + 0x06,0x01,0x30,0x01,0xD3,0x00,0x48,0x06,0x6D,0x0B,0x50,0x08, + 0x46,0x05,0x34,0x00,0x86,0xFB,0xE4,0xFA,0xFD,0xF9,0xFA,0xF9, + 0x23,0xF6,0xE5,0xF2,0x54,0xF3,0xC3,0xF3,0x06,0xF9,0x5C,0xFC, + 0x4B,0xFE,0x76,0x00,0xF4,0x00,0xCF,0x04,0x44,0x08,0xBA,0x0B, + 0x55,0x0D,0x33,0x0B,0xD4,0x09,0x7D,0x07,0x97,0x05,0x0A,0x05, + 0x44,0x02,0xB7,0xFE,0xD6,0xFB,0xD3,0xFA,0x58,0xFA,0x5F,0xFA, + 0x67,0xFC,0x08,0xFC,0xCC,0xFB,0x84,0xFD,0xE8,0xFE,0x08,0x01, + 0x85,0x02,0x36,0x03,0x99,0x02,0x88,0x01,0xBF,0x01,0x1A,0x01, + 0xE5,0x00,0x7A,0x00,0x9D,0xFE,0x95,0xFD,0xDD,0xFC,0xD4,0xFC, + 0x29,0xFD,0x53,0xFD,0xA1,0xFD,0x53,0xFD,0xE3,0xFD,0x60,0xFF, + 0x89,0x00,0xDF,0x01,0x08,0x02,0xB1,0x01,0x5A,0x01,0x65,0x01, + 0x4F,0x02,0x82,0x01,0x38,0x01,0xCE,0x00,0x33,0x04,0x09,0x0B, + 0xA2,0x09,0x24,0x06,0x3E,0x02,0x0A,0xFC,0xE4,0xFA,0x30,0xFA, + 0xEF,0xF9,0xA5,0xF7,0x23,0xF3,0x19,0xF3,0xA5,0xF2,0xA8,0xF6, + 0x6B,0xFB,0x17,0xFD,0x18,0x00,0x87,0x00,0x58,0x03,0x35,0x07, + 0xB2,0x0A,0xBE,0x0D,0x83,0x0C,0x23,0x0B,0x1A,0x09,0x61,0x06, + 0xD5,0x05,0xAF,0x03,0x2E,0x00,0x70,0xFD,0x49,0xFB,0xF3,0xF9, + 0x76,0xF9,0x10,0xFB,0x4D,0xFB,0xEE,0xFA,0x7A,0xFC,0xA8,0xFD, + 0x9D,0xFF,0xD3,0x01,0x32,0x03,0x5B,0x03,0x79,0x02,0x63,0x02, + 0xBE,0x01,0x6B,0x01,0x55,0x01,0xCF,0xFF,0x96,0xFE,0x67,0xFD, + 0xB9,0xFC,0xBA,0xFC,0xD8,0xFC,0x18,0xFD,0xF4,0xFC,0x60,0xFD, + 0x8B,0xFE,0x8C,0xFF,0xE2,0x00,0xC2,0x01,0xF1,0x01,0xEF,0x01, + 0xA0,0x01,0x02,0x02,0x9D,0x01,0x6B,0x01,0x0E,0x01,0x98,0x03, + 0xB0,0x0A,0xA9,0x0A,0xD6,0x06,0xB6,0x03,0x3D,0xFD,0xC6,0xFA, + 0x57,0xFA,0xBC,0xF9,0x33,0xF8,0xA8,0xF3,0xE3,0xF2,0x00,0xF2, + 0x9D,0xF4,0x0D,0xFA,0x1D,0xFC,0x7C,0xFF,0x67,0x00,0x82,0x02, + 0x22,0x06,0x9A,0x09,0xAC,0x0D,0x7C,0x0D,0x8D,0x0C,0xBA,0x0A, + 0x74,0x07,0x72,0x06,0x77,0x04,0xA7,0x01,0xA5,0xFF,0x70,0xFC, + 0xBA,0xF9,0xA3,0xF8,0x2C,0xF9,0xE7,0xF9,0x47,0xFA,0x9D,0xFB, + 0x88,0xFC,0xF8,0xFD,0x8D,0x00,0x86,0x02,0xE0,0x03,0xC6,0x03, + 0x40,0x03,0x9E,0x02,0xD9,0x01,0xBF,0x01,0xFE,0x00,0x05,0x00, + 0x8C,0xFE,0xF7,0xFC,0x46,0xFC,0x0A,0xFC,0x3A,0xFC,0xB4,0xFC, + 0x41,0xFD,0xFC,0xFD,0xBF,0xFE,0xDE,0xFF,0x1B,0x01,0xF2,0x01, + 0x35,0x02,0x18,0x02,0x1A,0x02,0x9B,0x01,0x21,0x01,0x63,0x01, + 0xA6,0x06,0x89,0x0C,0x28,0x0A,0xB4,0x06,0x3C,0x02,0xE8,0xFB, + 0x9E,0xFA,0x2C,0xFA,0xAD,0xF9,0x86,0xF6,0xCF,0xF2,0x1B,0xF2, + 0x09,0xF1,0x99,0xF5,0xE5,0xF9,0xA0,0xFC,0xC8,0xFF,0x6E,0x00, + 0x71,0x03,0x7E,0x06,0x15,0x0B,0x57,0x0E,0xEE,0x0D,0xA5,0x0D, + 0xA3,0x0A,0xEA,0x07,0xB4,0x06,0x30,0x04,0x5D,0x02,0xD3,0xFF, + 0xE6,0xFB,0x0C,0xF9,0xB9,0xF7,0x11,0xF8,0xBD,0xF8,0xDD,0xF9, + 0x37,0xFB,0xD8,0xFB,0xD6,0xFD,0x3D,0x00,0x80,0x02,0x31,0x04, + 0x31,0x04,0x02,0x04,0x02,0x03,0x64,0x02,0x20,0x02,0x87,0x01, + 0xC0,0x00,0xC7,0xFE,0x37,0xFD,0x19,0xFC,0x57,0xFB,0xB3,0xFB, + 0xA3,0xFC,0x72,0xFD,0xBD,0xFD,0x73,0xFE,0x81,0xFF,0x70,0x00, + 0x93,0x01,0x45,0x02,0x7A,0x02,0x34,0x02,0x5F,0x01,0x7D,0x00, + 0x24,0x05,0xC2,0x0C,0x54,0x0C,0xFB,0x08,0x06,0x05,0x75,0xFD, + 0x28,0xFA,0x05,0xFA,0xE3,0xF9,0xA7,0xF7,0x8F,0xF3,0x04,0xF2, + 0x97,0xEF,0x8E,0xF2,0xFB,0xF7,0x40,0xFB,0x6B,0xFF,0x59,0x00, + 0x46,0x02,0xAD,0x04,0x10,0x09,0xE2,0x0D,0xC4,0x0E,0x88,0x0F, + 0xE8,0x0C,0x2D,0x09,0x60,0x07,0x28,0x05,0xC4,0x03,0xD8,0x01, + 0x53,0xFE,0x26,0xFA,0x41,0xF7,0xD5,0xF6,0x3C,0xF7,0xB7,0xF8, + 0x8A,0xFA,0x32,0xFB,0x49,0xFC,0x32,0xFE,0xC6,0x00,0x2C,0x03, + 0x62,0x04,0x12,0x05,0x10,0x04,0x04,0x03,0x7F,0x02,0x1E,0x02, + 0xFA,0x01,0x85,0x00,0xF0,0xFE,0xDA,0xFC,0x10,0xFB,0xD2,0xFA, + 0xAD,0xFB,0x0D,0xFD,0xAF,0xFD,0x3C,0xFE,0x77,0xFE,0x09,0xFF, + 0x52,0x00,0x8C,0x01,0xA9,0x02,0xD7,0x02,0x11,0x02,0x4E,0x00, + 0xC2,0x04,0xB3,0x0C,0xCF,0x0C,0xDB,0x0A,0xCE,0x06,0x7E,0xFE, + 0xED,0xF9,0xEB,0xF8,0x44,0xF9,0x80,0xF7,0xC5,0xF4,0xEC,0xF2, + 0x55,0xEF,0xA2,0xF1,0x3E,0xF6,0x3C,0xFA,0x03,0xFF,0x9C,0x00, + 0x39,0x02,0x6C,0x03,0xBC,0x07,0x72,0x0C,0x72,0x0E,0xA0,0x10, + 0x58,0x0E,0x92,0x0A,0xD2,0x07,0x78,0x05,0x85,0x04,0x1A,0x03, + 0x9B,0x00,0xF0,0xFB,0xE4,0xF7,0x3D,0xF6,0xB7,0xF5,0x84,0xF7, + 0xB7,0xF9,0xE9,0xFA,0x9C,0xFB,0x9D,0xFC,0xEA,0xFE,0x22,0x01, + 0x84,0x03,0x80,0x05,0x36,0x05,0x4F,0x04,0x3B,0x03,0xB0,0x02, + 0x53,0x02,0x61,0x01,0x56,0x00,0xED,0xFD,0xC4,0xFB,0xA9,0xFA, + 0x0B,0xFB,0x71,0xFC,0x63,0xFD,0x4D,0xFE,0x32,0xFE,0x5E,0xFE, + 0x16,0xFF,0x77,0x00,0x32,0x02,0x28,0x03,0x9F,0x02,0xAA,0x00, + 0x0D,0x06,0xCD,0x0C,0x25,0x0C,0x9C,0x0B,0xEE,0x06,0x67,0xFE, + 0xB2,0xF9,0x64,0xF8,0xDD,0xF8,0xEB,0xF6,0xE0,0xF5,0xB6,0xF3, + 0xED,0xEF,0x55,0xF2,0x69,0xF5,0xAB,0xF9,0x2A,0xFE,0x7A,0x00, + 0x2D,0x02,0x00,0x03,0xAF,0x07,0x49,0x0B,0x18,0x0E,0xC8,0x10, + 0x99,0x0E,0x86,0x0B,0x30,0x08,0x2A,0x06,0x27,0x05,0xB2,0x03, + 0xA6,0x01,0xB0,0xFC,0x90,0xF8,0x1E,0xF6,0x1B,0xF5,0xC6,0xF6, + 0xB6,0xF8,0x4E,0xFA,0xD1,0xFA,0xCF,0xFB,0xFF,0xFD,0xF0,0xFF, + 0x21,0x03,0x76,0x05,0xB8,0x05,0xFA,0x04,0xBA,0x03,0x30,0x03, + 0x49,0x02,0xEE,0x01,0x1F,0x01,0xCB,0xFE,0xBA,0xFC,0x0C,0xFB, + 0x40,0xFB,0x1C,0xFC,0x28,0xFD,0x03,0xFE,0xC6,0xFD,0x11,0xFE, + 0x62,0xFE,0xF6,0xFF,0xA9,0x01,0x83,0x02,0x60,0x01,0x52,0x02, + 0x62,0x0A,0x99,0x0D,0x75,0x0C,0x0F,0x0B,0x32,0x03,0x35,0xFC, + 0x78,0xF9,0x57,0xF9,0xE4,0xF8,0xE7,0xF6,0x43,0xF6,0xE2,0xF1, + 0x63,0xF0,0x3C,0xF3,0x8F,0xF5,0x6B,0xFA,0xCC,0xFD,0x78,0x00, + 0x6F,0x01,0x32,0x04,0x4F,0x09,0xBE,0x0B,0x91,0x0F,0x37,0x10, + 0xA6,0x0D,0xCA,0x0A,0x1B,0x08,0xF5,0x06,0x54,0x05,0x80,0x04, + 0xD3,0x00,0xA4,0xFB,0x92,0xF8,0xD1,0xF5,0x9F,0xF5,0x12,0xF7, + 0xC2,0xF8,0x81,0xF9,0xE7,0xF9,0xBD,0xFB,0x58,0xFD,0x33,0x00, + 0x6B,0x03,0xD4,0x04,0x36,0x05,0x86,0x04,0xCC,0x03,0x11,0x03, + 0xE0,0x02,0xBC,0x02,0x20,0x01,0x6B,0xFF,0x2E,0xFD,0xB2,0xFB, + 0xEA,0xFB,0x9E,0xFC,0x9C,0xFD,0xA0,0xFD,0xAD,0xFD,0x62,0xFD, + 0xBD,0xFD,0x75,0xFF,0xC4,0x00,0xA0,0x01,0xD3,0x00,0x85,0x05, + 0xF9,0x0C,0x40,0x0D,0x32,0x0C,0x2F,0x08,0x7F,0x00,0xF5,0xFB, + 0x78,0xFA,0xB2,0xFA,0x2D,0xF9,0xD2,0xF7,0x45,0xF5,0xA8,0xF0, + 0x37,0xF1,0xE5,0xF2,0x00,0xF6,0xA9,0xFA,0x01,0xFE,0x4E,0x00, + 0x3F,0x01,0xB5,0x05,0x31,0x09,0x27,0x0C,0x9B,0x0F,0xEE,0x0E, + 0x43,0x0D,0xBE,0x0A,0x82,0x09,0x99,0x07,0x34,0x05,0xC7,0x04, + 0x7B,0x00,0x5C,0xFB,0xDF,0xF8,0x80,0xF6,0x69,0xF5,0x0D,0xF7, + 0x7A,0xF8,0x3D,0xF8,0x51,0xF9,0x5A,0xFB,0xC4,0xFC,0x92,0xFF, + 0x19,0x03,0x29,0x04,0x8C,0x04,0xB0,0x04,0xCF,0x03,0x4C,0x03, + 0xE3,0x03,0xAA,0x03,0xB6,0x01,0x59,0x00,0x41,0xFE,0x68,0xFC, + 0x77,0xFC,0x10,0xFD,0x28,0xFD,0xE2,0xFC,0x46,0xFD,0xBA,0xFC, + 0x59,0xFD,0xF6,0xFE,0x19,0x00,0xA4,0x00,0x83,0x00,0x37,0x07, + 0x8F,0x0D,0xB5,0x0C,0xCA,0x0B,0x0D,0x07,0xFE,0xFF,0x75,0xFC, + 0xE2,0xFB,0xFE,0xFB,0xED,0xF9,0x68,0xF8,0x73,0xF4,0x03,0xF0, + 0xE5,0xF0,0x8F,0xF2,0x9C,0xF6,0x11,0xFB,0xEC,0xFD,0x2B,0xFF, + 0x85,0x00,0x9E,0x05,0x86,0x08,0x77,0x0C,0xB3,0x0F,0xE8,0x0E, + 0x5F,0x0D,0x37,0x0B,0xA6,0x0A,0x87,0x07,0xCF,0x05,0xC8,0x04, + 0x91,0xFF,0xCE,0xFB,0x05,0xF9,0x55,0xF7,0x56,0xF6,0x18,0xF7, + 0x1C,0xF8,0x17,0xF7,0xCD,0xF8,0x6F,0xFA,0x2B,0xFC,0xAB,0xFF, + 0x74,0x02,0xD6,0x03,0xC1,0x03,0x81,0x04,0x09,0x04,0xCA,0x03, + 0xE1,0x04,0xF0,0x03,0xA7,0x02,0xDF,0x00,0x02,0xFF,0x3A,0xFD, + 0xA3,0xFC,0x41,0xFD,0x81,0xFC,0xA6,0xFC,0x96,0xFC,0x21,0xFC, + 0xC9,0xFC,0x48,0xFE,0xBF,0xFF,0x1E,0x00,0x5E,0x00,0x0A,0x03, + 0x9E,0x0A,0xFA,0x0D,0x88,0x0C,0xC7,0x0A,0xC8,0x03,0x6B,0xFE, + 0xA0,0xFC,0xD6,0xFC,0x75,0xFC,0x6C,0xF9,0xF6,0xF6,0x14,0xF1, + 0xFF,0xEE,0x1F,0xF1,0x48,0xF4,0x39,0xF9,0x96,0xFB,0x01,0xFE, + 0x7B,0xFE,0xE0,0x01,0xB7,0x06,0x4A,0x0A,0x19,0x0F,0x95,0x0F, + 0xF1,0x0E,0x98,0x0C,0x44,0x0B,0x1A,0x0A,0x5E,0x07,0x12,0x06, + 0x66,0x01,0x26,0xFE,0x1B,0xFB,0x1A,0xF8,0x7D,0xF7,0x49,0xF6, + 0x91,0xF6,0x40,0xF6,0x47,0xF7,0xE0,0xF8,0x97,0xFA,0x36,0xFE, + 0x85,0x00,0xBC,0x02,0x9A,0x03,0x24,0x04,0xDB,0x04,0xCA,0x04, + 0x8D,0x05,0xD6,0x04,0xE2,0x03,0x40,0x02,0x42,0x00,0xFD,0xFE, + 0x5B,0xFD,0x13,0xFD,0x2E,0xFC,0xFF,0xFB,0xF3,0xFB,0x67,0xFB, + 0x56,0xFC,0xF4,0xFC,0xA3,0xFE,0x4B,0xFF,0x35,0x00,0xA9,0x00, + 0x78,0x03,0x4C,0x0C,0x56,0x0E,0x14,0x0D,0xA8,0x0A,0x21,0x03, + 0x5E,0xFE,0x03,0xFD,0x26,0xFE,0xF3,0xFB,0xD4,0xF8,0x89,0xF5, + 0x02,0xEF,0xA8,0xEE,0x7E,0xF1,0x34,0xF5,0xAD,0xF8,0x6D,0xFB, + 0x7F,0xFD,0x89,0xFD,0xB3,0x02,0xD0,0x07,0xFD,0x0B,0xD1,0x0F, + 0x62,0x10,0x17,0x0F,0x1A,0x0C,0x27,0x0C,0xA7,0x0A,0x0C,0x08, + 0x8D,0x05,0x71,0x00,0x6F,0xFC,0xC6,0xF9,0x82,0xF8,0xDC,0xF6, + 0x6E,0xF6,0xEE,0xF5,0x02,0xF5,0x7C,0xF6,0xB9,0xF8,0xCB,0xFB, + 0xA2,0xFE,0x9A,0x01,0xC1,0x02,0x5C,0x03,0xDD,0x04,0x62,0x05, + 0x52,0x06,0x3C,0x06,0x5D,0x05,0x5B,0x03,0x7B,0x01,0x40,0x00, + 0x83,0xFE,0xBF,0xFD,0xAC,0xFC,0x9D,0xFB,0xD9,0xFA,0x0C,0xFB, + 0x63,0xFB,0x15,0xFC,0x8B,0xFD,0x78,0xFE,0x98,0xFF,0x2D,0x00, + 0xD8,0x00,0x6E,0x02,0xBA,0x09,0xB0,0x0F,0xF0,0x0D,0x0C,0x0C, + 0xDF,0x05,0x42,0xFF,0x57,0xFD,0xAE,0xFD,0x20,0xFD,0xEA,0xF8, + 0xE7,0xF5,0x52,0xF0,0x66,0xED,0x82,0xF0,0xBB,0xF3,0xBA,0xF7, + 0x24,0xFA,0xA5,0xFC,0x47,0xFD,0xC7,0x00,0x60,0x07,0x5E,0x0B, + 0xA7,0x0F,0xD4,0x10,0xE5,0x0F,0x4A,0x0D,0x64,0x0C,0x1A,0x0C, + 0xFF,0x08,0x6D,0x06,0x81,0x01,0x7E,0xFC,0x47,0xF9,0x8F,0xF8, + 0x4B,0xF7,0xFD,0xF5,0x24,0xF6,0x55,0xF4,0x4F,0xF5,0xEB,0xF7, + 0x6B,0xFB,0x75,0xFE,0x13,0x01,0x37,0x03,0x03,0x03,0xEE,0x04, + 0xF3,0x05,0xA8,0x06,0xE4,0x06,0xC0,0x05,0xF3,0x03,0x58,0x01, + 0x8F,0x00,0xCE,0xFE,0xB0,0xFD,0xB6,0xFC,0x57,0xFB,0x5D,0xFA, + 0x2E,0xFA,0xA5,0xFB,0x0A,0xFC,0x86,0xFD,0x78,0xFE,0x47,0xFF, + 0x0F,0x00,0xDA,0x00,0x0B,0x02,0x81,0x03,0xA3,0x0B,0x6F,0x0F, + 0x20,0x0D,0xFE,0x0A,0x3C,0x04,0xA4,0xFE,0x87,0xFC,0x5E,0xFD, + 0x87,0xFB,0xAC,0xF7,0x16,0xF5,0x87,0xEF,0x62,0xEE,0x58,0xF1, + 0x0F,0xF5,0x5D,0xF8,0x02,0xFB,0x8F,0xFD,0x08,0xFE,0xAD,0x02, + 0x45,0x08,0x3B,0x0C,0xA0,0x0F,0x74,0x10,0x4E,0x0F,0x65,0x0C, + 0x38,0x0C,0xD3,0x0A,0x07,0x08,0x38,0x05,0x36,0x00,0x9C,0xFB, + 0x82,0xF8,0xAF,0xF8,0x65,0xF7,0xBB,0xF6,0xE2,0xF6,0x26,0xF5, + 0x2F,0xF6,0xBD,0xF8,0x55,0xFC,0xED,0xFE,0xC0,0x01,0x4D,0x03, + 0x14,0x03,0xC8,0x04,0x6D,0x05,0xF8,0x05,0xC0,0x05,0xEA,0x04, + 0xE4,0x02,0xCF,0x00,0x1D,0x00,0x76,0xFE,0xDC,0xFD,0xFA,0xFC, + 0x61,0xFC,0x9A,0xFB,0x89,0xFB,0xAD,0xFC,0xFF,0xFC,0x61,0xFE, + 0x0A,0xFF,0x2D,0x00,0xF3,0x00,0x33,0x01,0x36,0x02,0x50,0x01, + 0x70,0x03,0x7E,0x08,0xA1,0x09,0xA7,0x08,0x8B,0x06,0x4C,0x01, + 0xCF,0xFC,0xD6,0xFB,0x18,0xFC,0x38,0xFB,0x41,0xFA,0xAD,0xF8, + 0xC9,0xF5,0x98,0xF5,0x73,0xF7,0x74,0xF9,0xFA,0xFB,0xA9,0xFE, + 0x37,0x00,0xF4,0x00,0xF9,0x02,0x96,0x04,0x18,0x06,0xDA,0x07, + 0xB6,0x08,0x4C,0x08,0x1A,0x07,0x66,0x06,0xD2,0x04,0x73,0x03, + 0x90,0x02,0x31,0x01,0xBA,0xFF,0x84,0xFE,0x04,0xFE,0x75,0xFD, + 0x74,0xFD,0x91,0xFD,0x87,0xFD,0xB9,0xFD,0x0A,0xFE,0x2E,0xFE, + 0x2B,0xFE,0xAF,0xFE,0xE6,0xFE,0x36,0xFF,0x8E,0xFF,0xBB,0xFF, + 0xC8,0xFF,0x95,0xFF,0x8A,0xFF,0x5F,0xFF,0x64,0xFF,0x9E,0xFF, + 0xDF,0xFF,0xB9,0xFF,0xC7,0xFF,0xDC,0xFF,0xAA,0xFF,0xF9,0xFF, + 0x02,0x00,0x05,0x00,0xF9,0xFF,0x04,0x00,0xF9,0xFF,0xCD,0xFF, + 0x1E,0x00,0x36,0x00,0x3D,0x00,0x2C,0x00,0x41,0x00,0x36,0x00, + 0x38,0x00,0x79,0x00,0x33,0x00,0x59,0x00,0x09,0x01,0x5E,0x01, + 0x63,0x01,0x4A,0x01,0xD4,0x00,0x22,0x00,0xD0,0xFF,0xE1,0xFF, + 0xF0,0xFF,0x10,0x00,0xFC,0xFF,0x46,0xFF,0x9C,0xFE,0x30,0xFE, + 0xFE,0xFD,0x2F,0xFE,0x9D,0xFE,0xFD,0xFE,0x1C,0xFF,0x57,0xFF, + 0x8D,0xFF,0xD3,0xFF,0x49,0x00,0xB8,0x00,0x13,0x01,0x49,0x01, + 0x66,0x01,0x38,0x01,0x1D,0x01,0x22,0x01,0x0C,0x01,0x05,0x01, + 0x0F,0x01,0x09,0x01,0xDC,0x00,0xBF,0x00,0x9A,0x00,0x81,0x00, + 0x81,0x00,0x8B,0x00,0x8D,0x00,0x50,0x00,0x25,0x00,0xD7,0xFF, + 0x8A,0xFF,0x6F,0xFF,0x52,0xFF,0x46,0xFF,0x3D,0xFF,0x2C,0xFF, + 0xF1,0xFE,0xDA,0xFE,0xD5,0xFE,0xD5,0xFE,0xF2,0xFE,0x16,0xFF, + 0x2A,0xFF,0x33,0xFF,0x52,0xFF,0x55,0xFF,0x6E,0xFF,0x9E,0xFF, + 0xC7,0xFF,0xE3,0xFF,0xF1,0xFF,0x0F,0x00,0x22,0x00,0x34,0x00, + 0x50,0x00,0x6B,0x00,0x89,0x00,0xA3,0x00,0xAB,0x00,0xAF,0x00, + 0xAF,0x00,0xA1,0x00,0x9C,0x00,0x96,0x00,0x88,0x00,0x7C,0x00, + 0x67,0x00,0x52,0x00,0x43,0x00,0x33,0x00,0x21,0x00,0x13,0x00, + 0x09,0x00,0x00,0x00,0xF3,0xFF,0xE4,0xFF,0xDF,0xFF,0xDA,0xFF, + 0xD0,0xFF,0xCC,0xFF,0xC9,0xFF,0xC2,0xFF,0xC4,0xFF,0xC3,0xFF, + 0xBF,0xFF,0xC1,0xFF,0xBD,0xFF,0xB0,0xFF,0xA9,0xFF,0xA7,0xFF, + 0xAB,0xFF,0xB5,0xFF,0xB6,0xFF,0xCA,0xFF,0xDD,0xFF,0xEE,0xFF, + 0x10,0x00,0x25,0x00,0x43,0x00,0x58,0x00,0x6E,0x00,0x85,0x00, + 0x8C,0x00,0x98,0x00,0x95,0x00,0x91,0x00,0x89,0x00,0x76,0x00, + 0x66,0x00,0x5D,0x00,0x49,0x00,0x2D,0x00,0x1C,0x00,0xFF,0xFF, + 0xEC,0xFF,0xD5,0xFF,0xC6,0xFF,0xB6,0xFF,0x9D,0xFF,0xA0,0xFF, + 0x92,0xFF,0x8C,0xFF,0x89,0xFF,0x8B,0xFF,0x94,0xFF,0x96,0xFF, + 0xA2,0xFF,0xA5,0xFF,0xB7,0xFF,0xBD,0xFF,0xB7,0xFF,0xD2,0xFF, + 0xE3,0xFF,0xDF,0xFF,0xE8,0xFF,0xFE,0xFF,0xF9,0xFF,0xFB,0xFF, + 0x0F,0x00,0x0A,0x00,0x11,0x00,0x1B,0x00,0x13,0x00,0x22,0x00, + 0x27,0x00,0x16,0x00,0x2E,0x00,0x2C,0x00,0x2B,0x00,0x2C,0x00, + 0x2D,0x00,0x35,0x00,0x28,0x00,0x2C,0x00,0x26,0x00,0x2D,0x00, + 0x29,0x00,0x27,0x00,0x14,0x00,0x13,0x00,0x1E,0x00,0x0D,0x00, + 0x15,0x00,0x10,0x00,0x02,0x00,0xF7,0xFF,0x05,0x00,0xF9,0xFF, + 0xED,0xFF,0xFF,0xFF,0xF6,0xFF,0xF8,0xFF,0xF4,0xFF,0xF0,0xFF, + 0xFB,0xFF,0x05,0x00,0xF9,0xFF,0xFC,0xFF,0x04,0x00,0x05,0x00, + 0x00,0x00,0xFA,0xFF,0x0F,0x00,0xF6,0xFF,0x16,0x00,0x19,0x00, + 0xE3,0xFF,0x02,0x00,0x16,0x00,0x06,0x00,0xFA,0xFF,0xFF,0xFF, + 0x00,0x00,0xF9,0xFF,0xF7,0xFF,0xF8,0xFF,0xF2,0xFF,0xF9,0xFF, + 0xFC,0xFF,0xE0,0xFF,0xEE,0xFF,0xF7,0xFF,0xEB,0xFF,0xEE,0xFF, + 0xF2,0xFF,0xEA,0xFF,0xE1,0xFF,0xFA,0xFF,0xEE,0xFF,0xE3,0xFF, + 0xF9,0xFF,0xF5,0xFF,0xDE,0xFF,0x03,0x00,0x13,0x00,0xD9,0xFF, + 0xED,0xFF,0x1B,0x00,0x01,0x00,0xF4,0xFF,0x1C,0x00,0x02,0x00, + 0xE4,0xFF,0x22,0x00,0x12,0x00,0xEA,0xFF,0x23,0x00,0x0F,0x00, + 0xF1,0xFF,0x12,0x00,0x07,0x00,0xF9,0xFF,0x10,0x00,0x0F,0x00, + 0xF2,0xFF,0x10,0x00,0x15,0x00,0xFF,0xFF,0x05,0x00,0x06,0x00, + 0x16,0x00,0xFD,0xFF,0x14,0x00,0x05,0x00,0xF3,0xFF,0x16,0x00, + 0xFE,0xFF,0x18,0x00,0xEC,0xFF,0xF5,0xFF,0x19,0x00,0x0B,0x00, + 0xEA,0xFF,0x06,0x00,0x09,0x00,0xEA,0xFF,0x23,0x00,0xEC,0xFF, + 0xFB,0xFF,0xFE,0xFF,0xFE,0xFF,0x0D,0x00,0xFD,0xFF,0xFA,0xFF, + 0xF0,0xFF,0x0C,0x00,0x00,0x00,0x09,0x00,0x01,0x00,0x04,0x00, + 0x09,0x00,0xFA,0xFF,0x10,0x00,0xF9,0xFF,0xFD,0xFF,0x13,0x00, + 0xF4,0xFF,0xF8,0xFF,0xEF,0xFF,0xFC,0xFF,0x10,0x00,0xEC,0xFF, + 0xF5,0xFF,0xFF,0xFF,0xFB,0xFF,0xFA,0xFF,0xFD,0xFF,0xFD,0xFF, + 0xFF,0xFF,0x0F,0x00,0xF8,0xFF,0x03,0x00,0x08,0x00,0xF3,0xFF, + 0x09,0x00,0xF7,0xFF,0xF9,0xFF,0x00,0x00,0xEE,0xFF,0xFC,0xFF, + 0x08,0x00,0x02,0x00,0x10,0x00,0xF2,0xFF,0xE3,0xFF,0x17,0x00, + 0xFB,0xFF,0xF9,0xFF,0xF3,0xFF,0x0D,0x00,0x1E,0x00,0xF3,0xFF, + 0x08,0x00,0xD9,0xFF,0x21,0x00,0x22,0x00,0xE6,0xFF,0x0C,0x00, + 0xE1,0xFF,0x1A,0x00,0xEE,0xFF,0xDE,0xFF,0x22,0x00,0xEB,0xFF, + 0xF4,0xFF,0x0E,0x00,0x05,0x00,0xFE,0xFF,0x00,0x00,0x03,0x00, + 0x0C,0x00,0x15,0x00,0xE5,0xFF,0xFF,0xFF,0x1C,0x00,0xE0,0xFF, + 0xF8,0xFF,0x0C,0x00,0x01,0x00,0x10,0x00,0xF1,0xFF,0x15,0x00, + 0x0E,0x00,0xFD,0xFF,0x1B,0x00,0xEB,0xFF,0xFD,0xFF,0x01,0x00, + 0xFF,0xFF,0x02,0x00,0x2A,0x00,0x0C,0x00,0xBE,0xFF,0xE3,0xFF, + 0xE5,0xFF,0x0E,0x00,0x08,0x00,0xF7,0xFF,0x16,0x00,0xF9,0xFF, + 0xD2,0xFF,0xF6,0xFF,0x35,0x00,0x2F,0x00,0x05,0x00,0xE8,0xFF, + 0x13,0x00,0x4F,0x00,0x19,0x00,0xDD,0xFF,0x00,0x00,0xE2,0xFF, + 0xDC,0xFF,0xF1,0xFF,0x07,0x00,0x13,0x00,0x27,0x00,0xD3,0xFF, + 0x3A,0x00,0xC4,0xFF,0x65,0x00,0xF3,0xFF,0x09,0x00,0xDD,0xFF, + 0x45,0xFE,0x5B,0x05,0xB6,0x02,0x68,0xFD,0x08,0xFD,0xFE,0xFB, + 0xEB,0xFE,0x49,0xFF,0xC0,0xFF,0x8A,0xFF,0x18,0x01,0x6D,0x01, + 0x77,0x01,0x74,0x01,0xC1,0x00,0x81,0x00,0x58,0x00,0xA0,0x00, + 0x5A,0x00,0x71,0x01,0xE7,0xFF,0x9B,0xFF,0x65,0xFF,0xBB,0xFE, + 0xB4,0xFF,0xFC,0xFE,0x66,0xFF,0x0C,0x00,0x3D,0xFF,0x77,0xFF, + 0xAF,0xFF,0x4C,0xFF,0x45,0x00,0x9A,0x00,0x4D,0x00,0xA5,0x00, + 0x32,0x00,0xE7,0xFF,0x4D,0x00,0x64,0x00,0x0B,0x00,0xC0,0xFF, + 0x42,0x00,0x02,0x00,0x59,0xFF,0x7C,0x00,0x67,0x00,0xEE,0xFF, + 0x43,0x00,0xE0,0xFF,0x0F,0x00,0x94,0xFF,0xEF,0xFF,0x4C,0x00, + 0xA8,0xFF,0x9D,0xFF,0xE2,0xFF,0xD3,0xFF,0x5F,0x00,0x3C,0x00, + 0x86,0xFF,0x5B,0x00,0x3D,0x00,0xCE,0xFF,0xD2,0xFF,0xB8,0x00, + 0x11,0x00,0xFD,0xFF,0x9F,0x00,0xA8,0xFF,0xB6,0xFF,0xAE,0xFF, + 0x51,0x00,0xF0,0xFF,0xA7,0xFF,0xA4,0xFF,0xF9,0xFF,0x87,0x01, + 0x8D,0x00,0xD7,0xFF,0x97,0xFE,0xA7,0xFE,0x39,0x00,0xA4,0xFF, + 0x44,0x00,0xAC,0x00,0x36,0x00,0x91,0xFF,0x51,0x00,0x5D,0x00, + 0xD0,0xFF,0x02,0x00,0x69,0xFF,0x5F,0x00,0xBE,0x00,0x7F,0x01, + 0xA1,0x00,0x4B,0xFF,0x1E,0x00,0xB2,0xFD,0xB0,0x00,0xDE,0x01, + 0xB9,0xFD,0x98,0xFE,0x63,0xFF,0x71,0x00,0xBF,0x00,0x35,0x02, + 0x56,0x00,0xF5,0xFE,0x2D,0xFF,0x51,0xFF,0xB6,0x00,0xE4,0xFE, + 0x3A,0x00,0x4B,0x00,0xC6,0xFF,0x55,0xFF,0x3A,0x00,0x64,0x01, + 0x27,0x00,0x93,0xFF,0x5E,0xFF,0x57,0x00,0x60,0x00,0x5B,0x00, + 0x5E,0x00,0xB6,0xFF,0xB8,0x00,0xE1,0x02,0x54,0x00,0x1C,0xFE, + 0x4C,0x00,0x92,0x00,0xBD,0xFF,0xB7,0xFF,0x18,0xFD,0x51,0xFD, + 0x25,0xFF,0xD4,0xFE,0x12,0x01,0x78,0x00,0xB8,0xFF,0x95,0xFF, + 0xD3,0xFF,0x18,0x02,0x87,0x02,0x54,0x03,0xC9,0x02,0x5E,0x00, + 0x86,0xFF,0x5F,0xFF,0xEA,0xFF,0x53,0x00,0xF8,0xFE,0x09,0xFD, + 0x5B,0xFD,0x07,0xFF,0x6A,0xFE,0xE6,0xFE,0x6A,0x00,0xF1,0x00, + 0xC4,0xFF,0xD2,0xFE,0xA5,0x00,0xB8,0x01,0xBF,0x03,0x5F,0x02, + 0x0D,0xFE,0x96,0xFE,0xB1,0xFF,0x51,0x00,0x8E,0x00,0x43,0xFF, + 0xA7,0xFD,0x71,0xFF,0x63,0x00,0x43,0xFF,0x4B,0x01,0xA6,0x00, + 0x7F,0xFF,0xB7,0x01,0x0F,0x01,0xB6,0xFE,0x5E,0x00,0xD1,0xFE, + 0xFC,0xFE,0x1A,0x03,0xBA,0x01,0x5F,0xFE,0xE4,0xFE,0xC1,0x00, + 0x8E,0x00,0x25,0x00,0x1D,0xFF,0x40,0x00,0x9A,0x00,0x37,0x01, + 0xCB,0x00,0xCD,0xFE,0xB8,0x00,0x3C,0x00,0x93,0xFE,0xAA,0xFF, + 0xC0,0xFF,0x94,0xFE,0xAC,0xFE,0x51,0xFF,0xAB,0xFF,0x1D,0x00, + 0xF5,0xFE,0x90,0xFE,0x73,0x00,0x5F,0x00,0xFB,0xFF,0x9A,0x00, + 0xA6,0x00,0x45,0x00,0x50,0x00,0xE9,0x00,0x97,0x00,0x84,0x00, + 0xE0,0x00,0xB1,0x00,0x56,0x00,0x22,0x00,0x15,0x00,0x18,0x00, + 0x87,0x00,0x9C,0x00,0xEB,0xFF,0x3B,0xFF,0xAE,0xFF,0x2D,0x00, + 0xBF,0xFF,0xB5,0xFF,0xC0,0xFF,0xCD,0xFF,0xB8,0xFF,0xAB,0xFF, + 0xD5,0xFF,0xEC,0xFF,0xE0,0xFF,0x94,0xFF,0xBA,0xFF,0xD8,0xFF, + 0xB8,0xFF,0xE6,0xFF,0xD2,0xFF,0xCB,0xFF,0xEB,0xFF,0xCC,0xFF, + 0xD0,0xFF,0xE6,0xFF,0x00,0x00,0xF8,0xFF,0x1A,0x00,0x36,0x00, + 0x06,0x00,0x17,0x00,0x57,0x00,0x57,0x00,0x2A,0x00,0x4A,0x00, + 0x4E,0x00,0x3C,0x00,0x34,0x00,0x16,0x00,0x00,0x00,0xFA,0xFF, + 0xF6,0xFF,0xE7,0xFF,0xDB,0xFF,0x03,0x00,0x1A,0x00,0xBB,0xFF, + 0xD7,0xFF,0x21,0x00,0xF9,0xFF,0x19,0x00,0x35,0x00,0x09,0x00, + 0x26,0x00,0x26,0x00,0x28,0x00,0x30,0x00,0x15,0x00,0x25,0x00, + 0x23,0x00,0x48,0x00,0x49,0x00,0x2A,0x00,0x19,0x00,0xE9,0xFF, + 0xE0,0xFF,0xCF,0xFF,0xA8,0xFF,0x8F,0xFF,0x95,0xFF,0xAE,0xFF, + 0x8F,0xFF,0x8D,0xFF,0xA5,0xFF,0xAF,0xFF,0xCD,0xFF,0xE8,0xFF, + 0xEF,0xFF,0x11,0x00,0x33,0x00,0x2E,0x00,0x36,0x00,0x47,0x00, + 0x45,0x00,0x37,0x00,0x32,0x00,0x24,0x00,0x13,0x00,0x1A,0x00, + 0x27,0x00,0x20,0x00,0x10,0x00,0x04,0x00,0xFD,0xFF,0xF2,0xFF, + 0xF1,0xFF,0xE8,0xFF,0xE3,0xFF,0xF7,0xFF,0xFB,0xFF,0xFD,0xFF, + 0xFD,0xFF,0xF5,0xFF,0xF8,0xFF,0xFD,0xFF,0xF1,0xFF,0xEE,0xFF, + 0xF1,0xFF,0xEB,0xFF,0xE3,0xFF,0xDF,0xFF,0xE5,0xFF,0xE7,0xFF, + 0xF3,0xFF,0xFC,0xFF,0xFE,0xFF,0x0B,0x00,0x10,0x00,0x17,0x00, + 0x1E,0x00,0x1E,0x00,0x1C,0x00,0x0A,0x00,0x05,0x00,0x07,0x00, + 0x02,0x00,0xFF,0xFF,0xFC,0xFF,0xFF,0xFF,0xFD,0xFF,0xFA,0xFF, + 0xF8,0xFF,0xFB,0xFF,0x00,0x00,0x09,0x00,0x0B,0x00,0x0C,0x00, + 0x10,0x00,0x15,0x00,0x17,0x00,0x1B,0x00,0x1D,0x00,0x1B,0x00, + 0x1D,0x00,0x18,0x00,0x12,0x00,0x10,0x00,0x0A,0x00,0x08,0x00, + 0x06,0x00,0x06,0x00,0x06,0x00,0x04,0x00,0x02,0x00,0x00,0x00, + 0xFE,0xFF,0xFC,0xFF,0xFB,0xFF,0xF9,0xFF,0xF4,0xFF,0xEE,0xFF, + 0xEC,0xFF,0xEC,0xFF,0xE9,0xFF,0xE6,0xFF,0xE4,0xFF,0xE4,0xFF, + 0xE4,0xFF,0xE5,0xFF,0xE5,0xFF,0xE3,0xFF,0xE3,0xFF,0xE5,0xFF, + 0xE6,0xFF,0xEA,0xFF,0xEC,0xFF,0xF1,0xFF,0xF8,0xFF,0xF9,0xFF, + 0xFB,0xFF,0x00,0x00,0x07,0x00,0x08,0x00,0x08,0x00,0x0B,0x00, + 0x0F,0x00,0x0C,0x00,0x13,0x00,0x1C,0x00,0x09,0x00,0x0B,0x00, + 0x0D,0x00,0x04,0x00,0x05,0x00,0x02,0x00,0x07,0x00,0x08,0x00, + 0x00,0x00,0x04,0x00,0x07,0x00,0x04,0x00,0x07,0x00,0x08,0x00, + 0x0B,0x00,0x07,0x00,0x0A,0x00,0x09,0x00,0x04,0x00,0x04,0x00, + 0x02,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0xFA,0xFF,0x01,0x00, + 0x01,0x00,0xFE,0xFF,0x01,0x00,0x03,0x00,0x01,0x00,0x01,0x00, + 0x01,0x00,0x02,0x00,0x08,0x00,0xFF,0xFF,0xFB,0xFF,0xFD,0xFF, + 0x04,0x00,0x05,0x00,0x04,0x00,0x03,0x00,0x02,0x00,0x03,0x00, + 0x02,0x00,0x03,0x00,0xFE,0xFF,0xFE,0xFF,0xFE,0xFF,0xFC,0xFF, + 0xFE,0xFF,0x05,0x00,0xF6,0xFF,0xF1,0xFF,0xF8,0xFF,0xF2,0xFF, + 0xF3,0xFF,0xF7,0xFF,0xFD,0xFF,0xFB,0xFF,0xFC,0xFF,0xFC,0xFF, + 0xFE,0xFF,0x01,0x00,0x00,0x00,0x02,0x00,0x02,0x00,0x03,0x00, + 0x03,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x01,0x00,0x02,0x00, + 0x01,0x00,0xFE,0xFF,0x02,0x00,0x01,0x00,0xFF,0xFF,0x00,0x00, + 0xFE,0xFF,0x00,0x00,0x02,0x00,0xFF,0xFF,0x03,0x00,0x01,0x00, + 0x00,0x00,0x06,0x00,0x03,0x00,0x04,0x00,0x04,0x00,0x04,0x00, + 0x05,0x00,0x03,0x00,0x02,0x00,0x01,0x00,0x02,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0xFD,0xFF,0xFF,0xFF,0xFE,0xFF,0xFF,0xFF, + 0xFD,0xFF,0xFD,0xFF,0xFC,0xFF,0xFD,0xFF,0xFF,0xFF,0xFC,0xFF, + 0xFE,0xFF,0xFE,0xFF,0xFE,0xFF,0xFE,0xFF,0xFE,0xFF,0xFE,0xFF, + 0xFC,0xFF,0xFC,0xFF,0xFE,0xFF,0xFE,0xFF,0x00,0x00,0xFF,0xFF, + 0xFF,0xFF,0x00,0x00,0x00,0x00,0xFE,0xFF,0x00,0x00,0x00,0x00, + 0xFE,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x03,0x00,0x03,0x00,0xFF,0xFF,0x01,0x00,0x02,0x00,0x00,0x00, + 0x02,0x00,0x04,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0xFF,0xFF, + 0x01,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x02,0x00, + 0x02,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0x02,0x00,0x02,0x00,0x02,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0xFF,0xFF,0x01,0x00, + 0x02,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x03,0x00, + 0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0x00,0x00,0x00,0x00, + 0xFE,0xFF,0xFE,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x01,0x00, + 0xFE,0xFF,0xFF,0xFF,0x01,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xFF,0xFF,0xFE,0xFF,0xFE,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFE,0xFF,0xFD,0xFF,0xFE,0xFF,0xFF,0xFF,0xFE,0xFF, + 0xFE,0xFF,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0xFE,0xFF, + 0xFE,0xFF,0xFE,0xFF,0xFF,0xFF,0x02,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x01,0x00,0x01,0x00, + 0x01,0x00,0x02,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x01,0x00, + 0x01,0x00,0xFF,0xFF,0x01,0x00,0x04,0x00,0x02,0x00,0xFF,0xFF, + 0xFF,0xFF,0x00,0x00,0x03,0x00,0x05,0x00,0x03,0x00,0x00,0x00, + 0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x01,0x00,0xFF,0xFF,0xFE,0xFF, + 0xFE,0xFF,0x02,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00, + 0x02,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x01,0x00, + 0x01,0x00,0x02,0x00,0xFF,0xFF,0x02,0x00,0x01,0x00,0x01,0x00, + 0x02,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFE,0xFF,0x00,0x00, + 0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0xFE,0xFF,0x00,0x00, + 0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF, + 0x00,0x00,0xFE,0xFF,0xFE,0xFF,0xFE,0xFF,0xFE,0xFF,0x00,0x00, + 0xFE,0xFF,0xFE,0xFF,0xFF,0xFF,0xFE,0xFF,0xFF,0xFF,0xFE,0xFF, + 0xFE,0xFF,0x02,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0xFE,0xFF, + 0x00,0x00,0x01,0x00,0xFF,0xFF,0xFE,0xFF,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xFF, + 0xFE,0xFF,0xFE,0xFF,0xFE,0xFF,0xFE,0xFF,0x01,0x00,0x01,0x00, + 0x00,0x00,0x01,0x00,0xFE,0xFF,0x01,0x00,0x03,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x03,0x00, + 0x00,0x00,0x02,0x00,0x02,0x00,0x03,0x00,0x00,0x00,0x02,0x00, + 0x01,0x00,0xFF,0xFF,0x01,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00, + 0x01,0x00,0x01,0x00,0xFF,0xFF,0x00,0x00,0xFE,0xFF,0xFF,0xFF, + 0x02,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x01,0x00, + 0xFF,0xFF,0x02,0x00,0xFE,0xFF,0xFF,0xFF,0x02,0x00,0xFF,0xFF, + 0x00,0x00,0xFE,0xFF,0xFF,0xFF,0x01,0x00,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0xFE,0xFF,0xFE,0xFF,0xFF,0xFF,0xFE,0xFF, + 0xFF,0xFF,0x01,0x00,0xFE,0xFF,0xFF,0xFF,0x02,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xFF,0xFF,0x02,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0xFF,0xFF,0xFE,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x01,0x00, + 0x01,0x00,0x03,0x00,0x01,0x00,0x00,0x00,0x03,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0xFF,0xFF,0x01,0x00,0x01,0x00, + 0xFE,0xFF,0xFD,0xFF,0xFC,0xFF,0xFD,0xFF,0xFB,0xFF,0xFA,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFC,0xFF,0xFE,0xFF,0xFE,0xFF,0xFE,0xFF, + 0x00,0x00,0xFE,0xFF,0xFF,0xFF,0x02,0x00,0x00,0x00,0x01,0x00, + 0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x03,0x00, + 0x04,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x01,0x00, + 0x00,0x00,0xFE,0xFF,0xFA,0xFF,0xFE,0xFF,0xFB,0xFF,0x00,0x00, + 0xFD,0xFF,0xFE,0xFF,0x02,0x00,0x00,0x00,0x05,0x00,0xF8,0xFF, + 0x10,0x00,0xF7,0xFF,0x0B,0x00,0xE5,0xFF,0xFA,0xFF,0x91,0x00, + 0x54,0x00,0xED,0xFF,0xDD,0xFF,0xAA,0xFF,0x07,0x00,0xDD,0x00, + 0xB0,0x00,0x49,0x01,0xC5,0xFF,0x26,0xFE,0x34,0xFF,0xE9,0xFF, + 0x7A,0x00,0xA0,0xFE,0x67,0x01,0xDE,0xFE,0x40,0xFE,0x4B,0x06, + 0xA0,0x00,0x59,0xFD,0x47,0xFD,0x4B,0xF9,0x72,0xFB,0x2F,0x00, + 0xED,0x01,0x1E,0x01,0x79,0x03,0xAE,0x04,0xA8,0x00,0x9B,0x01, + 0x30,0x04,0xF9,0xFF,0x85,0x00,0xC9,0x01,0xC8,0xFE,0xD7,0xFE, + 0xC9,0xFF,0x3D,0x00,0xDD,0xFE,0xBB,0xFE,0x0B,0x00,0xD0,0xFC, + 0x29,0xFC,0x5D,0xFF,0x73,0xFE,0x71,0xFF,0x50,0x03,0xBE,0x02, + 0xBD,0x01,0x1E,0x02,0x64,0x01,0x66,0xFF,0x1E,0xFE,0xB2,0xFF, + 0x64,0xFE,0x91,0xFD,0x95,0x00,0x57,0xFF,0x89,0xFE,0x90,0x00, + 0x72,0x00,0xB4,0xFF,0x78,0x00,0x3F,0x01,0x04,0x00,0x9D,0xFF, + 0xD1,0x00,0x1C,0x01,0xDE,0xFF,0x3B,0x01,0x2D,0x01,0x26,0xFE, + 0xBF,0xFE,0x93,0xFF,0xF4,0xFE,0x9F,0xFF,0x13,0x01,0x11,0x00, + 0x3D,0xFF,0x34,0x00,0xFB,0x00,0x7B,0xFF,0x0F,0x00,0x50,0x02, + 0x1D,0xFF,0x97,0xFF,0x94,0x00,0x64,0xFF,0x84,0xFF,0xE3,0xFF, + 0x2E,0x01,0xA0,0xFF,0x49,0xFF,0xAD,0x01,0x14,0x00,0x19,0x00, + 0x73,0x01,0x02,0xFF,0x80,0x01,0x0B,0xFE,0xA1,0x00,0x52,0x00, + 0x93,0xFD,0xD7,0x02,0xCC,0xFC,0xF7,0x01,0xD4,0xFF,0xC9,0xFE, + 0xEB,0x02,0xD1,0xFC,0xD1,0x00,0x48,0x00,0x29,0xFF,0xEB,0x00, + 0xBA,0xFF,0x96,0x00,0x44,0x00,0x63,0xFF,0x12,0x00,0x16,0xFF, + 0xE6,0xFF,0xF9,0xFF,0x43,0xFE,0xE7,0x00,0xC4,0xFF,0x5E,0xFF, + 0x5F,0x01,0xFD,0xFE,0x3D,0xFF,0x14,0x00,0xD5,0x00,0x01,0x00, + 0xB8,0xFF,0x36,0x02,0xEC,0xFF,0x6E,0x00,0x6F,0x00,0xC7,0xFF, + 0x9B,0xFE,0x61,0xFE,0x5D,0x01,0x0C,0xFE,0xEC,0xFF,0x95,0x00, + 0xCD,0xFE,0xDD,0x00,0x1F,0x01,0xF2,0x00,0xBC,0xFF,0x8C,0x00, + 0xBC,0xFF,0xC0,0xFF,0xC2,0x01,0x1A,0x01,0x36,0xFF,0xB6,0xFF, + 0xD1,0xFF,0xD5,0xFE,0xB4,0xFF,0x30,0xFF,0x24,0xFF,0xF6,0xFF, + 0x94,0x01,0x4D,0x01,0x72,0x00,0x06,0xFF,0xBE,0xFF,0x86,0x00, + 0x74,0x00,0xF9,0x01,0xC0,0xFE,0x45,0x00,0xEA,0xFF,0x29,0xFF, + 0x28,0x01,0xDE,0xFE,0xF1,0xFE,0x54,0xFF,0x02,0x01,0xB0,0xFF, + 0xBA,0xFE,0x8B,0x01,0x45,0xFF,0xE1,0xFE,0x33,0x02,0x46,0x01, + 0x08,0xFF,0x5B,0xFF,0x37,0x00,0x38,0x00,0x53,0x00,0x94,0x00, + 0xAA,0xFE,0x64,0xFF,0x5D,0x01,0x2C,0xFF,0x75,0xFF,0xA6,0x00, + 0x3E,0xFF,0xCF,0xFE,0x5F,0x01,0x39,0x00,0xDE,0xFD,0x77,0x00, + 0x08,0x00,0x84,0xFF,0x58,0x01,0x17,0x01,0x89,0xFF,0xB8,0xFF, + 0x71,0x01,0xB2,0xFE,0x01,0xFE,0x1F,0x00,0x12,0x00,0x94,0x00, + 0x0A,0x00,0xD6,0x00,0x0E,0x01,0xB7,0x01,0x01,0x00,0x12,0xFF, + 0x27,0x00,0x37,0xFE,0xC6,0xFF,0xDE,0xFF,0xF3,0xFF,0xC7,0x00, + 0x27,0x00,0xC3,0xFF,0x8E,0xFF,0x26,0x01,0x05,0x00,0x9D,0x00, + 0x70,0x00,0x04,0xFF,0x22,0x00,0x20,0xFE,0xE8,0xFF,0x37,0x00, + 0x64,0xFF,0x51,0x01,0xCF,0xFF,0x53,0xFF,0x54,0x01,0xAE,0x00, + 0xEF,0xFE,0x9D,0x00,0x6D,0x00,0xBE,0xFE,0xE7,0x00,0x23,0x01, + 0x03,0xFE,0x24,0x00,0xAD,0x00,0x8D,0xFE,0xEB,0xFF,0xCD,0x00, + 0x11,0x00,0xD6,0x00,0xD6,0xFF,0xEA,0xFE,0xE2,0xFF,0x5A,0xFF, + 0x15,0x01,0xE0,0xFF,0x8E,0xFF,0x43,0x00,0xA8,0xFF,0xED,0x00, + 0xF8,0xFF,0x59,0xFF,0x85,0x00,0xDF,0x01,0x8A,0x00,0xCC,0xFF, + 0x69,0x00,0x48,0xFF,0xBB,0xFF,0xE0,0x00,0x1E,0xFF,0xBD,0xFE, + 0x97,0x00,0x35,0xFF,0x56,0xFF,0xBC,0xFF,0xA2,0xFF,0x40,0x00, + 0x73,0x00,0xC0,0x00,0xEE,0xFF,0x29,0x00,0xE8,0xFE,0xAD,0x00, + 0x7C,0x00,0x3B,0xFF,0xCF,0x01,0xCA,0xFF,0xED,0xFD,0x20,0x00, + 0x9C,0x00,0xA7,0xFE,0x9C,0x00,0x01,0x02,0x17,0xFE,0x8F,0xFF, + 0x5E,0x02,0xB9,0xFD,0xC8,0xFF,0xC6,0x01,0xFB,0xFF,0xA4,0xFF, + 0x27,0x01,0xB2,0x00,0x50,0xFD,0xB2,0x01,0x5F,0x01,0x6F,0xFE, + 0x05,0x00,0xE8,0x00,0x32,0xFE,0x9B,0xFF,0xB3,0x01,0xDF,0xFD, + 0x3E,0x00,0xBD,0x01,0x09,0xFF,0x60,0x00,0x5C,0x01,0xB7,0xFE, + 0xDD,0xFE,0xC6,0x00,0xBF,0xFF,0x73,0xFE,0x2E,0x01,0xD8,0x00, + 0x3B,0xFE,0x34,0x01,0xEF,0x00,0x5E,0xFE,0xB7,0x00,0x05,0x01, + 0x8D,0xFE,0xCE,0xFF,0xBE,0x00,0x43,0xFE,0x46,0x00,0x26,0x01, + 0x30,0xFE,0xED,0x00,0x81,0x00,0xBB,0x00,0xCF,0xFE,0x25,0x01, + 0xFF,0x00,0x8E,0xFD,0x09,0x03,0x43,0xFE,0x2E,0x00,0x26,0x01, + 0x35,0xFF,0x53,0x00,0x83,0x00,0xE8,0xFF,0x26,0xFE,0x30,0x01, + 0xD9,0xFF,0x88,0xFF,0xC7,0x00,0xE7,0x00,0xB6,0xFF,0x21,0x00, + 0xE9,0x00,0x94,0xFF,0x4F,0xFF,0x90,0x00,0x2E,0xFF,0x14,0xFF, + 0x41,0x00,0xB8,0xFF,0x3F,0x00,0xD2,0xFF,0x7C,0x00,0xF0,0xFF, + 0x05,0x00,0x54,0x00,0xEC,0xFF,0xCA,0x00,0xE0,0xFF,0xA3,0xFF, + 0x07,0x00,0x9B,0xFF,0x92,0xFF,0x26,0x00,0xE0,0xFF,0xE9,0xFE, + 0xE8,0x00,0xE6,0xFF,0x38,0xFF,0x20,0x01,0x7A,0xFF,0x0A,0x00, + 0x22,0x01,0xB0,0xFF,0xBF,0xFF,0x89,0x00,0x5C,0xFF,0x96,0xFF, + 0x8D,0x00,0xD7,0xFF,0xA8,0xFF,0x6B,0x00,0x3C,0x00,0x84,0xFE, + 0x48,0x00,0x75,0x00,0x5F,0xFE,0x32,0x01,0xB3,0x00,0x3D,0xFF, + 0x5F,0x00,0x76,0x00,0x87,0xFF,0xC8,0xFF,0x91,0x01,0x5F,0xFF, + 0x18,0x00,0xFD,0x00,0x06,0xFF,0xCF,0xFF,0x6D,0x00,0xA4,0xFF, + 0x54,0xFF,0xF6,0x00,0xB3,0xFF,0xDC,0xFE,0x9A,0x00,0x62,0xFF, + 0x71,0xFF,0xBA,0x00,0x1F,0x00,0x3A,0x00,0xB6,0x00,0x22,0x00, + 0xCD,0xFF,0x0E,0x00,0x1A,0x00,0xD2,0xFF,0xFA,0xFF,0x32,0x00, + 0xCA,0xFF,0x1E,0x00,0xDC,0xFF,0x03,0x00,0x26,0x00,0x32,0x00, + 0x43,0x00,0x21,0x00,0x0D,0x00,0x59,0xFF,0x34,0x00,0xF2,0xFF, + 0x01,0x00,0xF0,0xFF,0x10,0x00,0xCD,0xFF,0x5C,0xFF,0x6B,0x00, + 0x51,0xFF,0x50,0xFF,0x64,0x00,0x33,0x00,0xE2,0xFF,0x0B,0x01, + 0x38,0x00,0x73,0xFF,0x6F,0x00,0x54,0x00,0xE0,0xFF,0x10,0x00, + 0xA7,0x00,0x67,0xFF,0x1C,0x00,0x84,0x00,0x0C,0xFF,0x95,0xFF, + 0x1B,0x00,0xC1,0xFF,0x41,0x00,0xCA,0x00,0xA0,0xFF,0x1A,0x00, + 0x8D,0x00,0x75,0xFF,0x50,0x00,0xEB,0xFF,0x41,0xFF,0x33,0x00, + 0x0A,0x00,0x50,0xFF,0xDA,0xFF,0x5C,0x00,0xF3,0xFF,0x30,0x00, + 0x85,0x00,0x2E,0x00,0x56,0x00,0xA8,0x00,0x5C,0x00,0x33,0x01, + 0xE8,0x00,0x6D,0x00,0xC4,0x00,0x29,0x00,0x75,0xFF,0xF6,0xFE, + 0xE3,0xFE,0xFE,0xFD,0x96,0xFD,0x28,0xFE,0xD8,0xFD,0xF2,0xFD, + 0xEE,0xFE,0xD4,0xFE,0xD1,0xFF,0x91,0x00,0x55,0x00,0x0B,0x01, + 0x56,0x01,0x50,0x01,0x9D,0x01,0x4D,0x02,0xEA,0x01,0xD3,0x01, + 0xE4,0x01,0xDA,0x00,0xBF,0x00,0xD7,0x00,0x0C,0x00,0xBD,0xFF, + 0x1D,0xFF,0xE2,0xFE,0x5C,0xFF,0xA4,0xFF,0x01,0x00,0x0D,0x00, + 0x8D,0x00,0x70,0x00,0x15,0x00,0x2A,0x00,0xE9,0xFF,0x20,0xFF, + 0x03,0xFF,0x2F,0xFF,0x72,0xFE,0xA7,0xFE,0xDA,0xFE,0xF4,0xFD, + 0xE4,0xFD,0x32,0x01,0xD4,0x03,0x9B,0x06,0x58,0x0A,0x3F,0x06, + 0x16,0x05,0xBD,0x02,0x64,0xF8,0x07,0xF8,0xC4,0xF7,0x41,0xF3, + 0x4E,0xF7,0x69,0xFB,0x71,0xF9,0xD1,0xFA,0x9E,0xFE,0x92,0xFC, + 0xD9,0xFD,0xF6,0x03,0x52,0x03,0x1F,0x06,0x31,0x0B,0x6E,0x08, + 0xD9,0x08,0x60,0x09,0xF9,0x04,0x4D,0x03,0x55,0x03,0x11,0x00, + 0x5A,0xFE,0xC1,0xFF,0x21,0xFE,0x9A,0xFD,0x7C,0xFE,0x43,0xFC, + 0xF8,0xFB,0xF6,0xFB,0xC6,0xFB,0xC4,0xFD,0xA0,0xFE,0x08,0x00, + 0x28,0x01,0x45,0x01,0x7E,0x01,0x1F,0x01,0xB6,0x00,0xFA,0xFF, + 0x24,0x00,0xB4,0xFF,0x8E,0xFE,0x9C,0xFE,0x61,0xFD,0xA8,0xFD, + 0xE7,0xFE,0x92,0xFF,0xF3,0x00,0x48,0x01,0xF2,0x00,0x5A,0x00, + 0x02,0x00,0x98,0xFF,0x03,0xFF,0x33,0xFF,0xD0,0x00,0x43,0x05, + 0x97,0x09,0xCA,0x0B,0xF4,0x0B,0xDA,0x07,0x03,0x05,0x10,0xF9, + 0xEE,0xEE,0xB1,0xF2,0x7B,0xEF,0x51,0xF2,0x46,0xFB,0x8B,0xFB, + 0xF1,0xFB,0x6E,0xFD,0xE4,0xFB,0xE0,0xFB,0x19,0x00,0xC9,0x04, + 0xCF,0x08,0x49,0x0E,0xAC,0x0E,0x70,0x0B,0xC9,0x08,0x37,0x02, + 0x1F,0xFF,0xC3,0xFD,0x41,0xFD,0x16,0xFF,0xBF,0xFE,0xA7,0xFE, + 0x59,0xFC,0x91,0xFA,0x25,0xFD,0x2E,0x00,0x3A,0x05,0xF6,0x07, + 0xD7,0x06,0xFD,0x03,0xBB,0xFD,0x74,0xFA,0xF1,0xF8,0xE6,0xFA, + 0x71,0xFF,0x36,0x01,0x72,0x02,0x7A,0xFF,0xCC,0xFB,0x87,0xFA, + 0x5E,0xFA,0x85,0xFE,0xDC,0x01,0xE4,0x02,0xF6,0x02,0x9C,0xFF, + 0x65,0xFE,0xCB,0xFE,0x22,0x00,0x1D,0x03,0x3A,0x03,0x46,0x02, + 0xDE,0xFE,0xDB,0xFA,0xB5,0xF9,0x57,0xFB,0xFD,0x08,0x25,0x1B, + 0xD1,0x1D,0x8F,0x17,0xBC,0x03,0x8B,0xE9,0x77,0xDC,0xDF,0xD7, + 0x13,0xE7,0x76,0xF8,0x25,0x04,0x41,0x0A,0xEA,0x02,0xA5,0xFF, + 0x5D,0xFD,0x89,0x03,0x73,0x11,0x8C,0x14,0x12,0x10,0x5E,0x05, + 0x18,0xF5,0x01,0xEE,0x99,0xF1,0x89,0xFD,0x3E,0x08,0xDE,0x0A, + 0x92,0x07,0xE4,0xFC,0x45,0xF8,0xAD,0xFB,0xDB,0x02,0x54,0x0B, + 0x96,0x0C,0xC8,0x07,0xD2,0x01,0x3A,0xFB,0xBC,0xFA,0x93,0xFF, + 0xB3,0x02,0xE9,0x01,0xC6,0xFB,0x82,0xF6,0x97,0xF3,0x63,0xF7, + 0x0D,0x00,0xBB,0x05,0xDB,0x07,0x11,0x04,0xCE,0xFF,0xD8,0xFE, + 0xFA,0xFF,0x00,0x03,0x79,0x03,0x36,0x00,0x17,0xFB,0xFC,0xF7, + 0xD6,0xF9,0x5F,0xFC,0x04,0x00,0xA0,0x02,0xCD,0x00,0x25,0xFF, + 0xFF,0xFE,0xDA,0xFE,0xF0,0x01,0x23,0x06,0xA7,0x06,0xA1,0x11, + 0xC8,0x1E,0xB4,0x13,0x72,0x03,0xB4,0xF1,0x7A,0xDD,0xFA,0xDC, + 0xA7,0xE7,0x80,0xF5,0xEF,0xFE,0x53,0x03,0x72,0x03,0xBE,0x02, + 0x42,0x0B,0x73,0x0F,0x18,0x0E,0x0E,0x0B,0x37,0xFE,0xFF,0xF2, + 0xA0,0xF3,0xAE,0xF8,0x61,0xFF,0x88,0x04,0x49,0x06,0x3B,0x02, + 0x21,0x00,0xEA,0x04,0x4B,0x06,0xB3,0x07,0xE6,0x05,0xEA,0xFD, + 0x1E,0xF9,0x3F,0xF9,0x9F,0xFF,0xC2,0x06,0x10,0x08,0x91,0x05, + 0xF4,0xFE,0x84,0xFB,0xEB,0xFB,0xB4,0xFB,0x1F,0xFE,0x76,0xFC, + 0x19,0xFA,0xF7,0xFB,0x49,0xFF,0x35,0x04,0x2D,0x06,0xAF,0x05, + 0xB7,0x01,0xF7,0xFC,0xAC,0xFC,0x99,0xFC,0x25,0xFE,0x31,0xFF, + 0xFC,0xFC,0x5A,0xFD,0xD3,0xFD,0x46,0xFE,0x69,0x00,0xF8,0x02, + 0xE5,0x02,0xB0,0xFF,0xF4,0x00,0x07,0x00,0x78,0xFE,0x91,0x01, + 0x87,0x02,0x9F,0x12,0xE1,0x21,0x60,0x11,0xB0,0xFE,0x5C,0xEC, + 0x38,0xDD,0x1C,0xE5,0xB6,0xF1,0x1B,0xFC,0xD4,0xFC,0xAE,0xFC, + 0x71,0x01,0x62,0x06,0x0B,0x12,0x57,0x11,0xA6,0x05,0xF8,0xFD, + 0xC6,0xF4,0xF5,0xF4,0x8A,0xFE,0x16,0x03,0x0B,0x03,0x15,0xFF, + 0xAB,0x00,0x33,0x03,0x8E,0x06,0x24,0x0C,0x2A,0x05,0xBA,0xFD, + 0xF9,0xF9,0xDA,0xF8,0x61,0xFE,0x2E,0x02,0x55,0x06,0x84,0x06, + 0x06,0x02,0x3C,0x02,0x69,0x01,0x39,0x01,0xB9,0xFF,0x3F,0xF9, + 0xA2,0xF6,0xAA,0xF6,0x32,0xFB,0x67,0x01,0x84,0x04,0xFD,0x04, + 0x66,0x01,0xE0,0x00,0x93,0x01,0xB0,0x00,0x80,0x00,0x7C,0xFD, + 0x75,0xFB,0xB6,0xFB,0xA1,0xFC,0x91,0xFF,0x62,0x00,0xB9,0xFF, + 0x99,0xFF,0xE8,0xFF,0x83,0x01,0xFA,0x01,0x28,0x02,0x8B,0x00, + 0x9C,0xFE,0xBF,0xFD,0x6C,0xFC,0xB3,0x0C,0xC5,0x24,0x13,0x1A, + 0xDC,0x00,0xB2,0xF0,0xBC,0xDF,0x4D,0xE5,0xFC,0xF4,0xE3,0xF9, + 0x46,0xF8,0x77,0xF6,0xC3,0xFE,0xD6,0x08,0x4C,0x14,0x28,0x13, + 0xD6,0x01,0xE5,0xF9,0x45,0xF8,0x93,0xF9,0xB8,0x02,0xCD,0x03, + 0x7B,0xFD,0x3C,0xFA,0x27,0x00,0xA5,0x08,0x95,0x09,0xEC,0x09, + 0x57,0x01,0xD1,0xF8,0xDF,0xFB,0x57,0xFF,0x72,0x02,0x8F,0x01, + 0x57,0xFE,0x26,0xFE,0x95,0x03,0x51,0x0D,0x23,0x0A,0x84,0xFF, + 0x6F,0xFA,0xAC,0xF4,0x07,0xF7,0xF8,0xFC,0x19,0xFD,0x9B,0xFC, + 0x62,0xFD,0x18,0x02,0x83,0x05,0x39,0x07,0xEA,0x03,0x92,0xFC, + 0x25,0xFC,0x71,0xFD,0x81,0xFE,0xAE,0x00,0x54,0xFE,0xDA,0xFB, + 0xA0,0xFC,0x2F,0x00,0xB9,0x02,0xFF,0x00,0x7A,0xFF,0x83,0xFE, + 0xAC,0x00,0x54,0x03,0xE9,0x01,0x32,0x00,0x00,0xFE,0x2A,0xF9, + 0x8D,0xFC,0x98,0x17,0x16,0x29,0x76,0x10,0x57,0xF9,0x90,0xEB, + 0x3C,0xE1,0x36,0xF0,0xF0,0xF9,0x75,0xF5,0x75,0xF0,0x8D,0xF6, + 0x41,0x06,0x14,0x11,0xC9,0x16,0x1A,0x08,0x25,0xF7,0xD6,0xFA, + 0x54,0xFE,0xC8,0x01,0x39,0x04,0x66,0xFC,0x2F,0xF7,0x94,0xFC, + 0x4F,0x0A,0x30,0x0C,0x45,0x06,0x10,0x02,0xAC,0xF9,0x76,0xFC, + 0x6A,0x03,0xD0,0x02,0x12,0xFF,0x58,0xFB,0xC9,0xFD,0xAF,0x02, + 0xE4,0x09,0xB8,0x0E,0x9A,0x03,0x3E,0xF8,0x5A,0xF9,0xA9,0xF9, + 0xA7,0xFB,0x32,0xFD,0xE0,0xF8,0x9E,0xF9,0xA8,0x00,0x12,0x07, + 0x76,0x07,0xD5,0x03,0x6A,0xFE,0x8C,0xFA,0x49,0xFF,0x10,0x02, + 0xE7,0xFD,0x52,0xFD,0x7C,0xFC,0x4E,0xFD,0x39,0x01,0x51,0x02, + 0xA0,0x00,0xFA,0xFC,0x0A,0xFE,0x7D,0x00,0xA6,0x02,0xAA,0x03, + 0xAE,0xFE,0x9D,0xFE,0x23,0x00,0xFC,0xFD,0xFB,0xFB,0x3D,0x04, + 0xE2,0x20,0x41,0x20,0x64,0x02,0xE0,0xF6,0xDE,0xE8,0x53,0xE9, + 0x9A,0xF6,0x51,0xF4,0xFD,0xEF,0xC0,0xF0,0x79,0x01,0x6D,0x0E, + 0x8A,0x11,0x5E,0x0D,0xCF,0xFA,0x50,0xFA,0xCD,0x03,0x75,0x02, + 0x8E,0x01,0x71,0xFB,0x8A,0xF8,0x98,0xFC,0x92,0x07,0x4E,0x0D, + 0xE9,0x02,0xB9,0xFF,0x50,0xFE,0x43,0xFF,0xAA,0x04,0x9C,0x01, + 0xAB,0xFC,0x3D,0xFA,0x73,0xFF,0xBA,0x04,0xB2,0x07,0x78,0x0A, + 0xAA,0x00,0x5B,0xFC,0x06,0x00,0x3F,0xFD,0x91,0xFB,0x57,0xF9, + 0x07,0xF8,0x56,0xFA,0x3D,0x01,0xAD,0x05,0x5E,0x02,0xD6,0x01, + 0x34,0x00,0xEA,0xFE,0xE3,0x01,0xA1,0xFF,0x01,0xFC,0xC2,0xFC, + 0xDF,0xFE,0x7C,0xFF,0x6A,0x00,0xA3,0x00,0x18,0xFE,0xC0,0xFE, + 0x6D,0x00,0x61,0xFF,0x28,0x00,0xBA,0x01,0x9D,0x00,0x07,0x00, + 0x3A,0x01,0x89,0x00,0x69,0xFE,0x7B,0xFE,0x04,0xFC,0xA1,0x02, + 0xC4,0x20,0x18,0x20,0x4A,0xFE,0xEB,0xF5,0xFB,0xEE,0x67,0xED, + 0x5F,0xF7,0x70,0xF2,0x8C,0xEC,0x52,0xF1,0xDA,0x05,0x6C,0x0F, + 0xB4,0x0B,0x44,0x08,0x0A,0xFA,0x03,0xFE,0x88,0x09,0x6E,0x03, + 0x99,0xFC,0xC2,0xF8,0xFF,0xFC,0x3E,0x01,0x40,0x08,0x0F,0x09, + 0xE1,0xFC,0xB2,0xFE,0x43,0x03,0x57,0x03,0x47,0x02,0xC8,0xFD, + 0x9D,0xFB,0x72,0xFD,0xEC,0x04,0xC8,0x04,0xC7,0x00,0x5D,0x02, + 0x07,0x05,0x17,0x07,0x12,0x00,0x76,0xFA,0xD1,0xF7,0x2B,0xF7, + 0xF7,0xFD,0xE1,0xFD,0xDF,0xFC,0x04,0xFF,0x69,0x01,0x90,0x05, + 0x69,0x04,0x2B,0x00,0xD9,0xFB,0x15,0xFD,0x0A,0x01,0xB5,0xFF, + 0x79,0xFE,0x39,0xFD,0x1E,0xFE,0x1C,0x01,0xCC,0x01,0x72,0x00, + 0x13,0xFD,0x75,0xFD,0xA1,0x00,0x30,0x02,0x97,0x01,0x3F,0xFF, + 0x47,0xFF,0x02,0x01,0x86,0x02,0x2C,0x00,0xBA,0xFD,0x12,0xFC, + 0x43,0xFE,0xBD,0x1A,0x97,0x23,0xB6,0x01,0xE1,0xF5,0xD4,0xF3, + 0xC0,0xF0,0x57,0xF7,0xEA,0xF1,0x02,0xEB,0xBC,0xEE,0x54,0x04, + 0x58,0x0F,0xF2,0x07,0xF3,0x05,0xA4,0xFC,0x3D,0x00,0xB5,0x0B, + 0x69,0x04,0x68,0xFA,0xFD,0xF7,0x58,0x00,0x73,0x03,0x0F,0x05, + 0xF6,0x04,0xE7,0xFB,0xF6,0xFF,0x82,0x06,0xF5,0x04,0x4D,0xFF, + 0xCA,0xFB,0x34,0xFE,0x44,0x00,0xAA,0x04,0x56,0x02,0xE7,0xFD, + 0xDC,0x01,0x28,0x04,0xE0,0x06,0x2A,0x06,0x74,0xFA,0x98,0xF6, + 0x29,0xFC,0x5F,0xFC,0x78,0xFC,0xF4,0xFB,0xA8,0xFC,0xCF,0x00, + 0x0F,0x05,0x50,0x05,0x7C,0xFE,0xCB,0xFD,0x0B,0xFF,0xCE,0xFE, + 0x37,0x01,0xA2,0xFD,0x0F,0xFC,0x85,0xFF,0x6C,0x02,0x6F,0x01, + 0xC5,0xFE,0xEA,0xFE,0x04,0xFE,0x48,0x00,0xBE,0x01,0xD1,0xFE, + 0xAA,0xFE,0x86,0x01,0x93,0x01,0x50,0xFF,0xEA,0x00,0xC3,0x00, + 0xC4,0xFE,0x92,0x00,0xDF,0xFC,0xFD,0x04,0x8D,0x21,0xCE,0x16, + 0x51,0xF8,0xFA,0xF7,0x83,0xF5,0x8E,0xF4,0x4A,0xF4,0xFB,0xED, + 0xB4,0xEB,0xE3,0xF5,0x40,0x0C,0xAF,0x09,0x44,0x02,0x50,0x03, + 0x05,0x00,0xB9,0x08,0xB1,0x08,0x9C,0xFE,0x2B,0xF8,0xCF,0xFD, + 0x77,0x06,0xD9,0x00,0x7A,0x00,0xC3,0xFF,0x3D,0xFF,0x3D,0x05, + 0xA7,0x05,0x2E,0x00,0xAB,0xFB,0x56,0x00,0xF5,0x02,0x30,0x01, + 0x96,0x00,0x04,0xFF,0xD1,0x00,0x46,0x04,0xF8,0x03,0x81,0xFF, + 0x5E,0xFD,0xE8,0xFE,0xC8,0xFE,0xEC,0xFD,0xBA,0xFB,0x17,0xFC, + 0x67,0xFC,0xCD,0x01,0x7D,0x19,0xB2,0x0A,0x91,0xE7,0x32,0xF4, + 0xDA,0xFD,0xED,0xFB,0x5F,0xFB,0x99,0xF7,0xFB,0xFA,0x8D,0x04, + 0xE3,0x12,0x6D,0x05,0x24,0xF9,0xD6,0xFE,0x1A,0xFD,0x11,0x04, + 0x1B,0x02,0x89,0xF9,0xA5,0xFA,0x17,0x05,0xA5,0x08,0xB1,0xFB, + 0x59,0xFE,0xF0,0x02,0x3C,0x01,0x05,0x02,0xB6,0xFF,0x7F,0xFE, + 0x8C,0xFC,0x06,0x00,0x85,0xFF,0xE9,0x12,0x48,0x20,0x98,0x00, + 0x68,0xF9,0x53,0xFF,0x64,0xF8,0x22,0xF2,0xE4,0xE9,0x4C,0xEC, + 0xB7,0xEF,0x0A,0x00,0x8F,0x09,0xE3,0xFF,0x10,0x04,0x0E,0x08, + 0x08,0x0A,0x9D,0x08,0x96,0x00,0xB6,0xFC,0x25,0xFD,0x13,0x04, + 0x00,0x00,0x0F,0xFA,0xF0,0xFE,0x65,0x02,0xC8,0x03,0xD6,0x02, + 0x20,0x02,0xDB,0x00,0xBA,0x01,0xA2,0x04,0x33,0x01,0xEC,0xFD, + 0x8A,0xFF,0xD7,0x01,0x5A,0x02,0x9D,0xFF,0x53,0xFF,0x8C,0x00, + 0x26,0x00,0xB6,0xFF,0x0B,0xFD,0xD3,0xFE,0xEC,0xFF,0x18,0xFE, + 0xD5,0xFE,0x05,0xFE,0x8D,0xFF,0x2D,0xFF,0x48,0xFF,0x92,0x00, + 0x9F,0xFE,0x13,0xFF,0x90,0xFF,0x1D,0xFF,0xB2,0xFC,0xF9,0xFC, + 0x7C,0xFF,0x42,0xFF,0x9F,0x00,0xD7,0x00,0xBC,0x00,0xBE,0x01, + 0x92,0x02,0x59,0x01,0xC6,0xFE,0xAF,0xFF,0x94,0xFF,0xB8,0xFE, + 0x13,0x00,0xC4,0xFF,0xC7,0xFE,0x59,0x00,0xA4,0x02,0xFE,0x01, + 0x95,0xFF,0xC2,0x00,0x9E,0x02,0x28,0x01,0xC5,0xFE,0xD8,0xFE, + 0x01,0xFC,0xE9,0x06,0x2B,0x1D,0xF2,0x08,0x37,0xF4,0xC9,0xFF, + 0xD7,0x00,0xF3,0xF7,0xE1,0xEC,0x98,0xEE,0x64,0xF3,0x4B,0xFC, + 0x40,0x07,0xF2,0xFD,0x07,0xFE,0x67,0x07,0x12,0x0A,0x2E,0x05, + 0x3F,0xFF,0xB4,0x00,0xE2,0x00,0x31,0x04,0x8C,0x01,0x9D,0xFA, + 0xF8,0xFD,0x1D,0x05,0x0E,0x04,0xEC,0xFD,0x88,0xFF,0x15,0x03, + 0xD9,0x02,0xAF,0x01,0xE0,0xFF,0xCC,0xFD,0xB3,0x00,0x39,0x04, + 0x79,0x01,0x1A,0xFE,0x09,0x00,0x17,0x03,0x5D,0x00,0x63,0xFD, + 0x6C,0xFC,0xC1,0xFC,0x67,0xFE,0xF0,0xFE,0xB0,0xFD,0x8F,0xFD, + 0xFD,0x00,0x33,0x03,0x0F,0x00,0xE7,0xFF,0xFC,0xFD,0x9D,0x04, + 0x41,0x16,0x88,0xFD,0x43,0xE7,0xB9,0xFB,0xE5,0x02,0xA3,0xF7, + 0xDD,0xF0,0x60,0xFC,0x80,0x04,0x09,0x07,0xD9,0x0A,0x44,0x01, + 0x2E,0xFE,0x4E,0x05,0xD0,0x04,0xA7,0xFB,0xD7,0xFA,0x10,0x01, + 0xB0,0x03,0x82,0x02,0x6C,0xFF,0x3B,0xFE,0xDE,0x01,0x2E,0x07, + 0x8F,0xFF,0x54,0xF9,0x38,0x00,0x56,0x05,0x2E,0x00,0x19,0xFB, + 0xED,0xFC,0x13,0x00,0x8D,0x01,0x38,0x0E,0xC9,0x14,0x52,0xFC, + 0x63,0xFB,0xC0,0x08,0x1A,0xFE,0x22,0xEE,0xB4,0xEB,0xE8,0xF6, + 0x15,0xF8,0x54,0xFB,0xA7,0xFD,0x88,0xFD,0x50,0x04,0x81,0x09, + 0x1C,0x06,0x45,0xFF,0x80,0x05,0x65,0x07,0x9E,0x03,0xA8,0xFF, + 0x82,0xFD,0x64,0xFF,0xE7,0x00,0xD3,0x01,0x2E,0xFC,0xB7,0xFD, + 0xF1,0x03,0x76,0x04,0xCB,0xFF,0xED,0xFE,0xAE,0x01,0x0C,0x02, + 0x3E,0x02,0x72,0xFF,0xE9,0xFF,0x63,0x01,0xA8,0x02,0x50,0x00, + 0x55,0xFD,0x61,0xFE,0xC9,0xFE,0x78,0xFE,0xD2,0xFC,0x39,0xFD, + 0x49,0xFE,0xE6,0xFF,0xD8,0xFF,0x4A,0xFF,0xAD,0xFF,0x36,0x01, + 0xA9,0x01,0x23,0x01,0x68,0x00,0x60,0x01,0xFD,0x02,0xCD,0xFB, + 0x60,0xFC,0x2B,0xFF,0x5F,0xFD,0x3E,0xFC,0x70,0xFC,0xCA,0x00, + 0x05,0x00,0x31,0x01,0x1A,0x01,0xE4,0x01,0x51,0x02,0x42,0x00, + 0x5D,0x01,0x27,0x00,0xA9,0x00,0xB5,0xFF,0x26,0x00,0x9E,0x00, + 0xF9,0xFF,0x4B,0x01,0x2E,0x01,0xC6,0x00,0xFE,0xFF,0x0E,0x01, + 0x17,0x01,0xF3,0xFF,0xB1,0xFF,0x26,0x00,0x80,0x00,0xFC,0xFF, + 0xA0,0xFF,0xD0,0xFF,0x5A,0x00,0x53,0x00,0xDC,0xFF,0x89,0xFF, + 0xB7,0xFF,0xDF,0xFF,0x05,0x00,0x38,0xFF,0x63,0x01,0xE8,0x02, + 0x30,0x00,0x89,0xFF,0x44,0x00,0xC6,0xFF,0x87,0xFD,0x42,0xFD, + 0xD0,0xFD,0x3D,0xFD,0xF3,0xFD,0xD0,0xFE,0x0B,0xFF,0x39,0xFF, + 0x50,0x00,0x0E,0x01,0x3B,0x01,0xA5,0x01,0x08,0x02,0xE6,0x01, + 0xA0,0x01,0x78,0x01,0xB1,0x00,0x50,0x00,0x28,0x00,0x74,0xFF, + 0x65,0xFF,0xD5,0xFF,0xB0,0xFF,0x57,0xFF,0xE6,0xFF,0xA1,0x00, + 0x5B,0x00,0x71,0x00,0x23,0x01,0x4A,0x01,0xA2,0x00,0x6E,0x00, + 0x5E,0x00,0x86,0xFF,0x3A,0xFF,0x06,0xFF,0x9A,0xFE,0x92,0xFE, + 0x1E,0xFF,0x41,0xFF,0x29,0xFF,0xA2,0xFF,0xF7,0xFF,0x0E,0x00, + 0x2C,0x00,0x5A,0x00,0x0C,0x00,0xF5,0xFF,0xF1,0xFF,0x71,0xFF, + 0xB0,0xFE,0x61,0xFE,0x4F,0xFE,0xB9,0xFE,0xFB,0xFF,0xC5,0x00, + 0x91,0x00,0x41,0x01,0x34,0x02,0xC0,0x01,0x91,0x00,0xF8,0xFF, + 0x51,0x01,0x93,0x03,0x1B,0x0A,0xED,0x05,0x4D,0xFE,0x7A,0x03, + 0xD2,0x02,0x04,0xFB,0xC8,0xF3,0x0C,0xF8,0x25,0xFB,0x72,0xF7, + 0xFA,0xF8,0x1C,0xFC,0xC9,0x00,0xF8,0x00,0xC2,0x01,0x9A,0x02, + 0xAC,0x04,0x58,0x07,0xB1,0x04,0xB6,0x02,0x92,0x02,0x63,0x03, + 0x7E,0x00,0x00,0xFE,0x9E,0xFE,0x35,0xFF,0x3E,0xFF,0xDE,0xFE, + 0x1A,0xFF,0xDE,0xFF,0xF7,0x00,0x16,0x01,0x74,0x00,0x75,0x01, + 0x11,0x03,0x48,0x02,0x08,0x01,0xF4,0x00,0x36,0x01,0x7F,0xFF, + 0x16,0xFE,0xEC,0xFD,0xFC,0xFD,0x11,0xFE,0xC2,0xFD,0x06,0xFE, + 0x5A,0xFE,0x66,0xFF,0xAB,0xFF,0xBE,0xFF,0x7D,0x00,0x55,0x01, + 0x57,0x01,0xC1,0x00,0x89,0x00,0x2A,0x00,0xC4,0xFF,0xE0,0xFE, + 0x81,0xFE,0x8B,0xFE,0x99,0xFE,0x8C,0xFE,0xD7,0xFE,0x87,0xFF, + 0xD6,0xFF,0x32,0x00,0xD3,0x00,0xE9,0x00,0x0A,0x01,0x60,0x01, + 0x11,0x01,0x9D,0x00,0x6A,0x00,0x9D,0x00,0xFD,0xFF,0xBC,0xFF, + 0x17,0x00,0x0E,0x00,0xDA,0xFF,0xEB,0xFF,0x66,0x00,0x39,0x00, + 0x36,0x00,0x60,0x00,0x64,0x00,0x3D,0x00,0x16,0x00,0xFD,0xFF, + 0xD2,0xFF,0xD8,0xFF,0xCB,0xFF,0xA4,0xFF,0xBD,0xFF,0x06,0x00, + 0xFD,0xFF,0xEC,0xFF,0x01,0x00,0x28,0x00,0x1F,0x00,0x11,0x00, + 0x17,0x00,0x11,0x00,0x17,0x00,0x16,0x00,0x03,0x00,0xD8,0xFF, + 0xE9,0xFF,0x03,0x00,0xEF,0xFF,0xD9,0xFF,0xF2,0xFF,0x08,0x00, + 0x07,0x00,0x09,0x00,0x16,0x00,0x27,0x00,0x2C,0x00,0x2F,0x00, + 0x26,0x00,0x1D,0x00,0x13,0x00,0xF8,0xFF,0xD0,0xFF,0xCB,0xFF, + 0xCE,0xFF,0xB4,0xFF,0xB1,0xFF,0xD5,0xFF,0xE8,0xFF,0xE8,0xFF, + 0xFD,0xFF,0x18,0x00,0x1D,0x00,0x16,0x00,0x18,0x00,0x10,0x00, + 0x0A,0x00,0xFF,0xFF,0xEC,0xFF,0xD3,0xFF,0xD4,0xFF,0xDE,0xFF, + 0xCE,0xFF,0xCD,0xFF,0xE2,0xFF,0xFD,0xFF,0x01,0x00,0x03,0x00, + 0x1A,0x00,0x27,0x00,0x28,0x00,0x25,0x00,0x1D,0x00,0x1C,0x00, + 0x19,0x00,0x09,0x00,0xF4,0xFF,0xEE,0xFF,0xF9,0xFF,0xEC,0xFF, + 0xDF,0xFF,0xE8,0xFF,0xF8,0xFF,0xF8,0xFF,0xF8,0xFF,0x07,0x00, + 0x10,0x00,0x14,0x00,0x15,0x00,0x1B,0x00,0x1B,0x00,0x18,0x00, + 0x12,0x00,0x06,0x00,0x01,0x00,0xFC,0xFF,0xF2,0xFF,0xE7,0xFF, + 0xE6,0xFF,0xE8,0xFF,0xE8,0xFF,0xF0,0xFF,0xF9,0xFF,0x03,0x00, + 0x0D,0x00,0x15,0x00,0x16,0x00,0x17,0x00,0x1B,0x00,0x18,0x00, + 0x0F,0x00,0x09,0x00,0x07,0x00,0xFF,0xFF,0xF9,0xFF,0xF8,0xFF, + 0xF9,0xFF,0xF6,0xFF,0xF8,0xFF,0xFD,0xFF,0xFD,0xFF,0x03,0x00, + 0x06,0x00,0x08,0x00,0x08,0x00,0x0B,0x00,0x0B,0x00,0x06,0x00, + 0x03,0x00,0x00,0x00,0xFE,0xFF,0xF7,0xFF,0xF4,0xFF,0xF9,0xFF, + 0xF5,0xFF,0xF5,0xFF,0xFA,0xFF,0xF7,0xFF,0xFB,0xFF,0xFB,0xFF, + 0x03,0x00,0x02,0x00,0x04,0x00,0x07,0x00,0x07,0x00,0x05,0x00, + 0xFB,0xFF,0x0A,0x00,0xF6,0xFF,0x02,0x00,0xE7,0xFF,0xFC,0xFF, + 0xF0,0xFF,0x05,0x00,0xE4,0xFF,0x04,0x00,0x04,0x00,0x21,0x00, + 0x14,0x00,0xE9,0xFF,0x2E,0x00,0xDE,0xFE,0x8B,0x03,0xEC,0x02, + 0x3F,0xFE,0x02,0x00,0xDC,0x01,0x72,0xFE,0x27,0xF9,0xD6,0xFD, + 0xD3,0xFF,0x32,0xFD,0xA3,0xFC,0xE0,0x01,0x58,0x03,0x62,0xFF, + 0x14,0x00,0xF9,0x02,0xDC,0x03,0x71,0x00,0xBB,0x01,0x32,0x03, + 0x5A,0x02,0x74,0xFF,0xE6,0xFF,0x6E,0x01,0xB0,0xFF,0x38,0xFE, + 0xBD,0xFE,0x79,0x00,0x7B,0xFE,0x9C,0xFD,0x3E,0xFE,0x60,0xFF, + 0x61,0xFE,0x7C,0xFE,0x14,0x00,0x78,0x00,0xD7,0xFF,0xA3,0xFF, + 0x99,0x00,0x0F,0x00,0x97,0xFF,0xA1,0xFF,0x50,0x00,0x30,0x00, + 0x10,0x00,0x3A,0x00,0x49,0x00,0x24,0x00,0xF5,0xFF,0x32,0x00, + 0x52,0x00,0x87,0x00,0x84,0x00,0xB6,0x00,0xB1,0x00,0x88,0x00, + 0x93,0x00,0x63,0x00,0x4B,0x00,0x3F,0x00,0x45,0x00,0xEE,0xFF, + 0xD4,0xFF,0xF6,0xFF,0xBC,0xFF,0x8E,0xFF,0xF3,0xFF,0x0E,0x00, + 0xC9,0xFF,0x05,0x00,0x39,0x00,0x08,0x00,0xB6,0xFF,0x1E,0x00, + 0xFB,0xFF,0xB5,0xFF,0xBF,0xFF,0x06,0x00,0xC3,0xFF,0x87,0xFF, + 0xC9,0xFF,0xBD,0xFF,0x9C,0xFF,0x8B,0xFF,0xD0,0xFF,0xB9,0xFF, + 0xBC,0xFF,0xB3,0xFF,0xE1,0xFF,0xDE,0xFF,0xE0,0xFF,0xF0,0xFF, + 0x18,0x00,0x3A,0x00,0x2A,0x00,0x43,0x00,0x51,0x00,0x5C,0x00, + 0x40,0x00,0x4E,0x00,0x45,0x00,0x38,0x00,0x1F,0x00,0x31,0x00, + 0x25,0x00,0x02,0x00,0xF6,0xFF,0x0F,0x00,0x11,0x00,0xF6,0xFF, + 0xFB,0xFF,0x0B,0x00,0x08,0x00,0xF3,0xFF,0xFB,0xFF,0xFF,0xFF, + 0xFA,0xFF,0xEB,0xFF,0xFA,0xFF,0xFD,0xFF,0xFB,0xFF,0xF2,0xFF, + 0xF3,0xFF,0xF1,0xFF,0xFB,0xFF,0xF3,0xFF,0xEB,0xFF,0xFD,0xFF, + 0xFD,0xFF,0xF1,0xFF,0xF3,0xFF,0x06,0x00,0xF9,0xFF,0xEF,0xFF, + 0xFA,0xFF,0x08,0x00,0x06,0x00,0x02,0x00,0x11,0x00,0x10,0x00, + 0x08,0x00,0x0D,0x00,0x0F,0x00,0x07,0x00,0xFD,0xFF,0x02,0x00, + 0x06,0x00,0xFD,0xFF,0xF8,0xFF,0xFE,0xFF,0xFE,0xFF,0xFC,0xFF, + 0xFD,0xFF,0xFE,0xFF,0xFE,0xFF,0xFF,0xFF,0xFD,0xFF,0xF5,0xFF, + 0xF8,0xFF,0xF6,0xFF,0xF4,0xFF,0xF4,0xFF,0xFA,0xFF,0xF7,0xFF, + 0xF4,0xFF,0xF9,0xFF,0xFF,0xFF,0x00,0x00,0xF9,0xFF,0x00,0x00, + 0x04,0x00,0x03,0x00,0xFE,0xFF,0x03,0x00,0x07,0x00,0x02,0x00, + 0xFF,0xFF,0x06,0x00,0x09,0x00,0x01,0x00,0x03,0x00,0x05,0x00, + 0x08,0x00,0x05,0x00,0x06,0x00,0x07,0x00,0x06,0x00,0x06,0x00, + 0x04,0x00,0x05,0x00,0x04,0x00,0x04,0x00,0x02,0x00,0x03,0x00, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0xFE,0xFF,0xFE,0xFF,0xFC,0xFF, + 0xFC,0xFF,0xFB,0xFF,0xFA,0xFF,0xFC,0xFF,0xFB,0xFF,0xFA,0xFF, + 0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFD,0xFF,0x01,0x00,0x02,0x00, + 0xFF,0xFF,0x02,0x00,0x01,0x00,0x01,0x00,0x04,0x00,0x04,0x00, + 0x06,0x00,0x05,0x00,0x02,0x00,0x05,0x00,0x06,0x00,0x02,0x00, + 0x02,0x00,0x00,0x00,0x03,0x00,0xFE,0xFF,0xFC,0xFF,0xFE,0xFF, + 0xFE,0xFF,0x00,0x00,0xFF,0xFF,0xFC,0xFF,0xFD,0xFF,0xFF,0xFF, + 0xFD,0xFF,0xFE,0xFF,0xFC,0xFF,0xFE,0xFF,0xFE,0xFF,0xFE,0xFF, + 0xFD,0xFF,0xFE,0xFF,0xFD,0xFF,0xFB,0xFF,0xFD,0xFF,0xFE,0xFF, + 0x00,0x00,0x00,0x00,0xFE,0xFF,0xFF,0xFF,0x01,0x00,0x01,0x00, + 0xFE,0xFF,0xFE,0xFF,0xFF,0xFF,0x02,0x00,0x02,0x00,0x01,0x00, + 0x02,0x00,0x01,0x00,0x05,0x00,0x02,0x00,0x02,0x00,0x04,0x00, + 0x00,0x00,0x03,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xFD,0xFF,0xFE,0xFF,0x01,0x00, + 0xFD,0xFF,0xFF,0xFF,0xFE,0xFF,0xFE,0xFF,0x00,0x00,0x00,0x00, + 0x02,0x00,0xFF,0xFF,0xFF,0xFF,0x02,0x00,0x02,0x00,0x01,0x00, + 0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x01,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00, + 0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x01,0x00, + 0xFF,0xFF,0x00,0x00,0x02,0x00,0x01,0x00,0xFE,0xFF,0xFE,0xFF, + 0x01,0x00,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0xFD,0xFF,0xFF,0xFF, + 0x00,0x00,0x00,0x00,0x02,0x00,0xFE,0xFF,0x00,0x00,0x03,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x01,0x00,0xFE,0xFF, + 0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFE,0xFF,0xFE,0xFF,0xFD,0xFF, + 0x00,0x00,0xFE,0xFF,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0x01,0x00, + 0xFF,0xFF,0x01,0x00,0x01,0x00,0xFE,0xFF,0x01,0x00,0x02,0x00, + 0x02,0x00,0x00,0x00,0xFF,0xFF,0x02,0x00,0x02,0x00,0xFF,0xFF, + 0xFF,0xFF,0x02,0x00,0xFF,0xFF,0xFE,0xFF,0xFE,0xFF,0x01,0x00, + 0xFF,0xFF,0xFE,0xFF,0x01,0x00,0xFF,0xFF,0xFE,0xFF,0xFF,0xFF, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0xFF,0xFF,0xFE,0xFF,0x01,0x00, + 0x01,0x00,0x01,0x00,0x00,0x00,0xFE,0xFF,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x02,0x00, + 0x01,0x00,0x01,0x00,0x01,0x00,0xFE,0xFF,0x02,0x00,0x02,0x00, + 0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x01,0x00,0x02,0x00,0xFF,0xFF,0xFB,0xFF,0x00,0x00, + 0x01,0x00,0xFE,0xFF,0xFF,0xFF,0xFE,0xFF,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0x00,0x00,0x01,0x00,0x02,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00, + 0x01,0x00,0xFF,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x02,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0xFE,0xFF, + 0x00,0x00,0x02,0x00,0x01,0x00,0xFE,0xFF,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0x01,0x00, + 0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFE,0xFF, + 0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xFF,0x01,0x00,0x01,0x00, + 0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0xFE,0xFF,0x00,0x00,0x01,0x00,0x02,0x00, + 0xFF,0xFF,0xFF,0xFF,0x02,0x00,0x01,0x00,0xFF,0xFF,0x01,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0xFF,0xFF, + 0x01,0x00,0x01,0x00,0x01,0x00,0xFC,0xFF,0xFE,0xFF,0xFF,0xFF, + 0xFE,0xFF,0x00,0x00,0x01,0x00,0x02,0x00,0xFF,0xFF,0xFF,0xFF, + 0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0xFE,0xFF, + 0xFF,0xFF,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00, + 0xFF,0xFF,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0xFF,0xFF,0xFE,0xFF,0x01,0x00,0x00,0x00,0xFF,0xFF, + 0x00,0x00,0x02,0x00,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00, + 0x02,0x00,0xF8,0xFF,0xF8,0xFF,0x0B,0x00,0x0A,0x00,0xFB,0xFF, + 0xF3,0xFF,0xFC,0xFF,0x05,0x00,0x03,0x00,0xFF,0xFF,0xFF,0xFF, + 0x02,0x00,0xFF,0xFF,0xFC,0xFF,0xFF,0xFF,0x05,0x00,0x02,0x00, + 0xFE,0xFF,0xFF,0xFF,0x03,0x00,0x00,0x00,0xFC,0xFF,0xFF,0xFF, + 0x03,0x00,0x02,0x00,0xFD,0xFF,0xFD,0xFF,0x01,0x00,0x02,0x00, + 0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x02,0x00,0x02,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0xFD,0xFF,0x31,0x00,0x49,0x00,0x0A,0x00, + 0xF4,0xFF,0x1B,0x00,0xF4,0xFF,0x95,0xFF,0xB4,0xFF,0xF5,0xFF, + 0xBD,0xFF,0x9B,0xFF,0xF9,0xFF,0x16,0x00,0xCC,0xFF,0xEC,0xFF, + 0x5E,0x00,0x4B,0x00,0x0A,0x00,0x50,0x00,0x8D,0x00,0x39,0x00, + 0x0B,0x00,0x4E,0x00,0x3B,0x00,0xD6,0xFF,0xD9,0xFF,0x19,0x00, + 0xE4,0xFF,0x9B,0xFF,0xD2,0xFF,0xF8,0xFF,0xB7,0xFF,0xAE,0xFF, + 0xFB,0xFF,0x00,0x00,0xCB,0xFF,0xF1,0xFF,0x2F,0x00,0x04,0x00, + 0xE0,0xFF,0x12,0x00,0x25,0x00,0xF3,0xFF,0xF3,0xFF,0x28,0x00, + 0x1C,0x00,0xF0,0xFF,0x0A,0x00,0x28,0x00,0xFF,0xFF,0xE8,0xFF, + 0x0A,0x00,0x12,0x00,0xEF,0xFF,0xF1,0xFF,0x14,0x00,0x06,0x00, + 0xEE,0xFF,0x03,0x00,0x15,0x00,0xFC,0xFF,0xF5,0xFF,0x0F,0x00, + 0x0E,0x00,0xFA,0xFF,0x00,0x00,0x16,0x00,0x08,0x00,0xF7,0xFF, + 0x09,0x00,0x12,0x00,0xFE,0xFF,0xF7,0xFF,0x0B,0x00,0x0A,0x00, + 0xF2,0xFF,0xF8,0xFF,0x08,0x00,0xF9,0xFF,0xEA,0xFF,0xFB,0xFF, + 0x04,0x00,0xEF,0xFF,0xEB,0xFF,0xFD,0xFF,0xFB,0xFF,0xEC,0xFF, + 0xF5,0xFF,0x09,0x00,0xFD,0xFF,0xF7,0xFF,0x0B,0x00,0x10,0x00, + 0xFE,0xFF,0xFF,0xFF,0x10,0x00,0x08,0x00,0xFA,0xFF,0x02,0x00, + 0x0D,0x00,0xFF,0xFF,0xFA,0xFF,0x09,0x00,0x05,0x00,0xF9,0xFF, + 0xFF,0xFF,0x0A,0x00,0x05,0x00,0xFD,0xFF,0x05,0x00,0x0A,0x00, + 0xFE,0xFF,0xFE,0xFF,0x07,0x00,0x05,0x00,0xFB,0xFF,0xFE,0xFF, + 0x03,0x00,0xFA,0xFF,0xF9,0xFF,0xFC,0xFF,0xFD,0xFF,0xF8,0xFF, + 0xFA,0xFF,0x00,0x00,0xFB,0xFF,0xF9,0xFF,0x00,0x00,0x02,0x00, + 0xFB,0xFF,0x00,0x00,0x05,0x00,0x00,0x00,0xFC,0xFF,0x02,0x00, + 0x05,0x00,0x00,0x00,0x03,0x00,0x05,0x00,0x05,0x00,0x01,0x00, + 0x01,0x00,0x05,0x00,0x02,0x00,0x00,0x00,0x04,0x00,0x03,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0xFE,0xFF,0xFF,0xFF, + 0x00,0x00,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0xFE,0xFF, + 0xFF,0xFF,0x01,0x00,0xFF,0xFF,0xFE,0xFF,0x01,0x00,0x02,0x00, + 0x02,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x02,0x00,0x01,0x00, + 0x03,0x00,0x01,0x00,0xFB,0xFF,0xFE,0xFF,0xFF,0xFF,0xFE,0xFF, + 0xFC,0xFF,0x00,0x00,0x00,0x00,0xFE,0xFF,0x00,0x00,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0x04,0x00,0xFE,0xFF,0x02,0x00,0xFA,0xFF, + 0x07,0x00,0x09,0x00,0xF9,0xFF,0xFF,0xFF,0xF8,0xFF,0x0B,0x00, + 0x08,0x00,0xFB,0xFF,0xFA,0xFF,0xFE,0xFF,0x08,0x00,0x05,0x00, + 0xF8,0xFF,0xFB,0xFF,0x04,0x00,0x04,0x00,0xFF,0xFF,0xF7,0xFF, + 0x03,0x00,0x07,0x00,0xFE,0xFF,0xFD,0xFF,0xFD,0xFF,0x03,0x00, + 0x02,0x00,0xFE,0xFF,0x01,0x00,0x01,0x00,0x03,0x00,0x02,0x00, + 0xFF,0xFF,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0xFD,0xFF,0xFF,0xFF,0x00,0x00, + 0xFE,0xFF,0xFE,0xFF,0xFE,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x02,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x02,0x00,0x00,0x00, + 0x00,0x00,0xFE,0xFF,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x02,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xFF, + 0xFE,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0xFE,0xFF, + 0x00,0x00,0x00,0x00,0xFE,0xFF,0x01,0x00,0x00,0x00,0x02,0x00, + 0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x03,0x00,0x02,0x00, + 0xFF,0xFF,0x00,0x00,0x00,0x00,0x01,0x00,0xFF,0xFF,0xFF,0xFF, + 0xFD,0xFF,0xFD,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFE,0xFF, + 0x00,0x00,0x00,0x00,0xFE,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0x04,0x00,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x01,0x00, + 0xFF,0xFF,0xFF,0xFF,0x01,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00, + 0xFD,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0x00,0x00,0x02,0x00,0xFF,0xFF,0xFF,0xFF,0x03,0x00, + 0x02,0x00,0x01,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0xFF,0xFF,0x02,0x00,0x00,0x00,0x02,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x01,0x00,0x02,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x01,0x00,0xFF,0xFF,0xFF,0xFF, + 0x00,0x00,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00, + 0x01,0x00,0xFF,0xFF,0xFF,0xFF,0x02,0x00,0xFD,0xFF,0xFE,0xFF, + 0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xFE,0xFF,0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0x03,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x02,0x00, + 0x01,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00, + 0xFD,0xFF,0x00,0x00,0x02,0x00,0xFF,0xFF,0x01,0x00,0xFE,0xFF, + 0xFD,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF, + 0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0xFF, + 0x01,0x00,0x02,0x00,0x01,0x00,0xFF,0xFF,0xFE,0xFF,0x02,0x00, + 0x03,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xFF,0xFE,0xFF, + 0x00,0x00,0xFE,0xFF,0xFE,0xFF,0xFE,0xFF,0x01,0x00,0x01,0x00, + 0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0x03,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x02,0x00,0x01,0x00, + 0xFE,0xFF,0x01,0x00,0x00,0x00,0xFE,0xFF,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0x01,0x00,0x01,0x00,0xFD,0xFF,0x01,0x00,0x01,0x00, + 0x01,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0xFE,0xFF,0x00,0x00, + 0x01,0x00,0x01,0x00,0xFE,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x01,0x00,0xFF,0xFF, + 0x02,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00, + 0xFE,0xFF,0xFF,0xFF,0x00,0x00,0x02,0x00,0x01,0x00,0x01,0x00, + 0xFF,0xFF,0xFE,0xFF,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0xFE,0xFF,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFE,0xFF, + 0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFE,0xFF,0xFE,0xFF,0xFF,0xFF, + 0x00,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0xFE,0xFF,0x01,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xFF, + 0xFE,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x02,0x00, + 0x01,0x00,0x00,0x00,0x01,0x00,0x02,0x00,0x02,0x00,0x02,0x00, + 0x00,0x00,0x01,0x00,0x01,0x00,0xFF,0xFF,0x01,0x00,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFE,0xFF,0x00,0x00,0xFE,0xFF,0x00,0x00,0x00,0x00,0x01,0x00, + 0x01,0x00,0xFE,0xFF,0x01,0x00,0x02,0x00,0x02,0x00,0xFF,0xFF, + 0xFD,0xFF,0x01,0x00,0x02,0x00,0x01,0x00,0x01,0x00,0xFE,0xFF, + 0xFE,0xFF,0xFF,0xFF,0x02,0x00,0x02,0x00,0x02,0x00,0x00,0x00, + 0xFF,0xFF,0x00,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x02,0x00, + 0x00,0x00,0x01,0x00,0x00,0x00,0xFD,0xFF,0x00,0x00,0x00,0x00, + 0x00,0x00,0xFD,0xFF,0xFD,0xFF,0xFF,0xFF,0xFD,0xFF,0x01,0x00, + 0xFC,0xFF,0xFF,0xFF,0x04,0x00,0x03,0x00,0x02,0x00,0x00,0x00, + 0xFA,0xFF,0xFE,0xFF,0x03,0x00,0x03,0x00,0x0A,0x00,0xFB,0xFF, + 0x06,0x00,0xEA,0xFF,0x02,0x00,0xED,0xFF,0x04,0x00,0x00,0x00, + 0x20,0x00,0x04,0x00,0x02,0x00,0x04,0x00,0x03,0x00,0x3B,0x00, + 0x86,0xFF,0x0E,0x00,0xB5,0xFF,0x70,0x02,0xF5,0x03,0x0D,0x00, + 0xAE,0xFB,0x56,0xF9,0x07,0xFC,0xFF,0x00,0x80,0x04,0x5D,0x04, + 0x8B,0x01,0x37,0xFE,0x7F,0xFD,0xF2,0xFE,0x78,0x01,0xA9,0x02, + 0xFE,0x01,0x49,0x00,0xDF,0xFE,0xD3,0xFE,0x6F,0xFF,0x58,0x00, + 0x68,0x00,0x26,0x00,0x4B,0xFF,0x14,0xFF,0x3C,0xFF,0xC1,0xFF, + 0x19,0x00,0x32,0x00,0x24,0x00,0xE2,0xFF,0x16,0x00,0x21,0x00, + 0x77,0x00,0x3D,0x00,0x0D,0x00,0xDF,0xFF,0xE4,0xFF,0x19,0x00, + 0x26,0x00,0x24,0x00,0xD7,0xFF,0xC6,0xFF,0xA4,0xFF,0xEE,0xFF, + 0x05,0x00,0x25,0x00,0x12,0x00,0xDF,0xFF,0xE0,0xFF,0xE2,0xFF, + 0x2B,0x00,0x21,0x00,0x56,0x00,0x42,0x00,0xFB,0xFF,0xC0,0xFF, + 0xBC,0xFF,0x07,0x00,0x39,0x00,0x46,0x00,0x0A,0x00,0xE0,0xFF, + 0xDD,0xFF,0x03,0x00,0xFA,0xFF,0x1C,0x00,0x06,0x00,0xEF,0xFF, + 0xDE,0xFF,0xE4,0xFF,0x1B,0x00,0x00,0x00,0xFB,0xFF,0xD5,0xFF, + 0xFF,0xFF,0x23,0x00,0x39,0x00,0x12,0x00,0xE8,0xFF,0xE9,0xFF, + 0xF4,0xFF,0x2C,0x00,0x07,0x00,0xD7,0xFF,0xC2,0xFF,0xEB,0xFF, + 0x1B,0x00,0x3B,0x00,0x36,0x00,0x13,0x00,0xE7,0xFF,0xC1,0xFF, + 0x0D,0x00,0x16,0x00,0xF0,0xFF,0x1F,0x00,0xEA,0xFF,0xA0,0xFF, + 0xD9,0xFF,0x05,0x00,0x2C,0x00,0xEE,0xFF,0xE0,0xFF,0x2A,0x00, + 0xE2,0xFF,0x3B,0x00,0x2C,0x00,0x11,0x00,0xF0,0xFF,0xCD,0xFF, + 0x41,0x00,0x00,0x00,0x1B,0x00,0xEC,0xFF,0x36,0x00,0x25,0x00, + 0x26,0xFF,0xDF,0xFF,0x61,0x00,0x4E,0x00,0xBE,0xFF,0x6A,0xFF, + 0x92,0x00,0x27,0x00,0xDB,0xFF,0x5B,0x00,0xD8,0xFF,0xF5,0xFF, + 0x03,0x00,0x1F,0x00,0x5A,0x00,0xA8,0xFF,0x67,0xFF,0x4D,0x00, + 0x1D,0x00,0xEB,0xFF,0x04,0x00,0x80,0xFF,0x3A,0x00,0x0F,0x00, + 0x33,0x00,0xFE,0xFF,0x81,0xFF,0x84,0x00,0x7F,0x00,0x9D,0x00, + 0xE2,0xFF,0xE5,0xFE,0x28,0x00,0x4E,0x00,0xCA,0xFF,0xFC,0xFF, + 0xB3,0xFF,0x60,0x00,0x12,0x00,0x98,0xFF,0x91,0xFF,0x70,0x00, + 0x1E,0x00,0x0C,0x00,0x74,0x00,0xC7,0xFF,0x0E,0x00,0xD3,0xFE, + 0x5E,0x00,0x5F,0x01,0xB4,0xFF,0x08,0xFF,0x05,0x00,0x2A,0x00, + 0xB2,0x00,0x32,0x00,0x0B,0xFF,0xF5,0x00,0x47,0xFF,0x12,0x00, + 0x15,0x01,0x0D,0xFF,0x83,0xFF,0x51,0x00,0x9C,0x00,0x2D,0xFF, + 0x9C,0xFF,0x03,0x00,0x9B,0xFF,0xEB,0x00,0x7F,0x00,0x7D,0xFF, + 0x0B,0xFF,0xE7,0x00,0x32,0x00,0x78,0x00,0x08,0x00,0x3B,0xFE, + 0x6B,0x02,0xA2,0xFF,0xC9,0xFE,0x36,0x01,0x76,0xFF,0xF5,0xFF, + 0xC2,0xFE,0xCD,0x00,0x63,0x00,0xA5,0xFE,0xC4,0x01,0xFA,0xFF, + 0x13,0x00,0x6D,0xFF,0x0B,0xFF,0x41,0x01,0x23,0x00,0x04,0x00, + 0x5B,0x00,0xF0,0xFD,0x7E,0x00,0x4E,0x02,0xB9,0xFE,0x60,0x00, + 0x02,0xFF,0x72,0xFF,0xE9,0xFF,0xF4,0x00,0x57,0x01,0x1E,0xFE, + 0x7D,0xFF,0x3B,0x00,0xE2,0x00,0x75,0x00,0x13,0xFF,0x8A,0xFF, + 0x77,0x01,0x5D,0xFF,0xAD,0xFF,0xAC,0x00,0x33,0xFF,0x91,0x01, + 0x17,0xFF,0x2A,0x00,0x5A,0x01,0x06,0xFE,0xFC,0xFF,0x2F,0x00, + 0x20,0x00,0xD4,0x00,0x8A,0xFE,0x7E,0xFF,0xD9,0x01,0x09,0xFE, + 0x64,0x00,0x71,0x02,0x96,0xFE,0x6C,0xFF,0x24,0x00,0xFC,0xFF, + 0xC4,0xFF,0xA6,0x01,0x4C,0xFF,0x5F,0xFF,0xB4,0x00,0xF1,0xFF, + 0xDF,0x00,0xD3,0xFE,0x73,0x00,0x67,0x00,0x34,0xFE,0xD1,0xFF, + 0x9E,0x02,0x6F,0xFE,0x7E,0xFF,0x7C,0x00,0x33,0xFF,0xD6,0x00, + 0xBE,0xFF,0x06,0x01,0xE8,0xFD,0x97,0x00,0xD9,0x00,0x52,0xFF, + 0x54,0x01,0xE5,0xFF,0xCC,0xFE,0xDB,0xFF,0xA7,0x01,0x04,0x00, + 0x20,0xFF,0x63,0xFF,0x96,0x00,0xC0,0xFE,0x26,0x01,0x22,0x01, + 0x00,0xFE,0xE7,0x00,0xE2,0xFF,0x95,0xFF,0x11,0x01,0xA5,0xFF, + 0xBA,0xFF,0xEB,0xFF,0xB8,0xFD,0xDA,0x01,0x4B,0x01,0x2D,0xFF, + 0x80,0x01,0x08,0xFD,0xD0,0x00,0xA7,0x02,0x6D,0xFE,0x3E,0xFF, + 0x36,0xFF,0x0E,0x02,0x35,0xFE,0x61,0x00,0x8B,0x01,0xF3,0xFD, + 0xB0,0x02,0xF8,0xFF,0x1B,0xFF,0xDE,0xFE,0x92,0x00,0x20,0x01, + 0x5F,0xFE,0x34,0x00,0x9A,0xFF,0x24,0xFF,0x0B,0x01,0x05,0x01, + 0xA3,0xFF,0xDD,0xFE,0x01,0x01,0x1C,0xFF,0x5C,0x00,0xA3,0x02, + 0xB2,0xFC,0x97,0x00,0x17,0x01,0x59,0x00,0x3F,0x00,0xDF,0xFE, + 0xDA,0xFF,0xD1,0xFF,0x7D,0x01,0x08,0xFF,0xA4,0xFD,0x88,0x00, + 0xB3,0x03,0x93,0xFD,0x14,0xFF,0xB3,0x00,0xAE,0xFF,0x4F,0x02, + 0x55,0xFF,0x21,0xFF,0x3C,0xFF,0x17,0x02,0x4C,0x00,0xBA,0xFE, + 0xD2,0xFD,0x3B,0x01,0xB3,0x02,0xC0,0xFC,0x26,0x01,0x57,0xFE, + 0x68,0x02,0xDB,0x00,0x6D,0xFC,0x98,0x02,0x89,0xFE,0x12,0x02, + 0x94,0xFF,0xD3,0xFE,0x6B,0x01,0x3A,0xFF,0xB9,0x00,0xE8,0xFF, + 0x01,0xFE,0xAE,0xFF,0x84,0x01,0x90,0x00,0x19,0x00,0x92,0xFD, + 0x3F,0x00,0x65,0x01,0x24,0x00,0x5B,0x00,0x44,0xFE,0x11,0x00, + 0xB6,0x02,0x24,0x00,0xC6,0xFE,0x81,0xFF,0xF3,0xFF,0x8B,0x00, + 0xC6,0xFF,0x4C,0x00,0x6D,0xFF,0x45,0xFF,0x40,0x01,0x9E,0x00, + 0xEF,0xFE,0x3E,0x00,0x1D,0xFF,0xEC,0xFF,0xA6,0x00,0x68,0x00, + 0x6A,0x00,0xF3,0xFE,0x5F,0x00,0xF6,0xFF,0x0E,0x00,0xAE,0xFF, + 0xF8,0x01,0x62,0xFC,0xA1,0xFF,0xDF,0x04,0x7E,0xFE,0x36,0x00, + 0xC6,0xFE,0x6E,0x00,0xB5,0xFF,0x5A,0xFF,0x8E,0x01,0x54,0xFC, + 0xAA,0x01,0x9B,0x02,0x9F,0xFD,0x09,0x00,0xE2,0x00,0x36,0xFF, + 0x91,0xFF,0x5D,0x02,0x21,0xFF,0xBA,0x00,0x8E,0xFE,0x41,0x01, + 0xDB,0x01,0xED,0xFB,0xAF,0x01,0x28,0xFE,0x7E,0xFF,0x44,0x03, + 0xE8,0xFD,0x3D,0x00,0x35,0x00,0x43,0xFF,0x9F,0x00,0xDE,0xFF, + 0x45,0x01,0xF9,0xFD,0xC2,0x01,0xF5,0x00,0x91,0xFD,0xA4,0x02, + 0xD0,0x00,0x12,0xFD,0x95,0xFF,0x6C,0x00,0xB4,0xFF,0x49,0x01, + 0x6E,0xFE,0xE2,0x00,0x5D,0x00,0xB6,0xFE,0x10,0x02,0x44,0xFE, + 0x25,0x01,0x20,0x01,0xFE,0xFD,0x06,0x00,0x25,0x02,0x16,0x01, + 0x82,0xFC,0xAA,0xFE,0x3D,0x00,0x04,0x03,0x4E,0xFF,0xBB,0x00, + 0x52,0xFE,0xC0,0xFF,0x6E,0x03,0x25,0xFD,0x67,0x00,0x0D,0xFE, + 0xFE,0x00,0x65,0x01,0x45,0xFF,0x33,0xFF,0xAB,0xFF,0xF8,0xFF, + 0x12,0x02,0xE5,0x00,0x7E,0xFD,0x88,0x00,0x25,0x00,0x0C,0x01, + 0x8C,0xFE,0xDE,0x00,0xF4,0xFF,0xC8,0xFF,0x2A,0x00,0xA7,0x00, + 0xA8,0xFF,0x9F,0xFE,0x42,0x00,0x79,0xFE,0xC3,0x02,0x77,0x00, + 0x94,0xFE,0xFF,0xFD,0x4E,0x03,0xBD,0xFE,0xF5,0xFE,0x1E,0x03, + 0x26,0xFE,0xA0,0x01,0x9A,0xFE,0x62,0x01,0x55,0xFE,0x51,0xFF, + 0xEE,0x01,0x78,0xFD,0x6F,0x01,0x99,0x00,0x6B,0xFF,0x25,0xFE, + 0x95,0x00,0xE2,0x00,0x6F,0xFE,0x79,0x02,0x46,0x00,0x8C,0xFE, + 0xC6,0x00,0xFF,0x00,0xE6,0xFF,0x47,0xFE,0x6E,0x01,0xD6,0xFF, + 0x6D,0xFF,0x2A,0x01,0x78,0xFD,0x46,0x01,0x53,0x00,0x1C,0xFF, + 0xC0,0xFF,0x5D,0x00,0x34,0x01,0x65,0xFE,0x60,0x00,0x74,0xFF, + 0xDD,0x00,0x9E,0x00,0x90,0x00,0xE0,0xFF,0xB1,0xFE,0xB0,0x01, + 0xAF,0xFF,0xB4,0xFD,0x22,0x01,0xA9,0xFF,0x8B,0x00,0xF6,0x01, + 0x55,0xFE,0x0E,0x01,0xD2,0xFD,0x40,0x00,0xB0,0x00,0x68,0xFE, + 0x70,0x00,0x8F,0xFE,0xFE,0x01,0xE8,0x00,0xB8,0x00,0xC9,0xFF, + 0xBB,0xFE,0x9F,0x01,0xED,0xFF,0xAC,0xFE,0xA5,0x00,0xF0,0x00, + 0x22,0xFF,0x98,0x00,0x5D,0x00,0x15,0xFD,0xA8,0x00,0x41,0xFF, + 0x3E,0xFF,0x45,0x02,0x0E,0xFD,0xF5,0x02,0x11,0x01,0x54,0xFF, + 0x23,0x01,0x85,0xFE,0x1F,0x00,0x98,0x00,0xB1,0xFF,0xB6,0xFE, + 0x3A,0x01,0xC6,0xFF,0x7D,0xFF,0x73,0x00,0x70,0x00,0x5D,0xFF, + 0xCF,0xFF,0x18,0x00,0x9D,0x00,0x0D,0xFF,0x35,0x01,0x4D,0xFF, + 0x49,0xFF,0xAC,0x01,0x44,0xFD,0xE8,0x02,0xD6,0xFF,0xC6,0xFE, + 0x66,0x01,0x45,0xFE,0x5F,0x01,0xE8,0xFE,0xD7,0xFE,0x74,0x01, + 0xF3,0xFE,0x35,0x00,0xCF,0xFF,0x5B,0x01,0xF7,0x00,0x85,0xFE, + 0x93,0x00,0x73,0xFF,0xF1,0xFF,0x9F,0xFF,0x49,0x00,0x25,0x00, + 0x2D,0x00,0x81,0x00,0x7A,0xFF,0x78,0x02,0x8D,0xFE,0x03,0xFE, + 0x46,0x01,0x54,0xFF,0xD8,0x00,0xB2,0xFE,0x05,0x00,0x32,0x01, + 0x2F,0xFE,0xB2,0x01,0x0D,0x01,0x93,0xFE,0xE2,0xFE,0x3C,0x02, + 0x0E,0x00,0xB8,0xFF,0xDA,0xFF,0x9E,0xFE,0x38,0x01,0x82,0xFF, + 0x43,0x00,0x5C,0xFF,0x9D,0xFE,0x4E,0x01,0x8B,0x01,0x40,0xFF, + 0x68,0xFF,0x9E,0xFF,0xA7,0xFF,0x69,0x01,0x86,0x00,0x9D,0xFE, + 0x5D,0x00,0x6D,0x00,0xCA,0x00,0x45,0xFF,0x55,0xFF,0xAF,0xFF, + 0x6D,0xFF,0x9C,0x00,0xC6,0x00,0x77,0x00,0x05,0xFE,0x6B,0x01, + 0x88,0x01,0xEE,0xFE,0xCD,0xFF,0xA1,0xFF,0x58,0xFF,0xCA,0x01, + 0x0E,0xFF,0xDC,0xFF,0xF2,0xFF,0x85,0x00,0x06,0x00,0xC4,0xFE, + 0x5B,0x01,0x72,0xFE,0x72,0x01,0x3B,0xFF,0x3D,0xFF,0x71,0x00, + 0x63,0x00,0x96,0xFF,0xEE,0xFF,0x96,0x01,0xDC,0xFE,0x15,0x01, + 0x3D,0xFF,0x08,0xFF,0xBD,0x00,0x8A,0x00,0x4D,0xFF,0x24,0x00, + 0xCF,0x00,0x87,0xFF,0xEC,0xFF,0x26,0x00,0x87,0x00,0x98,0xFE, + 0xD1,0x00,0xF3,0xFF,0x83,0xFF,0x26,0x00,0xF1,0xFD,0xCA,0x01, + 0xAB,0x00,0x55,0xFF,0x9B,0x00,0xB3,0xFF,0x1C,0x01,0xD1,0xFF, + 0x4B,0xFF,0xB8,0x00,0x7E,0xFF,0x8A,0x00,0x73,0xFF,0x12,0x00, + 0x90,0xFF,0x59,0xFF,0x1A,0xFF,0x66,0xFF,0xB5,0x02,0xD8,0xFE, + 0xE4,0xFF,0xE0,0x00,0x31,0x00,0xB4,0x00,0x38,0xFF,0xF0,0xFF, + 0xD9,0x00,0xA8,0xFF,0xA0,0x00,0xDE,0xFF,0x1A,0xFF,0x90,0xFF, + 0x92,0xFF,0x4E,0x00,0x23,0x00,0xE0,0xFE,0xD0,0xFF,0xA9,0x01, + 0xF9,0xFF,0x32,0x00,0x52,0xFF,0x6E,0x00,0x58,0x00,0xB9,0xFF, + 0x1A,0xFF,0xD6,0x01,0xBD,0xFE,0x70,0xFE,0x35,0x03,0x44,0xFF, + 0xB4,0x00,0xB5,0xFF,0x4E,0xFF,0xC7,0xFF,0xDC,0xFF,0xB4,0xFE, + 0x72,0xFF,0x71,0x01,0x1B,0xFF,0x9E,0x00,0x16,0x00,0x08,0x00, + 0xFC,0x00,0x07,0x00,0xF0,0xFF,0x3E,0xFF,0xDF,0x00,0xCC,0xFF, + 0x0E,0x01,0xB0,0xFF,0xCE,0xFD,0xA7,0x00,0xE0,0xFF,0x7F,0x00, + 0xBA,0xFF,0xBE,0xFF,0xAC,0x00,0x3E,0x00,0xFF,0xFF,0x1F,0x00, + 0x98,0xFF,0xC3,0x00,0x04,0xFF,0x5D,0x00,0x24,0x00,0x16,0x00, + 0x09,0x00,0x91,0xFE,0x0B,0x02,0x4C,0xFE,0x25,0x00,0x5A,0xFF, + 0xE7,0xFF,0x9C,0x01,0x5A,0xFE,0xE0,0x00,0xE1,0x00,0x61,0xFF, + 0x41,0x01,0xBE,0xFF,0x23,0xFF,0xF1,0x01,0x25,0xFE,0x52,0x00, + 0x3A,0xFF,0x5B,0x00,0x94,0x01,0xE8,0xFD,0x52,0x00,0xF7,0xFE, + 0x0A,0x01,0x22,0x01,0x13,0xFF,0x80,0xFF,0xC4,0xFF,0x73,0x00, + 0x6C,0x00,0xB8,0xFF,0xA3,0xFF,0x19,0x00,0x9E,0x00,0x13,0x00, + 0x27,0x00,0x9B,0x00,0x23,0xFE,0xB0,0x00,0xF4,0x00,0x00,0xFF, + 0xAC,0xFF,0xD9,0x00,0xD9,0xFF,0xDB,0xFF,0x1C,0x02,0xDD,0xFC, + 0x32,0x00,0xA6,0x00,0x2A,0xFF,0x45,0x02,0xE4,0xFD,0x88,0xFF, + 0x0E,0x01,0x94,0x00,0xD9,0xFF,0xE4,0xFF,0x3E,0x01,0x51,0xFE, + 0xFD,0x00,0xB0,0xFF,0x8E,0xFE,0x6C,0x01,0x88,0xFF,0x58,0x00, + 0xCA,0xFF,0x61,0x00,0x86,0x00,0x40,0xFF,0x33,0xFE,0x3D,0x01, + 0x22,0x00,0xE4,0xFF,0xB7,0x01,0xFE,0xFE,0x42,0x01,0xE8,0xFE, + 0xB8,0x00,0x1C,0xFE,0x23,0xFF,0x9E,0x01,0xF1,0xFE,0x84,0x02, + 0x78,0xFE,0x17,0x00,0x90,0xFF,0x60,0x00,0x39,0x00,0x88,0xFD, + 0xD6,0x01,0x80,0x00,0x85,0xFF,0x61,0x00,0x92,0x00,0xEB,0xFF, + 0xA0,0xFF,0xFB,0x00,0x5A,0xFF,0xD7,0xFF,0x87,0x00,0x96,0xFE, + 0x92,0x01,0xF3,0xFE,0xF8,0xFE,0x98,0x01,0x91,0xFE,0x46,0xFF, + 0x64,0x01,0xE4,0xFF,0x71,0x00,0x13,0x01,0x62,0xFF,0x1D,0x00, + 0x28,0x00,0x16,0x00,0x5C,0xFF,0x29,0x00,0x33,0x00,0xD0,0xFF, + 0x58,0xFF,0x1C,0x01,0x00,0xFF,0x54,0xFE,0xDB,0x01,0x3B,0xFF, + 0x56,0x00,0x74,0xFF,0x22,0x00,0xF2,0x01,0xCE,0xFE,0x33,0xFF, + 0xFD,0x00,0xFA,0x00,0x10,0x00,0xE1,0xFF,0x73,0xFF,0xC1,0xFE, + 0xA3,0x00,0xC5,0x00,0x1D,0xFF,0x09,0x00,0x13,0x00,0xB3,0xFF, + 0xCC,0x00,0x08,0x00,0xF5,0xFE,0x11,0x00,0xD1,0x00,0xB5,0x00, + 0xDD,0xFF,0x6A,0xFF,0xE2,0xFF,0x13,0x00,0x2D,0x00,0x43,0x01, + 0xFF,0xFE,0x69,0xFE,0x67,0x01,0x60,0xFF,0x60,0x00,0xCB,0xFF, + 0xF4,0xFE,0x93,0x00,0xF1,0xFF,0xA4,0x00,0x16,0xFF,0x8C,0x00, + 0xA6,0x00,0xA5,0xFF,0x4A,0xFF,0x50,0x00,0x71,0x00,0xD3,0xFF, + 0x12,0x00,0x1A,0x00,0x47,0x00,0x96,0xFF,0x3C,0x01,0x50,0xFE, + 0x0E,0x01,0x09,0xFF,0xAF,0xFF,0x3A,0x01,0x42,0xFF,0xE0,0x02, + 0x28,0xFE,0x9F,0xFF,0x65,0x00,0xB1,0xFF,0xE1,0xFF,0x5E,0xFF, + 0xAE,0x00,0x39,0xFF,0xF0,0xFF,0x90,0x01,0xEF,0xFD,0x5A,0x00, + 0x15,0x01,0x87,0xFE,0x6E,0x01,0xE9,0xFE,0xD3,0x00,0xF0,0xFF, + 0xAD,0xFF,0x0F,0x02,0x51,0xFE,0xA0,0xFF,0x7B,0x00,0x6C,0xFF, + 0xF1,0xFF,0x79,0xFF,0x7E,0x00,0x3E,0x00,0x61,0x00,0x63,0x00, + 0x83,0xFF,0x88,0x00,0xD0,0xFF,0xF3,0xFE,0x3D,0x00,0x63,0x00, + 0x07,0x00,0x47,0x00,0x46,0x00,0xB8,0x00,0x74,0xFF,0x3A,0xFF, + 0x34,0x00,0x20,0xFF,0x43,0x00,0x46,0x00,0x2D,0xFF,0x07,0x01, + 0x32,0x00,0xB1,0xFE,0xD7,0x00,0x1D,0x01,0x99,0xFF,0x08,0x00, + 0x97,0xFF,0xB5,0x00,0x80,0xFF,0x16,0x00,0xFE,0xFF,0xBC,0xFE, + 0xE6,0x00,0xAF,0xFF,0x9C,0xFF,0x89,0x00,0xC8,0x00,0x95,0xFF, + 0x18,0x00,0xEE,0xFF,0x9D,0xFF,0x28,0x00,0xA4,0xFF,0x64,0x00, + 0x1A,0x00,0x08,0x00,0x0B,0x00,0xB0,0xFF,0x01,0x00,0xE2,0xFF, + 0x1B,0x00,0x22,0x00,0xD0,0xFF,0xB2,0xFF,0x67,0x01,0xFE,0xFF, + 0x2D,0xFF,0x55,0x00,0x81,0xFE,0xCA,0x00,0x65,0x00,0xD3,0xFF, + 0xD5,0x00,0x54,0xFF,0x58,0x00,0xAA,0xFF,0x00,0xFF,0x1B,0x01, + 0xD0,0xFF,0x10,0x00,0x84,0xFF,0x15,0x00,0xD9,0x00,0xD5,0xFE, + 0x2E,0x01,0x40,0xFF,0x16,0x00,0x61,0x00,0x67,0xFF,0xD5,0x00, + 0x73,0xFF,0x9F,0x00,0x08,0x00,0xBA,0xFF,0x64,0xFF,0x18,0x00, + 0x4A,0x01,0x7E,0xFE,0xD4,0xFF,0x66,0x00,0xB6,0xFE,0x27,0x01, + 0x7B,0x00,0xD3,0xFF,0x4B,0x00,0x08,0x00,0xEA,0xFF,0x6D,0x00, + 0xA7,0xFF,0xB2,0xFF,0x53,0x00,0xDC,0xFF,0x0D,0x00,0x92,0xFF, + 0x75,0x00,0xD3,0xFF,0x23,0x00,0x9E,0xFF,0x30,0x00,0x43,0xFF, + 0xEC,0x00,0xDD,0xFF,0x07,0x00,0xF8,0xFF,0xBA,0xFE,0x28,0x02, + 0x54,0xFE,0x24,0x01,0xC0,0xFF,0xE7,0xFF,0xB2,0x00,0x17,0xFF, + 0xAD,0x00,0xF9,0xFE,0xAB,0x00,0xC1,0xFF,0xDD,0xFF,0xFC,0xFF, + 0x1C,0x00,0xB7,0xFF,0x7B,0x00,0xDC,0xFF,0x5D,0x00,0x87,0x00, + 0xD2,0xFE,0x85,0x01,0x4B,0xFF,0xB4,0xFF,0xEF,0xFF,0x0F,0x00, + 0x97,0xFF,0x30,0xFF,0x98,0x01,0xE8,0xFE,0xA1,0xFF,0xF2,0x00, + 0xB7,0xFF,0x82,0x00,0xC5,0xFF,0xCD,0xFF,0x38,0x00,0xE1,0xFF, + 0x6A,0x01,0x19,0xFF,0x41,0xFF,0xB8,0x00,0x98,0xFF,0xCB,0x00, + 0x77,0xFF,0x9A,0xFF,0x04,0x00,0xBC,0xFF,0xD0,0x00,0x01,0xFF, + 0x20,0x00,0x15,0x01,0x9B,0xFF,0xCE,0xFF,0xE5,0xFF,0x03,0x00, + 0x5D,0x00,0xBF,0xFF,0xF7,0xFF,0xB2,0x00,0x8C,0xFE,0x07,0x01, + 0xD9,0x00,0x37,0xFF,0xB6,0x00,0x08,0xFE,0xB0,0x00,0x5D,0x00, + 0x05,0xFF,0xA9,0x00,0x63,0xFF,0x06,0x01,0x8F,0xFF,0xFE,0xFF, + 0xDA,0x00,0x23,0xFF,0x75,0x00,0x2D,0x00,0xED,0xFF,0xDC,0xFF, + 0xE1,0xFF,0x5B,0x00,0x02,0x00,0x51,0xFF,0x95,0x00,0x4C,0x00, + 0x14,0xFF,0xA6,0x00,0xCC,0xFE,0xCD,0xFF,0xFB,0x01,0xD2,0xFE, + 0xE2,0xFF,0xA0,0x00,0x29,0xFF,0xB9,0x00,0xA6,0xFF,0x16,0x01, + 0x24,0x00,0xBD,0xFE,0xF5,0x01,0x44,0xFE,0x68,0x00,0x1F,0x01, + 0x2A,0xFD,0x9A,0x01,0x11,0xFF,0x4D,0x00,0x99,0x00,0x04,0xFF, + 0x48,0x02,0x00,0xFE,0x14,0x00,0x9D,0x00,0x58,0xFF,0x28,0x01, + 0x81,0xFF,0x3A,0xFF,0xC0,0x01,0x99,0xFE,0x2E,0x00,0x84,0x00, + 0xD6,0xFD,0x7E,0x02,0x4F,0xFE,0x13,0x01,0x19,0x00,0x85,0xFE, + 0xFA,0x02,0xFD,0xFD,0x24,0x00,0xAB,0x00,0x64,0xFE,0xE4,0x01, + 0xCE,0xFE,0xE2,0xFF,0xBA,0x01,0xAF,0xFD,0xEE,0x00,0x21,0x00, + 0xCE,0xFF,0x39,0x01,0xD9,0xFE,0xA1,0x00,0xCB,0xFF,0xC5,0xFE, + 0xE0,0x01,0xCD,0xFE,0x85,0xFF,0xE6,0x01,0x27,0xFE,0x3D,0x01, + 0x9E,0xFF,0x09,0xFF,0x6F,0x01,0x08,0xFE,0xAB,0x01,0x9B,0xFF, + 0x07,0xFF,0xC8,0x02,0xE0,0xFD,0x44,0x01,0xBF,0xFF,0x67,0xFE, + 0xDA,0x01,0x99,0xFE,0x73,0x00,0x4B,0x00,0x78,0xFF,0x63,0x00, + 0xF1,0xFF,0x17,0x00,0xF6,0xFF,0x53,0xFF,0x43,0x01,0x81,0xFF, + 0x54,0xFF,0x2A,0x01,0x11,0xFF,0x5F,0x00,0x1B,0x00,0xEA,0xFF, + 0x71,0x00,0x5C,0xFF,0xFB,0x00,0x25,0xFF,0x53,0xFF,0x20,0x01, + 0xC2,0xFE,0x51,0x01,0x5A,0xFF,0xBE,0xFF,0x81,0x00,0xFB,0xFE, + 0xEE,0x00,0xDE,0xFF,0xE5,0xFF,0x23,0x00,0x03,0x00,0x17,0x00, + 0x1F,0x00,0xD9,0xFF,0x49,0x00,0xD2,0xFF,0xC8,0xFF,0x1A,0x00, + 0xE4,0xFF,0x42,0x00,0xE1,0xFF,0x46,0x00,0x3D,0xFF,0xD5,0xFF, + 0xC8,0x00,0xDC,0xFF,0x8C,0x00,0xAC,0xFF,0x0E,0xFF,0xFC,0x00, + 0x56,0x00,0xCC,0xFE,0xA4,0x00,0x95,0xFF,0x47,0x00,0x49,0x00, + 0x4A,0xFF,0xC0,0x00,0x5A,0xFF,0x6B,0x00,0x4A,0x00,0x00,0xFF, + 0x55,0x00,0x9A,0x00,0xF2,0xFF,0x2B,0x00,0x25,0x00,0xCB,0xFF, + 0xE4,0xFF,0x68,0x00,0x77,0xFF,0x0B,0x00,0xA0,0xFF,0x4D,0xFF, + 0xEC,0x00,0xCD,0xFF,0x57,0x00,0x8E,0xFF,0x73,0x00,0x08,0x00, + 0x98,0xFF,0x0C,0x01,0xB1,0xFF,0xC3,0xFF,0x9B,0xFF,0x4E,0x00, + 0x0F,0x00,0x17,0x00,0x51,0x00,0xF7,0xFE,0xE5,0xFF,0x86,0x00, + 0xE6,0xFF,0xFD,0xFF,0xAE,0xFF,0xD5,0xFF,0x8E,0x00,0x98,0x00, + 0xDE,0xFF,0xD0,0xFF,0x35,0x00,0x36,0x00,0xA8,0xFF,0xF8,0xFF, + 0xDD,0xFF,0x9A,0xFF,0x71,0x00,0xC5,0xFF,0xE7,0xFF,0x71,0x00, + 0x6D,0xFF,0xA7,0xFF,0xDB,0x00,0xA3,0xFF,0x2B,0x00,0x2E,0x00, + 0x92,0xFF,0x04,0x00,0x11,0x00,0x14,0x01,0x8D,0xFF,0xFC,0xFE, + 0xC4,0x00,0xFB,0xFF,0x65,0x00,0xD8,0xFF,0xAE,0xFE,0xE9,0x00, + 0x8D,0xFF,0x7F,0x00,0xF1,0xFF,0xA6,0xFF,0xC7,0x00,0x84,0xFF, + 0x3A,0x00,0xE2,0xFF,0xE8,0xFF,0xD9,0xFF,0xDA,0xFF,0x7D,0x00, + 0x6A,0x00,0x5C,0xFF,0x52,0x00,0xFC,0xFF,0xEC,0xFF,0x3C,0x00, + 0x87,0xFF,0xE8,0xFF,0x1F,0x00,0xD4,0xFF,0x4B,0x00,0x01,0x00, + 0xD3,0xFF,0xB3,0xFF,0x1B,0x00,0x1B,0x01,0xBC,0xFE,0xDC,0x00, + 0x3D,0x00,0xB6,0xFE,0x25,0x01,0x2E,0xFF,0x19,0x00,0x5C,0x00, + 0xBF,0xFF,0xA8,0x00,0x26,0xFF,0xAB,0x00,0xE8,0xFF,0x73,0xFF, + 0xB9,0x00,0x79,0xFF,0xD3,0xFF,0x1F,0x00,0x0B,0x00,0x41,0x00, + 0xBA,0xFF,0x36,0x00,0x0E,0x00,0xA1,0xFF,0xF9,0x00,0xF1,0xFE, + 0x13,0x00,0xF8,0x00,0x2F,0xFF,0x44,0x00,0x13,0x00,0xE8,0xFF, + 0xD4,0xFF,0xEC,0xFF,0xAE,0x00,0xFB,0xFE,0x14,0x00,0x2E,0x01, + 0xD7,0xFE,0xA0,0x00,0x32,0x00,0x3E,0xFF,0x45,0x00,0x50,0x00, + 0xDB,0xFF,0x81,0xFF,0xD4,0x00,0xD1,0xFF,0x58,0xFF,0x29,0x00, + 0x16,0x00,0x01,0x00,0xB8,0xFF,0xDD,0x00,0xA8,0xFF,0xA2,0xFF, + 0x5C,0x00,0xD7,0xFF,0x1A,0x00,0xF8,0xFF,0x19,0x00,0x6E,0xFF, + 0x63,0x00,0x2C,0x00,0xC7,0xFF,0x2B,0x00,0x80,0x00,0x74,0xFF, + 0xB8,0xFF,0xD0,0x00,0x26,0xFF,0x38,0x00,0x78,0x00,0x4E,0xFF, + 0xAC,0x00,0x03,0x00,0x46,0xFF,0x2C,0x00,0xDC,0xFF,0xC5,0x00, + 0x1B,0x00,0x37,0xFF,0xF4,0xFF,0x75,0x00,0x29,0x00,0x0C,0x00, + 0xB8,0xFF,0x27,0x00,0xA5,0xFF,0x86,0xFF,0x50,0x01,0x1F,0xFF, + 0xC1,0xFF,0xE0,0x00,0x14,0xFF,0x4D,0x00,0x25,0x00,0xFF,0xFF, + 0x12,0x00,0xF9,0xFF,0x9C,0x00,0xFA,0xFE,0x24,0x00,0xAD,0x00, + 0xD6,0xFF,0x3B,0x00,0x03,0x00,0x2E,0xFF,0x2C,0x00,0x0D,0x00, + 0xFD,0xFF,0x13,0x00,0x70,0xFF,0xEB,0x00,0x7A,0xFF,0x31,0x00, + 0xF4,0xFF,0x5A,0xFF,0xCD,0x00,0xCF,0xFF,0xAB,0xFF,0x97,0x00, + 0x57,0x00,0x8E,0xFF,0x1A,0x00,0x2B,0x00,0x9D,0xFF,0x32,0x00, + 0xF7,0xFF,0xB2,0xFF,0x87,0x00,0x8F,0xFF,0xF4,0xFF,0x5A,0x00, + 0x87,0xFF,0xAE,0x00,0xE6,0xFF,0x7B,0xFF,0x6A,0x00,0xCA,0xFF, + 0xB3,0xFF,0x68,0x00,0x6D,0x00,0xAD,0xFF,0xFA,0xFF,0x00,0x00, + 0xBF,0xFF,0x23,0x00,0x64,0x00,0x78,0xFF,0x85,0xFF,0x82,0x00, + 0xE5,0xFF,0x1D,0x00,0x4F,0x00,0xB3,0xFF,0x88,0xFF,0xBB,0x00, + 0x34,0x00,0xA1,0xFF,0xD6,0xFF,0x1B,0x00,0xFA,0xFF,0x13,0x00, + 0x4F,0x00,0x7C,0xFF,0x62,0x00,0xE0,0xFF,0xE3,0xFF,0x61,0x00, + 0x83,0xFF,0xCB,0xFF,0x06,0x01,0x89,0xFF,0x48,0xFF,0x9B,0x00, + 0x28,0x00,0xBA,0xFF,0xEA,0xFF,0x58,0x00,0xD0,0xFF,0xFA,0xFF, + 0xEF,0xFF,0xAC,0xFF,0x54,0x00,0x65,0x00,0xC6,0xFF,0xD4,0xFF, + 0xA0,0xFF,0x6C,0x00,0x75,0x00,0x80,0xFF,0x51,0x00,0x20,0x00, + 0x80,0xFF,0x64,0x00,0x1E,0x00,0x5B,0xFF,0xF7,0xFF,0x5E,0x00, + 0x76,0xFF,0x78,0x00,0x12,0x00,0xE7,0xFF,0xF7,0xFF,0xF6,0xFF, + 0xC6,0x00,0x65,0xFF,0xA8,0x00,0xC2,0xFE,0x96,0x00,0x89,0x00, + 0xFB,0xFE,0xAF,0x00,0x2A,0xFF,0xB6,0x00,0xEA,0xFF,0xA7,0xFF, + 0x84,0x00,0xAF,0xFF,0x22,0x00,0xCF,0xFF,0x37,0x00,0x28,0x00, + 0xCD,0xFF,0xAE,0x00,0x65,0xFF,0xF4,0xFF,0x71,0x00,0xAC,0xFF, + 0xC1,0xFF,0xC2,0xFF,0x0F,0x00,0x1B,0x00,0x26,0x00,0xFD,0xFF, + 0x02,0x00,0xF9,0xFF,0x97,0x00,0xB3,0xFF,0x65,0xFF,0x79,0x00, + 0xD7,0xFF,0x3A,0x00,0xF3,0xFF,0x92,0xFF,0x50,0x00,0x61,0xFF, + 0x84,0x00,0xCD,0x00,0x28,0xFF,0x88,0x00,0xDF,0xFF,0x6B,0xFF, + 0xBC,0x00,0x2B,0xFF,0x55,0x00,0x96,0x00,0x57,0xFF,0x50,0x00, + 0xD3,0xFF,0x18,0x00,0xC2,0xFF,0x5C,0x00,0x1C,0x00,0x71,0xFF, + 0x40,0x00,0x35,0x00,0xF7,0xFF,0x3C,0x00,0x4F,0x00,0x5E,0xFF, + 0x42,0x00,0xB7,0xFF,0xDF,0xFF,0x5D,0x00,0x6F,0xFF,0x20,0x00, + 0x52,0x00,0x5B,0xFF,0x18,0x00,0x67,0x00,0x56,0x00,0xBC,0xFF, + 0x08,0x00,0xB7,0x00,0xCC,0xFE,0x45,0x00,0xAD,0x00,0x51,0xFF, + 0xF0,0xFF,0x1F,0x00,0x22,0x00,0x0D,0x00,0x2E,0x00,0xBC,0xFF, + 0x1D,0x00,0xFB,0xFF,0x6F,0xFF,0xC6,0x00,0xB9,0xFF,0x35,0x00, + 0xB4,0xFF,0x1D,0x00,0x71,0x00,0x21,0xFF,0x30,0x00,0x00,0x00, + 0x54,0x00,0x7B,0xFF,0x9B,0x00,0x02,0x00,0x9D,0xFF,0x38,0x00, + 0xBD,0xFF,0x4C,0x00,0x0D,0x00,0xFB,0xFF,0xB3,0xFF,0x2D,0x00, + 0x20,0x00,0xD0,0xFF,0x28,0x00,0x68,0x00,0x99,0xFF,0x0D,0x00, + 0x01,0x00,0x70,0xFF,0xA3,0x00,0xAC,0xFF,0x25,0x00,0x4E,0x00, + 0x2F,0xFF,0xD2,0x00,0x6B,0xFF,0x1B,0x00,0x22,0x00,0x86,0xFF, + 0xB0,0x00,0xB5,0xFF,0xDA,0xFF,0x6E,0x00,0x96,0x00,0x42,0xFF, + 0xA8,0xFF,0xB7,0x00,0x7D,0xFF,0xFF,0xFF,0x30,0x00,0xD4,0xFF, + 0x6E,0x00,0x3B,0xFF,0x6C,0x00,0x74,0xFF,0x2B,0x00,0xCC,0x00, + 0x35,0xFF,0x7F,0x00,0xA9,0xFF,0x61,0x00,0xF9,0xFF,0x1B,0x00, + 0xA7,0x00,0x41,0xFF,0xC4,0xFF,0x8F,0x00,0x89,0xFF,0x6D,0xFF, + 0xC4,0x00,0x86,0xFF,0x1B,0x00,0x2E,0x00,0x7A,0x00,0xDB,0xFF, + 0x85,0xFF,0x2C,0x00,0x84,0xFF,0x1D,0x01,0x62,0xFF,0xE4,0xFF, + 0x29,0x00,0xB2,0xFF,0x9B,0x00,0x1F,0x00,0xD7,0xFF,0x1C,0xFF, + 0x69,0x00,0x7F,0x00,0x7F,0xFF,0x33,0x00,0xDF,0xFF,0xD3,0xFF, + 0x20,0x00,0x19,0x00,0x3F,0x00,0xAE,0xFF,0xCE,0xFF,0x24,0x00, + 0x12,0x00,0x27,0x00,0x25,0x00,0xDE,0xFF,0x94,0xFF,0x50,0x00, + 0x46,0x00,0x02,0x00,0x90,0xFF,0x14,0x00,0x8C,0x00,0x80,0xFF, + 0x41,0x00,0xB3,0xFF,0x34,0x00,0x82,0x00,0x8F,0xFF,0x0E,0x00, + 0xCD,0xFF,0x8A,0xFF,0x8F,0x00,0x8F,0xFF,0x35,0x00,0x44,0x00, + 0xE7,0xFF,0x30,0x00,0x92,0xFF,0x8E,0x00,0xE4,0xFF,0xB6,0xFF, + 0xE5,0xFF,0x52,0x00,0x72,0xFF,0x73,0x00,0x24,0x00,0x55,0xFF, + 0xAC,0x00,0x11,0x00,0x9B,0xFF,0x53,0x00,0x91,0xFF,0x39,0x00, + 0xCF,0x00,0xF2,0xFE,0x2D,0x00,0x84,0xFF,0x13,0x00,0x21,0x01, + 0x4C,0xFF,0x28,0x00,0x30,0x00,0x59,0xFF,0x96,0x00,0xDD,0xFF, + 0xEA,0xFF,0xC1,0x00,0xD6,0xFE,0x08,0x00,0x0F,0x01,0x60,0xFF, + 0x74,0x00,0x45,0xFF,0xC8,0xFF,0x6A,0x00,0x5A,0xFF,0xE3,0x00, + 0xD0,0xFF,0xAE,0xFF,0x7B,0x00,0xF9,0xFF,0xDB,0xFF,0x86,0x00, + 0x7F,0xFF,0xC4,0xFF,0xB1,0x00,0xDA,0xFF,0x90,0xFF,0xD5,0xFF, + 0x3F,0x00,0x38,0x00,0xD7,0xFF,0xCE,0xFF,0x15,0x00,0xFB,0xFF, + 0xE8,0xFF,0x2E,0x00,0x27,0x00,0xF5,0xFF,0x03,0x00,0xBB,0xFF, + 0x89,0x00,0x9A,0xFF,0xA0,0xFF,0xD6,0x00,0x83,0xFF,0xE8,0xFF, + 0x62,0x00,0x96,0xFF,0xA9,0x00,0x47,0xFF,0xF3,0xFF,0xCC,0x00, + 0x89,0xFF,0x6F,0x00,0x8A,0xFF,0x0C,0x00,0xF7,0xFF,0xED,0xFF, + 0x2E,0x00,0xEE,0xFF,0x8D,0xFF,0x4F,0x00,0x4D,0x00,0xD7,0xFF, + 0x9E,0x00,0x87,0xFF,0xB5,0xFF,0x24,0x00,0xA5,0xFF,0x5B,0x00, + 0x89,0x00,0xA5,0xFF,0xEA,0xFF,0x91,0xFF,0x9A,0x00,0x96,0xFF, + 0xE5,0xFF,0x5A,0x00,0x4D,0xFF,0x79,0x00,0xE2,0xFF,0x25,0x00, + 0xE2,0xFF,0x88,0x00,0x0F,0x00,0x50,0xFF,0x31,0x00,0x35,0x00, + 0xF7,0xFF,0x1D,0x00,0x15,0x00,0xCC,0xFF,0x92,0x00,0x22,0xFF, + 0xAB,0xFF,0x16,0x01,0xF3,0xFE,0x45,0x00,0x99,0x00,0x55,0xFF, + 0x4D,0x00,0xE3,0xFF,0x2F,0x00,0x4E,0x00,0xAD,0xFF,0x1B,0x00, + 0xF1,0xFF,0xB3,0xFF,0x63,0x00,0xD6,0xFF,0x37,0x00,0x4E,0x00, + 0x00,0xFF,0xA0,0x01,0xE5,0x00,0xB4,0xFE,0x97,0x00,0x61,0xFF, + 0x96,0xFE,0xCB,0xFF,0xCA,0xFF,0x50,0x00,0xFA,0xFF,0xE1,0xFF, + 0xA0,0x00,0xEF,0xFE,0xF7,0xFF,0x3C,0x01,0x40,0x00,0x25,0x00, + 0x98,0x00,0xCB,0xFF,0xA1,0xFF,0x31,0x00,0x84,0xFF,0xF7,0xFF, + 0x9E,0xFF,0x0C,0x00,0xEA,0xFF,0x51,0x00,0x91,0x00,0xC1,0xFF, + 0x73,0x00,0x3D,0x00,0xA6,0xFF,0x1F,0x00,0xA9,0x00,0x80,0x00, + 0xBB,0xFF,0xCB,0xFF,0x50,0x00,0x33,0xFF,0x2B,0xFF,0x0A,0x00, + 0xFB,0xFF,0x55,0xFF,0x25,0x00,0x7F,0x00,0x82,0xFF,0x50,0x00, + 0x88,0x00,0xC6,0xFF,0x99,0xFF,0x2C,0x00,0xCA,0xFF,0x97,0xFF, + 0x9D,0x00,0x23,0x00,0x61,0xFF,0xC6,0xFF,0xD9,0xFF,0x33,0x00, + 0x53,0x00,0xAF,0xFF,0x30,0x00,0xDD,0xFF,0x22,0x00,0x87,0x00, + 0xC0,0xFF,0x2D,0x00,0x14,0x00,0xF7,0xFF,0x7A,0x00,0x5C,0x00, + 0xBF,0xFF,0x0E,0x00,0x57,0x00,0xE5,0xFF,0xC7,0xFF,0xEB,0xFF, + 0x77,0x00,0xD6,0xFF,0xEA,0xFF,0x92,0x00,0x44,0xFF,0x06,0x00, + 0x42,0x00,0x57,0xFF,0x2D,0x00,0x21,0x00,0x13,0x00,0xB5,0xFF, + 0xFA,0xFF,0x37,0x00,0xB3,0xFF,0xD9,0xFF,0x11,0x00,0xDD,0xFF, + 0x13,0x00,0x3A,0x00,0x99,0xFF,0x2E,0x00,0x25,0x00,0x00,0x00, + 0x38,0x00,0x70,0xFF,0xFC,0xFF,0x69,0x00,0xA6,0xFF,0x39,0x00, + 0xC2,0xFF,0x98,0xFF,0x71,0x00,0xDC,0xFF,0x26,0x00,0x2B,0x00, + 0xEF,0xFF,0x49,0x00,0xD1,0xFF,0xFA,0xFF,0x5D,0x00,0xBE,0xFF, + 0x72,0x00,0xE6,0xFF,0x6C,0xFF,0x4D,0x00,0x92,0xFF,0x29,0x00, + 0x29,0x00,0x2B,0x00,0xFA,0xFF,0x81,0xFF,0x5C,0x00,0x12,0x00, + 0xBC,0xFF,0x33,0x00,0x2A,0x00,0x9A,0xFF,0x50,0x00,0x10,0x00, + 0xC6,0xFF,0x2E,0x00,0xFB,0xFF,0x1D,0x00,0xCA,0xFF,0xB8,0xFF, + 0x33,0x00,0x36,0x00,0xE6,0xFF,0x32,0x00,0xF5,0xFF,0xC8,0xFF, + 0x03,0x00,0x07,0x00,0xF1,0xFF,0x0C,0x00,0x14,0x00,0x0E,0x00, + 0xFA,0xFF,0xE2,0xFF,0x1A,0x00,0xDB,0xFF,0x61,0x00,0xC0,0xFF, + 0xBE,0xFF,0x60,0x00,0xCF,0xFF,0x32,0x00,0x07,0x00,0x19,0x00, + 0x09,0x00,0xA9,0xFF,0x10,0x00,0x23,0x00,0xF6,0xFF,0xE9,0xFF, + 0xF3,0xFF,0x23,0x00,0xF6,0xFF,0xE1,0xFF,0x22,0x00,0xE0,0xFF, + 0x05,0x00,0x26,0x00,0xD7,0xFF,0x0E,0x00,0x2D,0x00,0xF9,0xFF, + 0xFA,0xFF,0xCE,0xFF,0x1A,0x00,0x3E,0x00,0xD8,0xFF,0xF8,0xFF, + 0xF9,0xFF,0xDF,0xFF,0xD2,0xFF,0x2E,0x00,0xE0,0xFF,0xDC,0xFF, + 0x17,0x00,0xFE,0xFF,0x0E,0x00,0xDD,0xFF,0x2A,0x00,0x11,0x00, + 0xF8,0xFF,0x10,0x00,0xF6,0xFF,0x01,0x00,0x02,0x00,0x08,0x00, + 0x25,0x00,0xCC,0xFF,0xDB,0xFF,0x00,0x00,0x10,0x00,0x22,0x00, + 0xE6,0xFF,0xF3,0xFF,0xFC,0xFF,0x01,0x00,0x1B,0x00,0x04,0x00, + 0xB7,0xFF,0x45,0x00,0x22,0x00,0xC3,0xFF,0xFB,0xFF,0x21,0x00, + 0x0E,0x00,0xCC,0xFF,0x30,0x00,0x20,0x00,0xE3,0xFF,0xF2,0xFF, + 0x2A,0x00,0xEB,0xFF,0xFC,0xFF,0x39,0x00,0xE7,0xFF,0xF5,0xFF, + 0xF7,0xFF,0x48,0x00,0xFA,0xFF,0xA1,0xFF,0x18,0x00,0x18,0x00, + 0xF1,0xFF,0xF6,0xFF,0x1A,0x00,0x11,0x00,0xDC,0xFF,0x0D,0x00, + 0x21,0x00,0xDD,0xFF,0x03,0x00,0x35,0x00,0xFE,0xFF,0xD2,0xFF, + 0x13,0x00,0xFA,0xFF,0xC9,0xFF,0x07,0x00,0x1E,0x00,0x07,0x00, + 0x95,0xFF,0xDF,0xFF,0x3D,0x00,0xF4,0xFF,0x0A,0x00,0x29,0x00, + 0xDF,0xFF,0xE2,0xFF,0x35,0x00,0x2C,0x00,0xE2,0xFF,0x03,0x00, + 0x50,0x00,0xD8,0xFF,0xDA,0xFF,0xFF,0xFF,0x0B,0x00,0x15,0x00, + 0xF7,0xFF,0x0E,0x00,0xCE,0xFF,0xEF,0xFF,0x30,0x00,0x16,0x00, + 0xFD,0xFF,0xFD,0xFF,0xF0,0xFF,0xF3,0xFF,0xFF,0xFF,0x25,0x00, + 0x06,0x00,0xC7,0xFF,0x00,0x00,0xED,0xFF,0xD5,0xFF,0x10,0x00, + 0x20,0x00,0xF3,0xFF,0xF8,0xFF,0x0D,0x00,0xED,0xFF,0xFC,0xFF, + 0x07,0x00,0x04,0x00,0x01,0x00,0xFB,0xFF,0x06,0x00,0xEC,0xFF, + 0xDC,0xFF,0x11,0x00,0x23,0x00,0x09,0x00,0xF8,0xFF,0xF5,0xFF, + 0x03,0x00,0x07,0x00,0x1A,0x00,0x25,0x00,0x21,0x00,0x08,0x00, + 0xEE,0xFF,0x04,0x00,0x12,0x00,0xF3,0xFF,0xF9,0xFF,0x0A,0x00, + 0xF1,0xFF,0xE6,0xFF,0xF3,0xFF,0xF7,0xFF,0xF4,0xFF,0x06,0x00, + 0x22,0x00,0x30,0x00,0x0E,0x00,0xE1,0xFF,0xEE,0xFF,0xFD,0xFF, + 0xF1,0xFF,0xF8,0xFF,0x07,0x00,0x0E,0x00,0xEC,0xFF,0xD4,0xFF, + 0x08,0x00,0x1E,0x00,0x0A,0x00,0x04,0x00,0x05,0x00,0xF4,0xFF, + 0xEA,0xFF,0x04,0x00,0x09,0x00,0x09,0x00,0xEF,0xFF,0xDF,0xFF, + 0xE5,0xFF,0xFA,0xFF,0x10,0x00,0x17,0x00,0x11,0x00,0xFC,0xFF, + 0x01,0x00,0x08,0x00,0x24,0x00,0x1A,0x00,0xF7,0xFF,0xFE,0xFF, + 0xF6,0xFF,0xF7,0xFF,0xFB,0xFF,0xF9,0xFF,0x09,0x00,0xFD,0xFF, + 0xF2,0xFF,0xF6,0xFF,0x00,0x00,0x0C,0x00,0x01,0x00,0xEC,0xFF, + 0x03,0x00,0x1E,0x00,0x17,0x00,0x0C,0x00,0x03,0x00,0xEF,0xFF, + 0xDB,0xFF,0xED,0xFF,0xFD,0xFF,0xF6,0xFF,0x06,0x00,0x00,0x00, + 0x00,0x00,0x01,0x00,0xFF,0xFF,0x01,0x00,0x02,0x00,0xFF,0xFF, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0xFF,0xFF,0x00,0x00, + 0x01,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0x00,0x00,0xFE,0xFF,0xFE,0xFF,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0xFE,0xFF,0x01,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x00,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0xFF,0xFF, + 0x00,0x00,0x00,0x00,0xFF,0xFF,0x01,0x00,0xFF,0xFF,0xFF,0xFF, + 0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF, + 0xFF,0xFF,0x00,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00, + 0xFF,0xFF,0x00,0x00,0x01,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00, + 0xFF,0xFF,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF, + 0xFF,0xFF,0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0x01,0x00,0xFF,0xFF,0xFE,0xFF,0x01,0x00, + 0x00,0x00,0xFF,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0xFF,0xFF, + 0xFF,0xFF,0x01,0x00,0xFF,0xFF,0x01,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF, + 0x01,0x00,0xFF,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00, + 0x00,0x00,0x01,0x00,0x01,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00, + 0xFF,0xFF,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF,0x01,0x00, + 0x01,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF, + 0x00,0x00,0x01,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xFF,0xFF,0x01,0x00,0x00,0x00,0xFE,0xFF, + 0x01,0x00,0x01,0x00,0xFF,0xFF,0x01,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0xFF,0xFF,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x00,0x00, + 0x01,0x00,0x01,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x01,0x00,0x00,0x00, + 0x00,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF, + 0xFF,0xFF,0x00,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x01,0x00, + 0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF, + 0x00,0x00,0x01,0x00,0xFF,0xFF,0x01,0x00,0x01,0x00,0xFF,0xFF, + 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF,0x01,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0xFF,0xFF,0x01,0x00,0xFE,0xFF,0x00,0x00,0x02,0x00, + 0xFF,0xFF,0x00,0x00,0xFF,0xFF,0x01,0x00,0x00,0x00,0xFF,0xFF, + 0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF,0x01,0x00, + 0xFF,0xFF,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0xFD,0xFF,0x02,0x00,0x01,0x00,0x01,0x00,0x02,0x00, + 0xFF,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFD,0xFF, + 0x00,0x00,0x00,0x00,0xFD,0xFF,0x01,0x00,0xFE,0xFF,0x03,0x00, + 0xFD,0xFF,0xF7,0xFF,0x12,0x00,0xFC,0xFF,0xFC,0xFF,0x08,0x00, + 0xFB,0xFF,0x04,0x00,0xFC,0xFF,0x02,0x00,0x00,0x00,0xFD,0xFF, + 0x00,0x00,0xFF,0xFF,0xFE,0xFF,0xFD,0xFF,0x01,0x00,0xFF,0xFF, + 0xFF,0xFF,0xFD,0xFF,0xFF,0xFF,0x01,0x00,0x02,0x00,0x00,0x00, + 0x04,0x00,0xFC,0xFF,0x06,0x00,0xFB,0xFF,0x06,0x00,0xFC,0xFF, + 0x04,0x00,0x00,0x00,0xF9,0xFF,0x14,0x00,0xEC,0xFF,0x15,0x00, + 0xEA,0xFF,0x12,0x00,0xF6,0xFF,0x14,0x00,0xDD,0xFF,0x2D,0x00, + 0xD7,0xFF,0x51,0x00,0xA8,0xFF,0x47,0x00,0x8C,0xFF,0xB7,0xFF, + 0x3F,0x05,0xAE,0xFB,0x41,0xFF,0x17,0x01,0x6F,0xFC,0x1E,0x03, + 0xB0,0xFD,0x74,0x01,0x5A,0x00,0xB1,0xFE,0x97,0x01,0x8B,0xFE, + 0xB1,0x01,0xBC,0xFE,0xCB,0x01,0x94,0x01,0xCB,0xFC,0x5A,0x02, + 0x95,0xFE,0x01,0x00,0xF0,0xFF,0x2E,0xFF,0xED,0x01,0x8E,0xFE, + 0xB1,0x00,0x9B,0xFF,0x52,0x00,0xCF,0xFF,0x4C,0x00,0xF4,0xFE, + 0xD2,0x01,0xC8,0x01,0x3D,0xFC,0x51,0x01,0x5F,0xFE,0x42,0xFF, + 0xE8,0x00,0x4F,0xFE,0xBD,0x01,0xCD,0xFF,0x6F,0x00,0xEF,0x00, + 0xC3,0xFE,0x50,0x02,0x71,0xFE,0x37,0x02,0x50,0xFF,0x92,0xFF, + 0x63,0x01,0x7A,0xFD,0x71,0x02,0xB0,0xFC,0xEA,0x01,0x6E,0xFE, + 0x66,0x01,0x27,0xFE,0x50,0x00,0xDF,0x01,0x6B,0xFC,0x59,0x04, + 0x61,0xFB,0xE0,0x03,0xCE,0xFD,0xB1,0x00,0x98,0x00,0xB9,0xFE, + 0x36,0x03,0xB9,0xFB,0x5A,0x03,0x9F,0xFE,0x8D,0x00,0xFB,0xFF, + 0x9F,0xFF,0xA7,0x00,0xD7,0xFE,0x42,0x01,0xDF,0xFE,0xEB,0xFF, + 0x39,0x01,0x65,0xFE,0x33,0x01,0x66,0x00,0xA9,0xFE,0x9D,0x01, + 0xE0,0xFF,0x69,0x00,0x51,0xFD,0xDB,0x01,0x5A,0x00,0xE9,0xFE, + 0xAD,0x01,0x48,0xFC,0xB3,0x03,0xDA,0xFD,0xBB,0x00,0x9C,0x00, + 0xB8,0xFC,0xF1,0x05,0x3A,0xFA,0xFF,0x05,0xD1,0xFB,0x1E,0x00, + 0xD8,0x03,0xE3,0xF9,0xD2,0x06,0xC4,0xF8,0x52,0x05,0x80,0xFC, + 0x00,0x03,0x4F,0xFD,0x87,0xFE,0x93,0x06,0x0A,0xF8,0xD9,0x07, + 0x2C,0xF8,0xC9,0x06,0x1B,0xFD,0x82,0x00,0x99,0x01,0x97,0xFA, + 0x54,0x07,0x57,0xFB,0x9C,0x05,0x4B,0xFA,0x77,0x00,0xE5,0x01, + 0x4F,0xFE,0x1A,0xFB,0x60,0x02,0x50,0x01,0x96,0xFF,0x90,0x00, + 0x74,0xFD,0x8F,0x06,0x98,0xFC,0xB8,0x04,0x29,0xF9,0x18,0x06, + 0x6E,0xFF,0x2D,0xFE,0x27,0x02,0x8A,0xFA,0x98,0x05,0xB9,0xFC, + 0xE5,0x00,0xA5,0xFC,0xAC,0x03,0x38,0xFF,0xD2,0xFE,0xB4,0x00, + 0xB8,0xFD,0x56,0x03,0x1F,0x00,0xBA,0xFC,0xBA,0x01,0x2D,0x02, + 0xD3,0xFD,0xAC,0x00,0x78,0x00,0x9A,0xFD,0x68,0x02,0xB3,0x02, + 0x71,0xFB,0xBE,0xFE,0x2A,0x05,0x13,0xFE,0x50,0xFD,0x50,0x01, + 0x28,0xFE,0x81,0x04,0x06,0xFD,0x5C,0x02,0xA1,0xFB,0x91,0x03, + 0xF6,0x03,0x98,0xF6,0x5C,0x06,0xAC,0xFE,0x7F,0x00,0x24,0xFD, + 0x87,0x01,0xF3,0xFF,0xFC,0xFE,0x8D,0x06,0xBD,0xF9,0x36,0x01, + 0xF3,0x02,0x81,0xFE,0xB8,0xFE,0x2C,0xFF,0xEE,0x02,0x2C,0xFD, + 0xEC,0x01,0xC4,0xFD,0x9C,0xFE,0x4D,0x04,0xA9,0xFE,0x5C,0x00, + 0x65,0xFD,0xFF,0x02,0xEF,0xFF,0x3E,0xFE,0x6F,0x00,0x40,0xFE, + 0x4F,0x03,0xC0,0x00,0xC5,0xFC,0x6F,0x01,0x42,0xFF,0xB3,0x01, + 0x7D,0xFF,0x41,0xFD,0x03,0x03,0x4B,0xFC,0x0B,0x05,0x7D,0xFE, + 0x2D,0xFD,0x18,0x03,0x31,0xFE,0x02,0x02,0x26,0xFE,0x6F,0xFE, + 0x93,0x02,0xA6,0x00,0x2D,0xFE,0x2B,0x01,0x44,0xFD,0xC2,0x02, + 0x8D,0x00,0xB2,0xFB,0xF8,0x02,0xE7,0x00,0x44,0xFE,0xAD,0xFE, + 0x3D,0x02,0x0F,0x01,0xB7,0xFD,0x10,0x03,0x9F,0xFE,0x31,0xFD, + 0x67,0x07,0xD5,0xFA,0x71,0xFE,0xDC,0x02,0xC1,0xFF,0x45,0xFF, + 0x16,0xFD,0xEB,0x05,0x56,0xFA,0xFF,0x01,0xA7,0x04,0xC9,0xF6, + 0x9E,0x03,0xA2,0x03,0x5B,0xFE,0x77,0xFE,0x55,0x02,0x9C,0x02, + 0x50,0xFA,0xB4,0x06,0xB6,0xFD,0x9D,0xFA,0x83,0x08,0xDA,0xFB, + 0x0D,0xFE,0x98,0xFF,0x32,0x00,0x65,0xFF,0xBF,0xFD,0x93,0x03, + 0xA8,0xFB,0x53,0x01,0x88,0x03,0xD4,0xFC,0x84,0x02,0x58,0x00, + 0xAA,0x00,0xAD,0x02,0x9A,0xFE,0x23,0x02,0xC7,0xFC,0x49,0x00, + 0x28,0x03,0x2E,0xF9,0xAB,0x02,0x05,0x00,0x40,0xFE,0x3C,0x01, + 0xB7,0xFE,0xDF,0x00,0xB8,0xFB,0x94,0x05,0x27,0xFE,0x75,0xFE, + 0x72,0x06,0x30,0xF9,0x23,0x03,0x77,0xFF,0xA2,0xFF,0x43,0x01, + 0xC5,0xFD,0x48,0x03,0x42,0xFD,0xD1,0x02,0x0C,0x00,0x33,0xFD, + 0xBA,0xFF,0xEA,0x01,0xDA,0xFF,0x19,0xFB,0x5D,0x02,0x7E,0x06, + 0x3D,0xFC,0xD2,0xFB,0xE2,0x05,0xAD,0xFD,0x5E,0x05,0x1C,0xFC, + 0xDB,0xF9,0x85,0x07,0xDE,0xFF,0x3F,0xFE,0x67,0xF9,0x2E,0x04, + 0x59,0x04,0xE9,0xFA,0x3E,0xFF,0x16,0x03,0x9F,0x00,0x09,0x00, + 0x4B,0xFF,0xAD,0xFD,0x1C,0x04,0x14,0x02,0xDA,0xFC,0x18,0xFF, + 0xD7,0x01,0x9C,0x00,0x88,0xFE,0x5C,0x01,0x8E,0xFE,0x98,0xFD, + 0x19,0x04,0x27,0xFB,0x09,0xFF,0x17,0x05,0x19,0xFF,0xBC,0xF9, + 0x46,0x00,0x4A,0x09,0x2C,0xFD,0xDE,0xFD,0xE1,0x00,0xE4,0x00, + 0xF2,0x01,0x05,0x01,0xB8,0xFB,0xFF,0xFB,0x70,0x07,0x44,0x01, + 0x16,0xFA,0x37,0xFE,0x02,0x05,0xD5,0x00,0x68,0xFC,0xA0,0xFE, + 0xFF,0xFF,0xD1,0x02,0x6B,0x00,0x27,0xFE,0x13,0xFC,0x08,0x03, + 0x52,0x05,0x9D,0xFF,0x06,0xFB,0x1E,0x00,0x03,0x06,0xAB,0xFF, + 0x7B,0xFF,0x4E,0xFB,0x20,0x00,0xEC,0x03,0x6E,0x01,0x2D,0xFB, + 0x2E,0xFB,0x18,0x05,0xF0,0x00,0x7D,0xFE,0xF5,0xFC,0x5D,0x00, + 0xC7,0x03,0xD8,0x02,0xDB,0xFD,0xB3,0xFD,0xCA,0x03,0x89,0x02, + 0x9C,0xFE,0x2E,0xFB,0xBE,0x00,0xAC,0x03,0x2F,0xFF,0xD0,0xFD, + 0xB7,0xFE,0xB1,0xFF,0xA9,0x03,0x97,0x01,0x99,0xF9,0x90,0xFF, + 0x89,0x04,0x7C,0xFF,0x0D,0x00,0x0F,0xFF,0x55,0xFD,0xB4,0x02, + 0x2E,0x05,0x40,0xFC,0x0E,0xFC,0x09,0x04,0x2D,0xFF,0x44,0xFE, + 0xD0,0x00,0x44,0xFE,0x2F,0x00,0x25,0x02,0xF5,0x00,0x06,0xFE, + 0x16,0x01,0x73,0x03,0x9B,0xFC,0xAE,0xFF,0x4E,0x03,0xC6,0xFE, + 0xB4,0xFD,0x86,0x00,0xB8,0x00,0xA9,0xFD,0x99,0xFF,0xBB,0x00, + 0xDB,0xFD,0xE9,0x00,0x02,0x02,0x61,0xFE,0x29,0xFF,0xBC,0x01, + 0x03,0x01,0xA8,0xFE,0xED,0x02,0xEA,0xFF,0xAC,0xFE,0xD9,0x02, + 0x51,0x00,0x4B,0xFE,0xDA,0xFE,0x15,0x02,0x75,0xFF,0x13,0xFF, + 0x12,0x00,0xEB,0xFE,0x72,0xFE,0xC1,0x00,0x0D,0x01,0xB4,0xFD, + 0x8D,0x00,0x00,0x00,0x84,0x00,0x42,0x01,0x59,0xFF,0xEC,0xFD, + 0x39,0xFF,0x27,0x03,0xE8,0x00,0xB0,0xFD,0x2E,0xFE,0xBF,0x02, + 0x49,0x02,0xA3,0xFE,0x8C,0xFF,0x7A,0xFF,0x50,0x00,0x9B,0x02, + 0x68,0x00,0x4F,0xFD,0x14,0x00,0x92,0x01,0x61,0xFF,0x1C,0xFF, + 0x6F,0x00,0x05,0x00,0x1B,0xFF,0x9D,0x00,0x06,0x00,0xA4,0xFF, + 0x0B,0x02,0x2C,0x00,0x66,0xFE,0x4D,0x00,0x26,0x01,0x69,0x00, + 0xF0,0xFE,0x07,0xFF,0x6D,0xFF,0x81,0x00,0x97,0x00,0xB6,0xFE, + 0x0A,0xFF,0xDB,0x00,0xBC,0x00,0x2D,0xFF,0x17,0xFF,0xE1,0xFF, + 0x0A,0x00,0x64,0x00,0x50,0xFF,0x24,0xFD,0x13,0xFF,0xBF,0xFF, + 0x11,0xFF,0x4C,0xFE,0xB1,0xFD,0x69,0x00,0x22,0x01,0xE5,0x00, + 0x15,0x00,0x80,0x00,0x82,0x04,0xDE,0x04,0x21,0x02,0xEA,0x03, + 0x1A,0x07,0x0C,0x06,0x2D,0x03,0x24,0x04,0xD7,0x03,0x63,0xFF, + 0xCB,0xFF,0xD0,0xFD,0x7A,0xF9,0xFB,0xF9,0x42,0xFA,0x80,0xF7, + 0xF7,0xF3,0xEF,0xF6,0x04,0xFA,0xE7,0xF8,0xF6,0xF9,0x3B,0xFC, + 0x98,0xFE,0x72,0x01,0x1F,0x02,0x5B,0x02,0x32,0x04,0x5A,0x07, + 0x1E,0x09,0x33,0x03,0xDE,0x03,0x28,0x09,0x82,0x02,0x9C,0x01, + 0x67,0x0F,0x6B,0x10,0x21,0x02,0x15,0x04,0xFF,0x09,0xB1,0x05, + 0xA6,0x01,0x4A,0xFF,0xB0,0xFA,0x62,0xF5,0x71,0xFB,0xAA,0xF8, + 0x35,0xED,0xD8,0xEF,0xD2,0xF3,0x46,0xF3,0xF5,0xF3,0x85,0xF7, + 0x80,0xFA,0x2E,0xFC,0x35,0x04,0xDE,0x06,0x1C,0x04,0x76,0x09, + 0x72,0x0C,0x02,0x0B,0x83,0x09,0xFD,0x08,0x10,0x06,0x3B,0x02, + 0xD4,0xFF,0x59,0xFB,0x79,0xFA,0xF8,0xF7,0x3A,0xF8,0xD5,0xF9, + 0xAF,0x02,0x2F,0x0C,0x36,0xFF,0x6B,0x02,0x2A,0x0D,0x46,0x0D, + 0xAC,0x0B,0xCD,0x05,0xEE,0x07,0x95,0x01,0xD6,0x01,0x97,0x01, + 0x64,0xF3,0x06,0xF1,0x11,0xF4,0xC5,0xF3,0xB3,0xF0,0xC7,0xEF, + 0x39,0xF3,0xD5,0xF5,0x15,0xFA,0x5D,0xFF,0xEB,0xFE,0x68,0x03, + 0x12,0x0A,0xCC,0x0A,0xA2,0x09,0x06,0x0A,0x69,0x0B,0xA2,0x09, + 0x99,0x07,0x02,0x00,0x31,0xFA,0x5F,0xFD,0x49,0xF9,0xCA,0xF6, + 0x91,0x01,0x2D,0x06,0x62,0x01,0xDF,0xFE,0x29,0x08,0x0F,0x0B, + 0x0D,0x06,0x13,0x0B,0xEB,0x06,0xA7,0x01,0x88,0x03,0x77,0x03, + 0x20,0xFB,0x4F,0xF3,0xFD,0xF6,0x92,0xF4,0xE4,0xF0,0xFE,0xF4, + 0x78,0xF6,0xD3,0xF2,0xFB,0xF6,0xC6,0xFE,0xEA,0xFE,0x05,0x00, + 0x5D,0x05,0x5F,0x07,0x57,0x07,0xA7,0x0A,0x25,0x0A,0x03,0x06, + 0xEC,0x05,0xDC,0x04,0x3C,0x00,0x03,0xFE,0xA9,0xF9,0xBC,0xF4, + 0x9A,0xF9,0x6F,0x07,0xE7,0x07,0xED,0xFB,0x67,0x01,0x7A,0x09, + 0x84,0x09,0x88,0x0A,0x9B,0x08,0x4F,0x03,0x83,0x00,0xA7,0x06, + 0x39,0x03,0xC5,0xF6,0x39,0xF5,0x1B,0xF6,0x12,0xF4,0xF2,0xF4, + 0x24,0xF7,0xF3,0xF4,0x1D,0xF3,0xF3,0xFA,0x16,0x00,0x42,0xFE, + 0x85,0x01,0xFA,0x04,0xA2,0x05,0x55,0x08,0xD6,0x09,0xD6,0x06, + 0x66,0x04,0x50,0x05,0x6E,0x03,0x74,0xFF,0x87,0xFC,0x67,0xF5, + 0xB4,0xFC,0xC8,0x0C,0x1E,0x05,0xA7,0xFC,0xEE,0x00,0x24,0x06, + 0xC5,0x07,0x4F,0x08,0xD3,0x07,0x7A,0xFD,0xE7,0xFD,0xA8,0x06, + 0x07,0x01,0xB9,0xF7,0x1B,0xF6,0xA9,0xF6,0x30,0xF5,0x90,0xF8, + 0x4F,0xFC,0xF0,0xF6,0x1E,0xF5,0x6A,0xFD,0x25,0x01,0xB6,0x00, + 0x24,0x03,0xB9,0x03,0x78,0x03,0x49,0x06,0xB3,0x08,0x6C,0x04, + 0x3B,0x01,0x73,0x01,0x0B,0xFF,0x0A,0xFD,0x0B,0xF8,0x7E,0xFF, + 0x32,0x0A,0x5C,0x03,0x56,0x01,0x6A,0x03,0xB5,0x04,0x12,0x07, + 0xC1,0x08,0x87,0x07,0x53,0xFF,0x80,0xFF,0x6D,0x03,0x0B,0xFF, + 0x51,0xFA,0x0F,0xF8,0x3E,0xF5,0x08,0xF5,0x59,0xF8,0xB4,0xFA, + 0xAF,0xF8,0x02,0xF7,0x5E,0xFB,0xD5,0xFE,0xDF,0x01,0xE0,0x03, + 0x46,0x03,0xF2,0x03,0x67,0x05,0x25,0x07,0xAC,0x05,0x4B,0x02, + 0xAA,0xFF,0x95,0xFE,0xA2,0xFA,0x7D,0xFE,0x13,0x0B,0x48,0x06, + 0x2F,0xFF,0x5B,0x02,0xF7,0x03,0x26,0x05,0x7E,0x07,0xE3,0x06, + 0x6A,0xFE,0x92,0xFC,0xA0,0x02,0x64,0xFF,0xB8,0xF9,0x1B,0xF9, + 0x8B,0xF6,0xBD,0xF5,0x08,0xFA,0x4D,0xFD,0xBE,0xF9,0xFE,0xF7, + 0xBF,0xFD,0x7A,0xFF,0xB5,0x01,0x0D,0x05,0xA1,0x03,0x44,0x02, + 0x84,0x04,0x84,0x06,0x7A,0x03,0xF8,0xFF,0x56,0xFF,0x6B,0xFA, + 0xA4,0xF9,0xA3,0x08,0x66,0x0A,0x59,0x00,0x81,0x01,0xBD,0x03, + 0x49,0x03,0xC1,0x06,0xB9,0x0A,0x0F,0x02,0x1E,0xFB,0xEA,0x00, + 0x5A,0x01,0xB8,0xFB,0x14,0xFB,0x1D,0xF8,0x4A,0xF3,0x43,0xF7, + 0x47,0xFD,0x4E,0xFC,0xD9,0xF7,0xA6,0xFA,0x66,0xFD,0x56,0xFF, + 0xE0,0x05,0xA5,0x05,0x64,0x01,0x93,0x02,0xF6,0x05,0x8B,0x05, + 0x08,0x03,0x93,0x01,0x28,0xFB,0xAB,0xF7,0xF6,0x06,0x8F,0x0C, + 0x65,0x02,0x87,0x01,0x03,0x02,0x82,0x00,0x21,0x05,0x47,0x0B, + 0x2E,0x03,0x4E,0xFA,0xC9,0xFE,0x01,0x00,0x73,0xFC,0x50,0xFD, + 0x14,0xFA,0x02,0xF3,0x25,0xF6,0x61,0xFD,0xF7,0xFD,0x37,0xFA, + 0x78,0xFB,0x2F,0xFC,0x9F,0xFD,0xCB,0x05,0x85,0x06,0x26,0x02, + 0x55,0x01,0xDB,0x03,0xD5,0x03,0x9B,0x02,0xAB,0x00,0x35,0xF9, + 0x26,0xFF,0xA8,0x0A,0x28,0x08,0xE7,0x03,0x18,0x03,0x00,0x01, + 0x4F,0x01,0x01,0x08,0xF5,0x07,0x8D,0xFF,0x96,0xFC,0xF3,0xFD, + 0xFC,0xFB,0x1F,0xFC,0xFB,0xFC,0x2E,0xF6,0x9D,0xF3,0xDA,0xF8, + 0xD2,0xFC,0x63,0xFC,0x87,0xFC,0x96,0xFC,0xD3,0xFB,0xC8,0x01, + 0xAF,0x06,0x27,0x05,0x84,0x03,0x11,0x04,0x48,0x02,0x19,0x02, + 0xCC,0x01,0x93,0xFC,0xD8,0x00,0x23,0x08,0xBD,0x06,0x08,0x04, + 0xBB,0x03,0xEF,0x01,0xC0,0x00,0x51,0x05,0xE1,0x05,0x0D,0x00, + 0x8A,0xFD,0xCF,0xFD,0x26,0xFB,0x02,0xFB,0x3A,0xFC,0xD9,0xF7, + 0x9C,0xF5,0x34,0xF9,0x62,0xFC,0xAC,0xFC,0xAE,0xFD,0x67,0xFD, + 0x44,0xFD,0x88,0x01,0x40,0x05,0xC0,0x04,0x10,0x04,0x73,0x03, + 0xF7,0x00,0x5E,0x01,0x6D,0xFE,0xDA,0xFE,0x79,0x06,0xFC,0x07, + 0x3D,0x05,0xD8,0x03,0x97,0x03,0x6A,0x01,0xF7,0x03,0xC5,0x05, + 0x38,0x02,0xE6,0xFE,0x75,0xFD,0x82,0xFC,0xBB,0xFA,0xF8,0xFB, + 0xEF,0xF8,0x75,0xF5,0xA3,0xF7,0x67,0xFB,0x5A,0xFC,0xF3,0xFC, + 0x27,0xFE,0x6A,0xFC,0x02,0xFF,0x24,0x04,0x48,0x05,0x06,0x05, + 0x56,0x04,0x81,0x01,0xB2,0xFF,0xA8,0xFE,0x13,0x01,0x16,0x07, + 0x39,0x08,0xCC,0x05,0xFC,0x03,0x58,0x02,0x2B,0x02,0x53,0x04, + 0x1A,0x05,0x72,0x02,0xC0,0xFE,0x5E,0xFC,0x0E,0xFB,0xD6,0xFA, + 0x1E,0xFB,0xAF,0xF9,0x33,0xF7,0x8E,0xF7,0x95,0xF9,0xE4,0xFB, + 0x58,0xFE,0x51,0xFE,0xCB,0xFD,0xBF,0xFF,0xBE,0x02,0x8F,0x04, + 0xBB,0x05,0xAA,0x04,0xE4,0x01,0x04,0xFE,0xB6,0xFD,0x3C,0x04, + 0x16,0x09,0xDB,0x08,0x55,0x06,0xD6,0x02,0x48,0x00,0x06,0x02, + 0x7B,0x05,0x35,0x05,0x66,0x01,0x6B,0xFD,0x01,0xFA,0x00,0xF9, + 0xFC,0xFA,0x83,0xFB,0x56,0xF9,0x53,0xF7,0xB8,0xF7,0x84,0xF9, + 0xEB,0xFC,0xBC,0xFF,0x3A,0xFF,0x03,0xFF,0xAB,0x00,0xD7,0x02, + 0x3A,0x05,0x52,0x06,0xBB,0x03,0xFC,0xFE,0x5A,0xFD,0xD9,0x01, + 0x54,0x08,0x2C,0x0A,0x29,0x08,0xD7,0x03,0x00,0x00,0xD7,0x00, + 0x38,0x04,0x8E,0x05,0xD8,0x02,0x4C,0xFE,0xE1,0xF9,0x1E,0xF8, + 0xC0,0xF9,0x3A,0xFB,0x6D,0xFA,0xDE,0xF8,0x97,0xF7,0x5E,0xF8, + 0xF2,0xFB,0xF7,0xFE,0x0B,0x00,0x3E,0x00,0xEF,0x00,0xF5,0x01, + 0x40,0x04,0xEA,0x05,0x56,0x03,0x01,0x00,0xFD,0xFE,0x68,0x02, + 0x24,0x08,0xEF,0x09,0x39,0x08,0x29,0x04,0x96,0x00,0xA9,0x00, + 0x50,0x03,0x9B,0x04,0x45,0x02,0x58,0xFE,0xF7,0xF9,0xC2,0xF7, + 0xF6,0xF8,0x65,0xFA,0x3B,0xFA,0x5F,0xF9,0x6E,0xF8,0x08,0xF9, + 0xA3,0xFB,0x8C,0xFE,0x2E,0x00,0x25,0x01,0xEA,0x01,0x86,0x02, + 0x27,0x04,0xE0,0x03,0xF2,0x01,0x89,0x00,0x4F,0x01,0xF7,0x05, + 0x32,0x09,0xD8,0x08,0xB3,0x06,0x33,0x03,0xEE,0x00,0xE0,0x01, + 0xC8,0x03,0xE5,0x02,0x0B,0x00,0x86,0xFC,0xCD,0xF8,0xE2,0xF7, + 0x24,0xF9,0x9E,0xF9,0xA7,0xF9,0x0B,0xF9,0xD5,0xF8,0x5E,0xFA, + 0xBF,0xFC,0x42,0xFF,0xBA,0x00,0xD4,0x01,0xCA,0x02,0x80,0x03, + 0x46,0x04,0xE9,0x02,0xD9,0x00,0x05,0x01,0x2C,0x04,0x46,0x08, + 0x68,0x09,0x13,0x08,0xBA,0x04,0x6B,0x01,0x03,0x01,0x93,0x02, + 0x61,0x03,0x9C,0x01,0x3D,0xFE,0x1C,0xFA,0xB8,0xF7,0x26,0xF8, + 0x23,0xF9,0xF8,0xF9,0xF7,0xF9,0xE5,0xF8,0x82,0xF9,0x8E,0xFB, + 0x9A,0xFD,0x6E,0x00,0x1E,0x02,0x83,0x02,0x3B,0x03,0xED,0x03, + 0xD7,0x02,0x35,0x01,0x53,0x01,0xC5,0x02,0xAA,0x06,0x7E,0x09, + 0xA7,0x08,0x28,0x06,0x26,0x03,0x1D,0x01,0x93,0x01,0x31,0x03, + 0x84,0x02,0xE9,0xFF,0x48,0xFC,0xBE,0xF8,0x6B,0xF7,0x4E,0xF8, + 0x54,0xF9,0x0C,0xFA,0xC2,0xF9,0x42,0xF9,0x77,0xFA,0x4D,0xFC, + 0xDF,0xFE,0x52,0x01,0xA4,0x02,0x40,0x03,0xBB,0x03,0x13,0x03, + 0x82,0x01,0x26,0x01,0xB2,0x01,0xEB,0x04,0xE6,0x08,0x58,0x09, + 0xAF,0x07,0xFD,0x04,0xF6,0x01,0x08,0x01,0x9F,0x02,0x17,0x03, + 0x56,0x01,0x8D,0xFE,0x8E,0xFA,0x75,0xF7,0x52,0xF7,0x48,0xF8, + 0x43,0xF9,0x21,0xFA,0xFC,0xF9,0x0A,0xFA,0xDB,0xFA,0xB6,0xFC, + 0x64,0xFF,0xC5,0x01,0x8C,0x03,0x93,0x04,0x37,0x04,0x6E,0x02, + 0xC6,0x00,0x94,0x00,0x6C,0x02,0x07,0x07,0x32,0x0A,0x6D,0x09, + 0x1D,0x07,0x8B,0x03,0xBA,0x00,0x0F,0x01,0xF6,0x02,0xCF,0x02, + 0xF9,0x00,0x81,0xFD,0xEE,0xF8,0x81,0xF6,0xE6,0xF6,0x27,0xF8, + 0xB3,0xF9,0xC4,0xFA,0x60,0xFA,0x23,0xFA,0xEF,0xFA,0x20,0xFD, + 0xCF,0xFF,0xB0,0x02,0xA4,0x04,0x25,0x05,0x1F,0x04,0xE2,0x01, + 0x70,0x00,0x4A,0x00,0x0D,0x03,0x0A,0x08,0x53,0x0A,0x6C,0x09, + 0xE6,0x06,0xDD,0x02,0x4C,0x00,0x01,0x01,0x96,0x02,0x9A,0x02, + 0x1F,0x01,0x54,0xFD,0x97,0xF8,0x1C,0xF6,0x0A,0xF6,0x9C,0xF7, + 0xBE,0xF9,0x04,0xFB,0x2B,0xFB,0xAC,0xFA,0x14,0xFB,0xC8,0xFC, + 0x6C,0xFF,0xC7,0x02,0x48,0x05,0x1F,0x06,0x09,0x05,0x5F,0x02, + 0x01,0x00,0x7D,0xFF,0xB6,0x01,0xEB,0x06,0x43,0x0A,0x19,0x0A, + 0xDF,0x07,0x94,0x03,0x29,0x00,0xE0,0xFF,0x84,0x01,0x96,0x02, + 0x40,0x02,0x3F,0xFF,0x4E,0xFA,0x5A,0xF6,0x03,0xF5,0xE2,0xF5, + 0x9D,0xF8,0x3F,0xFB,0x39,0xFC,0x12,0xFC,0x49,0xFB,0xAE,0xFB, + 0xAF,0xFD,0x2B,0x01,0xBB,0x04,0xE2,0x06,0xB9,0x06,0x16,0x04, + 0xCF,0x00,0xA2,0xFE,0xE2,0xFE,0x67,0x03,0xA5,0x08,0x91,0x0A, + 0x35,0x0A,0xCA,0x06,0xCA,0x01,0x26,0xFF,0x93,0xFF,0x0D,0x01, + 0xAB,0x02,0x6A,0x02,0x88,0xFE,0x7A,0xF9,0xA1,0xF5,0xD4,0xF3, + 0x42,0xF5,0xE2,0xF8,0xC6,0xFB,0x82,0xFD,0x78,0xFD,0x41,0xFC, + 0x1C,0xFC,0xDE,0xFD,0x36,0x01,0xED,0x04,0x8D,0x07,0x4A,0x07, + 0x48,0x04,0xB4,0x00,0xB9,0xFD,0xB9,0xFD,0x96,0x02,0xC8,0x07, + 0x9B,0x0A,0x46,0x0B,0xE2,0x07,0x70,0x02,0x03,0xFF,0x55,0xFE, + 0x7D,0xFF,0xF6,0x01,0xF2,0x02,0x24,0x00,0x91,0xFB,0xCB,0xF6, + 0x38,0xF3,0x95,0xF3,0xBB,0xF6,0x6A,0xFA,0x1F,0xFE,0x66,0xFF, + 0x66,0xFE,0x66,0xFD,0x50,0xFD,0x01,0xFF,0x74,0x02,0x1B,0x06, + 0xA8,0x07,0x9F,0x06,0x92,0x03,0x5E,0xFF,0xEF,0xFC,0x17,0xFF, + 0x9D,0x03,0xF0,0x07,0x69,0x0B,0x20,0x0B,0xE7,0x06,0x15,0x02, + 0x7E,0xFE,0xE5,0xFC,0x4D,0xFE,0x8F,0x00,0x0D,0x01,0x8D,0xFF, + 0xDA,0xFB,0x10,0xF7,0xE2,0xF3,0xAD,0xF3,0xC0,0xF5,0xF1,0xF9, + 0x43,0xFE,0x78,0x00,0x2A,0x01,0x84,0x00,0x4D,0xFF,0x64,0xFF, + 0x2A,0x01,0xB9,0x03,0x10,0x06,0xA5,0x06,0xEC,0x04,0x8A,0x01, + 0xD0,0xFE,0x40,0xFF,0x80,0x01,0xCD,0x04,0x6A,0x08,0x7D,0x09, + 0xC4,0x07,0x88,0x04,0xA2,0x00,0x98,0xFD,0xB0,0xFC,0x22,0xFD, + 0xE2,0xFD,0x4F,0xFE,0x5B,0xFD,0x0C,0xFB,0xA8,0xF8,0xCF,0xF6, + 0x4F,0xF6,0x00,0xF8,0xFF,0xFA,0x70,0xFE,0x7E,0x01,0xFD,0x02, + 0xBC,0x02,0xB7,0x01,0x13,0x01,0x4B,0x01,0x90,0x02,0xE8,0x03, + 0xEE,0x03,0xED,0x02,0x46,0x01,0x46,0x00,0xA8,0x00,0xB6,0x01, + 0x9B,0x03,0x5E,0x05,0xE4,0x05,0x15,0x05,0x62,0x03,0x32,0x01, + 0x49,0xFF,0x42,0xFE,0xB6,0xFD,0x93,0xFD,0x8E,0xFD,0x24,0xFD, + 0x50,0xFC,0x6A,0xFB,0x74,0xFA,0xCE,0xF9,0x2F,0xFA,0x99,0xFB, + 0xD8,0xFD,0x54,0x00,0xB7,0x01,0xF0,0x01,0xA3,0x01,0xE9,0x00, + 0xE8,0x00,0xA6,0x01,0x20,0x02,0x1F,0x02,0xAE,0x01,0xE9,0x00, + 0x45,0x00,0x61,0x00,0xAC,0x00,0x59,0x01,0x86,0x02,0x64,0x03, + 0xC6,0x03,0x77,0x03,0x7A,0x02,0x44,0x01,0x57,0x00,0xA4,0xFF, + 0x38,0xFF,0x14,0xFF,0xEB,0xFE,0x9E,0xFE,0x58,0xFE,0xEB,0xFD, + 0x2B,0xFD,0x6F,0xFC,0xF6,0xFB,0x9C,0xFC,0x19,0xFE,0x31,0xFF, + 0xBE,0xFF,0x04,0x00,0xF4,0xFF,0x06,0x00,0x69,0x00,0x83,0x00, + 0x8D,0x00,0x6C,0x00,0xDC,0xFF,0x67,0xFF,0x26,0xFF,0x10,0xFF, + 0x62,0xFF,0x00,0x00,0xED,0x00,0x21,0x02,0x2A,0x03,0x9B,0x03, + 0x85,0x03,0x3F,0x03,0xE6,0x02,0x94,0x02,0x40,0x02,0xDC,0x01, + 0x50,0x01,0x9C,0x00,0xD5,0xFF,0x10,0xFF,0x7D,0xFE,0xB5,0xFD, + 0xD9,0xFC,0xC2,0xFC,0x19,0xFD,0x81,0xFD,0xE8,0xFD,0x33,0xFE, + 0x60,0xFE,0x71,0xFE,0x94,0xFE,0xB5,0xFE,0xC8,0xFE,0xCF,0xFE, + 0xC6,0xFE,0xB7,0xFE,0xBC,0xFE,0xEC,0xFE,0x37,0xFF,0xA1,0xFF, + 0x3C,0x00,0x15,0x01,0x1A,0x02,0x51,0x03,0x5E,0x04,0xCD,0x04, + 0xBF,0x04,0x5B,0x04,0xE1,0x03,0x67,0x03,0xD2,0x02,0x28,0x02, + 0x61,0x01,0x79,0x00,0x99,0xFF,0xD5,0xFE,0x09,0xFE,0x59,0xFD, + 0x01,0xFD,0xDB,0xFC,0xD5,0xFC,0xFD,0xFC,0x2B,0xFD,0x63,0xFD, + 0xB4,0xFD,0xEA,0xFD,0x12,0xFE,0x3E,0xFE,0x55,0xFE,0x62,0xFE, + 0x7D,0xFE,0xAA,0xFE,0xE5,0xFE,0x41,0xFF,0xAA,0xFF,0x0F,0x00, + 0x91,0x00,0x2A,0x01,0xDC,0x01,0xA9,0x02,0x71,0x03,0x15,0x04, + 0x79,0x04,0x88,0x04,0x4C,0x04,0xEE,0x03,0x7E,0x03,0xE2,0x02, + 0x2A,0x02,0x6D,0x01,0x8C,0x00,0xAC,0xFF,0xE7,0xFE,0x26,0xFE, + 0x83,0xFD,0x10,0xFD,0xAC,0xFC,0x79,0xFC,0x8F,0xFC,0xBA,0xFC, + 0xF9,0xFC,0x52,0xFD,0xA6,0xFD,0xDD,0xFD,0x18,0xFE,0x51,0xFE, + 0x76,0xFE,0xAB,0xFE,0xEB,0xFE,0x2F,0xFF,0x84,0xFF,0xE8,0xFF, + 0x49,0x00,0xAA,0x00,0x0E,0x01,0x6F,0x01,0xF0,0x01,0x93,0x02, + 0x33,0x03,0xC2,0x03,0x2E,0x04,0x5C,0x04,0x3C,0x04,0xF7,0x03, + 0x8C,0x03,0xEA,0x02,0x2E,0x02,0x5C,0x01,0x76,0x00,0x9D,0xFF, + 0xD8,0xFE,0x1E,0xFE,0x82,0xFD,0xFC,0xFC,0x90,0xFC,0x4B,0xFC, + 0x3B,0xFC,0x5C,0xFC,0xA8,0xFC,0x16,0xFD,0x84,0xFD,0xE7,0xFD, + 0x44,0xFE,0x90,0xFE,0xC8,0xFE,0x03,0xFF,0x3D,0xFF,0x7C,0xFF, + 0xCB,0xFF,0x26,0x00,0x84,0x00,0xD1,0x00,0x14,0x01,0x46,0x01, + 0x85,0x01,0xED,0x01,0x63,0x02,0xEE,0x02,0x73,0x03,0xCD,0x03, + 0xF4,0x03,0xDD,0x03,0x95,0x03,0x2F,0x03,0xA2,0x02,0xEE,0x01, + 0x27,0x01,0x54,0x00,0x82,0xFF,0xBE,0xFE,0x13,0xFE,0x71,0xFD, + 0xE9,0xFC,0x7F,0xFC,0x34,0xFC,0x2E,0xFC,0x5F,0xFC,0xBB,0xFC, + 0x39,0xFD,0xC0,0xFD,0x2F,0xFE,0x8B,0xFE,0xDE,0xFE,0x17,0xFF, + 0x40,0xFF,0x72,0xFF,0xB2,0xFF,0xFA,0xFF,0x56,0x00,0xAD,0x00, + 0xEE,0x00,0x1F,0x01,0x43,0x01,0x57,0x01,0x8F,0x01,0xF3,0x01, + 0x65,0x02,0xE8,0x02,0x5E,0x03,0xA5,0x03,0xA6,0x03,0x6F,0x03, + 0x0F,0x03,0x8A,0x02,0xE7,0x01,0x33,0x01,0x76,0x00,0xBC,0xFF, + 0x0D,0xFF,0x6A,0xFE,0xD8,0xFD,0x4E,0xFD,0xDB,0xFC,0x81,0xFC, + 0x5C,0xFC,0x77,0xFC,0xC0,0xFC,0x36,0xFD,0xBA,0xFD,0x32,0xFE, + 0x96,0xFE,0xE6,0xFE,0x25,0xFF,0x4C,0xFF,0x74,0xFF,0xA7,0xFF, + 0xEE,0xFF,0x44,0x00,0x9A,0x00,0xE4,0x00,0x15,0x01,0x2D,0x01, + 0x37,0x01,0x41,0x01,0x77,0x01,0xD4,0x01,0x47,0x02,0xC3,0x02, + 0x27,0x03,0x65,0x03,0x56,0x03,0x12,0x03,0xAE,0x02,0x23,0x02, + 0x88,0x01,0xE4,0x00,0x38,0x00,0x93,0xFF,0xF7,0xFE,0x62,0xFE, + 0xCB,0xFD,0x4D,0xFD,0xDF,0xFC,0x91,0xFC,0x8D,0xFC,0xB7,0xFC, + 0x14,0xFD,0x91,0xFD,0x0C,0xFE,0x70,0xFE,0xB9,0xFE,0xF9,0xFE, + 0x20,0xFF,0x42,0xFF,0x79,0xFF,0xBE,0xFF,0x14,0x00,0x73,0x00, + 0xCB,0x00,0x0B,0x01,0x2E,0x01,0x38,0x01,0x33,0x01,0x3F,0x01, + 0x73,0x01,0xD6,0x01,0x4D,0x02,0xC1,0x02,0x18,0x03,0x3D,0x03, + 0x14,0x03,0xB6,0x02,0x47,0x02,0xC6,0x01,0x3C,0x01,0xB7,0x00, + 0x31,0x00,0x9C,0xFF,0x0B,0xFF,0x70,0xFE,0xCB,0xFD,0x41,0xFD, + 0xD2,0xFC,0x8D,0xFC,0x93,0xFC,0xD8,0xFC,0x44,0xFD,0xC5,0xFD, + 0x3F,0xFE,0x8A,0xFE,0xC3,0xFE,0xF7,0xFE,0x10,0xFF,0x3D,0xFF, + 0x86,0xFF,0xDD,0xFF,0x45,0x00,0xB3,0x00,0x04,0x01,0x2E,0x01, + 0x40,0x01,0x32,0x01,0x25,0x01,0x37,0x01,0x6D,0x01,0xD8,0x01, + 0x56,0x02,0xCF,0x02,0x16,0x03,0x27,0x03,0xEE,0x02,0x71,0x02, + 0xF7,0x01,0x7C,0x01,0x08,0x01,0xA3,0x00,0x34,0x00,0xAB,0xFF, + 0x19,0xFF,0x6E,0xFE,0xBC,0xFD,0x22,0xFD,0xB6,0xFC,0x83,0xFC, + 0x95,0xFC,0xF4,0xFC,0x6B,0xFD,0xEC,0xFD,0x58,0xFE,0x93,0xFE, + 0xBA,0xFE,0xE1,0xFE,0x06,0xFF,0x40,0xFF,0xA1,0xFF,0x0F,0x00, + 0x81,0x00,0xEC,0x00,0x30,0x01,0x43,0x01,0x39,0x01,0x1C,0x01, + 0x0D,0x01,0x23,0x01,0x66,0x01,0xE1,0x01,0x62,0x02,0xCF,0x02, + 0x08,0x03,0x02,0x03,0xB9,0x02,0x37,0x02,0xC8,0x01,0x66,0x01, + 0x0D,0x01,0xBF,0x00,0x62,0x00,0xD2,0xFF,0x26,0xFF,0x62,0xFE, + 0x99,0xFD,0xFD,0xFC,0x99,0xFC,0x86,0xFC,0xAD,0xFC,0x1E,0xFD, + 0x9E,0xFD,0x06,0xFE,0x58,0xFE,0x79,0xFE,0x96,0xFE,0xB6,0xFE, + 0xF0,0xFE,0x48,0xFF,0xC1,0xFF,0x3D,0x00,0xAC,0x00,0x02,0x01, + 0x2B,0x01,0x2F,0x01,0x19,0x01,0x0E,0x01,0x0D,0x01,0x41,0x01, + 0x90,0x01,0xFE,0x01,0x7E,0x02,0xC6,0x02,0xE8,0x02,0xC6,0x02, + 0x76,0x02,0x01,0x02,0xA0,0x01,0x62,0x01,0x24,0x01,0xED,0x00, + 0x86,0x00,0xEC,0xFF,0x29,0xFF,0x54,0xFE,0x85,0xFD,0xF0,0xFC, + 0xA4,0xFC,0xA3,0xFC,0xE1,0xFC,0x40,0xFD,0xB0,0xFD,0xFA,0xFD, + 0x2E,0xFE,0x45,0xFE,0x5F,0xFE,0x9D,0xFE,0xF4,0xFE,0x6F,0xFF, + 0xF3,0xFF,0x6E,0x00,0xC8,0x00,0xFB,0x00,0x11,0x01,0x0B,0x01, + 0x05,0x01,0x0A,0x01,0x28,0x01,0x65,0x01,0xB1,0x01,0x08,0x02, + 0x63,0x02,0x92,0x02,0x9D,0x02,0x83,0x02,0x42,0x02,0xF2,0x01, + 0xAE,0x01,0x87,0x01,0x57,0x01,0x11,0x01,0xA0,0x00,0xF0,0xFF, + 0x26,0xFF,0x53,0xFE,0x94,0xFD,0x14,0xFD,0xD3,0xFC,0xDA,0xFC, + 0x08,0xFD,0x4D,0xFD,0x9C,0xFD,0xC7,0xFD,0xF1,0xFD,0x17,0xFE, + 0x4B,0xFE,0xA9,0xFE,0x19,0xFF,0x9C,0xFF,0x10,0x00,0x6F,0x00, + 0xB2,0x00,0xCE,0x00,0xE9,0x00,0xF5,0x00,0x00,0x01,0x21,0x01, + 0x44,0x01,0x77,0x01,0xB1,0x01,0xE6,0x01,0x25,0x02,0x53,0x02, + 0x61,0x02,0x64,0x02,0x4A,0x02,0x19,0x02,0xE1,0x01,0xAB,0x01, + 0x6F,0x01,0x11,0x01,0x97,0x00,0xF0,0xFF,0x2D,0xFF,0x71,0xFE, + 0xC6,0xFD,0x4A,0xFD,0x09,0xFD,0xF8,0xFC,0x19,0xFD,0x35,0xFD, + 0x7A,0xFD,0xC2,0xFD,0xC2,0xFD,0x0C,0xFE,0x54,0xFE,0xC3,0xFE, + 0x48,0xFF,0x86,0xFF,0xEA,0xFF,0x4E,0x00,0x90,0x00,0xAC,0x00, + 0xD0,0x00,0xF1,0x00,0xFE,0x00,0x21,0x01,0x40,0x01,0x69,0x01, + 0xAD,0x01,0xB8,0x01,0xF6,0x01,0x67,0x02,0x75,0x02,0xA1,0x02, + 0x79,0x02,0x09,0x02,0x1F,0x02,0xEA,0x01,0x4C,0x01,0xFB,0x00, + 0xAA,0x00,0xFD,0xFF,0x1A,0xFF,0x54,0xFE,0x93,0xFD,0xFF,0xFC, + 0xC4,0xFC,0xF1,0xFC,0x1E,0xFD,0x30,0xFD,0xD0,0xFD,0xE3,0xFD, + 0x2D,0xFE,0x77,0xFE,0xA3,0xFE,0x7A,0xFF,0xAE,0xFF,0x8E,0xFF, + 0x08,0xFF,0xF1,0xFF,0x06,0x00,0xE5,0xFF,0x77,0x00,0xED,0xFF, + 0xAD,0x00,0x27,0x00,0x37,0x00,0x7F,0x00,0x5D,0x01,0xD3,0x02, + 0x49,0x04,0x3C,0x06,0xF8,0x04,0x21,0x05,0xCD,0x03,0xCA,0x01, + 0x06,0x02,0x4E,0x01,0xA3,0x01,0x25,0x01,0xC1,0xFF,0x8E,0xFD, + 0x5B,0xFB,0xDC,0xFA,0x3C,0xFA,0x23,0xFB,0xE5,0xFC,0x31,0xFC, + 0xBF,0xFC,0xFE,0xFC,0xC4,0xFC,0xBD,0xFE,0x74,0xFF,0xCA,0x00, + 0x66,0x01,0x59,0x01,0xF7,0x00,0x9B,0x00,0x52,0x01,0x78,0x00, + 0x3B,0x01,0xB9,0x00,0x74,0xFF,0xE2,0xFE,0x39,0xFD,0x4D,0xFE, + 0x0C,0x00,0xA1,0x02,0xB6,0x06,0xC8,0x07,0x72,0x07,0x9C,0x06, + 0x58,0x03,0x09,0x02,0xBC,0x01,0x91,0x01,0xC3,0x01,0xA9,0x00, + 0x2D,0xFE,0x7A,0xFA,0xA5,0xF8,0x82,0xF7,0xA7,0xF8,0xD5,0xFA, + 0x30,0xFC,0x75,0xFD,0xF6,0xFC,0x61,0xFD,0x46,0xFE,0xD9,0xFF, + 0x5D,0x02,0xC7,0x03,0xEE,0x03,0x10,0x03,0x00,0x02,0xFD,0x00, + 0x40,0x01,0x0C,0x01,0x90,0x00,0x56,0xFF,0x89,0xFD,0xDD,0xFB, + 0xEF,0xFB,0x7C,0xFE,0xBF,0x01,0x43,0x07,0x51,0x08,0x32,0x08, + 0x86,0x06,0x1D,0x03,0x5D,0x02,0x0B,0x02,0x43,0x03,0x59,0x02, + 0x6C,0x01,0x5D,0xFD,0x07,0xF9,0x50,0xF7,0x7E,0xF5,0x40,0xF8, + 0xCD,0xFA,0xF6,0xFB,0xFA,0xFC,0xBB,0xFC,0x73,0xFC,0x35,0xFD, + 0x65,0x00,0x72,0x02,0x67,0x04,0x9F,0x05,0x3A,0x03,0x16,0x03, + 0x5D,0x01,0xBC,0x00,0x3F,0x01,0x25,0x00,0xCE,0xFF,0xE2,0xFC, + 0x1A,0xFF,0x4B,0x01,0xB4,0x04,0x5C,0x08,0x99,0x07,0xC3,0x06, + 0xCF,0x02,0x42,0x01,0x54,0x00,0x5E,0x01,0x79,0x02,0xEB,0x00, + 0xCC,0xFE,0xE6,0xF9,0x01,0xF7,0xC4,0xF5,0xD4,0xF6,0x3C,0xFA, + 0x2F,0xFC,0xD2,0xFD,0xEE,0xFD,0xAF,0xFC,0x6A,0xFD,0x07,0xFF, + 0xD3,0x01,0x8A,0x04,0x98,0x05,0x07,0x05,0x26,0x03,0x7F,0x01, + 0x39,0x00,0xB5,0xFF,0xE3,0xFF,0x1A,0xFF,0x3F,0xFD,0x74,0xFF, + 0x4C,0x02,0xB0,0x04,0x0E,0x08,0x1B,0x08,0xFB,0x05,0xFB,0x03, + 0x2D,0x02,0x0E,0x01,0x78,0x02,0x7E,0x02,0x7C,0x00,0x31,0xFE, + 0xAC,0xF9,0x31,0xF6,0xE9,0xF5,0x8E,0xF6,0x0A,0xF9,0xCC,0xFB, + 0x64,0xFC,0xE4,0xFC,0x03,0xFD,0x23,0xFD,0x56,0xFF,0x62,0x02, + 0x7A,0x04,0x1E,0x06,0xCF,0x05,0xC2,0x03,0x4C,0x02,0x0A,0x01, + 0x2E,0x00,0xB7,0x00,0x04,0x00,0xD9,0xFC,0xE4,0xFD,0x73,0x01, + 0xF2,0x02,0x62,0x07,0xD4,0x08,0xC7,0x05,0x79,0x04,0xEB,0x01, + 0x3C,0x00,0x23,0x02,0x45,0x03,0x8A,0x01,0xFB,0xFF,0x8B,0xFB, + 0x66,0xF6,0x12,0xF6,0xEE,0xF5,0xC0,0xF7,0xE7,0xFB,0x8E,0xFC, + 0x76,0xFC,0x39,0xFD,0x22,0xFC,0x99,0xFD,0x88,0x01,0x9A,0x03, + 0xBA,0x05,0x9F,0x06,0xF7,0x03,0x63,0x02,0x6A,0x01,0xE6,0xFF, + 0xED,0x00,0x25,0x01,0x74,0xFE,0x27,0xFD,0x43,0x01,0x7E,0x02, + 0x6E,0x05,0x89,0x09,0x04,0x06,0x46,0x05,0x40,0x03,0x6C,0x00, + 0x73,0x01,0xF8,0x02,0x7A,0x01,0xF2,0xFF,0x70,0xFD,0x3A,0xF7, + 0x78,0xF6,0x16,0xF6,0x7D,0xF6,0xB6,0xFA,0x6D,0xFC,0x23,0xFC, + 0x9A,0xFD,0xD2,0xFC,0xD3,0xFC,0xEE,0x00,0x82,0x02,0xCA,0x04, + 0xAE,0x06,0xB0,0x04,0xDC,0x02,0x5F,0x02,0xDB,0xFF,0x88,0x00, + 0x64,0x01,0xD9,0xFE,0x03,0xFD,0xC3,0xFF,0x80,0x02,0xBD,0x03, + 0xA6,0x09,0x16,0x07,0x33,0x05,0x87,0x04,0x6C,0x00,0x38,0x01, + 0xB7,0x02,0x3F,0x02,0x93,0x00,0x15,0xFF,0xDD,0xF8,0x95,0xF6, + 0x71,0xF6,0x84,0xF5,0x6D,0xF9,0x30,0xFC,0xD4,0xFB,0x3B,0xFD, + 0x54,0xFD,0x08,0xFC,0xD1,0xFF,0xED,0x01,0xA4,0x03,0x79,0x06, + 0x7C,0x05,0x07,0x03,0xFB,0x02,0xA5,0x00,0x19,0x00,0x0B,0x02, + 0x11,0x00,0xC5,0xFD,0x4D,0xFD,0x04,0x01,0x1E,0x02,0x2C,0x07, + 0x4F,0x09,0x14,0x06,0xF9,0x05,0xB8,0x01,0x70,0x00,0x89,0x01, + 0x97,0x02,0xB2,0x01,0xE4,0x00,0x85,0xFC,0x60,0xF7,0xAE,0xF6, + 0xF1,0xF4,0x11,0xF7,0x11,0xFB,0x58,0xFC,0x81,0xFC,0xDF,0xFD, + 0x1A,0xFC,0x58,0xFD,0xD7,0x00,0x45,0x02,0x96,0x05,0x88,0x06, + 0x76,0x04,0xCB,0x02,0x14,0x02,0x89,0xFF,0xC5,0x01,0x4B,0x01, + 0x20,0xFF,0xC1,0xFD,0xC6,0xFC,0x2B,0x01,0xD8,0x02,0xEB,0x07, + 0x76,0x08,0xC4,0x06,0xF8,0x04,0x17,0x01,0xF1,0x00,0x38,0x01, + 0xF7,0x02,0x19,0x02,0x85,0x00,0xEC,0xFB,0xC3,0xF7,0xF6,0xF5, + 0x4A,0xF5,0xD4,0xF7,0x48,0xFB,0xE2,0xFC,0x04,0xFD,0x94,0xFD, + 0xEF,0xFB,0xA5,0xFD,0x8B,0x00,0xDA,0x02,0xD1,0x05,0x30,0x06, + 0x26,0x04,0x90,0x02,0xE0,0x00,0xD7,0xFF,0x29,0x01,0x2A,0x01, + 0x34,0x00,0x70,0xFC,0x09,0xFE,0xD3,0x00,0xD4,0x02,0xD0,0x08, + 0x83,0x08,0xFB,0x06,0x92,0x04,0x4A,0x01,0x6C,0x00,0xA2,0x01, + 0xB9,0x02,0xE5,0x01,0x8A,0x00,0x64,0xFB,0x69,0xF7,0xDC,0xF5, + 0x31,0xF5,0xF1,0xF7,0xC0,0xFB,0xDD,0xFC,0x21,0xFD,0x83,0xFD, + 0xF7,0xFB,0xE9,0xFD,0x06,0x01,0x47,0x03,0x59,0x06,0x48,0x06, + 0x52,0x04,0x68,0x02,0xE5,0x00,0xD1,0xFF,0xCA,0x00,0x93,0x00, + 0xAB,0x00,0x33,0xFD,0x48,0xFC,0x9C,0x01,0x51,0x01,0x6B,0x07, + 0x51,0x09,0x15,0x06,0xB5,0x05,0xF2,0x01,0x0D,0x00,0x3B,0x01, + 0x0D,0x03,0x31,0x01,0x67,0x01,0xD1,0xFC,0x68,0xF7,0x01,0xF7, + 0x11,0xF5,0x25,0xF7,0xB3,0xFB,0xF8,0xFC,0xF8,0xFC,0x63,0xFE, + 0x27,0xFC,0x21,0xFD,0x16,0x01,0x97,0x02,0xD4,0x05,0x8C,0x06, + 0xF6,0x03,0x41,0x02,0x75,0x01,0xFE,0xFE,0xA5,0x00,0x69,0x00, + 0x66,0xFF,0xA5,0xFE,0x5D,0xFC,0xD0,0x01,0x73,0x02,0xBB,0x06, + 0x23,0x09,0x1E,0x06,0xBC,0x05,0xAF,0x01,0x02,0x01,0xD0,0x00, + 0x97,0x02,0xA6,0x01,0x26,0x00,0x24,0xFD,0xCA,0xF7,0xD5,0xF6, + 0xE0,0xF5,0x8A,0xF7,0x72,0xFB,0x31,0xFD,0x10,0xFD,0xF5,0xFD, + 0xEA,0xFC,0x8C,0xFD,0xF3,0x00,0xE2,0x02,0x0E,0x05,0x1F,0x06, + 0x09,0x04,0x04,0x02,0x14,0x01,0x73,0xFF,0x6F,0x00,0x61,0x00, + 0x0A,0xFF,0x27,0xFD,0x46,0xFD,0x54,0x01,0x32,0x03,0xA9,0x07, + 0x2E,0x09,0x7F,0x06,0x44,0x05,0xF3,0x01,0xD4,0x00,0x0D,0x02, + 0x8F,0x02,0xCA,0x01,0xF7,0xFF,0x9B,0xFB,0x2C,0xF7,0x38,0xF6, + 0xAC,0xF5,0xBB,0xF7,0xDA,0xFB,0xA6,0xFC,0x13,0xFD,0x94,0xFD, + 0xA8,0xFC,0x7E,0xFE,0xC0,0x01,0xDB,0x03,0xE7,0x05,0x3F,0x06, + 0xA5,0x03,0x1C,0x02,0x64,0x00,0x84,0xFF,0x8F,0x00,0xC5,0xFF, + 0xAB,0xFF,0x3E,0xFC,0x78,0xFD,0xF4,0x01,0x06,0x03,0xD8,0x08, + 0x06,0x09,0x36,0x06,0xCC,0x04,0x53,0x01,0x6B,0x00,0x29,0x02, + 0xCD,0x02,0xF4,0x00,0x7E,0xFF,0x6A,0xFA,0x34,0xF6,0x47,0xF6, + 0xC5,0xF5,0xBF,0xF8,0xE0,0xFC,0xCB,0xFC,0x75,0xFD,0xC9,0xFD, + 0xBC,0xFC,0x60,0xFF,0xBC,0x02,0x83,0x04,0x4E,0x06,0xC7,0x05, + 0x4C,0x02,0x77,0x01,0x01,0x00,0xEF,0xFF,0x74,0x00,0x2F,0xFF, + 0xCA,0xFD,0x1B,0xFB,0x74,0x00,0xFF,0x02,0x71,0x06,0xE3,0x09, + 0x34,0x07,0x0E,0x06,0x23,0x03,0xFE,0x00,0x1E,0x01,0x04,0x04, + 0x21,0x02,0x34,0x00,0x51,0xFC,0x27,0xF6,0x24,0xF5,0x87,0xF5, + 0x5D,0xF7,0x80,0xFB,0x19,0xFD,0x06,0xFC,0x32,0xFD,0x01,0xFD, + 0x0F,0xFF,0x87,0x03,0xC6,0x05,0x33,0x06,0xF6,0x05,0x37,0x02, + 0x42,0x01,0x5D,0x00,0xED,0xFF,0x1E,0x01,0x86,0xFE,0x6E,0xFC, + 0x14,0xF7,0x10,0xFD,0x06,0x04,0x99,0x07,0x0A,0x0E,0xD6,0x0A, + 0x71,0x05,0x48,0x03,0xEB,0x01,0xE1,0x02,0x36,0x07,0x11,0x04, + 0x9A,0xFE,0x5E,0xF9,0x8E,0xF2,0xD3,0xF1,0x59,0xF5,0x8A,0xF7, + 0x40,0xFA,0x89,0xFB,0x69,0xF9,0x10,0xFB,0x5F,0xFE,0x41,0x02, + 0x8E,0x07,0x8B,0x09,0xF9,0x06,0x89,0x05,0x44,0x02,0x71,0x01, + 0x9C,0x02,0xB0,0x01,0x9F,0xFF,0x13,0xFC,0xE5,0xF8,0x1B,0xF4, + 0xD2,0xFC,0x79,0x05,0x6B,0x09,0xA1,0x0E,0x59,0x09,0x5D,0x03, + 0xF3,0x02,0x9C,0x03,0xDC,0x05,0xA1,0x09,0xFA,0x03,0x79,0xFC, + 0xF4,0xF6,0x33,0xF1,0xB1,0xF2,0x6F,0xF7,0x75,0xF8,0x84,0xF9, + 0xEF,0xF8,0x50,0xF7,0x50,0xFB,0xC3,0x00,0x1A,0x05,0xD5,0x08, + 0xD3,0x08,0xF7,0x04,0x4D,0x04,0xDE,0x02,0xBE,0x02,0x0F,0x04, + 0x77,0x01,0xC2,0xFD,0x80,0xFA,0xE0,0xF5,0xB6,0xF5,0xF9,0x00, + 0x21,0x07,0x3C,0x0B,0x3D,0x0D,0xC3,0x05,0x54,0x02,0xF7,0x03, + 0x0A,0x05,0x67,0x08,0x10,0x09,0x7F,0x00,0xC9,0xF9,0xEB,0xF4, + 0x83,0xF1,0x9C,0xF5,0xF6,0xF8,0x32,0xF8,0x7F,0xF8,0xDF,0xF6, + 0x8F,0xF7,0xEF,0xFD,0x60,0x03,0xEF,0x06,0xDA,0x08,0x85,0x06, + 0x9F,0x03,0x44,0x04,0xCE,0x03,0xF4,0x03,0xB0,0x03,0xA5,0xFF, + 0x11,0xFB,0xC1,0xF7,0xBD,0xF5,0xBF,0xFB,0xAF,0x05,0x23,0x09, + 0x4F,0x0B,0x83,0x08,0xB4,0x02,0x03,0x03,0xA5,0x05,0x65,0x07, + 0xA3,0x08,0x64,0x04,0xAF,0xFB,0xD8,0xF6,0x0D,0xF4,0xA7,0xF4, + 0x85,0xF8,0x24,0xF9,0xC5,0xF7,0x35,0xF7,0x6E,0xF7,0x38,0xFB, + 0xD4,0x01,0xED,0x05,0x69,0x07,0xFC,0x06,0x28,0x04,0x4C,0x03, + 0x72,0x04,0x3B,0x04,0x4E,0x03,0x4C,0x01,0x0E,0xFC,0xB8,0xF8, + 0xBF,0xF6,0x47,0xF8,0xCA,0x02,0x86,0x08,0x68,0x0A,0x4E,0x0A, + 0xE0,0x04,0x5D,0x02,0x17,0x05,0x41,0x07,0x1C,0x08,0x91,0x06, + 0x67,0xFE,0xA6,0xF7,0xBF,0xF4,0x11,0xF4,0xDA,0xF6,0x4F,0xF9, + 0x38,0xF8,0x32,0xF7,0x96,0xF7,0x2A,0xFA,0xDE,0xFF,0x60,0x05, + 0x6A,0x07,0x34,0x07,0xAC,0x05,0x20,0x03,0x2D,0x04,0xB8,0x04, + 0x15,0x04,0x36,0x01,0xC1,0xFD,0xE3,0xF8,0x2E,0xF4,0x57,0xF7, + 0xB0,0xFE,0x15,0x07,0xEE,0x0A,0x34,0x0A,0xA1,0x05,0x68,0x03, + 0x40,0x05,0x6E,0x08,0x2D,0x0B,0xA5,0x08,0x83,0x01,0xC7,0xF9, + 0xE9,0xF4,0xCE,0xF3,0x6B,0xF6,0xB6,0xF7,0x9B,0xF6,0xD8,0xF4, + 0xCE,0xF4,0x2B,0xF7,0xBA,0xFD,0xA9,0x03,0x0C,0x07,0xED,0x07, + 0x2A,0x06,0x49,0x05,0x08,0x06,0x44,0x07,0xBC,0x06,0x7D,0x04, + 0x54,0xFF,0x18,0xFB,0x85,0xF8,0xB3,0xF7,0xA0,0xF8,0xB8,0xFF, + 0x10,0x05,0xC7,0x05,0xBC,0x07,0xB5,0x04,0xC0,0x03,0x0F,0x06, + 0x6C,0x07,0x7D,0x07,0xE9,0x05,0x54,0x00,0xEF,0xFA,0x97,0xF8, + 0x0A,0xF7,0xD5,0xF7,0xAD,0xF8,0x06,0xF8,0x21,0xF7,0x72,0xF8, + 0xBE,0xFA,0x81,0xFE,0xA1,0x02,0xFE,0x03,0x57,0x04,0x12,0x04, + 0xA8,0x03,0xF7,0x03,0x51,0x04,0x1D,0x03,0xD0,0x00,0x28,0xFE, + 0x4D,0xFA,0x1C,0xF8,0x17,0xFB,0x78,0x03,0x43,0x08,0x98,0x0A, + 0x9F,0x09,0x20,0x04,0x94,0x02,0x22,0x04,0x2B,0x06,0x3A,0x07, + 0x08,0x05,0xF1,0xFD,0x2C,0xF8,0xF1,0xF4,0xA9,0xF4,0x3D,0xF7, + 0xF4,0xF8,0x12,0xF8,0x19,0xF8,0x72,0xF8,0xC6,0xFA,0x97,0x00, + 0xB7,0x04,0x1B,0x07,0xFD,0x06,0xC4,0x05,0x17,0x04,0xAE,0x04, + 0xEF,0x04,0xFE,0x03,0x6A,0x01,0xC9,0xFD,0xD0,0xF9,0x24,0xF8, + 0xF0,0xF7,0xF4,0xFA,0xF1,0x03,0x85,0x06,0x21,0x08,0xE6,0x07, + 0x41,0x04,0x3B,0x04,0xB8,0x06,0x79,0x07,0xED,0x06,0x3A,0x04, + 0x5A,0xFD,0x0A,0xF9,0xE2,0xF6,0x7E,0xF6,0xB3,0xF7,0x01,0xF9, + 0xD3,0xF7,0x8C,0xF7,0x86,0xF9,0xC9,0xFB,0xFE,0xFF,0x78,0x03, + 0x5A,0x04,0x95,0x04,0x24,0x04,0xCC,0x03,0xEF,0x03,0x60,0x04, + 0x81,0x02,0x55,0x00,0xD9,0xFD,0x4B,0xF8,0x2A,0xF9,0xF4,0xFE, + 0x68,0x05,0x84,0x09,0x69,0x0A,0xF9,0x05,0x5C,0x02,0xDF,0x02, + 0x29,0x04,0x7F,0x07,0x25,0x07,0x54,0x02,0xE5,0xFB,0x0B,0xF7, + 0x71,0xF4,0x46,0xF6,0x92,0xF8,0xB7,0xF8,0x6D,0xF8,0xFC,0xF7, + 0x7C,0xF8,0xC9,0xFC,0xB5,0x01,0x00,0x05,0xDD,0x06,0xFF,0x05, + 0x82,0x04,0x13,0x04,0xC1,0x04,0xA3,0x04,0xE8,0x03,0xB7,0x00, + 0x23,0xFD,0xFD,0xF9,0x81,0xF8,0xB6,0xF8,0x84,0xFF,0xC7,0x05, + 0x0D,0x07,0xB5,0x08,0x6F,0x05,0x06,0x03,0x4B,0x04,0x21,0x06, + 0x8A,0x06,0x1B,0x06,0x25,0x01,0x54,0xFB,0x3D,0xF8,0x81,0xF6, + 0x39,0xF7,0x1C,0xF9,0x50,0xF9,0x36,0xF8,0x41,0xF9,0x81,0xFA, + 0x4A,0xFD,0x4A,0x01,0xA6,0x03,0x4C,0x04,0x5A,0x04,0xB2,0x03, + 0x21,0x03,0xE3,0x03,0x94,0x03,0x2C,0x01,0x00,0x01,0x67,0xFB, + 0xFE,0xF7,0x93,0xFB,0x5D,0x00,0x04,0x07,0x39,0x0A,0x49,0x09, + 0x9F,0x04,0xE7,0x02,0x6B,0x02,0x1E,0x05,0xB9,0x07,0xD3,0x05, + 0x08,0x01,0xF2,0xFA,0xEA,0xF5,0x94,0xF4,0xD7,0xF6,0x21,0xF8, + 0x37,0xF9,0xFF,0xF8,0x2F,0xF8,0x70,0xF9,0xBE,0xFD,0x79,0x01, + 0x4C,0x05,0xC4,0x06,0xC2,0x05,0x82,0x04,0x20,0x04,0x00,0x04, + 0x5F,0x04,0x83,0x03,0x94,0x00,0xC4,0xFD,0x2E,0xFA,0x80,0xF8, + 0xE8,0xF9,0x04,0x01,0xB0,0x05,0xC1,0x07,0x1F,0x08,0x80,0x04, + 0xAC,0x02,0xAA,0x03,0x66,0x05,0x2F,0x06,0x56,0x05,0x5A,0x00, + 0x29,0xFB,0xD3,0xF7,0x90,0xF6,0x9D,0xF7,0xA4,0xF9,0xE9,0xF9, + 0x70,0xF9,0x07,0xFA,0xD1,0xFA,0x37,0xFD,0x38,0x01,0x87,0x03, + 0x4C,0x04,0x9C,0x04,0x2D,0x03,0xE3,0x02,0x58,0x03,0x81,0x02, + 0xF9,0x01,0x99,0x00,0xD0,0xFA,0x42,0xF9,0xD9,0xFB,0x64,0x00, + 0x13,0x07,0x77,0x09,0x7C,0x08,0x9A,0x05,0x57,0x03,0xF4,0x02, + 0x60,0x05,0xB6,0x06,0x45,0x05,0x1D,0x01,0x54,0xFB,0xA0,0xF6, + 0x24,0xF5,0x2E,0xF6,0xA9,0xF7,0xF7,0xF8,0x16,0xF9,0xE3,0xF8, + 0xE5,0xF9,0x65,0xFD,0xCA,0x00,0x3B,0x04,0x57,0x06,0x2E,0x06, + 0x45,0x05,0xD9,0x04,0x3F,0x04,0xA8,0x03,0x2E,0x03,0xD0,0x00, + 0x8F,0xFE,0x5A,0xFC,0x8B,0xF9,0x48,0xF9,0x64,0xFE,0x84,0x03, + 0x2F,0x06,0xF5,0x07,0xE0,0x05,0x71,0x03,0x6F,0x03,0x08,0x04, + 0xE2,0x04,0x23,0x05,0x30,0x02,0xB4,0xFD,0x1E,0xFA,0x72,0xF7, + 0x1E,0xF7,0xBC,0xF8,0x1F,0xFA,0x72,0xFA,0x18,0xFB,0x42,0xFB, + 0xDC,0xFB,0x07,0xFF,0xD6,0x01,0x74,0x03,0xF1,0x04,0xB9,0x03, + 0x18,0x03,0x5C,0x02,0xF6,0x01,0xBC,0x01,0xCC,0x00,0x8C,0xFE, + 0x92,0xFA,0x70,0xFB,0x3C,0xFE,0x7C,0x03,0x11,0x08,0x54,0x09, + 0x86,0x07,0x7E,0x04,0xEC,0x02,0x16,0x03,0xF5,0x04,0x0D,0x05, + 0xED,0x02,0x8F,0xFE,0x90,0xF9,0x02,0xF6,0x93,0xF5,0xD7,0xF6, + 0xAD,0xF8,0x17,0xFA,0x70,0xFA,0x75,0xFA,0x04,0xFC,0x78,0xFE, + 0x89,0x01,0x19,0x05,0x98,0x06,0x3D,0x06,0x1C,0x05,0x93,0x03, + 0xA1,0x02,0xE6,0x02,0x6E,0x02,0x49,0x00,0xF7,0xFD,0x39,0xFB, + 0xD9,0xF9,0x5B,0xFB,0xF5,0xFF,0x56,0x04,0x2F,0x06,0x01,0x07, + 0x05,0x05,0x6E,0x03,0x4E,0x03,0xD3,0x03,0x42,0x04,0x8E,0x03, + 0xBD,0x00,0x0D,0xFD,0x10,0xFA,0x2B,0xF8,0x24,0xF8,0xAA,0xF9, + 0xAB,0xFA,0xD2,0xFA,0xEA,0xFB,0x13,0xFC,0x46,0xFD,0xE4,0xFF, + 0x85,0x01,0xEE,0x02,0x38,0x04,0x88,0x03,0x0B,0x03,0xFE,0x01, + 0xC1,0x01,0x4C,0x01,0xBF,0xFF,0xFA,0xFD,0x47,0xFB,0xA9,0xFC, + 0x0C,0xFF,0x32,0x03,0xEC,0x06,0x7C,0x07,0xB1,0x06,0x3F,0x05, + 0xCD,0x03,0x79,0x03,0x45,0x04,0x8A,0x03,0xBA,0x01,0xAE,0xFE, + 0xB0,0xFA,0x9C,0xF7,0xFB,0xF6,0x6D,0xF7,0xAC,0xF8,0x45,0xFA, + 0x0C,0xFB,0x6E,0xFB,0xE2,0xFC,0x5C,0xFE,0xC4,0x00,0x34,0x04, + 0x92,0x05,0x3C,0x05,0xD7,0x04,0xBA,0x03,0x90,0x02,0x26,0x02, + 0x8C,0x01,0x66,0x00,0xFB,0xFE,0x10,0xFD,0xC5,0xFB,0x1C,0xFC, + 0xA4,0xFD,0x03,0x02,0x1D,0x05,0xE3,0x05,0xDD,0x05,0x7D,0x04, + 0x04,0x03,0xBE,0x02,0x08,0x03,0x62,0x02,0x3B,0x01,0xFA,0xFE, + 0x1C,0xFC,0x4F,0xFA,0x40,0xF9,0x61,0xF9,0xAF,0xFA,0xAD,0xFB, + 0x6C,0xFC,0xDA,0xFC,0xDC,0xFD,0xC7,0xFE,0x86,0x00,0x38,0x02, + 0xE5,0x02,0xF2,0x02,0x46,0x02,0x44,0x02,0x4E,0x01,0x30,0xFF, + 0x8F,0xFE,0xE0,0xFE,0x25,0xFD,0xBB,0xFD,0x3D,0xFF,0x73,0x01, + 0x6B,0x05,0xC1,0x06,0x40,0x07,0xBA,0x06,0x52,0x05,0xDC,0x03, + 0x68,0x03,0xA0,0x02,0xD2,0x00,0xEE,0xFE,0x5F,0xFC,0xBA,0xF9, + 0x2F,0xF8,0x0A,0xF8,0x65,0xF8,0x21,0xF9,0x46,0xFA,0xC6,0xFB, + 0xAD,0xFD,0x65,0xFF,0xD9,0x00,0x68,0x02,0x9C,0x03,0xAB,0x04, + 0xB9,0x04,0x47,0x04,0x7D,0x03,0x80,0x02,0x9C,0x01,0xCB,0xFF, + 0x2E,0xFF,0x8C,0xFE,0xB5,0xFD,0x53,0xFD,0x3C,0xFD,0x31,0xFE, + 0xC5,0xFE,0xC9,0x01,0x53,0x04,0xB5,0x04,0x7E,0x05,0x35,0x04, + 0xB5,0x02,0xCA,0x02,0x9B,0x02,0x63,0x01,0x0A,0x00,0x7B,0xFE, + 0x9A,0xFC,0xAC,0xFB,0xC1,0xFB,0x7B,0xFB,0x92,0xFB,0xA7,0xFB, + 0x7D,0xFB,0x6A,0xFD,0x28,0xFF,0x4F,0xFF,0x00,0x00,0xBB,0x00, + 0x4F,0x01,0x20,0x01,0xA7,0x01,0xA1,0x02,0x18,0x01,0xA4,0x00, + 0x5B,0x00,0x1C,0x00,0x15,0x00,0x3B,0xFF,0x55,0xFF,0x70,0xFF, + 0xD2,0xFF,0x22,0x01,0xD7,0x03,0x2A,0x05,0x74,0x05,0xD8,0x04, + 0xCC,0x02,0x04,0x02,0x7C,0x01,0x58,0x00,0x88,0xFF,0x97,0xFE, + 0x72,0xFD,0x34,0xFC,0x33,0xFB,0xF3,0xFA,0x1C,0xFB,0xF5,0xFB, + 0xA1,0xFC,0xB4,0xFD,0xA7,0xFE,0x42,0xFF,0x83,0x00,0xF0,0x00, + 0x73,0x01,0x3D,0x02,0x36,0x02,0x8F,0x01,0x52,0x01,0x0F,0x01, + 0xBA,0x00,0x17,0x01,0xF9,0xFF,0x01,0xFF,0xA3,0xFF,0x6E,0xFF, + 0x39,0xFF,0x57,0x00,0xEC,0x00,0x39,0x01,0x6E,0x02,0x61,0x03, + 0x9F,0x03,0x8F,0x03,0x3F,0x03,0x73,0x01,0x15,0x00,0xD2,0xFF, + 0x18,0xFF,0xC3,0xFE,0x23,0xFE,0xF7,0xFC,0x76,0xFC,0x5F,0xFC, + 0x3A,0xFC,0xF8,0xFC,0x97,0xFD,0x21,0xFE,0x86,0xFE,0xD8,0xFE, + 0x62,0x00,0x80,0x00,0x85,0x00,0x11,0x01,0x40,0x00,0x68,0x01, + 0xF6,0x02,0x0A,0x02,0xC2,0xFF,0xF0,0xFD,0xAE,0xFF,0x7C,0x01, + 0xE9,0x00,0x21,0x01,0x59,0x00,0x50,0xFF,0x66,0x00,0x19,0x02, + 0xCB,0x03,0x16,0x04,0x4E,0x03,0xEA,0x01,0x93,0x00,0x85,0x00, + 0xE7,0xFF,0x43,0xFF,0x16,0xFF,0x4F,0xFE,0x7C,0xFD,0xF1,0xFC, + 0x99,0xFC,0x51,0xFD,0x60,0xFD,0x6F,0xFC,0x3D,0xFE,0x7F,0xFF, + 0x7F,0xFF,0x13,0x00,0xC1,0xFF,0x03,0x00,0xA7,0x00,0x4C,0x01, + 0xF3,0x00,0xF7,0x00,0xC1,0x01,0x10,0x00,0x41,0xFF,0x1F,0x01, + 0x92,0x01,0x0B,0x01,0x76,0x00,0x83,0xFF,0x0F,0x00,0x17,0x01, + 0xB1,0x01,0xBC,0x01,0x47,0x02,0x13,0x03,0xA2,0x01,0x00,0x00, + 0x9D,0xFF,0x02,0x00,0x1E,0x00,0x7F,0xFF,0x34,0xFF,0xA2,0xFE, + 0x52,0xFE,0x2D,0xFE,0xFC,0xFD,0x4D,0xFE,0xBE,0xFD,0x83,0xFD, + 0xCF,0xFE,0x14,0x00,0x9E,0xFF,0x7F,0xFE,0x1D,0xFF,0xEC,0xFF, + 0x58,0x00,0x52,0x00,0x71,0x00,0xA5,0x01,0xE4,0x00,0x62,0x00, + 0x92,0x00,0xD4,0xFF,0x82,0x00,0xA3,0x00,0x9D,0x01,0x7D,0x01, + 0x9C,0xFF,0x53,0x00,0xCE,0x00,0x4A,0x01,0x11,0x02,0xAD,0x01, + 0xE7,0x00,0x3F,0x01,0x17,0x01,0x7E,0xFF,0x90,0xFF,0x14,0x00, + 0x47,0xFF,0xAD,0xFE,0x46,0xFF,0x20,0xFF,0x66,0xFE,0x82,0xFE, + 0x66,0xFE,0xAB,0xFE,0x36,0xFF,0xD4,0xFE,0xD7,0xFE,0x7F,0xFF, + 0x8A,0xFF,0xFD,0xFE,0xDB,0xFE,0x85,0x00,0x4D,0x01,0xB6,0x00, + 0xF2,0xFF,0xAE,0xFE,0xB0,0x00,0x6E,0x01,0xF1,0xFF,0x3D,0x00, + 0x2E,0x01,0x0F,0x02,0x33,0x00,0xB7,0xFF,0xC3,0x00,0x09,0x01, + 0xF3,0x01,0x66,0x01,0xF6,0x00,0x9F,0x00,0xFF,0xFF,0xEC,0x00, + 0x63,0x01,0x40,0x00,0x4E,0xFF,0x79,0xFF,0x9C,0xFF,0x0F,0xFF, + 0x75,0xFE,0x60,0xFE,0xEE,0xFE,0x8B,0xFF,0x9F,0xFE,0x81,0xFE, + 0xBB,0xFF,0x02,0xFF,0x46,0xFF,0x99,0xFF,0x31,0xFF,0xEC,0xFF, + 0xCD,0xFF,0x6F,0x00,0xD0,0xFF,0x8F,0xFF,0x33,0x01,0x0E,0x00, + 0x11,0xFF,0x6F,0xFF,0x0E,0x02,0xBB,0x03,0x38,0x01,0x65,0xFD, + 0x67,0xFC,0x04,0x01,0x15,0x04,0x51,0x02,0xD5,0xFF,0xAD,0x00, + 0xD2,0x00,0x37,0xFF,0x0F,0x01,0x27,0x02,0x1F,0x01,0xA4,0x00, + 0xF0,0xFF,0x0D,0xFF,0xA3,0xFE,0x29,0xFF,0x03,0xFF,0x8C,0xFE, + 0xFC,0xFE,0xE1,0xFE,0x66,0xFF,0xAE,0xFF,0x47,0xFF,0xD8,0xFE, + 0x96,0xFE,0x5F,0xFF,0xE9,0x00,0x9B,0x00,0x39,0xFE,0x96,0xFE, + 0xCE,0x00,0x3F,0x01,0xD7,0xFF,0xB0,0xFF,0x20,0x01,0xF7,0x00, + 0x48,0x00,0xEC,0xFF,0x44,0x01,0x92,0x00,0x8D,0xFF,0xCD,0x01, + 0x05,0x01,0xDD,0x00,0x9F,0x00,0x41,0xFF,0x75,0x00,0x70,0x01, + 0xAE,0x00,0x8F,0xFF,0xD0,0xFF,0xB0,0xFF,0xBF,0xFF,0x8C,0xFF, + 0x65,0xFE,0x60,0x00,0x1B,0x00,0xCC,0xFE,0x37,0x00,0xFA,0xFE, + 0x8E,0xFE,0x92,0xFF,0x91,0x00,0xBD,0x00,0xAB,0xFE,0x3C,0xFF, + 0x56,0x00,0x99,0xFF,0xB1,0x00,0x2E,0xFF,0x4A,0xFF,0x6B,0x00, + 0x89,0xFF,0x4E,0x01,0x83,0x00,0x50,0xFF,0xAF,0x00,0x36,0x00, + 0xF1,0xFF,0x2E,0x01,0x0D,0x00,0x57,0x00,0xAE,0x00,0xD7,0xFF, + 0x08,0x00,0xF6,0x00,0x7E,0x00,0x79,0xFF,0xA6,0x00,0x7A,0xFF, + 0x70,0x00,0x82,0x00,0x7E,0xFE,0xAD,0x00,0x19,0x00,0xC6,0xFE, + 0x77,0x00,0xD4,0xFF,0x24,0xFF,0xEA,0xFF,0x67,0x00,0xDB,0xFF, + 0x2D,0xFF,0xF6,0xFF,0x7B,0x00,0x1A,0x00,0x51,0xFF,0x34,0xFF, + 0xCC,0xFF,0x95,0x00,0x4F,0x00,0x95,0xFF,0x8A,0xFF,0x32,0xFF, + 0x2C,0x00,0xBB,0x00,0x8F,0x00,0xB2,0xFF,0x4D,0xFF,0xAB,0x01, + 0x00,0x00,0x93,0xFF,0x78,0x01,0x5D,0x00,0xE5,0xFF,0x43,0x00, + 0xE4,0xFF,0x3A,0x00,0x19,0x00,0x84,0xFF,0xB0,0x00,0x2D,0xFF, + 0x9A,0xFF,0x08,0x00,0x21,0x00,0xCB,0x00,0x01,0xFF,0x16,0xFF, + 0xA0,0x00,0xE4,0x00,0x98,0xFF,0x62,0xFF,0x29,0x00,0x37,0x00, + 0x9A,0xFF,0xCF,0xFF,0xBF,0xFF,0x63,0x00,0xF7,0xFF,0x00,0xFF, + 0x81,0xFF,0x1D,0x01,0x39,0x00,0x88,0xFF,0xD6,0x00,0x38,0xFF, + 0xDD,0xFF,0xED,0x00,0x8D,0xFF,0xBC,0xFF,0x56,0x00,0xF8,0xFF, + 0x75,0x00,0xDE,0xFF,0x5B,0xFF,0x57,0xFF,0xB2,0x00,0xB1,0x00, + 0x9C,0xFF,0xAE,0x00,0xD5,0xFF,0xF8,0xFF,0x1B,0x00,0xA8,0xFF, + 0xF2,0x00,0x30,0x00,0xCD,0xFE,0x3B,0x00,0x8C,0x00,0x10,0xFF, + 0x5F,0x00,0x8C,0x00,0x66,0xFF,0xAA,0x00,0xE5,0xFF,0xE1,0xFF, + 0xE7,0xFF,0x75,0xFF,0xBA,0x00,0x00,0x00,0xAD,0xFF,0x6D,0x00, + 0xA4,0xFF,0x7A,0xFF,0xF0,0xFF,0x96,0xFF,0x91,0xFF,0x4F,0x00, + 0x71,0x00,0x86,0xFF,0x98,0x00,0x08,0x00,0xD2,0xFF,0xC4,0x00, + 0x9F,0xFF,0xDB,0xFF,0x48,0x00,0x74,0x00,0x24,0x00,0x95,0xFF, + 0x29,0xFF,0x14,0x00,0x9F,0x00,0xBA,0xFF,0xC3,0x00,0xC8,0xFE, + 0xF2,0xFE,0xEE,0x01,0x57,0xFF,0x90,0x00,0x72,0x01,0xE8,0xFE, + 0x0B,0xFF,0x05,0x00,0x18,0x01,0x0F,0x00,0x80,0xFF,0xE2,0xFF, + 0xAC,0xFF,0x79,0xFF,0x72,0x00,0x4F,0x00,0x04,0x00,0xA2,0xFF, + 0x36,0x00,0xA4,0x00,0xC6,0xFE,0xC0,0x00,0x48,0x00,0x7A,0xFF, + 0x05,0x00,0x4A,0xFF,0x9E,0x00,0x64,0x00,0x88,0xFF,0xD4,0xFF, + 0xE5,0x00,0xC0,0xFF,0x7E,0xFF,0xC3,0x00,0xC4,0xFF,0x52,0x00, + 0x47,0x00,0x60,0xFF,0xCB,0x00,0x9F,0xFF,0x2B,0xFF,0xB6,0x00, + 0xC1,0xFF,0xA0,0xFF,0xDF,0xFF,0xF3,0xFE,0x22,0x00,0x58,0x01, + 0xAC,0xFF,0x6C,0x00,0x67,0x00,0x84,0xFF,0x57,0x00,0x6B,0x00, + 0xFC,0xFF,0xAF,0xFF,0xE0,0xFF,0xD9,0xFF,0xE4,0xFF,0x5D,0xFF, + 0x77,0xFF,0x1E,0x00,0x97,0x00,0x5B,0xFF,0xB5,0xFF,0x02,0x01, + 0xF7,0xFF,0x4F,0x00,0x51,0x00,0x4F,0x00,0x27,0x00,0xB7,0xFF, + 0xFE,0xFF,0x3D,0x00,0xB4,0xFF,0x0E,0xFF,0xDF,0xFF,0x01,0x00, + 0x35,0x00,0x0C,0x00,0x68,0x00,0xA1,0x00,0x4C,0xFF,0x21,0x00, + 0x5D,0x00,0x0C,0xFF,0x89,0x00,0x2A,0x01,0xC5,0xFE,0xC3,0xFF, + 0xF2,0xFF,0xA8,0xFF,0x01,0x01,0x77,0xFF,0x15,0x00,0xFA,0xFF, + 0xC2,0xFF,0x98,0xFF,0x97,0x00,0xF1,0x00,0xC6,0xFF,0xC3,0x00, + 0x26,0xFF,0x3A,0xFF,0x06,0x01,0xE3,0xFF,0xE3,0xFE,0xD9,0xFF, + 0xD1,0xFF,0x9A,0x00,0x11,0xFF,0x27,0x00,0x70,0x01,0x7B,0xFF, + 0x27,0x00,0x6D,0x00,0xCD,0x00,0x85,0xFF,0x07,0x00,0xC4,0xFF, + 0xE4,0xFE,0xDE,0x00,0xB0,0xFF,0x0B,0xFF,0x6E,0x00,0xB9,0x00, + 0xE9,0xFF,0x29,0xFF,0x69,0x00,0x3C,0x00,0x79,0x00,0x82,0x00, + 0x4E,0xFF,0x4D,0x00,0x87,0x00,0x48,0xFF,0x48,0xFF,0x7A,0x00, + 0x4D,0xFF,0x54,0x00,0x8A,0x00,0x7D,0xFE,0xF1,0x00,0x98,0x00, + 0x0E,0xFF,0x1E,0x00,0x1C,0x00,0x6C,0x00,0x3A,0x00,0x93,0xFF, + 0xC0,0x00,0x4B,0x00,0xF7,0xFF,0xD0,0xFF,0x15,0xFF,0x70,0x00, + 0x26,0xFF,0x27,0x00,0x22,0x00,0xD3,0xFF,0x77,0x01,0xF2,0xFE, + 0x3E,0x00,0x5D,0x00,0xAA,0xFF,0xCC,0x00,0x32,0x00,0xBD,0xFF, + 0x24,0xFF,0x5C,0xFF,0x2D,0x00,0x70,0x00,0xC4,0xFF,0x62,0x00, + 0x5E,0x00,0x82,0xFF,0x8F,0xFF,0x56,0x00,0x55,0x01,0xEE,0xFF, + 0x06,0xFF,0x6A,0x00,0xCD,0xFF,0xA3,0xFF,0xE8,0xFF,0x39,0x00, + 0x15,0x00,0x30,0xFF,0x00,0x00,0xD0,0xFF,0x58,0x00,0x8D,0x00, + 0xA5,0xFF,0x8E,0x00,0x1D,0x01,0x35,0xFF,0x76,0xFF,0x85,0x00, + 0xA3,0xFF,0x8D,0xFF,0x93,0xFF,0x5D,0x00,0xFC,0xFF,0x6F,0xFF, + 0xD8,0x00,0x33,0x00,0xB6,0xFF,0xA1,0x00,0x12,0x00,0xB9,0xFF, + 0xA7,0xFF,0x56,0x00,0x44,0x00,0x57,0xFF,0x61,0x00,0xE7,0xFF, + 0x24,0xFF,0x6E,0x00,0xF9,0xFF,0x06,0x00,0x37,0x00,0xFF,0xFF, + 0x09,0x00,0x03,0x00,0x6B,0x00,0x1C,0x00,0xF6,0xFF,0xC2,0xFF, + 0xBB,0x00,0xC3,0xFF,0xDF,0xFE,0x2E,0x00,0x95,0x00,0x8D,0xFF, + 0x73,0xFF,0xC9,0x00,0xF2,0xFF,0x93,0xFF,0xF7,0xFF,0xB1,0x00, + 0x84,0x00,0x8C,0xFF,0xC2,0xFF,0xF8,0xFF,0x4C,0x00,0x73,0xFF, + 0xBE,0xFF,0x29,0x00,0x15,0x00,0x40,0x00,0x50,0xFF,0x4A,0x00, + 0x84,0x00,0x9B,0x00,0x19,0x00,0x9A,0xFE,0x9D,0x00,0x90,0x00, + 0x30,0xFF,0x3B,0x00,0xFF,0xFF,0x93,0xFF,0xB8,0xFF,0xD6,0xFF, + 0xD2,0x00,0x76,0x00,0x4A,0xFF,0x80,0x00,0xF3,0xFF,0xA5,0xFF, + 0xA4,0x00,0x9E,0xFF,0xD9,0xFF,0x51,0x00,0xE9,0xFF,0xC1,0xFF, + 0xFF,0xFF,0xCF,0xFF,0x03,0x00,0x09,0x00,0x35,0x00,0xDB,0xFF, + 0xE0,0xFF,0x5E,0x00,0x0F,0x00,0x25,0x00,0xCB,0xFF,0x6E,0x00, + 0x9F,0xFF,0x80,0xFF,0x93,0x00,0xD9,0xFF,0x61,0xFF,0xF5,0xFF, + 0xF3,0x00,0xAB,0x00,0x8B,0xFF,0xDB,0xFE,0x68,0xFF,0x45,0x00, + 0xF6,0x00,0xDF,0xFF,0x4D,0xFF,0x3A,0x00,0x90,0x00,0xA9,0x00, + 0x78,0xFF,0xFD,0xFF,0x8B,0x00,0x74,0xFF,0xD6,0xFF,0x64,0x00, + 0xE2,0xFF,0x4D,0x00,0x12,0x00,0x81,0xFF,0x2E,0x00,0xAD,0xFF, + 0xB0,0xFF,0x2A,0x00,0xFE,0xFF,0xEC,0xFF,0x17,0x00,0x01,0x00, + 0xF8,0xFF,0x24,0x00,0x00,0x00,0x3D,0x00,0x05,0x00,0xE7,0xFF, + 0xAE,0xFF,0x45,0x00,0xFE,0xFF,0xC7,0xFF,0x98,0x00,0xC2,0xFF, + 0xB4,0xFF,0x95,0xFF,0x0E,0x00,0x6D,0x00,0xE9,0xFF,0xA8,0xFF, + 0x64,0x00,0x92,0x00,0x00,0x00,0xE9,0xFF,0xF6,0xFF,0x18,0x00, + 0xC3,0xFF,0x19,0x00,0xCE,0xFF,0x1D,0x00,0x29,0xFF,0xAA,0xFF, + 0x08,0x01,0xFE,0xFF,0x9A,0xFF,0x16,0x00,0xA9,0x00,0xFD,0xFF, + 0xDF,0xFF,0xD8,0xFF,0xF2,0x00,0xA2,0xFF,0x1E,0xFF,0x4C,0x00, + 0x9F,0xFF,0x6B,0xFF,0x34,0x00,0xDD,0x00,0xF1,0xFE,0x51,0x00, + 0xD0,0x00,0x96,0xFF,0x38,0x00,0x0C,0x00,0x83,0x00,0x6C,0x00, + 0x77,0xFF,0x4A,0xFF,0x6D,0x00,0x32,0x00,0x0E,0xFF,0xED,0xFF, + 0x2B,0x00,0xBF,0xFF,0x5D,0x00,0x44,0x00,0x98,0xFF,0x31,0x00, + 0xB2,0x00,0x53,0x00,0xCD,0xFF,0x6D,0xFF,0x34,0x00,0x15,0x00, + 0xC2,0xFF,0x4A,0x00,0x81,0xFF,0xE0,0xFF,0x66,0x00,0x8D,0xFF, + 0x9E,0x00,0xC2,0xFF,0x9B,0xFF,0x29,0x00,0x81,0xFF,0x50,0x01, + 0xF0,0xFF,0x14,0xFF,0x41,0x00,0xF4,0xFF,0xFE,0xFF,0xE1,0xFF, + 0xD2,0xFF,0x4D,0x00,0xC8,0x00,0xD5,0xFF,0xC2,0xFF,0x87,0x00, + 0xEB,0xFF,0xE7,0xFF,0xC1,0xFF,0xCA,0xFF,0x91,0xFF,0xC8,0xFF, + 0x90,0x00,0x99,0xFF,0xC0,0xFF,0xC7,0xFF,0x51,0x00,0x7D,0x00, + 0xBB,0x00,0x19,0x00,0x43,0xFF,0xA1,0x00,0xCD,0xFF,0xDD,0xFF, + 0xF4,0xFF,0x3C,0x00,0x25,0x00,0x7C,0xFF,0xF9,0xFF,0x15,0x00, + 0x0F,0x00,0xAB,0xFE,0x28,0x00,0xE2,0x00,0x6C,0xFF,0xA9,0x00, + 0xEE,0xFF,0xE6,0xFF,0x90,0x00,0xB4,0xFF,0xCF,0xFF,0x39,0x00, + 0x15,0x00,0xB1,0xFF,0x39,0x00,0xE9,0xFF,0x20,0x00,0x39,0x00, + 0x8B,0xFF,0xE3,0xFF,0xB1,0x00,0xEE,0xFF,0xA2,0xFF,0xF3,0xFF, + 0xA7,0xFF,0x73,0x00,0x46,0xFF,0xD3,0xFF,0xBA,0x00,0xD8,0xFF, + 0xED,0xFF,0x1B,0x00,0x2A,0x00,0x1A,0x00,0xF8,0xFF,0xEC,0xFF, + 0xA5,0x00,0xB7,0xFF,0xB7,0xFF,0x75,0x00,0x1B,0x00,0x1C,0x00, + 0xD0,0xFF,0x95,0xFF,0x4E,0xFF,0xDD,0xFF,0x76,0x00,0x00,0x00, + 0x9E,0xFF,0x3C,0x00,0x74,0x00,0x4E,0x00,0x8F,0xFF,0xAE,0xFF, + 0x00,0x01,0xA3,0xFF,0xE9,0xFF,0x98,0x00,0x71,0xFF,0xA1,0xFF, + 0x1A,0x00,0x0D,0x00,0x16,0x00,0x01,0x00,0xF6,0xFF,0x32,0x00, + 0x9F,0xFF,0x30,0x00,0x5B,0x00,0x95,0xFF,0xE6,0xFF,0x9B,0xFF, + 0x00,0x00,0xB3,0x00,0x4F,0xFF,0x40,0x00,0xAF,0x00,0x36,0xFF, + 0xCC,0xFF,0xE4,0x00,0x95,0x00,0x01,0xFF,0xEC,0xFF,0xC8,0x00, + 0xAB,0xFF,0xCA,0xFF,0xF3,0x00,0x34,0x00,0x2E,0xFF,0x2A,0xFF, + 0x9D,0xFF,0xB1,0x00,0xB0,0xFF,0x71,0xFF,0x9F,0x00,0xED,0xFF, + 0x00,0x00,0xC4,0x00,0x0C,0x00,0xAD,0xFF,0x52,0x00,0x7A,0xFF, + 0x99,0x00,0xAA,0x00,0xDC,0xFE,0x8A,0x00,0xBC,0xFF,0xFA,0xFF, + 0x2E,0x00,0x6B,0xFF,0x8D,0x00,0x3A,0x00,0xC4,0xFF,0x89,0xFF, + 0xA2,0xFF,0x8A,0xFF,0x5E,0x00,0x99,0x00,0xEE,0xFF,0x6F,0xFF, + 0x0B,0x00,0x41,0x00,0x0B,0x00,0x97,0x00,0x7C,0xFF,0x6E,0x00, + 0xD0,0x00,0x89,0xFF,0xCE,0xFF,0x98,0x00,0xC8,0xFF,0x32,0xFF, + 0x60,0x00,0x75,0xFF,0xE8,0xFF,0x0D,0x00,0xD5,0xFF,0xBE,0x00, + 0x2C,0xFF,0xF5,0xFF,0x7B,0x00,0xDB,0xFF,0x32,0x00,0x0F,0x00, + 0xC8,0x00,0x0A,0x00,0xEA,0xFF,0x6E,0xFF,0xB9,0xFF,0x94,0x00, + 0xB5,0xFE,0x1F,0x00,0x6A,0x00,0xAC,0xFF,0xD9,0x00,0x31,0xFF, + 0x48,0x00,0x58,0x01,0xE8,0xFE,0xA3,0xFF,0x06,0x01,0x6A,0xFF, + 0xE1,0xFF,0x91,0x00,0x63,0xFF,0x34,0x00,0x1D,0x00,0x72,0xFF, + 0xE0,0xFF,0x24,0x00,0xED,0x00,0xE7,0xFF,0x7A,0xFF,0xAD,0x00, + 0x77,0xFF,0x07,0x00,0xDF,0xFF,0x21,0x00,0x1D,0x00,0x24,0xFF, + 0x93,0x00,0x4D,0x00,0x7C,0xFF,0x95,0xFF,0xEC,0x00,0x74,0xFF, + 0x31,0x00,0x42,0x00,0x43,0xFF,0xA9,0x00,0x38,0x00,0xC7,0x00, + 0x23,0xFF,0x9B,0xFF,0xF6,0xFF,0x32,0x00,0x9A,0x00,0xA4,0xFF, + 0xEB,0xFF,0xC8,0xFF,0xBE,0x00,0x7B,0xFF,0xFA,0xFE,0xC8,0x00, + 0xFF,0xFF,0x94,0xFF,0x49,0x01,0x1B,0x00,0xDD,0xFE,0x4C,0x00, + 0x3A,0x01,0x26,0x00,0x6A,0xFE,0xD3,0xFE,0xE0,0x00,0xB0,0x00, + 0x86,0xFF,0x75,0xFF,0xD4,0xFF,0xCE,0x00,0xAC,0xFF,0x32,0x00, + 0xDA,0x00,0xBF,0xFF,0x72,0x00,0xE1,0xFF,0xFC,0xFF,0x61,0x00, + 0x22,0xFF,0x85,0xFF,0xF4,0x00,0x0D,0x00,0x0C,0xFF,0xF8,0xFF, + 0xEF,0xFF,0x88,0xFF,0xA5,0x00,0x94,0x00,0x50,0xFF,0x49,0x00, + 0x40,0x00,0xC0,0x00,0x43,0x00,0x3F,0xFE,0x04,0x01,0x0F,0x00, + 0x05,0xFF,0xB7,0x00,0x0F,0x00,0x80,0x00,0xE0,0xFE,0xDA,0xFF, + 0xF0,0x00,0x5A,0xFF,0xD0,0xFF,0x8A,0xFF,0xE1,0xFF,0xBF,0x00, + 0x95,0x00,0x8C,0x00,0x83,0xFF,0x6A,0xFF,0x2B,0x00,0x08,0x00, + 0x6E,0x00,0x34,0x00,0x48,0xFF,0x9B,0xFF,0x35,0x01,0xC3,0xFF, + 0x23,0xFF,0x81,0xFF,0x98,0x00,0x7C,0x00,0x2D,0xFF,0x9C,0x00, + 0x7E,0xFF,0x13,0x01,0x6E,0x00,0xB4,0xFE,0xF1,0xFF,0x96,0x00, + 0x10,0x00,0x04,0xFF,0x4C,0x01,0xB5,0x00,0x8B,0xFF,0x02,0xFF, + 0x1A,0x00,0x79,0x00,0xB6,0xFF,0xA6,0xFF,0x8B,0xFE,0x06,0x01, + 0xF5,0xFF,0x0F,0x00,0x0B,0x00,0x5A,0x00,0x53,0x01,0x30,0xFF, + 0xAE,0x00,0xCC,0xFF,0xC9,0xFF,0xCF,0xFF,0xAE,0xFF,0x83,0xFF, + 0x47,0xFF,0x46,0x00,0x31,0x00,0xA6,0x00,0xC6,0x00,0x5D,0xFF, + 0xF1,0xFE,0xC7,0x01,0x05,0x01,0x92,0xFE,0xBB,0xFF,0xE2,0x01, + 0xB9,0xFE,0x0C,0xFE,0xA0,0x00,0x87,0xFF,0xD8,0xFF,0x98,0x00, + 0xF5,0xFF,0x50,0xFF,0x4C,0x02,0x30,0x00,0x06,0xFF,0x78,0x00, + 0x19,0x00,0x05,0x01,0xA7,0xFE,0xB3,0x00,0x00,0x01,0x7C,0xFE, + 0x50,0xFE,0x44,0xFF,0x37,0x00,0xC9,0xFF,0x2D,0x00,0x84,0x00, + 0x4C,0x00,0xC3,0x00,0xCF,0x00,0xF8,0xFF,0x28,0x00,0xE3,0xFF, + 0xC0,0xFF,0x3F,0x00,0xB3,0xFF,0xE1,0xFF,0x71,0x01,0x76,0xFF, + 0x4E,0xFF,0x97,0xFF,0xBD,0xFE,0x79,0x00,0x03,0x00,0xFC,0xFE, + 0x88,0x00,0x4F,0x00,0x12,0x00,0x77,0x01,0xB5,0xFF,0xDD,0xFF, + 0xF0,0x00,0x9A,0x00,0x04,0xFF,0xB4,0xFF,0xE8,0xFF,0x89,0xFF, + 0xBA,0xFF,0xCD,0xFF,0xF6,0x00,0x5F,0xFF,0xC8,0xFF,0xCA,0xFF, + 0x30,0x00,0x05,0x00,0xA0,0xFF,0xD4,0xFF,0xF6,0xFF,0x2C,0x01, + 0x70,0x00,0xD9,0xFF,0x92,0xFF,0xBD,0x00,0xFC,0xFF,0x7E,0xFF, + 0xB5,0x00,0xA5,0xFF,0xE4,0xFF,0xB7,0xFF,0xD7,0xFF,0xE2,0xFF, + 0x8F,0xFF,0x88,0x00,0x5E,0xFF,0x9C,0xFF,0xB8,0x00,0x53,0x00, + 0x72,0xFF,0xF1,0xFF,0x46,0x01,0x70,0xFF,0xBA,0xFF,0x9D,0x00, + 0xD5,0xFF,0x4B,0x00,0xDA,0xFF,0x66,0xFF,0xF3,0xFF,0xC3,0x00, + 0xA6,0xFF,0xF5,0xFF,0x96,0xFF,0x7D,0xFF,0x08,0x01,0xD4,0xFF, + 0xAB,0xFF,0xB7,0xFF,0x8B,0x00,0xC8,0xFF,0x02,0x00,0x64,0x00, + 0x5C,0xFF,0x88,0x00,0xB9,0xFF,0x2F,0x00,0x58,0x00,0x1E,0x00, + 0x30,0x00,0x46,0xFF,0x1E,0x00,0x87,0x00,0xF5,0xFF,0x94,0xFF, + 0x27,0x00,0x5B,0x00,0x85,0xFF,0x6D,0xFF,0x4D,0x00,0x19,0x00, + 0x1D,0x00,0xC0,0xFF,0x6A,0xFF,0x7D,0x00,0x2C,0x00,0xF5,0xFF, + 0xE9,0xFF,0xEE,0xFF,0x20,0x00,0x5E,0x00,0xB3,0xFF,0xBC,0xFF, + 0xB4,0x00,0x2C,0x00,0xAB,0xFF,0xAD,0xFF,0x73,0x00,0x4C,0x00, + 0x13,0x00,0x01,0x00,0x88,0xFF,0xD1,0xFF,0x32,0x00,0x99,0x00, + 0xAA,0xFF,0x5F,0xFF,0x1A,0x00,0x0A,0x00,0x37,0x00,0xE8,0xFF, + 0x94,0xFF,0xAC,0xFF,0xF8,0xFF,0x54,0x00,0xC4,0xFF,0x63,0xFF, + 0x08,0x00,0x17,0x00,0x23,0x00,0x4A,0x00,0xE1,0xFF,0x2C,0x00, + 0x69,0x00,0x7A,0x00,0x96,0x00,0x42,0x00,0x05,0x00,0x3E,0x00, + 0xF2,0xFF,0x37,0x00,0x86,0x00,0x1A,0x00,0x25,0x00,0xC5,0xFF, + 0x0B,0x00,0x05,0x00,0xBC,0xFF,0xAF,0xFF,0x55,0xFF,0x56,0xFF, + 0x33,0xFF,0x4B,0xFF,0x77,0xFF,0xE3,0xFE,0xB1,0xFE,0xE1,0xFE, + 0xC0,0xFE,0x5B,0xFF,0x1C,0xFF,0x7B,0xFF,0x53,0x01,0x9D,0x01, + 0x38,0x01,0xD6,0x00,0x1F,0x03,0x48,0x05,0x13,0x03,0xE5,0x01, + 0x14,0x02,0xE4,0x01,0x9A,0x00,0xB4,0xFF,0x96,0xFF,0xD7,0xFD, + 0x0C,0xFD,0x05,0xFD,0xDC,0xFC,0x58,0xFC,0x8E,0xFC,0x72,0xFD, + 0x35,0xFE,0x36,0xFF,0x48,0xFF,0xBE,0xFF,0x47,0x00,0xB0,0x00, + 0xF4,0x00,0x16,0x01,0x92,0x01,0x17,0x01,0xED,0xFF,0xBF,0xFF, + 0xA2,0x00,0x5C,0x00,0x2A,0x00,0xBC,0xFF,0x1D,0x01,0x4D,0x05, + 0x80,0x05,0x85,0x02,0x42,0x01,0xC7,0x03,0xC6,0x03,0x60,0x00, + 0x81,0xFF,0x28,0xFF,0x33,0xFE,0x84,0xFC,0x23,0xFD,0xFE,0xFB, + 0xF1,0xF8,0xAE,0xFB,0x17,0xFE,0x04,0xFD,0x59,0xFD,0x0D,0x00, + 0x99,0x00,0xB4,0xFF,0x5F,0x01,0x74,0x02,0x66,0x01,0xCB,0x00, + 0xAB,0x01,0xDC,0x00,0x2E,0x00,0xD1,0x00,0x38,0xFD,0xE0,0xFB, + 0xAE,0xFB,0x16,0x00,0x06,0x05,0x4E,0x02,0xB3,0x08,0x8A,0x08, + 0x97,0x05,0xB9,0x05,0xB1,0x05,0xEB,0x06,0x35,0xFF,0xAB,0xFF, + 0x02,0xFD,0xB4,0xF9,0x39,0xF8,0x36,0xF6,0xFB,0xF6,0x40,0xF4, + 0xCE,0xF9,0xED,0xFB,0x8C,0xFD,0xBF,0xFE,0x37,0x01,0xE7,0x05, + 0x99,0x04,0x93,0x06,0xB0,0x05,0xEB,0x05,0xEC,0x05,0x08,0x04, + 0x67,0x01,0x39,0xFE,0x3D,0xFF,0x13,0xFD,0x5B,0xFB,0x69,0xFA, + 0xB5,0xFA,0xA1,0xFA,0x68,0xFB,0xDC,0xFE,0x63,0xFF,0x8D,0xFF, + 0x84,0x08,0x39,0x12,0x00,0x09,0xAF,0x03,0x4C,0x09,0x99,0x0B, + 0x3F,0x04,0xF6,0xFC,0x75,0xFD,0x38,0xF8,0x50,0xF7,0x5A,0xF6, + 0x88,0xF2,0xD0,0xF0,0x5A,0xF6,0xB0,0xFD,0x4B,0xFA,0xBB,0xFB, + 0xCE,0x03,0xBF,0x07,0x82,0x06,0xC1,0x07,0x10,0x0A,0x5E,0x08, + 0x2D,0x06,0x34,0x04,0x4A,0x00,0xCD,0xFD,0xD1,0xFD,0x08,0xFA, + 0xDF,0xF6,0x64,0xF8,0xD5,0xFA,0xDE,0xF3,0x1C,0xF3,0x82,0x03, + 0x4B,0x0B,0x9B,0x0E,0x51,0x0A,0x1D,0x0A,0x55,0x12,0xE3,0x11, + 0x14,0x0D,0xDB,0xFF,0xAA,0xFF,0x4F,0xFF,0x61,0xF8,0x6D,0xEF, + 0x81,0xE9,0xDC,0xEE,0x01,0xEF,0xB2,0xF1,0xFF,0xF0,0xD8,0xF7, + 0x55,0x02,0xC4,0x07,0x30,0x0A,0x56,0x0A,0x31,0x10,0x7B,0x11, + 0x9D,0x10,0xB8,0x0A,0xB7,0x04,0x55,0x03,0x2B,0x00,0xDC,0xF8, + 0x03,0xF3,0xCE,0xF3,0x4F,0xF3,0x0A,0xF3,0xAB,0xF4,0xCE,0xF7, + 0x5F,0xF9,0x50,0xFA,0xA8,0x01,0x57,0x07,0xA8,0x16,0xEA,0x1C, + 0x62,0x0D,0x17,0x0E,0x11,0x16,0x61,0x12,0x1D,0x00,0x4C,0xF9, + 0xC8,0xF9,0xBA,0xF1,0x68,0xEE,0x53,0xE7,0xB0,0xE5,0x86,0xEA, + 0x14,0xF5,0xF5,0xF4,0xEF,0xF4,0x79,0x03,0xE8,0x0D,0xB3,0x12, + 0xF7,0x0C,0xA1,0x10,0xCA,0x14,0x89,0x11,0x03,0x0D,0x96,0x02, + 0xDE,0xFD,0xCC,0xFA,0x13,0xF8,0x5D,0xEF,0x92,0xED,0x97,0xF2, + 0xB6,0xF3,0xCA,0xF3,0xC0,0xF5,0x3B,0xFB,0x5A,0xF9,0x5C,0x06, + 0x1D,0x11,0x2B,0x1C,0xBA,0x1A,0x29,0x0E,0xC7,0x18,0xF5,0x15, + 0xEB,0x0D,0x06,0xFC,0x4B,0xF8,0x82,0xF6,0xD9,0xEC,0x3A,0xE9, + 0x92,0xDF,0x5F,0xE6,0x76,0xEB,0x40,0xF3,0x16,0xF3,0x26,0xFB, + 0x35,0x0A,0x3B,0x10,0xDE,0x12,0xFF,0x14,0x24,0x18,0xA5,0x12, + 0xAE,0x12,0x6B,0x0A,0x28,0x02,0x9B,0xF9,0xFE,0xF7,0x7E,0xF2, + 0x05,0xEA,0x2A,0xEE,0xFB,0xEE,0x73,0xF2,0x2F,0xF5,0x67,0xFB, + 0xF0,0xFD,0xCE,0xFE,0xED,0x00,0x57,0x0D,0x37,0x1C,0x5C,0x21, + 0x2E,0x13,0x42,0x0C,0xE2,0x19,0x04,0x11,0x47,0x00,0x2B,0xF3, + 0xAF,0xF4,0xDB,0xF0,0x65,0xE9,0x86,0xE3,0xD5,0xDF,0x1D,0xED, + 0x97,0xF4,0xC6,0xF5,0x07,0xF6,0x09,0x07,0x14,0x14,0x4D,0x13, + 0x6B,0x13,0x1D,0x16,0xEE,0x14,0xA9,0x10,0xBD,0x0B,0xB2,0xFE, + 0xD3,0xF8,0xF8,0xF8,0x54,0xF3,0xCE,0xE9,0xF8,0xEC,0x7B,0xF3, + 0x03,0xF3,0xA3,0xF5,0x82,0xFB,0xF3,0xFF,0x14,0x03,0x40,0x00, + 0xCB,0x06,0xF6,0x12,0xB2,0x20,0x9B,0x1D,0xF1,0x05,0xCE,0x10, + 0x57,0x16,0x57,0x08,0xC1,0xF4,0xE5,0xF0,0xDE,0xF4,0xBF,0xEC, + 0x20,0xEA,0x07,0xDF,0x74,0xE7,0xEF,0xF4,0x34,0xFA,0x4B,0xF5, + 0x3B,0xFD,0x99,0x11,0x5E,0x15,0x8E,0x13,0xD2,0x10,0xA1,0x14, + 0x24,0x11,0x36,0x0E,0xBF,0x01,0x15,0xF8,0x13,0xF9,0x0C,0xF6, + 0xFC,0xEE,0x46,0xEA,0xB3,0xF2,0x1C,0xF5,0x72,0xF6,0xF5,0xF9, + 0xAF,0x00,0x7E,0x04,0x55,0x04,0x48,0x00,0xCC,0x05,0x85,0x19, + 0x44,0x21,0xB0,0x10,0x7E,0x02,0x22,0x14,0x6C,0x12,0xBF,0xFE, + 0x6C,0xF1,0x8E,0xF1,0x40,0xF5,0xEB,0xEE,0x50,0xE7,0x53,0xE0, + 0x7C,0xF0,0x3D,0xFC,0xAC,0xF8,0xDA,0xF5,0xFD,0x04,0x5E,0x15, + 0x69,0x13,0xA8,0x10,0xA4,0x0F,0x10,0x11,0x80,0x0F,0xDE,0x08, + 0x21,0xFA,0x34,0xF6,0x6F,0xFA,0x94,0xF4,0xA1,0xEB,0x42,0xEE, + 0x6E,0xF7,0x6D,0xF8,0xAE,0xF8,0xEC,0xFC,0x2D,0x02,0xDE,0x05, + 0x39,0x02,0xAB,0x01,0x76,0x0B,0xAE,0x1B,0x77,0x1F,0x52,0x05, + 0x2C,0x07,0x11,0x16,0x31,0x0B,0x34,0xF8,0x02,0xEF,0x5B,0xF5, + 0x3B,0xF2,0xCE,0xEE,0xC2,0xE2,0x33,0xE5,0x9F,0xF7,0x39,0xFD, + 0x5C,0xF7,0x47,0xF9,0x11,0x0E,0x19,0x15,0xCC,0x12,0x57,0x0F, + 0xD2,0x0E,0xB3,0x0F,0xFE,0x0D,0xBF,0x01,0x09,0xF5,0x38,0xF8, + 0x1C,0xF9,0xB1,0xF0,0x5F,0xEB,0x67,0xF3,0x2D,0xF9,0xBF,0xF9, + 0x48,0xFB,0xDE,0xFF,0xED,0x04,0xE7,0x05,0x42,0xFF,0xCB,0x04, + 0x08,0x14,0xED,0x1D,0xA1,0x13,0xFC,0x00,0xD7,0x0F,0x7D,0x11, + 0x20,0x02,0x67,0xF2,0x59,0xF0,0x46,0xF6,0xFD,0xF0,0x86,0xEA, + 0x80,0xE1,0x0C,0xF0,0x4D,0xFC,0x41,0xFB,0x84,0xF7,0x7F,0x02, + 0x7B,0x13,0x2D,0x14,0x75,0x11,0x9B,0x0C,0xB1,0x0D,0xFC,0x0E, + 0x7C,0x08,0x25,0xF9,0xF7,0xF3,0xA1,0xF9,0x6F,0xF6,0x7A,0xED, + 0x90,0xEE,0x40,0xF8,0x7B,0xFB,0x2C,0xFB,0xE5,0xFD,0xED,0x00, + 0xD1,0x07,0xCA,0x01,0xBE,0xFC,0x52,0x10,0xB6,0x20,0x40,0x17, + 0x92,0x00,0x47,0x0D,0x38,0x15,0x44,0x06,0x80,0xF7,0x0F,0xEE, + 0x02,0xF5,0x39,0xF5,0x04,0xED,0x02,0xDF,0x9C,0xEA,0x64,0xFC, + 0x02,0xFB,0xFA,0xF6,0xF6,0xFD,0xFB,0x0F,0x9F,0x15,0xFA,0x12, + 0xBC,0x0C,0xB1,0x0D,0x5E,0x11,0xCE,0x09,0x5C,0xFB,0x78,0xF5, + 0x43,0xF8,0xD8,0xF6,0xF2,0xEE,0x33,0xED,0xC2,0xF5,0xD7,0xFB, + 0x84,0xFB,0x3B,0xFC,0xFD,0x01,0x32,0x06,0x3B,0x06,0xDA,0xFD, + 0x7C,0x05,0x8C,0x1E,0xBA,0x1E,0xEA,0x02,0xA2,0x05,0x62,0x16, + 0xC2,0x07,0x8B,0xFA,0xBA,0xF1,0x15,0xF1,0x42,0xF5,0xEF,0xF2, + 0x14,0xE4,0xA3,0xE4,0x1F,0xFB,0x0C,0xFE,0xF6,0xF7,0xE2,0xFB, + 0x1F,0x0B,0xDA,0x13,0xC1,0x13,0xCE,0x0D,0xC2,0x09,0x12,0x0F, + 0x1C,0x0C,0xA7,0xFD,0x2B,0xF5,0xE4,0xF7,0xB0,0xF7,0xA8,0xF1, + 0x9B,0xEE,0x51,0xF4,0x08,0xFC,0xC7,0xFD,0x8A,0xFC,0x68,0x00, + 0xFF,0x05,0x52,0x05,0x0E,0x00,0xC4,0x02,0xCF,0x16,0x34,0x20, + 0x77,0x08,0x4A,0x01,0x96,0x12,0x38,0x0E,0x59,0xFD,0x75,0xF2, + 0x55,0xEF,0xEC,0xF4,0x72,0xF6,0xDB,0xE6,0x19,0xE3,0x27,0xF6, + 0x12,0x00,0x7A,0xFA,0x34,0xFA,0x06,0x07,0xFD,0x12,0xDC,0x15, + 0x25,0x0F,0x1B,0x08,0x90,0x0C,0x9E,0x0E,0x61,0x00,0x08,0xF4, + 0x95,0xF5,0x3D,0xF9,0x68,0xF3,0x56,0xEE,0x46,0xF3,0x4D,0xFB, + 0xCA,0xFE,0x88,0xFD,0xD0,0xFD,0x36,0x06,0x9E,0x04,0x31,0xFC, + 0x37,0x09,0x64,0x1A,0xA6,0x19,0x3F,0x06,0x59,0x07,0xB0,0x11, + 0x97,0x0A,0xD2,0xFE,0x0D,0xF1,0xC4,0xF3,0xD5,0xF7,0xC8,0xF1, + 0x59,0xE5,0x20,0xE8,0xC5,0xF7,0xFE,0xFA,0x6C,0xF9,0x05,0xFB, + 0xD5,0x07,0x72,0x12,0x61,0x12,0x86,0x0C,0x70,0x0B,0xC0,0x10, + 0xA4,0x09,0x58,0xFE,0xE9,0xF8,0xD9,0xF7,0xA0,0xF7,0x39,0xF2, + 0x3E,0xF0,0xE3,0xF4,0x41,0xFC,0x6C,0xFC,0xA5,0xFC,0x6D,0x00, + 0x2B,0x04,0x6D,0x05,0x60,0xFE,0x56,0x04,0xE0,0x18,0x6A,0x1E, + 0x48,0x08,0xB8,0x02,0x1E,0x0F,0xF7,0x0C,0xEF,0xFD,0x3B,0xEF, + 0xF1,0xF1,0xB9,0xF6,0x26,0xF4,0x94,0xE7,0x47,0xE7,0xD8,0xF7, + 0x30,0xFF,0x17,0xFC,0x1F,0xFB,0x0E,0x08,0x2A,0x13,0xB1,0x14, + 0x16,0x0C,0xAC,0x05,0x54,0x0D,0x25,0x0C,0xAB,0xFC,0x2C,0xF3, + 0x21,0xF8,0xEA,0xF8,0x3D,0xF3,0x66,0xF1,0x8E,0xF5,0x70,0xFD, + 0xB6,0xFF,0xE4,0xFE,0x55,0xFC,0xBF,0x04,0x11,0x04,0x9B,0xFC, + 0xC4,0x09,0x7E,0x1A,0x7B,0x16,0x9B,0x04,0xE2,0x0C,0xA8,0x10, + 0x2A,0x08,0x0B,0xFF,0xB2,0xF1,0xC5,0xF3,0xA7,0xF6,0xA0,0xEF, + 0xC1,0xE4,0x58,0xEC,0xD8,0xF6,0x3B,0xF8,0xC8,0xFA,0xA4,0xFD, + 0x97,0x08,0xAA,0x11,0xAE,0x13,0x60,0x0B,0x31,0x09,0x76,0x10, + 0x50,0x09,0x5B,0xFD,0x78,0xF7,0x43,0xF9,0x08,0xF8,0x61,0xF3, + 0x24,0xF2,0x6E,0xF6,0x45,0xFD,0x4A,0xFD,0xCD,0xFC,0xE1,0xFE, + 0x63,0x06,0x93,0x00,0xF8,0xFF,0xA0,0x0B,0x63,0x13,0xAF,0x15, + 0x33,0x05,0x7B,0x07,0xF0,0x0E,0xE9,0x07,0x88,0xFC,0xD3,0xF4, + 0x1D,0xF8,0x66,0xF6,0x1D,0xF3,0x45,0xEB,0xED,0xEE,0x5D,0xF9, + 0x6B,0xFA,0xF4,0xF9,0xAA,0xFD,0x30,0x07,0x60,0x0D,0xB1,0x0D, + 0x92,0x05,0xFD,0x07,0x22,0x0E,0x1B,0x04,0xB0,0xFC,0xB9,0xFB, + 0x88,0xFB,0xB3,0xF9,0x78,0xF7,0x10,0xF6,0xE5,0xF9,0xAB,0xFF, + 0x0C,0xFF,0x88,0xFA,0x35,0xFC,0xCD,0x02,0xB3,0xFD,0x73,0xFF, + 0xE6,0x0B,0xAB,0x15,0x6B,0x10,0xF5,0x07,0xDD,0x0E,0xF8,0x0C, + 0x1B,0x08,0x74,0xFE,0xFE,0xF5,0xB5,0xF7,0x5D,0xF5,0x0C,0xEF, + 0x07,0xEA,0x37,0xF0,0xFE,0xF4,0x0B,0xF7,0x1F,0xFA,0x8A,0xFE, + 0x99,0x07,0x1E,0x0E,0x9C,0x0B,0xB3,0x07,0xE5,0x0E,0x11,0x0C, + 0xE0,0x03,0x3C,0x01,0xBC,0xFC,0xE4,0xFB,0xA0,0xF9,0x90,0xF6, + 0x89,0xF5,0x47,0xFA,0x17,0xFE,0xC3,0xFA,0x2C,0xF7,0x0F,0xFF, + 0xA0,0x01,0xF4,0xFA,0x09,0x03,0x8F,0x0F,0xDD,0x17,0x81,0x0D, + 0x68,0x09,0x60,0x11,0xDE,0x0C,0x42,0x06,0x42,0xFB,0x81,0xF6, + 0x33,0xF7,0x7E,0xF4,0x88,0xEB,0x73,0xEA,0xCD,0xF2,0xF4,0xF4, + 0x5F,0xF7,0x5E,0xFA,0xCA,0x00,0x13,0x0A,0x0D,0x0E,0x7B,0x07, + 0xF1,0x09,0x49,0x0F,0xC3,0x07,0xA8,0x04,0x73,0xFF,0x8B,0xFC, + 0x6D,0xFD,0x88,0xF9,0x67,0xF5,0xD6,0xF9,0xDE,0xFD,0x44,0xF7, + 0x0E,0xF8,0x88,0xFD,0x4D,0xFF,0xE9,0xFC,0xDE,0xFD,0xE2,0x06, + 0xB5,0x16,0x57,0x14,0x9F,0x05,0x60,0x0F,0xD0,0x11,0x8B,0x09, + 0x61,0x01,0x25,0xF8,0x37,0xF8,0x00,0xF8,0x91,0xF0,0x0A,0xE9, + 0xF7,0xEF,0xC2,0xF5,0x2B,0xF5,0x47,0xF8,0xA9,0xFC,0x4A,0x04, + 0xE4,0x0C,0xD9,0x08,0x0C,0x05,0x8C,0x0D,0x09,0x0B,0x5B,0x06, + 0x6E,0x01,0x49,0xFE,0x28,0x01,0x10,0xFC,0x69,0xF7,0xA8,0xF7, + 0x32,0xFC,0x7B,0xF9,0x35,0xF6,0x5C,0xFB,0x61,0xFE,0x95,0xFE, + 0x19,0xFD,0x5D,0x02,0x3D,0x14,0x5F,0x15,0x4E,0x07,0xAD,0x0D, + 0x64,0x10,0xB7,0x0A,0x4D,0x05,0x69,0xF9,0xFB,0xF7,0x17,0xFB, + 0xAB,0xF2,0xAA,0xEB,0x19,0xF1,0x38,0xF4,0x3F,0xF5,0x84,0xF7, + 0x5F,0xFA,0xFD,0x01,0xEB,0x09,0xBB,0x07,0x31,0x04,0x39,0x0B, + 0xD5,0x0A,0x8A,0x06,0xA8,0x02,0xCA,0xFF,0xDE,0x01,0xBC,0xFD, + 0x2D,0xF9,0x3C,0xF9,0x9C,0xFB,0xC6,0xF9,0xA7,0xF6,0x45,0xFB, + 0x89,0xFE,0x70,0xFE,0x5F,0xFE,0x30,0x00,0x36,0x12,0x62,0x13, + 0x50,0x04,0x44,0x0E,0x4E,0x0E,0xF7,0x08,0xC8,0x06,0xA2,0xFA, + 0x62,0xFA,0xB3,0xFD,0x8B,0xF4,0xC6,0xED,0x50,0xF4,0x0F,0xF5, + 0xE2,0xF4,0x3B,0xF8,0x57,0xF9,0xB9,0x00,0x58,0x06,0xED,0x04, + 0x73,0x04,0x31,0x08,0xD5,0x08,0xA8,0x06,0x15,0x03,0x53,0x01, + 0x0B,0x02,0xEA,0xFD,0xEB,0xFA,0xE4,0xFA,0x13,0xFB,0x90,0xFA, + 0x11,0xF9,0x42,0xFC,0xD5,0xFF,0x47,0xFF,0x70,0xFF,0x34,0xFF, + 0xEC,0x0B,0x6F,0x13,0x6D,0x05,0x22,0x08,0x68,0x0D,0xAE,0x08, + 0x2F,0x05,0xC0,0xFC,0x27,0xFB,0xE8,0xFB,0x46,0xF8,0xB2,0xF3, + 0x65,0xF3,0x27,0xF7,0x74,0xF7,0x6A,0xF8,0x65,0xFA,0xF5,0xFE, + 0x99,0x04,0x49,0x04,0x87,0x03,0xA9,0x05,0x3E,0x07,0x92,0x04, + 0x3E,0x02,0xE6,0x01,0x99,0x00,0x86,0xFD,0x8F,0xFD,0x74,0xFC, + 0x78,0xFA,0x8B,0xFD,0xA6,0xFA,0x40,0xFE,0xC6,0x00,0xBF,0xFE, + 0x06,0x02,0x3A,0x00,0xEA,0x03,0xF8,0x0E,0x3F,0x0A,0x19,0x02, + 0x1D,0x0A,0xBE,0x07,0xA8,0x03,0xBB,0x01,0x0B,0xFA,0xFC,0xFB, + 0xFB,0xFC,0x3A,0xF7,0x93,0xF5,0xA8,0xF9,0x38,0xF9,0xD5,0xF9, + 0xCF,0xFB,0x87,0xFC,0xF0,0x01,0xA2,0x03,0xC8,0x01,0x3C,0x01, + 0xA5,0x04,0xEE,0x04,0xD6,0x00,0x3D,0x01,0xA9,0xFE,0xFF,0x00, + 0xB2,0xFF,0xC6,0xFA,0x89,0xFD,0xDD,0xFF,0x90,0xFD,0x89,0xFC, + 0xB7,0x03,0x02,0xFE,0xC4,0x01,0xAD,0x04,0x12,0xFE,0x63,0x06, + 0x1C,0x0B,0x17,0x04,0x74,0x01,0xFB,0x09,0x44,0x01,0xF8,0x01, + 0x86,0x02,0xB9,0xF8,0xEB,0xFE,0x5B,0xFD,0x89,0xF8,0x32,0xF8, + 0xC1,0xFC,0x03,0xFB,0x2A,0xFB,0xF2,0xFE,0xD4,0xFB,0x98,0x02, + 0x0E,0x02,0x10,0xFE,0xDC,0x03,0x8F,0x00,0x7F,0x00,0x07,0x03, + 0x3F,0xFD,0xA8,0x01,0x8A,0x01,0x76,0xF9,0x77,0x04,0x0C,0xFD, + 0xD4,0xFA,0x0D,0x0A,0x10,0xF8,0xAC,0x02,0xA6,0x05,0x10,0xFC, + 0xFC,0x03,0x36,0x03,0xA8,0x02,0x92,0x00,0x28,0x09,0xB1,0xFF, + 0x02,0x05,0xEB,0x01,0xDA,0xFF,0x8B,0x03,0x9C,0xF9,0x97,0x01, + 0x8A,0xFA,0x70,0xFE,0xC6,0xFB,0x7A,0xFA,0xEC,0xFE,0x41,0xFC, + 0x71,0xFE,0x9E,0xFD,0x80,0x01,0x84,0xFC,0x88,0x02,0x18,0x00, + 0x88,0xFD,0x38,0x04,0x4B,0xFD,0xE1,0xFF,0xC8,0x00,0xD5,0xFD, + 0x5E,0x03,0x27,0xFE,0x51,0xFC,0xE7,0x07,0xA9,0xFD,0x3B,0x00, + 0xBD,0x02,0x3E,0x01,0xFC,0x02,0x0B,0xFC,0x10,0x07,0x67,0x00, + 0xF8,0xFE,0xEF,0x03,0x5A,0x02,0xAC,0xFF,0x79,0x03,0x5F,0x02, + 0xC5,0xFA,0x9E,0x06,0xFE,0xFB,0xE1,0xFD,0x2C,0x01,0x62,0xF8, + 0x01,0x02,0x34,0xFF,0xA8,0xF7,0x5D,0x02,0x37,0xFE,0x23,0xFE, + 0x32,0xFF,0x05,0xF9,0x34,0x08,0x12,0xFB,0x00,0xFD,0xAF,0x02, + 0xB3,0x00,0xB2,0xFC,0x87,0x04,0xCA,0x01,0xF6,0xFA,0x33,0x06, + 0xCD,0x00,0x4D,0xFF,0xA0,0xFD,0x32,0x0D,0x6A,0xF5,0xEF,0x02, + 0x72,0x0B,0x51,0xF5,0x64,0x08,0x0A,0xFF,0x84,0xFD,0xB5,0x03, + 0xD8,0x01,0x51,0xF9,0xB8,0x03,0x75,0x07,0x3B,0xEF,0x41,0x0C, + 0x41,0x00,0x88,0xF0,0x91,0x10,0x9D,0xF9,0xBF,0xF2,0x54,0x0F, + 0xE0,0xFA,0x3C,0xF1,0x0D,0x16,0x4B,0xED,0xF6,0x02,0x97,0x0B, + 0x79,0xEC,0xB0,0x0A,0x25,0x01,0x0C,0xFD,0xC0,0xF9,0xBB,0x0B, + 0xCD,0xFB,0x4C,0xF9,0x67,0x0F,0xC2,0xF4,0xA9,0x03,0x76,0x09, + 0x88,0xF0,0xEF,0x0E,0xA8,0xF9,0x03,0x00,0xDB,0x05,0x6C,0xF9, + 0x46,0x0A,0x06,0xF4,0xBC,0x0E,0x92,0xF2,0xEE,0x09,0xB6,0xFA, + 0xA0,0xFE,0xF9,0x08,0xCC,0xEC,0x17,0x18,0xCC,0xE7,0xD9,0x0D, + 0xA8,0xFC,0xA5,0xF9,0x1E,0x0C,0x45,0xF2,0x0D,0x09,0xDF,0xFA, + 0x39,0x04,0xA3,0xF9,0xA5,0x06,0xF0,0xFA,0x59,0x01,0xCC,0x01, + 0x21,0xFC,0x67,0x04,0x88,0xF9,0x70,0x06,0x78,0xFA,0xA9,0x05, + 0xAB,0xFB,0xF3,0x01,0xD8,0x00,0x73,0xFF,0xC8,0x00,0x49,0xFD, + 0x1A,0x08,0x52,0xF5,0xF8,0x09,0x43,0xFB,0x7F,0xFF,0x1C,0x09, + 0x47,0xF3,0xB9,0x06,0x3A,0x01,0x4C,0xFB,0xCA,0x04,0xDB,0xFC, + 0x9D,0xFF,0xC8,0x03,0x26,0xFD,0x46,0xFF,0x56,0x02,0x1E,0xFD, + 0x8F,0x00,0x10,0x03,0x6E,0xFA,0xBC,0x02,0x47,0x02,0x75,0xF9, + 0x79,0x07,0xBB,0xFA,0xB3,0x00,0xE0,0x01,0xC1,0xFB,0xCC,0x08, + 0x55,0xF2,0x1B,0x0E,0x56,0xF8,0xA9,0xFC,0x8E,0x08,0xB1,0xF9, + 0xE1,0x02,0xAA,0xFC,0x7C,0x0A,0x87,0xEF,0xB5,0x0D,0xDE,0xFD, + 0x00,0xF7,0x99,0x0D,0x6D,0xF4,0xF9,0x07,0x42,0xFB,0xCA,0x01, + 0x47,0x03,0xB1,0xF8,0x8A,0x04,0x93,0x01,0x64,0xFD,0xC1,0xFD, + 0xBD,0x07,0x98,0xF7,0xDF,0x05,0x09,0xFE,0x64,0xFD,0x57,0x05, + 0x84,0xF9,0x16,0x06,0x0A,0xFB,0x45,0x03,0xBD,0x00,0x3B,0xFB, + 0x2B,0x04,0x97,0xFE,0xD1,0xFF,0xFF,0x00,0x98,0xFD,0x5E,0x04, + 0xC4,0xF9,0xD4,0x05,0xF5,0xFC,0xB0,0xFE,0xDA,0x04,0xC9,0xF8, + 0x47,0x08,0x59,0xF8,0xAD,0x02,0x6E,0x05,0x23,0xF7,0x94,0x05, + 0x83,0xFF,0x7D,0xFE,0xC1,0xFF,0x74,0x02,0x5F,0xFE,0x06,0xFE, + 0x82,0x08,0x9A,0xF2,0x72,0x0B,0x8D,0xFB,0xA6,0xFE,0xBD,0x03, + 0xD6,0xFC,0x42,0x02,0xCF,0xFC,0xC6,0x04,0xD5,0xF8,0x5D,0x0A, + 0x53,0xF4,0xC3,0x08,0xCA,0xFA,0xCC,0x02,0x76,0x01,0x58,0xF9, + 0x0D,0x08,0xAF,0xFA,0xF8,0x04,0xB7,0xF8,0x48,0x08,0x01,0xF9, + 0xC1,0x06,0xD3,0xF9,0xF1,0x00,0x29,0x04,0x74,0xF9,0x79,0x05, + 0xD9,0xFA,0xC1,0x04,0xF1,0xFF,0x4F,0xFD,0x8E,0x02,0xA8,0xFE, + 0x43,0xFC,0xE1,0x0B,0x27,0xF1,0xCF,0x07,0xEA,0x04,0x63,0xF3, + 0x41,0x0B,0x30,0xF9,0x90,0x00,0x74,0x03,0x8E,0xFD,0x98,0xFF, + 0x55,0x01,0x5A,0x02,0xC3,0xFA,0x66,0x03,0xA1,0x01,0x8B,0xF6, + 0xF2,0x0E,0xEA,0xF3,0xC7,0x02,0x73,0x09,0x4A,0xEE,0xCB,0x0F, + 0x36,0xF9,0x7B,0xFD,0x07,0x04,0x13,0x02,0x56,0xFA,0x9B,0x03, + 0xAF,0x02,0xAF,0xF5,0x92,0x0D,0x0D,0xF6,0xBB,0x02,0x4F,0x02, + 0xB3,0xFC,0xB4,0x00,0x3C,0x02,0xE8,0xFD,0x83,0x00,0x28,0x03, + 0x1D,0xF7,0x4C,0x0B,0x0D,0xF7,0xAD,0x03,0x51,0x01,0x49,0xFB, + 0xF8,0x06,0x14,0xF8,0x65,0x07,0xE9,0xFC,0x40,0xFD,0xBD,0x04, + 0x5B,0xFB,0x5A,0x04,0x7C,0xFC,0x78,0x04,0xA4,0xFD,0xCA,0xFB, + 0x08,0x09,0xC4,0xF5,0x33,0x08,0xEF,0xF8,0x18,0x07,0x7E,0xFB, + 0x01,0x00,0x4A,0x07,0x87,0xF1,0x60,0x10,0xC2,0xF3,0x04,0x05, + 0x2D,0xFF,0xE1,0x00,0xA9,0xFC,0xE8,0x03,0xBD,0x00,0x1D,0xF7, + 0xEF,0x0F,0xCB,0xED,0xA1,0x0F,0x86,0xF8,0x1A,0xFC,0x27,0x0C, + 0xED,0xF0,0x69,0x0D,0x7D,0xF7,0x28,0x03,0x13,0x01,0xA4,0xFE, + 0x46,0x01,0x33,0xFA,0x6E,0x0A,0x6B,0xF2,0x4C,0x0F,0x25,0xF4, + 0xA4,0xFF,0x5E,0x0F,0x5B,0xE9,0x9C,0x14,0xFF,0xF4,0x0D,0x00, + 0x05,0x08,0x2F,0xF5,0xBD,0x07,0xAC,0xFC,0x8A,0x02,0x2E,0xFA, + 0x7B,0x0A,0x74,0xF4,0xC0,0x02,0xCF,0x08,0x09,0xEE,0xB4,0x17, + 0xE9,0xEB,0x53,0x08,0x0F,0x04,0xF6,0xF4,0x55,0x0C,0x55,0xF5, + 0xD0,0x07,0xF3,0xF8,0x75,0x08,0x3C,0xF7,0x8B,0x06,0x51,0xFF, + 0x37,0xF9,0xB3,0x0C,0x76,0xF2,0xDB,0x07,0xD5,0xFE,0x45,0xFE, + 0x21,0x02,0x56,0x00,0xBF,0xFB,0xB9,0x05,0x63,0xFE,0x0F,0xF9, + 0xE6,0x0B,0xF4,0xF3,0x32,0x09,0xE5,0xFD,0xFA,0xFB,0xA8,0x07, + 0x18,0xF6,0xA4,0x09,0x53,0xF6,0xBA,0x08,0xF9,0xFA,0x1C,0x00, + 0x6B,0x04,0xBD,0xF7,0x26,0x0A,0x55,0xF6,0xD4,0x08,0x53,0xF8, + 0x54,0x06,0xDC,0xFB,0x07,0x00,0xC7,0x04,0x46,0xF8,0xB1,0x09, + 0x0F,0xF4,0x4C,0x0D,0x0F,0xF7,0xE1,0xFE,0x81,0x08,0xC3,0xF4, + 0x82,0x0A,0x6F,0xF9,0x45,0x02,0xE2,0xFF,0xB6,0xFD,0xEA,0x05, + 0x60,0xF8,0x46,0x06,0x6A,0xFD,0x3A,0x00,0x0D,0x01,0x44,0xFC, + 0x42,0x06,0xE4,0xF9,0xEA,0x01,0xE5,0x02,0x9F,0xFA,0x0D,0x06, + 0x3D,0xFB,0xB7,0x00,0x71,0x05,0xC7,0xF5,0x3F,0x0C,0x2A,0xF4, + 0xE4,0x06,0xFF,0x01,0xD1,0xF7,0x37,0x08,0x47,0xF9,0x6E,0x06, + 0x37,0xF9,0x30,0x07,0xAC,0xF8,0x60,0x05,0x59,0x00,0xDB,0xFB, + 0xC5,0x03,0x42,0xFD,0x55,0x04,0xDF,0xF9,0x09,0x05,0xC3,0xFB, + 0xE5,0x02,0x8C,0xFF,0x28,0xFE,0xC5,0x02,0xFC,0xFC,0x6B,0x02, + 0xAA,0xFF,0x3D,0xFF,0xBF,0xFF,0x39,0x03,0x83,0xFB,0xC4,0x01, + 0xDF,0x02,0xF0,0xFA,0xA1,0x04,0xBD,0xFD,0xF6,0xFE,0x73,0x02, + 0x06,0xFE,0x02,0x01,0x07,0xFF,0x2C,0x01,0xB3,0xFE,0xA8,0x00, + 0xA8,0x01,0xFA,0xFB,0x79,0x04,0x3F,0xFC,0x99,0x00,0x05,0x02, + 0x65,0xFF,0x8F,0x00,0x5A,0xFE,0xEC,0x02,0x1A,0xF9,0x77,0x09, + 0x19,0xF9,0x64,0x01,0x5E,0x04,0x78,0xF8,0xE5,0x07,0x60,0xF9, + 0xB5,0x05,0xC9,0xF9,0xC5,0x05,0x10,0xFB,0x3B,0x03,0x95,0xFF, + 0x82,0xFC,0x4B,0x08,0x6D,0xF3,0xD1,0x0E,0x35,0xF2,0xC9,0x0A, + 0x2F,0xFB,0x76,0xFE,0xAD,0x06,0x3F,0xF6,0xD3,0x09,0xF6,0xF6, + 0x75,0x09,0x99,0xF5,0x0F,0x09,0xDA,0xF9,0xD3,0x00,0x33,0x05, + 0xF7,0xF7,0xF5,0x08,0x23,0xF7,0x7D,0x06,0x2F,0xFC,0xA8,0x01, + 0xDD,0xFE,0xE2,0x03,0x77,0xFA,0x1F,0x03,0x59,0x02,0x3B,0xF8, + 0xE1,0x07,0x7A,0xFA,0xE4,0x02,0xAC,0x00,0x90,0xFE,0x5B,0xFF, + 0x55,0x04,0x42,0xF8,0xED,0x07,0xAA,0xFD,0x71,0xFA,0x44,0x0B, + 0x8B,0xF4,0x4E,0x08,0x51,0xFA,0x1E,0x02,0xCF,0x00,0xBA,0xFD, + 0xC9,0x02,0x3C,0xFC,0xA7,0x05,0xC5,0xF8,0x93,0x08,0x80,0xF7, + 0x44,0x04,0x8C,0x02,0xD6,0xF8,0xC7,0x07,0x78,0xFA,0x0F,0x03, + 0xF5,0xFD,0xF7,0x01,0x7C,0xFD,0x48,0x02,0xD6,0xFF,0xCE,0xFE, + 0xBA,0x00,0x95,0xFD,0xD9,0x05,0x98,0xF9,0x61,0x03,0x31,0x01, + 0x62,0xFC,0x95,0x03,0x9D,0xFF,0x57,0xFB,0xCF,0x04,0x58,0x00, + 0xFF,0xFA,0xD8,0x06,0x74,0xFA,0xBA,0x01,0xAF,0x01,0xBD,0xFC, + 0xBD,0x02,0xB4,0xFD,0x33,0x03,0x8F,0xFC,0x97,0x01,0x72,0x01, + 0xD0,0xFC,0xCF,0x03,0x4E,0xFD,0xF2,0x00,0x19,0xFF,0xDB,0x01, + 0x4B,0xFD,0x50,0x00,0xCD,0x03,0x35,0xF9,0x6C,0x07,0x4F,0xFB, + 0x41,0x00,0x87,0x03,0x2F,0xFC,0x9A,0x02,0x3F,0xFE,0xC5,0x00, + 0x6F,0xFE,0x81,0x03,0x48,0xFD,0x18,0x00,0x70,0x02,0xD2,0xFC, + 0x60,0x00,0xE4,0x01,0x0E,0xFE,0xF6,0xFF,0x80,0x02,0xBF,0xFD, + 0x59,0x00,0xA3,0x00,0x2F,0xFF,0xC9,0xFE,0x62,0x04,0xBA,0xFC, + 0x6B,0xFF,0x16,0x04,0xE9,0xFA,0x5B,0x02,0xDE,0x01,0x51,0xFC, + 0xE7,0x01,0x47,0x03,0x39,0xF9,0xC1,0x03,0xA0,0x00,0x87,0xFC, + 0x70,0x03,0xE1,0xFD,0xA9,0x01,0xBB,0xFD,0x5C,0x02,0x54,0xFD, + 0xDC,0x03,0xD7,0xFD,0x02,0x00,0x50,0x03,0x57,0xF9,0xAF,0x07, + 0xD7,0xF8,0xFC,0x05,0x34,0xFC,0x57,0xFF,0xCB,0x05,0xE4,0xF7, + 0x10,0x07,0x61,0xF9,0x49,0x06,0x00,0xFB,0xD5,0x01,0x26,0x00, + 0x44,0xFD,0x22,0x07,0xCB,0xF7,0x89,0x06,0x8F,0xFA,0x64,0x04, + 0x91,0x00,0x16,0xFA,0xD0,0x05,0x07,0xFE,0x8D,0xFF,0x0F,0x02, + 0x19,0xFD,0x19,0x02,0x92,0xFF,0x30,0xFF,0x37,0xFF,0xEF,0x00, + 0x58,0x02,0xE0,0xFB,0x88,0x04,0x4C,0xFA,0xD6,0x04,0xE6,0xFF, + 0x83,0xFC,0x7A,0x05,0xEA,0xF9,0x2D,0x03,0x8B,0x01,0x19,0xF9, + 0xC1,0x06,0xB2,0xFE,0xF2,0xFD,0x2D,0x02,0xE3,0xFC,0x4B,0x05, + 0x98,0xFA,0x16,0x04,0x3D,0xFE,0x8C,0xFD,0xE7,0x04,0x19,0xFD, + 0x21,0x01,0x30,0xFE,0x7E,0x00,0x12,0x04,0xC6,0xFA,0x9C,0x01, + 0x0E,0x02,0x9E,0xFB,0xC1,0x05,0xD7,0xF9,0xAA,0x03,0x90,0xFF, + 0x69,0x00,0xDB,0xFF,0xFF,0xFC,0x3B,0x05,0xB0,0xFA,0xE7,0x04, + 0x37,0xFC,0x7C,0x00,0xAF,0x02,0x70,0xFC,0xB8,0x03,0x37,0xFE, + 0x4B,0xFF,0x92,0x02,0xA0,0xFD,0x59,0x00,0x8F,0x00,0xB4,0xFF, + 0x97,0xFF,0x65,0x00,0x95,0xFE,0x7B,0x01,0xD2,0xFF,0x0D,0xFF, + 0xF0,0x01,0xEB,0xFC,0xAE,0x03,0xF9,0xFC,0xF7,0x02,0x37,0xFD, + 0xD3,0x01,0x02,0x00,0xA2,0xFE,0xCC,0x01,0xD5,0xFD,0x7C,0x02, + 0xCB,0xFD,0x36,0x02,0x3A,0xFD,0xDD,0x01,0xFC,0xFF,0x8E,0x00, + 0x7B,0xFD,0x4F,0x02,0x77,0xFF,0x5D,0xFE,0x43,0x02,0x27,0xFF, + 0x5F,0xFF,0x64,0x00,0x2B,0x01,0xFB,0xFD,0x04,0x01,0x52,0xFF, + 0x4B,0x01,0xE1,0xFE,0xEF,0x00,0x17,0x00,0x02,0x00,0x6B,0x00, + 0x97,0xFE,0xF3,0x00,0x69,0xFF,0xBA,0xFF,0x13,0x01,0xBE,0xFD, + 0x70,0x02,0xE8,0xFE,0xA8,0xFF,0xFE,0x01,0x5C,0xFD,0xA9,0x02, + 0xFE,0xFE,0xBC,0xFE,0x73,0x02,0xEB,0xFD,0xB8,0xFF,0x14,0x02, + 0x05,0xFD,0x75,0x02,0xDF,0xFD,0xC3,0x01,0x28,0xFF,0xD1,0xFF, + 0x63,0x00,0x7B,0xFF,0x93,0x03,0x9B,0xFA,0x10,0x06,0xD0,0xFA, + 0x4E,0x01,0xB1,0x02,0x9A,0xFB,0x75,0x03,0x70,0xFF,0xE1,0xFD, + 0xBD,0x01,0x25,0x00,0x21,0xFD,0x1F,0x04,0xD5,0xFC,0xC9,0x01, + 0xF5,0xFE,0x28,0x01,0x98,0xFF,0x89,0x00,0x8D,0x00,0x99,0xFC, + 0x68,0x05,0x81,0xFA,0xCB,0x02,0xC0,0x00,0xE2,0xFC,0x92,0x02, + 0x89,0xFE,0xB1,0x00,0xB8,0xFF,0xDD,0x00,0x08,0xFF,0xB9,0x00, + 0x77,0x00,0x7F,0xFF,0xEA,0xFF,0x39,0x00,0x68,0xFF,0x3C,0x00, + 0x16,0x00,0x55,0xFF,0x7B,0x00,0x2D,0xFF,0x66,0x01,0xEC,0xFE, + 0xE2,0x00,0xF6,0xFF,0x56,0x00,0x91,0xFF,0x14,0x00,0x21,0x00, + 0x5C,0xFF,0xF8,0x00,0x40,0xFE,0xA0,0x01,0x98,0xFF,0x7A,0xFF, + 0xC5,0x00,0x06,0xFF,0xA8,0x00,0x87,0xFF,0x50,0x00,0xCD,0x00, + 0xC7,0xFE,0xA9,0x01,0xCB,0xFD,0x04,0x02,0xDC,0xFE,0x2E,0xFF, + 0x5E,0x02,0x9C,0xFE,0x0C,0x00,0x8C,0xFF,0x55,0x02,0x43,0xFC, + 0x4A,0x02,0x32,0xFF,0x78,0xFE,0xC2,0x02,0x6A,0xFE,0x8A,0x00, + 0x02,0x00,0xB3,0xFF,0x1A,0x01,0xA9,0xFF,0xDC,0xFF,0xFE,0x00, + 0x58,0xFE,0x19,0x01,0x07,0xFF,0x44,0x01,0xF6,0xFF,0x2B,0xFE, + 0x1B,0x02,0x9B,0xFD,0x5A,0x01,0xAD,0x00,0xF1,0xFD,0xEA,0x01, + 0x90,0xFF,0x0B,0xFF,0x87,0x02,0x7D,0xFD,0x85,0x00,0xAA,0x00, + 0x50,0xFF,0x59,0x00,0xE2,0xFF,0x39,0x00,0xCE,0xFE,0xB3,0x02, + 0x03,0xFC,0x73,0x03,0xAD,0xFE,0xB9,0x00,0x40,0x00,0xB6,0xFE, + 0x5A,0x01,0xD8,0xFD,0x69,0x03,0x8B,0xFB,0xAB,0x03,0x8E,0xFD, + 0x33,0x01,0xDB,0x00,0x03,0xFF,0x2E,0x01,0xEF,0xFC,0x9C,0x03, + 0xAB,0xFC,0xE6,0x02,0xE7,0xFE,0xA1,0xFE,0x27,0x04,0xD9,0xFA, + 0x19,0x03,0xBA,0xFE,0x7C,0xFF,0x1D,0x03,0x1A,0xFD,0xF6,0x01, + 0x11,0xFE,0xBE,0x01,0x1A,0xFE,0x3C,0x01,0x42,0x00,0xC1,0xFD, + 0x0C,0x02,0xB1,0xFF,0x06,0x00,0x22,0x00,0x92,0xFE,0xA6,0x00, + 0x23,0x02,0xB4,0xFC,0x5F,0x03,0x5B,0xFD,0xF8,0x00,0x90,0x01, + 0x45,0xFD,0x39,0x02,0xF0,0xFD,0xC2,0x01,0x53,0xFF,0x4A,0xFF, + 0x4A,0x00,0xCB,0xFF,0xE5,0x01,0x3C,0xFE,0x6E,0x00,0xFA,0xFE, + 0xB2,0x00,0xDA,0x00,0x1B,0x00,0x18,0xFF,0xEA,0xFF,0xB9,0x00, + 0x32,0xFF,0xC4,0x01,0x9A,0xFD,0xAE,0x01,0xE5,0xFF,0x65,0xFF, + 0x67,0x00,0x75,0xFF,0x8F,0x00,0xE2,0xFE,0xD0,0x01,0xD1,0xFE, + 0x01,0xFF,0xF4,0x01,0x3F,0xFE,0xF5,0x00,0xA7,0x00,0x41,0xFE, + 0x08,0x01,0xEC,0x00,0x09,0xFF,0x4A,0x00,0x6F,0xFF,0x22,0x00, + 0x0B,0x02,0x40,0xFD,0x7C,0x01,0xD0,0xFF,0x27,0x00,0xA7,0x00, + 0xA6,0xFD,0x99,0x01,0xB1,0xFE,0x2B,0x01,0x84,0x00,0xC4,0xFC, + 0x9C,0x03,0x9D,0xFE,0x97,0xFF,0x5F,0x02,0x17,0xFC,0x32,0x03, + 0x47,0xFF,0x4C,0xFF,0x63,0x01,0x42,0xFD,0xF1,0x02,0x6A,0xFF, + 0xE5,0xFE,0x0D,0x02,0x30,0xFE,0x6A,0x00,0x90,0xFF,0xD1,0x00, + 0x41,0xFD,0xB9,0x04,0x5F,0xFD,0x70,0xFE,0x2D,0x04,0xCF,0xF9, + 0xF7,0x06,0xEE,0xFA,0xC1,0x01,0xD8,0xFF,0x38,0xFF,0x14,0x03, + 0x7B,0xFC,0x92,0x02,0xE1,0xFD,0xBC,0x01,0xA3,0xFF,0x4A,0x00, + 0xBA,0xFE,0x58,0x02,0x81,0xFD,0x07,0x02,0xB8,0xFE,0xE7,0xFD, + 0xC2,0x04,0xE1,0xF9,0xAF,0x04,0x01,0xFE,0xB7,0xFF,0xCD,0x01, + 0x97,0xFE,0xCF,0x00,0x93,0xFF,0xAE,0x00,0x30,0x00,0x31,0x00, + 0xF1,0x00,0xDD,0xFD,0x01,0x01,0x9F,0x00,0xA1,0xFD,0xCF,0x02, + 0x66,0xFB,0x18,0x05,0x3F,0xFC,0x13,0x02,0xFC,0xFE,0xD9,0xFF, + 0x61,0x00,0x6C,0xFF,0x25,0x03,0x12,0xFB,0xEA,0x05,0x8A,0xFA, + 0x58,0x04,0x62,0xFD,0x8B,0x01,0x45,0xFE,0x1D,0x00,0x23,0x02, + 0x39,0xFD,0x90,0x03,0x77,0xFB,0xC3,0x03,0x07,0xFD,0xC1,0x01, + 0x08,0xFF,0xF7,0xFF,0x70,0x01,0xDA,0xFE,0x35,0x01,0x71,0xFF, + 0x5C,0x00,0xA3,0xFE,0x0E,0x02,0x9A,0xFE,0x49,0xFF,0x76,0x02, + 0x1E,0xFD,0xC3,0x01,0x9A,0xFF,0xC5,0xFF,0x4C,0xFF,0xDC,0xFF, + 0x77,0x03,0xC5,0xF9,0x6D,0x05,0x33,0xFD,0x92,0xFF,0x18,0x05, + 0x54,0xFA,0x08,0x03,0x86,0x00,0xB3,0xFC,0xED,0x02,0x84,0xFF, + 0x34,0xFE,0xB3,0x02,0xFF,0xFC,0x49,0x03,0x0E,0xFD,0x81,0x02, + 0xF3,0xFD,0xC7,0xFD,0xA7,0x06,0xEE,0xF8,0xFB,0x04,0x6B,0xFE, + 0x30,0xFF,0x32,0x02,0x05,0xFF,0x7E,0xFE,0x68,0x00,0xB5,0x02, + 0xD7,0xFB,0x49,0x04,0x3C,0xFB,0xD7,0x03,0xBD,0xFE,0x91,0xFE, + 0x2A,0x03,0x3A,0xFA,0x95,0x07,0xED,0xFA,0x1B,0x03,0xE2,0xFF, + 0x11,0xFC,0x4A,0x04,0x11,0xFE,0x15,0x00,0x3F,0x00,0x5C,0xFF, + 0x03,0x01,0x13,0x00,0xF3,0xFC,0x74,0x04,0xE1,0xFA,0x09,0x05, + 0xE7,0xFD,0x0A,0xFF,0xCE,0x04,0xD2,0xF7,0xC5,0x08,0xF9,0xF9, + 0x8C,0x01,0x10,0x01,0xAE,0xFA,0x28,0x0A,0xF2,0xF6,0x02,0x05, + 0xA9,0xFF,0xB9,0xFA,0x61,0x09,0x33,0xF6,0x67,0x07,0xFE,0xFA, + 0x13,0x01,0x5E,0x03,0xBD,0xF8,0x8B,0x09,0x02,0xF7,0xBD,0x07, + 0xF2,0xFA,0x92,0x00,0xDC,0x03,0x2A,0xFA,0xCD,0x06,0xF6,0xF8, + 0x74,0x05,0x33,0xFC,0xA8,0x01,0x32,0x01,0xCC,0xFB,0x69,0x06, + 0x8A,0xF9,0x84,0x04,0xAA,0xFE,0x76,0xFD,0xB8,0x05,0x23,0xFA, + 0x16,0x03,0xD0,0xFE,0x3B,0x01,0xA7,0xFF,0x24,0xFE,0x8F,0x03, + 0xFA,0xF9,0xFE,0x07,0xF2,0xF9,0x8C,0x01,0xDF,0x04,0x9E,0xF6, + 0x3D,0x07,0x8C,0xFD,0xA7,0xFF,0xD4,0x02,0xC5,0xFC,0xD9,0x00, + 0xAF,0x00,0x29,0xFE,0x75,0x03,0x5E,0xFC,0xEF,0x02,0x97,0xFE, + 0x9C,0xFC,0x3C,0x09,0x78,0xF6,0xA9,0x03,0x5E,0x02,0x43,0xF9, + 0x1F,0x06,0xAA,0xFF,0x97,0xFB,0x30,0x06,0x4E,0xFC,0xA8,0xFD, + 0x2C,0x07,0xA8,0xF6,0x06,0x08,0xC6,0xFD,0x5E,0xFC,0x22,0x06, + 0xE3,0xF6,0xD3,0x0A,0x86,0xF9,0x95,0xFF,0x2C,0x05,0x09,0xF6, + 0x80,0x0E,0x18,0xF5,0x49,0x02,0x59,0x07,0x3D,0xF2,0xFB,0x0D, + 0x11,0xF6,0x29,0x01,0x14,0x07,0x13,0xF5,0x3D,0x0A,0xDD,0xF8, + 0xDA,0x01,0x66,0x04,0x3D,0xF8,0xB5,0x07,0x48,0xF9,0xAE,0x03, + 0x6F,0x03,0xCC,0xF7,0x0C,0x07,0x26,0xFC,0x48,0x02,0x80,0x00, + 0xCE,0xFB,0x2C,0x03,0x8D,0xFF,0x67,0x00,0xC1,0xFF,0x6E,0x00, + 0x02,0xFC,0x0A,0x05,0x0B,0xFE,0x74,0x01,0x6E,0xFF,0xFA,0xFA, + 0xF1,0x05,0xF7,0xFD,0xAE,0x02,0x81,0xFB,0xD4,0x02,0x71,0x00, + 0xFE,0xFB,0xF9,0x05,0x3F,0xFC,0x5A,0xFF,0xBD,0x02,0x3D,0xFD, + 0x46,0x01,0x1F,0x01,0xDE,0xFE,0x2F,0x00,0xED,0xFE,0x84,0x04, + 0x45,0xF9,0x34,0x03,0xB2,0x05,0x71,0xF5,0xC4,0x07,0xB4,0xFC, + 0x95,0xFC,0xB2,0x07,0xAB,0xFA,0xF8,0x00,0x93,0x00,0x9C,0xFF, + 0x64,0x00,0x30,0x01,0xCF,0xFD,0x6B,0x01,0x80,0x01,0x5C,0xFC, + 0xA4,0x04,0xEB,0xF9,0x5A,0x05,0x6A,0xFE,0x39,0xFB,0xE6,0x07, + 0x0B,0xF7,0x79,0x07,0x70,0xFE,0x14,0xFA,0xC1,0x0A,0xC4,0xF8, + 0x51,0x01,0x07,0x04,0xB3,0xF9,0xDC,0x03,0x13,0x00,0x06,0xFD, + 0x21,0x03,0x05,0xFF,0xAB,0xFD,0x63,0x04,0xC9,0xFC,0x6C,0xFF, + 0x64,0x02,0x1F,0xFD,0x61,0x02,0x9E,0xFD,0x31,0x02,0x02,0xFF, + 0xB7,0xFE,0xA0,0x04,0x6D,0xFE,0xEA,0xFC,0xAC,0x02,0x40,0x00, + 0x3C,0xFC,0x24,0x05,0x9D,0xFD,0x19,0xFF,0x3F,0x03,0xFE,0xFA, + 0x1A,0x03,0x7A,0x00,0x00,0xFC,0x42,0x06,0x2D,0xFC,0xD2,0xFD, + 0x66,0x05,0x9E,0xFC,0x49,0x01,0xC6,0xFF,0x07,0xFF,0x53,0x01, + 0x0A,0x00,0x3F,0x00,0x9A,0xFE,0x0C,0xFF,0x9E,0x01,0x7C,0x02, + 0x49,0xFD,0xC4,0xFE,0xAC,0x01,0xD7,0xFE,0x02,0xFF,0x3E,0x05, + 0xD6,0xFD,0xCD,0xFA,0x98,0x05,0x6F,0xFC,0x33,0x00,0x2D,0x06, + 0xA3,0xFB,0x19,0xFD,0x57,0x01,0xEE,0x02,0xB0,0xFD,0x49,0x01, + 0x2C,0x04,0xD4,0xF7,0x95,0x01,0x43,0x06,0x2F,0xF9,0xA8,0x01, + 0x4F,0x03,0xAC,0xFC,0x41,0xFF,0x52,0x02,0x13,0x01,0xB6,0xFB, + 0xA7,0x03,0xCF,0x01,0x55,0xF8,0xE9,0x03,0xF2,0x03,0xAC,0xFA, + 0x02,0x04,0xA6,0x00,0x3A,0xFC,0xBF,0x00,0xA8,0x01,0x21,0x00, + 0x37,0xFB,0x16,0x03,0xD8,0x02,0xFB,0xFD,0x3D,0xFF,0x1E,0x01, + 0xD6,0xFD,0x29,0xFF,0x91,0x06,0x9A,0xFB,0xCF,0xFB,0x35,0x05, + 0x8B,0x00,0x94,0xFD,0x14,0x02,0x7F,0x00,0x2C,0xFC,0x01,0x01, + 0xC9,0x03,0x87,0xFD,0xF8,0xFD,0x28,0x05,0x67,0xFE,0xA5,0xFC, + 0x82,0x03,0xD0,0xFE,0x01,0xFC,0xA4,0x01,0xEE,0x02,0xFD,0xFE, + 0x23,0xFF,0x0E,0x02,0x8E,0xFE,0x73,0xFD,0xA5,0x03,0x0B,0x01, + 0x50,0xFE,0x32,0x00,0x53,0x00,0x50,0xFE,0x15,0xFE,0xA5,0x04, + 0x6C,0x00,0x2A,0xFD,0xC4,0x00,0x3B,0xFE,0xC3,0xFF,0xC0,0x00, + 0x63,0x00,0x8C,0x01,0x63,0xFF,0xB0,0xFF,0xEB,0x00,0x61,0xFE, + 0xCB,0x00,0xD4,0x02,0x42,0xFE,0x57,0xFE,0x80,0x00,0xDA,0xFE, + 0x86,0x00,0x30,0x01,0x23,0x00,0x06,0x00,0x20,0xFE,0x33,0x01, + 0xC8,0xFF,0x6A,0xFE,0x33,0x02,0x55,0x00,0x42,0xFF,0xF3,0xFF, + 0xDB,0x00,0xE9,0xFF,0x55,0xFF,0xFD,0xFF,0x97,0x00,0xC2,0xFF, + 0x99,0xFD,0xA2,0x01,0x80,0xFF,0x58,0xFF,0x96,0x04,0xDC,0xFC, + 0x7B,0xFD,0x8C,0x02,0xB1,0x00,0xD9,0x00,0x4B,0xFE,0x07,0x00, + 0x42,0x00,0x8C,0xFF,0x5F,0x01,0xA5,0xFF,0xAD,0x00,0x06,0xFE, + 0xC4,0xFF,0x6E,0x01,0x33,0xFE,0x80,0x02,0x01,0x00,0x4E,0xFD, + 0xAB,0x00,0xCB,0x00,0xBE,0xFF,0x67,0x00,0xA2,0x00,0xBA,0xFE, + 0x44,0xFF,0x0A,0x01,0x30,0x01,0x20,0x00,0x07,0x00,0x91,0xFF, + 0x3A,0xFE,0x2F,0x00,0x00,0x01,0x4F,0x00,0xA9,0x00,0x67,0xFF, + 0xFF,0xFE,0x0A,0xFF,0x46,0x00,0x90,0x01,0xBF,0xFF,0xAD,0xFF, + 0xD1,0x00,0xAA,0xFF,0xEA,0xFE,0xCC,0xFF,0x13,0x01,0xEA,0x00, + 0xB6,0xFF,0x66,0xFF,0xAC,0xFF,0x1E,0xFF,0xAB,0xFF,0x4F,0x01, + 0xB0,0x00,0x74,0x00,0x4E,0xFF,0xE5,0xFE,0x81,0x00,0x2F,0x00, + 0xE8,0x00,0x5F,0x00,0x76,0xFF,0xF3,0xFF,0x3B,0xFF,0x14,0x00, + 0xD9,0x00,0x38,0x00,0xBC,0xFF,0x40,0xFF,0xA9,0xFF,0x7C,0x00, + 0x52,0x00,0x42,0x00,0xD7,0xFF,0xEB,0xFF,0x25,0x00,0x23,0xFF, + 0xC8,0xFF,0x2A,0x00,0x59,0x00,0x67,0x00,0x4C,0xFF,0x33,0xFF, + 0xBD,0xFF,0x64,0x00,0xD4,0x00,0x06,0x00,0xE9,0xFF,0x72,0x00, + 0xC5,0xFF,0x3A,0x00,0x49,0x00,0x94,0xFF,0xF8,0xFF,0x85,0x00, + 0x6A,0x00,0x82,0xFF,0x14,0xFF,0xC3,0xFF,0x6B,0x00,0xC3,0xFF, + 0xC8,0xFF,0x24,0x00,0xBD,0xFF,0xCB,0xFF,0x27,0x00,0x31,0x00, + 0xEE,0xFF,0x53,0x00,0xF6,0xFF,0x09,0x00,0x67,0x00,0x1A,0x00, + 0x3F,0x00,0xEC,0xFF,0x23,0x00,0x29,0x00,0x28,0x00,0x0C,0x00, + 0x86,0xFF,0x10,0x00,0x5B,0x00,0x3D,0x00,0x28,0x00,0xDA,0xFF, + 0x1A,0x00,0x17,0x00,0xF6,0xFF,0x38,0x00,0xF9,0xFF,0x1A,0x00, + 0xE0,0xFF,0xE5,0xFF,0x19,0x00,0xD3,0xFF,0xE9,0xFF,0xA4,0xFF, + 0xED,0xFF,0xE3,0xFF,0x95,0xFF,0xC9,0xFF,0xC9,0xFF,0xF9,0xFF, + 0xE0,0xFF,0xC3,0xFF,0xC3,0xFF,0xB5,0xFF,0xF4,0xFF,0xEE,0xFF, + 0xC9,0xFF,0xF1,0xFF,0xEE,0xFF,0xDA,0xFF,0xC3,0xFF,0xE7,0xFF, + 0x08,0x00,0x99,0xFF,0xE3,0xFF,0x68,0x00,0x47,0x00,0x88,0x00, + 0x9D,0x00,0x61,0x00,0x8E,0x00,0xD1,0x00,0x3D,0x01,0xA9,0x01, + 0xBC,0x01,0xAB,0x01,0x5C,0x01,0x95,0x00,0xBF,0xFF,0xF5,0xFE, + 0x51,0xFE,0x40,0xFE,0x3E,0xFE,0x3A,0xFE,0x21,0xFE,0x1D,0xFE, + 0x73,0xFE,0x7B,0xFE,0xC6,0xFE,0x62,0xFF,0xA9,0xFF,0x0E,0x00, + 0x5D,0x00,0x56,0x00,0xCA,0x00,0x01,0x01,0xC8,0x00,0x11,0x01, + 0x1B,0x01,0x0B,0x01,0x0C,0x01,0xB1,0x00,0x76,0x00,0x55,0x00, + 0x2B,0x00,0x0D,0x00,0xDD,0xFF,0x30,0x00,0x22,0x00,0xB8,0xFF, + 0x3C,0x00,0x48,0x00,0xE3,0x00,0x08,0x02,0x60,0x02,0x43,0x03, + 0x6C,0x03,0xBA,0x02,0x10,0x02,0x93,0x00,0xFC,0xFE,0x7C,0xFD, + 0xCB,0xFB,0x27,0xFB,0x9F,0xFB,0x81,0xFC,0x60,0xFD,0x87,0xFD, + 0x83,0xFD,0x98,0xFD,0xBC,0xFD,0x74,0xFE,0x51,0xFF,0x3F,0x00, + 0x1B,0x01,0x9F,0x01,0x07,0x02,0x26,0x02,0x2C,0x02,0x1A,0x02, + 0xDE,0x01,0xB3,0x01,0x6A,0x01,0x1F,0x01,0xE7,0x00,0x94,0x00, + 0x62,0x00,0x4B,0x00,0x22,0x00,0x20,0x00,0x12,0x00,0x01,0x00, + 0x0E,0x00,0xF8,0xFF,0xE4,0xFF,0xC9,0xFF,0x8F,0xFF,0x47,0xFF, + 0x05,0xFF,0xBE,0xFE,0xB0,0xFE,0xBB,0xFE,0xD7,0xFE,0xB2,0xFF, + 0x8C,0x00,0xBD,0x01,0x45,0x03,0xEB,0x03,0x0E,0x04,0x3B,0x03, + 0xA5,0x01,0x41,0x00,0xD0,0xFE,0x7D,0xFD,0x6F,0xFC,0x8A,0xFB, + 0x3D,0xFB,0xDA,0xFB,0x2A,0xFD,0x2B,0xFE,0x9B,0xFE,0x0A,0xFF, + 0x4C,0xFF,0xB7,0xFF,0x5C,0x00,0xEA,0x00,0x70,0x01,0xCF,0x01, + 0xFF,0x01,0x19,0x02,0x31,0x02,0x40,0x02,0x31,0x02,0x0D,0x02, + 0xC2,0x01,0x48,0x01,0xC7,0x00,0x48,0x00,0xE8,0xFF,0xAB,0xFF, + 0x7C,0xFF,0x60,0xFF,0x56,0xFF,0x52,0xFF,0x56,0xFF,0x67,0xFF, + 0x77,0xFF,0x76,0xFF,0x56,0xFF,0x28,0xFF,0xE3,0xFE,0xC4,0xFE, + 0xAA,0xFE,0xE5,0xFE,0x8B,0xFF,0x62,0x00,0xCE,0x01,0x10,0x03, + 0xF6,0x03,0xB5,0x03,0x4C,0x02,0x51,0x01,0x73,0x00,0x93,0xFF, + 0xBE,0xFE,0xE5,0xFD,0xFA,0xFC,0xFE,0xFB,0xED,0xFB,0x93,0xFC, + 0x29,0xFD,0xDE,0xFD,0x7D,0xFE,0xFC,0xFE,0x8F,0xFF,0xFA,0xFF, + 0x63,0x00,0xD9,0x00,0x15,0x01,0x49,0x01,0x82,0x01,0xA4,0x01, + 0xC2,0x01,0xD7,0x01,0xEB,0x01,0xE4,0x01,0xDB,0x01,0xC9,0x01, + 0x81,0x01,0x20,0x01,0xB1,0x00,0x4B,0x00,0xF9,0xFF,0xB7,0xFF, + 0x80,0xFF,0x50,0xFF,0x38,0xFF,0x2E,0xFF,0x2B,0xFF,0x24,0xFF, + 0x0A,0xFF,0xDE,0xFE,0xC5,0xFE,0xB0,0xFE,0xB5,0xFE,0x11,0xFF, + 0x9B,0xFF,0x6F,0x00,0xCA,0x01,0x20,0x03,0xCA,0x02,0xE3,0x01, + 0xEF,0x01,0xA3,0x01,0x00,0x01,0x4C,0x00,0x88,0xFF,0xB9,0xFE, + 0xD9,0xFD,0x2E,0xFD,0xA6,0xFC,0xBC,0xFC,0x54,0xFD,0xCB,0xFD, + 0x3D,0xFE,0xD0,0xFE,0x50,0xFF,0xC1,0xFF,0x2E,0x00,0x80,0x00, + 0xC6,0x00,0x13,0x01,0x4E,0x01,0x61,0x01,0x7F,0x01,0x8E,0x01, + 0x8A,0x01,0x97,0x01,0x9C,0x01,0x93,0x01,0x76,0x01,0x48,0x01, + 0xF8,0x00,0x9E,0x00,0x49,0x00,0xF1,0xFF,0xAA,0xFF,0x73,0xFF, + 0x48,0xFF,0x2B,0xFF,0x0F,0xFF,0xF4,0xFE,0xD3,0xFE,0xB3,0xFE, + 0xA8,0xFE,0x9A,0xFE,0xB1,0xFE,0x1E,0xFF,0xC4,0xFF,0xE6,0x00, + 0x09,0x02,0x35,0x02,0xE9,0x01,0xDF,0x01,0xDB,0x01,0x8E,0x01, + 0x09,0x01,0x5D,0x00,0x8E,0xFF,0xAD,0xFE,0x01,0xFE,0x91,0xFD, + 0x39,0xFD,0x35,0xFD,0x83,0xFD,0xE0,0xFD,0x50,0xFE,0xDF,0xFE, + 0x58,0xFF,0xC0,0xFF,0x18,0x00,0x5E,0x00,0xAA,0x00,0xF4,0x00, + 0x27,0x01,0x3C,0x01,0x5D,0x01,0x71,0x01,0x77,0x01,0x8C,0x01, + 0x8F,0x01,0x90,0x01,0x78,0x01,0x4D,0x01,0x1A,0x01,0xD2,0x00, + 0x88,0x00,0x1B,0x00,0xB9,0xFF,0x75,0xFF,0x3A,0xFF,0x16,0xFF, + 0xFB,0xFE,0xDF,0xFE,0xBC,0xFE,0x9C,0xFE,0x90,0xFE,0x89,0xFE, + 0xA7,0xFE,0x0E,0xFF,0xAA,0xFF,0xD9,0x00,0xE8,0x01,0x05,0x02, + 0xE0,0x01,0xEB,0x01,0xF2,0x01,0xB4,0x01,0x3B,0x01,0x94,0x00, + 0xC8,0xFF,0xE9,0xFE,0x3D,0xFE,0xD8,0xFD,0x8D,0xFD,0x69,0xFD, + 0x84,0xFD,0xCA,0xFD,0x38,0xFE,0xC5,0xFE,0x33,0xFF,0x91,0xFF, + 0xE9,0xFF,0x33,0x00,0x76,0x00,0xB9,0x00,0xF3,0x00,0x17,0x01, + 0x38,0x01,0x5A,0x01,0x65,0x01,0x79,0x01,0x8E,0x01,0x87,0x01, + 0x78,0x01,0x5F,0x01,0x34,0x01,0x03,0x01,0xCD,0x00,0x72,0x00, + 0xF8,0xFF,0x92,0xFF,0x4D,0xFF,0x18,0xFF,0xF7,0xFE,0xDC,0xFE, + 0xB6,0xFE,0x90,0xFE,0x7C,0xFE,0x74,0xFE,0x7C,0xFE,0xD0,0xFE, + 0x59,0xFF,0x62,0x00,0x07,0x02,0x8E,0x02,0xE1,0x01,0xDD,0x01, + 0x13,0x02,0xD0,0x01,0x63,0x01,0xBE,0x00,0x02,0x00,0x1B,0xFF, + 0x4D,0xFE,0xDD,0xFD,0x8C,0xFD,0x5B,0xFD,0x5D,0xFD,0x93,0xFD, + 0x08,0xFE,0xA3,0xFE,0x27,0xFF,0x8B,0xFF,0xE1,0xFF,0x32,0x00, + 0x6A,0x00,0xA8,0x00,0xE1,0x00,0x03,0x01,0x25,0x01,0x42,0x01, + 0x55,0x01,0x5F,0x01,0x77,0x01,0x80,0x01,0x75,0x01,0x6C,0x01, + 0x53,0x01,0x2C,0x01,0xFB,0x00,0xB0,0x00,0x35,0x00,0xAB,0xFF, + 0x4F,0xFF,0x0C,0xFF,0xEB,0xFE,0xCF,0xFE,0xA6,0xFE,0x77,0xFE, + 0x5C,0xFE,0x57,0xFE,0x5F,0xFE,0x83,0xFE,0x1A,0xFF,0xD1,0xFF, + 0xC2,0x01,0x5F,0x04,0x24,0x03,0x65,0x01,0x0E,0x02,0xEA,0x01, + 0x50,0x01,0xAF,0x00,0xFF,0xFF,0x2A,0xFF,0x21,0xFE,0x95,0xFD, + 0x45,0xFD,0x0D,0xFD,0x1D,0xFD,0x55,0xFD,0xBB,0xFD,0x7B,0xFE, + 0x47,0xFF,0xCE,0xFF,0x14,0x00,0x52,0x00,0x8B,0x00,0xB6,0x00, + 0xF1,0x00,0x0D,0x01,0x23,0x01,0x49,0x01,0x67,0x01,0x79,0x01, + 0x8F,0x01,0xA3,0x01,0x92,0x01,0x80,0x01,0x6F,0x01,0x33,0x01, + 0xF2,0x00,0x99,0x00,0xFE,0xFF,0x4A,0xFF,0xE6,0xFE,0xCB,0xFE, + 0xAC,0xFE,0xB2,0xFE,0xAF,0xFE,0x88,0xFE,0x49,0xFE,0x3D,0xFE, + 0x29,0xFE,0x45,0xFE,0xE9,0xFE,0x46,0x00,0x74,0x02,0x2C,0x05, + 0xB3,0x06,0xE0,0x03,0x5E,0x01,0xA8,0x01,0xDD,0x00,0xE7,0xFF, + 0x20,0xFF,0x2B,0xFE,0x08,0xFD,0xE2,0xFB,0x60,0xFB,0x60,0xFB, + 0x0A,0xFC,0x04,0xFD,0xE6,0xFD,0x0A,0xFF,0x4A,0x00,0x17,0x01, + 0x7F,0x01,0xA0,0x01,0x7F,0x01,0x32,0x01,0x18,0x01,0x15,0x01, + 0x0D,0x01,0x46,0x01,0x9D,0x01,0xD8,0x01,0x04,0x02,0x33,0x02, + 0x1F,0x02,0xDA,0x01,0x5F,0x01,0x78,0x00,0x5B,0xFF,0x18,0xFE, + 0xF5,0xFC,0xAE,0xFC,0xE7,0xFD,0xA7,0xFF,0x62,0x00,0x25,0x00, + 0x56,0xFF,0x80,0xFE,0xD2,0xFD,0xB7,0xFD,0x94,0xFD,0x32,0xFE, + 0x80,0x00,0xFD,0x02,0x08,0x06,0x9C,0x08,0xFF,0x09,0xEB,0x08, + 0x87,0x05,0xC5,0x01,0xF2,0xFD,0x1E,0xFB,0x9F,0xF8,0x35,0xF6, + 0xFF,0xF4,0xD7,0xF5,0xAC,0xF7,0x29,0xFA,0x81,0xFD,0x77,0x00, + 0xA8,0x02,0x08,0x04,0xA1,0x04,0x45,0x04,0x99,0x03,0x21,0x03, + 0x8C,0x02,0x54,0x02,0xB1,0x02,0xF7,0x02,0x42,0x03,0x52,0x03, + 0xD1,0x02,0xAA,0x01,0x08,0x00,0x1C,0xFE,0xEE,0xFB,0xE8,0xFA, + 0xE7,0xFA,0x87,0xFB,0x10,0xFD,0xE0,0xFE,0x74,0x00,0x91,0x01, + 0x24,0x02,0xF6,0x01,0x90,0x01,0xDC,0x00,0x52,0xFF,0xFC,0xFC, + 0x75,0xFB,0x59,0xFB,0xDA,0xFC,0x71,0x02,0xAC,0x08,0x9E,0x0D, + 0xD6,0x10,0x73,0x10,0x80,0x0C,0x82,0x04,0x4C,0xFC,0x1E,0xF4, + 0x68,0xED,0xDC,0xEB,0xC3,0xED,0x3D,0xF1,0xA2,0xF6,0xB8,0xFC, + 0xD5,0x00,0xD4,0x03,0x34,0x05,0x74,0x04,0xC4,0x03,0x5D,0x04, + 0x6F,0x05,0x29,0x07,0x8E,0x09,0x42,0x0A,0x52,0x08,0x12,0x05, + 0x5D,0x00,0xA0,0xFA,0xAF,0xF6,0x52,0xF5,0x8F,0xF6,0xFD,0xF9, + 0xE6,0xFE,0x3C,0x03,0xAF,0x05,0x02,0x06,0xD2,0x03,0xEC,0x00, + 0xE1,0xFE,0x06,0xFE,0x4A,0xFE,0x22,0x00,0xFE,0x01,0xE3,0x02, + 0x0A,0x02,0xD1,0xFF,0x83,0xFB,0x51,0xF5,0xF3,0xF2,0xD9,0xF5, + 0xD6,0x00,0x50,0x0D,0x49,0x16,0xD5,0x1C,0xB0,0x1A,0xA7,0x0F, + 0x19,0xFE,0x76,0xEE,0x35,0xE5,0x61,0xE3,0xAA,0xE9,0x3B,0xF3, + 0x14,0xFC,0xED,0x00,0x46,0x01,0xD5,0xFE,0x79,0xFD,0xCD,0xFF, + 0x5A,0x06,0x6C,0x0F,0xBD,0x16,0x14,0x17,0x50,0x0F,0xFE,0x01, + 0xFE,0xF2,0xC8,0xE8,0x46,0xE8,0x3A,0xF0,0x37,0xFC,0x35,0x07, + 0xC4,0x0C,0xF9,0x0A,0x04,0x04,0x2D,0xFE,0x0A,0xFD,0x15,0x01, + 0xAF,0x07,0x3F,0x0C,0xDC,0x0A,0xDA,0x02,0xFA,0xF7,0xF9,0xEF, + 0x05,0xEF,0xC8,0xF5,0x96,0xFF,0x5A,0x07,0xCA,0x05,0x67,0xFC, + 0x13,0xF5,0x6B,0xF7,0x4C,0x09,0x2F,0x1B,0x64,0x25,0xD4,0x22, + 0x21,0x0F,0xE7,0xF4,0xB0,0xDE,0x40,0xD9,0x92,0xE1,0x13,0xF2, + 0x95,0x01,0x4F,0x05,0x07,0xFF,0xD2,0xF6,0x1B,0xF5,0x20,0xFE, + 0xF6,0x0F,0x16,0x1F,0xDB,0x20,0x0B,0x14,0xD5,0xFE,0xD6,0xEA, + 0x30,0xE3,0x83,0xEB,0x6C,0xFA,0x50,0x06,0x04,0x0A,0x4E,0x03, + 0x50,0xF9,0xD5,0xF5,0xCA,0xFD,0xDD,0x0A,0x96,0x15,0x37,0x17, + 0x98,0x0B,0x47,0xFB,0x74,0xEF,0xD9,0xED,0xAB,0xF5,0x69,0x00, + 0x33,0x06,0x43,0x03,0xAA,0xFC,0x99,0xF8,0xF8,0xF8,0x5A,0xFF, + 0x3C,0x01,0xF5,0xFD,0x7C,0x00,0x3F,0x0D,0xDF,0x1A,0x8E,0x1A, + 0x3A,0x14,0xD0,0x04,0x85,0xF1,0x8A,0xE8,0x3A,0xEA,0xFD,0xF0, + 0x32,0xF5,0x89,0xF7,0x85,0xF4,0xBD,0xF2,0x03,0xFB,0x34,0x08, + 0x45,0x12,0x84,0x16,0xE8,0x11,0xFD,0x04,0x02,0xFB,0x47,0xFA, + 0xB3,0xFC,0x2C,0xFD,0xE3,0xFB,0x8B,0xF6,0x46,0xF1,0xD3,0xF5, + 0x6D,0x01,0x0A,0x0B,0xCA,0x0E,0x82,0x0C,0x48,0x05,0x07,0xFF, + 0x94,0xFF,0x76,0x03,0x24,0x05,0x85,0x01,0x71,0xF9,0xEE,0xF2, + 0x07,0xF4,0xF1,0xFA,0x97,0x03,0x1F,0x07,0x1A,0x05,0x4C,0x00, + 0x4C,0xFC,0xC0,0xFA,0x57,0xF7,0x33,0xF9,0xF4,0x0C,0x12,0x23, + 0x29,0x1D,0x26,0x0B,0x8B,0xFC,0x7A,0xF0,0x42,0xEF,0x92,0xF7, + 0x5F,0xFA,0xFF,0xEE,0xF1,0xE9,0xDB,0xF0,0xE2,0xFA,0x9D,0x08, + 0xE6,0x11,0xC3,0x0B,0x0F,0x01,0x96,0x02,0xDC,0x08,0xF4,0x08, + 0x4E,0x06,0x94,0xFD,0x93,0xEF,0x38,0xED,0xD8,0xF8,0xDD,0x02, + 0x4A,0x04,0x8A,0x03,0x3B,0x00,0x81,0xFF,0xDD,0x08,0x6D,0x0F, + 0x34,0x09,0xB9,0xFE,0x6B,0xFB,0x0C,0xFC,0x63,0xFC,0x5D,0xFE, + 0xB9,0xFB,0x11,0xF7,0xD0,0xFA,0xB0,0x02,0x52,0x07,0x84,0x05, + 0x34,0xFD,0x42,0xF1,0xB0,0xF4,0x08,0x02,0xB6,0x0D,0xD3,0x24, + 0xC2,0x20,0xA0,0x03,0xEB,0xF5,0x73,0xF8,0xFA,0xFB,0x73,0xF4, + 0x5A,0xF0,0xDC,0xE8,0xCE,0xE6,0x8C,0xFA,0x19,0x0B,0x75,0x08, + 0x61,0x01,0xA8,0x02,0x70,0x05,0x1B,0x0A,0x49,0x10,0xC0,0x07, + 0x66,0xF6,0xF2,0xF3,0x0D,0xFB,0x62,0xFB,0xBA,0xF9,0xE1,0xFA, + 0x2D,0xFA,0x05,0xFF,0x81,0x0B,0x10,0x10,0x88,0x07,0x59,0x01, + 0xB7,0x03,0x61,0x03,0xFC,0xFE,0xE6,0xFB,0xF0,0xF9,0x54,0xF9, + 0x32,0xFC,0xF3,0xFF,0xBF,0xFF,0x9B,0xFF,0xD5,0x01,0x3C,0x03, + 0xB6,0x02,0x97,0x00,0x34,0xFC,0x9A,0xF7,0x10,0xF4,0xAB,0xFB, + 0x32,0x1D,0x8C,0x28,0xEC,0x09,0x00,0xF7,0xB4,0xFB,0xA7,0xFF, + 0xB3,0xF7,0xFA,0xF0,0x9F,0xE9,0xFB,0xE7,0xAD,0xFC,0x41,0x0C, + 0xE2,0x02,0x99,0xF9,0x3A,0x02,0xDD,0x0A,0x7A,0x0B,0xF9,0x0A, + 0x1E,0x02,0x45,0xF8,0x03,0xFD,0x97,0x03,0x18,0xFB,0xC6,0xF0, + 0x12,0xF6,0xF0,0xFF,0x53,0x05,0x35,0x08,0xBD,0x05,0xAA,0x02, + 0x34,0x07,0xB0,0x0A,0xEE,0x02,0x63,0xF9,0xBE,0xFA,0x45,0xFD, + 0x47,0xFB,0x6C,0xFC,0x16,0xFD,0xD4,0xFC,0x09,0x01,0xCE,0x06, + 0x0D,0x02,0x75,0xFB,0x77,0xF7,0x62,0xF6,0x7E,0xFE,0x52,0x1A, + 0xA3,0x2C,0x3C,0x0B,0x43,0xF3,0x35,0xFD,0xA7,0x00,0x25,0xF5, + 0x2C,0xEC,0x54,0xEC,0x80,0xED,0x09,0xFE,0xB0,0x0B,0xB6,0x00, + 0x49,0xF9,0x31,0x05,0x4D,0x0C,0x65,0x05,0x7A,0x02,0xD2,0x03, + 0x44,0x00,0x48,0xFF,0xD5,0xFE,0x41,0xF6,0xD6,0xF2,0x25,0xFC, + 0x44,0x02,0xCA,0x00,0xE4,0x03,0x9C,0x09,0xF9,0x07,0xE8,0x05, + 0xCD,0x05,0x1A,0x00,0xA5,0xFA,0x63,0xFB,0x95,0xFD,0xAA,0xFC, + 0x3D,0xFD,0x38,0xFF,0x7D,0xFF,0xD7,0x00,0x52,0x02,0x94,0x01, + 0xDE,0xFF,0x9F,0xFE,0x32,0xFA,0xCC,0xF1,0x5B,0xF5,0x3C,0x1A, + 0x69,0x31,0x40,0x0D,0xDB,0xEF,0x8D,0xFC,0x8A,0x06,0x47,0xF8, + 0x66,0xE9,0x3E,0xE9,0x7C,0xF0,0x29,0x02,0xD5,0x0A,0x43,0xFC, + 0xCD,0xF5,0x04,0x06,0x9D,0x0F,0xA5,0x05,0x49,0xFF,0xAC,0x03, + 0xC1,0x04,0x44,0x02,0x9E,0xFD,0xE7,0xF3,0xD5,0xF1,0xDA,0xFB, + 0x4B,0x02,0x7A,0x00,0x27,0x03,0xB6,0x09,0xBE,0x09,0x71,0x07, + 0xAD,0x04,0xCF,0xFE,0x25,0xFA,0xE1,0xFA,0xED,0xFD,0x20,0xFD, + 0x4C,0xFD,0x6B,0xFF,0x18,0x00,0x0D,0x00,0x6D,0x01,0x76,0x02, + 0xB2,0xFF,0xAF,0xFC,0xD3,0xF4,0x05,0xF0,0x37,0x05,0x13,0x2C, + 0xAC,0x26,0x48,0xF9,0x8C,0xF2,0x6C,0x05,0xA9,0x00,0x3F,0xED, + 0x8A,0xE7,0x50,0xEF,0x60,0xFB,0x1A,0x08,0x7B,0x02,0xB4,0xF5, + 0xE3,0xFE,0xC2,0x0D,0xFD,0x07,0x14,0xFD,0x0D,0x02,0xE2,0x09, + 0x37,0x06,0x80,0xFD,0x69,0xF5,0xAC,0xF2,0x93,0xF9,0x9C,0xFF, + 0x69,0xFD,0x38,0xFF,0x11,0x0A,0x9E,0x0F,0x35,0x08,0x32,0x01, + 0xB3,0x00,0xE7,0xFE,0x4D,0xFA,0x57,0xF8,0x93,0xFC,0x56,0x01, + 0x47,0x02,0x56,0xFF,0xBF,0xFD,0x02,0x00,0xD0,0x02,0x48,0x01, + 0x92,0xFF,0xC2,0xFB,0x39,0xF9,0x0F,0xF9,0xAF,0xFB,0x3D,0x21, + 0x65,0x2A,0x7C,0xFA,0xF7,0xEC,0x43,0x03,0x04,0x06,0x03,0xEF, + 0xC0,0xE8,0xB2,0xF4,0x29,0xFC,0x61,0x06,0xE6,0x02,0x48,0xF5, + 0xBA,0xFB,0xA7,0x0C,0x92,0x09,0x01,0xFC,0xD7,0x01,0x32,0x0C, + 0x9B,0x05,0x52,0xFB,0x28,0xF6,0xD0,0xF4,0xA0,0xF9,0x46,0x00, + 0x27,0x01,0x99,0xFE,0x30,0x05,0xD4,0x0E,0x22,0x0A,0x2D,0xFF, + 0xD1,0xFA,0x3C,0xFF,0x6B,0x01,0x38,0xFC,0x58,0xF9,0x00,0xFD, + 0xE4,0x00,0x1A,0x01,0x20,0xFD,0xB6,0xFC,0x53,0xFC,0x75,0xF5, + 0x69,0xFB,0xEC,0x0B,0x01,0x28,0xD0,0x24,0xA0,0xFA,0xB9,0xF4, + 0x4A,0x02,0x5C,0xFA,0x4B,0xE7,0x21,0xE7,0x6C,0xF8,0xAA,0x01, + 0x2C,0x04,0xA9,0xFD,0x27,0xFA,0xBE,0x05,0xFC,0x0B,0x7F,0x02, + 0x93,0xFB,0x0F,0x04,0xAA,0x0A,0x8E,0x02,0xA9,0xF8,0x06,0xF5, + 0x54,0xF6,0x80,0xFB,0xA8,0xFE,0x45,0xFE,0xD8,0x02,0xDA,0x0B, + 0xB6,0x0E,0x72,0x08,0x73,0xFE,0xC8,0xFB,0xBF,0xFE,0xEC,0xFB, + 0x32,0xF7,0x73,0xFA,0xD7,0x01,0xD7,0x05,0x4D,0x06,0x30,0x00, + 0xBC,0xFB,0xCC,0x00,0xC6,0x03,0x1D,0xFE,0xCE,0xFB,0x00,0xFC, + 0x79,0xF9,0xD5,0xF3,0xA5,0x04,0x36,0x2C,0x66,0x19,0x6E,0xEE, + 0x6B,0xF8,0xA2,0x0A,0x5E,0xFE,0x4D,0xEB,0xB8,0xEE,0xC0,0xF8, + 0x9C,0x00,0x81,0x03,0x4D,0xF6,0xF3,0xF4,0x76,0x07,0x96,0x0D, + 0x45,0x02,0x8D,0x00,0x17,0x0B,0x0C,0x0A,0xF9,0xFE,0x47,0xF7, + 0x5B,0xF3,0xE3,0xF7,0xD6,0xFF,0xDD,0xFB,0xC6,0xFA,0x86,0x07, + 0x25,0x0E,0x90,0x07,0x89,0x00,0x99,0x00,0x0D,0x02,0xC4,0x00, + 0x5C,0xFB,0xCD,0xF6,0xB7,0xFC,0xB9,0x02,0x69,0xFE,0x01,0xF9, + 0x33,0xF8,0x90,0xFC,0x69,0xF9,0x86,0x0C,0x4F,0x34,0x88,0x1B, + 0xD2,0xF0,0x07,0xFB,0x23,0x06,0x30,0xF4,0x3C,0xE5,0x4F,0xED, + 0x39,0xF7,0x7F,0x01,0x65,0x07,0x72,0xFB,0x39,0xFA,0x60,0x0A, + 0x26,0x0C,0xC4,0xFE,0xB2,0xFD,0x75,0x07,0x7F,0x07,0xFC,0xFD, + 0xA6,0xF4,0xF7,0xF2,0x12,0xFA,0x71,0xFF,0x48,0xFD,0xA9,0xFF, + 0x11,0x0C,0x96,0x0E,0xE5,0x04,0xF3,0x00,0xE4,0xFF,0x45,0xFD, + 0x96,0xFA,0x54,0xFA,0x23,0xFE,0x33,0x00,0x99,0x00,0xEB,0xFE, + 0xC8,0xFD,0x18,0x00,0xF9,0xFF,0x26,0xFF,0xF3,0xF9,0x98,0xF1, + 0x0E,0x11,0xC3,0x35,0x38,0x11,0x9C,0xEA,0xB7,0xFA,0xF5,0x05, + 0x58,0xF5,0x17,0xE9,0x40,0xF0,0x65,0xFE,0xEF,0x09,0xF6,0x01, + 0x4F,0xF1,0xAC,0xFA,0x52,0x0C,0xB7,0x06,0x3B,0xFA,0xB6,0xFE, + 0x7C,0x0A,0x64,0x0C,0xE1,0xFF,0x55,0xF2,0xFF,0xF4,0x34,0xFD, + 0x1C,0xFE,0x0E,0xFC,0xA5,0x00,0x59,0x0B,0xCA,0x0B,0x87,0x04, + 0xBB,0xFF,0x2E,0xFD,0x5E,0xFD,0xCC,0xFB,0xB3,0xFA,0xEB,0xFD, + 0xD3,0x02,0xDC,0x04,0xBA,0xFF,0x08,0xFC,0x66,0xFE,0x1E,0xFF, + 0x20,0xFB,0x93,0xF2,0x89,0x03,0x07,0x2C,0x13,0x22,0x04,0xF6, + 0x6B,0xF2,0x32,0x03,0x41,0xFD,0x2D,0xED,0x3E,0xEB,0x6B,0xF7, + 0x84,0x08,0x91,0x09,0xCA,0xF6,0xD4,0xF3,0xCE,0x05,0x92,0x0A, + 0x5B,0xFE,0x79,0xFA,0xD4,0x05,0x8A,0x0D,0x2D,0x04,0x4A,0xF6, + 0x45,0xF4,0x31,0xFB,0x67,0xFE,0xB9,0xFC,0x66,0xFF,0xB5,0x07, + 0x53,0x0C,0x9C,0x05,0xD6,0xFB,0x33,0xFC,0x39,0x00,0x9C,0xFE, + 0xC8,0xFA,0xED,0xFB,0xF8,0x04,0xF2,0x07,0x45,0xFF,0x8E,0xF9, + 0xB5,0xFD,0x49,0xFF,0x53,0xF2,0xFD,0xF5,0xC1,0x1D,0xEB,0x2C, + 0x58,0x08,0x5F,0xF1,0x87,0xFC,0x45,0x01,0xBF,0xF6,0xF5,0xEC, + 0x4D,0xEF,0x3E,0xFF,0xD7,0x0A,0xE3,0xFF,0x15,0xF3,0x90,0xFC, + 0x9B,0x08,0x95,0x04,0x23,0xFD,0x47,0x00,0x20,0x0A,0x4E,0x09, + 0x76,0xFB,0xC5,0xF2,0xA1,0xF7,0x02,0xFF,0x40,0xFE,0x80,0xFD, + 0xCF,0x03,0x55,0x09,0x84,0x09,0x0F,0x03,0x7D,0xFC,0xDC,0xFC, + 0xB5,0xFE,0xAE,0xFD,0x18,0xFB,0xD2,0xFD,0xA8,0x04,0x4F,0x04, + 0x57,0xFD,0x16,0xFD,0xFA,0xFD,0x12,0xFB,0x76,0xF5,0x18,0x07, + 0xEA,0x2E,0x12,0x1B,0x22,0xF0,0x68,0xF6,0xFA,0x01,0xF8,0xF9, + 0xC3,0xEE,0x8F,0xEE,0xB1,0xF8,0x3D,0x08,0x88,0x08,0x33,0xF6, + 0xCA,0xF6,0x31,0x07,0xA3,0x08,0x56,0xFE,0x08,0xFD,0xD1,0x06, + 0xEA,0x08,0xF6,0xFE,0xE4,0xF4,0xE2,0xF4,0xCB,0xFD,0x57,0x01, + 0x2D,0xFF,0x70,0xFF,0x99,0x05,0x25,0x0B,0x14,0x05,0x59,0xFD, + 0x8A,0xFD,0xDD,0xFF,0x3A,0xFD,0xF3,0xF9,0x17,0xFC,0xAB,0x02, + 0xD4,0x04,0x4D,0xFE,0x15,0xF8,0x2B,0xF0,0x27,0xFA,0x56,0x1F, + 0x2C,0x2E,0x66,0x0C,0x88,0xF1,0xE9,0xFD,0x9C,0x02,0x33,0xF4, + 0x84,0xEA,0xCE,0xEE,0x0B,0xFF,0x50,0x07,0x16,0xFE,0xE6,0xF6, + 0x8F,0x00,0x6B,0x09,0x90,0x04,0x59,0xFE,0x20,0x00,0x06,0x08, + 0x9B,0x08,0x69,0xF9,0xD6,0xEE,0x00,0xF6,0xFD,0xFE,0xA2,0x00, + 0x04,0x00,0x86,0x03,0x1D,0x0A,0x9A,0x0B,0x8E,0x04,0xE5,0xFB, + 0x0F,0xFB,0x6D,0xFD,0xDE,0xFC,0x02,0xFC,0x1B,0xFD,0x4B,0x02, + 0x62,0x05,0x22,0x00,0x5A,0xFC,0x71,0xFA,0x15,0xFD,0x72,0xFC, + 0xE6,0x05,0xA1,0x29,0x60,0x1C,0xA0,0xF1,0xC1,0xF3,0x51,0xFE, + 0x04,0xFA,0x81,0xF2,0xC9,0xF0,0xB7,0xF7,0xBC,0x07,0x4E,0x0B, + 0xCE,0xF8,0x25,0xF5,0x53,0x02,0x2C,0x07,0x99,0x03,0xD8,0xFF, + 0x4C,0xFF,0x3E,0x03,0xCE,0x03,0x2B,0xFA,0x66,0xF5,0xB2,0xFC, + 0x41,0x00,0x79,0xFE,0x89,0x00,0x61,0x04,0x9E,0x07,0xE7,0x06, + 0x09,0xFF,0xE9,0xFA,0x36,0xFD,0x76,0xFC,0x69,0xFB,0x07,0xFF, + 0xAE,0x01,0x1C,0x00,0x1E,0xF8,0xD2,0xEF,0xE5,0x15,0x88,0x3C, + 0x1F,0x10,0xA6,0xE8,0x2B,0xF8,0xFD,0x02,0x17,0xFC,0x77,0xEF, + 0xFD,0xE9,0x37,0xF8,0x45,0x0C,0x3D,0x03,0x8C,0xF0,0x84,0xFA, + 0xBE,0x0C,0xD5,0x0D,0xC8,0x01,0x2B,0xFC,0xB9,0x04,0xD6,0x07, + 0x71,0xFA,0x70,0xED,0xA7,0xF0,0x6C,0xFD,0x04,0x06,0x06,0x05, + 0xAC,0x01,0x8A,0x06,0xF0,0x0A,0x6E,0x04,0x5C,0xFC,0x63,0xFB, + 0xA9,0xFD,0xFC,0xFD,0xB3,0xFC,0xDF,0xFC,0x92,0x01,0x40,0x03, + 0xCD,0xFB,0x47,0xF7,0x24,0xF6,0x05,0xFB,0xC2,0x1D,0x9D,0x34, + 0x57,0x0E,0x48,0xEE,0x67,0xF7,0xFD,0xFC,0xEC,0xF6,0xB3,0xEE, + 0x50,0xED,0x8A,0xFB,0x1D,0x0A,0x8F,0x03,0x51,0xF8,0x13,0xFD, + 0x0E,0x05,0xF5,0x06,0x3E,0x02,0x85,0xFF,0xA0,0x05,0xDC,0x04, + 0x69,0xF9,0x12,0xF2,0x44,0xF5,0x91,0xFC,0x11,0x03,0x6B,0x04, + 0xD7,0x00,0x61,0x06,0x27,0x0C,0x38,0x05,0xAE,0xFB,0xD8,0xF9, + 0xBE,0xFD,0xD8,0xFE,0x53,0xFD,0xF4,0xFD,0xAE,0x00,0x80,0x03, + 0x91,0xFE,0x20,0xF6,0xF2,0xF9,0x21,0xFD,0x30,0x1A,0xD4,0x32, + 0xE8,0x07,0xF4,0xEB,0x77,0xF8,0xBB,0xFD,0x61,0xF9,0xEF,0xF0, + 0x60,0xEE,0xF4,0xF9,0xCA,0x0A,0x5E,0x04,0xD1,0xF7,0x8C,0xFC, + 0xA0,0x03,0xB7,0x07,0x2E,0x07,0x1B,0x00,0xD3,0xFC,0x23,0x01, + 0x27,0xFF,0x7C,0xF6,0x96,0xF5,0x8B,0xFC,0xE6,0x01,0x5E,0x03, + 0xE4,0x01,0x11,0x04,0x56,0x09,0x39,0x06,0xEC,0xFC,0x2A,0xF9, + 0xB6,0xFB,0x55,0xFF,0xEF,0xFF,0xFB,0xFC,0xD9,0xFC,0xF9,0xF7, + 0xDC,0xF1,0x9F,0x02,0xDA,0x2C,0x3F,0x31,0x7B,0x00,0x87,0xEE, + 0xC7,0xF9,0xBF,0xFD,0x69,0xF9,0xD4,0xEB,0x39,0xEB,0x1D,0xFE, + 0x61,0x0A,0x3B,0xFE,0xB7,0xF4,0xE0,0xFE,0xB7,0x0B,0x40,0x0E, + 0xEF,0x02,0xE8,0xFC,0x98,0x04,0xFF,0x02,0x4A,0xF6,0x78,0xEE, + 0xC9,0xF0,0xF0,0xFC,0x21,0x08,0x4D,0x07,0x6D,0x03,0xE7,0x06, + 0xF1,0x08,0x23,0x04,0x19,0xFD,0x60,0xFA,0x29,0xFC,0x6D,0xFE, + 0x6F,0xFF,0xBA,0xFE,0x67,0xFF,0x45,0x00,0x27,0xFD,0x5C,0xF9, + 0x52,0xFB,0x16,0xFD,0x86,0x18,0x70,0x31,0xF5,0x0E,0x5B,0xF2, + 0xAC,0xF2,0xA9,0xF3,0x44,0xF8,0x04,0xF5,0x75,0xEF,0x79,0xF7, + 0x97,0x08,0x6C,0x09,0xE2,0xFE,0x76,0xFB,0xB8,0xFD,0x19,0x07, + 0x00,0x0B,0x9B,0x00,0x70,0xFA,0x1A,0xFE,0x37,0xFE,0xE4,0xF9, + 0x01,0xF7,0x1F,0xF8,0x31,0xFF,0x79,0x05,0xA7,0x05,0x3A,0x07, + 0xD5,0x07,0x37,0x03,0x1E,0xFE,0xE4,0xFA,0xEE,0xF9,0x65,0xFC, + 0xC7,0xFE,0x44,0xFF,0x45,0xFB,0xA5,0xF4,0x4E,0xF7,0x62,0x0C, + 0x6A,0x30,0xFB,0x28,0x1D,0xFB,0xF5,0xED,0x28,0xF9,0x34,0x00, + 0xC1,0xF7,0x93,0xEB,0x7C,0xED,0xD6,0xFD,0x73,0x09,0x04,0xFF, + 0x5F,0xF6,0xCC,0xFD,0xCE,0x0B,0xD9,0x0E,0x1E,0x04,0x67,0xFF, + 0xE1,0xFF,0x69,0xFD,0x6E,0xF7,0xBB,0xF0,0x33,0xF2,0xA5,0xFD, + 0x19,0x08,0x7D,0x07,0xAA,0x05,0x55,0x06,0xB8,0x04,0x41,0x03, + 0x09,0x00,0xB9,0xFB,0x6C,0xFA,0x9E,0xFD,0xBD,0xFF,0x36,0x00, + 0x57,0x00,0xE4,0xFE,0x6B,0xFA,0xAE,0xF9,0x52,0xF8,0x6F,0x03, + 0x28,0x2C,0xF6,0x29,0x91,0xFE,0x41,0xF0,0xE1,0xF2,0xC6,0xF9, + 0xE2,0xFC,0x2F,0xF2,0x18,0xEA,0xB3,0xF9,0x0B,0x0B,0xE1,0x05, + 0xC1,0xFD,0xDE,0xF9,0xC6,0xFE,0xB7,0x0C,0xA1,0x0B,0x6D,0xFD, + 0x08,0xFA,0xC7,0xFD,0x92,0xFD,0xEC,0xFB,0xB6,0xF6,0x5A,0xF4, + 0xA4,0xFF,0xE5,0x07,0x43,0x07,0x31,0x07,0xCB,0x04,0x41,0x02, + 0x15,0x01,0x95,0xFD,0x06,0xF8,0xDC,0xFA,0x25,0xFE,0xE6,0xFD, + 0xAA,0xFB,0x5E,0xF0,0x61,0xF8,0xFC,0x21,0x7D,0x36,0x13,0x14, + 0xAB,0xF5,0x16,0xF5,0xD2,0xFB,0x58,0x01,0x00,0xF5,0x9B,0xE4, + 0x5F,0xEC,0xCD,0x01,0x42,0x06,0xA1,0xFC,0x65,0xF8,0xFC,0xFE, + 0x8F,0x0D,0x13,0x11,0xAF,0x05,0x70,0xFE,0x39,0xFD,0x8C,0xFB, + 0x37,0xF8,0xBA,0xF1,0xB3,0xF1,0xCA,0xFD,0x0D,0x08,0x70,0x08, + 0x97,0x06,0x9C,0x04,0xBC,0x03,0xD2,0x04,0x50,0x02,0x82,0xFB, + 0xBD,0xF9,0x53,0xFE,0x5C,0x00,0x2C,0x00,0xE6,0xFD,0x5A,0xFA, + 0x8E,0xF8,0x2D,0xFD,0xF0,0xFE,0x2C,0x10,0xE7,0x2D,0x33,0x1B, + 0xDC,0xF8,0xA1,0xF1,0x8F,0xF3,0xA3,0xF8,0x56,0xF9,0xFF,0xF0, + 0x97,0xEC,0xF9,0xFC,0xF7,0x09,0x2D,0x05,0x2A,0xFE,0x77,0xFB, + 0x9C,0x03,0x65,0x0C,0xC5,0x08,0xBF,0xFD,0x89,0xF8,0x56,0xFC, + 0x2B,0xFE,0x01,0xFA,0xE8,0xF4,0x20,0xF6,0x10,0x01,0xCA,0x0A, + 0x28,0x09,0x9B,0x04,0xF3,0x02,0xE1,0x01,0x6D,0xFF,0x4F,0xFC, + 0xC1,0xF8,0x22,0xF9,0x78,0xFF,0x0F,0xF7,0x20,0xF0,0x69,0x0C, + 0x02,0x30,0xC2,0x28,0x52,0x04,0x6E,0xF0,0x86,0xF3,0x3D,0x02, + 0x29,0x02,0xF4,0xEE,0x95,0xE4,0x67,0xF1,0xCA,0x04,0xD1,0x06, + 0x43,0xFD,0xD6,0xF6,0xF8,0xFE,0xEE,0x0F,0x2C,0x14,0x86,0x08, + 0x5C,0xFA,0xBA,0xF5,0x01,0xF9,0x73,0xF9,0xF1,0xF5,0xDA,0xF4, + 0xC2,0xFA,0x42,0x05,0x8C,0x0B,0xD6,0x09,0x80,0x03,0x75,0x00, + 0x7B,0x03,0x62,0x03,0x2C,0xFE,0xFF,0xF9,0xD6,0xFA,0xAB,0xFF, + 0x68,0x02,0x52,0xFD,0xB5,0xF5,0x8E,0xF5,0x1E,0xF8,0xD9,0x15, + 0x87,0x35,0xF2,0x1C,0x1C,0xFA,0x6C,0xF0,0xB1,0xF5,0x85,0xFD, + 0x1D,0xFB,0x94,0xEC,0x87,0xE4,0x4E,0xF7,0x22,0x09,0x5B,0x09, + 0xCC,0x00,0xDE,0xF9,0xDB,0xFE,0xED,0x0D,0x03,0x13,0xB3,0x03, + 0x27,0xF6,0x0D,0xF5,0x10,0xFB,0x09,0xFE,0x51,0xF9,0x0F,0xF5, + 0xA2,0xF8,0x3B,0x03,0x7B,0x0B,0x19,0x0B,0x1D,0x05,0x23,0x01, + 0xDE,0x00,0xAC,0xFF,0xF0,0xFC,0x3C,0xF9,0x90,0xFA,0x07,0xFE, + 0x41,0xF1,0x9A,0xF1,0xF5,0x17,0xB2,0x30,0x07,0x1E,0xBD,0x00, + 0xE2,0xF3,0x54,0xF7,0xB2,0x02,0x77,0x01,0xC5,0xED,0x93,0xE4, + 0xA7,0xF1,0x38,0x01,0xBF,0x04,0xE5,0xFE,0x67,0xF9,0xCF,0xFE, + 0xEC,0x0C,0x33,0x13,0x79,0x0B,0x6C,0xFD,0x9B,0xF5,0x23,0xF7, + 0xB6,0xF9,0xB3,0xF9,0xC9,0xF7,0x57,0xF8,0x05,0xFF,0x5F,0x08, + 0x8A,0x0A,0xA6,0x04,0xEC,0x01,0xD4,0x02,0xEA,0x02,0x78,0x01, + 0xAC,0xFD,0x5A,0xFB,0x9F,0xFC,0xC0,0xFE,0x32,0xFC,0x1D,0xF8, + 0xB2,0xF5,0x32,0x02,0x3F,0x22,0xB7,0x27,0x68,0x0E,0x6E,0xF9, + 0x9A,0xF3,0x7F,0xF9,0x01,0xFE,0xC0,0xFA,0xB1,0xEC,0x03,0xE8, + 0xE5,0xF4,0x72,0x02,0x8E,0x08,0x6D,0x03,0xA9,0xFD,0x4A,0x00, + 0x34,0x0A,0xBF,0x0F,0x37,0x08,0xCD,0xFC,0x96,0xF6,0xEE,0xF6, + 0xD8,0xF9,0xE4,0xFB,0x2F,0xFA,0x95,0xF8,0x5E,0xFD,0x3A,0x04, + 0x76,0x08,0xDD,0x07,0x91,0x03,0xC9,0xFF,0xFF,0xFE,0xCC,0x00, + 0xB8,0x00,0x24,0xFB,0x57,0xF3,0xF5,0xF6,0xB4,0x0C,0x0F,0x1F, + 0x35,0x19,0x49,0x07,0x82,0xFB,0x35,0xFA,0x02,0xFF,0xA9,0x01, + 0x6A,0xFC,0x5A,0xF2,0x42,0xEF,0x6D,0xF4,0x8B,0xFA,0x2E,0xFE, + 0x13,0xFF,0xAD,0xFF,0x26,0x01,0x88,0x04,0x1C,0x07,0xE1,0x05, + 0xC8,0x03,0xA5,0x00,0xCF,0xFC,0x73,0xFB,0x08,0xFD,0xE4,0xFE, + 0x7D,0xFE,0x2A,0xFE,0x97,0xFD,0x51,0xFC,0x6B,0xFE,0x7C,0x02, + 0x3B,0x04,0x22,0x03,0xB4,0x01,0xB8,0x01,0x33,0x02,0x68,0x02, + 0xF4,0x00,0x47,0xFE,0xFE,0xFB,0x3A,0xFE,0x49,0x07,0x2E,0x0C, + 0xAF,0x07,0x7E,0x00,0x2A,0xFE,0x22,0x00,0x17,0x00,0x0E,0x00, + 0x56,0xFF,0x16,0xFC,0xF9,0xF9,0xAE,0xFB,0x65,0xFE,0x42,0xFE, + 0x4B,0xFE,0xFC,0xFE,0x24,0xFE,0xDF,0xFC,0x42,0xFC,0x7B,0xFC, + 0xD3,0xFD,0x45,0x00,0xF9,0x01,0x5B,0x02,0x5B,0x02,0x8B,0x01, + 0xBA,0x00,0x43,0x01,0x0C,0x02,0x23,0x01,0x97,0xFF,0xF0,0xFE, + 0x4C,0xFE,0xD9,0xFD,0xB2,0xFE,0xE7,0xFF,0xCC,0xFD,0x0B,0xFB, + 0xC2,0xFC,0xEB,0x02,0xFF,0x07,0x8F,0x06,0x0B,0x04,0x1D,0x04, + 0x30,0x06,0x8A,0x07,0x22,0x07,0xF6,0x05,0xF6,0x01,0x42,0xFE, + 0x84,0xFC,0x0D,0xFC,0xD0,0xFB,0xE8,0xFB,0x45,0xFC,0x74,0xFA, + 0x8B,0xF8,0x9D,0xF7,0x35,0xF8,0x97,0xFA,0x3B,0xFD,0x58,0xFF, + 0xE5,0xFF,0x43,0x00,0xBB,0x00,0x04,0x02,0x3B,0x04,0xCE,0x04, + 0xCC,0x03,0x37,0x02,0x36,0x01,0x9B,0x00,0x4F,0x00,0xA7,0x00, + 0x59,0x00,0xDF,0xFF,0x2C,0xFF,0x1E,0xFE,0x7A,0xFD,0xF2,0xFC, + 0x07,0x01,0xE2,0x06,0xCD,0x06,0x4E,0x03,0xBC,0x02,0x8A,0x06, + 0x2F,0x07,0x96,0x05,0x3F,0x04,0x9C,0x00,0x3B,0xFD,0xAD,0xFC, + 0xA0,0xFD,0xFC,0xFB,0x9C,0xFA,0x49,0xFA,0x95,0xF9,0x7F,0xF9, + 0x91,0xF9,0xD4,0xF9,0x93,0xFA,0x52,0xFD,0xDF,0xFE,0x50,0xFF, + 0xF0,0x00,0xEE,0x01,0x78,0x02,0x51,0x03,0x52,0x04,0xFE,0x02, + 0x78,0x01,0x93,0x01,0xE5,0x00,0x28,0x00,0x63,0x00,0xA1,0x00, + 0x00,0xFF,0xE3,0xFC,0x39,0xFC,0x11,0xFF,0x39,0x05,0x0E,0x07, + 0x8D,0x03,0x11,0x02,0x52,0x05,0x02,0x08,0x6E,0x07,0x9C,0x06, + 0xA7,0x03,0x77,0xFF,0x02,0xFE,0x4D,0xFE,0x31,0xFD,0x47,0xFB, + 0xA0,0xFA,0x34,0xF9,0xF3,0xF7,0x4B,0xF8,0x09,0xF9,0xA7,0xF9, + 0xD7,0xFA,0x86,0xFC,0x85,0xFD,0x65,0xFF,0x8B,0x01,0x45,0x02, + 0x1F,0x03,0x48,0x04,0x51,0x04,0x08,0x03,0x98,0x02,0x85,0x02, + 0x78,0x01,0x57,0x01,0x6F,0x01,0xBD,0x00,0xAC,0xFF,0x9F,0xFF, + 0xF6,0xFE,0xDB,0xFD,0xB5,0xFD,0x32,0xFE,0x48,0x03,0xBF,0x06, + 0x47,0x05,0xE1,0x02,0x26,0x04,0x0A,0x07,0xF6,0x04,0xC2,0x03, + 0x19,0x02,0xD9,0xFE,0x0F,0xFD,0x8B,0xFD,0x40,0xFD,0x5F,0xFA, + 0x36,0xFA,0xE3,0xF9,0x87,0xF9,0x23,0xFA,0x3A,0xFA,0x29,0xFA, + 0xE8,0xFA,0x04,0xFE,0x6E,0xFF,0xB0,0x00,0x98,0x02,0x90,0x02, + 0x9E,0x02,0xAA,0x03,0x3E,0x04,0x52,0x02,0x65,0x01,0xC3,0x01, + 0xDA,0x00,0x98,0x00,0x7B,0x00,0x30,0x00,0x2C,0xFE,0x4D,0xFC, + 0xB2,0xFC,0x96,0xFF,0x46,0x05,0x8E,0x05,0xC7,0x02,0x1B,0x03, + 0x61,0x06,0x56,0x08,0x89,0x06,0x3F,0x05,0xDA,0x01,0x1F,0xFF, + 0x1A,0xFF,0x0D,0xFF,0x44,0xFD,0xC3,0xFA,0x44,0xFA,0x09,0xF9, + 0xF8,0xF8,0x7F,0xF9,0x45,0xF9,0xA6,0xF9,0x73,0xFA,0x4C,0xFC, + 0xA0,0xFD,0x1A,0x00,0x0B,0x02,0x43,0x02,0x27,0x03,0xB2,0x03, + 0xC8,0x03,0xF8,0x02,0xCB,0x02,0x74,0x02,0x7C,0x01,0xA8,0x01, + 0x46,0x01,0xC7,0x00,0xB5,0xFF,0x2C,0xFF,0x65,0xFE,0xFA,0xFD, + 0x23,0xFE,0x5B,0xFE,0xAC,0x03,0x73,0x06,0xE6,0x04,0x30,0x03, + 0xC5,0x04,0xF3,0x06,0x40,0x04,0x7A,0x03,0x55,0x01,0x54,0xFE, + 0x7A,0xFD,0x14,0xFE,0x59,0xFD,0x60,0xFA,0x65,0xFA,0x79,0xF9, + 0x64,0xF9,0xC5,0xFA,0x8B,0xFA,0x55,0xFA,0x22,0xFB,0x21,0xFE, + 0x6D,0xFF,0x06,0x01,0x00,0x03,0x43,0x02,0x5D,0x02,0x6B,0x03, + 0xC9,0x03,0x0C,0x02,0x75,0x01,0xCC,0x01,0xC6,0x00,0xD0,0x00, + 0xB3,0x00,0xAD,0xFF,0x7C,0xFE,0x63,0xFD,0xC3,0xFB,0x56,0xFF, + 0x37,0x06,0xCE,0x05,0x21,0x02,0x32,0x03,0x90,0x07,0x45,0x07, + 0x87,0x05,0xF3,0x04,0xD8,0x00,0x65,0xFE,0xC4,0xFF,0xE6,0xFF, + 0x9D,0xFC,0xE6,0xFA,0x89,0xFA,0xDB,0xF8,0xBA,0xF9,0xA4,0xFA, + 0x69,0xF9,0x1D,0xF9,0xE6,0xFA,0x58,0xFC,0x9D,0xFD,0x1A,0x01, + 0x49,0x02,0x64,0x01,0x98,0x02,0xDC,0x03,0x22,0x03,0x5B,0x02, + 0xD7,0x02,0xF3,0x01,0x34,0x01,0x5A,0x02,0x08,0x02,0xBA,0x00, + 0xDE,0xFF,0x74,0xFF,0x57,0xFE,0x19,0xFE,0x08,0xFF,0x33,0xFE, + 0x07,0x02,0x19,0x06,0x3C,0x05,0x73,0x03,0x4D,0x04,0xE7,0x06, + 0x46,0x04,0xD5,0x02,0xAC,0x01,0x7C,0xFE,0xA0,0xFD,0x60,0xFE, + 0x06,0xFE,0x97,0xFA,0x2D,0xFA,0x03,0xFA,0x3E,0xF9,0xCC,0xFA, + 0xD1,0xFA,0x25,0xFA,0x9F,0xFA,0xD3,0xFD,0x81,0xFF,0x59,0x00, + 0xD9,0x02,0x61,0x02,0xFC,0x01,0x4A,0x03,0x1A,0x04,0x5F,0x02, + 0x39,0x01,0x28,0x02,0x0B,0x01,0xE8,0x00,0x3A,0x01,0xEE,0xFF, + 0xEC,0xFD,0x76,0xFC,0x88,0xFC,0x8F,0xFF,0x4E,0x05,0x7A,0x05, + 0x56,0x02,0xC7,0x03,0xC5,0x07,0x11,0x08,0x56,0x05,0x26,0x04, + 0x0F,0x01,0xE1,0xFE,0x65,0x00,0x28,0x00,0xA9,0xFC,0xFB,0xF9, + 0x17,0xFA,0x6D,0xF9,0xAB,0xF9,0x78,0xFA,0xFB,0xF8,0x88,0xF8, + 0x2A,0xFA,0x6D,0xFC,0x1E,0xFE,0x9E,0x00,0x26,0x02,0x7A,0x01, + 0xB9,0x02,0x20,0x04,0x8A,0x03,0x84,0x02,0x74,0x02,0x4F,0x02, + 0xA0,0x01,0x9C,0x02,0x71,0x02,0xC7,0x00,0xE0,0xFF,0x46,0xFF, + 0xD2,0xFE,0xFE,0xFD,0xE2,0xFE,0x0D,0xFE,0xBE,0xFF,0x58,0x05, + 0xCE,0x05,0x2A,0x04,0xA6,0x03,0x72,0x06,0xBC,0x05,0xB5,0x02, + 0x3D,0x02,0x59,0xFF,0xF1,0xFD,0x53,0xFE,0x84,0xFE,0xAC,0xFB, + 0x98,0xF9,0x29,0xFA,0x07,0xF9,0x0D,0xFA,0xEE,0xFA,0xFC,0xF9, + 0xFC,0xF9,0x74,0xFC,0x83,0xFF,0xF6,0xFF,0xE6,0x01,0xF4,0x02, + 0x0A,0x02,0x21,0x03,0x64,0x04,0x99,0x03,0x58,0x01,0xB7,0x01, + 0xFA,0x01,0x3B,0x01,0xB3,0x01,0x85,0x00,0xA7,0xFE,0x39,0xFD, + 0x85,0xFC,0xB3,0xFC,0xF8,0x00,0xE3,0x05,0x28,0x04,0x81,0x02, + 0x73,0x05,0x88,0x08,0x16,0x07,0x97,0x04,0x1E,0x03,0xE9,0xFF, + 0x93,0xFF,0x03,0x01,0x2D,0xFF,0x46,0xFB,0x91,0xF9,0xC4,0xF9, + 0x65,0xF9,0x52,0xFA,0x0D,0xFA,0x1F,0xF8,0x83,0xF8,0xE7,0xFA, + 0x34,0xFD,0x1F,0xFF,0x61,0x01,0xDA,0x01,0xC9,0x01,0xB9,0x03, + 0x9A,0x04,0x4B,0x03,0x5E,0x02,0x5B,0x02,0xFC,0x01,0x2E,0x02, + 0x20,0x03,0xF3,0x01,0xEF,0xFF,0x6A,0xFF,0x2F,0xFF,0xA7,0xFE, + 0x41,0xFE,0x9C,0xFE,0x7F,0xFD,0x9C,0x00,0x26,0x06,0xB8,0x05, + 0xFB,0x03,0x3A,0x04,0xB2,0x06,0x3C,0x05,0xBF,0x02,0xE3,0x01, + 0xC1,0xFE,0xCD,0xFD,0x9B,0xFE,0xCD,0xFD,0xE2,0xFA,0xB1,0xFA, + 0x46,0xF9,0x83,0xF7,0x6E,0xFB,0xB5,0xFA,0x21,0xF8,0xB9,0xF9, + 0x30,0xFD,0x36,0x00,0xC8,0x01,0xDB,0x03,0xFF,0x01,0xDB,0x02, + 0x38,0x06,0x3E,0x05,0x60,0x02,0x1B,0x00,0xFF,0x00,0x5D,0x00, + 0xF9,0xFF,0x3D,0xFE,0x9A,0xFB,0x97,0xFC,0x8E,0xFB,0xB1,0xF7, + 0xAE,0xFE,0x4E,0x10,0x98,0x0E,0xB6,0x06,0x01,0x0A,0xB1,0x0C, + 0x02,0x0F,0x82,0x08,0x3E,0xFC,0xFC,0xF6,0x20,0xFD,0x7E,0xFB, + 0x66,0xF2,0x30,0xEF,0x36,0xEF,0x73,0xF7,0x76,0xFB,0x4A,0xF8, + 0x24,0xF9,0xB5,0x02,0x43,0x08,0x7C,0x08,0x06,0x06,0xE7,0x01, + 0x9D,0x09,0x79,0x0B,0x9A,0x02,0x29,0xFC,0xEA,0xFB,0xC8,0xFD, + 0x68,0xFC,0x9D,0xF7,0x0F,0xF5,0xD7,0xFC,0x61,0x01,0xF0,0xFF, + 0x5D,0xFF,0x87,0x02,0xAE,0x06,0x02,0x07,0x56,0x01,0x5F,0xFF, + 0xA0,0x05,0x6C,0x01,0x8E,0x0B,0xB8,0x0F,0x77,0x02,0x82,0x06, + 0x83,0x06,0xEE,0x00,0x0D,0xFD,0x3C,0xF5,0x39,0xF2,0x1E,0xFA, + 0x2A,0xF7,0xDC,0xF2,0xC8,0xF7,0xC2,0xF9,0x4D,0xFF,0x67,0x01, + 0xD1,0xFD,0x8D,0x04,0x68,0x0B,0xDC,0x05,0xA2,0x05,0xDE,0x03, + 0x16,0x01,0x01,0x03,0x64,0xFD,0x4A,0xF9,0xC7,0xFB,0x5D,0xFB, + 0x21,0xFB,0xAB,0xFB,0x5F,0xFB,0x89,0x00,0xB1,0x03,0xBD,0x00, + 0x44,0x02,0x4A,0x03,0x7B,0x02,0x46,0x00,0x08,0xF8,0x65,0x00, + 0xE2,0x15,0x98,0x11,0x5E,0x04,0x79,0x07,0x26,0x0A,0x57,0x0A, + 0xF5,0xFD,0x0F,0xEF,0xD6,0xF6,0xD2,0xFC,0x7A,0xF4,0x2B,0xF0, + 0x62,0xF1,0xD1,0xFA,0x41,0x03,0xAE,0xFA,0xFA,0xF9,0x56,0x07, + 0x6B,0x0C,0xFB,0x09,0xE8,0x01,0xCD,0xFE,0xC1,0x07,0x58,0x06, + 0x96,0xFB,0x08,0xF8,0x5E,0xFB,0x43,0xFF,0x63,0xFB,0x84,0xF5, + 0x6F,0xFA,0x74,0x03,0x57,0x03,0x71,0x00,0x1A,0x01,0xC7,0x06, + 0xD0,0x08,0xF3,0x01,0x05,0xFD,0xCB,0xFE,0x25,0x04,0xFE,0xFD, + 0x20,0x02,0xAC,0x0D,0xE1,0x08,0xC1,0x07,0x90,0x04,0x1E,0x02, + 0x2D,0x04,0x39,0xFB,0xFF,0xF4,0xFD,0xF7,0x16,0xF7,0x15,0xF8, + 0x3E,0xF8,0x78,0xF5,0x50,0xFC,0x1A,0x00,0xE7,0xFD,0xEE,0x01, + 0x5B,0x04,0x8E,0x05,0x14,0x08,0x6C,0x03,0x71,0x00,0xA2,0x02, + 0x95,0x00,0xEC,0xFD,0x0C,0xFB,0x84,0xFA,0x34,0xFE,0x0C,0xFD, + 0x38,0xFB,0x78,0xFE,0xC4,0x00,0x2B,0x02,0xFD,0x01,0x36,0x00, + 0x7B,0x01,0xD7,0xFD,0x35,0xFC,0xC1,0x03,0x1D,0x0D,0x4D,0x10, + 0xB2,0x0A,0x57,0x09,0x95,0x0B,0xEF,0x08,0xB7,0xFF,0xF2,0xF6, + 0x74,0xF6,0x2C,0xF8,0xF1,0xF4,0xAE,0xF0,0xAD,0xF1,0xE3,0xF7, + 0xA7,0xFC,0x82,0xFB,0x6B,0xFC,0x47,0x03,0xBF,0x09,0x5C,0x09, + 0xDB,0x03,0xD9,0x02,0x53,0x06,0x17,0x06,0x36,0x00,0xB7,0xFA, + 0x43,0xFC,0x68,0xFE,0xCF,0xFA,0xBF,0xF7,0x7E,0xF9,0x27,0xFF, + 0xA1,0x01,0x2A,0x00,0xF6,0x00,0x0C,0x05,0xDD,0x06,0x21,0x04, + 0x10,0x00,0x11,0xFF,0xBA,0x03,0x84,0xFF,0x29,0x02,0x2E,0x0D, + 0xFD,0x08,0xC3,0x06,0x5B,0x05,0xA4,0x02,0xD6,0x03,0xF3,0xFB, + 0x19,0xF5,0xE7,0xF7,0xC1,0xF7,0xB7,0xF7,0x12,0xF8,0xF8,0xF5, + 0x78,0xFB,0xBD,0xFF,0x8B,0xFD,0x38,0x00,0x2D,0x03,0x92,0x04, + 0xF3,0x06,0xF6,0x03,0x40,0x01,0xB8,0x02,0x08,0x02,0x13,0x00, + 0x5B,0xFD,0x98,0xFB,0xF0,0xFD,0x9D,0xFD,0x76,0xFB,0xEE,0xFC, + 0xF3,0xFE,0xCC,0x00,0xC6,0x01,0x1E,0x01,0xE1,0x00,0x72,0x00, + 0xBD,0xFE,0xB8,0xFE,0xE8,0x05,0x27,0x0E,0x68,0x0C,0x6A,0x08, + 0x79,0x08,0x65,0x09,0xE5,0x06,0x1A,0xFE,0xD9,0xF7,0x53,0xF8, + 0xC1,0xF8,0x8F,0xF6,0x44,0xF3,0x47,0xF3,0xE5,0xF8,0x3E,0xFC, + 0x45,0xFB,0xBE,0xFC,0x0C,0x01,0x79,0x05,0x12,0x06,0xAB,0x02, + 0xFE,0x01,0x13,0x05,0xAD,0x06,0xBA,0x03,0xBE,0xFF,0xBE,0xFE, + 0x7E,0xFF,0x09,0xFE,0xB1,0xFA,0x01,0xFA,0x85,0xFC,0x3A,0xFF, + 0x86,0x00,0x8E,0x00,0xBF,0x01,0xCC,0x03,0x82,0x03,0x6D,0x01, + 0x18,0x00,0x30,0x01,0x77,0x00,0xAE,0x02,0xDF,0x07,0x10,0x07, + 0xE3,0x04,0x14,0x04,0x96,0x04,0xAF,0x03,0xD7,0xFE,0xFF,0xFA, + 0x30,0xFA,0x06,0xFB,0xFD,0xFB,0x7E,0xFA,0xE4,0xF8,0xB7,0xFA, + 0x9B,0xFC,0x00,0xFD,0x42,0xFD,0x69,0xFD,0xA7,0xFE,0xCD,0x00, + 0xC3,0x01,0x50,0x01,0x30,0x01,0xA7,0x02,0x4A,0x04,0x86,0x03, + 0x3E,0x01,0x63,0x00,0x20,0x00,0x71,0xFF,0x1E,0xFF,0x93,0xFE, + 0xEC,0xFE,0x46,0x00,0x65,0x00,0xE4,0xFF,0x50,0xFF,0x02,0xFE, + 0x49,0xFD,0x49,0xFE,0x3D,0x01,0x86,0x04,0x20,0x05,0xDA,0x04, + 0x1E,0x06,0xF6,0x06,0xAB,0x05,0x48,0x03,0x1C,0x01,0x2C,0x00, + 0x99,0x00,0xB8,0xFF,0x58,0xFD,0x29,0xFC,0xD0,0xFB,0x82,0xFB, + 0x7C,0xFB,0xA2,0xFA,0xB3,0xF9,0xFE,0xF9,0x8F,0xFA,0xFA,0xFA, + 0x2F,0xFC,0xFB,0xFD,0x20,0x00,0x99,0x02,0x99,0x03,0x6D,0x03, + 0x12,0x03,0x69,0x02,0x52,0x02,0x34,0x02,0x86,0x01,0x84,0x01, + 0xE6,0x01,0x9D,0x01,0x06,0x01,0xEC,0xFF,0xCA,0xFE,0x8D,0xFE, + 0x60,0xFE,0x90,0xFE,0x89,0xFE,0x45,0xFF,0x62,0x03,0xC1,0x05, + 0xFB,0x03,0x3C,0x05,0x38,0x07,0x30,0x05,0xD9,0x03,0xD3,0x01, + 0xDE,0xFE,0xED,0xFF,0xFB,0xFF,0x07,0xFD,0x26,0xFC,0x01,0xFB, + 0xC8,0xF9,0xE1,0xFA,0x29,0xFA,0x7D,0xF8,0x58,0xF9,0xA1,0xFA, + 0x5B,0xFC,0x16,0xFE,0x0B,0xFE,0xB5,0xFF,0xC3,0x02,0x66,0x03, + 0x79,0x03,0x45,0x03,0xFF,0x01,0xA6,0x02,0x69,0x03,0x2E,0x02, + 0xCC,0x01,0xA7,0x01,0x21,0x01,0x04,0x01,0xA4,0xFF,0xDB,0xFD, + 0xEE,0xFC,0xE4,0xFC,0xF5,0xFF,0xAB,0x03,0x95,0x03,0xC5,0x03, + 0xA4,0x06,0xE6,0x07,0xEA,0x06,0xE5,0x04,0x14,0x02,0x6A,0x01, + 0x0B,0x02,0x06,0x00,0x27,0xFD,0x65,0xFB,0x00,0xFA,0x15,0xFA, + 0x44,0xFA,0x90,0xF8,0x5A,0xF7,0x0D,0xF8,0x9C,0xF9,0xCB,0xFA, + 0x48,0xFB,0xCC,0xFC,0x09,0x00,0xC0,0x02,0xFC,0x03,0x2F,0x04, + 0xAC,0x03,0xAF,0x03,0x20,0x04,0xB0,0x03,0xE0,0x02,0x8C,0x02, + 0x93,0x02,0xB9,0x02,0xF8,0x01,0x5E,0x00,0x57,0xFF,0x23,0xFF, + 0xF0,0xFE,0x95,0xFE,0x3B,0xFE,0x96,0xFD,0xB3,0xFF,0x4B,0x04, + 0x4E,0x04,0xB6,0x02,0x72,0x05,0x38,0x06,0x55,0x04,0x77,0x03, + 0x54,0x00,0xB1,0xFE,0xA4,0x00,0x1C,0xFF,0x83,0xFC,0xEB,0xFB, + 0xE5,0xF9,0xB7,0xF9,0x0B,0xFB,0x50,0xF9,0x95,0xF8,0x02,0xFA, + 0xB1,0xFA,0xD0,0xFC,0x9E,0xFE,0x72,0xFE,0xA8,0x00,0x90,0x03, + 0x13,0x04,0x5F,0x04,0xB8,0x03,0x8E,0x02,0x42,0x03,0x6F,0x03, + 0x7B,0x02,0x24,0x02,0x7C,0x01,0x1D,0x01,0x25,0x01,0x7B,0xFF, + 0xEF,0xFD,0xE4,0xFD,0x07,0xFD,0x08,0xFD,0xA8,0x00,0x94,0x03, + 0xFC,0x02,0xB4,0x03,0x87,0x06,0x34,0x07,0x17,0x06,0x22,0x04, + 0x8F,0x01,0x39,0x01,0x8B,0x01,0x68,0xFF,0x21,0xFD,0x63,0xFB, + 0xDD,0xF9,0x60,0xFA,0x6A,0xFA,0x6F,0xF8,0x98,0xF7,0x70,0xF8, + 0xD3,0xF9,0x4F,0xFB,0x09,0xFC,0x79,0xFD,0x7F,0x00,0xD4,0x02, + 0xF2,0x03,0x9E,0x04,0x20,0x04,0x98,0x03,0x34,0x04,0x00,0x04, + 0x0C,0x03,0xC2,0x02,0x80,0x02,0x34,0x02,0xB0,0x01,0x42,0x00, + 0x25,0xFF,0xE7,0xFE,0x61,0xFE,0x44,0xFE,0x1B,0xFE,0x50,0xFD, + 0x31,0x00,0x61,0x04,0x42,0x03,0x17,0x03,0x7D,0x06,0x91,0x05, + 0xDE,0x03,0xBF,0x03,0x2E,0x00,0x0E,0xFF,0xD5,0x00,0x5B,0xFE, + 0x9A,0xFC,0x70,0xFC,0xBF,0xF9,0xD8,0xF9,0x1A,0xFB,0x14,0xF9, + 0xFB,0xF8,0x54,0xFA,0x6B,0xFA,0x82,0xFC,0x55,0xFE,0x9D,0xFE, + 0xFD,0x00,0x2E,0x03,0xF2,0x03,0xCD,0x04,0xDC,0x03,0xCE,0x02, + 0x76,0x03,0x08,0x03,0x73,0x02,0x9C,0x02,0xAA,0x01,0x39,0x01, + 0x4D,0x01,0xCE,0xFF,0x61,0xFE,0x06,0xFE,0xC5,0xFD,0x95,0xFD, + 0x3F,0xFE,0x49,0x01,0xB5,0x03,0xED,0x02,0x0D,0x04,0xC4,0x06, + 0x1D,0x06,0xE1,0x04,0xAD,0x03,0x0A,0x01,0xBF,0x00,0xEA,0x00, + 0x43,0xFE,0x7C,0xFC,0x7B,0xFB,0xFE,0xF9,0x5E,0xFA,0x2F,0xFA, + 0x3D,0xF8,0x1E,0xF8,0x37,0xF9,0x41,0xFA,0xDF,0xFB,0xF3,0xFC, + 0x85,0xFE,0x45,0x01,0xE1,0x02,0xF2,0x03,0xE5,0x04,0x14,0x04, + 0xB2,0x03,0x5C,0x04,0xAA,0x03,0x05,0x03,0xE9,0x02,0x12,0x02, + 0xD3,0x01,0x8B,0x01,0x25,0x00,0x54,0xFF,0xDC,0xFE,0x23,0xFE, + 0x52,0xFE,0xD8,0xFD,0x87,0xFD,0x24,0x01,0xC1,0x03,0x57,0x02, + 0x9A,0x03,0x02,0x06,0x02,0x05,0x5E,0x04,0xFC,0x02,0xF0,0xFF, + 0x1F,0x00,0x51,0x00,0xC0,0xFD,0xB4,0xFC,0x88,0xFB,0xA9,0xF9, + 0x93,0xFA,0xD1,0xFA,0x3E,0xF9,0x84,0xF9,0x3D,0xFA,0xD8,0xFA, + 0xE2,0xFC,0x28,0xFE,0xF5,0xFE,0x2B,0x01,0x0B,0x03,0x38,0x04, + 0xDA,0x04,0xF3,0x03,0x5B,0x03,0xA9,0x03,0x2C,0x03,0xB0,0x02, + 0x61,0x02,0x7C,0x01,0x24,0x01,0x08,0x01,0xE3,0xFF,0x9F,0xFE, + 0x17,0xFE,0xE3,0xFD,0x80,0xFD,0x5F,0xFE,0x63,0x01,0xB2,0x02, + 0x1B,0x02,0x1D,0x04,0x3B,0x06,0x98,0x05,0x00,0x05,0x7F,0x03, + 0x1A,0x01,0x15,0x01,0x96,0x00,0x04,0xFE,0x86,0xFC,0x3C,0xFB, + 0x17,0xFA,0xCA,0xFA,0x7D,0xFA,0xD2,0xF8,0xC1,0xF8,0x9B,0xF9, + 0x91,0xFA,0xF0,0xFB,0xE0,0xFC,0x6E,0xFE,0xE1,0x00,0x93,0x02, + 0xE9,0x03,0xD0,0x04,0x55,0x04,0x04,0x04,0x52,0x04,0xD2,0x03, + 0x30,0x03,0xC4,0x02,0xFB,0x01,0xB4,0x01,0x76,0x01,0x7C,0x00, + 0xB8,0xFF,0x36,0xFF,0x77,0xFE,0x78,0xFE,0x63,0xFE,0x4A,0xFD, + 0x05,0xFF,0x79,0x02,0x30,0x02,0xF5,0x01,0xC8,0x04,0x28,0x05, + 0x18,0x04,0xE3,0x03,0x5F,0x01,0xAC,0xFF,0x61,0x00,0xB7,0xFE, + 0xD1,0xFC,0x89,0xFC,0xAB,0xFA,0x1B,0xFA,0x96,0xFB,0xC1,0xFA, + 0xEB,0xF9,0xE3,0xFA,0xFF,0xFA,0xFA,0xFB,0xFD,0xFD,0x86,0xFE, + 0xC9,0xFF,0x0C,0x02,0x25,0x03,0x4D,0x04,0xC0,0x04,0x9B,0x03, + 0x78,0x03,0x87,0x03,0x8E,0x02,0x58,0x02,0xFE,0x01,0x02,0x01, + 0xFD,0x00,0xCA,0x00,0x9F,0xFF,0xED,0xFE,0x63,0xFE,0x97,0xFD, + 0x25,0xFE,0x0F,0x00,0x2A,0x01,0x40,0x01,0x4D,0x02,0x1B,0x04, + 0xF7,0x04,0xD4,0x04,0xBD,0x03,0xF9,0x01,0x1D,0x01,0xC9,0x00, + 0x6C,0xFF,0x99,0xFD,0x2F,0xFC,0x51,0xFB,0x76,0xFB,0xD0,0xFB, + 0x1F,0xFB,0x3C,0xFA,0x32,0xFA,0xD9,0xFA,0xC1,0xFB,0x7A,0xFC, + 0x5C,0xFD,0xE3,0xFE,0xA1,0x00,0x5B,0x02,0x8F,0x03,0xC8,0x03, + 0xBA,0x03,0xCA,0x03,0xB4,0x03,0x72,0x03,0xDC,0x02,0x19,0x02, + 0xC4,0x01,0xAE,0x01,0x51,0x01,0xC3,0x00,0x3C,0x00,0xC4,0xFF, + 0x75,0xFF,0x01,0xFF,0xA9,0xFE,0x28,0xFE,0x1D,0xFE,0x38,0x00, + 0x16,0x02,0x96,0x01,0x28,0x02,0xF9,0x03,0xAA,0x03,0x04,0x03, + 0x74,0x02,0x57,0x00,0x44,0xFF,0x6B,0xFF,0x24,0xFE,0xF9,0xFC, + 0x4F,0xFC,0x1E,0xFB,0x44,0xFB,0x43,0xFC,0xF7,0xFB,0x89,0xFB, + 0xF3,0xFB,0x48,0xFC,0x53,0xFD,0xA4,0xFE,0x5B,0xFF,0x42,0x00, + 0xD4,0x01,0x21,0x03,0x1B,0x04,0x1A,0x04,0x7A,0x03,0x21,0x03, + 0x97,0x02,0x88,0x02,0xE7,0x00,0x81,0x02,0x9D,0x02,0xFF,0xFE, + 0x24,0xFF,0x9C,0xFE,0x26,0xFE,0x57,0xFD,0x44,0xFC,0x73,0xFE, + 0x83,0x02,0x8B,0x04,0x39,0x03,0x1E,0x02,0x53,0x04,0x97,0x07, + 0xC4,0x07,0xCD,0x03,0x41,0xFF,0x7D,0xFE,0x01,0xFF,0xD8,0xFD, + 0x58,0xFA,0x3A,0xF6,0xD5,0xF5,0xD4,0xF8,0xEF,0xFA,0x1E,0xFB, + 0x9F,0xFA,0xE1,0xFB,0x2A,0x00,0x67,0x03,0x3C,0x03,0xED,0x02, + 0x3E,0x04,0x04,0x06,0xD6,0x06,0xBD,0x04,0x83,0x01,0x3C,0x00, + 0x7B,0xFF,0x3A,0xFE,0xBB,0xFC,0x64,0xFB,0xE7,0xFB,0x4F,0xFE, + 0x1D,0x00,0x9F,0x00,0x53,0x01,0x53,0x02,0xA4,0x03,0x7C,0x03, + 0x86,0x01,0x29,0xFF,0xBB,0xFC,0xB1,0xFF,0x7C,0x07,0xBE,0x08, + 0xB6,0x03,0x61,0x01,0x3A,0x02,0x00,0x05,0x65,0x05,0xCD,0xFE, + 0x24,0xF9,0x99,0xF9,0xCF,0xFA,0xA9,0xFA,0xDE,0xF7,0x78,0xF4, + 0x32,0xF7,0x36,0xFD,0xCD,0xFF,0x3A,0x00,0x8C,0x00,0x72,0x02, + 0x90,0x06,0x1A,0x07,0x9A,0x03,0x02,0x02,0x61,0x02,0xB2,0x02, + 0xDB,0x01,0x7A,0xFE,0x87,0xFC,0xC5,0xFD,0x60,0xFE,0xD3,0xFD, + 0x2A,0xFD,0x7E,0xFD,0xFB,0xFF,0x90,0x02,0xA0,0x02,0xF6,0x01, + 0x73,0x02,0x42,0x03,0x40,0x03,0x86,0x01,0xAD,0xFE,0x58,0xFD, + 0xF1,0xFB,0xB6,0xFC,0x89,0x05,0x7D,0x09,0xD3,0x04,0xCD,0x03, + 0x2C,0x04,0xB6,0x04,0x83,0x05,0xA7,0xFF,0xC6,0xF8,0xB1,0xF8, + 0xEB,0xF8,0xE0,0xF7,0x16,0xF7,0xAB,0xF4,0xD1,0xF6,0x75,0xFD, + 0x14,0x00,0x8B,0x01,0x51,0x04,0xF4,0x04,0xDB,0x06,0x44,0x08, + 0xF5,0x04,0xFB,0x02,0x62,0x02,0xDF,0xFF,0x0D,0xFF,0x67,0xFD, + 0x97,0xFA,0x53,0xFC,0xC4,0xFD,0xE9,0xFC,0xED,0xFD,0x39,0xFF, + 0x2F,0x01,0xEC,0x03,0x8C,0x03,0x1E,0x02,0xD9,0x02,0xD1,0x00, + 0xE5,0x00,0xE0,0xFF,0x9B,0xFA,0xD2,0xFC,0x39,0x04,0xE6,0x0A, + 0xEA,0x09,0xF9,0x03,0xEC,0x02,0xE7,0x05,0xE9,0x06,0xF0,0x01, + 0x3C,0xFA,0x80,0xF6,0x43,0xF8,0xDE,0xF8,0x6C,0xF5,0x3F,0xF2, + 0xBD,0xF3,0xE1,0xF9,0x87,0xFF,0x4B,0x01,0x6B,0x01,0xE0,0x04, + 0x43,0x09,0xAC,0x09,0xA5,0x07,0x44,0x04,0x9C,0x02,0x22,0x03, + 0x8A,0x00,0x5C,0xFC,0x9D,0xFA,0x49,0xFA,0xE9,0xFB,0x7C,0xFD, + 0x9D,0xFC,0x7B,0xFD,0xBA,0x00,0x0E,0x03,0x70,0x04,0xA0,0x04, + 0xDA,0x03,0x0B,0x04,0xF1,0x03,0x1A,0x01,0xA8,0xFE,0x8A,0xFC, + 0x03,0xFA,0x09,0xFB,0xE6,0x01,0x73,0x0B,0xB8,0x09,0x1E,0x02, + 0xB0,0x02,0x83,0x05,0x51,0x06,0x71,0x02,0xAA,0xF9,0xF0,0xF5, + 0x23,0xF9,0x52,0xF9,0xD7,0xF5,0x76,0xF3,0x50,0xF4,0xF8,0xFA, + 0xE3,0x00,0x4F,0x01,0xC0,0x02,0x62,0x05,0xE4,0x07,0x5C,0x0A, + 0xF3,0x07,0x79,0x03,0x63,0x02,0xF9,0x01,0x75,0xFF,0x05,0xFC, + 0x69,0xF9,0x8D,0xFA,0xFC,0xFC,0xDD,0xFC,0x02,0xFD,0x33,0xFF, + 0xCD,0x01,0xE8,0x03,0x17,0x04,0x83,0x02,0x9B,0x02,0x02,0x02, + 0xB7,0x00,0xFF,0xFC,0xD3,0xF9,0x1C,0xFD,0xA0,0x05,0x6F,0x0E, + 0xF5,0x0A,0x1B,0x04,0x8D,0x05,0x2F,0x08,0x18,0x07,0x4A,0x00, + 0xC0,0xF6,0x41,0xF4,0xEE,0xF7,0xBD,0xF6,0xF3,0xF1,0x30,0xF0, + 0x23,0xF3,0x21,0xFB,0xB1,0x00,0x52,0x00,0xF5,0x01,0x21,0x07, + 0x14,0x0C,0x27,0x0C,0xCE,0x07,0xC8,0x04,0xB1,0x04,0x86,0x04, + 0x13,0x00,0xD9,0xFA,0x70,0xF9,0x16,0xFA,0x73,0xFB,0xC8,0xFB, + 0x29,0xFB,0xDE,0xFC,0xC4,0x00,0x56,0x03,0x15,0x04,0x0B,0x04, + 0x70,0x04,0x3A,0x05,0x55,0x04,0x5B,0x00,0x31,0xFE,0xA4,0xFF, + 0xB5,0xFB,0x88,0xF8,0x84,0xFD,0x16,0x06,0x8E,0x0A,0x45,0x07, + 0xD1,0x03,0xBC,0x04,0x37,0x07,0xBA,0x05,0xA6,0xFF,0x46,0xF9, + 0x64,0xF7,0x7C,0xF8,0xFA,0xF6,0x23,0xF3,0x12,0xF2,0x64,0xF5, + 0x89,0xFA,0x16,0xFE,0xA3,0xFF,0x5A,0x03,0x49,0x07,0x4A,0x09, + 0x4C,0x0A,0x6F,0x09,0xEF,0x07,0x18,0x06,0xA0,0x03,0x2A,0x00, + 0x50,0xFC,0xBE,0xFA,0x8E,0xFA,0xE7,0xF9,0x84,0xF9,0x14,0xFB, + 0xD7,0xFD,0x2A,0x00,0xAB,0x01,0xF5,0x02,0xDF,0x03,0x13,0x04, + 0x77,0x03,0x51,0x02,0xAA,0x00,0xCC,0xFC,0xA5,0xFB,0xC1,0xFE, + 0x99,0x07,0xAC,0x0D,0xEC,0x06,0x49,0x03,0xCC,0x05,0xDF,0x05, + 0xA8,0x04,0x3B,0xFE,0x5B,0xF6,0x5A,0xF7,0xEE,0xF8,0x3F,0xF5, + 0x27,0xF3,0x5C,0xF2,0x99,0xF5,0x88,0xFC,0x6B,0xFE,0x4D,0xFE, + 0xEF,0x02,0xFB,0x07,0xBA,0x09,0xB2,0x09,0x2B,0x07,0xDF,0x06, + 0x13,0x08,0xFE,0x03,0x89,0xFF,0xDF,0xFC,0xE0,0xFA,0x2C,0xFC, + 0xCE,0xFB,0xCE,0xF8,0x48,0xFA,0x2A,0xFE,0x03,0x00,0x2A,0x01, + 0xB5,0x01,0x7A,0x02,0x09,0x05,0x31,0x05,0x24,0x01,0xB5,0xFF, + 0x70,0x00,0xB2,0xFD,0xFC,0xFB,0x26,0xFC,0x9E,0x04,0x79,0x0E, + 0x0D,0x09,0x43,0x04,0x3E,0x05,0x36,0x05,0x6B,0x06,0x50,0x01, + 0xA1,0xF7,0x75,0xF6,0x87,0xF8,0x17,0xF6,0x57,0xF4,0x0B,0xF2, + 0x2B,0xF3,0xBF,0xFA,0x6A,0xFD,0x6A,0xFD,0xB2,0x01,0x05,0x06, + 0x1C,0x09,0x43,0x0A,0xE7,0x07,0xC5,0x07,0x4A,0x09,0x9B,0x05, + 0x70,0x01,0xA2,0xFE,0xBD,0xFB,0xB0,0xFC,0x2F,0xFC,0x7F,0xF8, + 0xBE,0xF9,0xC1,0xFC,0x32,0xFE,0xFC,0xFF,0xB7,0x00,0xF8,0x01, + 0x1E,0x05,0x06,0x04,0xD8,0x00,0xD0,0x00,0x55,0x01,0x69,0x01, + 0xAE,0xFE,0xCF,0xFA,0x85,0xFE,0x75,0x0B,0xFA,0x0C,0xB0,0x04, + 0x5B,0x03,0x18,0x03,0x82,0x05,0x62,0x05,0x81,0xFB,0x45,0xF6, + 0xF2,0xF8,0xC6,0xF8,0x4E,0xF7,0x65,0xF4,0x0A,0xF2,0x32,0xF8, + 0x19,0xFD,0x45,0xFC,0x82,0xFE,0x19,0x02,0xE8,0x05,0x1E,0x09, + 0x04,0x07,0xAD,0x05,0xCB,0x08,0xC4,0x07,0xE6,0x03,0x8E,0x01, + 0x17,0xFE,0x2F,0xFE,0x0A,0xFF,0x1E,0xFB,0x4A,0xFA,0x4C,0xFC, + 0xBB,0xFC,0x9A,0xFE,0x6D,0xFF,0x24,0xFF,0x1D,0x02,0x91,0x03, + 0x56,0x01,0x90,0x01,0xB2,0x01,0xE9,0x01,0x85,0x03,0x4A,0xFF, + 0xD2,0xFB,0x53,0xFE,0xF6,0x05,0x22,0x0B,0x54,0x05,0xA9,0x00, + 0xB4,0x01,0x01,0x04,0xD7,0x04,0x78,0xFF,0xF0,0xF8,0x7C,0xF9, + 0xF6,0xFB,0x8A,0xFA,0xAF,0xF7,0x65,0xF5,0x3B,0xF7,0xDB,0xFB, + 0x91,0xFC,0x23,0xFC,0x10,0xFF,0x9D,0x02,0x40,0x05,0x4F,0x05, + 0x0A,0x04,0xD2,0x05,0x4E,0x07,0x28,0x05,0x05,0x03,0x19,0x01, + 0xF8,0xFF,0xBE,0x00,0xF8,0xFE,0x77,0xFC,0x04,0xFD,0x28,0xFE, + 0xD9,0xFD,0xA8,0xFE,0x05,0x00,0x85,0x00,0x21,0x02,0x0E,0x01, + 0xCC,0xFF,0xC5,0x00,0x45,0x00,0xE2,0x01,0xF1,0x01,0x54,0xFF, + 0x52,0xFF,0xB7,0xFE,0x03,0x00,0xC4,0x04,0x82,0x05,0xFC,0x01, + 0x23,0x01,0x09,0x02,0xD2,0x01,0x54,0x01,0x16,0xFE,0x9D,0xFB, + 0x44,0xFD,0x5D,0xFD,0xF9,0xFB,0xF8,0xFA,0x17,0xFA,0xB2,0xFB, + 0xB4,0xFD,0x05,0xFD,0x33,0xFE,0xAD,0xFF,0x9F,0x00,0x97,0x02, + 0x50,0x01,0xA5,0x01,0x9E,0x02,0x58,0x02,0x5C,0x02,0x49,0x01, + 0x30,0x01,0x7F,0x01,0x1E,0x01,0xC4,0x00,0x4A,0x00,0x49,0x00, + 0x56,0x00,0x74,0x00,0x9F,0x00,0x21,0x00,0xAE,0x00,0x2C,0x00, + 0xD5,0xFF,0xA2,0x00,0xE8,0xFF,0x12,0x00,0xBB,0x00,0x57,0xFF, + 0xAD,0xFF,0x43,0x00,0x6E,0xFF,0x11,0x00,0xCD,0xFF,0x9C,0xFF, + 0xEB,0xFF,0xE8,0xFF,0x2B,0x00,0x14,0x00,0xE4,0xFF,0x7F,0xFF, + 0xBA,0xFF,0xC3,0xFF,0x9E,0xFF,0xCF,0xFF,0x6B,0xFF,0x89,0xFF, + 0xB0,0xFF,0x92,0xFF,0xA9,0xFF,0x99,0xFF,0x60,0xFF,0x65,0xFF, + 0x7B,0xFF,0x6F,0xFF,0xA3,0xFF,0x90,0xFF,0x80,0xFF,0xB2,0xFF, + 0xCF,0xFF,0xE1,0xFF,0xF5,0xFF,0x10,0x00,0x33,0x00,0x6E,0x00, + 0x6F,0x00,0x87,0x00,0xAD,0x00,0xB0,0x00,0xC9,0x00,0xC7,0x00, + 0x95,0x00,0xBA,0x00,0xB5,0x00,0xAA,0x00,0xAE,0x00,0x5F,0x00, + 0x86,0x00,0x4A,0x00,0x94,0x00,0x34,0x00,0xD0,0xFF,0x2D,0x00, + 0xB5,0xFF,0xD8,0xFF,0xBA,0xFF,0x8F,0xFF,0x8C,0xFF,0x9A,0xFF, + 0x96,0xFF,0x9B,0xFF,0x97,0xFF,0xA0,0xFF,0xCC,0xFF,0xB5,0xFF, + 0xDA,0xFF,0xC2,0xFF,0xC0,0xFF,0xD3,0xFF,0xCB,0xFF,0xC8,0xFF, + 0xBE,0xFF,0xBE,0xFF,0xC7,0xFF,0xCB,0xFF,0xC8,0xFF,0xD1,0xFF, + 0xDF,0xFF,0xF0,0xFF,0xFC,0xFF,0xFC,0xFF,0x07,0x00,0x1D,0x00, + 0x22,0x00,0x20,0x00,0x27,0x00,0x33,0x00,0x3F,0x00,0x45,0x00, + 0x44,0x00,0x46,0x00,0x4C,0x00,0x55,0x00,0x53,0x00,0x50,0x00, + 0x44,0x00,0x3B,0x00,0x3B,0x00,0x2D,0x00,0x19,0x00,0x0C,0x00, + 0x02,0x00,0xF7,0xFF,0xF0,0xFF,0xDC,0xFF,0xD7,0xFF,0xD6,0xFF, + 0xD0,0xFF,0xD7,0xFF,0xD7,0xFF,0xD0,0xFF,0xD1,0xFF,0xDA,0xFF, + 0xDB,0xFF,0xD7,0xFF,0xE9,0xFF,0xEA,0xFF,0xEF,0xFF,0xF7,0xFF, + 0xF5,0xFF,0xFE,0xFF,0x03,0x00,0x07,0x00,0x06,0x00,0x0A,0x00, + 0x08,0x00,0x0B,0x00,0x08,0x00,0x04,0x00,0x06,0x00,0x04,0x00, + 0x04,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00, + 0xFF,0xFF,0x02,0x00,0x07,0x00,0x07,0x00,0x08,0x00,0x09,0x00, + 0x0A,0x00,0x0C,0x00,0x0A,0x00,0x0A,0x00,0x09,0x00,0x08,0x00, + 0x08,0x00,0x04,0x00,0x00,0x00,0x01,0x00,0xFD,0xFF,0xFA,0xFF, + 0xFA,0xFF,0xFA,0xFF,0xF8,0xFF,0xF5,0xFF,0xF6,0xFF,0xF6,0xFF, + 0xF8,0xFF,0xFA,0xFF,0xFB,0xFF,0xFB,0xFF,0xFE,0xFF,0x00,0x00, + 0x01,0x00,0x02,0x00,0x04,0x00,0x05,0x00,0x03,0x00,0x05,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x01,0x00,0x01,0x00, + 0x00,0x00,0xFE,0xFF,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00, + 0x03,0x00,0x02,0x00,0x01,0x00,0x01,0x00,0xFE,0xFF,0x00,0x00, + 0x01,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xFF, + 0xFF,0xFF,0x01,0x00,0x02,0x00,0xFB,0xFF,0x04,0x00,0xF3,0xFF, + 0x03,0x00,0x10,0x00,0x0E,0x00,0xD1,0xFF,0xC8,0xFF,0x22,0x00, + 0x01,0x00,0x1A,0x00,0xF9,0xFF,0x18,0x00,0x27,0x00,0x9F,0xFF, + 0x06,0x00,0x2D,0x00,0xF7,0xFF,0x08,0x00,0xF7,0xFF,0x05,0x00, + 0xF7,0xFF,0x08,0x00,0x15,0x00,0xCC,0xFF,0x06,0x00,0x19,0x00, + 0xF1,0xFF,0x0E,0x00,0xFE,0xFF,0x08,0x00,0x01,0x00,0xF8,0xFF, + 0x04,0x00,0x00,0x00,0x04,0x00,0x01,0x00,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0x02,0x00,0x00,0x00,0xFB,0xFF,0x02,0x00,0x02,0x00, + 0xFD,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00, + 0x00,0x00,0x02,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00, + 0xFE,0xFF,0x03,0x00,0xFE,0xFF,0x08,0x00,0xFC,0xFF,0xE4,0xFF, + 0x14,0x00,0x06,0x00,0xF5,0xFF,0x04,0x00,0x02,0x00,0x02,0x00, + 0x01,0x00,0xFE,0xFF,0xFF,0xFF,0x0C,0x00,0xF3,0xFF,0xFD,0xFF, + 0x0F,0x00,0xF8,0xFF,0xFC,0xFF,0x05,0x00,0x00,0x00,0x02,0x00, + 0xFF,0xFF,0xFE,0xFF,0x00,0x00,0xFE,0xFF,0x02,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF, + 0x00,0x00,0x01,0x00,0xFE,0xFF,0xFF,0xFF,0x00,0x00,0xFF,0xFF, + 0x00,0x00,0x00,0x00,0xFE,0xFF,0xFF,0xFF,0x00,0x00,0xFF,0xFF, + 0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0xFF,0xFF,0x01,0x00, + 0x03,0x00,0x02,0x00,0xFF,0xFF,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x04,0x00,0x04,0x00,0x00,0x00,0xFF,0xFF,0xFD,0xFF,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xFF, + 0x00,0x00,0x00,0x00,0x01,0x00,0xFF,0xFF,0x00,0x00,0x02,0x00, + 0xFF,0xFF,0x01,0x00,0x00,0x00,0xFE,0xFF,0x00,0x00,0x01,0x00, + 0x00,0x00,0xFE,0xFF,0x02,0x00,0x03,0x00,0x01,0x00,0xFE,0xFF, + 0xFE,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0xF7,0xFF,0x03,0x00, + 0x06,0x00,0xF7,0xFF,0x03,0x00,0x03,0x00,0xFF,0xFF,0x01,0x00, + 0xFF,0xFF,0x02,0x00,0xFF,0xFF,0x00,0x00,0x03,0x00,0x00,0x00, + 0xFF,0xFF,0xFB,0xFF,0xFC,0xFF,0x03,0x00,0x04,0x00,0x02,0x00, + 0xFE,0xFF,0xFB,0xFF,0xFE,0xFF,0x02,0x00,0x02,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0xFE,0xFF,0x00,0x00,0x00,0x00,0xFE,0xFF, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFB,0xFF,0x04,0x00,0x05,0x00,0xF6,0xFF,0x04,0x00,0x06,0x00, + 0xFA,0xFF,0x00,0x00,0xFF,0xFF,0x01,0x00,0x02,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x03,0x00, + 0xFF,0xFF,0xFE,0xFF,0x00,0x00,0x01,0x00,0x03,0x00,0x02,0x00, + 0x09,0x00,0xF0,0xFF,0xF5,0xFF,0x04,0x00,0xFF,0xFF,0x0D,0x00, + 0x02,0x00,0xFD,0xFF,0x00,0x00,0xFF,0xFF,0xFE,0xFF,0x00,0x00, + 0x01,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00,0x00,0x00, + 0xFF,0xFF,0xFD,0xFF,0x01,0x00,0x03,0x00,0xFE,0xFF,0xFD,0xFF, + 0x00,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00,0x00,0x00, + 0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x00, + 0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0x01,0x00,0xFF,0xFF,0x02,0x00,0xFF,0xFF, + 0x01,0x00,0xFE,0xFF,0x01,0x00,0xFF,0xFF,0xFF,0xFF,0x01,0x00, + 0x00,0x00,0x01,0x00,0xFF,0xFF,0x04,0x00,0xF7,0xFF,0x0A,0x00, + 0xEF,0xFF,0x13,0x00,0xE1,0xFF,0x20,0x00,0x1D,0x00,0xA9,0xFF, + 0x34,0x00,0xEF,0xFF,0xFF,0xFF,0x0E,0x00,0xF8,0xFF,0x14,0x00, + 0xF0,0xFF,0x0A,0x00,0xFF,0xFF,0x01,0x00,0x01,0x00,0x04,0x00, + 0xFF,0xFF,0xFD,0xFF,0xFD,0xFF,0x00,0x00,0xFC,0xFF,0xFD,0xFF, + 0x01,0x00,0xFF,0xFF,0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0xFD,0xFF,0xFC,0xFF,0xFF,0xFF,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xFF,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x02,0x00,0x01,0x00,0x07,0x00,0xF5,0xFF, + 0x0C,0x00,0xF5,0xFF,0x08,0x00,0xF7,0xFF,0x03,0x00,0xFC,0xFF, + 0x06,0x00,0xFF,0xFF,0xF9,0xFF,0x0A,0x00,0xF5,0xFF,0x42,0x00, + 0xA8,0xFF,0x92,0x00,0xEC,0xFE,0x70,0x01,0x6F,0x02,0x9C,0xFE, + 0x79,0xFF,0x9E,0xFD,0x5B,0xFF,0x19,0x00,0x94,0xFF,0xF4,0x00, + 0xC7,0xFF,0x01,0x00,0x6C,0x00,0xC1,0x00,0xF2,0xFF,0x72,0x00, + 0xBC,0xFF,0x9B,0x00,0xFD,0x02,0x7D,0xFE,0x4B,0x01,0x47,0xFF, + 0xF9,0xFB,0x19,0x00,0xDF,0xFD,0x89,0x00,0xA6,0x00,0xA2,0xFF, + 0x38,0x01,0xB9,0x00,0x18,0xFF,0xC1,0x01,0x66,0x01,0x6A,0xFF, + 0x96,0x01,0x8A,0xFE,0x8C,0xFF,0x69,0x00,0x28,0xFF,0x84,0x00, + 0x7C,0x00,0xDA,0xFF,0xE7,0xFF,0x34,0x00,0x3D,0xFF,0xFB,0xFF, + 0xB4,0xFF,0xD7,0xFF,0x8B,0x00,0x8E,0xFE,0x9F,0x00,0x8F,0xFF, + 0x5C,0x00,0x78,0xFF,0x00,0x00,0x46,0x01,0x1B,0xFF,0xE4,0xFF, + 0x6B,0x00,0x60,0x00,0xAB,0xFE,0x0E,0x01,0xD2,0xFF,0x20,0xFF, + 0x7E,0x00,0x45,0x00,0x51,0xFF,0x38,0x01,0x14,0x00,0x2B,0xFE, + 0xCC,0x01,0xA7,0xFF,0xE5,0xFF,0xC7,0x01,0x80,0xFD,0x40,0x01, + 0x1B,0x00,0xFE,0xFD,0x2D,0x02,0x76,0xFE,0xCA,0x00,0xB8,0x00, + 0xA5,0xFF,0x15,0x00,0x07,0x01,0x4F,0x00,0x9A,0xFE,0x3F,0x01, + 0x3D,0xFF,0xBC,0x00,0x27,0x00,0x32,0xFE,0x25,0x01,0xB6,0xFF, + 0x0D,0xFF,0xC1,0x00,0xB5,0xFF,0x98,0xFF,0x51,0x01,0x12,0xFF, + 0x7F,0xFE,0x3D,0x02,0x13,0xFF,0x8F,0xFF,0xE0,0x01,0xE3,0xFD, + 0x32,0x00,0x50,0x02,0xE2,0xFD,0x47,0x00,0xCB,0x00,0xF6,0xFD, + 0xC4,0x00,0xF5,0xFE,0xCE,0x00,0xFC,0xFF,0x8A,0xFF,0xC0,0x01, + 0xED,0xFF,0xFD,0xFD,0x9B,0x03,0xFE,0xFE,0x38,0xFD,0x6B,0x04, + 0x66,0xFC,0x27,0x01,0xC0,0x00,0x48,0x00,0xF6,0xFD,0xD9,0x01, + 0xFE,0xFE,0xAD,0xFD,0x83,0x04,0x49,0xFB,0xD1,0x02,0xA6,0xFE, + 0xFE,0xFF,0x84,0x02,0xD0,0xFC,0x33,0x02,0x88,0x02,0x0A,0xFA, + 0xA8,0x02,0x42,0x04,0x1F,0xFA,0x8F,0x02,0x7C,0xFF,0x90,0x00, + 0x5D,0xFF,0xA5,0x00,0x7D,0x00,0x2A,0x00,0x69,0xFE,0x15,0xFD, + 0xAE,0x02,0x6A,0xFD,0x47,0x02,0x95,0xFE,0x1F,0x00,0xC4,0x03, + 0xA2,0xFE,0x0F,0x00,0x03,0x03,0xE8,0xFC,0xAA,0x01,0x7B,0x01, + 0xBE,0xFC,0xE4,0x02,0x7B,0xFB,0x1C,0x00,0x1F,0x02,0x8F,0xFB, + 0x68,0x00,0xCC,0x06,0x6F,0xFB,0xCB,0x02,0x56,0x04,0x68,0xF8, + 0x3C,0x07,0x89,0xFE,0xFF,0xFA,0x5E,0x03,0xB9,0xFF,0xFC,0xFC, + 0x8E,0x01,0x4A,0x01,0x56,0xFA,0xE3,0x04,0xD1,0xFB,0xC7,0xFD, + 0x93,0x03,0x6B,0xFC,0x07,0x00,0xD9,0x01,0x30,0xFF,0x62,0x00, + 0x1E,0x05,0x05,0xFC,0xE5,0x00,0x00,0x04,0x39,0xFD,0xFA,0xFE, + 0x79,0x03,0x4F,0xFC,0x4C,0x01,0x72,0x01,0x4A,0xFC,0xD7,0x04, + 0x71,0xFF,0xBD,0xFD,0xA3,0x03,0x97,0x00,0x12,0xFE,0x0F,0x02, + 0xF6,0xFE,0xE6,0xFF,0x39,0x01,0xCA,0xFD,0x59,0x00,0xC5,0xFF, + 0x7E,0xFC,0xAC,0x01,0x2C,0x00,0x92,0xFD,0xDC,0x00,0x2A,0x00, + 0x1E,0x00,0x1E,0x00,0xC1,0xFF,0x0A,0x00,0xB0,0x01,0x1F,0xFE, + 0x21,0x01,0x55,0x01,0x55,0xFD,0x64,0x02,0xA2,0xFD,0xC8,0xFF, + 0x1B,0x02,0x15,0xFD,0xFF,0x00,0xA2,0x01,0xF6,0xFD,0xD4,0xFF, + 0xE4,0x02,0x29,0xFE,0xD1,0x00,0x89,0x01,0x22,0xFE,0x0F,0x03, + 0x3C,0x00,0x62,0xFD,0x87,0x02,0xAE,0x00,0x88,0xFC,0xC6,0x02, + 0x15,0xFE,0xEF,0xFD,0x09,0x02,0x9A,0xFE,0x5C,0xFF,0xF3,0x00, + 0x72,0x00,0xDA,0xFE,0x7C,0x03,0xAA,0xFC,0x65,0x00,0x60,0x03, + 0xDA,0xFC,0x3D,0x00,0x51,0x01,0x29,0xFF,0x60,0xFF,0xD1,0xFF, + 0x53,0xFE,0x5C,0x02,0xB5,0xFE,0x68,0xFF,0x3F,0x02,0x5E,0x00, + 0xB6,0xFE,0x9D,0xFF,0xE5,0x01,0x54,0x00,0x6C,0xFF,0xDE,0xFF, + 0x5C,0x01,0xDE,0xFD,0xF0,0xFF,0x7F,0x00,0xF3,0xFD,0x09,0x02, + 0x1E,0xFF,0x08,0xFF,0xDA,0x03,0x09,0xFE,0x5B,0xFF,0x76,0x02, + 0x57,0xFE,0x71,0xFF,0x56,0x01,0xBB,0xFE,0x53,0xFE,0x3A,0x01, + 0xCA,0xFF,0x88,0xFF,0x46,0x00,0x63,0x01,0x88,0x00,0xC0,0xFF, + 0x5B,0x00,0x0A,0x00,0xC8,0xFE,0xAD,0xFF,0xB7,0xFF,0x1D,0xFE, + 0x3E,0x01,0x04,0xFF,0x5E,0xFF,0xAB,0x01,0x4E,0x00,0x2F,0x01, + 0xD8,0x00,0xBC,0xFF,0x45,0x00,0xF0,0x00,0xF2,0xFE,0x78,0xFF, + 0x82,0x00,0x42,0xFE,0xD9,0xFF,0xC9,0x00,0x6D,0xFE,0xC1,0x00, + 0xB4,0x00,0xC9,0xFE,0x05,0x01,0x78,0x00,0x7A,0xFF,0x1A,0x01, + 0x22,0x00,0xF1,0xFE,0xA1,0x00,0x6D,0xFF,0x96,0xFF,0xE1,0x00, + 0xD9,0xFE,0x0D,0x01,0x4E,0x00,0x59,0xFE,0x08,0x02,0x3B,0x01, + 0x5A,0xFE,0xC4,0x00,0x2F,0x01,0xE9,0xFE,0x03,0x00,0x55,0xFE, + 0xBA,0xFE,0xAD,0x01,0x58,0xFF,0x16,0xFF,0x53,0x00,0xCB,0xFF, + 0x5E,0x01,0x93,0x00,0xC4,0xFE,0x48,0x01,0x90,0x01,0xDD,0xFE, + 0xE7,0xFF,0x31,0x00,0x6D,0xFF,0xB4,0x00,0x2E,0xFE,0x9C,0xFF, + 0xB5,0x02,0x0D,0xFF,0x8D,0xFD,0x09,0x01,0x11,0x01,0xAC,0xFE, + 0x04,0xFF,0xB5,0xFF,0xC1,0x01,0x4D,0x00,0x43,0xFF,0x70,0x00, + 0x80,0x00,0x96,0xFF,0x41,0xFF,0x70,0x00,0x6C,0xFF,0x46,0x00, + 0xE4,0x00,0xCC,0xFF,0xB9,0x00,0x8A,0x01,0xEE,0x00,0x06,0x00, + 0x6D,0xFF,0x4F,0x00,0x1D,0x01,0xBF,0xFF,0x3F,0xFF,0x25,0x00, + 0x49,0xFF,0xBA,0xFE,0x76,0xFE,0x2C,0xFF,0x0E,0x00,0x13,0x00, + 0xD0,0xFF,0x85,0xFF,0x32,0x00,0xE7,0xFF,0xF5,0xFE,0x8B,0xFE, + 0x85,0xFE,0x9A,0xFF,0xE8,0xFF,0x61,0xFD,0xE7,0xFE,0xE2,0x01, + 0x65,0x01,0x6E,0x00,0x7F,0x02,0xF3,0x04,0xBE,0x01,0xD2,0xFF, + 0x00,0x04,0x41,0x04,0x30,0x00,0xF6,0x01,0x35,0x01,0xF0,0xFE, + 0xD6,0xFF,0x9B,0xFE,0x71,0x00,0x93,0x00,0x4F,0xFD,0x6F,0xFE, + 0x75,0xFF,0x0F,0xFC,0x0A,0xFB,0xBE,0xFC,0xC3,0xFB,0x1F,0xFB, + 0x54,0xFD,0xA3,0xFE,0x8F,0xFF,0xBF,0xFF,0xFB,0xFE,0x2F,0x02, + 0x83,0x02,0x39,0x00,0x8E,0x02,0x42,0x02,0xC8,0xFF,0x50,0x01, + 0xE6,0x00,0x6D,0x00,0x81,0x02,0x2A,0x02,0x6E,0x01,0xD2,0x03, + 0x55,0x07,0x54,0x03,0x58,0x07,0x74,0x0C,0x6D,0x02,0x5E,0x01, + 0x04,0x02,0x6D,0xFB,0x14,0xFA,0x72,0xF9,0x9E,0xF5,0x27,0xF8, + 0x08,0xFC,0x91,0xFB,0xCC,0xFE,0xAD,0xFE,0xBF,0xFA,0xAC,0xFD, + 0x12,0x00,0x2B,0xFC,0x42,0xFD,0x55,0xFF,0x67,0xFE,0x0C,0xFF, + 0xCC,0xFE,0x23,0xFF,0xFE,0x00,0x33,0x02,0xE5,0x01,0x63,0x03, + 0x2B,0x04,0x93,0x03,0x0B,0x03,0xF0,0x00,0xBC,0x00,0x32,0x04, + 0x3A,0x03,0xA1,0x02,0x16,0x07,0x7D,0x06,0x99,0x0C,0x17,0x10, + 0x6B,0x03,0x6B,0xFF,0xB0,0x02,0xBC,0xF9,0x55,0xF4,0xFC,0xF6, + 0x2D,0xF3,0x23,0xF5,0xB5,0xFA,0xD2,0xF8,0xE7,0xFA,0xF8,0x00, + 0x5F,0x02,0x85,0x01,0xD7,0x00,0x38,0xFF,0x51,0xFF,0xE9,0xFE, + 0x5F,0xF9,0x77,0xF7,0x7C,0xFC,0xB6,0xFE,0x1D,0xFE,0x9B,0x00, + 0x6F,0x03,0x0F,0x06,0x7B,0x07,0x11,0x04,0xF1,0x02,0x84,0x05, + 0x55,0x02,0x48,0xFE,0xAB,0xFF,0xA7,0xFF,0xDC,0x00,0xA2,0x0C, + 0x0D,0x15,0xEE,0x09,0xAC,0x07,0x6B,0x0D,0xB7,0x01,0x99,0xF9, + 0x4F,0xF7,0x19,0xF2,0x3C,0xF2,0x20,0xF5,0xFE,0xF4,0x3F,0xF7, + 0x0A,0xFC,0x62,0xFC,0x4D,0xFE,0xD2,0x00,0xF7,0x02,0x46,0x05, + 0x46,0x05,0x28,0x02,0x50,0xFC,0xDB,0xFA,0x36,0xFA,0xF7,0xF8, + 0xAA,0xFA,0x44,0xFE,0x8C,0x02,0x0E,0x04,0x36,0x05,0x20,0x05, + 0xA3,0x01,0x88,0x00,0x39,0x02,0x16,0x01,0x4B,0x01,0x8C,0x02, + 0x26,0x01,0x73,0x00,0xAE,0xF9,0xE6,0x08,0x9B,0x1E,0x9A,0x0D, + 0x3F,0x04,0x6D,0x0E,0x78,0x07,0xF8,0xF8,0x45,0xF3,0x88,0xEE, + 0xB5,0xF0,0x94,0xF6,0x93,0xF2,0x79,0xF5,0x5A,0x00,0x3E,0x01, + 0xFB,0xFC,0x89,0xFF,0x60,0x02,0x19,0x06,0x5E,0x06,0x93,0xFE, + 0xC3,0xFD,0x0D,0x00,0x14,0xFC,0xE7,0xF8,0xB8,0xFB,0x7B,0xFF, + 0xBC,0x01,0x2B,0x02,0x31,0x03,0x94,0x05,0xE8,0x03,0x8D,0xFF, + 0xE3,0xFE,0x1E,0xFD,0x2B,0xFD,0x81,0x02,0xAD,0x04,0xA2,0x00, + 0xA9,0xFE,0x3F,0x00,0xA0,0xF4,0xF9,0x0B,0xB8,0x28,0xC9,0x0A, + 0x3A,0x01,0x3C,0x10,0xEF,0x06,0x4F,0xF7,0x20,0xEF,0x92,0xEC, + 0xE1,0xF3,0xBB,0xF7,0xB9,0xF0,0x8E,0xF8,0x92,0x04,0xF3,0xFF, + 0x17,0xFC,0x5B,0x00,0x6E,0x04,0x92,0x07,0xEE,0x04,0xF0,0xFC, + 0x98,0xFC,0x43,0x00,0x68,0xFB,0x20,0xF9,0xC5,0xFC,0xAC,0xFF, + 0xB2,0x04,0xF9,0x03,0xCE,0x01,0xA0,0x05,0x50,0x04,0xA5,0xFD, + 0x22,0xFD,0xCB,0xFE,0x4A,0xFD,0xBF,0xFD,0x5E,0x00,0x40,0x03, + 0xAE,0xFF,0x99,0xFC,0x72,0xF5,0xE3,0x04,0x92,0x2B,0x7D,0x19, + 0xCC,0xFA,0x80,0x09,0x8A,0x0F,0xC4,0xFA,0x17,0xEA,0x17,0xEB, + 0x33,0xF4,0xA9,0xF9,0xF9,0xF3,0xCC,0xF4,0x54,0x02,0x86,0x05, + 0x7D,0xFE,0x52,0xFD,0x1D,0x03,0x9D,0x09,0xA5,0x07,0xB2,0xFC, + 0xEF,0xF8,0xFC,0xFD,0x26,0xFE,0xB4,0xF8,0xAE,0xF8,0x47,0x00, + 0xC0,0x05,0x66,0x04,0x8C,0x03,0x41,0x06,0xC9,0x05,0xE5,0x00, + 0xFE,0xFB,0x72,0xFC,0x64,0x00,0x3D,0x00,0x28,0xFB,0x57,0xFB, + 0x62,0x01,0x9B,0xFF,0x00,0xFE,0x67,0xF5,0x53,0xFF,0x21,0x2A, + 0x60,0x21,0x35,0xFC,0x97,0x04,0x5A,0x10,0xF9,0xFD,0xF6,0xEA, + 0xD9,0xE8,0xDC,0xF0,0x8A,0xFB,0x95,0xF8,0x59,0xF2,0x41,0xFD, + 0xD5,0x07,0x0A,0x04,0x06,0xFE,0x2E,0x01,0xCF,0x09,0x4B,0x0A, + 0xC1,0xFE,0x17,0xF7,0xA9,0xFB,0x3B,0xFF,0x09,0xF9,0x9D,0xF5, + 0xE0,0xFE,0x94,0x07,0x80,0x05,0x75,0x01,0x41,0x03,0x0A,0x07, + 0xBC,0x05,0xFF,0xFE,0xBE,0xFB,0x38,0xFF,0xE1,0x02,0x59,0x00, + 0x65,0xFC,0xF0,0xFA,0x00,0xFB,0x15,0xFE,0xB9,0xFD,0xE5,0xF4, + 0xFD,0xFA,0x41,0x21,0x99,0x28,0xA6,0x03,0x8A,0xFE,0x3D,0x0F, + 0xB4,0x04,0x5E,0xED,0x33,0xE7,0xB5,0xF0,0x0D,0xFC,0xD6,0xFA, + 0xC4,0xF0,0xD6,0xF7,0x6C,0x07,0xE8,0x06,0x22,0xFE,0xEB,0xFF, + 0xD7,0x09,0xF3,0x0C,0x9C,0x03,0x95,0xF7,0x44,0xFA,0x07,0x00, + 0x0B,0xF8,0x37,0xF4,0x74,0xFD,0xC4,0x05,0xD1,0x05,0xB8,0x02, + 0x7A,0x02,0xDE,0x05,0x12,0x06,0x88,0xFF,0x94,0xFB,0xE5,0xFF, + 0x72,0x04,0xF7,0x00,0x86,0xFC,0xFD,0xFD,0xA5,0x00,0xBC,0xFE, + 0x27,0xFA,0xFD,0xF7,0x18,0xF7,0xC7,0xFF,0x0E,0x18,0x87,0x1D, + 0x76,0x07,0xA4,0x03,0xC1,0x0C,0x52,0x01,0x06,0xF0,0xDC,0xED, + 0x4C,0xF5,0x2B,0xFA,0x47,0xF7,0x47,0xF4,0x74,0xFB,0xC5,0x03, + 0xFE,0x01,0x35,0xFE,0x4F,0x02,0xF8,0x09,0xF1,0x0A,0xAC,0x02, + 0x1C,0xFE,0x29,0xFE,0xD8,0xF9,0x0B,0xF7,0x10,0xF9,0x3D,0xFE, + 0xB3,0x01,0xB3,0x02,0xB1,0x03,0x41,0x04,0x43,0x05,0x2E,0x03, + 0x5F,0xFF,0x04,0xFF,0x89,0x00,0x1C,0x01,0xA7,0x00,0x4F,0x00, + 0xBB,0xFF,0x60,0xFE,0x68,0xFD,0x62,0xFA,0x8B,0xF9,0x63,0xFA, + 0xF8,0x01,0xD9,0x1C,0x82,0x1D,0x99,0xFB,0x44,0xFA,0xC9,0x0C, + 0x6C,0x00,0x44,0xEB,0x19,0xEE,0xA9,0xFB,0x06,0x01,0xBF,0xFA, + 0xF6,0xF3,0xE4,0xFC,0x2C,0x08,0xE2,0x02,0xB2,0xFA,0xC0,0x00, + 0x2E,0x0B,0x19,0x0A,0xDF,0xFF,0x42,0xF8,0xC7,0xFA,0xFD,0xFE, + 0xBF,0xFA,0x61,0xF8,0x1C,0xFF,0x23,0x06,0x7D,0x04,0xFE,0xFF, + 0x0F,0x02,0x4C,0x05,0x07,0x03,0x5B,0xFD,0x05,0xFD,0xB5,0x02, + 0xB7,0x03,0xE3,0xFE,0x0D,0xFD,0x09,0xFF,0x0B,0xFD,0xBD,0xF9, + 0xB7,0xF8,0x1D,0xFA,0xEA,0x10,0x56,0x22,0x63,0x08,0xD2,0xF9, + 0xCE,0x0B,0xC9,0x09,0xBA,0xF2,0x54,0xEB,0x6F,0xF5,0x3E,0xFD, + 0x93,0xFB,0x19,0xF5,0x8E,0xF8,0xAC,0x04,0xD1,0x05,0x3B,0xFD, + 0x07,0xFE,0xD1,0x08,0xA6,0x0B,0x4C,0x02,0xE9,0xF9,0xF8,0xFC, + 0x97,0x00,0x7A,0xF9,0x73,0xF4,0x1C,0xFC,0x97,0x05,0x72,0x04, + 0x93,0x00,0x3B,0x03,0x70,0x06,0x78,0x04,0x3E,0xFF,0xE1,0xFC, + 0x35,0x00,0xA1,0x02,0x2B,0xFF,0xC4,0xFC,0x3F,0x00,0x68,0x02, + 0xCB,0xFF,0x98,0xFD,0x9E,0xFB,0x7A,0xFB,0xEF,0xF9,0x59,0xFF, + 0xAF,0x18,0x09,0x1B,0x65,0x01,0xEA,0xFF,0xA7,0x08,0x22,0xFD, + 0x4D,0xEE,0x96,0xEF,0xC8,0xF8,0xA9,0xFC,0x97,0xF9,0x3A,0xF7, + 0x02,0xFF,0x4C,0x06,0x52,0x02,0x56,0xFD,0xA5,0x02,0xF4,0x0A, + 0xC4,0x08,0xA2,0x00,0x62,0xFC,0x57,0xFB,0x6B,0xFA,0xD7,0xF8, + 0x0C,0xFA,0xB4,0xFE,0xD1,0x02,0xD3,0x04,0xA6,0x03,0x1D,0x03, + 0xC4,0x04,0x34,0x03,0x7E,0xFF,0x40,0xFE,0x5D,0xFF,0x9F,0x00, + 0x2C,0x01,0x2B,0xFF,0x5A,0xFD,0x73,0xFE,0xC6,0xFC,0xE3,0xF8, + 0xB2,0xF7,0x31,0xFE,0x36,0x13,0x17,0x1F,0xAE,0x0B,0x65,0xFE, + 0x50,0x06,0xB7,0x02,0x50,0xF2,0xA2,0xEB,0x90,0xF3,0x4A,0xFD, + 0x15,0xFD,0x9F,0xF6,0x19,0xFA,0xBA,0x05,0x2E,0x06,0xE0,0xFD, + 0x4B,0xFF,0x20,0x09,0x60,0x0B,0x59,0x02,0xD8,0xFA,0x63,0xFC, + 0x8C,0xFC,0x76,0xF7,0xBF,0xF6,0x3A,0xFE,0xE1,0x04,0x47,0x03, + 0xEB,0x01,0xB2,0x05,0x32,0x06,0x70,0x02,0x73,0xFF,0x8E,0xFE, + 0x67,0x00,0x1F,0x01,0x4F,0xFE,0x7E,0xFD,0x9F,0x00,0xB8,0x00, + 0x21,0xFD,0xE6,0xFE,0x9C,0x00,0x50,0xFC,0x98,0xF7,0x8E,0xFC, + 0xB1,0x14,0x7D,0x1C,0xA4,0x04,0x32,0xFE,0xA7,0x09,0x26,0x01, + 0x8C,0xEE,0xD4,0xED,0xCE,0xF8,0x0D,0xFD,0x9A,0xF8,0x92,0xF5, + 0xCA,0xFD,0x59,0x07,0x88,0x03,0x17,0xFC,0x74,0x01,0xF3,0x0B, + 0x30,0x0A,0xD5,0x00,0x3C,0xFD,0x17,0xFD,0xE7,0xFA,0x69,0xF7, + 0x14,0xF8,0xBC,0xFE,0xC0,0x02,0xA1,0x02,0x7D,0x03,0xAE,0x04, + 0x4E,0x05,0x88,0x03,0x09,0x00,0x8F,0xFF,0x69,0x00,0x33,0xFF, + 0x36,0xFF,0x31,0x00,0xDA,0xFE,0x8B,0xFD,0xA7,0xFD,0xB0,0xFC, + 0x44,0xF9,0x22,0xF8,0x46,0x02,0x06,0x18,0xE4,0x19,0x8C,0x04, + 0xAB,0x03,0x5B,0x0B,0xBD,0xFC,0xE3,0xEB,0x5D,0xEE,0xDB,0xF8, + 0x3F,0xFB,0x71,0xF6,0x0E,0xF6,0xC7,0x00,0x95,0x07,0x06,0x01, + 0x51,0xFD,0xE5,0x05,0xAF,0x0C,0xE2,0x07,0x75,0xFE,0xA6,0xFC, + 0x24,0x00,0x03,0xFA,0xAB,0xF2,0x36,0xF8,0xEF,0x01,0xC5,0x03, + 0xFF,0xFF,0x5D,0x02,0x9A,0x09,0xE5,0x07,0xE1,0xFF,0x81,0xFE, + 0xCE,0x01,0x49,0x01,0xAB,0xFD,0x63,0xFC,0x82,0xFF,0xFA,0x00, + 0x56,0xFE,0x92,0xFD,0x74,0x00,0x00,0x02,0xC0,0xFD,0x19,0xF9, + 0x5A,0xF7,0x88,0x0B,0x5F,0x20,0x45,0x09,0x0C,0xFA,0xDE,0x0A, + 0x78,0x07,0x34,0xF0,0x3B,0xEC,0xE9,0xF8,0x1D,0xFD,0xFD,0xF7, + 0x23,0xF4,0x30,0xFC,0x9B,0x07,0xD9,0x03,0xA8,0xFA,0x86,0x00, + 0x2C,0x0C,0x21,0x0B,0xF4,0x00,0x64,0xFD,0x22,0x01,0x6D,0xFD, + 0x4C,0xF5,0xB8,0xF5,0xD1,0xFE,0x88,0x03,0x73,0xFF,0x51,0x00, + 0x02,0x08,0x2D,0x08,0x6C,0x01,0xA2,0xFF,0xA2,0x01,0x99,0x01, + 0x18,0xFF,0x65,0xFD,0x6E,0xFF,0xE9,0x00,0xF1,0xFD,0x57,0xFC, + 0xF4,0xFE,0x89,0x00,0x2D,0xFE,0xC8,0xF8,0x78,0xF6,0x38,0x0B, + 0xFD,0x20,0x54,0x0C,0x83,0xFB,0x99,0x0B,0xC1,0x08,0x13,0xF0, + 0xCA,0xEA,0x4E,0xF8,0xF6,0xFC,0x32,0xF6,0xEA,0xF2,0x91,0xFC, + 0x0E,0x08,0xB3,0x03,0xF7,0xFA,0x50,0x01,0x1C,0x0D,0xD8,0x0A, + 0xA1,0x00,0x73,0xFD,0xE4,0x00,0xBE,0xFE,0xB5,0xF4,0x11,0xF4, + 0xAD,0xFE,0x90,0x03,0xE9,0xFF,0xD6,0xFF,0x6B,0x06,0x2C,0x0A, + 0x6E,0x04,0x13,0xFE,0xEF,0x00,0xFB,0x02,0x03,0xFE,0x0C,0xFC, + 0xAF,0xFF,0xE4,0x00,0x3B,0xFE,0x61,0xFC,0x07,0xFF,0x35,0x02, + 0x06,0x02,0x88,0xFD,0x51,0xFD,0x83,0xFB,0xBF,0xFB,0x35,0x17, + 0xF5,0x17,0x7D,0xFA,0xEF,0x01,0xDA,0x0C,0xA5,0xF9,0x82,0xED, + 0xF7,0xF5,0xAD,0xFA,0xFF,0xF8,0x92,0xF6,0xD5,0xF7,0xF1,0x02, + 0xE9,0x06,0xA0,0xFD,0x51,0xFD,0x0F,0x09,0x3C,0x0B,0x48,0x04, + 0x36,0xFF,0x21,0xFF,0xCE,0x00,0x15,0xFA,0x22,0xF4,0x49,0xFA, + 0x2B,0x02,0xAF,0x01,0xC4,0xFE,0xE8,0x02,0x51,0x09,0xC8,0x06, + 0xAB,0xFF,0x8B,0xFF,0x79,0x02,0xD0,0x00,0x96,0xFD,0xC8,0xFD, + 0xDD,0xFF,0x73,0xFF,0xC9,0xFC,0x74,0xFD,0xF4,0x00,0x63,0x02, + 0xDE,0xFE,0x37,0xFC,0x5D,0xF8,0x94,0xFB,0xB7,0x17,0xF4,0x18, + 0xFF,0xFB,0x0D,0x03,0xFB,0x0F,0x17,0xFB,0x8E,0xEB,0x13,0xF5, + 0x9D,0xFB,0xD1,0xF7,0x21,0xF4,0x6C,0xF7,0xAC,0x03,0x16,0x07, + 0xD3,0xFC,0xC2,0xFC,0xC5,0x09,0xF2,0x0B,0x7C,0x03,0x0E,0x00, + 0x0D,0x01,0x11,0xFF,0x6D,0xFA,0x8D,0xF6,0xD0,0xF8,0x81,0x00, + 0x80,0x00,0x99,0xFE,0x98,0x04,0x51,0x08,0xF1,0x04,0x02,0x03, + 0x8F,0x03,0x67,0x00,0xAC,0xFE,0xB3,0xFE,0x8D,0xFD,0x2F,0xFE, + 0xBE,0xFE,0xE4,0xFD,0x26,0xFF,0x0C,0x01,0x0D,0x00,0xCE,0x00, + 0x00,0x02,0xA1,0x01,0xC3,0xFD,0xB8,0xFE,0x22,0xFB,0x8C,0xFC, + 0xD4,0x16,0x08,0x0E,0x65,0xF6,0x2A,0x07,0x58,0x0D,0xCB,0xF5, + 0x1F,0xEE,0xD1,0xFA,0x99,0xFC,0x0B,0xF7,0x80,0xF5,0x35,0xFB, + 0x42,0x05,0xFD,0x03,0x64,0xFB,0x15,0x01,0x2C,0x0C,0xC3,0x07, + 0xE2,0xFF,0xC9,0x01,0x62,0x02,0xCE,0xFD,0x00,0xF9,0xAF,0xF6, + 0xAE,0xFD,0xC7,0x01,0xC3,0xFD,0xAC,0x00,0x15,0x07,0xA6,0x05, + 0x87,0x02,0x2E,0x03,0x21,0x02,0xD8,0x00,0x5E,0xFF,0x12,0xFD, + 0x70,0xFE,0x55,0xFF,0xD3,0xFC,0xF7,0xFC,0x76,0x00,0x4D,0x01, + 0xBE,0xFF,0xC0,0x01,0x10,0x02,0x74,0xFE,0xBA,0xF8,0xF4,0xF5, + 0x48,0x0B,0x33,0x1A,0xEA,0x02,0xCD,0xFC,0x3A,0x10,0x00,0x07, + 0x60,0xEF,0x20,0xF1,0x29,0xFD,0xB2,0xFA,0x7D,0xF2,0x90,0xF4, + 0x2E,0x01,0x40,0x06,0xC5,0xFC,0x22,0xFB,0x84,0x07,0x69,0x0C, + 0xC9,0x04,0x54,0x00,0x03,0x04,0x56,0x03,0x49,0xFA,0x9D,0xF5, + 0x60,0xFA,0x8B,0x00,0x73,0xFF,0x18,0xFB,0x45,0x01,0x4B,0x09, + 0x8A,0x04,0xF8,0x00,0x55,0x05,0x87,0x05,0xA3,0x00,0x44,0xFE, + 0x2F,0xFF,0x77,0xFE,0xFB,0xFB,0x58,0xFB,0x80,0xFD,0x9A,0x00, + 0x46,0x01,0xDF,0x00,0xF6,0x01,0xBB,0x02,0xB8,0x01,0x0C,0x00, + 0x38,0x00,0xD7,0xFF,0x6A,0xFE,0xF0,0xFA,0xC3,0xFB,0xA5,0xFC, + 0x39,0x00,0x43,0x0F,0x96,0x09,0x01,0xFF,0x86,0x08,0xC6,0x06, + 0xBC,0xF7,0x4B,0xF5,0x4A,0xFB,0x46,0xF9,0x21,0xF6,0x33,0xF8, + 0x1F,0xFE,0x1F,0x02,0xE7,0xFF,0xBD,0xFF,0x19,0x06,0xD4,0x08, + 0x77,0x04,0xC8,0x02,0x1C,0x04,0x53,0x00,0xCF,0xFB,0x00,0xFB, + 0xC2,0xFA,0x5B,0xFD,0x98,0xFE,0x95,0xFD,0xF7,0x01,0x6F,0x05, + 0x81,0x03,0x09,0x03,0x9F,0x03,0x5E,0x02,0xDF,0x00,0xD7,0xFE, + 0xFE,0xFD,0x8B,0xFE,0x01,0xFE,0xA8,0xFC,0xD0,0xFE,0x79,0x01, + 0x86,0xFF,0xA1,0xFF,0x5A,0xFF,0xA2,0xFF,0xF7,0xF8,0x0E,0xFA, + 0x03,0x02,0x2B,0x0A,0x93,0x16,0x85,0x05,0x45,0x02,0x4F,0x10, + 0x5E,0xFF,0x72,0xEE,0x57,0xF7,0xFE,0xFC,0xE5,0xF1,0x5F,0xF1, + 0x0B,0xFB,0xC1,0x00,0x11,0xFF,0xA7,0xFC,0xEF,0x02,0xCE,0x0A, + 0xE7,0x07,0x94,0x03,0x81,0x06,0x3A,0x05,0xBF,0xFC,0x95,0xF8, + 0x0C,0xFB,0x95,0xFB,0x72,0xFA,0xA7,0xFB,0xDF,0xFF,0x76,0x03, + 0x81,0x03,0xC3,0x03,0x30,0x06,0x53,0x07,0x7A,0x03,0xD8,0xFF, + 0xA8,0x01,0x84,0x00,0x35,0xFB,0x16,0xFC,0xD7,0xFF,0xDB,0xFE, + 0xB4,0xFD,0xFA,0xFF,0x3E,0x02,0x00,0x01,0x25,0xFF,0x0E,0x01, + 0x7F,0x02,0x8D,0xFF,0x34,0xFE,0x76,0xFF,0x96,0xFE,0x34,0xFD, + 0x6F,0xFE,0xBC,0x00,0x68,0x00,0x64,0xFF,0xA4,0x00,0xD1,0x00, + 0x90,0xFF,0x47,0xFF,0x68,0x00,0x80,0xFF,0x77,0xFF,0x27,0x02, + 0xB9,0xFE,0xF4,0x03,0x70,0x06,0xFB,0xFC,0x29,0x02,0x1E,0x05, + 0x5E,0xFC,0x76,0xFB,0x1D,0x00,0x1B,0xFD,0x26,0xFA,0x1A,0xFD, + 0xCE,0xFE,0x3A,0xFF,0x2F,0xFF,0xF1,0xFF,0x35,0x02,0xFC,0x02, + 0x71,0x01,0x68,0x02,0x06,0x03,0x61,0x00,0x7C,0xFE,0xAB,0xFE, + 0x1B,0xFE,0x92,0xFD,0x5C,0xFE,0x04,0xFF,0x6D,0x00,0x29,0x01, + 0xDD,0x00,0xF4,0x01,0x77,0x02,0x59,0x00,0x77,0x01,0x72,0xFF, + 0xEE,0x00,0x4D,0xFD,0xA1,0xFE,0x45,0xF9,0x6A,0xF6,0x54,0x1E, + 0x23,0x0A,0x11,0xF1,0x0B,0x1A,0xAE,0x11,0x61,0xE9,0x78,0xF5, + 0x56,0x09,0xF1,0xF1,0xA3,0xE9,0xB3,0xFA,0x89,0x00,0x46,0xFB, + 0x36,0xF9,0x86,0x01,0xFF,0x09,0xED,0x03,0xF8,0xFF,0x5F,0x0B, + 0x1C,0x0A,0x65,0xFB,0x0E,0xFB,0xB9,0x01,0x50,0xFB,0x64,0xF6, + 0xBB,0xFC,0xB4,0x00,0x7E,0xFE,0xAF,0xFE,0xC5,0x03,0xC8,0x06, + 0xD4,0x04,0x52,0x03,0x98,0x05,0xBB,0x05,0xD8,0xFF,0x62,0xFD, + 0xBC,0xFF,0xA6,0xFD,0xEE,0xFA,0xCA,0xFE,0xB5,0x00,0x2F,0xFF, + 0xA8,0x00,0x16,0x01,0xF4,0x00,0x61,0x01,0xCC,0xFF,0x92,0xFF, + 0x7C,0x00,0x17,0xFF,0x8D,0xFD,0xAF,0xFD,0x07,0xFF,0xCE,0xFE, + 0xF9,0xFC,0xD9,0xFE,0x5C,0x01,0xE2,0xFE,0x71,0xFE,0xD0,0x00, + 0xBD,0x00,0x43,0xFC,0x39,0xFD,0x55,0xFD,0x80,0x00,0x47,0xFE, + 0xA1,0xFE,0xA6,0x19,0xC2,0x0A,0x67,0xFA,0x59,0x14,0x90,0x0A, + 0x42,0xEE,0xDA,0xF8,0xAD,0x03,0x70,0xEF,0x2B,0xEE,0x3C,0xFE, + 0xEC,0xFD,0xB3,0xF8,0x8F,0xFE,0x5D,0x06,0xC1,0x06,0xE5,0x03, + 0xF3,0x05,0x0E,0x0A,0xB5,0x03,0x88,0xFB,0x56,0xFE,0xC0,0xFF, + 0x66,0xF8,0x64,0xF8,0x54,0xFF,0x79,0xFE,0xAB,0xFB,0xC8,0x00, + 0xFA,0x04,0x3C,0x03,0x93,0x03,0x6F,0x06,0x8E,0x05,0x28,0x02, + 0xAA,0x00,0xED,0xFF,0x23,0xFE,0x49,0xFC,0xD4,0xFC,0x4D,0xFF, + 0x36,0xFF,0x92,0xFE,0xE5,0x00,0xD8,0x01,0x72,0x00,0x27,0x01, + 0x28,0x02,0x01,0x01,0xE0,0xFF,0x30,0xFF,0xD0,0xFE,0x7E,0xFE, + 0x10,0xFE,0xCE,0xFD,0x32,0xFE,0xB7,0xFE,0x81,0xFE,0x93,0xFE, + 0x6F,0xFF,0xE0,0xFF,0xAE,0xFF,0x78,0x00,0x78,0x01,0x5B,0x00, + 0x82,0x00,0x99,0x01,0xE3,0xFF,0x12,0x00,0x7F,0x01,0x8E,0x00, + 0x3F,0x00,0x40,0x01,0xBB,0x00,0x6B,0x00,0x45,0x00,0x8D,0xFF, + 0x79,0x00,0xD6,0xFF,0x24,0xFF,0x85,0xFF,0x2B,0x00,0x16,0x01, + 0x01,0x00,0xF6,0xFF,0x73,0x02,0xBB,0x02,0xA9,0xFF,0x6A,0x00, + 0x4E,0x02,0x79,0xFE,0x60,0xFC,0xF1,0xFE,0x18,0xFE,0x6E,0xFB, + 0xA1,0xFC,0x07,0xFF,0xC9,0xFE,0xF1,0xFD,0x80,0x00,0xFD,0x01, + 0xED,0xFF,0x5A,0x00,0xBE,0x02,0xAA,0x01,0x1F,0x00,0x71,0x01, + 0xA3,0x01,0x19,0x00,0x6C,0x00,0xF6,0x00,0xB8,0x00,0xF6,0xFF, + 0xC4,0x00,0xB2,0x00,0x23,0x00,0xCE,0x00,0x85,0xFF,0x58,0x00, + 0xD0,0x00,0xDA,0xFE,0x84,0xFE,0x9B,0x04,0x94,0x04,0xA0,0xFE, + 0xF1,0x03,0x0A,0x07,0x28,0xFD,0xFF,0xFA,0x1D,0x02,0xE1,0xFB, + 0x16,0xF5,0xF1,0xFB,0x31,0xFE,0x67,0xF8,0x76,0xFB,0xBA,0x01, + 0x9A,0xFF,0xDA,0xFE,0x19,0x04,0x87,0x05,0x3F,0x02,0x6E,0x02, + 0xE0,0x03,0x5E,0x01,0x81,0xFE,0x7A,0xFF,0xC5,0xFF,0xE5,0xFD, + 0x21,0xFE,0x6A,0xFF,0x88,0xFF,0x91,0xFF,0xCD,0x00,0xA5,0x01, + 0x83,0x02,0x63,0x03,0x9E,0x02,0xD5,0x02,0x6C,0x02,0x3C,0x00, + 0x77,0xFF,0xA1,0xFF,0x71,0xFE,0xA1,0xFD,0xD4,0xFE,0x59,0xFE, + 0xCB,0xFD,0x6B,0xFE,0x74,0xFE,0xBD,0xFD,0x02,0xFF,0xE2,0xFE, + 0x27,0xFF,0x47,0xFF,0x30,0x00,0xC7,0x03,0xFD,0xFE,0x73,0x08, + 0x3E,0x0C,0x7F,0xFE,0x39,0x06,0xD2,0x09,0x2D,0xF8,0xF5,0xF6, + 0x22,0x02,0x51,0xF6,0x93,0xEF,0x97,0xFE,0x6A,0xFE,0x9C,0xF5, + 0xCA,0xFF,0x12,0x07,0x3D,0xFF,0xC4,0x01,0x88,0x0A,0x3E,0x06, + 0xF9,0x00,0xC0,0x04,0xA8,0x03,0x7D,0xFD,0x92,0xFD,0xAF,0xFF, + 0x0E,0xFD,0x4A,0xFB,0xA7,0xFD,0x98,0xFE,0x9C,0xFD,0x6E,0xFF, + 0x86,0x02,0xE2,0x02,0xFE,0x02,0xE0,0x04,0xB8,0x04,0x59,0x02, + 0xA1,0x01,0x47,0x01,0x38,0xFF,0x55,0xFE,0xE2,0xFE,0x14,0xFE, + 0x60,0xFD,0x2A,0xFE,0x7E,0xFE,0x42,0xFE,0x27,0xFF,0x29,0x00, + 0x1A,0x00,0x44,0x00,0x1F,0x01,0xDC,0x00,0x6F,0x00,0xF5,0x00, + 0x32,0x00,0x45,0xFF,0x56,0xFF,0xB6,0xFE,0xA9,0xFD,0xFA,0xFD, + 0xB8,0xFE,0x2D,0xFE,0x38,0xFF,0xD8,0x00,0x1A,0x00,0xF2,0x00, + 0xC8,0x01,0xD2,0x00,0x17,0x01,0x56,0x01,0xA0,0x00,0x80,0x00, + 0xB3,0x00,0xF9,0xFF,0x33,0x00,0x36,0x00,0x57,0xFF,0xEF,0xFF, + 0x0C,0x00,0xDC,0xFE,0x5F,0xFF,0x68,0x00,0x9A,0xFF,0xA3,0xFF, + 0xFA,0x00,0xBB,0x00,0xB7,0xFF,0x9B,0x00,0xB8,0x00,0x5F,0xFF, + 0xF0,0xFF,0x5C,0x00,0x4F,0xFF,0xCA,0xFF,0x49,0x00,0x98,0xFF, + 0xC5,0xFF,0xEF,0xFF,0xC0,0xFF,0x73,0xFF,0xF4,0xFF,0xE1,0xFF, + 0x5A,0xFE,0x76,0x00,0x8E,0x02,0xCD,0x00,0x04,0x02,0xFA,0x05, + 0xE1,0x02,0x42,0xFF,0x7A,0x03,0x2F,0x01,0x48,0xFA,0x45,0xFD, + 0x13,0xFF,0xFC,0xF8,0x7F,0xFA,0x9C,0xFF,0x0C,0xFC,0x9C,0xFB, + 0xAA,0x01,0x43,0x01,0x02,0xFF,0x46,0x03,0x1E,0x04,0x9E,0x00, + 0x5F,0x02,0xF8,0x03,0xC0,0x00,0x97,0x00,0x3F,0x02,0x9C,0xFF, + 0x44,0xFE,0xE5,0xFF,0xAA,0xFE,0x51,0xFD,0x3B,0xFF,0xDD,0xFF, + 0x47,0xFF,0xFC,0x00,0x05,0x02,0x35,0x01,0xB0,0x01,0x5D,0x02, + 0x78,0x01,0xE9,0x00,0x0A,0x01,0x23,0x00,0x42,0xFF,0x42,0xFF, + 0xD7,0xFE,0x2D,0xFE,0x52,0xFE,0x5E,0xFE,0x26,0xFE,0x59,0xFE, + 0x9F,0xFE,0xAA,0xFE,0x9A,0xFE,0x44,0xFF,0x6C,0xFF,0xB5,0xFF, + 0xE1,0xFF,0xAD,0x00,0x9E,0x00,0x90,0x00,0x25,0x04,0x52,0x03, + 0xC2,0x02,0x44,0x06,0x52,0x03,0x63,0xFF,0xF1,0x01,0x12,0x00, + 0x14,0xFA,0x4B,0xFC,0xFA,0xFD,0x60,0xF9,0x10,0xFB,0x66,0xFF, + 0x9F,0xFC,0x25,0xFD,0x36,0x02,0x7B,0x01,0x62,0x00,0xD0,0x03, + 0x84,0x03,0xAE,0x00,0xD1,0x02,0x59,0x03,0x54,0x00,0x44,0x01, + 0x11,0x02,0xD5,0xFE,0x56,0xFE,0xA8,0xFF,0x7F,0xFD,0xDD,0xFC, + 0x4D,0xFF,0x29,0xFF,0xE5,0xFE,0x77,0x01,0x02,0x02,0xF4,0x00, + 0x36,0x02,0xA9,0x02,0x33,0x01,0x66,0x01,0xAE,0x01,0x2D,0x00, + 0xC4,0xFF,0x12,0x00,0x0D,0xFF,0xA4,0xFE,0x2E,0xFF,0xA4,0xFE, + 0x2E,0xFE,0xC7,0xFE,0xA9,0xFE,0x72,0xFE,0x27,0xFF,0x75,0xFF, + 0x7F,0xFF,0x18,0x00,0x5B,0x00,0x2F,0x00,0x55,0x00,0x40,0x00, + 0xED,0xFF,0x09,0x00,0xF6,0xFF,0xD4,0xFF,0x12,0x00,0x05,0x00, + 0x03,0x00,0x34,0x00,0x20,0x00,0x1D,0x00,0x4E,0x00,0x30,0x00, + 0x12,0x00,0x4B,0x00,0x59,0x00,0x44,0x00,0x72,0x00,0x95,0x00, + 0x72,0x00,0x7E,0x00,0x8A,0x00,0x41,0x00,0x19,0x00,0x11,0x00, + 0xD1,0xFF,0xA6,0xFF,0xBE,0xFF,0xAA,0xFF,0x8F,0xFF,0xBD,0xFF, + 0xC7,0xFF,0xB7,0xFF,0xDE,0xFF,0xF0,0xFF,0xE3,0xFF,0xF2,0xFF, + 0x04,0x00,0x09,0x00,0x16,0x00,0x1C,0x00,0x19,0x00,0x1F,0x00, + 0x18,0x00,0x0E,0x00,0x06,0x00,0xF1,0xFF,0xED,0xFF,0xFB,0xFF, + 0xF0,0xFF,0xEA,0xFF,0x07,0x00,0x11,0x00,0x0D,0x00,0x24,0x00, + 0x31,0x00,0x17,0x00,0x11,0x00,0x23,0x00,0x1C,0x00,0x0F,0x00, + 0x08,0x00,0xE0,0xFF,0xBD,0xFF,0xB8,0xFF,0x94,0xFF,0x86,0xFF, + 0xA9,0xFF,0xB3,0xFF,0xBA,0xFF,0xF0,0xFF,0x0F,0x00,0x18,0x00, + 0x40,0x00,0x4B,0x00,0x43,0x00,0x52,0x00,0x30,0x00,0xB7,0xFF, + 0xF0,0xFF,0xD6,0x00,0x29,0x01,0x60,0x01,0xD8,0x01,0xE4,0x01, + 0x8B,0x01,0xDE,0x00,0xFA,0xFF,0x67,0xFF,0xAC,0xFE,0xAF,0xFD, + 0x83,0xFD,0x79,0xFD,0xF6,0xFC,0x52,0xFD,0x3B,0xFE,0x8D,0xFE, + 0x26,0xFF,0x18,0x00,0x4B,0x00,0x74,0x00,0x16,0x01,0x38,0x01, + 0x59,0x01,0xF3,0x01,0xF5,0x01,0xB1,0x01,0xE0,0x01,0x81,0x01, + 0xA8,0x00,0x63,0x00,0xFB,0xFF,0x3F,0xFF,0x3C,0xFF,0x63,0xFF, + 0x1F,0xFF,0x57,0xFF,0xD6,0xFF,0xE4,0xFF,0x2A,0x00,0xAC,0x00, + 0xAC,0x00,0xA9,0x00,0xD4,0x00,0x8C,0x00,0x49,0x00,0x57,0x00, + 0x12,0x00,0xC1,0xFF,0xD7,0xFF,0xA8,0xFF,0x4B,0xFF,0x4C,0xFF, + 0x2B,0xFF,0xD2,0xFE,0xE0,0xFE,0xFA,0xFE,0xD7,0xFE,0x0E,0xFF, + 0x54,0xFF,0x5E,0xFF,0xB5,0xFF,0x0F,0x00,0x1E,0x00,0x6B,0x00, + 0xCF,0x00,0xD1,0x00,0xD1,0x00,0xE8,0x00,0xBE,0x00,0x8D,0x00, + 0x6E,0x00,0x26,0x00,0xEA,0xFF,0xD4,0xFF,0xB0,0xFF,0xA9,0xFF, + 0xCD,0xFF,0xDC,0xFF,0xEE,0xFF,0x19,0x00,0x2C,0x00,0x2B,0x00, + 0x41,0x00,0x34,0x00,0x27,0x00,0x42,0x00,0x49,0x00,0x80,0x00, + 0xE5,0x00,0x04,0x01,0x1A,0x01,0x1A,0x01,0xFF,0x00,0xBC,0x00, + 0x3A,0x00,0xB9,0xFF,0x4A,0xFF,0xF7,0xFE,0x3A,0xFE,0x0F,0xFE, + 0x32,0xFE,0xC2,0xFD,0xE3,0xFD,0x69,0xFE,0xA7,0xFE,0x1A,0xFF, + 0xEA,0xFF,0x51,0x00,0xAF,0x00,0x2B,0x01,0x4A,0x01,0x59,0x01, + 0x6C,0x01,0x3A,0x01,0x02,0x01,0xE8,0x00,0x9E,0x00,0x58,0x00, + 0x3A,0x00,0x06,0x00,0xE5,0xFF,0x06,0x00,0x0D,0x00,0x03,0x00, + 0x1C,0x00,0x19,0x00,0x09,0x00,0x22,0x00,0x32,0x00,0x2C,0x00, + 0x37,0x00,0x27,0x00,0xFF,0xFF,0xED,0xFF,0xCD,0xFF,0x9A,0xFF, + 0x88,0xFF,0x80,0xFF,0x6A,0xFF,0x73,0xFF,0x86,0xFF,0x7E,0xFF, + 0x88,0xFF,0xA2,0xFF,0xA3,0xFF,0xB3,0xFF,0xCE,0xFF,0xCE,0xFF, + 0xD5,0xFF,0xE8,0xFF,0xE5,0xFF,0xEA,0xFF,0x09,0x00,0x1E,0x00, + 0x2C,0x00,0x46,0x00,0x59,0x00,0x61,0x00,0x6F,0x00,0x72,0x00, + 0x69,0x00,0x69,0x00,0x61,0x00,0x49,0x00,0x3D,0x00,0x32,0x00, + 0x15,0x00,0x08,0x00,0x04,0x00,0xF7,0xFF,0xF6,0xFF,0xF7,0xFF, + 0xE8,0xFF,0xE5,0xFF,0xE9,0xFF,0xDD,0xFF,0xDE,0xFF,0xE7,0xFF, + 0xE1,0xFF,0xE1,0xFF,0xEE,0xFF,0xEE,0xFF,0xF0,0xFF,0xFB,0xFF, + 0xF7,0xFF,0xFA,0xFF,0x02,0x00,0xFF,0xFF,0xFC,0xFF,0x00,0x00, + 0xFE,0xFF,0xF9,0xFF,0x00,0x00,0x02,0x00,0xFF,0xFF,0x08,0x00, + 0x0D,0x00,0x0B,0x00,0x10,0x00,0x14,0x00,0x11,0x00,0x0E,0x00, + 0x11,0x00,0x09,0x00,0x02,0x00,0x01,0x00,0xF8,0xFF,0xF1,0xFF, + 0xF0,0xFF,0xEB,0xFF,0xE8,0xFF,0xEF,0xFF,0xEC,0xFF,0xEC,0xFF, + 0xF4,0xFF,0xF8,0xFF,0xF8,0xFF,0xFE,0xFF,0x01,0x00,0x00,0x00, + 0x05,0x00,0x06,0x00,0x08,0x00,0x0B,0x00,0x08,0x00,0x07,0x00, + 0x07,0x00,0x03,0x00,0xFD,0xFF,0xFC,0xFF,0xFE,0xFF,0x00,0x00, + 0x01,0x00,0xFC,0xFF,0xF8,0xFF,0xF7,0xFF,0xF6,0xFF,0xEF,0xFF, + 0xEF,0xFF,0xF8,0xFF,0xFC,0xFF,0x00,0x00,0x04,0x00,0x07,0x00, + 0x09,0x00,0x09,0x00,0x09,0x00,0x08,0x00,0x07,0x00,0x07,0x00, + 0x02,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x03,0x00, + 0x03,0x00,0x05,0x00,0x05,0x00,0x06,0x00,0x05,0x00,0x01,0x00, + 0x03,0x00,0x03,0x00,0x03,0x00,0x05,0x00,0x05,0x00,0x03,0x00, + 0x01,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFD,0xFF,0xFD,0xFF,0xFD,0xFF,0xFF,0xFF,0xFE,0xFF,0xFF,0xFF, + 0x03,0x00,0x02,0x00,0x02,0x00,0x03,0x00,0x01,0x00,0x01,0x00, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0x01,0x00,0xFF,0xFF,0x01,0x00,0x02,0x00,0xFF,0xFF, + 0x00,0x00,0xFE,0xFF,0xFD,0xFF,0xFE,0xFF,0xFD,0xFF,0xFD,0xFF, + 0xFF,0xFF,0xFE,0xFF,0xFB,0xFF,0xFF,0xFF,0xFD,0xFF,0xFE,0xFF, + 0x01,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00, + 0xFE,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00, + 0x01,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF, + 0x00,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF,0x02,0x00,0xFA,0xFF, + 0x03,0x00,0x09,0x00,0xF8,0xFF,0x01,0x00,0x07,0x00,0xFE,0xFF, + 0xFD,0xFF,0x05,0x00,0x04,0x00,0xFF,0xFF,0x03,0x00,0x04,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x01,0x00, + 0x02,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x00, + 0x01,0x00,0x00,0x00,0x02,0x00,0x02,0x00,0x01,0x00,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF, + 0x00,0x00,0xFF,0xFF,0xFE,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00, + 0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x01,0x00,0x00,0x00, + 0xFF,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00,0x02,0x00,0xFF,0xFF, + 0x02,0x00,0x07,0x00,0x02,0x00,0x00,0x00,0x03,0x00,0xFD,0xFF, + 0xF7,0xFF,0xFC,0xFF,0xFB,0xFF,0xF1,0xFF,0xF5,0xFF,0xF8,0xFF, + 0xF3,0xFF,0xF9,0xFF,0x00,0x00,0x01,0x00,0x02,0x00,0x08,0x00, + 0x08,0x00,0x07,0x00,0x0E,0x00,0x0D,0x00,0x08,0x00,0x0A,0x00, + 0x0A,0x00,0x04,0x00,0x03,0x00,0x04,0x00,0x00,0x00,0xFF,0xFF, + 0x00,0x00,0xFB,0xFF,0xF8,0xFF,0xFE,0xFF,0xFB,0xFF,0xF6,0xFF, + 0xFC,0xFF,0xFC,0xFF,0xF9,0xFF,0xFE,0xFF,0xFE,0xFF,0xFB,0xFF, + 0xFE,0xFF,0x00,0x00,0xFF,0xFF,0x01,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x02,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x01,0x00, + 0x01,0x00,0x03,0x00,0x03,0x00,0x01,0x00,0x03,0x00,0x01,0x00, + 0xFF,0xFF,0x01,0x00,0x03,0x00,0x01,0x00,0x00,0x00,0x04,0x00, + 0x01,0x00,0x01,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x01,0x00, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00,0xFF,0xFF,0xFF,0xFF, + 0x00,0x00,0xFE,0xFF,0xFE,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0x01,0x00,0xFF,0xFF,0x01,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF, + 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF, + 0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00, + 0x00,0x00,0xFF,0xFF,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00, + 0x01,0x00,0x00,0x00,0xFF,0xFF,0x02,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00, + 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF,0x01,0x00, + 0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0xFF,0xFF,0x00,0x00,0xFF,0xFF,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0x01,0x00,0xFF,0xFF,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00, + 0x01,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00, + 0x00,0x00,0x00,0x00,0xFE,0xFF,0xFF,0xFF,0x01,0x00,0xFF,0xFF, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00, + 0x00,0x00,0xFF,0xFF,0x00,0x00,0x02,0x00,0xFB,0xFF,0xFF,0xFF, + 0x06,0x00,0xFA,0xFF,0xFB,0xFF,0x03,0x00,0x02,0x00,0xFE,0xFF, + 0xFD,0xFF,0x07,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x04,0x00, + 0xFD,0xFF,0x05,0x00,0x00,0x00,0xFE,0xFF,0x08,0x00,0xF7,0xFF, + 0x07,0x00,0xF2,0xFF,0x11,0x00,0xF0,0xFF,0x0F,0x00,0xEC,0xFF, + 0x22,0x00,0xD4,0xFF,0x28,0x00,0xEC,0xFF,0x2F,0x00,0xDA,0xFF, + 0xCA,0xFF,0xA4,0x00,0x98,0xFE,0xD1,0x05,0x98,0xFF,0x26,0xF6, + 0xDD,0x02,0xEB,0x04,0x6D,0xFC,0xB1,0xFD,0x48,0x01,0x56,0x02, + 0xB1,0x02,0xFB,0xFC,0xC8,0xFC,0x7D,0x03,0x3C,0x02,0x31,0xFD, + 0x3E,0xFF,0xD6,0x02,0x05,0x00,0xD8,0xFD,0x7C,0x00,0x73,0x01, + 0x76,0xFE,0xD4,0xFF,0xD5,0x00,0x1C,0x00,0x96,0xFE,0x6D,0x00, + 0xE8,0x04,0xAB,0xFD,0x44,0xFC,0xA0,0x01,0x52,0x00,0x02,0xFE, + 0x8E,0xFF,0x9A,0x01,0xB0,0xFF,0x53,0xFF,0x69,0x00,0x84,0x00, + 0x14,0x00,0x22,0x01,0x01,0xFF,0xD5,0xFE,0x87,0x03,0x90,0xFF, + 0xEA,0xFE,0xAF,0x03,0x1B,0xFF,0x8E,0xFB,0xE4,0xFF,0xF5,0x00, + 0xF1,0xFD,0x6E,0xFF,0x12,0x02,0x09,0x00,0x51,0xFE,0xD5,0x00, + 0x98,0x01,0xC9,0xFF,0x56,0x00,0x1C,0x01,0x07,0x00,0xBA,0xFF, + 0xB7,0x00,0xA2,0xFF,0x03,0xFF,0x56,0x00,0x82,0xFF,0x11,0xFF, + 0x52,0x00,0x67,0x00,0x03,0xFF,0xCC,0xFF,0x40,0x01,0x62,0x00, + 0xD7,0xFF,0x59,0x00,0x4D,0x00,0xEB,0xFF,0x15,0x00,0x34,0x00, + 0xA1,0xFF,0xBD,0xFF,0xC3,0xFF,0x4F,0xFF,0xB7,0xFF,0x35,0x00, + 0x22,0x00,0x80,0xFF,0xEC,0xFF,0xA9,0x00,0xE9,0xFF,0x59,0x00, + 0x8C,0x00,0x99,0xFF,0x3C,0x00,0x43,0x00,0x06,0x00,0x80,0xFF, + 0x14,0x00,0xF3,0xFF,0xFA,0xFE,0x41,0x00,0x4C,0x00,0x96,0xFF, + 0xCC,0xFF,0x9E,0x00,0x57,0x00,0xFA,0xFF,0x5B,0x00,0x1A,0x00, + 0x0E,0x00,0x0C,0x00,0xE2,0xFF,0x16,0x00,0xE0,0xFF,0xED,0xFF, + 0x0F,0x00,0x6F,0xFF,0xD5,0xFF,0x6B,0x00,0x1F,0x00,0x85,0xFF, + 0x3D,0x00,0x83,0x00,0x7B,0xFF,0xDF,0xFF,0xAF,0x00,0xFE,0xFF, + 0x99,0xFF,0x28,0x00,0xE2,0xFF,0xF3,0xFF,0x1B,0x00,0xD8,0xFF, + 0x02,0x00,0xF0,0xFF,0x13,0x00,0xE4,0xFF,0xF6,0xFF,0x5C,0x00, + 0xC0,0xFF,0xE1,0xFF,0x10,0x00,0x56,0x00,0x03,0x00,0x5F,0xFF, + 0x35,0x00,0xF2,0xFF,0x17,0x00,0x19,0x01,0xE0,0xFF,0xFE,0xFF, + 0xFD,0xFF,0xDB,0xFE,0xC7,0xFF,0xF0,0xFF,0xFA,0xFF,0xFB,0xFF, + 0x24,0x00,0x09,0x00,0x83,0xFF,0xF0,0xFF,0x34,0x00,0x30,0x00, + 0x01,0x00,0xA6,0x00,0x63,0x00,0x38,0x00,0x1C,0x00,0x07,0x00, + 0x3F,0x00,0x3B,0x00,0xDF,0xFF,0xF0,0xFE,0x73,0x00,0xBB,0xFF, + 0x03,0xFF,0x0C,0x00,0x79,0x00,0x47,0x00,0x6C,0xFF,0xBA,0x00, + 0xB1,0x00,0xBB,0xFF,0xDD,0xFF,0x29,0x00,0x1E,0x00,0xA8,0xFF, + 0xCE,0xFF,0x6E,0x00,0xA4,0xFF,0x2A,0xFF,0xC4,0xFF,0x16,0x00, + 0xE6,0xFF,0x4B,0xFF,0xDD,0x00,0x98,0x00,0x09,0xFF,0x92,0x00, + 0xFC,0x00,0xCC,0xFF,0x56,0xFF,0xBF,0x00,0x97,0x00,0xAB,0xFE, + 0x16,0x00,0xB4,0x00,0x86,0xFF,0x87,0xFF,0x8D,0x00,0x05,0x00, + 0x18,0xFF,0xDD,0x00,0x95,0x00,0x2F,0xFF,0x18,0x00,0xCC,0x00, + 0xF9,0xFF,0xAA,0xFF,0x40,0x00,0x2B,0x00,0x79,0xFF,0xFA,0xFF, + 0xF6,0xFF,0x5E,0xFF,0x7E,0x00,0xD5,0xFF,0xD0,0xFF,0x0F,0x00, + 0xC4,0xFF,0x63,0x00,0x78,0x00,0xD4,0xFF,0xB9,0xFE,0xFB,0x00, + 0xE0,0x00,0xF2,0xFE,0x1D,0x00,0xCB,0x00,0x33,0x00,0x13,0xFF, + 0xF3,0xFF,0x74,0x00,0xBF,0xFF,0xBE,0xFF,0xE2,0xFF,0xE6,0xFF, + 0x43,0x00,0xDB,0x00,0x73,0xFF,0x48,0xFF,0x4C,0x01,0xBE,0xFF, + 0x4B,0xFF,0xD8,0x00,0x88,0xFF,0x6A,0xFF,0x7E,0x00,0x60,0x00, + 0x16,0xFF,0x70,0x00,0xA2,0x00,0x4C,0x49,0x53,0x54,0x12,0x00, + 0x00,0x00,0x49,0x4E,0x46,0x4F,0x49,0x47,0x4E,0x52,0x06,0x00, + 0x00,0x00,0x4F,0x74,0x68,0x65,0x72,0x00 +}; + diff --git a/code/gamespy/changelog.txt b/code/gamespy/changelog.txt new file mode 100644 index 00000000..5909c2c0 --- /dev/null +++ b/code/gamespy/changelog.txt @@ -0,0 +1,151 @@ +Changelog for: GameSpy Common Code +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 2.06.00 RMV RELEASE Released to Developer Site +11-27-2007 2.05.01 SAH CLEANUP Removed extern "c" block in nonport.h to prevent linker errors +08-06-2007 2.05.00 RMV RELEASE Released to Developer Site *Note - fixed version number to match other common changelog +06-22-2007 2.01.02 SAH FIX Fixed MD5Print to null terminate the output string. +05-16-2007 2.01.01 DES CLEANUP Changed MD5Print to non use sprintf, to avoid security warnings +12-15-2006 2.01.00 MJW RELEASE Releasing to developer site. +05-26-2005 2.00.00 BED RELEASE Releasing to developer site. +05-16-2005 2.00.00 BED OTHER Moved common code into /common directory. + BED OTHER Stubbed nonport.h and nonport.c to include new common files. +04-28-2005 1.11.02 SN RELEASE Releasing to developer site. +04-27-2005 1.11.02 DES RELEASE Limited release to Nintendo DS developers. +04-25-2005 1.11.02 DES FIX Nitro updates + DES FEATURE Addition categories and types added to gsiDebug +04-08-2005 1.11.01 SN FIX Fixed SN Systems bug where a socket was shown to be readable even when an error was recorded for it. +04-08-2005 1.11.00 DES FEATURE Changes for XBox support. +04-04-2005 1.10.72 SN RELEASE Releasing to developer site. +04-04-2005 1.10.72 DES CLEANUP Removed unused GS_BIG_ENDIAN define. + DES FEATURE Added gsi_is_false() and gsi_is_true() defines. + DES CLEANUP Removed unused PATHCHAR define. + DES CLEANUP Removed old _NITRO code from SetSockBlocking. +03-31-2005 1.10.71 BED FIX Removed some CodeWarrior strict compile warnings. +03-31-2005 1.10.70 SN FIX Fixed build problem by moving defines that had dependancies +03-14-2005 1.10.69 DES FEATURE Nintendo DS support +02-18-2005 1.10.68 BED FIX Added timeout for gsoap recv so it could recover from a hosed server + BED FIX Explicit __cdecl for hashtable and darray are used on Win32 only + BED FIX Switched gsimemory to track peak memory usage rather than total num allocs. +01-27-2005 1.10.67 DES FIX Added GSI_NO_THREADS for platforms without thread support + DES FIX Fixed custom SN sendto and moved it to nonport +01-04-2005 1.10.66 DDW FIX Added malloc cast in XXTEA encryption code +12-21-2004 1.10.65 SN FIX Added code to register the hostname resolution thread with SN_SYSTEMS +12-07-2004 1.10.64 BED FIX Added explicit __cdecl in darray.c and gsidebug.c +11-18-2004 1.10.63 SN RELEASE Releasing to developer site. +11-18-2004 1.10.63 SN FIX Fixed conversion warnings for XXTEA algorithm +11-17-2004 1.10.62 SN FIX Modified the XXTEA headers and renamed global key size +11-16-2004 1.10.62 BED FEATURE Added Thread/Semaphore common functions + BED FEATURE Added AD sdk to common debugger code. + BED FEATURE Common debug functions and memory manager are now thread safe + BED FEATURE Added core task processor (see core.h) + BED FEATURE Added common gsoap task (see soap.h) +11-16-2004 1.10.61 SN FEATURE Added URL-safe Base64 encoding and XXTEA encrypt and decrypt +09-21-2004 1.10.60 SN FIX Added the directories for MacOS X and Win32 common to goacommon.bat +09-16-2004 1.10.59 SN RELEASE Releasing to developer site. +09-16-2004 1.10.59 SN FIX Modified header define of NOFILE to avoid conflict with MacOS X +09-03-2004 1.10.58 BED CLEANUP Removed misc compiler warnings. +08-27-2004 1.10.57 BED FEATURE Added memory diagnostic functions to memory manager. +08-27-2004 1.10.56 DES CLEANUP Changed UNDER_UNIX to _UNIX, and define it if _LINUX or _MACOSX are defined. + DES OTHER Generate an error if _WIN32, _PS2, or _UNIX are not defined. + DES CLEANUP General Unicode cleanup + DES CLEANUP Fixed minor warnings under OSX + BED FIX Fixed typo in SN Systems version of GOAGetLastError. +08-25-2004 1.10.55 BED FEATURE Added common debug utilities to nonport. (See gsiDebug.h) + BED FEATURE Added memory pool manager to nonport. (See top of nonport.h) +08-24-2004 1.10.54 DES CLEANUP Removed references to MacOS. +08-20-2004 1.10.53 SN FIX Changed the way errors are obtained from the SN Systems network stack. +08-05-2004 1.10.52 SN RELEASE Releasing to developer site. +08-04-2004 1.10.51 SN FIX Fixed a function prototype causing compiler warnings for codewarrior +08-02-2004 1.10.50 SN FEATURE Added support for developers to use winsock 2 +07-13-2004 1.10.49 DES FEATURE Added GSIStartResolvingHostname() for doing async hostname lookups. +07-12-2004 1.10.48 SN FIX Cleared warnings when warnings are treated as errors for gamspy common code. +07-09-2004 1.10.47 SN FIX Updated portions of code to eliminate warnings for PS2 +07-08-2004 1.10.46 SN FIX Commented an include line +07-01-2004 1.10.46 SN FIX Includeded for ps2 +06-29-2004 1.10.45 BED RELEASE Releasing to developer site. +06-28-2004 1.10.45 DES FEATURE Added gsimemalign to the list of memory functions. +06-24-2004 1.10.44 BED FEATURE Util_Rand functions no longer static. + BED FEATURE Added B64 encode and decode from matrix source. +06-22-2004 1.10.43 BED RELEASE Releasing with updated PS2Common code +06-18-2004 1.10.42 BED RELEASE Releasing to developer site. +06-01-2004 1.10.42 BED FEATURE Phase 1 of PS2 Insock integration. (needs further testing) + BED FIX Found case where unsigned long was used on Ps2 instead of gsi_time. +04-27-2004 1.10.41 DES FEATURE Added current_time_hires(), returns time in microseconds. +03-10-2004 1.10.40 BED FEATURE Added some more types to nonport.h + FIX Undefine socket types to workaround SNSys bug. (They plan to patch in march 04) +12-03-2003 1.10.39 BED FEATURE Added "GameSpy Help.chm" to goapackage.bat +11-10-2003 1.10.38 DES RELEASE Releasing to developer site. +11-10-2003 1.10.38 BED FIX Remove misc compiler warnings. +11-07-2003 1.10.37 DES FIX Added linux support for the common integers datatypes. + FIX Added a newline to the bottom of available.h. + FEATURE The available check now uses .available.gamespy.com. +10-29-2003 1.10.36 DES FEATURE Added available.h,.c for doing backend services availability checks. +10-09-2003 1.10.35 BED FEATURE Added gsi_time type for PS2 compatibility +10-08-2003 1.10.34 JED FEATURE Added common integer datatypes +08-25-2003 1.10.33 JED CLEANUP Added some sanity checks in hashtable.c +07-24-2003 1.10.32 DES RELEASE Releasing to developer site. +07-23-2003 1.10.32 DES FIX Moved EENet includes in nonport.h to fix CW PS2 warnings. + DES FEATURE Added memory tracking. Use GSI_MEM_TRACK to enable. +07-22-2203 1.10.31 BED CLEANUP General cleanup to remove CodeWarrior compiler warnings. +07-16-2003 1.10.30 DES CLEANUP Removed support for Cisco NFT for the PS2. + FIX Changed some __mips64 checks to _PS2 checks in nonport.c + BED FIX Changed nonport.c to not use #pragma comment when _PS2 if defined +07-10-2003 1.10.29 BED CLEANUP Added GSI_UNUSED to nonport.h to silence unused variable warnings. +05-09-2003 1.10.28 DES CLEANUP Removed Dreamcast support. + CLEANUP Changed nonport.h to use EENet if no network stack is defined for the PS2. + FIX Metrowerks for Win32 is no longer falsely identified as MacOS. +05-07-2003 1.10.27 DES RELEASE Releasing to developer site. + FIX Rewrote EENet GetMAC to be 2.7.x compatibile. +04-28-2003 1.10.26 DES RELEASE Releasing to developer site. + FIX Changed malloc/free in new EENet getlocalhost to gsimalloc/gsifree. +04-28-2003 1.10.25 DES RELEASE Releasing to developer site. +04-17-2003 1.10.25 DES FIX Rewrote EENet getlocalhost again, to be compatible with the 2.7.0.1 release. +04-15-2003 1.10.24 DES FIX Rewrote the EENet implementation of getlocalhost to build its own HOSTENT. +04-15-2003 1.10.23 JED CLEANUP Corrected DevStudio Level4 warnings for use of FD_SET +04-08-2003 1.10.22 JED FIX converted md5 function parameter type declarations from K&R C to ANSI C +03-27-2003 1.10.21 DES FIX IsPrivateIP no longer flips the source IP's byte order. +03-26-2003 1.10.20 DES RELEASE Releasing to developer site. +03-25-2003 1.10.20 DES FIX The EENet version of getlocalhost() wasn't checking all possible local IPs. +03-24-2003 1.10.19 DES FEATURE Added IsPrivateIP() for checking if an IP is a private network IP. + DES FIX GetTicks() no longer causes a compiler warning. +03-10-2003 1.10.18 DES RELEASE Releasing to developer site. +02-25-2003 1.10.18 DES CLEANUP Added headers to nonport.h for 2.6.1 EENet compatibility. + EENET_260 can be defined for 2.6.0 compatibility. +02-05-2003 1.10.17 DES RELEASE Releasing to developer site. +02-05-2003 1.10.17 DES FEATURE Added CanReceiveOnSocket and CanSendOnSocket as wrappers for select. + Needed because the SN stack for the PS2 has non-standard behavior. +01-23-2003 1.10.16 DES FEATURE Added the ability to just get the gsi*() memory function defines + by defining GSI_MEM_ONLY before including nonport.h. +01-07-2003 1.10.15 DES RELEASE Releasing to developer site. +01-07-2003 1.10.15 DES CLEANUP Removed a comment and a printf that were no longer needed. +01-02-2003 1.10.14 DES CLEANUP Removed the typedef for PSOCKADDR + It doesn't appear to be used anywhere, and was causing compile problems. +12-20-2002 1.10.13 DES FEATURE Implemented new code from SN Systems to get the MAC for the unique ID. +12-19-2002 1.10.12 DES RELEASE Releasing to developer site. +12-19-2002 1.10.12 DES CLEANUP Removed assert.h include from darray.h and hashtable.h. +12-18-2002 1.10.11 DES CLEANUP Put in a stub function fr getting the unique ID when using the SN stack. +12-16-2002 1.10.10 DES FEATURE Defined NOFILE when _PS2 is defined to exclude all file writing code. + FEATURE Defined SOMAXCONN to 5 when not defined. This is used as the backlog + parameter in calls to listen(). 5 is the max for SN Systems. + FEATURE gethostbyaddr() is not supported by SN Systems, defined it to NULL. + CLEANUP Changed GOAGetLastError() to clear the error for SN Systems. + Also removed GOAClearSocketError(), which was only needed for SN Systems. +12-13-2002 1.10.09 DES FEATURE Added EENet specific code for getting the MAC address for the Unique ID. +12-11-2002 1.10.08 DES FEATURE Additional eenet support. + FEATURE Added getlocalhost() for getting the local machine's hostent struct. + FEATURE Added SetSendBufferSize(), GetSendBufferSize(), and GetReceiveBufferSize(). +12-05-2002 1.10.07 DES CLEANUP General cleanup + FEATURE Initial PS2 eenet stack support +11-22-2002 1.10.06 DES RELEASE Releasing to developer site. +11-22-2002 1.10.06 DES FIX Fixed bug with checking the current time on the PS2. +11-20-2002 1.10.05 DES FEATURE Switched to using the MAC address for the Unique ID on the PS2. + CLEANUP Cleaned up to compile without warnings on the PS2. +11-18-2002 1.10.04 JED FIX nonport.c now using ansi registry funcs for WIN32/unicode +11-14-2002 1.10.03 DES FEATURE Added assert.h include to nonport.h. +11-13-2002 1.10.02 DDW FEATURE Changed GOAGetUniqueID to use redefinable function pointer. + Made Cisco the default stack for PS2. +11-13-2002 1.10.01 DES FIX Removed Reference to unsupported non-blocking CISCO stack +09-25-2002 1.10.00 DDW OTHER Changelog started diff --git a/code/gamespy/common/changelog.txt b/code/gamespy/common/changelog.txt new file mode 100644 index 00000000..5b92b94b --- /dev/null +++ b/code/gamespy/common/changelog.txt @@ -0,0 +1,321 @@ +Changelog for: GameSpy Common Code +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 2.06.00 RMV RELEASE Released to Developer Site +12-12-2007 2.05.14 SN FIX Fixed getlocalhost for revolution +12-10-2007 2.05.13 SN FIX Added limits.h for PS2 +11-27-2007 2.05.12 SAH FIX Removed tm struct and added SB_NO_ICMP define for revolution + SAH CLEANUP Moved extern "c" block below includes to prevent linker errors +11-21-2007 2.05.11 SAH FIX Added include for Nitro/Revolution +11-16-2007 2.05.10 SAH FIX Fixed mem leak in gsCore from not freeing the dynamic array + SAH CLEANUP Changed async DNS code to set the handle pointer to null after freeing +11-09-2007 2.05.09 SAH CLEANUP Added wchar_t typecasts to goawstrdup to remove warnings +11-05-2007 2.05.08 SAH FIX Removed GP_NP_BUDDY_SYNC define, NP modules load now regardless +10-31-2007 2.05.07 SAH FIX Fixed to be included regardless of GSI_UNICODE define +10-19-2007 2.05.06 SAH OTHER Added goawstrdup function for wchar strdup operations +10-09-2007 2.05.05 SAH FIX Fixed compiler errors with gsiInt64ToString functions +10-08-2007 2.05.04 BED RELEASE Limited release +10-04-2007 2.05.04 DES FEATURE The task list can now grow dynamically +09-26-2007 2.05.03 SAH FIX Removed overrided PS3 time() function +09-18-2007 2.05.02 SAH OTHER Added two PS3 modules to ps3common for GP-NP support +08-16-2007 2.05.01 SAH FIX Removed unnecessary len > 0 assert in gsXmlWriteBase64BinaryElement +08-14-2007 2.05.01 SN FEATURE Added 64 bit integer function gsiStringToInt64 + SN FEATURE Added 64 bit versions of gsXml read and write functions +08-06-2007 2.05.00 RMV RELEASE Released to Developer Site +07-19-2007 2.04.07 SAH FIX Added explicit typecasting to UdpEngine to get rid of compiler warnings +07-16-2007 2.04.06 RMV FIX Defined SB_NO_ICMP_SUPPORT for Mac and Linux. +06-07-2007 2.04.05 SN RELEASE Releasing to developer site +06-01-2007 2.04.05 DES FIX Fixed warning under Win32 due to using a pointer to malloc functions +05-18-2007 2.04.04 SAH FIX Fixed memory leak from unfreed string in gsiXxteaAlg + 2.04.04 SAH FIX Fixed trashed memory bug in gsiXxteaAlg from null char overwrite +05-16-2007 2.04.03 DES FIX Wrapped gsPlatform.h in extern "C" if C++ + DES CLEANUP Cleaned up #undefs of common string funcs + DES CLEANUP Removed CE support from common code +04-17-2007 2.04.02 SAH OTHER Added gsiFloatSwap and Unswap byte reversal functions +03-15-2007 2.04.01 SN FIX Fixed some compiler warnings and errors for other platforms. +03-05-2007 2.04.00 SAH RELEASE Released to Developer Site +03-02-2007 2.03.10 SN FIX Fixed gsi common debug printing for Revolution + SN FIX Switched printf for nitro to properly print common debug logs + SN FIX Fixed warnings for code warrior +03-02-2007 2.03.09 SAH FIX Fixed MACOSX endian-ness to default to GSI_LITTLE_ENDIAN +03-02-2007 2.03.08 SN FIX Fixed declaration warning in gsXml.c +02-22-2007 2.03.07 SN FIX Fixed prototypes for extern aligned mem functions +01-30-2007 2.03.06 DES FIX Fixed compile error in gsXml.c +01-24-2007 2.03.05 SN FIX Fixed some Visual Studio 2005 warnings +01-23-2007 2.03.04 SAH FIX Added generalized UNICODE support for gsXML + 2.03.04 BED FIX Added UTF8ToUCS2StringLength for gsXML changes +01-17-2007 2.03.03 DES RELEASE Limited Release +01-16-2007 2.03.03 DES FEATURE Added X360 support +01-04-2007 2.03.02 BED FIX Added 2 new functions used in large int to ensure proper endian-ness for encryption + 2.03.02 BED FIX Fixed EncryptBuffer function to fix PS3 byte-order bug +12-20-2006 2.03.01 SN FIX Added code to free allocated memory for memory manager +12-15-2006 2.03.00 MJW RELEASE Released to Developer Site +12-13-2006 2.02.23 SN FIX Fixed issue where PSP uses the wrong FD_SETSIZE +12-11-2006 2.02.22 SN FIX Fixed VS 2005 warnings of deprecated CRT function itoa using a define +12-05-2006 2.02.21 SN FIX Made changes to the memory manager getting rid of unnamed union warnings +11-29-2006 2.02.20 SN FIX Small changes made to remove implicit cast warnings +11-10-2006 2.02.19 JR RELEASE Limited Release +11-08-2006 2.02.19 DES FIX Fixed so Core code will work managed C++ +11-06-2006 2.02.18 BED FEATURE Encryption clean up and speed up + Implemented RSA signatures. + Replaced Montgomery Multiplication with faster algorithm. + Fixed some bugs with 16bit compatability. +11-02-2006 2.02.17 SAH FIX Added CloseHandle to free Win32 thread resources +11-01-2006 2.02.16 BED FIX Added const keyword to various encrytion and large integer functions +10-31-2006 2.02.15 SN FIX Added a line to have system determine DST for all platforms supporting localtime and mktime +10-23-2006 2.02.14 DES RELEASE Limited release +10-11-2006 2.02.14 SAH OTHER Added gsThreadMacOSX.c and Mac OSX Asynchronous DNS Threading Support (uses pthreads) +10-11-2006 2.02.13 DES FIX Soap works with Unicode +10-05-2006 2.02.12 SAH FIX Added MacOSXCommon.c, updated gsDebug and Makefile.common to get rid of Mac compiler warnings +09-28-2006 2.02.11 SAH FIX Fixed ps3common.c to work with PS3 095.00x SDK. Must now explicitly load modules prior to initializing + the network since these libraries were made into stub libraries. +09-21-2006 2.02.10 SAH FIX Fixed a memory leak in Linux threads from not detaching a thread before exiting. +09-15-2006 2.02.09 DES FEATURE Added GS_ASSERT_NO_MESSAGEBOX define +09-13-2006 2.02.08 SN FIX Added recommended fix for broadcast IP +09-13-2006 2.02.07 SN FIX Updated Revolution CW Projects + Fixed some impclicit type casts + Miscalaneous Fixes for Revolution +09-05-2006 2.02.06 SN FIX Updated Revolution support + Added a revolutionCommon.c file for testing sample applications +08-24-2006 2.02.05 SAH FIX Removed GSI_NO_THREADS define around func prototypes +08-23-2006 2.02.04 SAH FIX Added #ifdef around the gsi time functions to get rid of warnings on unsupported plats +08-17-2006 2.02.03 SAH FIX Cleaned up the gsi threaded/nonthreaded DNS lookup calls in gsPlatformUtil.c + SAH OTHER GSI_NO_ASYNC_DNS can now be defined to turn off threaded lookups +08-04-2006 2.02.02 SN FIX Fixed XML date and time read function to use time_t +08-04-2006 2.02.02 SN FIX Fixed the resolve host name thread function for Nintendo Wii +08-04-2006 2.02.01 SAH OTHER Added gsi time utility functions as wrappers for gmtime, ctime, mktime + SAH FIX Added gsXmlReadChildAsDateTimeElement function to fix SAKE readTimeDate + SAH OTHER Removed function definitions/prototypes for NITRO ctime/gtime, now uses gsi versions +08-03-2006 2.02.00 SN OTHER Completed port for the Nintendo Revolution platform +08-02-2006 2.01.02 SAH RELEASE Releasing to developer site +08-01-2006 2.01.02 SAH FIX Removed PS2-asychronous DNS lookup calls - unsupported in current version + SAH FIX Changed Increment/Decrement functions for Linux - unsupported currently, throws assert +07-24-2006 2.01.01 SAH FIX Removed #ifdef _PS3 for socket calls in gsPlatformSocket.c and gsAvailable.c + SAH FIX Added typecasts to PS3 socket calls in gsPlatformSocket.h + SAH FIX Fixed a variable in gsThreadWin32.c used for gsiExitThread + SAH FIX Added GSI_UNUSED call for unused variable in gsPlatformUtil.c + SAH FIX Added typecast to get rid of NITRO warnings +07-21-2006 2.01.00 SN FEATURE Added initial support for the Nintendo Revolution +07-17-2006 2.00.50 SAH FIX Added gsiExitThread() to explicitly cleanup threads and free resources +07-07-2006 2.00.49 SAH FIX Changed debug statements to make note of asynch vs. synch DNS lookups +06-30-2006 2.00.48 SAH FIX Added _NITRO && _LINUX common debug call. added #ifndef _NITRO for file logging +06-26-2006 2.00.47 SAH FIX Changed timeout value for PS2 Insock socket calls to 3ms. +06-20-2006 2.00.46 SAH FEATURE Added gsLinuxThreads.c (Linux pthreads support) for ghttp asynch DNS + SAH FEATURE Added Linux implementation of gsiResolveHostnameThread +06-09-2006 2.00.45 SAH OTHER threaded asynch DNS code - explicitly set freed vars to NULL +06-02-2006 2.00.44 SAH FIX Added (void *) typecasts to function call in gsMemory.c + SAH FIX Added __cdecl to "main" function in Win32common.c +05-31-2006 2.00.43 SAH RELEASE Releasing to developer site + SAH FIX Added LinuxCommon.c for linux projects +05-30-2006 2.00.42 SAH FIX #include "time.h" for PS3 time support in gsPlatform.h + SAH FIX Added newlines to end of files, couple GSI_UNUSED calls + SAH FIX Added PS3 Threads stub in gsPlatformThreads +05-25-2006 2.00.41 SAH FIX added GSI_UNUSED call to gsAssert to get rid of Nitro warning +05-24-2006 2.00.40 SAH FIX moved GSI_UNUSED call below #endif in win32common.c +05-23-2006 2.00.39 SAH FIX Got rid of month/week arrays in gsXML.c - they are declared in nitroThread +05-22-2006 2.00.38 SAH FIX added GSI_UNUSED call to win32common.c - to get rid of warning +05-19-2006 2.00.37 SAH FIX added #define GS_STATIC_CALLBACK __cdecl if (_WIN32) +05-18-2006 2.00.36 DES RELEASE Limited developer release +05-17-2006 2.00.36 DES FIX Updates to fix DS compiler errors + DES FEATURE Added gmtime and ctime for DS + DES FEATURE Added gsiInterlockedIncrement/Decrement for the DS + DES FIX Changed call to strftime to sprintf +05-17-2006 2.00.35 SAH FIX Got rid of comma in MEMTAG enum causing codewarrior error +05-17-2006 2.00.34 DES FIX gsXmlReadChildAsBase64Binary now correctly handles empty data +05-15-2006 2.00.33 DES FEATURE Added Xml support for reading Base64 binary + DES FEATURE Added B64DecodeLen for getting the unencoded length of a Base64 string +04-28-2006 2.00.32 SAH FIX Got rid of unnecessary #include ../gsoap/stdsoap2.h in gsSoap.c +04-25-2006 2.00.31 SAH RELEASE Releasing to developer site +04-24-2006 2.00.31 SAH FIX Added/fixed some typecasts, manually defined gmtime in gsPlatform.h - DS doesn't support time +04-24-2006 2.00.30 SAH FIX Added prototype definition for _gsi_memalign (codewarrior warning) +04-20-2006 2.00.29 SAH FIX commented out unused variables, changed psp path to /usr/local/devkit +04-20-2006 2.00.28 SAH FIX Added newline to end of gsSHA1.h to get rid of error + SAH FIX Added #include to gsXML.c + SAH FIX Added _PS3 wrapper typecast to socket calls + SAH FIX Changed gsi_64 variables to long long - PS3 now uses 32 bit + SAH FIX added _tstrncpy defines for UNICODE && WIN32 + SAH FIX made a returned local variable static in gsUtilPS3 + SAH FIX Added a (char *) typecast ot gsSocketPS3 +04-18-2006 2.00.27 SAH FIX Added newline to end of gsXML.h to get rid of error +04-13-2006 2.00.26 SAH FIX Added #ifdef UNICODE, #include to platform.h +04-13-2006 2.00.26 SAH FIX Added #ifdef UNICODE headers around functions that call wcslen +04-05-2006 2.00.25 DDW OTHER Replace __asm int 3 with DebugBreak() call in gsAssert for x64 support +03-20-2006 2.00.24 DES FIX Fixed cross-platform string function undefines +03-15-2006 2.00.23 SN FIX Removed old __WINSOCK_2__ in preference to GSI_WINSOCK2 +03-02-2006 2.00.22 DES FEATURE Added encoding of Base64 as a stream + DES FEATURE Added writing Base64Binary and DateTime to XML streams +02-23-2006 2.00.21 DES FEATURE Added reading and writing functionality to the Xml code. +01-27-2006 2.00.20 SN RELEASE Releasing to developer site +01-11-2006 2.00.20 SN FIX Fixed function to return value for insock +01-09-2006 2.00.19 SN FIX Separated code for WIN32 and WIN32 Console in the assert function + Added platform checks around types and function pointers for memory code +10-08-2005 2.00.18 DES FIX Don't assert when freeing NULL, just ignore it. + DES CLEANUP Cleaned up assert code. +12-06-2005 2.00.17 SN FEATURE Temporarily added an Xbox assert function +11-15-2005 2.00.16 DES FIX Updated cross-platform compatibility. +11-14-2005 2.00.15 DES FEATURE Added GSI_DOMAIN_NAME to define "gamespy.com". +11-14-2005 2.00.14 MJ FEATURE Replaced memory management code. + Added stubbed files for cross platform rendering, input, and fileio +11-14-2005 2.00.13 DES CLEANUP Cleanup for Nitro and OSX. +11-11-2005 2.00.12 DES FIX Changes to crypt code for OSX compiler. + DES FIX added GSI_UNUSED for OSX. + BED FIX Fixed length check in gsLargeInt.c +10-12-2005 2.00.11 SN FIX Modified the INSOCK shutdown alias to use internal function +10-12-2005 2.00.10 BED RELEASE Releasing to developer site. +10-12-2005 2.00.09 DES FEATURE Updated to use SOC_ instead of SO_ prefixes for DS. +09-21-2005 2.00.08 DES FEATURE Updated DS support + DES FEATURE Added GS_WIRELESS_DEVICE for PSP and Nitro +08-12-2005 2.00.07 DES FIX Made changes to gsLargeInt.c for DS compatibility. +08-11-2005 2.00.06 BED FEATURE Added gsiCoreTaskThink so tasks could be pumped invidiually. +07-28-2005 2.00.05 SN RELEASE Releasing to developer site +07-28-2005 2.00.05 SN FIX Added an Extern C that was missing +07-27-2005 2.00.05 SN OTHER Removed include file until Xbox threading is implemented. +07-27-2005 2.00.04 SN FIX Fixed backwards IP address problem for Insock +06-23-2005 2.00.03 BED FEATURE Added PSP support. +06-03-2005 2.00.02 SN RELEASE Releasing to developer site. +05-26-2005 2.00.02 BED RELEASE Releasing to developer site. +05-26-2005 2.00.02 BED FIX Added missing extern "C" brace to end of gsMemory.h +05-26-2005 2.00.01 BED RELEASE Releasing to developer site. +05-26-2005 2.00.01 BED FIX Added old header stubs for new common code compatability with old sdks +05-26-2005 2.00.00 BED RELEASE Releasing to developer site. +05-16-2005 2.00.00 BED OTHER Moved common code into /common folder. + BED OTHER Split nonport.c and nonport.h into multiple files. +04-28-2005 1.11.02 SN RELEASE Releasing to developer site. +04-27-2005 1.11.02 DES RELEASE Limited release to Nintendo DS developers. +04-25-2005 1.11.02 DES FIX Nitro updates + DES FEATURE Addition categories and types added to gsiDebug +04-08-2005 1.11.01 SN FIX Fixed SN Systems bug where a socket was shown to be readable even when an error was recorded for it. +04-08-2005 1.11.00 DES FEATURE Changes for XBox support. +04-04-2005 1.10.72 SN RELEASE Releasing to developer site. +04-04-2005 1.10.72 DES CLEANUP Removed unused GS_BIG_ENDIAN define. + DES FEATURE Added gsi_is_false() and gsi_is_true() defines. + DES CLEANUP Removed unused PATHCHAR define. + DES CLEANUP Removed old _NITRO code from SetSockBlocking. +03-31-2005 1.10.71 BED FIX Removed some CodeWarrior strict compile warnings. +03-31-2005 1.10.70 SN FIX Fixed build problem by moving defines that had dependancies +03-14-2005 1.10.69 DES FEATURE Nintendo DS support +02-18-2005 1.10.68 BED FIX Added timeout for gsoap recv so it could recover from a hosed server + BED FIX Explicit __cdecl for hashtable and darray are used on Win32 only + BED FIX Switched gsimemory to track peak memory usage rather than total num allocs. +01-27-2005 1.10.67 DES FIX Added GSI_NO_THREADS for platforms without thread support + DES FIX Fixed custom SN sendto and moved it to nonport +01-04-2005 1.10.66 DDW FIX Added malloc cast in XXTEA encryption code +12-21-2004 1.10.65 SN FIX Added code to register the hostname resolution thread with SN_SYSTEMS +12-07-2004 1.10.64 BED FIX Added explicit __cdecl in darray.c and gsidebug.c +11-18-2004 1.10.63 SN RELEASE Releasing to developer site. +11-18-2004 1.10.63 SN FIX Fixed conversion warnings for XXTEA algorithm +11-17-2004 1.10.62 SN FIX Modified the XXTEA headers and renamed global key size +11-16-2004 1.10.62 BED FEATURE Added Thread/Semaphore common functions + BED FEATURE Added AD sdk to common debugger code. + BED FEATURE Common debug functions and memory manager are now thread safe + BED FEATURE Added core task processor (see core.h) + BED FEATURE Added common gsoap task (see soap.h) +11-16-2004 1.10.61 SN FEATURE Added URL-safe Base64 encoding and XXTEA encrypt and decrypt +09-21-2004 1.10.60 SN FIX Added the directories for MacOS X and Win32 common to goacommon.bat +09-16-2004 1.10.59 SN RELEASE Releasing to developer site. +09-16-2004 1.10.59 SN FIX Modified header define of NOFILE to avoid conflict with MacOS X +09-03-2004 1.10.58 BED CLEANUP Removed misc compiler warnings. +08-27-2004 1.10.57 BED FEATURE Added memory diagnostic functions to memory manager. +08-27-2004 1.10.56 DES CLEANUP Changed UNDER_UNIX to _UNIX, and define it if _LINUX or _MACOSX are defined. + DES OTHER Generate an error if _WIN32, _PS2, or _UNIX are not defined. + DES CLEANUP General Unicode cleanup + DES CLEANUP Fixed minor warnings under OSX + BED FIX Fixed typo in SN Systems version of GOAGetLastError. +08-25-2004 1.10.55 BED FEATURE Added common debug utilities to nonport. (See gsiDebug.h) + BED FEATURE Added memory pool manager to nonport. (See top of nonport.h) +08-24-2004 1.10.54 DES CLEANUP Removed references to MacOS. +08-20-2004 1.10.53 SN FIX Changed the way errors are obtained from the SN Systems network stack. +08-05-2004 1.10.52 SN RELEASE Releasing to developer site. +08-04-2004 1.10.51 SN FIX Fixed a function prototype causing compiler warnings for codewarrior +08-02-2004 1.10.50 SN FEATURE Added support for developers to use winsock 2 +07-13-2004 1.10.49 DES FEATURE Added GSIStartResolvingHostname() for doing async hostname lookups. +07-12-2004 1.10.48 SN FIX Cleared warnings when warnings are treated as errors for gamspy common code. +07-09-2004 1.10.47 SN FIX Updated portions of code to eliminate warnings for PS2 +07-08-2004 1.10.46 SN FIX Commented an include line +07-01-2004 1.10.46 SN FIX Includeded for ps2 +06-29-2004 1.10.45 BED RELEASE Releasing to developer site. +06-28-2004 1.10.45 DES FEATURE Added gsimemalign to the list of memory functions. +06-24-2004 1.10.44 BED FEATURE Util_Rand functions no longer static. + BED FEATURE Added B64 encode and decode from matrix source. +06-22-2004 1.10.43 BED RELEASE Releasing with updated PS2Common code +06-18-2004 1.10.42 BED RELEASE Releasing to developer site. +06-01-2004 1.10.42 BED FEATURE Phase 1 of PS2 Insock integration. (needs further testing) + BED FIX Found case where unsigned long was used on Ps2 instead of gsi_time. +04-27-2004 1.10.41 DES FEATURE Added current_time_hires(), returns time in microseconds. +03-10-2004 1.10.40 BED FEATURE Added some more types to nonport.h + FIX Undefine socket types to workaround SNSys bug. (They plan to patch in march 04) +12-03-2003 1.10.39 BED FEATURE Added "GameSpy Help.chm" to goapackage.bat +11-10-2003 1.10.38 DES RELEASE Releasing to developer site. +11-10-2003 1.10.38 BED FIX Remove misc compiler warnings. +11-07-2003 1.10.37 DES FIX Added linux support for the common integers datatypes. + FIX Added a newline to the bottom of available.h. + FEATURE The available check now uses .available.gamespy.com. +10-29-2003 1.10.36 DES FEATURE Added available.h,.c for doing backend services availability checks. +10-09-2003 1.10.35 BED FEATURE Added gsi_time type for PS2 compatibility +10-08-2003 1.10.34 JED FEATURE Added common integer datatypes +08-25-2003 1.10.33 JED CLEANUP Added some sanity checks in hashtable.c +07-24-2003 1.10.32 DES RELEASE Releasing to developer site. +07-23-2003 1.10.32 DES FIX Moved EENet includes in nonport.h to fix CW PS2 warnings. + DES FEATURE Added memory tracking. Use GSI_MEM_TRACK to enable. +07-22-2203 1.10.31 BED CLEANUP General cleanup to remove CodeWarrior compiler warnings. +07-16-2003 1.10.30 DES CLEANUP Removed support for Cisco NFT for the PS2. + FIX Changed some __mips64 checks to _PS2 checks in nonport.c + BED FIX Changed nonport.c to not use #pragma comment when _PS2 if defined +07-10-2003 1.10.29 BED CLEANUP Added GSI_UNUSED to nonport.h to silence unused variable warnings. +05-09-2003 1.10.28 DES CLEANUP Removed Dreamcast support. + CLEANUP Changed nonport.h to use EENet if no network stack is defined for the PS2. + FIX Metrowerks for Win32 is no longer falsely identified as MacOS. +05-07-2003 1.10.27 DES RELEASE Releasing to developer site. + FIX Rewrote EENet GetMAC to be 2.7.x compatibile. +04-28-2003 1.10.26 DES RELEASE Releasing to developer site. + FIX Changed malloc/free in new EENet getlocalhost to gsimalloc/gsifree. +04-28-2003 1.10.25 DES RELEASE Releasing to developer site. +04-17-2003 1.10.25 DES FIX Rewrote EENet getlocalhost again, to be compatible with the 2.7.0.1 release. +04-15-2003 1.10.24 DES FIX Rewrote the EENet implementation of getlocalhost to build its own HOSTENT. +04-15-2003 1.10.23 JED CLEANUP Corrected DevStudio Level4 warnings for use of FD_SET +04-08-2003 1.10.22 JED FIX converted md5 function parameter type declarations from K&R C to ANSI C +03-27-2003 1.10.21 DES FIX IsPrivateIP no longer flips the source IP's byte order. +03-26-2003 1.10.20 DES RELEASE Releasing to developer site. +03-25-2003 1.10.20 DES FIX The EENet version of getlocalhost() wasn't checking all possible local IPs. +03-24-2003 1.10.19 DES FEATURE Added IsPrivateIP() for checking if an IP is a private network IP. + DES FIX GetTicks() no longer causes a compiler warning. +03-10-2003 1.10.18 DES RELEASE Releasing to developer site. +02-25-2003 1.10.18 DES CLEANUP Added headers to nonport.h for 2.6.1 EENet compatibility. + EENET_260 can be defined for 2.6.0 compatibility. +02-05-2003 1.10.17 DES RELEASE Releasing to developer site. +02-05-2003 1.10.17 DES FEATURE Added CanReceiveOnSocket and CanSendOnSocket as wrappers for select. + Needed because the SN stack for the PS2 has non-standard behavior. +01-23-2003 1.10.16 DES FEATURE Added the ability to just get the gsi*() memory function defines + by defining GSI_MEM_ONLY before including nonport.h. +01-07-2003 1.10.15 DES RELEASE Releasing to developer site. +01-07-2003 1.10.15 DES CLEANUP Removed a comment and a printf that were no longer needed. +01-02-2003 1.10.14 DES CLEANUP Removed the typedef for PSOCKADDR + It doesn't appear to be used anywhere, and was causing compile problems. +12-20-2002 1.10.13 DES FEATURE Implemented new code from SN Systems to get the MAC for the unique ID. +12-19-2002 1.10.12 DES RELEASE Releasing to developer site. +12-19-2002 1.10.12 DES CLEANUP Removed assert.h include from darray.h and hashtable.h. +12-18-2002 1.10.11 DES CLEANUP Put in a stub function fr getting the unique ID when using the SN stack. +12-16-2002 1.10.10 DES FEATURE Defined NOFILE when _PS2 is defined to exclude all file writing code. + FEATURE Defined SOMAXCONN to 5 when not defined. This is used as the backlog + parameter in calls to listen(). 5 is the max for SN Systems. + FEATURE gethostbyaddr() is not supported by SN Systems, defined it to NULL. + CLEANUP Changed GOAGetLastError() to clear the error for SN Systems. + Also removed GOAClearSocketError(), which was only needed for SN Systems. +12-13-2002 1.10.09 DES FEATURE Added EENet specific code for getting the MAC address for the Unique ID. +12-11-2002 1.10.08 DES FEATURE Additional eenet support. + FEATURE Added getlocalhost() for getting the local machine's hostent struct. + FEATURE Added SetSendBufferSize(), GetSendBufferSize(), and GetReceiveBufferSize(). +12-05-2002 1.10.07 DES CLEANUP General cleanup + FEATURE Initial PS2 eenet stack support +11-22-2002 1.10.06 DES RELEASE Releasing to developer site. +11-22-2002 1.10.06 DES FIX Fixed bug with checking the current time on the PS2. +11-20-2002 1.10.05 DES FEATURE Switched to using the MAC address for the Unique ID on the PS2. + CLEANUP Cleaned up to compile without warnings on the PS2. +11-18-2002 1.10.04 JED FIX nonport.c now using ansi registry funcs for WIN32/unicode +11-14-2002 1.10.03 DES FEATURE Added assert.h include to nonport.h. +11-13-2002 1.10.02 DDW FEATURE Changed GOAGetUniqueID to use redefinable function pointer. + Made Cisco the default stack for PS2. +11-13-2002 1.10.01 DES FIX Removed Reference to unsupported non-blocking CISCO stack +09-25-2002 1.10.00 DDW OTHER Changelog started diff --git a/code/gamespy/common/gsAssert.c b/code/gamespy/common/gsAssert.c new file mode 100644 index 00000000..c4c56861 --- /dev/null +++ b/code/gamespy/common/gsAssert.c @@ -0,0 +1,182 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsCommon.h" +#include "gsDebug.h" + + + +// This is the platform specific default assert condition handler +extern void _gsDebugAssert (const char * string); + +static gsDebugAssertCallback gsDebugAssertHandler = _gsDebugAssert; + +// Call this function to override the default assert handler +// New function should render message / log message based on string passed +void gsDebugAssertCallbackSet(gsDebugAssertCallback theCallback) +{ + if (theCallback) + gsDebugAssertHandler = theCallback; + else + gsDebugAssertHandler = _gsDebugAssert; +} + + +// This is the default assert condition handler +void gsDebugAssert (const char *szError,const char *szText, const char *szFile, int line) +{ + char String[256]; + // format into buffer + sprintf(&String[0], szError,szText,szFile,line); + + // call plat specific handler + (*gsDebugAssertHandler)(String); + +} + + +// **************************************************** +// Todo: move to platform specific modules +#ifdef _XBOX + // ErrorMessage: Displays message and goes into an infinite loop + // continues rendering + void _gsDebugAssert (const char *string) + { + //DebugBreak(); + OutputDebugString( string); + exit(0); + } + +#elif defined _WIN32 + #include + #pragma warning(disable: 4127) // disable warnings from "conditional expression is constant" + + // ErrorMessage: Displays message and goes into an infinite loop + // continues rendering + void _gsDebugAssert (const char *string) + { + + //DebugBreak(); + + + #ifdef _CONSOLE //,_MBCS + + printf("%s",string); + while(1) + { + }; + + #else + { + OutputDebugStringA( string); + + #ifndef GS_ASSERT_NO_MESSAGEBOX + { + int rcode = MessageBoxA(NULL,string,"Assert Failed",MB_ABORTRETRYIGNORE|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL); + + if(rcode == IDABORT) + { + exit(0); + } + if(rcode == IDRETRY) + { + DebugBreak(); + return; + } + } + #else + DebugBreak(); + #endif + } + #endif + } + +#elif defined _PSP + // ToDo: + // ErrorMessage: Displays message and goes into an infinite loop + // continues rendering + void _gsDebugAssert (const char *string) + { + printf(string); + + while(1) + { + }; + + } + + +#elif defined _PS2 + + // already included in gsPlatform.h + /* + #include + #include + */ + + // ErrorMessage: Displays message and goes into an infinite loop + // continues rendering + void _gsDebugAssert (const char *string) + { + + scePrintf(string); + while(1);; + } + + + +#elif defined _MACOSX + void _gsDebugAssert (const char *string) + { + printf(string); + + while(1) + { + }; + + } + + +#elif defined _LINUX + void _gsDebugAssert (const char *string) + { + printf(string); + + while(1) + { + }; + + } + + +#elif defined _NITRO + void _gsDebugAssert (const char *string) + { +#if SDK_FINALROM != 1 + OS_TPanic("%s",string); +#else + GSI_UNUSED(string); +#endif + } + +#elif defined(_REVOLUTION) + void _gsDebugAssert(const char *string) + { + OSHalt(string); + + } +#else + // ErrorMessage: Displays message and goes into an infinite loop + // continues rendering + void _gsDebugAssert (const char *string) + { + + printf(string); + while(1);; + + } + +#endif + + + + diff --git a/code/gamespy/common/gsAssert.h b/code/gamespy/common/gsAssert.h new file mode 100644 index 00000000..ee5f5b0e --- /dev/null +++ b/code/gamespy/common/gsAssert.h @@ -0,0 +1,86 @@ +#ifndef __GSIASSERT_H__ +#define __GSIASSERT_H__ + +#if defined(__LANGUAGE_C_PLUS_PLUS)||defined(__cplusplus)||defined(c_plusplus) +extern "C" { +#endif +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Assert for GameSpy SDKs +// +// Usage: +// 1) #define _DEBUG to enable assert. This should be set by compiler configuration. +// +// Todo: +// Allow user to specify IP to send debug output to (remote log for PS2) + + +// GS_ASSERT Use this to trap any programming bugs, such as range checks, invalid parameters +// use at start of each function to check all parameters +// also check all assumptions, ex// assume module is init. +// + +// GS_FAIL() Use instead of GS_ASSERT(0) when reaching an illegal area of code +// ex// the default: in a case statement that should be completely handled. + + +/* + ***The reason for using a GameSpy assert or custom assert*** + + although assert is handled very gracefully on the windows platform, most consoles do very little of real use during assert. + Furthermore, the program counter is lost, along with the callstack sometimes. + By having a custom critical error function, an asm "break" can be set in it, or a debugger break point. a call stack is + immediately available. The error can be drawn onto the screen. And the choice to ignore can also be given, in order + to continue stepping through code and further debug. + +*/ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +extern void gsDebugAssert (const char *format,const char *szError,const char *szFile, int line); +#ifndef _DEBUG + // On non-debug builds, release builds, ignore all of this. + // be carefull never to have function calls within one of these macros, as they will + // be ignored. + // ex// BAD: GS_ASSERT( i== FN()) // FN() will never be called, i will never be set in release builds + + #define GS_ASSERT(x) {}; // ex// GS_ASSERT( result == GS_OK ) + #define GS_ASSERT_STR(x, t) {}; // ex// GS_ASSERT_STR( result == GS_OK ,"GSFunction failed") + #define GS_ASSERT_ALIGN_16(x) {}; + #define GS_FAIL() + #define GS_FAIL_STR(x) + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#else + + #define GS_ASSERT(x) { if(!(x)) { gsDebugAssert("ASSERT on '" #x "' [%s] in %s line:%d\n", "", __FILE__,__LINE__); } }; + #define GS_ASSERT_STR(x,t) { if(!(x)) { gsDebugAssert("ASSERT on '" #x "' [%s] in %s line:%d\n", t, __FILE__,__LINE__); } }; + #define GS_ASSERT_ALIGN_16(x) { if(((U32)(x))%16) { gsDebugAssert("ASSERT on '" #x "' [%s] in %s line:%d\n","16 byte misalign", __FILE__,__LINE__); } }; + #define GS_FAIL() { gsDebugAssert("FAIL [%s] ln %s line:%d\n", "", __FILE__,__LINE__); }; + #define GS_FAIL_STR(t) { gsDebugAssert("FAIL [%s] ln %s line:%d\n", t, __FILE__,__LINE__); }; + + + +#endif // GSI_COMMON_DEBUG + + +// This is the default assert condition handler +typedef void (*gsDebugAssertCallback) (const char *string); + +// Call this function to override the default assert handler +// New function should render message / log message based on string passed +// calling this with NULL is restores the default setting. +void gsDebugAssertCallbackSet(gsDebugAssertCallback theCallback); + + +// This is like an assert, but test at compile, not run time. +// ex use STATIC_CHECK(DIM(array) == enumArrayCount) +#define GS_STATIC_CHECK(expr, msg) { CompileTimeError<((expr) != 0)> ERROR_##msg; (void)ERROR_##msg; } + + +#if defined(__LANGUAGE_C_PLUS_PLUS)||defined(__cplusplus)||defined(c_plusplus) +} +#endif + +#endif // __GSIDEBUG_H__ diff --git a/code/gamespy/common/gsAvailable.c b/code/gamespy/common/gsAvailable.c new file mode 100644 index 00000000..00d5a526 --- /dev/null +++ b/code/gamespy/common/gsAvailable.c @@ -0,0 +1,215 @@ +#include "gsCommon.h" +#include "gsAvailable.h" + +#define PACKET_TYPE 0x09 +#define MASTER_PORT 27900 +#define MAX_RETRIES 1 +#define TIMEOUT_TIME 2000 + +// this is the global var that the SDKs check +// to see if they should communicate with the backend +GSIACResult __GSIACResult = GSIACWaiting; + +// this makes the gamename available to all of the SDKs +char __GSIACGamename[64] = {0}; + +// this allows devs to do their own hostname resolution +char GSIACHostname[64] = {0}; + +// used to keep state during the check +static struct +{ + SOCKET sock; + SOCKADDR_IN address; + char packet[64]; + int packetLen; + gsi_time sendTime; + int retryCount; +} AC; + +static int get_sockaddrin(const char * hostname, int port, SOCKADDR_IN * saddr) +{ + GS_ASSERT(hostname) + GS_ASSERT(saddr) + + saddr->sin_family = AF_INET; + saddr->sin_port = htons((unsigned short)port); + saddr->sin_addr.s_addr = inet_addr(hostname); + + if(saddr->sin_addr.s_addr == INADDR_NONE) + { + HOSTENT * host = gethostbyname(hostname); + if(!host) + return 0; + saddr->sin_addr.s_addr = *(unsigned int *)host->h_addr_list[0]; + } + + return 1; +} + +static void SendPacket(void) +{ + sendto(AC.sock, AC.packet, AC.packetLen, 0, (SOCKADDR *)&AC.address, sizeof(AC.address)); + AC.sendTime = current_time(); +} + +void GSIStartAvailableCheckA(const char * gamename) +{ + char hostname[64]; + int override; + int rcode; + int len; + + GS_ASSERT(gamename) + + // store the gamename + strcpy(__GSIACGamename, gamename); + + // clear the sock + AC.sock = INVALID_SOCKET; + + // startup sockets + SocketStartUp(); + + // setup the hostname + override = GSIACHostname[0]; + if(!override) + sprintf(hostname, "%s.available." GSI_DOMAIN_NAME, gamename); + + // get the master address + rcode = get_sockaddrin(override?GSIACHostname:hostname, MASTER_PORT, &AC.address); + if(!rcode) + return; + + // create the socket + AC.sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if(AC.sock == INVALID_SOCKET) + return; + + // setup our packet + AC.packet[0] = PACKET_TYPE; + len = (int)strlen(gamename); + memcpy(AC.packet + 5, gamename, (size_t)len + 1); + AC.packetLen = (len + 6); + + // send it + SendPacket(); + + // no retries yet + AC.retryCount = 0; +} +#ifdef GSI_UNICODE +void GSIStartAvailableCheckW(const unsigned short * gamename) +{ + char gamename_A[32]; + GS_ASSERT(gamename) + + UCS2ToAsciiString(gamename, gamename_A); + + GSIStartAvailableCheckA(gamename_A); +} +#endif + +static int HandlePacket(char * packet, int len, SOCKADDR_IN * address, int * disabledservices) +{ + int bitfield; + + // check the length + if(len < 7) + return 1; + + // check the IP + if(memcmp(&address->sin_addr, &AC.address.sin_addr, sizeof(IN_ADDR)) != 0) + return 1; + + // check the port + if(address->sin_port != AC.address.sin_port) + return 1; + + // check the header + if(memcmp(packet, "\xFE\xFD\x09", 3) != 0) + return 1; + + // read out the bitfield + // read byte-by-byte to avoid alignment issues + bitfield = (int)((packet[3] << 24) & 0xFF000000); + bitfield |= ((packet[4] << 16) & 0x00FF0000); + bitfield |= ((packet[5] << 8) & 0x0000FF00); + bitfield |= (packet[6] & 0x000000FF); + + // set it + *disabledservices = bitfield; + + return 0; +} + +GSIACResult GSIAvailableCheckThink(void) +{ + char packet[64]; + SOCKADDR_IN address; + int len = sizeof(address); + int rcode; + int disabledservices; + + // if we don't have a sock, possibly because of an initialization error, default to available + if(AC.sock == INVALID_SOCKET) + { + __GSIACResult = GSIACAvailable; + return __GSIACResult; + } + + // did we get a response? + if(CanReceiveOnSocket(AC.sock)) + { + // read it from the socket + rcode = (int)recvfrom(AC.sock, packet, (int)sizeof(packet), 0, (SOCKADDR *)&address, &len); + + // verify the packet + rcode = HandlePacket(packet, rcode, &address, &disabledservices); + if(rcode == 0) + { + // we got a valid response, clean up + closesocket(AC.sock); + + // set the result based on the bit flags + if(disabledservices & 1) + __GSIACResult = GSIACUnavailable; + else if(disabledservices & 2) + __GSIACResult = GSIACTemporarilyUnavailable; + else + __GSIACResult = GSIACAvailable; + + // return it + return __GSIACResult; + } + } + + // check for a timeout + if(current_time() > (AC.sendTime + TIMEOUT_TIME)) + { + // check for too many retries + if(AC.retryCount == MAX_RETRIES) + { + // default to available + closesocket(AC.sock); + __GSIACResult = GSIACAvailable; + return __GSIACResult; + } + + // send a retry + SendPacket(); + AC.retryCount++; + } + + return GSIACWaiting; +} + +void GSICancelAvailableCheck(void) +{ + if(AC.sock != INVALID_SOCKET) + { + closesocket(AC.sock); + AC.sock = INVALID_SOCKET; + __GSIACResult = GSIACWaiting; + } +} diff --git a/code/gamespy/common/gsAvailable.h b/code/gamespy/common/gsAvailable.h new file mode 100644 index 00000000..73bddfb9 --- /dev/null +++ b/code/gamespy/common/gsAvailable.h @@ -0,0 +1,54 @@ +#ifndef _AVAILABLE_H_ +#define _AVAILABLE_H_ + +#include "gsStringUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef GSI_UNICODE +#define GSIStartAvailableCheck GSIStartAvailableCheckA +#else +#define GSIStartAvailableCheck GSIStartAvailableCheckW +#endif + +// the available check contacts a backend server at ".available.gamespy.com" +// an app can resolve the hostname itself and store the IP here before starting the check +extern char GSIACHostname[64]; + +// these are possible return types for GSIAvailableCheckThink +typedef enum +{ + GSIACWaiting, // still waiting for a response from the backend + GSIACAvailable, // the game's backend services are available + GSIACUnavailable, // the game's backend services are unavailable + GSIACTemporarilyUnavailable // the game's backend services are temporarily unavailable +} GSIACResult; + +// start an available check for a particular game +// return 0 if no error starting up, non-zero if there's an error +void GSIStartAvailableCheck(const gsi_char * gamename); + +// let the available check think +// continue to call this while it returns GSIACWaiting +// if it returns GSIACAvailable, use the GameSpy SDKs as normal +// if it returns GSIACUnavailable or GSIACTemporarilyUnavailable, do NOT +// continue to use the GameSpy SDKs. the backend services are not available +// for the game. in this case, you can show the user a +// message based on the particular result. +GSIACResult GSIAvailableCheckThink(void); + +// this should only be used if the availability check needs to be aborted +// for example, if the player leaves the game's multiplayer area before the check completes +void GSICancelAvailableCheck(void); + +// internal use only +extern GSIACResult __GSIACResult; +extern char __GSIACGamename[64]; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/common/gsCommon.h b/code/gamespy/common/gsCommon.h new file mode 100644 index 00000000..40e840b2 --- /dev/null +++ b/code/gamespy/common/gsCommon.h @@ -0,0 +1,48 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __GSCOMMON_H__ +#define __GSCOMMON_H__ + +// Common is more like "all" + +// Build settings (set by developer project) +//#define GS_MEM_MANAGED // use GameSpy memory manager for SDK allocations +//#define GS_COMMON_DEBUG // use GameSpy debug utilities for SDK debug output +//#define GS_WINSOCK2 // use winsock2 +//#define GS_UNICODE // Use widechar (UCS2) SDK interface. +//#define GS_NO_FILE // disable file storage (on by default for PS2/DS) +//#define GS_NO_THREAD // no multi-thread support +//#define GS_PEER // MUST be defined if you are using Peer SDK + + +#ifdef GS_PEER + #define UNIQUEID // enable unique id support +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsPlatform.h" +#include "gsPlatformSocket.h" +#include "gsPlatformThread.h" +#include "gsPlatformUtil.h" + +// platform independent +#include "gsMemory.h" +#include "gsDebug.h" +#include "gsAssert.h" +#include "gsStringUtil.h" + +//#include "md5.h" +//#include "darray.h" +//#include "hashtable.h" + + +#define GSI_MIN(a,b) (((a) < (b)?(a):(b))) +#define GSI_MAX(a,b) (((a) > (b)?(a):(b))) +#define GSI_LIMIT(x, minx, maxx) (((x) < (minx)? (minx) : ((x) > (maxx)? (maxx) : (x)))) +#define GSI_WRAP(x, minx, maxx) (((x) < (minx)? (maxx-1 ) : ((x) >= (maxx)? (minx) : (x)))) +#define GSI_DIM( x ) ( sizeof( x ) / sizeof((x)[ 0 ])) + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __GSCOMMON_H__ diff --git a/code/gamespy/common/gsCore.c b/code/gamespy/common/gsCore.c new file mode 100644 index 00000000..d23f8f19 --- /dev/null +++ b/code/gamespy/common/gsCore.c @@ -0,0 +1,446 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Core task/callback manager +#include "gsPlatform.h" +#include "gsPlatformThread.h" + +#include "gsCommon.h" +#include "gsCore.h" +#include "gsAssert.h" +#include "../ghttp/ghttp.h" + + + + +// This defines how long the core will wait if there is a thread synchronization +// problem when initializing or shutting down the core. +#define GSI_CORE_INIT_YIELD_MS 100 +#define GSI_CORE_SHUTDOWN_YIELD_MS 50 + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static GSCoreMgr* gsiGetStaticCore() +{ + static GSCoreMgr gStaticCore; + return &gStaticCore; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// This is registered with the ANSI atexit() function +// - don't do anything that might fail +// - don't do anything that won't complete instantly +// - don't do anything that requires other objects/resources to exist +static void gsiCoreAtExitShutdown(void) +{ + // delete queue critical section + GSCoreMgr * aCore = gsiGetStaticCore(); + gsiDeleteCriticalSection(&aCore->mQueueCrit); + GSI_UNUSED(aCore); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Increment core ref count, initialize the core if necessary +// - WARNING: This code is a bit tricky do to multithread issues +void gsCoreInitialize() +{ + GSCoreMgr* aCore = gsiGetStaticCore(); + + // Is someone else shutting down the core? + while(gsi_is_true(aCore->mIsShuttingDown)) + msleep(GSI_CORE_INIT_YIELD_MS); // yield to other thread + + // If we're the first reference, initialize the core + if (gsiInterlockedIncrement(&aCore->mRefCount) == 1) + { + // Are we the first ever? + if (gsi_is_false(aCore->mIsStaticInitComplete)) + { + // perform one-time initialization of core critical section + gsiInitializeCriticalSection(&aCore->mQueueCrit); + + // register function to destroy critical section at program termination + #ifndef _MANAGED + atexit(gsiCoreAtExitShutdown); + #endif + + // one time init completed + aCore->mIsStaticInitComplete = gsi_true; + } + + // take the critical section to begin initialization + // this is necessary in case another thread began shutdown before we incremented ref count + gsiEnterCriticalSection(&aCore->mQueueCrit); + gsiLeaveCriticalSection(&aCore->mQueueCrit); + + // wait here if another thread is concurrently shutting down the core + // we may need to wait a few times if the shutdown does not complete immediately + while(gsi_is_true(aCore->mIsShuttingDown)) + msleep(GSI_CORE_INIT_YIELD_MS); + + // Setup the task array + #ifdef GSICORE_DYNAMIC_TASK_LIST + aCore->mTaskArray = ArrayNew(sizeof(GSTask*), 10, NULL); + GS_ASSERT(aCore->mTaskArray); + #else + memset(aCore->mTaskArray, 0, sizeof(aCore->mTaskArray)); + #endif + + // Init http sdk (ghttp is ref counted) + ghttpStartup(); + + // release other threads that may have blocked during init + // - this must be the last thing done at end of init + aCore->mIsInitialized = gsi_true; + } + else + { + // Core is already initialized -OR- another thread will initialize the core + + // make sure critical section has been initialized + while(gsi_is_false(aCore->mIsStaticInitComplete)) + msleep(GSI_CORE_INIT_YIELD_MS); + + // take the critical section + // this is necessary in case another thread began shutdown before we incremented ref count + gsiEnterCriticalSection(&aCore->mQueueCrit); + gsiLeaveCriticalSection(&aCore->mQueueCrit); + + // wait for other thread to initial core + while(gsi_is_false(aCore->mIsInitialized)) + msleep(GSI_CORE_INIT_YIELD_MS); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void gsiCoreTaskDispatchCallback(GSTask *theTask, GSTaskResult theResult) +{ + if (theTask->mIsCallbackPending) + { + theTask->mIsCallbackPending = 0; + if (theTask->mCallbackFunc) + (theTask->mCallbackFunc)(theTask->mTaskData, theResult); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Return values: +// GSTaskResult_InProgress - Keep calling gsCoreTaskThink +// GSTaskResult_Finished - Task memory freed; task object is now invalid +GSTaskResult gsCoreTaskThink(GSTask* theTask) +{ + GSCoreMgr* aCore = gsiGetStaticCore(); + GSTaskResult aResult = GSTaskResult_None; + + if (theTask == NULL) + return GSTaskResult_Finished; + + // If the task is running let it think (it may be cancelled and still running) + if (theTask->mIsRunning && theTask->mThinkFunc) + aResult = (theTask->mThinkFunc)(theTask->mTaskData); + + // Check for time out + if ((!theTask->mIsCanceled) && (aResult == GSTaskResult_InProgress)) + { + if ((theTask->mTimeout != 0) && (current_time() - theTask->mStartTime > theTask->mTimeout)) + { + // Cancel the task... + gsiCoreCancelTask(theTask); + + // ...but trigger callback immediately with "Timed Out" + gsiCoreTaskDispatchCallback(theTask, GSTaskResult_TimedOut); + } + //else + // continue processing it + } + else if (aResult != GSTaskResult_InProgress) + { + // Note: This section may be triggered multiple times if the cleanup + // function fails. (possibly due to lack of memory) + int i=0; + gsi_bool removeTask = gsi_true; + + // Call the callback if we haven't already + if (theTask->mIsRunning) + { + gsiCoreTaskDispatchCallback(theTask, aResult); + theTask->mIsRunning = 0; + } + + // Call Cleanup hook and remove task + if (theTask->mCleanupFunc) + removeTask = (theTask->mCleanupFunc)(theTask->mTaskData); + + // Remove the task + if (gsi_is_true(removeTask)) + { + gsiEnterCriticalSection(&aCore->mQueueCrit); + #ifdef GSICORE_DYNAMIC_TASK_LIST + { + int len = ArrayLength(aCore->mTaskArray); + for (i=0; i < len; i++) + { + if(*(GSTask**)ArrayNth(aCore->mTaskArray, i) == theTask) + { + ArrayRemoveAt(aCore->mTaskArray, i); + gsifree(theTask); + break; + } + } + } + #else + for (i=0; i < GSICORE_MAXTASKS; i++) + { + if (aCore->mTaskArray[i] == theTask) + { + aCore->mTaskArray[i] = NULL; + gsifree(theTask); + break; + } + } + #endif + gsiLeaveCriticalSection(&aCore->mQueueCrit); + return GSTaskResult_Finished; + } + } + + // Note: This function should always return InProgress until + // the task has been removed from the TaskArray. + // The developer may have already received a completed callback + // while this continue to return InProgress meaning "still needs to be pumped" + return GSTaskResult_InProgress; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Optional maximum processing time +// - Pass in 0 to process each task once +void gsCoreThink(gsi_time theMS) +{ + GSCoreMgr* aCore = gsiGetStaticCore(); + int i=0; + gsi_time aStartTime = 0; + gsi_i32 allTasksAreDead = 1; + + if (gsi_is_false(aCore->mIsInitialized)) + return; + + // enter queue critical section + gsiEnterCriticalSection(&aCore->mQueueCrit); + + // start timing + aStartTime = current_time(); + + // process all tasks in the queue, dispatch callbacks + // cancelled tasks continue processing until the cancel is acknowledge by the task + #ifdef GSICORE_DYNAMIC_TASK_LIST + { + int len = ArrayLength(aCore->mTaskArray); + if(len > 0) + allTasksAreDead = 0; + for(i=(len-1); i>=0; i--) + { + GSTask* task = *(GSTask**)ArrayNth(aCore->mTaskArray, i); + if(gsi_is_true(task->mAutoThink)) + gsCoreTaskThink(task); + if (theMS != 0 && (current_time()-aStartTime > theMS)) + break; + } + } + #else + for (i=0; imTaskArray[i] != NULL) + { + allTasksAreDead = 0; + + if (aCore->mTaskArray[i]->mAutoThink == gsi_true) + gsCoreTaskThink(aCore->mTaskArray[i]); + } + // Enough time to process another? (if not, break) + if (theMS != 0 && (current_time()-aStartTime > theMS)) + break; + } + #endif + + // shutting down? + if (aCore->mIsShuttingDown && allTasksAreDead) + { + ghttpCleanup(); + +#ifdef GSICORE_DYNAMIC_TASK_LIST + if(aCore->mTaskArray) + { + ArrayFree(aCore->mTaskArray); + aCore->mTaskArray = NULL; + } +#endif + + aCore->mIsShuttingDown = 0; + } + + // leave queue critical section + gsiLeaveCriticalSection(&aCore->mQueueCrit); + + GSI_UNUSED(theMS); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsCoreShutdown() +{ + GSCoreMgr* aCore = gsiGetStaticCore(); + int i=0; + + // If not initialized, just bail + if (gsi_is_false(aCore->mIsInitialized)) + return; + + // Take the critical section to prevent anyone from re-initializing while + // we decide if we need to shutdown + gsiEnterCriticalSection(&aCore->mQueueCrit); + + // If there are other references, just return + if (gsiInterlockedDecrement(&aCore->mRefCount)>0) + { + gsiLeaveCriticalSection(&aCore->mQueueCrit); + return; + } + else + { + // we released the final reference, begin shutdown + // no other thread will begin using the core until + // mIsShuttingDown has been set back to false + aCore->mIsShuttingDown = gsi_true; + + // Cancel all tasks + #ifdef GSICORE_DYNAMIC_TASK_LIST + { + int len = ArrayLength(aCore->mTaskArray); + for(i=0; imTaskArray, i)); + } + } + #else + for (i=0; imTaskArray[i] != NULL) + { + gsiCoreCancelTask(aCore->mTaskArray[i]); + } + } + #endif + gsiLeaveCriticalSection(&aCore->mQueueCrit); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +GSCoreValue gsCoreIsShutdown() +{ + GSCoreMgr* aCore = gsiGetStaticCore(); + + if (gsi_is_true(aCore->mIsShuttingDown)) + return GSCore_SHUTDOWN_PENDING; + if (aCore->mRefCount == 0) + return GSCore_SHUTDOWN_COMPLETE; + + // The core isn't shutting down, and ref count > 0, + // therefore the core is in use + return GSCore_IN_USE; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Adds a GSCoreTask to the execution array +// - Tasks may come from multiple threads +void gsiCoreExecuteTask(GSTask* theTask, gsi_time theTimeoutMs) +{ + GSCoreMgr* aCore = gsiGetStaticCore(); + + // Bail, if the task has already started + GS_ASSERT(!theTask->mIsRunning); + + // Mark it as started and running + theTask->mIsCallbackPending = 1; + theTask->mIsStarted = 1; + theTask->mIsRunning = 1; + theTask->mTimeout = theTimeoutMs; + theTask->mStartTime = current_time(); + + // Execute the task + if (theTask->mExecuteFunc) + (theTask->mExecuteFunc)(theTask->mTaskData); + + gsiEnterCriticalSection(&aCore->mQueueCrit); + // add it to the process list + #ifdef GSICORE_DYNAMIC_TASK_LIST + ArrayAppend(aCore->mTaskArray, &theTask); + #else + { + int anInsertPos = -1; + int i=0; + for (i=0; imTaskArray[i] == NULL) + { + anInsertPos = i; + break; + } + } + GS_ASSERT(anInsertPos != -1); // make sure it got in + aCore->mTaskArray[anInsertPos] = theTask; + } + #endif + gsiLeaveCriticalSection(&aCore->mQueueCrit); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// cancelling a task is an *async request* +// A task that doesn't support cancelling, such as a blocking socket operation, +// may complete normally even though it was cancelled. +void gsiCoreCancelTask(GSTask* theTask) +{ + GSCoreMgr* aCore = gsiGetStaticCore(); + + // Enter critical secction here so the developer + // may cancel a task from any thread. (e.g. The task thread has blocked) + gsiEnterCriticalSection(&aCore->mQueueCrit); + if (theTask->mIsRunning && !theTask->mIsCanceled) + { + theTask->mIsCanceled = 1; + if (theTask->mCancelFunc) + (theTask->mCancelFunc)(theTask->mTaskData); + } + gsiLeaveCriticalSection(&aCore->mQueueCrit); + GSI_UNUSED(aCore); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +GSTask* gsiCoreCreateTask() +{ + GSTask* aTask = (GSTask*)gsimalloc(sizeof(GSTask)); + if (aTask == NULL) + return NULL; + + memset(aTask, 0, sizeof(GSTask)); + aTask->mAutoThink = gsi_true; + return aTask; +} diff --git a/code/gamespy/common/gsCore.h b/code/gamespy/common/gsCore.h new file mode 100644 index 00000000..5d092b4e --- /dev/null +++ b/code/gamespy/common/gsCore.h @@ -0,0 +1,128 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __CORE_H__ +#define __CORE_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Core task/callback manager +#include "gsCommon.h" +#include "../darray.h" + +#if defined(__cplusplus) +extern "C" +{ +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define GSICORE_DYNAMIC_TASK_LIST +#define GSICORE_MAXTASKS 40 + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef enum +{ + GSCore_IN_USE, + GSCore_SHUTDOWN_PENDING, + GSCore_SHUTDOWN_COMPLETE +} GSCoreValue; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef enum +{ + GSTaskResult_None, + GSTaskResult_InProgress, + GSTaskResult_Canceled, + GSTaskResult_TimedOut, + GSTaskResult_Finished +} GSTaskResult; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// delegates (optional, may be NULL) +typedef void(*GSTaskExecuteFunc) (void* theTaskData); +typedef void(*GSTaskCallbackFunc)(void* theTaskData, GSTaskResult theResult); +typedef void(*GSTaskCancelFunc) (void* theTaskData); +typedef gsi_bool(*GSTaskCleanupFunc) (void* theTaskData); // post run cleanup +typedef GSTaskResult(*GSTaskThinkFunc)(void* theTaskData); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// "Private" struct for dispatching tasks. Once tasks have been put in the queue +// they should only be modified from the think thread. +// - When creating a task, you should set only the task data and delegates +typedef struct +{ + int mId; + gsi_time mTimeout; + gsi_time mStartTime; + gsi_bool mAutoThink; + + // These are not exclusive states (use bit flags?) + gsi_i32 mIsStarted; + gsi_i32 mIsRunning; + gsi_i32 mIsCanceled; + gsi_i32 mIsCallbackPending; // does the task require a callback? + + // delegates + void* mTaskData; + GSTaskExecuteFunc mExecuteFunc; + GSTaskCallbackFunc mCallbackFunc; + GSTaskCancelFunc mCancelFunc; + GSTaskCleanupFunc mCleanupFunc; + GSTaskThinkFunc mThinkFunc; +} GSTask; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct +{ + gsi_u32 mRefCount; + + gsi_bool volatile mIsStaticInitComplete; // once per application init + gsi_bool volatile mIsInitialized; // gsi_true when ready to use + gsi_bool volatile mIsShuttingDown; // gsi_true when shutting down + + GSICriticalSection mQueueCrit; + #ifdef GSICORE_DYNAMIC_TASK_LIST + DArray mTaskArray; + #else + GSTask* mTaskArray[GSICORE_MAXTASKS]; + #endif + +} GSCoreMgr; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsCoreInitialize (void); +void gsCoreThink (gsi_time theMS); +void gsCoreShutdown (void); +GSCoreValue gsCoreIsShutdown(void); + +GSTaskResult gsCoreTaskThink(GSTask* theTask); +void gsiCoreExecuteTask (GSTask* theTask, gsi_time theTimeoutMs); +void gsiCoreCancelTask (GSTask* theTask); + +GSTask* gsiCoreCreateTask(void); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __CORE_H__ diff --git a/code/gamespy/common/gsCrypt.c b/code/gamespy/common/gsCrypt.c new file mode 100644 index 00000000..0ac8de8c --- /dev/null +++ b/code/gamespy/common/gsCrypt.c @@ -0,0 +1,509 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsCrypt.h" +#include "gsLargeInt.h" +#include "gsSHA1.h" + +// **Please refer to gsCrypt.h for public interface functions** + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef GS_CRYPT_RSA_ES_OAEP + static gsi_i32 gsCryptRSAOAEPEncryptBuffer(const gsCryptRSAKey *publicKey, const unsigned char *plainText, gsi_u32 len, unsigned char buffer[GS_CRYPT_RSA_BYTE_SIZE]); + static gsi_i32 gsCryptRSAOAEPDecryptBuffer(const gsCryptRSAKey *privateKey, const unsigned char cipherText[GS_CRYPT_RSA_BYTE_SIZE], unsigned char *plainText, gsi_u32 *lenout); + + static void gsiCryptRSAGenerateSeed(unsigned char *buffer, gsi_u32 len); + static gsi_bool gsiCryptRSAMaskData(unsigned char *data, gsi_u32 len, const unsigned char *maskSeed, gsi_u32 maskLen); +#else + static gsi_i32 gsCryptRSAPKCS1EncryptBuffer(const gsCryptRSAKey *publicKey, const unsigned char *plainText, gsi_u32 len, unsigned char buffer[GS_CRYPT_RSA_BYTE_SIZE]); + static gsi_i32 gsCryptRSAPKCS1DecryptBuffer(const gsCryptRSAKey *privateKey, const unsigned char ciperText[GS_CRYPT_RSA_BYTE_SIZE], unsigned char *plainTextOut, gsi_u32 *lenOut); + + static void gsiCryptRSAGeneratePad(unsigned char *buffer, gsi_u32 len); +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef GS_CRYPT_RSA_ES_OAEP + // generate a random seed for OAEP + void gsiCryptRSAGenerateSeed(unsigned char *buffer, gsi_u32 len) + { + unsigned int i=0; + + Util_RandSeed(current_time()); + for (i=0; i < len; i++) + { + //buffer[i] = 0x0c; + buffer[i] = (unsigned char)(Util_RandInt(0x00, 0xFF)+1); + GS_ASSERT(buffer[i] != 0x00); + } + } + + // PKCS#1 v2.1, B.2.1 MGF1, mask generation function + // modification: generates mask and applies in place + gsi_bool gsiCryptRSAMaskData(unsigned char *data, gsi_u32 dataLen, const unsigned char* seed, gsi_u32 seedLen) + { + int i=0; + int k=0; + + // The datablock may be used as the seed + unsigned char hashValue[GS_CRYPT_HASHSIZE]; // in integer form, NOT HEXSTRING + + // seed should never be larger than the data block size (but it may be less) + if (seedLen > GS_CRYPT_RSA_DATABLOCKSIZE) + return gsi_false; + + for (i=0; (gsi_u32)i= dataLen) + return gsi_true; + + data[i+k] ^= (gsi_u8)hashValue[k]; + } + } + return gsi_true; + } +#else + // generate a random pad for PKCS1 + // [0x01 - 0xFF] + void gsiCryptRSAGeneratePad(unsigned char *buffer, gsi_u32 len) + { + unsigned int i=0; + + Util_RandSeed(current_time()); + for (i=0; i < len; i++) + { + #if defined(GS_CRYPT_NO_RANDOM) + #pragma message("GS_CRYPT_NO_RANDOM defined, SSL is NOT SECURE!!!!\r\n") + buffer[i] = 0x0c; + #else + buffer[i] = (unsigned char)(Util_RandInt(0x00, 0xFF)+1); + #endif + GS_ASSERT(buffer[i] != 0x00); + } + } +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// RSA Packet = encrypt(lhash, pad, 0x01, plainText) +// lhash is constant, since labels are not supported +// pad is variable length, depending on plainText length +gsi_i32 gsCryptRSAEncryptBuffer(const gsCryptRSAKey *publicKey, const unsigned char *plainText, gsi_u32 len, unsigned char buffer[GS_CRYPT_RSA_BYTE_SIZE]) +{ + #ifdef GS_CRYPT_RSA_ES_OAEP + return gsCryptRSAOAEPEncryptBuffer(publicKey, plainText, len, buffer); + #else + return gsCryptRSAPKCS1EncryptBuffer(publicKey, plainText, len, buffer); + #endif +} + +#ifdef GS_CRYPT_RSA_ES_OAEP + gsi_i32 gsCryptRSAOAEPEncryptBuffer(const gsCryptRSAKey *publicKey, const unsigned char *plainText, gsi_u32 len, unsigned char buffer[GS_CRYPT_RSA_BYTE_SIZE]) + { + gsLargeInt_t lintRSAPacket; + gsCryptRSAOAEPPacket* packet = (gsCryptRSAOAEPPacket*)lintRSAPacket.mData; + + #if (GS_CRYPT_HASHSIZE==GS_CRYPT_MD5_HASHSIZE) + const gsi_u8 lhash[GS_CRYPT_HASHSIZE] = {0xd4,0x1d,0xd4,0x1d,0x8c,0xd9,0x8f,0x00,0xb2,0x04,0xe9,0x80,0x09,0x98,0xec,0xf8,0x42,0x7e}; // hash of "" + #else + const gsi_u8 lhash[GS_CRYPT_HASHSIZE] = {0xda,0x39,0xa3,0xee,0x5e,0x6b,0x4b,0x0d,0x32,0x55,0xbf,0xef,0x95,0x60,0x18,0x90,0xaf,0xd8,0x07,0x09}; // hash of "" + #endif + const unsigned int maxPlainTextLen = GS_CRYPT_RSA_BYTE_SIZE-2*GS_CRYPT_HASHSIZE-2; + const unsigned int padSize = maxPlainTextLen-len; + + // The steps below are taken from PKCS#1, section 7.1.1 "Encryption Operation" + // 1. check length + if (len > maxPlainTextLen) + return -1; + + // 2. EME-OAEP encoding (pad & pad format) + // a. precalculated above (const lhash) + + // b. create pad + // c. concatenate hash+pad+0x01+plainText + memcpy(packet->maskedData, lhash, GS_CRYPT_HASHSIZE); + memset(&packet->maskedData[GS_CRYPT_HASHSIZE], 0, padSize); // pad with zero bytes + packet->maskedData[GS_CRYPT_HASHSIZE+padSize] = 0x01; // RSA encoding format ID (EME-OAEP) + memcpy(&packet->maskedData[GS_CRYPT_HASHSIZE+padSize+1], plainText, len); + + // d. generate random seed (seed isn't masked until h.) + gsiCryptRSAGenerateSeed(packet->maskedSeed, GS_CRYPT_HASHSIZE); + + // e. use (still unmasked) seed to generate a mask for the datablock + // f. apply it with xor + gsiCryptRSAMaskData(packet->maskedData, GS_CRYPT_RSA_DATABLOCKSIZE, packet->maskedSeed, GS_CRYPT_HASHSIZE); + + // g. use the masked datablock to generate a mask for the seed + // h. apply it with xor + gsiCryptRSAMaskData(packet->maskedSeed, GS_CRYPT_HASHSIZE, packet->maskedData, GS_CRYPT_RSA_DATABLOCKSIZE); + + // i. set first byte to 0x00 + packet->headerByte = 0x00; + + // 3. Encryptitize + /* + lintRSAPacket.mLength = GS_CRYPT_RSA_BYTE_SIZE/sizeof(gsi_u32); + gsLargeIntReverseBytes(&lintRSAPacket); + gsLargeIntPowerMod(&lintRSAPacket, &publicKey->exponent, &publicKey->modulus, &lintRSAPacket); + gsLargeIntReverseBytes(&lintRSAPacket); + + // 4. return cipher text + memcpy(buffer, lintRSAPacket.mData, GS_CRYPT_RSA_BYTE_SIZE); + */ + GS_ASSERT(0); // Section above needs revision due to byte order issues + + return 0; + } +#else + gsi_i32 gsCryptRSAPKCS1EncryptBuffer(const gsCryptRSAKey *publicKey, const unsigned char *plainText, gsi_u32 len, unsigned char buffer[GS_CRYPT_RSA_BYTE_SIZE]) + { + gsi_u8 buf[GS_CRYPT_RSA_BYTE_SIZE]; + gsCryptRSAPKCS1Packet* packet = (gsCryptRSAPKCS1Packet*)buf; + gsLargeInt_t lintRSAPacket; + + if (len > (GS_CRYPT_RSA_BYTE_SIZE-11)) // 2 byte header, 8 byte pad minimum, 1 byte separator + return -1; + + // form the packet + packet->headerByte[0] = 0x00; + packet->headerByte[1] = 0x02; + + gsiCryptRSAGeneratePad(packet->data, GS_CRYPT_RSA_BYTE_SIZE-len-3); + packet->data[GS_CRYPT_RSA_BYTE_SIZE-len-3] = 0x00; // separator + memcpy(&packet->data[GS_CRYPT_RSA_BYTE_SIZE-len-3+1], plainText, len); + + if (gsi_is_false(gsLargeIntSetFromMemoryStream(&lintRSAPacket, (const gsi_u8*)buf, GS_CRYPT_RSA_BYTE_SIZE)) || + gsi_is_false(gsLargeIntPowerMod(&lintRSAPacket, &publicKey->exponent, &publicKey->modulus, &lintRSAPacket)) || + gsi_is_false(gsLargeIntWriteToMemoryStream(&lintRSAPacket, buffer)) ) + { + return -1; + } + + return 0; + } +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_i32 gsCryptRSADecryptBuffer(const gsCryptRSAKey *privateKey, const unsigned char cipherText[GS_CRYPT_RSA_BYTE_SIZE], unsigned char *plainText, gsi_u32 *lenout) +{ + #ifdef GS_CRYPT_RSA_ES_OAEP + return gsCryptRSAOAEPDecryptBuffer(privateKey, cipherText, plainText, lenout); + #else + return gsCryptRSAPKCS1DecryptBuffer(privateKey, cipherText, plainText, lenout); + #endif +} + +// Since decryption requires the privatekey, it is usually done by the server. +// Decryption is also much slower than encryption. +// This is included here as a testing utility but should probably not be used in a game client. +#ifndef GS_CRYPT_RSA_ES_OAEP + static gsi_i32 gsCryptRSAPKCS1DecryptBuffer(const gsCryptRSAKey *privateKey, const unsigned char cipherText[GS_CRYPT_RSA_BYTE_SIZE], unsigned char *plainText, gsi_u32 *lenout) + { + int i=0; + char* temp; + gsLargeInt_t lintRSAPacket; + lintRSAPacket.mLength = GS_CRYPT_RSA_BYTE_SIZE/GS_LARGEINT_DIGIT_SIZE_BYTES; + memcpy(lintRSAPacket.mData, cipherText, GS_CRYPT_RSA_BYTE_SIZE); + + if (gsi_is_false(gsLargeIntReverseBytes(&lintRSAPacket)) || // reverse from bytebuffer to lint format + gsi_is_false(gsLargeIntPowerMod(&lintRSAPacket, &privateKey->exponent, &privateKey->modulus, &lintRSAPacket)) || + gsi_is_false(gsLargeIntReverseBytes(&lintRSAPacket)) // reverse back into a bytebuffer + ) + { + return -1; + } + + // check post exponentiation length + if (lintRSAPacket.mLength < (GS_CRYPT_RSA_BYTE_SIZE/GS_LARGEINT_DIGIT_SIZE_BYTES)) + return -1; + + // Check the packet for legality + // 1. first byte must be 0x00 + // 2. send byte must be 0x02 + // 3. pad must be at least 8 bytes and end with 0x00 + // 4. payload must be at least 1 byte + temp = (char*)lintRSAPacket.mData; + if (temp[0] != 0x00) + return -1; + if (temp[1] != 0x02) + return -2; + + // find the start of the data (first 0x00 byte after the 1st) + temp = (char*)lintRSAPacket.mData; + for (i=2; iexponent, &privateKey->modulus, &lintRSAPacket); + gsLargeIntReverseBytes(&lintRSAPacket); // reverse back into a bytebuffer + + // check post exponentiation length + if (lintRSAPacket.mLength < (GS_CRYPT_RSA_BYTE_SIZE/4)) + return -1; + + // Check the packet for legality + // 1. "un-mask" the maskedSeed, using the maskedData + gsiCryptRSAMaskData(packet->maskedSeed, GS_CRYPT_HASHSIZE, packet->maskedData, GS_CRYPT_RSA_DATABLOCKSIZE); + // 2. "un-mask" the maskedData, using the previously unmasked maskedSeed + gsiCryptRSAMaskData(packet->maskedData, GS_CRYPT_RSA_DATABLOCKSIZE, packet->maskedSeed, GS_CRYPT_HASHSIZE); + // 3. datablock = [lhash][0x00...][0x01][M] + if (0 != memcmp(packet->maskedData, lhash, GS_CRYPT_HASHSIZE)) + return -2; // label has doesn't match (mismatched hash algorithms?) + i = 33; + while(imaskedData[i] == 0x00) + i++; // may be zero bytes pad + if (i==GS_CRYPT_RSA_BYTE_SIZE || packet->maskedData[i] != 0x01) + return -3; // must be a 0x01 following the pad + i++; + if (i == GS_CRYPT_RSA_BYTE_SIZE) + return -4; // founnd the separator, but no message! + + memcpy(plainText, &packet->maskedData[i], (size_t)(GS_CRYPT_RSA_DATABLOCKSIZE-i)); + *lenout = (gsi_u32)(GS_CRYPT_RSA_DATABLOCKSIZE-i); // final length = blocksize - pad + return 0; + } +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_i32 gsCryptRSASignData(const gsCryptRSAKey *privateKey, const unsigned char *plainText, gsi_u32 plainTextLen, unsigned char *signedDataOut, gsi_u32 *lenOut) +{ + const unsigned char * hash = NULL; + + GSI_UNUSED(privateKey); + GSI_UNUSED(plainText); + GSI_UNUSED(plainTextLen); + GSI_UNUSED(signedDataOut); + GSI_UNUSED(lenOut); + + // 1) hash data + // hash = MD5(plainText); + //GS_ASSERT(0); // not implemented yet + + + // 2) Sign + return gsCryptRSASignHash(privateKey, hash, GS_CRYPT_MD5_HASHSIZE, signedDataOut, lenOut); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// The more usual use of RSA signatures +// Constructs a PKCS1 signature form of type { [0x00][0x01][0xFF..FF][0x00][hash oid][hash] } = RSA key length +gsi_i32 gsCryptRSASignHash(const gsCryptRSAKey *privateKey, const unsigned char *hash, gsi_u32 hashLen, unsigned char *signedDataOut, gsi_u32 *lenOut) +{ + // Encode to PKCS1 signature form + gsi_u32 aKeyByteLength = privateKey->modulus.mLength * GS_LARGEINT_DIGIT_SIZE_BYTES; // key length in bytes + gsi_u32 aReservedLength = 3; + gsi_u32 anOidLen; + + gsLargeInt_t dataToSign; + char * writeBuf = (char*)dataToSign.mData; + + // Microsoft PKCS #1 headers for various hash algorithms. + gsi_u8 md5Header[18] = {0x30,0x20,0x30,0x0C,0x06,0x08,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x02,0x05,0x05,0x00,0x04,0x10}; + gsi_u8 sha1Header[15] = {0x30,0x21,0x30,0x09,0x06,0x05,0x2B,0x0E,0x03,0x02,0x1A,0x05,0x00,0x04,0x14}; + + if (hashLen == GS_CRYPT_MD5_HASHSIZE) + anOidLen = sizeof(md5Header); + else if (hashLen == GS_CRYPT_SHA1_HASHSIZE) + anOidLen = sizeof(sha1Header); + else + return -1; // hash algorithm could not be identified from hashLen + + // Make sure the key is large enough to sign this hash + GS_ASSERT(hashLen + anOidLen + aReservedLength <= aKeyByteLength); + if (hashLen + anOidLen + aReservedLength > aKeyByteLength) + return -2; // key is too small or hash is too large + + // fill in header bytes + writeBuf[0] = 0x00; + writeBuf[1] = 0x01; + + // pad with 0xFF + memset(&writeBuf[2], 0xFF, aKeyByteLength - hashLen - anOidLen - aReservedLength); + + // set a 0x00 at the end of the 0xFF pad + writeBuf[aKeyByteLength - hashLen - anOidLen - 1] = 0x00; + + // copy in the oid + if (hashLen == GS_CRYPT_MD5_HASHSIZE) + memcpy(&writeBuf[aKeyByteLength-hashLen-anOidLen], md5Header, sizeof(md5Header)); + else if (hashLen == GS_CRYPT_SHA1_HASHSIZE) + memcpy(&writeBuf[aKeyByteLength-hashLen-anOidLen], sha1Header, sizeof(sha1Header)); + else + return -1; // should probably assert here + + // copy in the hash + memcpy(&writeBuf[aKeyByteLength-hashLen], hash, hashLen); + + // fix byte order for large int + dataToSign.mLength = privateKey->modulus.mLength; + gsLargeIntReverseBytes(&dataToSign); + + // sign (a.k.a. encrypt) + gsLargeIntPowerMod(&dataToSign, &privateKey->exponent, &privateKey->modulus, &dataToSign); + + // length of output data is always the length of the private key's modulus + GS_ASSERT(dataToSign.mLength == privateKey->modulus.mLength); + gsLargeIntReverseBytes(&dataToSign); // switch back to rawbuffer byte order + memcpy(signedDataOut, dataToSign.mData, aKeyByteLength); + *lenOut = aKeyByteLength; + + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// "data" is the text that was signed, encrypted or plaintext +// "sig" is the signature value attached to the msg (BIG-endian) +// Signature format: +// [ 0x00 0x01 0xFF ... 0x00 HashHeader Hash(Data) ] +gsi_i32 gsCryptRSAVerifySignedHash(const gsCryptRSAKey *publicKey, const unsigned char *hash, gsi_u32 hashLen, const unsigned char *sig, gsi_u32 sigLen) +{ + gsLargeInt_t lintRSASignature; + gsi_u8* packet = (gsi_u8*)lintRSASignature.mData; + int i=0; + + // Microsoft PKCS #1 headers for various hash algorithms. + gsi_u8 md5Header[18] = {0x30,0x20,0x30,0x0C,0x06,0x08,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x02,0x05,0x05,0x00,0x04,0x10}; + gsi_u8 sha1Header[15] = {0x30,0x21,0x30,0x09,0x06,0x05,0x2B,0x0E,0x03,0x02,0x1A,0x05,0x00,0x04,0x14}; + + // check parameters for common errors + if (hash==NULL || sig==NULL) + return -1; + if (sigLen != publicKey->modulus.mLength*GS_LARGEINT_DIGIT_SIZE_BYTES) + return -1; + if (hashLen != GS_CRYPT_MD5_HASHSIZE && hashLen != GS_CRYPT_SHA1_HASHSIZE) + return -1; // invalid hashsize + + // "decrypt" the signature + lintRSASignature.mLength = (l_word)(sigLen / GS_LARGEINT_DIGIT_SIZE_BYTES); + memcpy(lintRSASignature.mData, sig, sigLen); + gsLargeIntReverseBytes(&lintRSASignature); + gsLargeIntPowerMod(&lintRSASignature, &publicKey->exponent, &publicKey->modulus, &lintRSASignature); + gsLargeIntReverseBytes(&lintRSASignature); + + // Check format, first by 0x00, second byte 0x01 + if (packet[0] != 0x00 || packet[1] != 0x01) + return -2; + + // Loop through the 0xFF's + for (i=2; i +//#include + + +// THIS FILE ONLY INCLUDED WHEN USING GAMESPY DEBUG FUNCTIONS +// (don't put this above the header includes or VC will whine +#ifdef GSI_COMMON_DEBUG + +#if defined(_NITRO) +#include "../../common/nitro/screen.h" +#define printf Printf +#define vprintf VPrintf +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Static debug data +static struct GSIDebugInstance gGSIDebugInstance; // simple singleton "class" + +// Line prefixes, e.g. "[ cat][type][ lev] text" +char* gGSIDebugCatStrings[GSIDebugCat_Count] = +{ + " APP", " GP ", "PEER", " QR2", " SB", " V2", " AD", " NN", "HTTP", "CDKY", " CMN" +}; +char* gGSIDebugTypeStrings[GSIDebugType_Count] = +{ + " NET", "FILE", " MEM", "STAT", "MISC" +}; +char* gGSIDebugLevelStrings[GSIDebugLevel_Count] = +{ + "*ERR", "****", "----", " ", " ", " ", " ->" +}; +char* gGSIDebugLevelDescriptionStrings[8] = +{ + "None", "", "", "", "", "", "", "" +}; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// utility to convert bit flag back to base (e.g. 1<<2 returns 2) +static gsi_u32 gsiDebugLog2(gsi_u32 theInt) +{ + gsi_u32 total = 0; + while (theInt > 1) + { + theInt = theInt >> 1; + total++; + } + return total; +} + + +// default supplied debug function, will receive debug text +// this is platform specific +static void gsiDebugCallback(GSIDebugCategory category, GSIDebugType type, + GSIDebugLevel level, const char * format, va_list params) +{ + #if defined(_PSP) + // Output line prefix + vprintf(format, params); + //gsDebugTTyPrint(string); + #elif defined(_PS2) + // Output line prefix + vprintf(format, params); + + #elif defined(_PS3) + // Output line prefix + vprintf(format, params); + + #elif defined(_WIN32) + static char string[256]; + vsprintf(string, format, params); + OutputDebugStringA(string); + + #elif defined(_LINUX) || defined(_MACOSX) + //static char string[256]; + //vsprintf(string, format, params); + vprintf(format, params); + #elif defined(_NITRO) + VPrintf(format, params); + #elif defined(_REVOLUTION) + static char string[256]; + vsprintf(string, format, params); + OSReport(string); + #else + va_list argptr; + static char string[256]; + va_start(argptr, format); + vsprintf(string, format, argptr); + va_end(argptr); + gsDebugTTyPrint(string); + #endif + + GSI_UNUSED(category); + GSI_UNUSED(type); + GSI_UNUSED(level); +} + + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// process debug output +void gsDebugVaList(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel, const char* theTokenStr, + va_list theParamList) +{ + // Retrieve the current debug level + GSIDebugLevel aCurLevel; + + // Verify Parameters + assert(theCat <= GSIDebugCat_Count); + assert(theType <= GSIDebugType_Count); + assert(theLevel <= (1< 0) + { + gsi_i32 aBytesToRead = min(aBytesLeft, 16); + + HexEncode16(aReadPos, aHexStr, (unsigned int)aBytesToRead); + gsDebugFormat(theCat, theType, theLevel, " %s\r\n", aHexStr); + + aReadPos += aBytesToRead; + aBytesLeft -= aBytesToRead; + }; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsSetDebugLevel(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel) +{ + // Verify Parameters + assert(theCat <= GSIDebugCat_Count); + assert(theType <= GSIDebugType_Count); + + // Set for all categories? + if (theCat == GSIDebugCat_Count) + { + int i=0; + for (; i + +#if defined(__LANGUAGE_C_PLUS_PLUS)||defined(__cplusplus)||defined(c_plusplus) +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Input levels (text is reported at one of these levels) +typedef gsi_u8 GSIDebugLevel; +#define GSIDebugLevel_HotError (GSIDebugLevel)(1<<0) // 1 Unexpected Error +#define GSIDebugLevel_WarmError (GSIDebugLevel)(1<<1) // 2 Expected Error +#define GSIDebugLevel_Warning (GSIDebugLevel)(1<<2) // 4 Warnings and Errors +#define GSIDebugLevel_Notice (GSIDebugLevel)(1<<3) // 8 Usefull debug info +#define GSIDebugLevel_Comment (GSIDebugLevel)(1<<4) // 16 Debug spam +#define GSIDebugLevel_RawDump (GSIDebugLevel)(1<<5) // 32 e.g. MemoryBuffer +#define GSIDebugLevel_StackTrace (GSIDebugLevel)(1<<6) // 64 Important function entries +// add new ones here (update string table in gsiDebug.c!) +#define GSIDebugLevel_Count 7 // 7 reporting levels + +// Output levels (a mask for the levels you want to receive) +// (update string table in gsiDebug.c!) +#define GSIDebugLevel_None (GSIDebugLevel)(0) // No output +#define GSIDebugLevel_Normal (GSIDebugLevel)(0x07) // Warnings and above +#define GSIDebugLevel_Debug (GSIDebugLevel)(0x0F) // Notice and above +#define GSIDebugLevel_Verbose (GSIDebugLevel)(0x1F) // Comment and above +#define GSIDebugLevel_Hardcore (GSIDebugLevel)(0xFF) // Recv all + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Output types +typedef enum +{ + GSIDebugType_Network, // Network activity + GSIDebugType_File, // File output + GSIDebugType_Memory, // Memory allocations + GSIDebugType_State, // State update + GSIDebugType_Misc, // None of the above + // add new ones here (update string table in gsiDebug.c!) + + GSIDebugType_Count, + GSIDebugType_All = GSIDebugType_Count +} GSIDebugType; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Debug categories (SDKs) +typedef enum +{ + GSIDebugCat_App, + GSIDebugCat_GP, + GSIDebugCat_Peer, + GSIDebugCat_QR2, + GSIDebugCat_SB, + GSIDebugCat_Voice, + GSIDebugCat_AD, + GSIDebugCat_NatNeg, + GSIDebugCat_HTTP, + GSIDebugCat_CDKey, + // Add new ones here (update string table in gsiDebug.c!) + + + GSIDebugCat_Common, // Common should be last to prevent display weirdness + // resulting from initialization order + GSIDebugCat_Count, + GSIDebugCat_All = GSIDebugCat_Count +} GSIDebugCategory; + +extern char* gGSIDebugCatStrings[GSIDebugCat_Count]; +extern char* gGSIDebugTypeStrings[GSIDebugType_Count]; +extern char* gGSIDebugLevelStrings[GSIDebugLevel_Count]; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Only include static data and functions if GSI_COMMON_DEBUG is defined +#ifndef GSI_COMMON_DEBUG + // not using GSI debug! Define functions to + // (put these here so VisualAssist will resolve the definitions below) + #if !defined(_WIN32) && !defined(__MWERKS__) + // WIN32 doesn't like "..." in a macro + #define gsDebugFormat(c,t,l,f,...) + #define gsDebugVaList(c,t,l,f,v) + #define gsDebugBinary(c,t,l,b,n) + #define gsSetDebugLevel(c,t,l) + #define gsSetDebugFile(f) + #define gsOpenDebugFile(f) + #define gsGetDebugFile + #define gsSetDebugCallback(c) + #elif defined(_NITRO) + #define gsDebugFormat(...) + #define gsDebugVaList(c,t,l,f,v) + #define gsDebugBinary(c,t,l,b,n) + #define gsSetDebugLevel(c,t,l) + #define gsSetDebugFile(f) + #define gsOpenDebugFile(f) + #define gsGetDebugFile + #define gsSetDebugCallback(c) + #else + #define gsDebugFormat + #define gsDebugVaList + #define gsDebugBinary + #define gsSetDebugLevel + #define gsSetDebugFile + #define gsOpenDebugFile + #define gsGetDebugFile + #define gsSetDebugCallback + #endif +#else + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// User supplied debug function, will receive debug text +typedef void (*GSIDebugCallback)(GSIDebugCategory,GSIDebugType,GSIDebugLevel, + const char*, va_list); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Global debug instance +typedef struct GSIDebugInstance +{ +#if !defined(_NITRO) + FILE* mGSIDebugFile; +#endif + GSIDebugCallback mDebugCallback; + gsi_i32 mInitialized; + +#if !defined(GSI_NO_THREADS) + GSICriticalSection mDebugCrit; +#endif + + GSIDebugLevel mGSIDebugLevel[GSIDebugCat_Count][GSIDebugType_Count]; +} GSIDebugInstance; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Logging functions +void gsDebugFormat(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel, const char* theTokenStr, ...); + +void gsDebugVaList(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel, const char* theTokenStr, + va_list theParams); + +void gsDebugBinary(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel, const char* theBuffer, gsi_i32 theLength); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Output functions +void gsSetDebugLevel(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel); + +#if !defined(_NITRO) + +// Set the output file (NULL for no file) +void gsSetDebugFile(FILE* theFile); + +// Open and set the debug file +FILE* gsOpenDebugFile(const char* theFileName); + +// Retrieve the debug file +FILE* gsGetDebugFile(); + +#endif + +// Set a callback to be triggered with debug output +void gsSetDebugCallback(GSIDebugCallback theCallback); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // GSI_COMMON_DEBUG + +#if defined(__LANGUAGE_C_PLUS_PLUS)||defined(__cplusplus)||defined(c_plusplus) +} +#endif + +#endif // __GSIDEBUG_H__ diff --git a/code/gamespy/common/gsLargeInt.c b/code/gamespy/common/gsLargeInt.c new file mode 100644 index 00000000..9b778eae --- /dev/null +++ b/code/gamespy/common/gsLargeInt.c @@ -0,0 +1,1904 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsLargeInt.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Many parameters are gsi_u32* instead of gsLargeInt_t*. +// This was done to allow easy conversion of databuffer to gsLargeInt_t +// Raw buffer destinations must have enough space to store the result +static gsi_bool gsiLargeIntPrint(FILE* logFile, const l_word *data, l_word length); +static gsi_bool gsiLargeIntResize(gsLargeInt_t *lint, l_word length); +static gsi_bool gsiLargeIntStripLeadingZeroes(gsLargeInt_t* lint); +static gsi_bool gsiLargeIntSizePower2(const gsLargeInt_t *src1, const gsLargeInt_t *src2, l_word *lenout); +static gsi_i32 gsiLargeIntCompare(const l_word *data1, l_word len1, const l_word *data2, l_word len2); + +static gsi_bool gsiLargeIntKMult(const l_word *data1, const l_word *data2, l_word length, l_word *dest, l_word *lenout, l_word maxlen); +static gsi_bool gsiLargeIntMult (const l_word *data1, l_word length1, const l_word *data2, l_word length2, l_word *dest, l_word *lenout, l_word maxlen); +static gsi_bool gsiLargeIntDiv (const l_word *src1, l_word length1, const gsLargeInt_t *divisor, gsLargeInt_t *dest, gsLargeInt_t *remainder); + +// Dest may be data1 or data2 to support in-place arithmetic +static gsi_bool gsiLargeIntAdd (const l_word *data1, l_word length1, const l_word *data2, l_word length2, l_word *dest, l_word *lenout, l_word maxlen); +static gsi_bool gsiLargeIntSub (const l_word *amount, l_word length1, const l_word *from, l_word length2, l_word *dest, l_word *lenout); + +// Special division, removes divisor directly from src1, leaving remainder +static gsi_bool gsiLargeIntSubDivide(l_word *src1, l_word length, const l_word *divisor, l_word dlen, gsi_u32 highbit, l_word *quotient); + +// Montgomery utilities +//gsi_bool gsiLargeIntSquareM(const gsLargeInt_t *src, const gsLargeInt_t *mod, gsi_u32 modPrime, gsi_u32 R, gsLargeInt_t *dest); +//gsi_bool gsiLargeIntMultM(gsLargeInt_t *src1, gsLargeInt_t *src2, const gsLargeInt_t *mod, gsi_u32 modPrime, gsLargeInt_t *dest); +gsi_bool gsiLargeIntMultM(gsLargeInt_t *src1, gsLargeInt_t *src2, const gsLargeInt_t *mod, gsi_u32 modPrime, gsLargeInt_t *dest); +gsi_bool gsiLargeIntInverseMod(const gsLargeInt_t *mod, l_word *modPrimeOut); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + // execution timing/profiling +#define GS_LINT_TIMING +#ifdef GS_LINT_TIMING + +typedef enum +{ + GSLintTimerMult, // "regular" multiplication + GSLintTimerMultM, // montgomery + GSLintTimerKMult, // karatsuba + GSLintTimerAdd, + GSLintTimerSub, // subtract + GSLintTimerDiv, + GSLintTimerSubDivide, // atomic divide + GSLintTimerSquareMod, + GSLintTimerPowerMod, // modular exponentiation + + GSLintTimerCount +} GSLintTimerID; + +typedef struct GSLintTimer +{ + gsi_time started; + gsi_time total; + gsi_u32 entries; + gsi_u32 running; // already entered? +} GSLintTimer; +static struct GSLintTimer gTimers[GSLintTimerCount]; + +static void gsiLargeIntTimerEnter(GSLintTimerID id) +{ + if (gTimers[id].running==0) + { + gTimers[id].entries++; + gTimers[id].started = current_time_hires(); + gTimers[id].running = 1; + } +} +static void gsiLargeIntTimerExit(GSLintTimerID id) +{ + if (gTimers[id].running==1) + { + gTimers[id].total += current_time_hires()-gTimers[id].started; + gTimers[id].running = 0; + } +} + +#define GSLINT_ENTERTIMER(id) gsiLargeIntTimerEnter(id) +#define GSLINT_EXITTIMER(id) gsiLargeIntTimerExit(id) + +#else +#define GSLINT_ENTERTIMER(id) +#define GSLINT_EXITTIMER(id) +#endif + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsLargeIntSetValue(gsLargeInt_t *lint, l_word value) +{ + lint->mLength = 1; + lint->mData[0] = value; + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Resize by: +// Padding a GSLINT with leading zeroes. +// or stripping lead zeroes. +// This function will not strip digits other than zero. +gsi_bool gsiLargeIntResize(gsLargeInt_t *lint, l_word length) +{ + if (length > GS_LARGEINT_MAX_DIGITS) + return gsi_false; + + // strip leading zeroes until length is reached + if (lint->mLength >= length) + { + while(lint->mLength > length && lint->mData[lint->mLength-1]==0) + lint->mLength--; // check each digit to make sure it's zero + if (lint->mLength == length) + return gsi_true; + else + return gsi_false; + } + + // otherwise, add zeroes until length is reached + else + { + memset(&lint->mData[lint->mLength], 0, (length-lint->mLength)*sizeof(l_word)); + lint->mLength = length; + return gsi_true; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Makes two GSLINT the same size, the size being a power of 2 +// NOTE: Testing next multiple of two, not power of 2 +gsi_bool gsiLargeIntSizePower2(const gsLargeInt_t *src1, const gsLargeInt_t *src2, l_word *lenout) +{ + unsigned int i = 0; + + int len1 = (int)src1->mLength; + int len2 = (int)src2->mLength; + + // strip leading zeroes + while(len1>0 && src1->mData[len1-1] == 0) + len1--; + while(len2>0 && src2->mData[len2-1] == 0) + len2--; + + // set to longer length + *lenout = (l_word)max(len1, len2); + + // search for power of two >= length + // (this length is in digits, not bits) + i=1; + while(i < *lenout) + i = i<<1; + *lenout = (l_word)i; + + if (*lenout > GS_LARGEINT_MAX_DIGITS) + return gsi_false; + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Compare two integers +// -1 = data1 < data2 +// 0 = data1 = data2 +// 1 = data1 > data2 +static gsi_i32 gsiLargeIntCompare(const l_word *data1, l_word len1, const l_word *data2, l_word len2) +{ + // skip leading whitespace, if any + while(data1[len1-1] == 0 && len1>0) + len1--; + while(data2[len2-1] == 0 && len2>0) + len2--; + if (len1len2) + return 1; + else + { + // same size, compare digits + while(len1 > 0) + { + if (data1[len1-1] < data2[len1-1]) + return -1; + else if (data1[len1-1] > data2[len1-1]) + return 1; + len1--; + } + } + return 0; // equal! +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiLargeIntStripLeadingZeroes(gsLargeInt_t* lint) +{ + while(lint->mLength >0 && lint->mData[lint->mLength-1]==0) + lint->mLength--; + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Addition may cause overflow +gsi_bool gsLargeIntAdd(const gsLargeInt_t *src1, const gsLargeInt_t *src2, gsLargeInt_t *dest) +{ + gsi_bool result = gsiLargeIntAdd(src1->mData, src1->mLength, src2->mData, src2->mLength, dest->mData, &dest->mLength, GS_LARGEINT_MAX_DIGITS); + if (gsi_is_false(result)) + memset(dest, 0, sizeof(gsLargeInt_t)); // overflow + return result; +} + +// len: In value = maxsize +// Out value = actual size +static gsi_bool gsiLargeIntAdd(const l_word *data1, l_word length1, const l_word *data2, l_word length2, l_word *dest, l_word *lenout, l_word maxlen) +{ + gsi_u32 i=0; + l_dword carry = 0; // to hold overflow + + gsi_u32 shorterLen = 0; + gsi_u32 longerLen = 0; + //const gsi_u32 *shorterSrc = NULL; + const l_word *longerSrc = NULL; + + GSLINT_ENTERTIMER(GSLintTimerAdd); + + if (maxlen < length1 || maxlen < length2) + return gsi_false; // dest not large enough, OVERFLOW + + if (length1 < length2) + { + shorterLen = length1; + //shorterSrc = data1; + longerLen = length2; + longerSrc = data2; + } + else + { + shorterLen = length2; + //shorterSrc = data2; + longerLen = length1; + longerSrc = data1; + } + + // Add digits until the shorterSrc's length is reached + while(i < shorterLen) + { + carry += (l_dword)data1[i] + data2[i]; + dest[i] = (l_word)carry; + carry = carry >> GS_LARGEINT_DIGIT_SIZE_BITS; //32; + i++; + } + + // Continue adding until carry is zero + while((carry > 0) && (i < longerLen)) + { + carry += (l_dword)longerSrc[i]; + dest[i] = (l_word)carry; + carry = carry >> GS_LARGEINT_DIGIT_SIZE_BITS; //32; + i++; + } + + // Is there still a carry? + // do not perform length check here, so that we can support oversized buffers + if (carry > 0) // && i < GS_LARGEINT_INT_SIZE) + { + if (maxlen <= i) + return gsi_false; // OVERFLOW, no room for extra digit + dest[i++] = (l_word)carry; + carry = 0; + } + + // Copy the rest of the bytes straight over (careful of memory overlap) + // this can't happen if there was a carry (see above carry>0 check) + if (i < longerLen) + { + // check overlap + if (&dest[i] != &longerSrc[i]) + memcpy(&dest[i], &longerSrc[i], (longerLen-i)*sizeof(l_word)); + i = longerLen; + } + *lenout = (l_word)i; + + GSLINT_EXITTIMER(GSLintTimerAdd); + + if (carry) + return gsi_false; // overflow + else + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Subtraction may cause underflow +// subtracts src1 FROM src2 +// strips leading zeroes (gsiLargeIntSub doesn't strip for compatability with karatsuba fixed size numbers) +gsi_bool gsLargeIntSub(const gsLargeInt_t *src1, const gsLargeInt_t *src2, gsLargeInt_t *dest) +{ + gsi_bool result = gsiLargeIntSub(src1->mData, src1->mLength, src2->mData, src2->mLength, dest->mData, &dest->mLength); + if (gsi_is_true(result)) + gsiLargeIntStripLeadingZeroes(dest); + return result; +} + +gsi_bool gsiLargeIntSub(const l_word *src1, l_word length1, const l_word *src2, l_word length2, l_word *dest, l_word *lenout) +{ + l_dword borrow = 0; // to hold overflow + gsi_u32 shorterLen = min(length1, length2); + gsi_u32 i=0; + + GSLINT_ENTERTIMER(GSLintTimerSub); + + //printf("--From: "); + //gsiLargeIntPrint(src2, length2); + //printf("--Subtracting: "); + //gsiLargeIntPrint(src1, length1); + + // Subtract digits + while(i < shorterLen) + { + borrow = (l_dword)src2[i] - src1[i] - borrow; + dest[i] = (l_word)borrow; + borrow = borrow>>63; // shift to last bit. This will be 1 if negative, 0 if positive + i++; + } + while(i < length2) + { + borrow = (l_dword)src2[i]-borrow; + dest[i] = (l_word)borrow; + borrow = borrow>>63; + i++; + } + + // check for underflow + if (borrow != 0) + { + GSLINT_EXITTIMER(GSLintTimerSub); + return gsi_false; + } + while(length1 > i) // make sure remaining digits are only leading zeroes + { + if (src1[i] != 0) + { + GSLINT_EXITTIMER(GSLintTimerSub); + return gsi_false; + } + i++; + } + + // Don't reduce length from subtraction, instead keep leading zeroes + // (do this for ease of use with Karatsuba which requires Power2 length) + *lenout = length2; + + GSLINT_EXITTIMER(GSLintTimerSub); + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Multiply using normal method (use KMult when working with LargeInt*LargeInt) +gsi_bool gsLargeIntMult(const gsLargeInt_t *src1, const gsLargeInt_t *src2, gsLargeInt_t *dest) +{ + gsi_bool result = gsiLargeIntMult(src1->mData, src1->mLength, src2->mData, src2->mLength, dest->mData, &dest->mLength, GS_LARGEINT_MAX_DIGITS); + if (gsi_is_false(result)) + memset(dest, 0, sizeof(gsLargeInt_t)); // overflow + return result; +} + +static gsi_bool gsiLargeIntMult(const l_word *data1, l_word length1, const l_word *data2, l_word length2, l_word *dest, l_word *lenout, l_word maxlen) +{ + unsigned int i=0; + unsigned int k=0; + + gsLargeInt_t temp; + memset(&temp, 0, sizeof(temp)); + *lenout = 0; + + GSLINT_ENTERTIMER(GSLintTimerMult); + + for(i=0; i= maxlen) + { + GSLINT_EXITTIMER(GSLintTimerMult); + return gsi_false; // overflow + } + while(carry) + { + carry += temp.mData[digit]; + temp.mData[digit] = (l_word)carry; + carry = carry >> GS_LARGEINT_DIGIT_SIZE_BITS; + digit++; + if ((digit > maxlen) || + (digit == maxlen && carry>0)) + { + GSLINT_EXITTIMER(GSLintTimerMult); + return gsi_false; // overflow + } + } + if (digit > (gsi_i32)temp.mLength) + temp.mLength = (l_word)digit; + } + } + } + // copy into destination (calculate length at this time) + while(temp.mLength>0 && temp.mData[temp.mLength-1] == 0) + temp.mLength--; // strip leading zeroes + *lenout = temp.mLength; + memcpy(dest, temp.mData, (*lenout)*sizeof(l_word)); + + GSLINT_EXITTIMER(GSLintTimerMult); + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// divide src1 by divisor +gsi_bool gsLargeIntDiv(const gsLargeInt_t *src1, const gsLargeInt_t *divisor, gsLargeInt_t *dest, gsLargeInt_t *remainder) +{ + // call the free-buffer version + return gsiLargeIntDiv(src1->mData, src1->mLength, divisor, dest, remainder); +} + +// length1 can be, at most, 2*GS_LARGEINT_INT_SIZE +static gsi_bool gsiLargeIntDiv(const l_word *src, l_word len, const gsLargeInt_t *div, gsLargeInt_t *dest, gsLargeInt_t *remainder) +{ + gsi_i32 result = 0; // temp, to store compare result + gsi_i32 divisorHighBit = GS_LARGEINT_DIGIT_SIZE_BITS-1; // pre-calculate this + + // Bytes used from src1 + int readIndex = 0; + int readLength = 0; + + // setup scratch copies + gsLargeInt_t quotient; + + l_word scopy[GS_LARGEINT_MAX_DIGITS*2]; // we support double length source for division, when dest is null + l_word scopyLen = len; + + const l_word* divisorData = div->mData; + l_word divisorLen = div->mLength; + + gsi_bool endLoop = gsi_false; + + GSLINT_ENTERTIMER(GSLintTimerDiv); + + memset(scopy, 0, sizeof(scopy)); + + // we only support oversized sources for calculating a remainder + // e.g. dest must be null + if (scopyLen > GS_LARGEINT_MAX_DIGITS && dest != NULL) + return gsi_false; + + // strip leading zeroes (from our scratch copies) + while(scopyLen>0 && src[scopyLen-1]==0) + scopyLen--; + while(divisorLen>0 && divisorData[divisorLen-1]==0) + divisorLen--; + + memcpy(scopy, src, scopyLen*sizeof(l_word)); + memset("ient, 0, sizeof(quotient)); + + // check the unusual cases + if (scopyLen==0 || divisorLen==0) + { + if (dest) + { + dest->mData[0] = 0; + dest->mLength = 0; + } + if (remainder) + { + remainder->mData[0] = 0; + remainder->mLength = 0; + } + + GSLINT_EXITTIMER(GSLintTimerDiv); + + if (divisorLen == 0) + return gsi_false; // division by zero + else + return gsi_true; // zero divided, this is legal + } + if (gsiLargeIntCompare(scopy, scopyLen, divisorData, divisorLen)==-1) + { + // divisor is larger than source + if (dest) + { + dest->mLength = 0; + dest->mData[0] = 0; + } + remainder->mLength = scopyLen; + memcpy(remainder->mData, scopy, scopyLen*sizeof(l_word)); + GSLINT_EXITTIMER(GSLintTimerDiv); + return gsi_true; + } + + // calculate the divisor high bit + while((divisorData[divisorLen-1]&(1<<(gsi_u32)divisorHighBit))==0 && divisorHighBit>=0) + divisorHighBit--; + if (divisorHighBit == -1) + { + GSLINT_EXITTIMER(GSLintTimerDiv); + return gsi_false; // divide by zero + } + divisorHighBit += (divisorLen-1)*GS_LARGEINT_DIGIT_SIZE_BITS; + + // position "sliding" window for first interation + // 41529 / [71389]2564 + // WARNING: digits are indexed [2][1][0], first byte to read is index[2] + readIndex = (int)(scopyLen - divisorLen); + readLength = (int)divisorLen; + + //if (readIndex < 0) + // _asm {int 3}; // overflow readIndex + + do + { + result = gsiLargeIntCompare(&scopy[readIndex], (l_word)readLength, divisorData, divisorLen); + if (result == -1) + { + // scopy window is smaller, we'll need an extra digit + if (readIndex > 0) + { + readIndex--; + readLength++; + } + else + { + // no more digits! + endLoop = gsi_true; + } + } + else if (result == 0) + { + // not likely! set digits to zero and slide window + memset(&scopy[readIndex], 0, readLength*sizeof(l_word)); + quotient.mData[readIndex] += 1; + if (quotient.mLength < (l_word)(readIndex+readLength)) + quotient.mLength = (l_word)(readIndex+readLength); + readIndex -= readLength; + readLength = 1; + + if (readIndex < 0) + endLoop = gsi_true;; // no more digits + } + else + { + // subtract directly onto our temp copy, so we don't have to worry about carry values + l_word quotientTemp = 0; + //if (readLength > 0xffff) + // _asm {int 3} + if (gsi_is_false(gsiLargeIntSubDivide(&scopy[readIndex], (l_word)readLength, divisorData, divisorLen, (gsi_u32)divisorHighBit, "ientTemp))) + { + // overflow + GSLINT_EXITTIMER(GSLintTimerDiv); + return gsi_false; + } + quotient.mData[readIndex] = (l_word)(quotient.mData[readIndex] + quotientTemp); + if (quotient.mLength < (l_word)(readIndex+readLength)) + quotient.mLength = (l_word)(readIndex+readLength); + // remove new leading zeroes + while(scopy[readIndex+readLength-1] == 0 && readLength>1) + readLength--; + while(scopy[readIndex+readLength-1] == 0 && readIndex>1) + readIndex--; + } + } + while(gsi_is_false(endLoop)); + + // no more digits, leftover is remainder + if (readIndex >= 0) + { + memcpy(remainder->mData, &scopy[readIndex], readLength*sizeof(l_word)); + remainder->mLength = (l_word)readLength; + } + else + { + remainder->mData[0] = 0; + remainder->mLength = 0; + } + + // save off quotient, if desired + if (dest) + { + memcpy(dest->mData, quotient.mData, quotient.mLength*sizeof(l_word)); + dest->mLength = quotient.mLength; + } + GSLINT_EXITTIMER(GSLintTimerDiv); + return gsi_true; +} + + +// atomic divide. +// Subtract divisor directly from src. +// Leave remainder in src. +static gsi_bool gsiLargeIntSubDivide(l_word *src, l_word length, const l_word *divisor, l_word dlen, + gsi_u32 highbit, l_word *quotient) +{ + l_dword aboveBits = 0; + gsLargeInt_t temp; // stores temporary product before subtraction + gsLargeInt_t quotientCopy; // copy of quotient, length padded for multiplication + + GSLINT_ENTERTIMER(GSLintTimerSubDivide); + // assert(src > divisor) + // assert(src < (MAX_DIGIT_VALUE * divisor)) + //if(dlen==1 && *divisor==0) + // _asm {int 3} // division by zero + + // Q: how many times to subtract? + // A: we estimate by taking the bits in src above the highest bit in divisor + if (length > dlen) + aboveBits = (src[length-2]&divisor[dlen-1]) | ((l_dword)src[length-1]<>GS_LARGEINT_DIGIT_SIZE_BITS); + + // We only support quotients up to MAX_INT + if (quotientCopy.mData[1] != 0) + { + quotientCopy.mData[0] = (l_word)(-1); + quotientCopy.mData[1] = 0; + } + quotientCopy.mLength = 1; + + // multiply this value by divisor, and that's how much to subtract + if (gsi_is_false(gsiLargeIntMult(divisor, dlen, quotientCopy.mData, quotientCopy.mLength, temp.mData, &temp.mLength, GS_LARGEINT_MAX_DIGITS))) + { + GSLINT_EXITTIMER(GSLintTimerSubDivide); + return gsi_false; // overflow + } + + // while subtraction amount is larger than src, reduce it + while(gsiLargeIntCompare(temp.mData, temp.mLength, src, length)==1) + { + // divide by two + quotientCopy.mData[0] = (l_word)(quotientCopy.mData[0]>>1); + //if (quotientCopy.mData[0] == 0) + // _asm {int 3} + if (gsi_is_false(gsiLargeIntMult(divisor, dlen, quotientCopy.mData, quotientCopy.mLength, temp.mData, &temp.mLength, GS_LARGEINT_MAX_DIGITS))) + { + GSLINT_EXITTIMER(GSLintTimerSubDivide); + return gsi_false; // overflow + } + } + //if (gsiLargeIntCompare(temp.mData, temp.mLength, src, length)==1) + // _asm {int 3} // temp > src, subtraction will cause underflow! + + // subtract it + gsiLargeIntSub(temp.mData, temp.mLength, src, length, src, &length); + + *quotient = quotientCopy.mData[0]; + //if (quotientCopy.mData[1] != 0) + // _asm {int 3} + GSLINT_EXITTIMER(GSLintTimerSubDivide); + + GSI_UNUSED(highbit); + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Multiply using Karatsuba +// Karatsuba requires that the sizes be equal and a power of two +gsi_bool gsLargeIntKMult(const gsLargeInt_t *src1, const gsLargeInt_t *src2, gsLargeInt_t *dest) +{ + l_word len = 0; + gsi_bool result = gsi_false; + + gsLargeInt_t temp; // to prevent issues if (src1 == src2 == dest) + + // quick check for multiplication by 0 + if (src1->mLength == 0 || src2->mLength == 0) + { + dest->mLength = 0; + return gsi_true; + } + + // when length is small it's faster to use "normal" multiplication + if (max(src1->mLength,src2->mLength) < GS_LARGEINT_KARATSUBA_CUTOFF) + return gsLargeIntMult(src1, src2, dest); + + // Check for size/length restrictions + result = gsiLargeIntSizePower2(src1, src2, &len); + if (gsi_is_false(result) || len>(GS_LARGEINT_MAX_DIGITS/2)) + { + // try regular multiplication + return gsLargeIntMult(src1, src2, dest); + } + + // (don't time above section since it defers to Mult) + GSLINT_ENTERTIMER(GSLintTimerKMult); + + // clear the temporary dest + memset(&temp, 0, sizeof(gsLargeInt_t)); + temp.mLength = 0; + + // resize if necessary + if (src1->mLength != len || src2->mLength != len) + { + // size is not correct, make a copy then multiply + gsLargeInt_t src1Copy; + gsLargeInt_t src2Copy; + memcpy(&src1Copy, src1, sizeof(gsLargeInt_t)); + memcpy(&src2Copy, src2, sizeof(gsLargeInt_t)); + gsiLargeIntResize(&src1Copy, len); + gsiLargeIntResize(&src2Copy, len); + + result = gsiLargeIntKMult(src1Copy.mData, src2Copy.mData, len, temp.mData, &temp.mLength, GS_LARGEINT_MAX_DIGITS); + } + else + { + // size is correct, perform multiplication + result = gsiLargeIntKMult(src1->mData, src2->mData, len, temp.mData, &temp.mLength, GS_LARGEINT_MAX_DIGITS); + } + if (gsi_is_true(result)) + { + // strip leading zeroes and copy into dest + gsiLargeIntStripLeadingZeroes(&temp); + memcpy(dest, &temp, sizeof(gsLargeInt_t)); + } + GSLINT_EXITTIMER(GSLintTimerKMult); + return result; +} + + +// Utility for Karasuba +static gsi_bool gsiLargeIntKMult(const l_word *data1, const l_word *data2, l_word length, + l_word *dest, l_word *lenout, l_word maxlen) +{ + // No timer here, this function is only called from GSLINTKMult + //GSLINT_ENTERTIMER(GSLintTimerKMult); + + // "normal" multiplication is faster when length is small + if (length <= GS_LARGEINT_KARATSUBA_CUTOFF) + return gsiLargeIntMult(data1, length, data2, length, dest, lenout, maxlen); + else + { + gsLargeInt_t temp1, temp2, temp3; + l_word halfLen = (l_word)(length>>1); + + temp1.mLength = 0; + temp2.mLength = 0; + temp3.mLength = 0; + + //printf("Karasuba splitting at %d (1/2 = %d)\r\n", length, halfLen); + + // Karatsuba: k = 12*34 + // a = (1*3) + // b = (1+2)*(3+4)-a-c + // c = (2*4) + // k = a*B^N+b*B^(N/2)+c = a*100+b*10+c + + // Enter the recursive portion + // TH = top half + // BH = bottom half + + // Note that since (a*B^N + c) cannot overlap, we can immediately store both in dest + + // Compute a. (TH of data1 * TH of data2) + // Stores in TH of dest, so later *B^N isn't necessary + // For the example, this puts 1*3 into the high half 03xx + gsiLargeIntKMult(&data1[halfLen], &data2[halfLen], halfLen, &dest[length], lenout, (l_word)(maxlen-length)); + //printf("Calculated A (%d) = ", *lenout); + //gsiLargeIntPrint(&dest[length], *lenout); + + // Compute c. (BH of data1 * BH of data2) + // For the example, this puts 2*4 into the low half xx08 + gsiLargeIntKMult(data1, data2, halfLen, dest, lenout, maxlen); + //printf("Calculated C (%d) = ", *lenout); + //gsiLargeIntPrint(dest, *lenout); + + // Compute b1. (TH of data1 + BH of data1) + gsiLargeIntAdd(&data1[halfLen], halfLen, data1, halfLen, temp1.mData, &temp1.mLength, GS_LARGEINT_MAX_DIGITS); + //printf("Calculated B1 (%d) = ", temp1.mLength); + //gsiLargeIntPrint(temp1.mData, temp1.mLength); + + // Compute b2. (TH of data2 + BH of data2) + gsiLargeIntAdd(&data2[halfLen], halfLen, data2, halfLen, temp2.mData, &temp2.mLength, GS_LARGEINT_MAX_DIGITS); + //printf("Calculated B2 (%d) = ", temp2.mLength); + //gsiLargeIntPrint(temp2.mData, temp2.mLength); + + // Compute b3. (b1*b2) (*B^N) + // For the example, (1+2)(3+4)*B^N = 21*B^N = 0210 + memset(&temp3, 0, sizeof(gsLargeInt_t)); + + // May require resizing, but don't go above halfLen + if (temp1.mLength > halfLen || temp2.mLength > halfLen) + gsiLargeIntMult(temp1.mData, temp1.mLength, temp2.mData, temp2.mLength, &temp3.mData[halfLen], &temp3.mLength, (l_word)(GS_LARGEINT_MAX_DIGITS-halfLen)); + else + { + gsi_bool result = gsiLargeIntSizePower2(&temp1, &temp2, lenout); + if (gsi_is_false(result)) + return gsi_false; // could not resize + gsiLargeIntResize(&temp1, *lenout); // pad to new size + gsiLargeIntResize(&temp2, *lenout); // pad to new size + gsiLargeIntKMult(temp1.mData, temp2.mData, *lenout, &temp3.mData[halfLen], &temp3.mLength, (l_word)(GS_LARGEINT_MAX_DIGITS-halfLen)); + } + temp3.mLength = (l_word)(temp3.mLength + halfLen); // fix length for temp3 + //if (temp3.mLength > GS_LARGEINT_INT_SIZE) + // _asm {int 3} // this should be at most temp1.mLength+temp2.mLength + memset(temp3.mData, 0, halfLen*sizeof(l_word)); + //printf("Calculated B3 (%d) = ", temp3.mLength); + //gsiLargeIntPrint(&temp3.mData[halfLen], temp3.mLength-halfLen); + + // Compute final b. (b3-a-c) (*B^N) + // Note: The subtraction is in terms of (*B^N) + // For the example, 021x - 03x - 08x = 0100 + gsiLargeIntSub(&dest[length], length, &temp3.mData[halfLen], (l_word)(temp3.mLength-halfLen), &temp3.mData[halfLen], &temp3.mLength); + temp3.mLength = (l_word)(temp3.mLength + halfLen); + gsiLargeIntSub( dest , length, &temp3.mData[halfLen], (l_word)(temp3.mLength-halfLen), &temp3.mData[halfLen], &temp3.mLength); + temp3.mLength = (l_word)(temp3.mLength + halfLen); + //printf("Calculated B (%d) = ", temp3.mLength); + //gsiLargeIntPrint(temp3.mData, temp3.mLength); + + // Add em up + // Dest already contains A+C, so Add B + // For the example, 0308 + 0100 = 0408 (the correct answer) + gsiLargeIntAdd(dest, (l_word)(length*2), temp3.mData, temp3.mLength, dest, lenout, maxlen); + } + // strip leading zeroes from dest + while(*lenout > 0 && dest[*lenout-1] == 0) + *lenout = (l_word)(*lenout-1); + + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsLargeIntSquareMod(const gsLargeInt_t *lint, const gsLargeInt_t *mod, gsLargeInt_t *dest) +{ + int i = 0; + int k = 0; + int len = (int)lint->mLength; // signed version + l_dword carry = 0; + int oldShiftBit = 0; + int newShiftBit = 0; + gsi_bool result = gsi_false; + unsigned int mask = (unsigned int)1<<(GS_LARGEINT_DIGIT_SIZE_BITS-1); + + l_word squareSums[GS_LARGEINT_MAX_DIGITS*2]; // temp dest for square sums + l_word otherSums[GS_LARGEINT_MAX_DIGITS*2]; // temp dest for other sums + l_word squareLen = 0; + l_word otherLen = 0; + + GSLINT_ENTERTIMER(GSLintTimerSquareMod); + + memset(&squareSums, 0, sizeof(squareSums)); + memset(&otherSums, 0, sizeof(otherSums)); + + // Go through each digit, multiplying with each other digit + // (only do this once per pair, since AB == BA) + // Ex: ABC * ABC, we want AB,AC,BC only + for (i=1; i < len; i++) + { + for(k=0; k < i; k++) + { + carry += (l_dword)lint->mData[i]*lint->mData[k] + otherSums[i+k]; + otherSums[i+k] = (l_word)carry; + carry = carry >> GS_LARGEINT_DIGIT_SIZE_BITS; + } + if(carry) + { + otherSums[i+k] = (l_word)carry; + carry = carry >> GS_LARGEINT_DIGIT_SIZE_BITS; + } + } + + // Multiply by 2 (because each internal pair appears twice) + for (i=0; i < (2*len); i++) + { + newShiftBit = (otherSums[i] & mask)==mask?1:0; // calc next carry 1 or 0 + otherSums[i] = (l_word)((otherSums[i] << 1) + oldShiftBit); // do the shift + oldShiftBit = newShiftBit; + } + // don't worry about left-overy carry because this can't overflow + // maxlen N-digit*N-digit = 2n-digit + + // Go through each digit, multiplying with itself + for (i=0; i mData[i] * lint->mData[i]; + squareSums[i*2] = (l_word)carry; + squareSums[i*2+1] = (l_word)(carry >> GS_LARGEINT_DIGIT_SIZE_BITS); + } + squareLen = (l_word)(2*len); + otherLen = (l_word)(2*len); + + // Add the two together + result = gsiLargeIntAdd(otherSums, otherLen, squareSums, squareLen, squareSums, &squareLen, GS_LARGEINT_MAX_DIGITS*2); + result = gsiLargeIntDiv(squareSums, squareLen, mod, NULL, dest); + + GSLINT_EXITTIMER(GSLintTimerSquareMod); + return result; +} + +//#define NEWEXP +#ifdef NEWEXP + +//#define printf + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Montgomery exponentiation (see HAC 14.94) +// +// SPECIAL NOTE: +// A small public exponent will reduce the load on client encryption. +// (below 65535 is a security risk, so don't go too small) +gsi_bool gsLargeIntPowerMod(const gsLargeInt_t *b, const gsLargeInt_t *p, const gsLargeInt_t *m, gsLargeInt_t *dest) +{ + gsLargeInt_t base; + gsLargeInt_t power; + gsLargeInt_t mod; + gsLargeInt_t one; + + gsi_u32 expHighBit; // highest bit set in exponent; + + int i = 0; // temp / counter + int k = 0; // binary size of our subdigits + int pow2k = 0; // 2^k + int kmask = 0; // 2^k-1 + int kdigits = 0; // number of k-sized digits in p + //int leadingZeroBits = 0; // to make p evenly divisible by k + + l_word modPrime; + gsLargeInt_t R; // "R" as used in the montgomery exponentiation algorithm. + //gsLargeInt_t Rmod; // R mod n + //gsLargeInt_t R2mod; // R^2 mod n + + gsLargeInt_t * lut = NULL; + + GSLINT_ENTERTIMER(GSLintTimerPowerMod); + + memcpy(&base, b, sizeof(base)); + memcpy(&power, p, sizeof(power)); + memcpy(&mod, m, sizeof(mod)); + memset(&R, 0, sizeof(R)); + + gsLargeIntSetValue(&one, 1); + + // Catch the unusual cases + if (mod.mLength == 0) + { + // mod 0 = undefined + dest->mLength = 0; + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + else if (mod.mLength==1 && mod.mData[0]==1) + { + // mod 1 = 0 + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_true; + } + else if (power.mLength == 0) + { + // x^0 = 1 + dest->mLength = 1; + dest->mData[0] = 1; + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_true; + } + else if ((mod.mData[0]&1) == 0) + { + // Montgomery only works with odd modulus! + // (rsa modulus is prime1*prime2, which must be odd) + dest->mLength = 0; + dest->mData[0] = 0; + //_asm {int 3} + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + // If base is larger than mod, we can (must) reduce it + if (gsiLargeIntCompare(base.mData, base.mLength, mod.mData, mod.mLength)!=-1) + { + gsLargeIntDiv(&base, &mod, NULL, &base); + } + if (base.mLength == 0) + { + // 0^e = 0 + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_true; + } + + // find the highest bit set in power + expHighBit=GS_LARGEINT_DIGIT_SIZE_BITS; + while(((1<<(expHighBit-1))&power.mData[power.mLength-1]) == 0) + expHighBit--; + expHighBit += ((power.mLength-1) * GS_LARGEINT_DIGIT_SIZE_BITS); // add in 32 bits for each extra byte + + // The previous algorithm used 1-bit digits + // This algorithm uses k-bit digits + // Determine the optimal size for k + k=8; // this will support up to 4096 bit encryption (and probably higher) + while ( (k > 1) && + (gsi_u32)((k - 1) * (k << ((k - 1) << 1)) / ((1 << k) - k - 1)) >= expHighBit - 1 + ) + { + --k; + } + pow2k = 1 << k; + kmask = pow2k-1; + kdigits = (expHighBit+(k-1)) / k; // ceiling(expHighBit/k) + + // calculate "R" (if mod=5678, R=10000 e.g. One digit higher) + memset(&R, 0, sizeof(R)); + R.mLength = (l_word)(mod.mLength+1); + if (R.mLength > GS_LARGEINT_MAX_DIGITS) + return gsi_false; // you need to increase the large int capacity + R.mData[R.mLength-1] = 1; // set first bit one byte higher than mod + + // find the multiplicative inverse of mod + gsiLargeIntInverseMod(&mod, &modPrime); + +/* + // calculate Rmod (R%mod) + if (gsi_is_false(gsLargeIntDiv(&R, &mod, NULL, &Rmod))) + { + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + + // calculate R2mod (R^2%mod = (Rmod*Rmod)%mod) + if (gsi_is_false(gsLargeIntSquareMod(&Rmod, &mod, &R2mod))) + { + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } +*/ + // Allocate space for a table of values that will come up repeatedly + // xwiggle is (br mod n) + // These are the odd powers of xwiggle, x^3, x^5 and so on + // We generate these by repeated multiplications by xwiggle + //if (k >= 3) + { + // no no no[0] = xwiggle^3 (montgomery multiply [2]*[1]) + // no no no[1] = xwiggle^5 (montgomery multiply [2]*[3]) + // no no no[2] = xwiggle^7 (montgomery multiply [2]*[5]) + + // allocate space + // ~1k for typical small RSA public exponents (e.g. 65537) + // ~16k for 1024-bit RSA exponent + // ~32k for 2048-bit RSA exponent + // ~64k for 4096-bit RSA exponent + int i=0; + int valuesNeeded = pow2k;//((pow2k/2)-1); + int spaceneeded = sizeof(gsLargeInt_t) * valuesNeeded; + + lut = (gsLargeInt_t*)gsimalloc(spaceneeded); + if (lut == NULL) + return gsi_false; // out of memory + memset(lut, 0x00, spaceneeded); + + // set first values + // [0] = 1 + // [1] = br mod n (normal multiplication) + // [i] = mont([1] * [i-1]) + gsLargeIntSetValue(&lut[0], 1); + if (gsi_is_false(gsLargeIntMult(&base, &R, &lut[1])) || + gsi_is_false(gsLargeIntDiv(&lut[1], &mod, NULL, &lut[1])) ) + { + gsifree(lut); + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + + // fill in the values + for (i=2; i < valuesNeeded; i++) + { + if (gsi_is_false(gsiLargeIntMultM(&lut[1], &lut[i-1], &mod, modPrime, &lut[i])) ) + { + gsifree(lut); + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + } + } + + // set starting point + if (gsi_is_false(gsLargeIntMult(&base, &R, dest)) || // Normal multiply + gsi_is_false(gsLargeIntDiv(dest, &mod, NULL, dest)) ) // A mod operation + { + gsifree(lut); + return gsi_false; + } + + // loop through the k-sized digits + for (i=0; i < kdigits; i++) + { + int bitReadIndex = expHighBit - (i*k); // index of the bit we're reading + int l_index; // = ((bitReadIndex-1)/GS_LARGEINT_DIGIT_SIZE_BITS); // -1 to use zero based indexes + int l_firstbit; + l_dword twodigits; + l_dword mask; + l_word digitval; + + l_index = ((bitReadIndex-1)/GS_LARGEINT_DIGIT_SIZE_BITS); // -1 to use zero based indexes + + // for first digit, use leading zeroes when necessary + if ((bitReadIndex % k) != 0) + bitReadIndex += k - (bitReadIndex % k); // round up to next k + if (i != 0) + { + if (bitReadIndex - (l_index*GS_LARGEINT_DIGIT_SIZE_BITS)> GS_LARGEINT_DIGIT_SIZE_BITS) + l_index++; + } + + if (i==0) + { + // first digit + l_firstbit = l_index * GS_LARGEINT_DIGIT_SIZE_BITS; // first bit of this digit + twodigits = p->mData[l_index]; + } + else if (l_index > 0) + { + // middle digits + l_firstbit = (l_index-1) * GS_LARGEINT_DIGIT_SIZE_BITS; // first bit of this digit + twodigits = (l_dword)((l_dword)p->mData[l_index] << GS_LARGEINT_DIGIT_SIZE_BITS) | p->mData[l_index-1]; + } + else if (l_index == 0 && p->mLength > 1) + { + // final digit, when there are proceeding digits + l_firstbit = 0; + twodigits = (l_dword)(p->mData[l_index+1] << GS_LARGEINT_DIGIT_SIZE_BITS) | p->mData[l_index]; + } + else + { + // final digit, no proceeding digits + l_firstbit = l_index * GS_LARGEINT_DIGIT_SIZE_BITS; // first bit of this digit + twodigits = p->mData[l_index]; + } + mask = (l_dword)kmask << (bitReadIndex-l_firstbit-k); + digitval = (l_word)((twodigits & mask) >> (bitReadIndex-l_firstbit-k)); + + // use digitval to determine how many squaring and multiplication operations we need to perform + { + static int twotab[] = + {0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, + 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, + 3, 0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, + 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0}; + + + static USHORT oddtab[] = + {0, 1, 1, 3, 1, 5, 3, 7, 1, 9, 5, 11, 3, 13, 7, 15, 1, 17, 9, 19, 5, 21, 11, 23, 3, 25, 13, 27, 7, 29, 15, 31, 1, + 33, 17, 35, 9, 37, 19, 39, 5, 41, 21, 43, 11, 45, 23, 47, 3, 49, 25, 51, 13, 53, 27, 55, 7, 57, 29, 59, 15, + 61, 31, 63, 1, 65, 33, 67, 17, 69, 35, 71, 9, 73, 37, 75, 19, 77, 39, 79, 5, 81, 41, 83, 21, 85, 43, 87, 11, + 89, 45, 91, 23, 93, 47, 95, 3, 97, 49, 99, 25, 101, 51, 103, 13, 105, 53, 107, 27, 109, 55, 111, 7, 113, + 57, 115, 29, 117, 59, 119, 15, 121, 61, 123, 31, 125, 63, 127, 1, 129, 65, 131, 33, 133, 67, 135, 17, + 137, 69, 139, 35, 141, 71, 143, 9, 145, 73, 147, 37, 149, 75, 151, 19, 153, 77, 155, 39, 157, 79, 159, + 5, 161, 81, 163, 41, 165, 83, 167, 21, 169, 85, 171, 43, 173, 87, 175, 11, 177, 89, 179, 45, 181, 91, + 183, 23, 185, 93, 187, 47, 189, 95, 191, 3, 193, 97, 195, 49, 197, 99, 199, 25, 201, 101, 203, 51, 205, + 103, 207, 13, 209, 105, 211, 53, 213, 107, 215, 27, 217, 109, 219, 55, 221, 111, 223, 7, 225, 113, + 227, 57, 229, 115, 231, 29, 233, 117, 235, 59, 237, 119, 239, 15, 241, 121, 243, 61, 245, 123, 247, 31, + 249, 125, 251, 63, 253, 127, 255}; + + + //printf("[gsint] Digit %d = %d\r\n", i, digitval); + if (i==0) + { + int counter = 0; + + memcpy(dest, &lut[oddtab[digitval]], sizeof(gsLargeInt_t)); + //printf("[gsint] Set start to %d\r\n", dest->mData[0]); + + for (counter = twotab[digitval]; counter> 0; counter--) + { + if (gsi_is_false(gsiLargeIntMultM(dest,dest, &mod, modPrime, dest))) + { + gsifree(lut); + return gsi_false; + } + //printf("[gsint] First digit, squared to %d\r\n", dest->mData[0]); + } + } + else if (digitval != 0) + { + int counter = 0; + int lutindex = oddtab[digitval]; // we only precalculate the odd powers + //int lutindex = (oddtab[digitval]+1)/2; // we only precalculate the odd powers + + for (counter = (int)(k-twotab[digitval]); counter> 0; counter--) + { + if (gsi_is_false(gsiLargeIntMultM(dest,dest, &mod, modPrime, dest))) + { + gsifree(lut); + return gsi_false; + } + //printf("[gsint] Squared to %d\r\n", dest->mData[0]); + } + + if (gsi_is_false(gsiLargeIntMultM(dest, &lut[lutindex], &mod, modPrime, dest))) + { + gsifree(lut); + return gsi_false; + } + //printf("[gsint] Mult by [%d](%d) to %d\r\n", lutindex, lut[lutindex].mData[0], dest->mData[0]); + for (counter = twotab[digitval]; counter> 0; counter--) + { + if (gsi_is_false(gsiLargeIntMultM(dest,dest, &mod, modPrime, dest))) + { + gsifree(lut); + return gsi_false; + } + //printf("[gsint] Squared to %d\r\n", dest->mData[0]); + } + } + else + { + int counter = 0; + for (counter = k; counter > 0; counter--) + { + if (gsi_is_false(gsiLargeIntMultM(dest,dest, &mod, modPrime, dest))) + { + gsifree(lut); + return gsi_false; + } + //printf("[gsint] Squared to %d\r\n", dest->mData[0]); + } + } + } + } + + // normalize (MultM by 1) + if (gsi_is_false(gsiLargeIntMultM(dest, &one, &mod, modPrime, dest))) + return gsi_false; + + gsifree(lut); + return gsi_true; +} + +#else + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Montgomery exponentiation (see HAC 14.94) +// +// SPECIAL NOTE: +// A small public exponent will reduce the load on client encryption. +// (below 65535 is a security risk, so don't go too small) +gsi_bool gsLargeIntPowerMod(const gsLargeInt_t *b, const gsLargeInt_t *p, const gsLargeInt_t *m, gsLargeInt_t *dest) +{ + int i=0; // temp/counter + int digitNum=0; // temp/counter + int digitBit=0; + + l_word modPrime; + + gsi_u32 expHighBit; // highest bit set in exponent; + + gsLargeInt_t R; // "R" as used in the montgomery exponentiation algorithm. + gsLargeInt_t Rmod; // R%mod + gsLargeInt_t R2mod; // R^2%mod + gsLargeInt_t temp; + gsLargeInt_t xwiggle; // montgomery mult of (x,R2mod) + + gsLargeInt_t base; + gsLargeInt_t power; + gsLargeInt_t mod; + + GSLINT_ENTERTIMER(GSLintTimerPowerMod); + + memset(&R, 0, sizeof(R)); + memset(&Rmod, 0, sizeof(Rmod)); + memset(&R2mod, 0, sizeof(R2mod)); + memset(&temp, 0, sizeof(temp)); + memset(&xwiggle, 0, sizeof(xwiggle)); + + memcpy(&base, b, sizeof(base)); + memcpy(&power, p, sizeof(power)); + memcpy(&mod, m, sizeof(mod)); + + gsiLargeIntStripLeadingZeroes(&base); + gsiLargeIntStripLeadingZeroes(&power); + gsiLargeIntStripLeadingZeroes(&mod); + + // Catch the unusual cases + if (mod.mLength == 0) + { + // mod 0 = undefined + dest->mLength = 0; + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + else if (mod.mLength==1 && mod.mData[0]==1) + { + // mod 1 = 0 + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_true; + } + else if (power.mLength == 0) + { + // x^0 = 1 + dest->mLength = 1; + dest->mData[0] = 1; + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_true; + } + else if ((mod.mData[0]&1) == 0) + { + // Montgomery only works with odd modulus! + // (rsa modulus is prime1*prime2, which must be odd) + dest->mLength = 0; + dest->mData[0] = 0; + //_asm {int 3} + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + // If base is larger than mod, we can (must) reduce it + if (gsiLargeIntCompare(base.mData, base.mLength, mod.mData, mod.mLength)!=-1) + { + gsLargeIntDiv(&base, &mod, NULL, &base); + } + if (base.mLength == 0) + { + // 0^e = 0 + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_true; + } + + // find the highest bit set in power + expHighBit=GS_LARGEINT_DIGIT_SIZE_BITS; + while(((1<<(expHighBit-1))&power.mData[power.mLength-1]) == 0) + expHighBit--; + expHighBit += ((power.mLength-1) * GS_LARGEINT_DIGIT_SIZE_BITS); // add in 32 bits for each extra byte + + // On to the tricky tricky! + // 1) We can't compute B^P and later apply the mod; B^P is just too big + // So we have to make modular reductions along the way + // 2) Since modular reduction is essentially a division, we would like + // to use a mod 2^E so that division is just a bit strip. + // ex. (1383 mod 16) = binary(0000010101100111 mod 00010000) = 00000111 = dec 7 + + // Precalculate some values that will come up repeatedly + + // calculate "R" (if mod=5678, R=10000 e.g. One digit higher) + memset(&R, 0, sizeof(R)); + R.mLength = (l_word)(mod.mLength+1); + if (R.mLength > GS_LARGEINT_MAX_DIGITS) + return gsi_false; // you need to increase the large int capacity + R.mData[R.mLength-1] = 1; // set first bit one byte higher than mod + + // find the multiplicative inverse of mod + gsiLargeIntInverseMod(&mod, &modPrime); + + // calculate Rmod (R%mod) + if (gsi_is_false(gsLargeIntDiv(&R, &mod, NULL, &Rmod))) + { + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + + // calculate R2mod (R^2%mod = (Rmod*Rmod)%mod) + if (gsi_is_false(gsLargeIntSquareMod(&Rmod, &mod, &R2mod))) + { + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + + // calculate xwiggle + if (gsi_is_false(gsiLargeIntMultM(&base, &R2mod, &mod, modPrime, &xwiggle))) + { + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + + // loop through the BITS of power + // if the bit is 1, perform a multiplication by xwiggle? (11/2/2006) + // TODO: THIS DOESN'T WORK IF THE HIGHBIT IS EVER ABOVE GS_LARGEINT_DIGIT_SIZE_BITS + memcpy(dest, &Rmod, sizeof(gsLargeInt_t)); // start dest at Rmod + for (i=(int)(expHighBit-1); i>=0; i--) + { + // mont square the current total + gsiLargeIntMultM(dest, dest, &mod, modPrime, dest); + digitNum = (gsi_i32)(i/GS_LARGEINT_DIGIT_SIZE_BITS); // which digit to extract a bit from? + digitBit = (gsi_i32)(i % GS_LARGEINT_DIGIT_SIZE_BITS); // which bit to extract from that digit? + //if ((power.mData[k] & (1<mLength; + + memset(temp, 0, sizeof(temp)); + + if (gsi_is_false(gsiLargeIntMult(x->mData, x->mLength, y->mData, y->mLength, temp, &tempLen, GS_LARGEINT_MAX_DIGITS*2))) + return gsi_false; + + lasttnptr = &temp[m->mLength-1]; + lastnptr = &m->mData[m->mLength-1]; + + if (tempLen < m->mLength*2) + { + memset(&temp[tempLen], 0, (m->mLength*2 - tempLen) * GS_LARGEINT_DIGIT_SIZE_BYTES); + //memset(&temp[tempLen], 0, sizeof(temp) - tempLen * GS_LARGEINT_DIGIT_SIZE_BYTES); // safer to clear out the whole thing? + tempLen = (l_word)(m->mLength*2); + } + + for (tptr = &temp[0]; tptr <= lasttnptr; tptr++) + { + carry = 0; + mi = (l_word)((l_dword)modPrime * (l_dword)*tptr); + tiptr = tptr; + for (nptr = &m->mData[0]; nptr <= lastnptr; nptr++, tiptr++) + { + carry = (l_dword)mi * (l_dword)*nptr + + (l_dword)*tiptr + (l_dword)(l_word)(carry >> GS_LARGEINT_DIGIT_SIZE_BITS); + *tiptr = (l_word)(carry); + } + + // apply the carry value + for (; ((carry >> GS_LARGEINT_DIGIT_SIZE_BITS) > 0) && tiptr <= &temp[tempLen-1]; tiptr++) + { + *tiptr = (l_word)(carry = (l_dword)*tiptr + (l_dword)(l_word)(carry >> GS_LARGEINT_DIGIT_SIZE_BITS)); + } + + // If we still have a carry, increase the length of temp + if (((carry >> GS_LARGEINT_DIGIT_SIZE_BITS) > 0)) + { + *tiptr = (l_word)(carry >> GS_LARGEINT_DIGIT_SIZE_BITS); + tempLen++; + } + } + + // **WARNING** + // Bytes from the plain text message may appear within the temporary buffer. + // These bytes should be cleared to prevent bugs where that data may be exposed. (buffer overrun?) + if (gsiLargeIntCompare(&temp[logB_r], tempLen - logB_r, m->mData, m->mLength) != -1) + { + if (gsi_is_false(gsiLargeIntSub(m->mData, m->mLength, &temp[logB_r], tempLen - logB_r, dest->mData, &dest->mLength))) + { + memset(temp, 0, sizeof(temp)); + memset(dest, 0, sizeof(gsLargeInt_t)); + return gsi_false; + } + } + else + { + memset(dest, 0, sizeof(gsLargeInt_t)); + dest->mLength = m->mLength; + memcpy(dest->mData, &temp[logB_r], (tempLen - logB_r)*GS_LARGEINT_DIGIT_SIZE_BYTES); + memset(temp, 0, sizeof(temp)); + } + + return gsi_true; +} + +#else + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Montgomery multiplication +// Computes (src1*src2*r^-1)%mod +// Note: +// This implementation is based on HAC14.36 which has a lot of room for improvement +// FLINT algorithm runs approx 30 times faster. +gsi_bool gsiLargeIntMultM(gsLargeInt_t *x, gsLargeInt_t *y, const gsLargeInt_t *m, gsi_u32 modPrime, gsLargeInt_t *dest) +{ + int i=0; + l_dword xiy0; + l_word u = 0; + + gsLargeInt_t A; + gsLargeInt_t xiy; + gsLargeInt_t temp; + + GSLINT_ENTERTIMER(GSLintTimerMultM); + + gsiLargeIntStripLeadingZeroes(x); + gsiLargeIntStripLeadingZeroes(y); + + // Check inputs + i=(int)(m->mLength); + while(i>0 && m->mData[i-1]==0) + i--; + if (i==0) + { + // modulus is zero, answer undefined + dest->mData[0] = 0; + dest->mLength = 0; + GSLINT_EXITTIMER(GSLintTimerMultM); + return gsi_false; + } + if (x->mLength==0) + { + // x == 0 + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerMultM); + return gsi_true; + } + if (y->mLength==0) + { + // y == 0 + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerMultM); + return gsi_true; + } + + // We pad with zeroes so that we don't have to check for overruns in the loop below + // (note: resize will not remove non-zero digits from x or y) + gsiLargeIntResize(x, m->mLength); + gsiLargeIntResize(y, m->mLength); + + // Continue with the Multiplication + memset(&A, 0, sizeof(A)); + memset(&temp, 0, sizeof(temp)); + memset(&xiy, 0, sizeof(xiy)); + + for (i=0; (gsi_u32)i < m->mLength; i++) + { + xiy0 = (l_dword)x->mData[i]*y->mData[0]; // y[0], NOT y[i] !! + u = (l_word)((xiy0+A.mData[0])*modPrime); // strip bits over the first digit + + // A = (A+x[i]*y + u[i]*m)/b + // compute x[i]*y + memset(temp.mData, 0, y->mLength*sizeof(l_word)); // clear out a portion of temp + temp.mData[0] = x->mData[i]; + temp.mLength = y->mLength; // xi padded with zeroes + if (gsi_is_false(gsiLargeIntMult(temp.mData, temp.mLength, y->mData, y->mLength, xiy.mData, &xiy.mLength, GS_LARGEINT_MAX_DIGITS))) + { + // overflow + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerMultM); + return gsi_false; + } + // compute u[i]*m + memset(temp.mData, 0, m->mLength*sizeof(l_word)); // clear out a portion of temp + temp.mData[0] = u; + temp.mLength = m->mLength; + //if (gsi_is_false(gsiLargeIntMult(temp.mData, temp.mLength, m->mData, m->mLength, temp.mData, &temp.mLength))) + if (gsi_is_false(gsLargeIntKMult(&temp, m, &temp))) + { + // overflow + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerMultM); + return gsi_false; + } + // Add both to A + if (gsi_is_false(gsiLargeIntAdd(xiy.mData, xiy.mLength, A.mData, A.mLength, A.mData, &A.mLength, GS_LARGEINT_MAX_DIGITS))) + { + // overflow + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerMultM); + return gsi_false; + } + if (gsi_is_false(gsiLargeIntAdd(temp.mData, temp.mLength, A.mData, A.mLength, A.mData, &A.mLength, GS_LARGEINT_MAX_DIGITS))) + { + // overflow + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerMultM); + return gsi_false; + } + // Divide by b (e.g. Remove first digit from A) + if (A.mLength > 1) + { + memmove(&A.mData[0], &A.mData[1], (A.mLength-1)*sizeof(l_word)); + A.mData[A.mLength-1] = 0; + A.mLength--; + } + else + { + A.mLength = 0; + A.mData[0] = 0; + } + } + + //if (A >= m then subtract another m) + if (gsiLargeIntCompare(A.mData, A.mLength, m->mData, m->mLength)!=-1) + gsiLargeIntSub(m->mData, m->mLength, A.mData, A.mLength, dest->mData, &dest->mLength); + else + memcpy(dest, &A, sizeof(A)); + GSLINT_EXITTIMER(GSLintTimerMultM); + return gsi_true; +} + +#endif +/* +// Computes (src*src*r^-1)%mod +static gsi_bool gsiLargeIntSquareM(const gsLargeInt_t *src, const gsLargeInt_t *mod, gsi_u32 modPrime, gsi_u32 R, gsLargeInt_t *dest) +{ + GSI_UNUSED(src); + GSI_UNUSED(mod); + GSI_UNUSED(modPrime); + GSI_UNUSED(R); + GSI_UNUSED(dest); + assert(0); + return gsi_true; +}*/ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Calculate multiplicative inverse of mod, (-mod^-1 mod 2^R) +// ala. Dusse and Kaliski, extended Euclidean algorithm +gsi_bool gsiLargeIntInverseMod(const gsLargeInt_t *mod, l_word *dest) +{ + l_dword x=2; + l_dword y=1; + l_dword check = 0; + + gsi_u32 i; + for (i = 2; i <= GS_LARGEINT_DIGIT_SIZE_BITS; i++) + { + check = (l_dword)mod->mData[0] * (l_dword)y; + if (x < (check & ((x<<1)-1))) + y += x; + x = x << 1; + } + *dest = (l_word)(x-y); + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsLargeIntPrint(FILE* logFile, const gsLargeInt_t *lint) +{ + return gsiLargeIntPrint(logFile, lint->mData, lint->mLength); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsiLargeIntPrint(FILE* logFile, const l_word *data, l_word length) +{ +// this is only specific to NITRO since for other platforms the fprintf will +// resolve to a STDOUT +#if !defined(_NITRO) + while(length >0) + { + fprintf(logFile, "%08X", data[length-1]); + length--; + } + fprintf(logFile, "\r\n"); + return gsi_true; +#else + GSI_UNUSED(logFile); + GSI_UNUSED(data); + GSI_UNUSED(length); + return gsi_false; +#endif +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// stream of bytes, big endian. (first byte = most significant digit) +gsi_bool gsLargeIntSetFromHexString(gsLargeInt_t *lint, const char* hexstream) +{ + l_word* writePos = lint->mData; + gsi_u32 temp; + int len = 0; + int byteIndex = 0; + + GS_ASSERT(hexstream != NULL); + + len = (int)strlen(hexstream); + if (len == 0) + { + lint->mLength = 0; + lint->mData[0] = 0; + return gsi_true; + } + if ((len/2) > (GS_LARGEINT_MAX_DIGITS*GS_LARGEINT_DIGIT_SIZE_BYTES)) + return gsi_false; + + // 2 characters per byte, 4 bytes per integer + lint->mLength = (l_word)((len+(2*GS_LARGEINT_DIGIT_SIZE_BYTES-1))/(2*GS_LARGEINT_DIGIT_SIZE_BYTES)); + lint->mData[lint->mLength-1] = 0; // set last byte to zero for left over characters + + while(len > 0) + { + if(len >= 2) + sscanf((char*)(hexstream+len-2), "%02x", &temp); // sscanf requires a 4 byte dest + else + sscanf((char*)(hexstream+len-1), "%01x", &temp); // sscanf requires a 4 byte dest + if(byteIndex == 0) + *writePos = 0; + *writePos |= (temp << (byteIndex * 8)); + if(++byteIndex == GS_LARGEINT_DIGIT_SIZE_BYTES) + { + writePos++; + byteIndex = 0; + } + len-=min(2,len); + } + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Reverse bytes in a LINT, which are LittleEndian +// ex: Packing an RSA message of which the first bytes are 0x00 0x02 +// The first bytes of the packet must become the MSD of the LINT +gsi_bool gsLargeIntReverseBytes(gsLargeInt_t *lint) +{ +#if defined(GSI_LITTLE_ENDIAN) + char *left = (char*)&lint->mData[0]; + char *right = ((char*)&lint->mData[lint->mLength])-1; + char temp; +#else + l_word *left = lint->mData; + l_word *right = lint->mData + (lint->mLength - 1); + l_word temp; +#endif + + + if (lint->mLength == 0) + return gsi_true; + + while(left < right) + { + temp = *left; + (*left++) = (*right); + (*right--) = temp; + } + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// hashing is made complicated by differing byte orders +void gsLargeIntAddToMD5(const gsLargeInt_t * _lint, MD5_CTX * md5) +{ + int byteLength = 0; + gsi_u8 * dataStart = NULL; + + // Create a non-const copy so we can reverse bytes to add to the MD5 hash + gsLargeInt_t lint; + memcpy(&lint, _lint, sizeof(lint)); + + // first, calculate the byte length + byteLength = (int)gsLargeIntGetByteLength(&lint); + if (byteLength == 0) + return; // no data + + dataStart = (gsi_u8*)lint.mData; + if ((byteLength % GS_LARGEINT_DIGIT_SIZE_BYTES) != 0) + dataStart += GS_LARGEINT_DIGIT_SIZE_BYTES - (byteLength % GS_LARGEINT_DIGIT_SIZE_BYTES); + + // reverse to big-endian (MS) then hash + gsLargeIntReverseBytes(&lint); + MD5Update(md5, dataStart, (unsigned int)byteLength); + gsLargeIntReverseBytes(&lint); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Length in bytes so leading zeroes can be dropped from hex strings +gsi_u32 gsLargeIntGetByteLength(const gsLargeInt_t *lint) +{ + int intSize = (int)lint->mLength; + int byteSize = 0; + int i=0; + l_word mask = 0xFF; + + // skip leading zeroes + while(intSize > 0 && lint->mData[intSize-1] == 0) + intSize --; + if (intSize == 0) + return 0; // no data + + byteSize = intSize * (gsi_i32)sizeof(l_word); + + // subtract bytes for each leading 0x00 byte + mask = 0xFF; + for (i=1; i < GS_LARGEINT_DIGIT_SIZE_BYTES; i++) + { + if (lint->mData[intSize-1] <= mask) + { + byteSize -= sizeof(l_word)-i; + break; + } + mask = (l_word)((mask << 8) | 0xFF); + } + + return (gsi_u32)byteSize; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Creates a large int from a byte buffer +// Essentially, constructs the array of digits in appropriate byte order +gsi_bool gsLargeIntSetFromMemoryStream(gsLargeInt_t *lint, const gsi_u8* data, gsi_u32 len) +{ + lint->mData[0] = 0; + memcpy(((char*)lint->mData)+(4-len%4)%4, data, len); + + // Set length to ceiling of len/digit_size + lint->mLength = (unsigned int)((len+(GS_LARGEINT_DIGIT_SIZE_BYTES-1))/GS_LARGEINT_DIGIT_SIZE_BYTES); + + return gsLargeIntReverseBytes(lint); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsLargeIntWriteToMemoryStream(const gsLargeInt_t *lint, gsi_u8* data) +{ + gsLargeInt_t copy; + memcpy(©, lint, sizeof(gsLargeInt_t)); + + gsLargeIntReverseBytes(©); + + memcpy(data, copy.mData, copy.mLength * GS_LARGEINT_DIGIT_SIZE_BYTES); + return gsi_true; +} diff --git a/code/gamespy/common/gsLargeInt.h b/code/gamespy/common/gsLargeInt.h new file mode 100644 index 00000000..2ca665c7 --- /dev/null +++ b/code/gamespy/common/gsLargeInt.h @@ -0,0 +1,88 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Large Integer Library +#ifndef __GSLARGEINT_H__ +#define __GSLARGEINT_H__ + +#include "gsCommon.h" +#include "gsXML.h" +#include "../md5.h" + + +#if defined(__cplusplus) +extern "C" { +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +// for simplicity, set the binary size to a value > gsCrypt.h binary size +#ifndef GS_LARGEINT_BINARY_SIZE +#define GS_LARGEINT_BINARY_SIZE (2048) // *BIT* size (divide by 8 for byte size) +#endif + + // !!!!!!WARNING!!!!!! Encryption is fastest when digit type is the default system type, (ex: gsi_u32 on 32bit processor) +#define GS_LARGEINT_DIGIT_TYPE gsi_u32 +#define GS_LARGEINT_DIGIT_LONG_TYPE gsi_u64 + +#define GS_LARGEINT_DIGIT_SIZE_BYTES (sizeof(GS_LARGEINT_DIGIT_TYPE)) +#define GS_LARGEINT_DIGIT_SIZE_BITS (GS_LARGEINT_DIGIT_SIZE_BYTES*8) + +// short forms for legibility +#define l_word GS_LARGEINT_DIGIT_TYPE +#define l_dword GS_LARGEINT_DIGIT_LONG_TYPE + +//#define GS_LARGEINT_BYTE_SIZE 32 // binary size of system data type +//#define GS_LARGEINT_INT_SIZE (GS_LARGEINT_BINARY_SIZE/GS_LARGEINT_BYTE_SIZE) // size in values +#define GS_LARGEINT_MAX_DIGITS (GS_LARGEINT_BINARY_SIZE / GS_LARGEINT_DIGIT_SIZE_BITS) + +#define GS_LARGEINT_KARATSUBA_CUTOFF 32 + +typedef struct gsLargeInt_s +{ + GS_LARGEINT_DIGIT_TYPE mLength; + GS_LARGEINT_DIGIT_TYPE mData[GS_LARGEINT_MAX_DIGITS]; +} gsLargeInt_t; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Commonly used functions +void gsLargeIntAddToMD5(const gsLargeInt_t * lint, MD5_CTX * md5); +gsi_bool gsLargeIntSetFromHexString(gsLargeInt_t *lint, const char* hexstring); +gsi_bool gsLargeIntPrint (FILE* logFile, const gsLargeInt_t *lint); +gsi_u32 gsLargeIntGetByteLength(const gsLargeInt_t *lint); + + // Modular exponentiation (and helpers) + // -- uses Montgomery exponentiation, reduction, multiplication +gsi_bool gsLargeIntPowerMod(const gsLargeInt_t *base, const gsLargeInt_t *power, const gsLargeInt_t *mod, gsLargeInt_t *dest); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsLargeIntSquareMod(const gsLargeInt_t *lint, const gsLargeInt_t *mod, gsLargeInt_t *dest); +gsi_bool gsLargeIntAdd (const gsLargeInt_t *src1, const gsLargeInt_t *src2, gsLargeInt_t *dest); +gsi_bool gsLargeIntSub (const gsLargeInt_t *src1, const gsLargeInt_t *fromsrc2, gsLargeInt_t *dest); +gsi_bool gsLargeIntMult (const gsLargeInt_t *src1, const gsLargeInt_t *src2, gsLargeInt_t *dest); +gsi_bool gsLargeIntDiv (const gsLargeInt_t *src1, const gsLargeInt_t *divisor, gsLargeInt_t *dest, gsLargeInt_t *remainder); + + //Karatsuba requires that the sizes be equal and a power of two +gsi_bool gsLargeIntKMult(const gsLargeInt_t *src1, const gsLargeInt_t *src2, gsLargeInt_t *dest); + + //This is useful when packing a BigEndian message directly into a LittleEndian lint buffer. +gsi_bool gsLargeIntReverseBytes(gsLargeInt_t *lint); +gsi_bool gsLargeIntSetValue(gsLargeInt_t *lint, l_word value); + + //These are necessary to preserve byte order when copying from array-of-int to char* +gsi_bool gsLargeIntSetFromMemoryStream(gsLargeInt_t *lint, const gsi_u8* data, gsi_u32 len); +gsi_bool gsLargeIntWriteToMemoryStream(const gsLargeInt_t *lint, gsi_u8* data); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} +#endif + +#endif // __GSLARGEINT_H__ diff --git a/code/gamespy/common/gsMemory.c b/code/gamespy/common/gsMemory.c new file mode 100644 index 00000000..8b615607 --- /dev/null +++ b/code/gamespy/common/gsMemory.c @@ -0,0 +1,1774 @@ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsPlatform.h" +#include "gsPlatformUtil.h" +#include "gsMemory.h" +#include "gsAssert.h" +#include "gsDebug.h" + +#ifdef _PSP + #include +#endif + +// toDo: move some of this to platform.h +#ifdef _PS3 + #if(0) + typedef gsi_u64 gsi_uint; + #define PTR_ALIGNMENT 32 + #define GSI_64BIT (1) + #define GS_BIG_ENDIAN + #else + // changed as of SDK 0.8 Sony moved back to using 32 bit pointers + typedef gsi_u32 gsi_uint; + #define PTR_ALIGNMENT 16 + #define GSI_64BIT (0) + #define GS_BIG_ENDIAN + #endif +#else + typedef gsi_u32 gsi_uint; + #define PTR_ALIGNMENT 16 + #define GSI_64BIT (0) +#endif + + + +// To Do: +// Small block optimization using fixed size mempool. +// add multi-threaded support + +#define MEM_PROFILE (1) // if on additional memprofiling code will be enabled for such things as high water mark calcs +#if defined(MEM_PROFILE) + #define IF_MEM_PROFILE_ISON(a) a +#else + #define IF_MEM_PROFILE_ISON(a) +#endif + +// Disable compiler warnings for issues that are unavoidable. +///////////////////////////////////////////////////////////// +#if defined(_MSC_VER) // DevStudio + // Level4, "conditional expression is constant". + // Occurs with use of the MS provided macro FD_SET + #pragma warning ( disable: 4127 ) +#include +#endif // _MSC_VER + +#ifdef _WIN32 + #define MEM_MANAGER_CALL _cdecl +#else + #define MEM_MANAGER_CALL +#endif + +//#if !defined(_WIN32) +// #define MEM_MANAGER_DIRECT +//#endif + +typedef struct +{ + void* (MEM_MANAGER_CALL *malloc )(size_t size); + void (MEM_MANAGER_CALL *free )(void* ptr); + void* (MEM_MANAGER_CALL *realloc )(void* ptr, size_t size); + void* (MEM_MANAGER_CALL *memalign)(size_t boundary, size_t size); +}MemManagerCallbacks; + +static void* MEM_MANAGER_CALL _gsi_malloc(size_t size) +{ + return malloc(size); +} + +static void MEM_MANAGER_CALL _gsi_free(void* ptr) +{ + free(ptr); +} + +static void* MEM_MANAGER_CALL _gsi_realloc(void* ptr, size_t size) +{ + return realloc(ptr, size); +} + +#if defined(_PS2) || defined(_PSP) || defined(_PS3) + static void* _gsi_memalign(size_t boundary, size_t size) + { + return memalign(boundary, size); + } +#elif defined (_WIN32) + #if (_MSC_VER < 1300) + //extern added for vc6 compatability. + extern void* __cdecl _aligned_malloc(size_t size, int boundary); + #endif + static void* __cdecl _gsi_memalign(size_t boundary, size_t size) + { + return _aligned_malloc(size, (int)boundary); + } +#else + // no built in system memalign + static void* _gsi_memalign(size_t boundary, size_t size) + { + void *ptr = calloc((size)/(boundary), (boundary)); + // check alignment + GS_ASSERT((((gsi_u32)ptr)% boundary)==0); + return ptr; + } +#endif + +static MemManagerCallbacks memmanagercallbacks = +{ +#ifdef MEM_MANAGER_DIRECT + &malloc, + &free, + &realloc, + #if defined(_PS2) || defined(_PSP) || defined(_PS3) + &memalign, // a version already exists on this platform + #else + &_gsi_memalign, //wrote our own + #endif +#else + &_gsi_malloc, + &_gsi_free, + &_gsi_realloc, + &_gsi_memalign +#endif +}; + + +void gsiMemoryCallbacksSet(gsMallocCB p_malloc, gsFreeCB p_free, gsReallocCB p_realloc, gsMemalignCB p_memalign) +{ + + memmanagercallbacks.malloc = p_malloc; + memmanagercallbacks.free = p_free; + memmanagercallbacks.realloc = p_realloc; + memmanagercallbacks.memalign = p_memalign; +} + + + + + + +// These functions shunt to virtual function pointer +void* gsimalloc (size_t size) +{ + return (*memmanagercallbacks.malloc)(size); +} +void* gsirealloc (void* ptr, size_t size) +{ + return (*memmanagercallbacks.realloc)(ptr,size); +} +void gsifree (void* ptr) +{ + if(ptr == NULL) + return; + (*memmanagercallbacks.free)(ptr); +} +void* gsimemalign (size_t boundary, size_t size) +{ + return (*memmanagercallbacks.memalign)(boundary,size); +} + + + +#ifdef GSI_MEM_MANAGED + + + + +/***************************************************************************/ +/* + + Random Access Memory Pool + +*/ +/***************************************************************************/ + + +// Context Stack +#define MEM_CONTEXT_STACK_MAX 10 // max stack depth +static gsMemMgrContext MemTypeStack [MEM_CONTEXT_STACK_MAX] = {gsMemMgrContext_Default}; +static gsi_u32 MemTypeStackIndex = 0; +extern gsMemMgrContext gsMemMgrContextCurrent; + +// Memtype Tag stack +#define MEM_TAG_STACK_MAX 10 // max stack depth +static gsi_u8 MemTagStack [MEM_TAG_STACK_MAX] = {0}; +static gsi_u32 MemTagStackIndex = 0; + + +// ToDo: +// - Add 64 bit pointer support + + + +// Default pointer alignment. Must be 16, 32, 64, 128, or 256 bytes. +// i.e. malloc (x) = memalign(default alignment,x); + + + +#define MEM_IS_POWER_OF_2(x) (((x) & ((x)-1)) == 0) +#define MEMALIGN_POWEROF2(x,a) (((gsi_uint)(x)+(a-1)) &~ ( ((gsi_uint)(a)) -1)) + +#if(1) // enable assert, otherwise this runs faster + #define MP_ASSERT(x) GS_ASSERT(x) +#else + #define MP_ASSERT(x) +#endif + + +#define MEM_TYPES_MAX 127 + + +typedef struct +{ + gsi_u32 MemTotal; + gsi_u32 MemAvail; + gsi_u32 MemUsed; + gsi_u32 MemUsed_At_HighWater; + gsi_u32 MemWasted; // overhead memory + memory lost due to fragmentation. + + gsi_u32 ChunksCount; // number of ChunkHeaders in linked list. + gsi_u32 ChunksFreeCount; // number of free ChunkHeaders in linked list. + gsi_u32 ChunksFreeLargestAvail; + // these are the same as handles + gsi_u32 ChunksUsedCount; // number of ChunkHeaders which are in use. + gsi_u32 ChunksUsedCount_At_HighWater; // the most handles used at any one time + + // memtype specifics + gsi_u32 MemType_ChunksCount [MEM_TYPES_MAX]; + gsi_u32 MemType_MemUsed [MEM_TYPES_MAX]; + gsi_u32 MemType_MemUsed_At_HighWater [MEM_TYPES_MAX]; + + +} MEM_STATS; + +void MEM_STATSAddAll (MEM_STATS *_this, const MEM_STATS *ms); +void MEM_STATSClear (MEM_STATS *_this); +// except HW +void MEM_STATSClearAll (MEM_STATS *_this); + + +// RA_MEM_CHUNK +typedef struct tMEM_CHUNK +{ + + // private + union + { + gsi_uint MemUsed; // size used by application. ex// malloc(size) + #ifdef GS_BIG_ENDIAN + struct + { + #if (GSI_64BIT) + char pad[7],MemType; + #else + char pad[3],MemType; + #endif + }MEM_TypeStruct; + #else + struct + { + #if (GSI_64BIT) + char MemType,pad[7]; + #else + char MemType,pad[3]; + #endif + } MEM_TypeStruct; + #endif + } MEM_UsageStat; + + // public: + // double linked list of all chunks + struct tMEM_CHUNK *prev; + struct tMEM_CHUNK *next; // next chunk + // single linked list of free chunks + struct tMEM_CHUNK *NextFree; // next free chunk +} MEM_CHUNK; + + + +/***************************************/ +// flag as in use, set size, memtype +void MEM_CHUNKAlloc (MEM_CHUNK *_this, gsi_u8 _MemType, size_t _UsedSize) +{ + _UsedSize = MEMALIGN_POWEROF2(_UsedSize,4); //The lower 2 bits are zero, so we don't store them. + GS_ASSERT_STR(_UsedSize < 0x3FFFFFC, "Alloc Memory size is too big."); + _this->MEM_UsageStat.MemUsed = _UsedSize<<6; + _this->MEM_UsageStat.MEM_TypeStruct.MemType = _MemType; +} +void MEM_CHUNKFree (MEM_CHUNK *_this) +{ + _this->MEM_UsageStat.MemUsed = 0; +} + +/***************************************/ +// returns true if not in use +gsi_bool MEM_CHUNKIsFree (MEM_CHUNK *_this) +{ + return (_this->MEM_UsageStat.MemUsed == 0); +} + +/***************************************/ +gsi_u32 MEM_CHUNKTotalSizeGet(MEM_CHUNK *_this) +// Total size chunk is using up, including header. +{ + if (!_this->next) + { + return PTR_ALIGNMENT + sizeof(MEM_CHUNK)/*Nub*/; + } + return (gsi_uint) _this->next - (gsi_uint) _this; +} + +/***************************************/ +gsi_u32 MEM_CHUNKChunkSizeGet(MEM_CHUNK *_this) +// size of chunk, without header. "Available memory" +{ + if (!_this->next) + return PTR_ALIGNMENT;/*Nub*/; + return (gsi_uint) _this->next - (gsi_uint) _this - sizeof(MEM_CHUNK); +} + +gsi_u32 MEM_CHUNKMemUsedGet (MEM_CHUNK *_this) +{ + return (_this->MEM_UsageStat.MemUsed & ~0xFF)>>6; +} + +void MEM_CHUNKMemUsedSet (MEM_CHUNK *_this, gsi_u32 size) +{ + _this->MEM_UsageStat.MemUsed = (MEMALIGN_POWEROF2(size,4)<<6) + _this->MEM_UsageStat.MEM_TypeStruct.MemType; +} + +gsi_u32 MEM_CHUNKMemAvailGet(MEM_CHUNK *_this) +{ + return MEM_CHUNKChunkSizeGet(_this) - MEM_CHUNKMemUsedGet(_this); +} + +char MEM_CHUNKMemTypeGet (MEM_CHUNK *_this) +{ + return _this->MEM_UsageStat.MEM_TypeStruct.MemType; +} + +void MEM_CHUNKMemTypeSet (MEM_CHUNK *_this, char _MemType) +{ + GS_ASSERT(_MemType < MEM_TYPES_MAX); + _this->MEM_UsageStat.MEM_TypeStruct.MemType = _MemType; +} + +void* MEM_CHUNKMemPtrGet (MEM_CHUNK *_this) +{ + return (void*)((gsi_uint) _this + sizeof(MEM_CHUNK)); +} + +/*inline */MEM_CHUNK *Ptr_To_MEM_CHUNK(void *ptr) +{ + return ((MEM_CHUNK *)ptr)-1; +} + +/***************************************/ +/***************************************/ +typedef struct MEM_CHUNK_POOL +{ + // public: + char Name[20]; // name of this pool. Used for debug purposes + // private: + MEM_CHUNK *HeaderStart; + MEM_CHUNK *HeaderEnd; + MEM_CHUNK *pFirstFree; + gsi_u32 HeapSize; + #if MEM_PROFILE + gsi_u32 HWMemUsed; + gsi_u32 MemUsed; + #endif +} MEM_CHUNK_POOL; + +// private +MEM_CHUNK *MEM_CHUNK_POOLFindPreviousFreeChunk (MEM_CHUNK_POOL *_this, MEM_CHUNK *header); +MEM_CHUNK *MEM_CHUNK_POOLFindNextFreeChunk (MEM_CHUNK_POOL *_this, MEM_CHUNK *header); +void MEM_CHUNK_POOLSplitChunk (MEM_CHUNK_POOL *_this, MEM_CHUNK *header,gsi_bool ReAlloc); +void MEM_CHUNK_POOLFreeChunk (MEM_CHUNK_POOL *_this, MEM_CHUNK *header); +MEM_CHUNK *MEM_CHUNK_POOLAllocChunk (MEM_CHUNK_POOL *_this, size_t Size,int Alignment , gsi_bool Backwards );//int Alignment = PTR_ALIGNMENT, gsi_bool Backwards = gsi_false); + +// move a chunk within the limits of prev + prev_size and next - this_size +void MEM_CHUNK_POOLChunkMove (MEM_CHUNK_POOL *_this, MEM_CHUNK *oldpos, MEM_CHUNK *newpos); + +// public +/***************************************/ +void MEM_CHUNK_POOLCreate (MEM_CHUNK_POOL *_this, const char *szName, char *ptr, gsi_u32 _size); +void MEM_CHUNK_POOLDestroy (MEM_CHUNK_POOL *_this) ; +gsi_bool MEM_CHUNK_POOLIsValid (MEM_CHUNK_POOL *_this) +{ + return _this->HeapSize > 0; +} + + +/***************************************/ +void *MEM_CHUNK_POOLmalloc (MEM_CHUNK_POOL *_this, size_t Size, gsi_i32 Alignment );//= PTR_ALIGNMENT); +// allocated backwards from top of heap +void *MEM_CHUNK_POOLmalloc_backwards (MEM_CHUNK_POOL *_this, size_t Size, gsi_i32 Alignment );//= PTR_ALIGNMENT); +void *MEM_CHUNK_POOLrealloc (MEM_CHUNK_POOL *_this, void *oldmem, size_t newSize); +void MEM_CHUNK_POOLfree (MEM_CHUNK_POOL *_this, void *mem); + +/***************************************/ +void MEM_CHUNK_POOLCheckValidity (MEM_CHUNK_POOL *_this ); +void MEM_CHUNK_POOLMemStatsGet (MEM_CHUNK_POOL *_this, MEM_STATS *stats); +gsi_u32 MEM_CHUNK_POOLWalkForType (MEM_CHUNK_POOL *_this, int _MemType, gsi_bool _LogUse); + +// returns true if this is a valid heap ptr +gsi_bool MEM_CHUNK_POOLIsHeapPtr (MEM_CHUNK_POOL *_this, void * mem); + +/***************************************/ +// add to table, filling in memtype . +void MEM_CHUNK_POOLFillMemoryTable (MEM_CHUNK_POOL *_this, char *Table, const int TableSize, gsi_u32 _HeapStart, gsi_u32 _HeapSize); + +/***************************************/ +// returns true if mem handle is in range of heap +gsi_bool MEM_CHUNK_POOLItemIsInPoolMemory (MEM_CHUNK_POOL *_this, void *ptr) +{ + GS_ASSERT(MEM_CHUNK_POOLIsValid(_this)); + return (((gsi_uint)ptr >= (gsi_uint)MEM_CHUNKMemPtrGet(_this->HeaderStart)) &&((gsi_uint)ptr <= (gsi_uint)MEM_CHUNKMemPtrGet(_this->HeaderEnd))); +} + + + + + + + + + + +void MEM_STATSAddAll(MEM_STATS *_this, const MEM_STATS *ms) +{ + int i; + _this->MemTotal += ms->MemTotal ; + _this->MemAvail += ms->MemAvail ; + _this->MemUsed += ms->MemUsed ; + _this->MemUsed_At_HighWater += ms->MemUsed_At_HighWater ; + _this->MemWasted += ms->MemWasted ; + _this->ChunksCount += ms->ChunksCount ; + _this->ChunksFreeCount += ms->ChunksFreeCount ; + _this->ChunksFreeLargestAvail += ms->ChunksFreeLargestAvail ; + _this->ChunksUsedCount += ms->ChunksUsedCount ; + _this->ChunksUsedCount_At_HighWater += ms->ChunksUsedCount_At_HighWater; + for (i =0; iMemType_ChunksCount[i] +=ms->MemType_ChunksCount[i]; + _this->MemType_MemUsed[i] +=ms->MemType_MemUsed[i] ; + } + +} + +void MEM_STATSClear(MEM_STATS *_this ) +// except HW +{ + _this->MemTotal = 0; + _this->MemAvail = 0; + _this->MemUsed = 0; + _this->MemWasted = 0; + _this->ChunksCount = 0; + _this->ChunksFreeCount = 0; + _this->ChunksFreeLargestAvail = 0; + _this->ChunksUsedCount = 0; + + memset(_this->MemType_ChunksCount, 0,4 * MEM_TYPES_MAX); + memset(_this->MemType_MemUsed, 0,4 * MEM_TYPES_MAX); + +} + +void MEM_STATSClearAll(MEM_STATS *_this ) +{ + int i; + MEM_STATSClear(_this); + _this->MemUsed_At_HighWater = 0; + for (i=0;i< MEM_TYPES_MAX;i++ ) + _this->MemType_MemUsed_At_HighWater[i] = 0; + _this->ChunksUsedCount_At_HighWater = 0; +} + + + +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLChunkMove (MEM_CHUNK_POOL *_this, MEM_CHUNK *oldpos, MEM_CHUNK *newpos) +//-------------------------------------------------------------------------- +{ + MEM_CHUNK *firstfree; + //todo!!! + MEM_CHUNK temp = *oldpos; + + // can not be end/start chunk + MP_ASSERT(oldpos->prev) + MP_ASSERT(oldpos->next) + + // check if within movement limits + MP_ASSERT((gsi_uint) newpos <= (gsi_uint)oldpos->next - MEM_CHUNKMemUsedGet(oldpos) - sizeof(MEM_CHUNK)) + MP_ASSERT((gsi_uint) newpos >= (gsi_uint)oldpos->prev + MEM_CHUNKMemUsedGet(oldpos->prev) + sizeof(MEM_CHUNK)) + + // check if alignment is valid + MP_ASSERT((((gsi_uint) newpos) % sizeof(MEM_CHUNK)) == 0) + + *newpos = temp; + + // link into chunk list + newpos->prev->next = newpos; + newpos->next->prev = newpos; + + // Fix links in free chunk list + if (MEM_CHUNKIsFree(newpos)) + { + + if (_this->pFirstFree == oldpos) + _this->pFirstFree = newpos; + else + { + firstfree = MEM_CHUNK_POOLFindPreviousFreeChunk(_this,newpos->prev); + if (firstfree != newpos) + firstfree->NextFree = newpos; + else + { + // first in list. + _this->pFirstFree = newpos; + } + + MP_ASSERT((newpos->NextFree==NULL) || ((gsi_uint)newpos->NextFree > (gsi_uint)newpos)) + } + } + + +} + +void MEM_CHUNK_POOLDestroy(MEM_CHUNK_POOL *_this) +{ + memset(_this, 0, sizeof (MEM_CHUNK_POOL)); +} +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLCreate(MEM_CHUNK_POOL *_this, const char * szNameIn, char *ptr, gsi_u32 size) +//-------------------------------------------------------------------------- +{ + int len; + MEM_CHUNK *HeaderMid; + MP_ASSERT(((gsi_uint)ptr & 15 )==0) // ensure 16 byte alignment + + //Copy limited length name + len = strlen(szNameIn)+1; + if (len > 20) len = 20; + memcpy(_this->Name,szNameIn, len); + _this->Name[19]='\0'; // in case str is too long. + + // create two nubs, at start, and end, with a chunk in between + MP_ASSERT(size > 48 + 3 * sizeof(MEM_CHUNK)) + + _this->HeaderStart = (MEM_CHUNK *) (ptr); + HeaderMid = (MEM_CHUNK *) (ptr + 2 * sizeof(MEM_CHUNK)); + _this->HeaderEnd = (MEM_CHUNK *) (ptr + size - 2 * sizeof(MEM_CHUNK)); + + // Bogus nub which is never freed. + _this->HeaderStart->prev = NULL; + _this->HeaderStart->next = HeaderMid; + _this->HeaderStart->NextFree = HeaderMid; + MEM_CHUNKAlloc (_this->HeaderStart,0,sizeof(MEM_CHUNK)); // don't mark as free + + // Here is our real heap, after before and after overhead + HeaderMid->prev = _this->HeaderStart; + HeaderMid->next = _this->HeaderEnd; + HeaderMid->NextFree = 0; + MEM_CHUNKFree(HeaderMid); + + // Bogus nub which is never freed. + _this->HeaderEnd->prev = HeaderMid; + _this->HeaderEnd->next = NULL; + _this->HeaderEnd->NextFree = NULL; + MEM_CHUNKAlloc (_this->HeaderEnd,0,sizeof(MEM_CHUNK)); // don't mark as free + + _this->HeapSize = size; + _this->pFirstFree = HeaderMid; + +} + + +//-------------------------------------------------------------------------- +MEM_CHUNK *MEM_CHUNK_POOLFindPreviousFreeChunk(MEM_CHUNK_POOL *_this, MEM_CHUNK *header) +// find previous free chunk +// return NULL if start header is not free, and there is nothing free before it. +// return header if start header is first free chunk +{ + while ((header) && (!MEM_CHUNKIsFree(header))) + { + //GS_ASSERT(header->prev == NULL || (header->prev >= _this->HeaderStart && header->prev <= _this->HeaderEnd)); + header = header->prev; + } + + GSI_UNUSED(_this); + return header; +} + +//-------------------------------------------------------------------------- +MEM_CHUNK *MEM_CHUNK_POOLFindNextFreeChunk(MEM_CHUNK_POOL *_this, MEM_CHUNK *header_in) +// find previous free chunk +// return NULL if no next free chunk. +{ + MEM_CHUNK *header = header_in; + while ((header) && (!MEM_CHUNKIsFree(header))) + { + header = header->next; + } + if (header == header_in) + return NULL; + + GSI_UNUSED(_this); + return header; +} + + + + +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLSplitChunk(MEM_CHUNK_POOL *_this, MEM_CHUNK *header, gsi_bool ReAlloc) +// split a used chunk into two if the UsedSize is smaller then the ChunkSize +//-------------------------------------------------------------------------- +{ + MEM_CHUNK *next; + MEM_CHUNK *PrevFree; + MEM_CHUNK *NewHeader; + + // calc new position at end of used mem + NewHeader = (MEM_CHUNK *) ((gsi_u8*)header + MEM_CHUNKMemUsedGet(header) + sizeof(MEM_CHUNK)); + NewHeader = (MEM_CHUNK *)MEMALIGN_POWEROF2(NewHeader,sizeof(MEM_CHUNK)); + + //assert we have enough room for this new chunk + MP_ASSERT ((gsi_uint)NewHeader + 2 * sizeof(MEM_CHUNK) <= (gsi_uint)header->next) + + // update some stats + #if (MEM_PROFILE) + if(ReAlloc) + { + //09-OCT-07 BED: Since we're splitting the chunk, it seems more accurate + // to use the full size of the chunk, not just the used portion + _this->MemUsed -= MEM_CHUNKChunkSizeGet(header); + //_this->MemUsed -= MEM_CHUNKMemUsedGet(header); + GS_ASSERT(_this->MemUsed >= 0); + } + #endif + + // Can this new chunk fit in the current one? + // create a new chunk header, at the end of used space, plus enough to align us to 16 bytes + + // Splice into linked list + NewHeader->prev = header; + NewHeader->next = header->next; + MEM_CHUNKFree(NewHeader); + + if (NewHeader->next) + { + NewHeader->next->prev = NewHeader; + } + + header->next = NewHeader; + + // Splice into free chunks linked list + + // this need to merge can happen on a realloc before a free chunk + if (MEM_CHUNKIsFree(NewHeader->next)) + { + MP_ASSERT(ReAlloc) + + // merge and splice + next = NewHeader->next->next; + next->prev = NewHeader; + + NewHeader->NextFree = NewHeader->next->NextFree; + NewHeader->next = next; + } + else + { + if (ReAlloc) + { + // on a realloc, this next value is useless + NewHeader->NextFree = MEM_CHUNK_POOLFindNextFreeChunk(_this,NewHeader->next); + } + else + NewHeader->NextFree = header->NextFree; + } + + if (_this->pFirstFree == header) + { + // this is first free chunk + _this->pFirstFree = NewHeader; + } + else + { + // link previous free chunk to this one. + PrevFree = MEM_CHUNK_POOLFindPreviousFreeChunk(_this,header); + if (PrevFree) + PrevFree->NextFree = NewHeader; + else + // this is first free chunk + _this->pFirstFree = NewHeader; + } + + #if (MEM_PROFILE) + if(ReAlloc) + { + _this->MemUsed += MEM_CHUNKMemUsedGet(header); + // update highwater mark + if(_this->MemUsed > _this->HWMemUsed) + _this->HWMemUsed = _this->MemUsed; + + GS_ASSERT(_this->MemUsed <= _this->HeapSize); + } + #endif + +#ifdef _DEBUG_ + header->NextFree = NULL; +#endif + +} + + +//-------------------------------------------------------------------------- +gsi_bool MEM_CHUNK_POOLIsHeapPtr(MEM_CHUNK_POOL *_this, void * mem) +// returns true if this is a valid heap ptr +{ + MEM_CHUNK *headertofind = Ptr_To_MEM_CHUNK(mem); + MEM_CHUNK *header = _this->HeaderStart; + + while (header) + { + header = header->next; + if (headertofind == header) + return gsi_true; + } + + return gsi_false; + +} + + + + + + + +//-------------------------------------------------------------------------- +MEM_CHUNK *MEM_CHUNK_POOLAllocChunk(MEM_CHUNK_POOL *_this,size_t Size, gsi_i32 Alignment, gsi_bool Backwards) +// size = requested size from app. + +// Find first chunk that will fit, +// allocate from it, splitting it +// merge split with next free chunk, if next chunk is free +//-------------------------------------------------------------------------- +{ + gsi_u32 Ptr ; + gsi_u32 AlignedPtr ; + int delta ; + MEM_CHUNK *PrevFree ; + int total_size ; + int MemRemain ; + MEM_CHUNK *alignedheader; + + + MEM_CHUNK *header; + gsi_u32 SizeNeeded = Size + sizeof(MEM_CHUNK); + SizeNeeded = MEMALIGN_POWEROF2(SizeNeeded,sizeof(MEM_CHUNK)); // must be aligned to this at least!!! + + MP_ASSERT(Size) + MP_ASSERT(MEM_IS_POWER_OF_2(Alignment)) // must be power of two!!! + MP_ASSERT(Alignment >= PTR_ALIGNMENT) + + +// Backwards = gsi_false; + + if(Backwards) + header = MEM_CHUNK_POOLFindPreviousFreeChunk(_this,_this->HeaderEnd); + else + header = _this->pFirstFree; + + + // should all be free chunks linked from here in. + while (header) + { + // is this chunk available + MP_ASSERT (MEM_CHUNKIsFree(header)) + + // Calc memory left in this chunk after we alloc + total_size = MEM_CHUNKTotalSizeGet(header); + MemRemain = total_size - SizeNeeded; + + // can we fit? + if (MemRemain >= 0 ) + { + // are we aligned properly? + Ptr = (gsi_uint)MEM_CHUNKMemPtrGet(header); + AlignedPtr = MEMALIGN_POWEROF2(Ptr,Alignment); + delta = AlignedPtr - Ptr; + if (delta) + { + // we need to move free chunk over by ptr. + if (MemRemain < delta) + { + // not enough space in this chunk + header = header->NextFree; + continue; + } + + // move the chunk over so that the pointer is aligned. + alignedheader = Ptr_To_MEM_CHUNK((void*)(gsi_uint)AlignedPtr); + MEM_CHUNK_POOLChunkMove (_this,header,alignedheader); + header = alignedheader; + MemRemain -= delta; + + } + + + // at this point we've taken this chunk, and need to split off the unused part + // in theory, there should be no other free chunk ahead of us. + + MEM_CHUNKAlloc(header,MemTagStack[MemTagStackIndex],Size); + + // split as needed + if (MemRemain > sizeof(MEM_CHUNK)*2) + { + + // split chunk, this will handle free chunk pointer list + MEM_CHUNK_POOLSplitChunk(_this,header, gsi_false); + } + else + { + // remove from free list + if (_this->pFirstFree == header) + { + // this is first free chunk + _this->pFirstFree = header->NextFree; + + } + else + { + // link previous free chunk to this one. + PrevFree = MEM_CHUNK_POOLFindPreviousFreeChunk(_this,header); + if (PrevFree) + PrevFree->NextFree = header->NextFree; + else + _this->pFirstFree = header->NextFree; + + } + } + { + #if (MEM_PROFILE) + _this->MemUsed += MEM_CHUNKMemUsedGet(header); + // update highwater mark + if(_this->MemUsed > _this->HWMemUsed) + _this->HWMemUsed = _this->MemUsed; + + GS_ASSERT(_this->MemUsed <= _this->HeapSize); + #endif + } + return header; + + } + if (Backwards) + header = MEM_CHUNK_POOLFindPreviousFreeChunk(_this,header); + else + header = header->NextFree; + } + // not crashing here. + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Misc, GSIDebugLevel_Notice," Could not allocate %i bytes\n", Size); + GS_ASSERT_STR(0,"Out of memory");//(_this->Name); + + + return NULL; + +} + + + +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLFreeChunk(MEM_CHUNK_POOL *_this,MEM_CHUNK *header) +// set chunk as free +// merge if possible with prev and next +// adding chunk to free chunks list. +//-------------------------------------------------------------------------- +{ + + MEM_CHUNK *prev = header; + MEM_CHUNK *next = header; + MEM_CHUNK *PrevFree; + + #if (MEM_PROFILE) + _this->MemUsed -= MEM_CHUNKMemUsedGet(header); + GS_ASSERT(_this->MemUsed >= 0); + #endif + + while (next->next && (MEM_CHUNKIsFree(next->next))) + { + next = next->next; + } + + while (prev->prev && (MEM_CHUNKIsFree(prev->prev))) + { + prev = prev->prev; + } + + if (prev != next) + { + // merge + // prev becomes the new chunk. + prev->next = next->next; + + if (next->next) + next->next->prev = prev; + + } + + // since this is now a free chunk, we must add it to the free chunk list + + // find previous free + PrevFree = MEM_CHUNK_POOLFindPreviousFreeChunk(_this,prev); + if (PrevFree == NULL) + { + // this is first free chunk + _this->pFirstFree = prev; + + } + else + { + // link previous free chunk to this one. + PrevFree->NextFree = prev; + } + + // find and set next free chunk + if(next->next) + prev->NextFree = MEM_CHUNK_POOLFindNextFreeChunk(_this,next->next); + else + prev->NextFree = NULL; + + MEM_CHUNKFree(prev); + + +#if(0) + //ToDo: steal unused memory from previous used chunk + gsi_u32 destptr = (gsi_u32)prev->prev + prev->prev->MemAvailGet() + sizeof(MEM_CHUNK); + destptr = MEMALIGN_POWEROF2(destptr,sizeof(MEM_CHUNK)); + + // we can move back to this ptr. Is it worth it? + if (destptr < (gsi_u32)prev ) + ChunkMove(prev,(MEM_CHUNK *)destptr); +#endif +} + + + + +//-------------------------------------------------------------------------- +void *MEM_CHUNK_POOLmalloc(MEM_CHUNK_POOL *_this,size_t Size, gsi_i32 Alignment) +//-------------------------------------------------------------------------- +{ + void *mem; + + // return ptr to the first block big enough + MEM_CHUNK *header = MEM_CHUNK_POOLAllocChunk( _this,Size, Alignment, gsi_false); + + if (header) + { + // alloc new chunk + mem = MEM_CHUNKMemPtrGet(header); + return mem; + } + + return NULL; +} + + +//-------------------------------------------------------------------------- +void *MEM_CHUNK_POOLmalloc_backwards(MEM_CHUNK_POOL *_this,size_t Size, gsi_i32 Alignment) +//-------------------------------------------------------------------------- +{ + void *mem; + + // return ptr to the first block big enough + MEM_CHUNK *header = MEM_CHUNK_POOLAllocChunk( _this,Size, Alignment, gsi_true); + + if (header) + { + // alloc new chunk + mem = MEM_CHUNKMemPtrGet(header); + return mem; + } + + return NULL; +} + + +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLfree(MEM_CHUNK_POOL *_this,void *mem) +// return 0 if memory freed in this call +// else return mem value passed in +//-------------------------------------------------------------------------- +{ + MEM_CHUNK *header = Ptr_To_MEM_CHUNK(mem); + MEM_CHUNK_POOLFreeChunk(_this,header); +} + + +//-------------------------------------------------------------------------- +void *MEM_CHUNK_POOLrealloc(MEM_CHUNK_POOL *_this,void *oldmem, size_t newSize) +//-------------------------------------------------------------------------- +{ + MEM_CHUNK *oldheader; + MEM_CHUNK *NewHeader; + gsi_u32 OldSize; + char MemType; + + MP_ASSERT(newSize) + + if (!oldmem) + { + return MEM_CHUNK_POOLmalloc( _this, newSize,PTR_ALIGNMENT); + } + + + oldheader = Ptr_To_MEM_CHUNK(oldmem); + OldSize = MEM_CHUNKMemUsedGet(oldheader); + + if (newSize == OldSize) + return oldmem; + + if (newSize < OldSize ) + { + + if ((newSize + 2 * sizeof(MEM_CHUNK))> OldSize ) + { + // not enough room to create another chunk, can't shrink + return oldmem; + } + + // shrink it + MEM_CHUNKMemUsedSet(oldheader,newSize); + MEM_CHUNK_POOLSplitChunk(_this,oldheader, gsi_true); + return MEM_CHUNKMemPtrGet(oldheader); + } + else + { + // get a new chunk + MemType = MEM_CHUNKMemTypeGet(oldheader); + MEM_CHUNK_POOLFreeChunk(_this,oldheader); + NewHeader = MEM_CHUNK_POOLAllocChunk( _this,newSize,PTR_ALIGNMENT,gsi_false); + MEM_CHUNKMemTypeSet(NewHeader,MemType); + + memmove(MEM_CHUNKMemPtrGet(NewHeader),oldmem,OldSize); + + return MEM_CHUNKMemPtrGet(NewHeader); + } + +} + +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLMEM_CHUNK_POOL(MEM_CHUNK_POOL *_this) +//-------------------------------------------------------------------------- +{ + _this->Name[0] = 0; + _this->HeaderEnd = NULL; + _this->HeaderStart = NULL; + _this->HeapSize = 0; + _this->pFirstFree = NULL; +} + + + + + +//-------------------------------------------------------------------------- +gsi_u32 MEM_CHUNK_POOLWalkForType(MEM_CHUNK_POOL *_this,int type, gsi_bool _LogUse) +//-------------------------------------------------------------------------- +{ + MEM_CHUNK *header; + gsi_u32 Total = 0; + header = _this->HeaderStart; + + while (header) + { + MP_ASSERT((header->next == NULL) || ((gsi_uint)header < (gsi_uint)header->next )) // infinite loop or out of place + MP_ASSERT((header->prev == NULL) || ((gsi_uint)header->prev < (gsi_uint)header )) // infinite loop or out of place + MP_ASSERT((header->prev == NULL) || (header->prev->next == header)) // previous linked correctly to us + MP_ASSERT((header->next == NULL) || (header->next->prev == header)) // next linked correctly to us + MP_ASSERT( MEM_CHUNKMemUsedGet(header) <= MEM_CHUNKChunkSizeGet(header) ) // using too much mem + + if (!MEM_CHUNKIsFree(header) && (MEM_CHUNKMemTypeGet(header) == type)) + { + //Don't log a message for the HeaderStart and HeaderEnd blocks. + if ((header != _this->HeaderStart) && (header != _this->HeaderEnd)) + { + // Used Chunk + Total += MEM_CHUNKTotalSizeGet(header); + if (_LogUse) + { + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Misc, GSIDebugLevel_Notice,"MemFound ptr:0x%8x size:%8u %s\n", MEM_CHUNKMemPtrGet(header), + MEM_CHUNKMemUsedGet(header),MemMgrBufferGetName((gsMemMgrContext) type)); + } + } + + } + + // make sure we hit the correct end + MP_ASSERT (header->next || (header == _this->HeaderEnd)) + header = header->next; + + } + return Total; +} + + +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLMemStatsGet(MEM_CHUNK_POOL *_this,MEM_STATS *pS) +{ + int ChunksFreeLostCount ; + int i,type; + MEM_CHUNK *header ; + MEM_CHUNK *NextFree; + MEM_STATSClear(pS); + + // check free chunk linked list + header = _this->HeaderStart; + NextFree = _this->pFirstFree; + + + + /*** Test validity of all chunks chain ***/ + while (header) + { + MP_ASSERT((header->next == NULL) || ((gsi_uint)header < (gsi_uint)header->next )) // infinite loop or out of place + MP_ASSERT((header->prev == NULL) || ((gsi_uint)header->prev < (gsi_uint)header )) // infinite loop or out of place + MP_ASSERT((header->prev == NULL) || (header->prev->next == header)) // previous linked correctly to us + MP_ASSERT((header->next == NULL) || (header->next->prev == header)) // next linked correctly to us + MP_ASSERT( MEM_CHUNKMemUsedGet(header) <= MEM_CHUNKChunkSizeGet(header) ) // using too much mem + + pS->MemTotal += MEM_CHUNKTotalSizeGet(header); + if (!MEM_CHUNKIsFree(header)) + { + // Used Chunk + pS->ChunksUsedCount++; + if (pS->ChunksUsedCount_At_HighWater < pS->ChunksUsedCount) + pS->ChunksUsedCount_At_HighWater = pS->ChunksUsedCount; + + // calc overhead and waste + pS->MemWasted += MEM_CHUNKTotalSizeGet(header) - MEM_CHUNKMemUsedGet(header); + pS->MemUsed += MEM_CHUNKTotalSizeGet(header); + + type = MEM_CHUNKMemTypeGet(header); + pS->MemType_MemUsed[type] += MEM_CHUNKTotalSizeGet(header); + pS->MemType_ChunksCount[type]++; + + } + else + { + // free chunk + MP_ASSERT((header->NextFree == NULL) || ((gsi_uint)header < (gsi_uint)header->NextFree )) // infinite loop or out of place + + // make sure we aren't fragmented, as this ruins some algorithm assumptions + MP_ASSERT((header->next == NULL) || (!MEM_CHUNKIsFree(header->next))) // infinite loop or out of place + MP_ASSERT((header->prev == NULL) || (!MEM_CHUNKIsFree(header->prev))) // infinite loop or out of place + + // previous free chunk linked correctly to us, we aren't a lost chunk + MP_ASSERT(header == NextFree) + NextFree = header->NextFree; + + // calc overhead and waste (in this case, the same value...sizeof(MEM_CHUNK) header) + pS->MemWasted += MEM_CHUNKTotalSizeGet(header) - MEM_CHUNKChunkSizeGet(header); + pS->MemUsed += MEM_CHUNKTotalSizeGet(header) - MEM_CHUNKChunkSizeGet(header); + + pS->ChunksFreeCount++; + if (pS->ChunksFreeLargestAvail < MEM_CHUNKChunkSizeGet(header)) + pS->ChunksFreeLargestAvail = MEM_CHUNKChunkSizeGet(header); + } + + pS->ChunksCount++; + + // make sure we hit the correct end + MP_ASSERT (header->next || (header == _this->HeaderEnd)) + header = header->next; + + } + + // Check free chunks + header = _this->HeaderStart; + + + /*** Test validity of free chunks chain ***/ + // Walk heap looking for first free chunk, + while(header && (!MEM_CHUNKIsFree(header))) + header = header->next; + + // make sure the first free one is linked correctly + MP_ASSERT(_this->pFirstFree == header) + + ChunksFreeLostCount = pS->ChunksFreeCount; + while (header) + { + // add up sizes + ChunksFreeLostCount --; + pS->MemAvail +=MEM_CHUNKChunkSizeGet(header); + header = header->NextFree; + + } + + + // Update stats + if (pS->MemUsed_At_HighWater < pS->MemUsed) + pS->MemUsed_At_HighWater = pS->MemUsed; + + for ( i=0;i< MEM_TYPES_MAX;i++ ) + { + if (pS->MemType_MemUsed_At_HighWater[i] < pS->MemType_MemUsed[i] ) + pS->MemType_MemUsed_At_HighWater[i] = pS->MemType_MemUsed[i]; + } + + MP_ASSERT(ChunksFreeLostCount == 0) // lost free blocks +} + +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLCheckValidity(MEM_CHUNK_POOL *_this) +{ + MEM_STATS stats; + MEM_CHUNK_POOLMemStatsGet(_this,&stats); + +} + + +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLFillMemoryTable(MEM_CHUNK_POOL *_this,char *Table, const int TableSize, gsi_u32 _HeapStart, gsi_u32 _HeapSize) +//-------------------------------------------------------------------------- +{ + int s,e,j; + gsi_u32 start_address; + gsi_u32 end_address ; + MEM_CHUNK *pChunk = _this->HeaderStart; + MP_ASSERT(_this->HeapSize) + + + while (pChunk) + { + if (!MEM_CHUNKIsFree(pChunk)) + { + start_address = (gsi_uint)pChunk; + end_address = ((gsi_uint)pChunk->next)-1; + + // translate address into table positions + s= ((start_address - _HeapStart) * (TableSize>>4)) / (_HeapSize>>4); + MP_ASSERT(s < TableSize) + MP_ASSERT(s >= 0) + + e= (( end_address - _HeapStart) * (TableSize>>4)) / (_HeapSize>>4); + MP_ASSERT(e < TableSize) + MP_ASSERT(e >= 0) + + for ( j= s; j<= e; j++) + { + // if(Table[j] != -2) + // Table[j] = -1; + // else + Table[j] = MEM_CHUNKMemTypeGet(pChunk); + } + + } + pChunk = pChunk->next; + } + + +} + + + +static MEM_CHUNK_POOL gChunkPool [gsMemMgrContext_Count] ; + + + +// Use this to determine which pool and subsequent allocations will be taken from. +gsMemMgrContext gsMemMgrContextCurrent = gsMemMgrContext_Default; + +//static GSICriticalSection gMemCrit; + +//-------------------------------------------------------------------------- +gsMemMgrContext gsMemMgrContextFind (void *ptr) +// find pool corresponding to mem ptr. +{ + int i; + // find which pool owns this pointer!!!!, this is kind of a hack.... but here goes. + for (i=0; i< gsMemMgrContext_Count;i++) + { + if ( + MEM_CHUNK_POOLIsValid(&gChunkPool[i]) && + MEM_CHUNK_POOLItemIsInPoolMemory(&gChunkPool[i],ptr) + ) + { + return (gsMemMgrContext) i; + } + + } + return gsMemMgrContext_Invalid; +} + +void *gs_malloc(size_t size) +{ + GS_ASSERT(size) + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[gsMemMgrContextCurrent]),"malloc: context is invalid mempool"); + + return MEM_CHUNK_POOLmalloc(&gChunkPool[gsMemMgrContextCurrent], size,PTR_ALIGNMENT); +} + +void *gs_calloc(size_t size,size_t size2) +{ + GS_ASSERT(size) + GS_ASSERT(size2) + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[gsMemMgrContextCurrent]),"calloc: context is invalid mempool"); + + return MEM_CHUNK_POOLmalloc(&gChunkPool[gsMemMgrContextCurrent], size*size2,PTR_ALIGNMENT); +} + +void *gs_realloc(void* ptr,size_t size) +{ + GS_ASSERT(size) + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[gsMemMgrContextCurrent]),"realloc: context is invalid mempool"); + + return MEM_CHUNK_POOLrealloc(&gChunkPool[gsMemMgrContextCurrent],ptr, size); +} + +void *gs_memalign(size_t boundary,size_t size) +{ + GS_ASSERT(size) + GS_ASSERT(boundary) + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[gsMemMgrContextCurrent]),"memalign: context is invalid mempool"); + + return MEM_CHUNK_POOLmalloc(&gChunkPool[gsMemMgrContextCurrent], size,boundary); +} + +void gs_free(void *ptr) +{ + gsMemMgrContext context; + + context = gsMemMgrContextFind(ptr); + GS_ASSERT_STR(context != gsMemMgrContext_Invalid,"Attempt to free invalid ptr") + + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[context]),"free: ptr context is invalid mempool"); + MEM_CHUNK_POOLfree(&gChunkPool[context],ptr); +} + +//-------------------------------------------------------------------------- +const char *MemMgrBufferGetName(gsMemMgrContext context) +{ + GS_ASSERT_STR(context != gsMemMgrContext_Invalid, "Invalid Context"); + GS_ASSERT_STR(context < gsMemMgrContext_Count, "Context out of range"); + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[context ]),"Invalid mempool"); + + return gChunkPool[context].Name; +} + + +void gsMemMgrContextSet(gsMemMgrContext context) +{ + GS_ASSERT_STR(context != gsMemMgrContext_Invalid, "Invalid Context"); + GS_ASSERT_STR(context < gsMemMgrContext_Count, "Context out of range"); + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[context]),"Setting context to invalid mempool"); + + gsMemMgrContextCurrent = context; +} + + + +//-------------------------------------------------------------------------- +// call this to enable GameSpy's provided memory manager +// Create a mem pool for the given context. If that context is in use, it will return the next available +// if none are available it will return gsMemMgrContext_Invalid +// ex use: gQR2MemContext = gsMemMgrCreate (0,0,16 * 1024); +// will find the first avaiable spot, create a mem pool of 16k, and return the context handle. +// then later in your API +// enter an API function +// gsMemMgrContextPush(gQR2MemContext); +// do some allocs +// gQR2MemContextPop() +// return from function. +gsMemMgrContext gsMemMgrCreate (gsMemMgrContext context, const char *PoolName,void* thePoolBuffer, size_t thePoolSize) +{ + char *ptr = (char *)thePoolBuffer; + + GS_ASSERT_STR(thePoolSize,"Cannnot create a pool of size 0") + GS_ASSERT_STR(thePoolSize,"thePoolBuffer ptr is inivalid"); + GS_ASSERT_STR(((((gsi_uint)thePoolSize) &15) ==0) ,"PoolSize must be aligned to 16 bytes"); + GS_ASSERT_STR(((((gsi_uint)thePoolBuffer)&15) ==0) ,"thePoolBuffer must be aligned to 16 bytes"); + + + while (MEM_CHUNK_POOLIsValid(&gChunkPool[context])) + { + context = (gsMemMgrContext)(context + 1); + } + if (context == gsMemMgrContext_Count) + { + // Warn!!!! + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, GSIDebugLevel_Comment, + "Out of memory context handles!\n"); + GS_ASSERT(0); + return gsMemMgrContext_Invalid; // ran out of context slots + } + + MEM_CHUNK_POOLCreate(&gChunkPool[context],PoolName,ptr,thePoolSize); + // Set call backs. + gsiMemoryCallbacksSet(gs_malloc, gs_free, gs_realloc, gs_memalign); + return context; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsMemMgrDestroy(gsMemMgrContext context) +{ + GS_ASSERT(gChunkPool[context].HeapSize != 0); + MEM_CHUNK_POOLDestroy(&gChunkPool[context]); + + // if this is the last one, +#if(0) + { + // Set call backs. + gsiMemoryCallbacksSet(malloc,free,realloc,memalign); + + // Reset memmgr + gsiDeleteCriticalSection(&gMemCrit); + + // #ifdef _GSI_MULTI_THREADED_ + // gsiLeaveCriticalSection(&gMemCrit); + // gsiEnterCriticalSection(&gMemCrit); + // #endif + } +#endif +} + + +//-------------------------------------------------------------------------- +void gsMemMgrTagPush (gsi_u8 tag) +{ + GS_ASSERT(MemTagStackIndex < MEM_TAG_STACK_MAX-1) + MemTagStackIndex++; + MemTagStack[MemTagStackIndex] = tag; +} +//-------------------------------------------------------------------------- +void gsMemMgrTagPop () +{ + GS_ASSERT(MemTagStackIndex > 0) + MemTagStackIndex--; +} +//-------------------------------------------------------------------------- +gsi_u8 gsMemMgrTagGet (void *ptr) +{ + GS_ASSERT(ptr); + return MEM_CHUNKMemTypeGet( Ptr_To_MEM_CHUNK(ptr)); +} +//-------------------------------------------------------------------------- +gsi_u32 gsMemMgrMemUsedByTagGet(gsi_u8 tag) +{ + int i; + gsi_u32 used = 0; + for ( i=0;i< gsMemMgrContext_Count;i++) + { + used+= MEM_CHUNK_POOLWalkForType(&gChunkPool[i] ,tag, gsi_false); + } + return used; + +} + +//-------------------------------------------------------------------------- +void gsMemMgrContextPush(gsMemMgrContext NewType) +{ +// PARANOID_MemProfilerCheck(); + GS_ASSERT(MemTypeStackIndex < MEM_CONTEXT_STACK_MAX) + GS_ASSERT(NewType < gsMemMgrContext_Count) + +// gsDebugFormat(GSIDebugCat_App, GSIDebugType_State, GSIDebugLevel_Comment,"MemProfilerStart: %s\n",MemProfiler.MemPool[NewType].Name); + MemTypeStack[MemTypeStackIndex++] = gsMemMgrContextCurrent; + gsMemMgrContextCurrent = NewType; +} + +//-------------------------------------------------------------------------- +gsMemMgrContext gsMemMgrContextPop() +{ +// PARANOID_MemProfilerCheck(); + GS_ASSERT(MemTypeStackIndex > 0) +// gsDebugFormat(GSIDebugCat_App, GSIDebugType_State, GSIDebugLevel_Comment,"MemProfilerEnd: %s\n",MemProfiler.MemPool[OldType].Name); + gsMemMgrContextCurrent = MemTypeStack[--MemTypeStackIndex]; + return gsMemMgrContextCurrent; +} + + +//-------------------------------------------------------------------------- +// return total available memory for the given memory pool +gsi_u32 gsMemMgrMemAvailGet (gsMemMgrContext context) +{ + MEM_STATS stats; + MEM_STATSClearAll(&stats); + GS_ASSERT_STR(context < gsMemMgrContext_Count, "gsMemMgrMemAvailGet: context out of range"); + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[context]), "gsMemMgrMemAvailGet: context is invalid mempool"); + MEM_CHUNK_POOLMemStatsGet (&gChunkPool[context], &stats); + return stats.MemAvail; +} + +//-------------------------------------------------------------------------- +// return total used memory for the given memory pool +gsi_u32 gsMemMgrMemUsedGet (gsMemMgrContext context) +{ + MEM_STATS stats; + MEM_STATSClearAll(&stats); + GS_ASSERT_STR(context < gsMemMgrContext_Count, "gsMemMgrMemUsedGet: context out of range"); + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[context]), "gsMemMgrMemUsedGet: context is invalid mempool"); + MEM_CHUNK_POOLMemStatsGet (&gChunkPool[context], &stats); + return stats.MemUsed; +} + + +//-------------------------------------------------------------------------- +// return largest allocatable chunk the given memory pool. This +// will be the same or probably smaller then the value returned by gsMemMgrMemAvailGet +// depending on degree of memory fragmentation. +gsi_u32 gsMemMgrMemLargestAvailGet (gsMemMgrContext context) +{ + MEM_STATS stats; + MEM_STATSClearAll(&stats); + GS_ASSERT_STR(context < gsMemMgrContext_Count, "gsMemMgrMemLargestAvailGet: context out of range"); + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[context]), "gsMemMgrMemLargestAvailGet: context is invalid mempool"); + MEM_CHUNK_POOLMemStatsGet (&gChunkPool[context], &stats); + return stats.ChunksFreeLargestAvail; +} + +//-------------------------------------------------------------------------- +gsi_u32 gsMemMgrMemHighwaterMarkGet (gsMemMgrContext context) +{ + GS_ASSERT_STR(context < gsMemMgrContext_Count, "gsMemMgrMemLargestAvailGet: context out of range"); + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[context]), "gsMemMgrMemLargestAvailGet: context is invalid mempool"); + + #if(MEM_PROFILE) + return gChunkPool[context].HWMemUsed; + #else + // Display info - App type b/c it was requested by the app + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, GSIDebugLevel_Comment, + "gsMemMgrMemHighwaterMarkGet called without MEM_PROFILE enabled."); + return 0; + #endif +} + +//-------------------------------------------------------------------------- +void gsMemMgrValidateMemoryPool() +{ + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[gsMemMgrContextCurrent]),"memalign: context is invalid mempool"); + MEM_CHUNK_POOLCheckValidity(&gChunkPool[gsMemMgrContextCurrent]); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Show allocated, free, total memory, num blocks +void gsMemMgrDumpStats() +{ +#if(0) + int numUsed = 0; + int numFree = 0; + + struct GSIMemoryBlock* aTempPtr = NULL; + + gsiEnterCriticalSection(&gMemCrit); + + // Display the number of free blocks + // TODO: dump size statistics + aTempPtr = gMemoryMgr->mFirstFreeBlock; + while(aTempPtr != NULL) + { + numFree++; + aTempPtr = aTempPtr->mNext; + } + + // Display the number of used blocks + // TODO: dump size statistics + aTempPtr = gMemoryMgr->mFirstUsedBlock; + while(aTempPtr != NULL) + { + numUsed++; + aTempPtr = aTempPtr->mNext; + } + + // Display info - App type b/c it was requested by the app + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, GSIDebugLevel_Comment, + "BytesUsed: %d, BlocksUsed: %d, BlocksFree: %d\r\n", + gMemoryMgr->mMemUsed, numUsed, numFree); + + gsiLeaveCriticalSection(&gMemCrit); +#endif +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsMemMgrDumpAllocations() +{ +#if(0) + struct GSIMemoryBlock* aBlockPtr = NULL; + gsi_time aStartTime = 0; + gsi_i32 aNumAllocations = 0; + gsi_i32 aNumBytesAllocated = 0; + + gsiEnterCriticalSection(&gMemCrit); + + aStartTime = current_time(); + aBlockPtr = (GSIMemoryBlock*)gMemoryMgr->mPoolStart; + + // Announce start + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, GSIDebugLevel_Comment, + "Dumping allocations from pool - [0x%08x] %d bytes.\r\n", + gMemoryMgr->mPoolStart, gMemoryMgr->mPoolSize); + + // Dump information about each allocated block + // - Do this in linear order, not list order + while(aBlockPtr != NULL) + { + // If it's in use, verify contents and dump info + if (gsiMemBlockIsFlagged(aBlockPtr, BlockFlag_Used)) + { + int anObjectSize = gsiMemBlockGetObjectSize(aBlockPtr); + aNumAllocations++; + aNumBytesAllocated += anObjectSize; + + if (aBlockPtr == gMemoryMgr->mPoolStart) + { + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, GSIDebugLevel_Comment, + "\t[0x%08x] Size: %d (memmgr instance)\r\n", (gsi_u32)aBlockPtr, anObjectSize); + } + else + { + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, GSIDebugLevel_Comment, + "\t[0x%08x] Size: %d\r\n", (gsi_u32)(gsiMemBlockGetObjectPtr(aBlockPtr)), anObjectSize); + } + } + else + { + // Verify that the block has the correct memory fill + } + // Get linear next (not list next!) + aBlockPtr = gsiMemBlockGetLinearNext(aBlockPtr); + } + + // Announce finish + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, GSIDebugLevel_Comment, + "\t--%d allocations, %d bytes allocated.\r\n", aNumAllocations, aNumBytesAllocated); + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, GSIDebugLevel_Comment, + "\t--%d peak memory usage\r\n", gMemoryMgr->mPeakMemoryUsage); + + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, GSIDebugLevel_Comment, + "Memory dump complete. (%d ms)\r\n", current_time() - aStartTime); + + gsiLeaveCriticalSection(&gMemCrit); + + GSI_UNUSED(aStartTime); // may be unused if common debug is not defined +#endif +} + + + +#if (1) // test stuff + +#define PTR_TABLE_SIZE 2048 +static int PtrTableCount = 0; +static void *PtrTable[2048]; + +int Random(int x) +{ + return Util_RandInt(0,x); +} +//-------------------------------------------------------------------------- +void gsMemMgrSelfText() +//-------------------------------------------------------------------------- +{ + + + static MEM_CHUNK_POOL gChunkPool; + int size = 32 * 1024 * 1024; + int c= 0; + int i,j,k; + + char *ptr = (char *) ( ((gsi_uint)malloc(size-PTR_ALIGNMENT)+(PTR_ALIGNMENT-1))&~ (PTR_ALIGNMENT-1) ) ; + MEM_CHUNK_POOLCreate(&gChunkPool,"",ptr,size); + + while(1) + { + + i= Random(4); + if ((i==0) &&(PtrTableCount < 1024)) + { + // malloc + j = Random(1024)+1; + k = 32<< (Random(4)); + + if (c&1) + PtrTable[PtrTableCount] = MEM_CHUNK_POOLmalloc(&gChunkPool, j,k); + else + PtrTable[PtrTableCount] = MEM_CHUNK_POOLmalloc_backwards(&gChunkPool, j,k); + + if(PtrTable[PtrTableCount]) + { + PtrTableCount++; + } + else + { + GS_ASSERT(0); + } + + } + else + if ((i==1) &&(PtrTableCount)) + { + // free + j = Random(PtrTableCount); + MP_ASSERT(j < PtrTableCount) + + + MEM_CHUNK_POOLfree(&gChunkPool,PtrTable[j]); + + // swap with last. + PtrTableCount--; + PtrTable[j] = PtrTable[PtrTableCount]; + + } + else + if ((i==2) &&(PtrTableCount)) + { + j = Random(PtrTableCount); + MP_ASSERT(j < PtrTableCount) + + // realloc + k = Random(1024) +1; + #if(1) + PtrTable[j] = MEM_CHUNK_POOLrealloc(&gChunkPool,PtrTable[j], k); + #else + // skip + PtrTable[j] = PtrTable[j]; + #endif + + if(PtrTable[j]) + { + } + else + { + GS_ASSERT(0); + } + + } + else + continue; // skip count + + c++; + MEM_CHUNK_POOLCheckValidity(&gChunkPool); + } + +} + + +#endif + + + + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // GSI_MEM_MANAGED + diff --git a/code/gamespy/common/gsMemory.h b/code/gamespy/common/gsMemory.h new file mode 100644 index 00000000..c68e0eda --- /dev/null +++ b/code/gamespy/common/gsMemory.h @@ -0,0 +1,177 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __GSIMEMORY_H__ +#define __GSIMEMORY_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsCommon.h" + +// GameSpy allocation wrappers. Used for quickly adding pre-post allocation functionality such as +// - routing to a specific mempool +// - collecting mem usage stats +// (x) is a enumerated type for the specific module +#if(1) + #define GSI_PRE_ALLOC(x) + #define GSI_POST_ALLOC() +#elif(0) + // - collecting mem usage stats + #define GSI_PRE_ALLOC(x) gsMemMgrTagPush (x); + #define GSI_POST_ALLOC() gsMemMgrTagPop (); +#elif(0) + // - routing to a specific mempool + #define GSI_PRE_ALLOC(x) gsMemMgrContextPush (x); + #define GSI_POST_ALLOC() gsMemMgrContextPop (); +#endif + +#if defined (__cplusplus) +extern "C" +{ +#endif + + +typedef enum +{ + MEMTAG_DEFAULT, + MEMTAG_SERVER_BROWSER, + MEMTAG_PEER, + MEMTAG_GP, + MEMTAG_QR2, + MEMTAG_NN, + MEMTAG_GT2, + MEMTAG_COUNT +} MEMTAG_SDK; + +//-------------------------------------------------------------------------- +// GameSpy specific memory functions. By default these will route to system malloc +// calls. Use gsiMemoryCallbacksSet or gsiMemoryCallbacksGameSpySet to change this. +void* gsimalloc (size_t size); +void* gsirealloc (void* ptr, size_t size); +void gsifree (void* ptr); +void* gsimemalign (size_t boundary, size_t size); // TODO + +//-------------------------------------------------------------------------- +// Customer supplied memory manager customization interface +// call this to replace the Gamespy specific memory functions with your own. +#ifdef WIN32 +typedef void *(__cdecl *gsMallocCB) (size_t size); +typedef void (__cdecl *gsFreeCB) (void* ptr); +typedef void *(__cdecl *gsReallocCB) (void* ptr, size_t size); +typedef void *(__cdecl *gsMemalignCB)(size_t boundary, size_t size); +#else +typedef void *(*gsMallocCB) (size_t size); +typedef void (*gsFreeCB) (void* ptr); +typedef void *(*gsReallocCB) (void* ptr, size_t size); +typedef void *(*gsMemalignCB)(size_t boundary, size_t size); +#endif +// call this to override above gsi.... calls with your own. +void gsiMemoryCallbacksSet(gsMallocCB p_malloc, gsFreeCB p_free, gsReallocCB p_realloc, gsMemalignCB p_memalign); + +//-------------------------------------------------------------------------- +// GameSpy Built in Memory Manager +// call this to override above gsi.... calls with GameSpy's built in memory manager +// *** You must have GSI_MEM_MANAGED defined, otherwise, you will have a link error ***/ + +// This is a list of memory pools used. API specific values determined at run time. +// the gsi mem manager uses the concept of multiple memory pools or contexts. +// use pop and push commands to pop and push the current context off of the stack + +typedef enum +{ + gsMemMgrContext_Invalid= -1, + gsMemMgrContext_Default= 0, + gsMemMgrContext_Count = 16 // max number of mempools +}gsMemMgrContext; + + +// call this to enable GameSpy's provided memory manager +// Create a mempool for the given context. If that context is in use, it will return the next available +// if none are avaible it will return gsMemMgrContext_Invalid +// exx use: gQR2MemContext = gsMemMgrCreate (0,0,16 * 1024); +// will find the first avaiable spot, create a mempool of 16k, and return the context handle. +// then later in your API +// enter an API function +// gsMemMgrContextPush(gQR2MemContext); +// do some allocs +// gQR2MemContextPop() +// return from function. +// PoolName is purely for debugging and stats feedback purposes only. +// If you want your api to use the current, or the default pool, then don't bother creating one +// just always set the context to 0, and make sure int your app init, the default (0) pool is created. +/* + Recommended usage: + Call gsMemMgrCreate once at app start with a static buffer. Make all calls to this. + Alternatively, call it once per API to sue a seperate pool per API. +*/ +gsMemMgrContext gsMemMgrCreate (gsMemMgrContext context, const char *PoolName,void* thePoolBuffer, size_t thePoolSize); + +// Use this to determine which pool and subsequent allocations will be taken from. +//exx use +/* + fn() + { + gsMemMgrContextPush(thisAPIContext); + + make allocations. + + //restore settings + gsMemMgrContextPop(thisAPIContext); + + } +*/ +// note, this is not neccessary for "free". +void gsMemMgrContextPush (gsMemMgrContext context); +gsMemMgrContext gsMemMgrContextPop (); + + +// clear contents, original mempool ptr must still be freed by app. +void gsMemMgrDestroy(gsMemMgrContext context); + +// -------------Diagnostics------------------------ +// These functions all run on the current mempool context. +void gsMemMgrDumpStats(); +void gsMemMgrDumpAllocations(); +void gsMemMgrValidateMemoryPool(); // walk heap and check integrity + +// -------------Tool use ------------------------ +// find which mempool context this ptr is part of, if any. +// returns gsMemMgrContext_Invalid otherwise. +gsMemMgrContext gsMemMgrContextFind (void *ptr); +const char *MemMgrBufferGetName(gsMemMgrContext context); + + +// -------------Memory Use Profiling ------------------------ +// this tag is added to each concurrent alloc. Use this to reference allocations. +// For example, you can find out the mem used by all ptr with a given tag +// in order to find out how much mem a module or set of allocs use. +void gsMemMgrTagPush (gsi_u8 tag); +void gsMemMgrTagPop (); +gsi_u8 gsMemMgrTagGet (void *ptr); +gsi_u32 gsMemMgrMemUsedByTagGet(gsi_u8 tag); + +// return total available memory for the given memory pool context +gsi_u32 gsMemMgrMemAvailGet (gsMemMgrContext context); +// return total used memory for the given memory pool context +gsi_u32 gsMemMgrMemUsedGet (gsMemMgrContext context); +// return largest allocatable chunk within the given memory pool context. This +// will be the same or probably smaller then the value returned by gsMemMgrMemAvailGet +// depending on degree of memory fragmentation. +gsi_u32 gsMemMgrMemLargestAvailGet (gsMemMgrContext context); + +// The Highwater mark for memory used is the highest memory usage ever gets to for this +// given heap. It is the most important stat, as your mempool must be at least this big. +// Exactly how big your pool needs to be depends on fragmentation. So it may need to be slightly +// bigger then this amount. +gsi_u32 gsMemMgrMemHighwaterMarkGet (gsMemMgrContext context); + + +// -------------Self Test, not for production use ------------------------ +void gsMemMgrSelfText(); + +#if defined (__cplusplus) +} +#endif + +#endif // __GSIMEMORY_H__ + diff --git a/code/gamespy/common/gsPlatform.c b/code/gamespy/common/gsPlatform.c new file mode 100644 index 00000000..ee4888f8 --- /dev/null +++ b/code/gamespy/common/gsPlatform.c @@ -0,0 +1,88 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsPlatform.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Include standard network lib +#if defined(_WIN32) && !defined(_XBOX) + #if defined(GSI_WINSOCK2) + #pragma comment(lib, "ws2_32") + #else + #pragma comment(lib, "wsock32") + #endif + #pragma comment(lib, "advapi32") +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Floating point specific byte reversal functions +// stores the result in a 4-byte character array +unsigned char * gsiFloatSwap(unsigned char buf[4], float f) +{ + unsigned char *dst = (unsigned char *)buf; + unsigned char *src = (unsigned char *)&f; + + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; + + return buf; +} + +// unswap using char pointers +float gsiFloatUnswap(unsigned char buf[4]) +{ + float f; + unsigned char *src = (unsigned char *)buf; + unsigned char *dst = (unsigned char *)&f; + + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; + + return f; +} + +gsi_u16 gsiByteOrderSwap16(gsi_u16 _in) +{ + gsi_u16 t; + const char *in = (char *)&_in; + char *out = (char *)&t; + out[0] = in[1]; + out[1] = in[0]; + return t; +} + +gsi_u32 gsiByteOrderSwap32(gsi_u32 _in) +{ + gsi_u32 t; + const char *in = (char *)&_in; + char *out = (char *)&t; + out[0] = in[3]; + out[1] = in[2]; + out[2] = in[1]; + out[3] = in[0]; + return t; +} + +gsi_u64 gsiByteOrderSwap64(gsi_u64 _in) +{ + gsi_u64 t; + const char *in = (char *)&_in; + char *out = (char *)&t; + out[0] = in[7]; + out[1] = in[6]; + out[2] = in[5]; + out[3] = in[4]; + out[4] = in[3]; + out[5] = in[2]; + out[6] = in[1]; + out[7] = in[0]; + return t; +} + diff --git a/code/gamespy/common/gsPlatform.h b/code/gamespy/common/gsPlatform.h new file mode 100644 index 00000000..a4daf16e --- /dev/null +++ b/code/gamespy/common/gsPlatform.h @@ -0,0 +1,487 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __GSPLATFORM_H__ +#define __GSPLATFORM_H__ + +// GameSpy platform definition and headers + +// Windows: _WIN32 +// Xbox: _WIN32 + _XBOX +// Xbox360: _WIN32 + _XBOX + _X360 +// MacOSX: _MACOSX + _UNIX +// Linux: _LINUX + _UNIX +// Nintendo DS: _NITRO +// PSP: _PSP +// PS3: _PS3 + +// PlayStation2: _PS2 +// w/ EENET: EENET (set by developer project) +// w/ INSOCK: INSOCK (set by developer project) +// w/ SNSYSTEMS: SN_SYSTEMS (set by developer project) +// Codewarrior: __MWERKS__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Set the platform define +#ifdef __mips64 + #ifndef _PS2 + #define _PS2 + #endif + + // Make sure a network stack was defined + #if !defined(SN_SYSTEMS) && !defined(EENET) && !defined(INSOCK) + #error "PlayStation2 network stack was not defined!" + #endif +#endif + +#if defined(_LINUX) || defined(_MACOSX) + #define _UNIX +#endif + +#if defined(_XBOX) || defined (_X360) +#if _XBOX_VER >= 200 + #define _X360 +#endif +#endif + +// WIN32, set by OS headers +// _XBOX, set by OS headers +// __MWERKS__, set by compiler +// _NITRO, set by OS headers + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Include common OS headers +#include +#include +#include +#include + + +// XBOX/X360 +#if defined(_XBOX) + #include + +// WIN32 +#elif defined(_WIN32) + #define WIN32_LEAN_AND_MEAN + #include + #include + #include + + #if defined(GSI_WINSOCK2) + #include + #else + #include + #endif + + #if (_MSC_VER > 1300) + #define itoa(v, s, r) _itoa(v, s, r) + #endif +// PS2 +#elif defined(_PS2) + // EENet headers must be included before common PS2 headers + #ifdef EENET + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #endif // EENET + + // Common PS2 headers + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #ifdef SN_SYSTEMS + // undefine socket defines from sys/types.h + // This is to workaround sony now automatically including sys/types.h + // and SNSystems having not produce a patch yet (they'll likely do the same since + // the SNSystems fd_set is a slightly different size than the sys/types.h. + #undef FD_CLR + #undef FD_ZERO + #undef FD_SET + #undef FD_ISSET + #undef FD_SETSIZE + #undef fd_set + #include "snskdefs.h" + #include "sntypes.h" + #include "snsocket.h" + #include "sneeutil.h" + #include "sntcutil.h" + #endif // SN_SYSTEMS + + #ifdef INSOCK + #include "libinsck.h" + #include "libnet.h" + #include "sys/errno.h" + //#include "libmrpc.h" + #endif // INSOCK + +// LINUX and MACOSX +#elif defined(_UNIX) + #include + #include + #include + #include + #include + #include + #include + #include + + // MACOSX Warning!! netdb.h has it's own NOFILE define. + // GameSpy uses NOFILE to determine if an HD is available + #ifndef NOFILE + // Since GameSpy NOFILE is not defined, include netdb.h then undef NOFILE + #include + #undef NOFILE + #else + // Otherwise leave NOFILE defined + #include + #endif + + #include + #include + #include + #include + //#include + #include + + // ICMP ping support is unsupported on Linux/MacOSX due to needing super-user access for raw sockets + #define SB_NO_ICMP_SUPPORT + +// Nintendo DS +#elif defined(_NITRO) + #include + #define NINET_NWBASE_MD5_H_ // resolves md5 conflicts + #include + #include // mwerks + #include + + // Raw sockets are undefined on Nitro + #define SB_NO_ICMP_SUPPORT + +// Sony PSP +#elif defined(_PSP) + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #include + #include + #include + #include + #include +// PS3 +#elif defined(_PS3) +#include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +// #include + #include + #include + +// Nintendo Wii +#elif defined(_REVOLUTION) + #include + #include + #include + #include + + // Raw sockets are undefined on Revolution + #define SB_NO_ICMP_SUPPORT + +// Unsupported platform or no platform defined! +#else + #error "The GameSpy SDKs do not support this operating system" + +#endif //(platform switch) + + + +//---------- __cdecl fix for __fastcall conventions ---------- +#if defined(_WIN32) + #define GS_STATIC_CALLBACK __cdecl +#else + #define GS_STATIC_CALLBACK +#endif + + +//---------- Handle Endianess ---------------------- +#if defined(_PS3) || defined(_REVOLUTION) || defined(_X360) //defined(_MACOSX) + #define GSI_BIG_ENDIAN +#endif +#ifndef GSI_BIG_ENDIAN + #define GSI_LITTLE_ENDIAN +#endif + + + +#include + +#if defined(_MACOSX) + #undef _T +#endif + +#include + +#if defined(GS_NO_FILE) || defined(_PS2) || defined(_PS3) || defined(_NITRO) || defined(_PSP) || defined(_XBOX) + #define NOFILE +#endif + +#if defined(_PSP) || defined(_NITRO) + #define GS_WIRELESS_DEVICE +#endif + +#if !defined(GSI_DOMAIN_NAME) + #define GSI_DOMAIN_NAME "gamespy.com" +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Define GameSpy types + +// common base type defines, please refer to ranges below when porting +typedef char gsi_i8; +typedef unsigned char gsi_u8; +typedef short gsi_i16; +typedef unsigned short gsi_u16; +typedef int gsi_i32; +typedef unsigned int gsi_u32; +typedef unsigned int gsi_time; // must be 32 bits + +// decprecated +typedef gsi_i32 goa_int32; // 2003.Oct.04.JED - typename deprecated +typedef gsi_u32 goa_uint32; // these types will be removed once all SDK's are updated + +typedef int gsi_bool; +#define gsi_false ((gsi_bool)0) +#define gsi_true ((gsi_bool)1) +#define gsi_is_false(x) ((x) == gsi_false) +#define gsi_is_true(x) ((x) != gsi_false) + +// Max integer size +#if defined(_INTEGRAL_MAX_BITS) && !defined(GSI_MAX_INTEGRAL_BITS) + #define GSI_MAX_INTEGRAL_BITS _INTEGRAL_MAX_BITS +#else + #define GSI_MAX_INTEGRAL_BITS 32 +#endif + +// Platform dependent types +#ifdef _PS2 + typedef signed long gsi_i64; + typedef unsigned long gsi_u64; +#elif defined(_WIN32) + #if (!defined(_M_IX86) || (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 64)) + typedef __int64 gsi_i64; + typedef unsigned __int64 gsi_u64; + #endif +#elif defined(_NITRO) + typedef s64 gsi_i64; + typedef u64 gsi_u64; +#elif defined (_PSP) + typedef long long gsi_i64; + typedef unsigned long long gsi_u64; +#elif defined (_PS3) + typedef int64_t gsi_i64; + typedef uint64_t gsi_u64; +#elif defined (_REVOLUTION) + typedef signed long long gsi_i64; + typedef unsigned long long gsi_u64; +#else /* _UNIX */ + typedef long long gsi_i64; + typedef unsigned long long gsi_u64; +#endif // 64 bit types + +#ifndef GSI_UNICODE + #define gsi_char char +#else + #define gsi_char unsigned short +#endif // goa_char + + +// expected ranges for integer types +#define GSI_MIN_I8 CHAR_MIN +#define GSI_MAX_I8 CHAR_MAX +#define GSI_MAX_U8 UCHAR_MAX + +#define GSI_MIN_I16 SHRT_MIN +#define GSI_MAX_I16 SHRT_MAX +#define GSI_MAX_U16 USHRT_MAX + +#define GSI_MIN_I32 INT_MIN +#define GSI_MAX_I32 INT_MAX +#define GSI_MAX_U32 UINT_MAX + +#if (GSI_MAX_INTEGRAL_BITS >= 64) +#define GSI_MIN_I64 (-9223372036854775807 - 1) +#define GSI_MAX_I64 9223372036854775807 +#define GSI_MAX_U64 0xffffffffffffffffui64 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Common platform string functions +#undef _vftprintf +#undef _ftprintf +#undef _stprintf +#undef _tprintf +#undef _tcscpy +#undef _tcsncpy +#undef _tcscat +#undef _tcslen +#undef _tcschr +#undef _tcscmp +#undef _tfopen +#undef _T +#undef _tsnprintf + +#ifdef GSI_UNICODE + #define _vftprintf vfwprintf + #define _ftprintf fwprintf + #define _stprintf swprintf + #define _tprintf wprintf + #define _tcscpy wcscpy + #define _tcsncpy(d, s, l) wcsncpy((wchar_t *)d, (wchar_t *)s, l) + #define _tcscat wcscat + #define _tcslen wcslen + #define _tcschr wcschr + #define _tcscmp(s1, s2) wcscmp((wchar_t *)s1, (wchar_t *)s2) + #define _tfopen _wfopen + #define _T(a) L##a + + #if defined(_WIN32) || defined(_PS2) + #define _tsnprintf _snwprintf + #else + #define _tsnprintf swprintf + #endif +#else + #define _vftprintf vfprintf + #define _ftprintf fprintf + #define _stprintf sprintf + #define _tprintf printf + #define _tcscpy strcpy + #define _tcsncpy strncpy + #define _tcscat strcat + #define _tcslen strlen +#if defined (_MSC_VER) +#if (_MSC_VER < 1400) + #define _tcschr strchr +#endif +#else + #define _tcschr strchr +#endif + #define _tcscmp strcmp + #define _tfopen fopen +#ifndef _T + #define _T(a) a +#endif + + #if defined(_WIN32) + #define _tsnprintf _snprintf + #else + #define _tsnprintf snprintf + #endif +#endif // GSI_UNICODE + +#if defined(_WIN32) + #define snprintf _snprintf +#endif // _WIN32 + +#if defined(_WIN32) + #define strcasecmp _stricmp + #define strncasecmp _strnicmp +#endif + +#if !defined(_WIN32) + char *_strlwr(char *string); + char *_strupr(char *string); +#endif + +char * goastrdup(const char *src); +unsigned short * goawstrdup(const unsigned short *src); + + +// ------ Cross Plat Alignment macros ------------ +/* ex use +PRE_ALIGN(16) struct VECTOR +{ + float x,y,z,_unused; +} POST_ALIGN(16); + +// another example when defining a variable: +PRE_ALIGN(16); +static char _mempool[MEMPOOL_SIZE] POST_ALIGN(16); + +*/ +#if defined _WIN32 + #define PRE_ALIGN(x) __declspec(align(x)) // ignore Win32 directive + #define POST_ALIGN(x) // ignore +#elif defined (_PS2) || defined (_PSP) || defined (_PS3) + #define PRE_ALIGN(x) // ignored this on psp/ps2 + #define POST_ALIGN(x) __attribute__((aligned (x))) // +#elif defined (_REVOLUTION) + #define PRE_ALIGN(x) // not needed + #define POST_ALIGN(x) __attribute__((aligned(32))) +#else + // #warning "Platform not supported" + #define PRE_ALIGN(x) // ignore + #define POST_ALIGN(x) // ignore +#endif + +#define DIM( x ) ( sizeof( x ) / sizeof((x)[ 0 ])) + +unsigned char * gsiFloatSwap(unsigned char buf[4], float); +float gsiFloatUnswap(unsigned char buf[4]); +extern gsi_u16 gsiByteOrderSwap16(gsi_u16); +extern gsi_u32 gsiByteOrderSwap32(gsi_u32); +extern gsi_u64 gsiByteOrderSwap64(gsi_u64); + + +#ifdef __cplusplus +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __GSPLATFORM_H__ + diff --git a/code/gamespy/common/gsPlatformSocket.c b/code/gamespy/common/gsPlatformSocket.c new file mode 100644 index 00000000..d8445078 --- /dev/null +++ b/code/gamespy/common/gsPlatformSocket.c @@ -0,0 +1,685 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsPlatformSocket.h" +#include "gsPlatformUtil.h" +#include "gsMemory.h" + +// mj-ToDo: remove these and include the files int the linker instead. +// removing reference to other platforms. +// remove all plat specific code from here, move it to the platspecific files. + +// Include platform separated functions +#if defined(_X360) + #include "x360/gsSocketX360.c" +#elif defined(_XBOX) + #include "xbox/gsSocketXBox.c" +#elif defined(_WIN32) + #include "win32/gsSocketWin32.c" +#elif defined(_LINUX) + //#include "linux/gsSocketLinux.c" +#elif defined(_MACOSX) + //#include "macosx/gsSocketMacOSX.c" +#elif defined(_NITRO) + #include "nitro/gsSocketNitro.c" +#elif defined(_PS2) + #include "ps2/gsSocketPs2.c" +#elif defined(_PS3) + #include "ps3/gsSocketPs3.c" + #include +#elif defined(_PSP) + #include "psp/gsSocketPSP.c" +#elif defined(_REVOLUTION) + #include "revolution/gsSocketRevolution.c" +#else + #error "Missing or unsupported platform" +#endif + + +// Disable compiler warnings for issues that are unavoidable. +///////////////////////////////////////////////////////////// +#if defined(_MSC_VER) // DevStudio + // Level4, "conditional expression is constant". + // Occurs with use of the MS provided macro FD_SET + #pragma warning ( disable: 4127 ) +#endif // _MSC_VER + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +int SetSockBlocking(SOCKET sock, int isblocking) +{ + int rcode; + +#if defined(_REVOLUTION) + int val; + + val = SOFcntl(sock, SO_F_GETFL, 0); + + if(isblocking) + val &= ~SO_O_NONBLOCK; + else + val |= SO_O_NONBLOCK; + + rcode = SOFcntl(sock, SO_F_SETFL, val); +#elif defined(_NITRO) + int val; + + val = SOC_Fcntl(sock, SOC_F_GETFL, 0); + + if(isblocking) + val &= ~SOC_O_NONBLOCK; + else + val |= SOC_O_NONBLOCK; + + rcode = SOC_Fcntl(sock, SOC_F_SETFL, val); +#else + #if defined(_PS2) || defined(_PS3) + // EENet requires int + // SNSystems requires int + // Insock requires int + // PS3 requires int + gsi_i32 argp; + #else + unsigned long argp; + #endif + + if(isblocking) + argp = 0; + else + argp = 1; + + #ifdef _PS2 + #ifdef SN_SYSTEMS + rcode = setsockopt(sock, SOL_SOCKET, (isblocking) ? SO_BIO : SO_NBIO, &argp, sizeof(argp)); + #endif + + #ifdef EENET + rcode = setsockopt(sock, SOL_SOCKET, SO_NBIO, &argp, sizeof(argp)); + #endif + + #ifdef INSOCK + if (isblocking) + argp = -1; + else + argp = 5; //added longer timeout to 5ms + sceInsockSetRecvTimeout(sock, argp); + sceInsockSetSendTimeout(sock, argp); + sceInsockSetShutdownTimeout(sock, argp); + GSI_UNUSED(sock); + rcode = 0; + #endif + #elif defined(_PSP) + rcode = setsockopt(sock, SCE_NET_INET_SOL_SOCKET, SCE_NET_INET_SO_NBIO, &argp, sizeof(argp)); + #elif defined(_PS3) + rcode = setsockopt(sock, SOL_SOCKET, SO_NBIO, &argp, sizeof(argp)); + #else + rcode = ioctlsocket(sock, FIONBIO, &argp); + #endif +#endif + + if(rcode == 0) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "SetSockBlocking: Set socket %d to %s\r\n", (unsigned int)sock, isblocking ? "blocking":"non-blocking"); + return 1; + } + + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "SetSockBlocking failed: tried to set socket %d to %s\r\n", (unsigned int)sock, isblocking ? "blocking":"non-blocking"); + return 0; +} + +int SetSockBroadcast(SOCKET sock) +{ +#if !defined(INSOCK) && !defined(_NITRO) && !defined(_REVOLUTION) + int optval = 1; + if(setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&optval, sizeof(optval)) != 0) + return 0; +#else + GSI_UNUSED(sock); +#endif + + return 1; +} + +int DisableNagle(SOCKET sock) +{ +#if defined(_WIN32) || defined(_UNIX) + int rcode; + int noDelay = 1; + + rcode = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&noDelay, sizeof(int)); + return gsiSocketIsError(rcode); +#else + GSI_UNUSED(sock); + + // not supported + return 0; +#endif // moved this to here to silence VC warning +} + + +#ifndef INSOCK + int SetReceiveBufferSize(SOCKET sock, int size) + { + int rcode; + rcode = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (const char *)&size, sizeof(int)); + return gsiSocketIsNotError(rcode); + } + + int SetSendBufferSize(SOCKET sock, int size) + { + int rcode; + rcode = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (const char *)&size, sizeof(int)); + return gsiSocketIsNotError(rcode); + } + + int GetReceiveBufferSize(SOCKET sock) + { + int rcode; + int size; + int len; + + len = sizeof(size); + + rcode = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&size, &len); + + if(gsiSocketIsError(rcode)) + return -1; + + return size; + } + + int GetSendBufferSize(SOCKET sock) + { + int rcode; + int size; + int len; + + len = sizeof(size); + + rcode = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&size, &len); + + if(gsiSocketIsError(rcode)) + return -1; + + return size; + } + + // Formerly known as ghiSocketSelect +#ifdef SN_SYSTEMS + #undef FD_SET + #define FD_SET(s,p) ((p)->array[((s) - 1) >> SN_FD_SHR] |= \ + (unsigned int)(1 << (((s) - 1) & SN_FD_BITS)) ) + +#endif +#endif + +#if !defined(_NITRO) && !defined(INSOCK) && !defined(_REVOLUTION) + int GSISocketSelect(SOCKET theSocket, int* theReadFlag, int* theWriteFlag, int* theExceptFlag) + { + fd_set aReadSet; + fd_set aWriteSet; + fd_set aExceptSet; + fd_set * aReadFds = NULL; + fd_set * aWriteFds = NULL; + fd_set * aExceptFds = NULL; + int aResult; +// 04-13-2005, Saad Nader +// Added case for SN Systems that would +// handle errors after performing selects. +#ifdef SN_SYSTEMS + int aOut, aOutLen = sizeof(aOut); +#endif + + struct timeval aTimeout = { 0, 0 }; + + if (theSocket == INVALID_SOCKET) + return -1; + + // Setup the parameters. + //////////////////////// + if(theReadFlag != NULL) + { + FD_ZERO(&aReadSet); + FD_SET(theSocket,&aReadSet); + aReadFds = &aReadSet; + } + if(theWriteFlag != NULL) + { + FD_ZERO(&aWriteSet); + FD_SET(theSocket, &aWriteSet); + aWriteFds = &aWriteSet; + } + if(theExceptFlag != NULL) + { + FD_ZERO(&aExceptSet); + FD_SET(theSocket, &aExceptSet); + aExceptFds = &aExceptSet; + } +#ifdef _PS3 + // to do, port what is below in the else +//int socketselect(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout); + aResult = socketselect(FD_SETSIZE, aReadFds, aWriteFds, aExceptFds, &aTimeout); +#else + // Perform the select + aResult = select(FD_SETSIZE, aReadFds, aWriteFds, aExceptFds, &aTimeout); +#endif + if(gsiSocketIsError(aResult)) + return -1; + +// 04-13-2005, Saad Nader +// Added case for SN Systems that would +// handle errors after performing selects. +#ifdef SN_SYSTEMS + getsockopt(theSocket, SOL_SOCKET, SO_ERROR, (char *)&aOut, &aOutLen); + if (aOut != 0) + { + return 0; + } +#endif + // Check results. + ///////////////// + if(theReadFlag != NULL) + { + if((aResult > 0) && FD_ISSET(theSocket, aReadFds)) + *theReadFlag = 1; + else + *theReadFlag = 0; + } + if(theWriteFlag != NULL) + { + if((aResult > 0) && FD_ISSET(theSocket, aWriteFds)) + *theWriteFlag = 1; + else + *theWriteFlag = 0; + } + if(theExceptFlag != NULL) + { + if((aResult > 0) && FD_ISSET(theSocket, aExceptFds)) + *theExceptFlag = 1; + else + *theExceptFlag = 0; + } + return aResult; // 0 or 1 at this point + } +#endif // !nitro && !revolution && !insock + + +// Return 1 for immediate recv, otherwise 0 +int CanReceiveOnSocket(SOCKET sock) +{ + int aReadFlag = 0; + if (1 == GSISocketSelect(sock, &aReadFlag, NULL, NULL)) + return aReadFlag; + + // SDKs expect 0 on SOCKET_ERROR + return 0; +} + +// Return 1 for immediate send, otherwise 0 +int CanSendOnSocket(SOCKET sock) +{ + int aWriteFlag = 0; + if (1 == GSISocketSelect(sock, NULL, &aWriteFlag, NULL)) + return aWriteFlag; + + // SDKs expect 0 on SOCKET_ERROR + return 0; +} + + +#if defined(_PS3) || defined (_PSP) + +#else + +HOSTENT * getlocalhost(void) +{ +#ifdef EENET + #define MAX_IPS 5 + + static HOSTENT localhost; + static char * aliases = NULL; + static char * ipPtrs[MAX_IPS + 1]; + static unsigned int ips[MAX_IPS]; + + struct sceEENetIfname * interfaces; + struct sceEENetIfname * interface; + int num; + int i; + int count; + int len; + u_short flags; + IN_ADDR address; + + // initialize the host + localhost.h_name = "localhost"; + localhost.h_aliases = &aliases; + localhost.h_addrtype = AF_INET; + localhost.h_length = 0; + localhost.h_addr_list = ipPtrs; + + // get the local interfaces + sceEENetGetIfnames(NULL, &num); + interfaces = (struct sceEENetIfname *)gsimalloc(num * sizeof(struct sceEENetIfname)); + if(!interfaces) + return NULL; + sceEENetGetIfnames(interfaces, &num); + + // loop through the interfaces + count = 0; + for(i = 0 ; i < num ; i++) + { + // the next interface + interface = &interfaces[i]; + //printf("eenet%d: %s\n", i, interface->ifn_name); + + // get the flags + len = sizeof(flags); + if(sceEENetGetIfinfo(interface->ifn_name, sceEENET_IFINFO_IFFLAGS, &flags, &len) != 0) + continue; + //printf("eenet%d flags: 0x%X\n", i, flags); + + // check for up, running, and non-loopback + if(!(flags & (IFF_UP|IFF_RUNNING)) || (flags & IFF_LOOPBACK)) + continue; + //printf("eenet%d: up and running, non-loopback\n", i); + + // get the address + len = sizeof(address); + if(sceEENetGetIfinfo(interface->ifn_name, sceEENET_IFINFO_ADDR, &address, &len) != 0) + continue; + //printf("eenet%d: %s\n", i, inet_ntoa(address)); + + // add this address + ips[count] = address.s_addr; + ipPtrs[count] = (char *)&ips[count]; + count++; + } + + // free the interfaces + gsifree(interfaces); + + // check that we got at least one IP + if(!count) + return NULL; + + // finish filling in the host struct + localhost.h_length = (gsi_u16)sizeof(ips[0]); + ipPtrs[count] = NULL; + + return &localhost; + + //////////////////// + // INSOCK +#elif defined(INSOCK) + // Global storage + #define MAX_IPS sceLIBNET_MAX_INTERFACE + static HOSTENT localhost; + static char * aliases = NULL; + static char * ipPtrs[MAX_IPS + 1]; + static unsigned int ips[MAX_IPS]; + + // Temp storage + int aInterfaceIdArray[MAX_IPS]; + int aNumInterfaces = 0; + int aInterfaceNum = 0; + int aCount = 0; + + // Get the list of interfaces + aNumInterfaces = sceInetGetInterfaceList(&gGSIInsockClientData, + &gGSIInsockSocketBuffer, aInterfaceIdArray, MAX_IPS); + if (aNumInterfaces < 1) + return NULL; + + // initialize the HOSTENT + localhost.h_name = "localhost"; + localhost.h_aliases = &aliases; + localhost.h_addrtype = AF_INET; + localhost.h_addr_list = ipPtrs; + + // Look up each address and copy into the HOSTENT structure + aCount = 0; // count of valid interfaces + for (aInterfaceNum = 0; aInterfaceNum < aNumInterfaces; aInterfaceNum++) + { + sceInetAddress_t anAddr; + int result = sceInetInterfaceControl(&gGSIInsockClientData, &gGSIInsockSocketBuffer, + aInterfaceIdArray[aInterfaceNum], sceInetCC_GetAddress, + &anAddr, sizeof(anAddr)); + if (result == 0) + { + // Add this interface to the array + memcpy(&ips[aCount], anAddr.data, sizeof(ips[aCount])); + ips[aCount] = htonl(ips[aCount]); + ipPtrs[aCount] = (char*)&ips[aCount]; + aCount++; + } + } + + // Set the final hostent data, then return + localhost.h_length = (gsi_u16)sizeof(ips[0]); + ipPtrs[aCount] = NULL; + return &localhost; + +#elif defined(_NITRO) + #define MAX_IPS 5 + + static HOSTENT localhost; + static char * aliases = NULL; + static char * ipPtrs[MAX_IPS + 1]; + static unsigned int ips[MAX_IPS]; + + int count = 0; + + localhost.h_name = "localhost"; + localhost.h_aliases = &aliases; + localhost.h_addrtype = AF_INET; + localhost.h_length = 0; + localhost.h_addr_list = (u8 **)ipPtrs; + + ips[count] = 0; + IP_GetAddr(NULL, (u8*)&ips[count]); + if(ips[count] == 0) + return NULL; + ipPtrs[count] = (char *)&ips[count]; + count++; + + localhost.h_length = (gsi_u16)sizeof(ips[0]); + ipPtrs[count] = NULL; + + return &localhost; + +#elif defined(_REVOLUTION) + #define MAX_IPS 5 + static HOSTENT aLocalHost; + static char * aliases = NULL; + int aNumOfIps, i; + int aSizeNumOfIps; + static IPAddrEntry aAddrs[MAX_IPS]; + int aAddrsSize, aAddrsSizeInitial; + static u8 * ipPtrs[MAX_IPS + 1]; + static unsigned int ips[MAX_IPS]; + int ret; + aSizeNumOfIps = sizeof(aNumOfIps); + ret = SOGetInterfaceOpt(NULL, SO_SOL_CONFIG, SO_CONFIG_IP_ADDR_NUMBER, &aNumOfIps, &aSizeNumOfIps); + if (ret != 0) + return NULL; + + aAddrsSize = (int)(MAX_IPS * sizeof(IPAddrEntry)); + aAddrsSizeInitial = aAddrsSize; + ret = SOGetInterfaceOpt(NULL, SO_SOL_CONFIG, SO_CONFIG_IP_ADDR_TABLE, &aAddrs, &aAddrsSize); + if (ret != 0) + return NULL; + + if (aAddrsSize != aAddrsSizeInitial) + { + aNumOfIps = aAddrsSize / (int)sizeof(IPAddrEntry); + } + + aLocalHost.h_name = "localhost"; + aLocalHost.h_aliases = &aliases; + aLocalHost.h_addrtype = AF_INET; + aLocalHost.h_length = SO_IP4_ALEN; + + for (i = 0; i < MAX_IPS; i++) + { + if (i < aNumOfIps) + { + memcpy(&ips[i], &aAddrs[i].addr, sizeof(aAddrs[i].addr)); + ipPtrs[i] = (u8 *)&ips[i]; + } + else + ipPtrs[i] = NULL; + } + aLocalHost.h_addr_list = ipPtrs; + + return &aLocalHost; + +#elif defined(_X360) + XNADDR addr; + DWORD rcode; + static HOSTENT localhost; + static char * ipPtrs[2]; + static IN_ADDR ip; + + while((rcode = XNetGetTitleXnAddr(&addr)) == XNET_GET_XNADDR_PENDING) + msleep(1); + + if((rcode == XNET_GET_XNADDR_NONE) || (rcode == XNET_GET_XNADDR_TROUBLESHOOT)) + return NULL; + + localhost.h_name = "localhost"; + localhost.h_aliases = NULL; + localhost.h_addrtype = AF_INET; + localhost.h_length = (gsi_u16)sizeof(IN_ADDR); + localhost.h_addr_list = (gsi_i8 **)ipPtrs; + + ip = addr.ina; + ipPtrs[0] = (char *)&ip; + ipPtrs[1] = NULL; + + return &localhost; + +#elif defined(_XBOX) + return NULL; + + +#else + char hostname[256] = ""; + + // get the local host's name + gethostname(hostname, sizeof(hostname)); + + // return the host for that name + return gethostbyname(hostname); +#endif +} +#endif + +int IsPrivateIP(IN_ADDR * addr) +{ + int b1; + int b2; + unsigned int ip; + + // get the first 2 bytes + ip = ntohl(addr->s_addr); + b1 = (int)((ip >> 24) & 0xFF); + b2 = (int)((ip >> 16) & 0xFF); + + // 10.X.X.X + if(b1 == 10) + return 1; + + // 172.16-31.X.X + if((b1 == 172) && ((b2 >= 16) && (b2 <= 31))) + return 1; + + // 192.168.X.X + if((b1 == 192) && (b2 == 168)) + return 1; + + return 0; +} + +gsi_u32 gsiGetBroadcastIP(void) +{ +#if defined(_NITRO) + gsi_u32 ip; + IP_GetBroadcastAddr(NULL, (u8*)&ip); + return ip; + +#elif defined(_REVOLUTION) + /* + int length; + gsi_u32 ip; + + length = (gsi_u32)sizeof(ip); + + // IP_GetBroadcastAddr replaced by SOGetInterfaceOpt + // IP_GetBroadcastAddr(NULL, (u8*)&ip); + SOGetInterfaceOpt(NULL, SO_SOL_IP, SO_INADDR_BROADCAST, (u8*)&ip, &length); + */ + IPAddrEntry* addrtbl; + int addrnum; + int ret; + int length; + gsi_u32 ip; + length = (int)sizeof( addrnum ); + + ret = SOGetInterfaceOpt(NULL, + SO_SOL_CONFIG, + SO_CONFIG_IP_ADDR_NUMBER, + (u8*)&addrnum, &length); + + if( ret >= 0 ) + return 0xFFFFFFFF; + + + length = (int)(sizeof( IPAddrEntry ) * addrnum); + + addrtbl = (IPAddrEntry*)gsimalloc( (u32)length ); + + if( addrtbl == NULL ) + return 0xFFFFFFFF; + + ret = SOGetInterfaceOpt(NULL, + SO_SOL_CONFIG, + SO_CONFIG_IP_ADDR_TABLE, + (u8*)addrtbl, + &length); + + if( ret < 0 ) + { + gsifree( addrtbl ); + return 0xFFFFFFFF; + } + + ip = (u32)(addrtbl->bcastAddr[3] + | (addrtbl->bcastAddr[2] << 8) + | (addrtbl->bcastAddr[1] << 16) + | (addrtbl->bcastAddr[0] << 24)); + + gsifree( addrtbl ); + + + return ip; + + return ip; + +#else + return 0xFFFFFFFF; +#endif +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +// Re-enable previously disabled compiler warnings +/////////////////////////////////////////////////// +#if defined(_MSC_VER) +#pragma warning ( default: 4127 ) +#endif // _MSC_VER + diff --git a/code/gamespy/common/gsPlatformSocket.h b/code/gamespy/common/gsPlatformSocket.h new file mode 100644 index 00000000..ab7f2e07 --- /dev/null +++ b/code/gamespy/common/gsPlatformSocket.h @@ -0,0 +1,654 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __GSSOCKET_H__ +#define __GSSOCKET_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsPlatform.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + + + +// GSI Cross Platform Socket Wrapper + +// this should all inline and optimize out... I hope +// if they somehow get really complex, we need to move the implementation into the .c file. +#if defined _PS3 || defined _PSP + #define gsiSocketIsError(theReturnValue) ((theReturnValue) < 0) + #define gsiSocketIsNotError(theReturnValue) ((theReturnValue) >= 0) +#else + #define gsiSocketIsError(theReturnValue) ((theReturnValue) == -1) + #define gsiSocketIsNotError(theReturnValue) ((theReturnValue) != -1) +#endif + + +#if (0) +// to do for Saad and Martin, move towards this and phase out the #define jungle. +// we will trade a little speed for a lot of portability, and stability +// also out debug libs will assert all params comming in. +typedef enum +{ + GS_SOCKERR_NONE = 0, + GS_SOCKERR_EWOULDBLOCK, + GS_SOCKERR_EINPROGRESS, + GS_SOCKERR_EALREADY, + GS_SOCKERR_ENOTSOCK, + GS_SOCKERR_EDESTADDRREQ, + GS_SOCKERR_EMSGSIZE, + GS_SOCKERR_EPROTOTYPE, + GS_SOCKERR_ENOPROTOOPT , + GS_SOCKERR_EPROTONOSUPPORT , + GS_SOCKERR_ESOCKTNOSUPPORT, + GS_SOCKERR_EOPNOTSUPP , + GS_SOCKERR_EPFNOSUPPORT, + GS_SOCKERR_EAFNOSUPPORT, + GS_SOCKERR_EADDRINUSE , + GS_SOCKERR_EADDRNOTAVAIL , + GS_SOCKERR_ENETDOWN , + GS_SOCKERR_ENETUNREACH , + GS_SOCKERR_ENETRESET, + GS_SOCKERR_ECONNABORTED, + GS_SOCKERR_ECONNRESET , + GS_SOCKERR_ENOBUFS , + GS_SOCKERR_EISCONN , + GS_SOCKERR_ENOTCONN, + GS_SOCKERR_ESHUTDOWN, + GS_SOCKERR_ETOOMANYREFS , + GS_SOCKERR_ETIMEDOUT, + GS_SOCKERR_ECONNREFUSED, + GS_SOCKERR_ELOOP, + GS_SOCKERR_ENAMETOOLONG, + GS_SOCKERR_EHOSTDOWN , + GS_SOCKERR_EHOSTUNREACH , + GS_SOCKERR_ENOTEMPTY , + GS_SOCKERR_EPROCLIM , + GS_SOCKERR_EUSERS , + GS_SOCKERR_EDQUOT , + GS_SOCKERR_ESTALE , + GS_SOCKERR_EREMOTE , + GS_SOCKERR_EINVAL , + GS_SOCKERR_COUNT , +} GS_SOCKET_ERROR; + +#define gsiSocketIsError(theReturnValue) ((theReturnValue) != GS_SOCKERR_NONE) +#define gsiSocketIsNotError(theReturnValue) ((theReturnValue) == GS_SOCKERR_NONE) + +typedef int GSI_SOCKET; + +// mj - may need to pragma pack this, otherwise, it will pad after u_short +typedef struct +{ + // this is the same as the "default" winsocks + u_short sa_family; /* address family */ + char sa_data[14]; /* up to 14 bytes of direct address */ +} GS_SOCKADDR; + +GSI_SOCKET gsiSocketAccept (GSI_SOCKET sock, GS_SOCKADDR* addr, int* len); +GS_SOCKET_ERROR gsiSocketSocket (int pf, int type, int protocol); +GS_SOCKET_ERROR gsiSocketClosesocket(GSI_SOCKET sock); +GS_SOCKET_ERROR gsiSocketShutdown (GSI_SOCKET sock, int how); +GS_SOCKET_ERROR gsiSocketBind (GSI_SOCKET sock, const GS_SOCKADDR* addr, int len); +GS_SOCKET_ERROR gsiSocketConnect (GSI_SOCKET sock, const GS_SOCKADDR* addr, int len); +GS_SOCKET_ERROR gsiSocketListen (GSI_SOCKET sock, int backlog); +GS_SOCKET_ERROR gsiSocketRecv (GSI_SOCKET sock, char* buf, int len, int flags); +GS_SOCKET_ERROR gsiSocketRecvfrom (GSI_SOCKET sock, char* buf, int len, int flags, GS_SOCKADDR* addr, int* fromlen); +GS_SOCKET_ERROR gsiSocketSend (GSI_SOCKET sock, const char* buf, int len, int flags); +GS_SOCKET_ERROR gsiSocketSendto (GSI_SOCKET sock, const char* buf, int len, int flags, const GS_SOCKADDR* addr, int tolen); +GS_SOCKET_ERROR gsiSocketGetsockopt (GSI_SOCKET sock, int level, int optname, char* optval, int* optlen); +GS_SOCKET_ERROR gsiSocketSetsockopt (GSI_SOCKET sock, int level, int optname, const char* optval, int optlen); +GS_SOCKET_ERROR gsiSocketGetsockname(GSI_SOCKET sock, GS_SOCKADDR* addr, int* len); +GS_SOCKET_ERROR GOAGetLastError (GSI_SOCKET sock); + +gsiSocketGethostbyaddr(a,l,t) SOC_GetHostByAddr(a,l,t) +gsiSocketGethostbyname(n) SOC_GetHostByName(n) + + +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Types + + +#ifndef INADDR_NONE + #define INADDR_NONE 0xffffffff +#endif + +#ifndef INVALID_SOCKET + #define INVALID_SOCKET (-1) +#endif + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Platform socket types +#if defined(_PSP) + #define AF_INET SCE_NET_INET_AF_INET + #define SOCK_STREAM SCE_NET_INET_SOCK_STREAM + #define SOCK_DGRAM SCE_NET_INET_SOCK_DGRAM + #define SOCK_RAW SCE_NET_INET_SOCK_RAW + #define INADDR_ANY SCE_NET_INET_INADDR_ANY + #define SOL_SOCKET SCE_NET_INET_SOL_SOCKET + #define SO_SNDBUF SCE_NET_INET_SO_SNDBUF + #define SO_RCVBUF SCE_NET_INET_SO_RCVBUF + #define SO_NBIO SCE_NET_INET_SO_NBIO + #define SO_BROADCAST SCE_NET_INET_SO_BROADCAST + #define SO_KEEPALIVE SCE_NET_INET_SO_KEEPALIVE + #define SO_REUSEADDR SCE_NET_INET_SO_REUSEADDR + + #define IPPROTO_TCP SCE_NET_INET_IPPROTO_TCP // protocol defined by SOCK_STREAM + #define IPPROTO_UDP SCE_NET_INET_IPPROTO_UDP // protocol defined by SOCK_DGRAM + #define IPPROTO_ICMP SCE_NET_INET_IPPROTO_ICMP // protocol for ICMP pings + + // structures + #define in_addr SceNetInetInAddr + #define sockaddr_in SceNetInetSockaddrIn + #define sockaddr SceNetInetSockaddr + + // Remove FD types set in sys/types.h + // Replace with types in pspnet/sys/select.h + #if defined(_SYS_TYPES_H) && defined(FD_SET) + #undef fd_set + #undef FD_SET + #undef FD_CLR + #undef FD_ZERO + #undef timeval + #undef FD_SETSIZE + #endif + #define fd_set SceNetInetFdSet + #define timeval SceNetInetTimeval + #define FD_SET SceNetInetFD_SET + #define FD_CLR SceNetInetFD_CLR + #define FD_ZERO SceNetInetFD_ZERO + #define FD_SETSIZE SCE_NET_INET_FD_SETSIZE + + // functions + #define htonl sceNetHtonl + #define ntohl sceNetNtohl + #define htons sceNetHtons + #define ntohs sceNetNtohs + #define socket sceNetInetSocket + #define shutdown sceNetInetShutdown + #define closesocket sceNetInetClose + + #define setsockopt sceNetInetSetsockopt + #define getsockopt(s, l, on, ov, ol) sceNetInetGetsockopt(s, l, on, ov, (SceNetInetSocklen_t *)ol) + + #define bind sceNetInetBind + #define select sceNetInetSelect + + #define connect sceNetInetConnect + #define listen sceNetInetListen + #define accept(s,a,l) sceNetInetAccept(s, a, (SceNetInetSocklen_t *)l) + + #define send sceNetInetSend + #define recv sceNetInetRecv + #define sendto sceNetInetSendto + #define recvfrom(s, b, l, f, fr, fl) sceNetInetRecvfrom(s, b, l, f, fr, (SceNetInetSocklen_t *)fl) + + + #define inet_addr sceNetInetInetAddr + // This is not the correct function for gethostname, it should get the string name of the local host + // not the sockaddr_in struct + #define gethostname // sceNetInetGetsockname + #define getsockname(s,n,l) sceNetInetGetsockname(s, n, (SceNetInetSocklen_t *)l) + + #define GOAGetLastError(s) sceNetInetGetErrno() + + // hostent support + struct hostent + { + char* h_name; + char** h_aliases; + gsi_u16 h_addrtype; // AF_INET + gsi_u16 h_length; + char** h_addr_list; + }; + + #define gethostbyname gsSocketGetHostByName + #define inet_ntoa gsSocketInetNtoa + + #define GSI_RESOLVER_TIMEOUT (5*1000*1000) // 5 seconds + #define GSI_RESOLVER_RETRY (2) + + struct hostent* gsSocketGetHostByName(const char* name); // gsSocketPSP.c + const char* gsSocketInetNtoa(struct in_addr in); + + +#endif // _PSP + +// XBOX doesn't have host lookup +#if defined(_XBOX) + #if defined(_X360) + // hostent support + struct hostent + { + char* h_name; + char** h_aliases; + gsi_u16 h_addrtype; // AF_INET + gsi_u16 h_length; + char** h_addr_list; + }; + + typedef struct hostent HOSTENT; + struct hostent * gethostbyname(const char* name); + #else + typedef int HOSTENT; + #endif + + char * inet_ntoa(IN_ADDR in_addr); +#endif + +#if defined(SN_SYSTEMS) + #define IPPROTO_TCP PF_INET + #define IPPROTO_UDP PF_INET + #define FD_SETSIZE SN_MAX_SOCKETS +#endif + +// SOCKET ERROR CODES +#if defined(_REVOLUTION) //not sure if Wii uses this or _REV + #define WSAEWOULDBLOCK SO_EWOULDBLOCK + #define WSAEINPROGRESS SO_EINPROGRESS + #define WSAEALREADY SO_EALREADY + #define WSAENOTSOCK SO_ENOTSOCK + #define WSAEDESTADDRREQ SO_EDESTADDRREQ + #define WSAEMSGSIZE SO_EMSGSIZE + #define WSAEPROTOTYPE SO_EPROTOTYPE + #define WSAENOPROTOOPT SO_ENOPROTOOPT + #define WSAEPROTONOSUPPORT SO_EPROTONOSUPPORT + #define WSAEOPNOTSUPP SO_EOPNOTSUPP + #define WSAEAFNOSUPPORT SO_EAFNOSUPPORT + #define WSAEADDRINUSE SO_EADDRINUSE + #define WSAEADDRNOTAVAIL SO_EADDRNOTAVAIL + #define WSAENETDOWN SO_ENETDOWN + #define WSAENETUNREACH SO_ENETUNREACH + #define WSAENETRESET SO_ENETRESET + #define WSAECONNABORTED SO_ECONNABORTED + #define WSAECONNRESET SO_ECONNRESET + #define WSAENOBUFS SO_ENOBUFS + #define WSAEISCONN SO_EISCONN + #define WSAENOTCONN SO_ENOTCONN + #define WSAETIMEDOUT SO_ETIMEDOUT + #define WSAECONNREFUSED SO_ECONNREFUSED + #define WSAELOOP SO_ELOOP + #define WSAENAMETOOLONG SO_ENAMETOOLONG + #define WSAEHOSTUNREACH SO_EHOSTUNREACH + #define WSAENOTEMPTY SO_ENOTEMPTY + #define WSAEDQUOT SO_EDQUOT + #define WSAESTALE SO_ESTALE + #define WSAEINVAL SO_EINVAL +#elif defined(_NITRO) + #define WSAEWOULDBLOCK SOC_EWOULDBLOCK + #define WSAEINPROGRESS SOC_EINPROGRESS + #define WSAEALREADY SOC_EALREADY + #define WSAENOTSOCK SOC_ENOTSOCK + #define WSAEDESTADDRREQ SOC_EDESTADDRREQ + #define WSAEMSGSIZE SOC_EMSGSIZE + #define WSAEPROTOTYPE SOC_EPROTOTYPE + #define WSAENOPROTOOPT SOC_ENOPROTOOPT + #define WSAEPROTONOSUPPORT SOC_EPROTONOSUPPORT + #define WSAEOPNOTSUPP SOC_EOPNOTSUPP + #define WSAEAFNOSUPPORT SOC_EAFNOSUPPORT + #define WSAEADDRINUSE SOC_EADDRINUSE + #define WSAEADDRNOTAVAIL SOC_EADDRNOTAVAIL + #define WSAENETDOWN SOC_ENETDOWN + #define WSAENETUNREACH SOC_ENETUNREACH + #define WSAENETRESET SOC_ENETRESET + #define WSAECONNABORTED SOC_ECONNABORTED + #define WSAECONNRESET SOC_ECONNRESET + #define WSAENOBUFS SOC_ENOBUFS + #define WSAEISCONN SOC_EISCONN + #define WSAENOTCONN SOC_ENOTCONN + #define WSAETIMEDOUT SOC_ETIMEDOUT + #define WSAECONNREFUSED SOC_ECONNREFUSED + #define WSAELOOP SOC_ELOOP + #define WSAENAMETOOLONG SOC_ENAMETOOLONG + #define WSAEHOSTUNREACH SOC_EHOSTUNREACH + #define WSAENOTEMPTY SOC_ENOTEMPTY + #define WSAEDQUOT SOC_EDQUOT + #define WSAESTALE SOC_ESTALE + #define WSAEINVAL SOC_EINVAL +#elif defined(_PS3) + #define WSAEWOULDBLOCK SYS_NET_EWOULDBLOCK + #define WSAEINPROGRESS SYS_NET_EINPROGRESS //SYS_NET_ERROR_EINPROGRESS + #define WSAEALREADY SYS_NET_EALREADY + #define WSAENOTSOCK SYS_NET_ENOTSOCK + #define WSAEDESTADDRREQ SYS_NET_EDESTADDRREQ + #define WSAEMSGSIZE SYS_NET_EMSGSIZE + #define WSAEPROTOTYPE SYS_NET_EPROTOTYPE + #define WSAENOPROTOOPT SYS_NET_ENOPROTOOPT + #define WSAEPROTONOSUPPORT SYS_NET_EPROTONOSUPPORT + #define WSAESOCKTNOSUPPORT SYS_NET_ESOCKTNOSUPPORT + #define WSAEOPNOTSUPP SYS_NET_EOPNOTSUPP + #define WSAEPFNOSUPPORT SYS_NET_EPFNOSUPPORT + #define WSAEAFNOSUPPORT SYS_NET_EAFNOSUPPORT + #define WSAEADDRINUSE SYS_NET_EADDRINUSE + #define WSAEADDRNOTAVAIL SYS_NET_EADDRNOTAVAIL + #define WSAENETDOWN SYS_NET_ENETDOWN + #define WSAENETUNREACH SYS_NET_ENETUNREACH + #define WSAENETRESET SYS_NET_ENETRESET + #define WSAECONNABORTED SYS_NET_ECONNABORTED + #define WSAECONNRESET SYS_NET_ECONNRESET // SYS_NET_ERROR_ECONNRESET + #define WSAENOBUFS SYS_NET_ENOBUFS // SYS_NET_ERROR_ENOBUFS + #define WSAEISCONN SYS_NET_EISCONN + #define WSAENOTCONN SYS_NET_ENOTCONN + #define WSAESHUTDOWN SYS_NET_ESHUTDOWN + #define WSAETOOMANYREFS SYS_NET_ETOOMANYREFS + #define WSAETIMEDOUT SYS_NET_ERROR_ETIMEDOUT + #define WSAECONNREFUSED SYS_NET_ECONNREFUSED + #define WSAELOOP SYS_NET_ELOOP + #define WSAENAMETOOLONG SYS_NET_ENAMETOOLONG + #define WSAEHOSTDOWN SYS_NET_EHOSTDOWN + #define WSAEHOSTUNREACH SYS_NET_EHOSTUNREACH + #define WSAENOTEMPTY SYS_NET_ENOTEMPTY + #define WSAEPROCLIM SYS_NET_EPROCLIM + #define WSAEUSERS SYS_NET_EUSERS + #define WSAEDQUOT SYS_NET_EDQUOT + #define WSAESTALE SYS_NET_ESTALE + #define WSAEREMOTE SYS_NET_EREMOTE + #define WSAEINVAL SYS_NET_EINVAL +#elif !defined(_WIN32) + #define WSAEWOULDBLOCK EWOULDBLOCK + #define WSAEINPROGRESS EINPROGRESS + #define WSAEALREADY EALREADY + #define WSAENOTSOCK ENOTSOCK + #define WSAEDESTADDRREQ EDESTADDRREQ + #define WSAEMSGSIZE EMSGSIZE + #define WSAEPROTOTYPE EPROTOTYPE + #define WSAENOPROTOOPT ENOPROTOOPT + #define WSAEPROTONOSUPPORT EPROTONOSUPPORT + #define WSAESOCKTNOSUPPORT ESOCKTNOSUPPORT + #define WSAEOPNOTSUPP EOPNOTSUPP + #define WSAEPFNOSUPPORT EPFNOSUPPORT + #define WSAEAFNOSUPPORT EAFNOSUPPORT + #define WSAEADDRINUSE EADDRINUSE + #define WSAEADDRNOTAVAIL EADDRNOTAVAIL + #define WSAENETDOWN ENETDOWN + #define WSAENETUNREACH ENETUNREACH + #define WSAENETRESET ENETRESET + #define WSAECONNABORTED ECONNABORTED + #define WSAECONNRESET ECONNRESET + #define WSAENOBUFS ENOBUFS + #define WSAEISCONN EISCONN + #define WSAENOTCONN ENOTCONN + #define WSAESHUTDOWN ESHUTDOWN + #define WSAETOOMANYREFS ETOOMANYREFS + #define WSAETIMEDOUT ETIMEDOUT + #define WSAECONNREFUSED ECONNREFUSED + #define WSAELOOP ELOOP + #define WSAENAMETOOLONG ENAMETOOLONG + #define WSAEHOSTDOWN EHOSTDOWN + #define WSAEHOSTUNREACH EHOSTUNREACH + #define WSAENOTEMPTY ENOTEMPTY + #define WSAEPROCLIM EPROCLIM + #define WSAEUSERS EUSERS + #define WSAEDQUOT EDQUOT + #define WSAESTALE ESTALE + #define WSAEREMOTE EREMOTE + #define WSAEINVAL EINVAL +#endif + +// make caps types interchangeable on all platforms +#if !defined(_WIN32) && !defined(_NITRO) && !defined(_REVOLUTION) // necessary for Wii?? + typedef int SOCKET; + typedef struct sockaddr SOCKADDR; + typedef struct sockaddr_in SOCKADDR_IN; + typedef struct in_addr IN_ADDR; + typedef struct hostent HOSTENT; + typedef struct timeval TIMEVAL; +#endif + +#ifdef EENET + #define GOAGetLastError(s) sceEENetErrno + #define closesocket sceEENetClose +#endif + +#ifdef INSOCK + //#define NETBUFSIZE (sceLIBNET_BUFFERSIZE) + #define NETBUFSIZE (32768) // buffer size for our samples + +// used in place of shutdown function to avoid blocking shutdown call +int gsiShutdown(SOCKET s, int how); + + #define GOAGetLastError(s) sceInsockErrno // not implemented + #define closesocket(s) gsiShutdown(s,SCE_INSOCK_SHUT_RDWR) + #undef shutdown + #define shutdown(s,h) gsiShutdown(s,h) +#endif + +#ifdef _UNIX + #define GOAGetLastError(s) errno + #define closesocket close //on unix +#endif + +#if !defined(_WIN32) + #define ioctlsocket ioctl +#endif + +#if defined(_WIN32) + #define GOAGetLastError(s) WSAGetLastError() +#endif + +#if defined(_REVOLUTION) + #define AF_INET SO_PF_INET + #define SOCK_DGRAM SO_SOCK_DGRAM + #define SOCK_STREAM SO_SOCK_STREAM + #define IPPROTO_UDP SO_IPPROTO_UDP + #define IPPROTO_TCP SO_IPPROTO_TCP + #define INADDR_ANY SO_INADDR_ANY + #define SOL_SOCKET SO_SOL_SOCKET + #define SO_SNDBUF SO_SO_SNDBUF + #define SO_RCVBUF SO_SO_RCVBUF + #define SO_REUSEADDR SO_SO_REUSEADDR + + typedef int SOCKET; + typedef struct SOSockAddr SOCKADDR; + #define sockaddr SOSockAddr + typedef struct SOSockAddrIn SOCKADDR_IN; + #define sockaddr_in SOSockAddrIn + #define sin_family family + #define sin_port port + #define sin_addr addr + typedef struct SOInAddr IN_ADDR; + #define in_addr SOInAddr + #define s_addr addr + typedef struct SOHostEnt HOSTENT; + #define hostent SOHostEnt + #define h_name name + #define h_aliases aliases + #define h_addrtype addrType + #define h_length length + #define h_addr_list addrList + #define h_addr addrList[0] + + int socket(int pf, int type, int protocol); + int closesocket(SOCKET sock); + int shutdown(SOCKET sock, int how); + int bind(SOCKET sock, const SOCKADDR* addr, int len); + + int connect(SOCKET sock, const SOCKADDR* addr, int len); + int listen(SOCKET sock, int backlog); + SOCKET accept(SOCKET sock, SOCKADDR* addr, int* len); + + int recv(SOCKET sock, char* buf, int len, int flags); + int recvfrom(SOCKET sock, char* buf, int len, int flags, SOCKADDR* addr, int* fromlen); + int send(SOCKET sock, const char* buf, int len, int flags); + int sendto(SOCKET sock, const char* buf, int len, int flags, const SOCKADDR* addr, int tolen); + + int getsockopt(SOCKET sock, int level, int optname, char* optval, int* optlen); + int setsockopt(SOCKET sock, int level, int optname, const char* optval, int optlen); + + #define gethostbyaddr(a,l,t) SOGetHostByAddr(a,l,t) + #define gethostbyname(n) SOGetHostByName(n) + + // thread safe DNS lookups + #define getaddrinfo(n,s,h,r) SOGetAddrInfo(n,s,h,r) + #define freeaddrinfo(a) SOFreeAddrInfo(a) + + + int getsockname(SOCKET sock, SOCKADDR* addr, int* len); + + #define htonl(l) SOHtoNl((u32)l) + #define ntohl(l) SONtoHl((u32)l) + #define htons(s) SOHtoNs((u16)s) + #define ntohs(s) SONtoHs((u16)s) + + #define inet_ntoa(n) SOInetNtoA(n) + unsigned long inet_addr(const char * name); + + int GOAGetLastError(SOCKET sock); +#endif + +#if defined(_NITRO) + #define AF_INET SOC_PF_INET + #define SOCK_DGRAM SOC_SOCK_DGRAM + #define SOCK_STREAM SOC_SOCK_STREAM + #define IPPROTO_UDP 0 + #define IPPROTO_TCP 0 + #define INADDR_ANY SOC_INADDR_ANY + #define SOL_SOCKET SOC_SOL_SOCKET + #define SO_SNDBUF SOC_SO_SNDBUF + #define SO_RCVBUF SOC_SO_RCVBUF + #define SO_REUSEADDR SOC_SO_REUSEADDR + + typedef int SOCKET; + typedef struct SOSockAddr SOCKADDR; + #define sockaddr SOSockAddr + typedef struct SOSockAddrIn SOCKADDR_IN; + #define sockaddr_in SOSockAddrIn + #define sin_family family + #define sin_port port + #define sin_addr addr + typedef struct SOInAddr IN_ADDR; + #define in_addr SOInAddr + #define s_addr addr + typedef struct SOHostEnt HOSTENT; + #define hostent SOHostEnt + #define h_name name + #define h_aliases aliases + #define h_addrtype addrType + #define h_length length + #define h_addr_list addrList + #define h_addr addrList[0] + + int socket(int pf, int type, int protocol); + int closesocket(SOCKET sock); + int shutdown(SOCKET sock, int how); + int bind(SOCKET sock, const SOCKADDR* addr, int len); + + int connect(SOCKET sock, const SOCKADDR* addr, int len); + int listen(SOCKET sock, int backlog); + SOCKET accept(SOCKET sock, SOCKADDR* addr, int* len); + + int recv(SOCKET sock, char* buf, int len, int flags); + int recvfrom(SOCKET sock, char* buf, int len, int flags, SOCKADDR* addr, int* fromlen); + int send(SOCKET sock, const char* buf, int len, int flags); + int sendto(SOCKET sock, const char* buf, int len, int flags, const SOCKADDR* addr, int tolen); + + int getsockopt(SOCKET sock, int level, int optname, char* optval, int* optlen); + int setsockopt(SOCKET sock, int level, int optname, const char* optval, int optlen); + + #define gethostbyaddr(a,l,t) SOC_GetHostByAddr(a,l,t) + #define gethostbyname(n) SOC_GetHostByName(n) + + int getsockname(SOCKET sock, SOCKADDR* addr, int* len); + + #define htonl(l) SOC_HtoNl(l) + #define ntohl(l) SOC_NtoHl(l) + #define htons(s) SOC_HtoNs(s) + #define ntohs(s) SOC_NtoHs(s) + + #define inet_ntoa(n) SOC_InetNtoA(n) + unsigned long inet_addr(const char * name); + + int GOAGetLastError(SOCKET sock); +#endif + +#if defined(_PS3) + #define accept(s,a,al) accept(s,a,(socklen_t*)(al)) + #define bind(s,a,al) bind(s,a,(socklen_t)(al)) + #define connect(s,a,al) connect(s,a,(socklen_t)(al)) + #define getpeername(s,a,al) getpeername(s,a,(socklen_t*)(al)) + #define getsockname(s,a,al) getsockname(s,a,(socklen_t*)(al)) + #define getsockopt(s,l,o,v,vl) getsockopt(s,l,o,v,(socklen_t*)(vl)) + #define recvfrom(s,b,l,f,a,al) recvfrom(s,b,l,f,a,(socklen_t*)(al)) + #define sendto(s,b,l,f,a,al) sendto(s,b,l,f,a,(socklen_t)(al)) + #define setsockopt(s,l,o,v,vl) setsockopt(s,l,o,v,(socklen_t)(vl)) + #define closesocket socketclose + #define GOAGetLastError(s) sys_net_errno + #define EWOULDBLOCK sceNET_EWOULDBLOCK +#endif + +#if defined(_MACOSX) + #define accept(s,a,al) accept(s,a,(socklen_t*)(al)) + #define bind(s,a,al) bind(s,a,(socklen_t)(al)) + #define connect(s,a,al) connect(s,a,(socklen_t)(al)) + #define getpeername(s,a,al) getpeername(s,a,(socklen_t*)(al)) + #define getsockname(s,a,al) getsockname(s,a,(socklen_t*)(al)) + #define getsockopt(s,l,o,v,vl) getsockopt(s,l,o,v,(socklen_t*)(vl)) + #define recvfrom(s,b,l,f,a,al) recvfrom(s,b,l,f,a,(socklen_t*)(al)) + #define sendto(s,b,l,f,a,al) sendto(s,b,l,f,a,(socklen_t)(al)) + #define setsockopt(s,l,o,v,vl) setsockopt(s,l,o,v,(socklen_t)(vl)) +#endif + +#if defined(SN_SYSTEMS) + int GOAGetLastError(SOCKET s); + + #if !defined(__MWERKS__) + #define send(s,b,l,f) (int)send(s,b,(unsigned long)l,f) + #define recv(s,b,l,f) (int)recv(s,b,(unsigned long)l,f) + #define sendto(s,b,l,f,a,al) (int)sendto(s,b,(unsigned long)l,f,a,al) + #define recvfrom(s,b,l,f,a,al) (int)recvfrom(s,b,(unsigned long)l,f,a,al) + #endif +#endif + +// SN Systems doesn't support gethostbyaddr +#if defined(SN_SYSTEMS) + #define gethostbyaddr(a,b,c) NULL +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Functions +int SetSockBlocking(SOCKET sock, int isblocking); +int SetSockBroadcast(SOCKET sock); +int DisableNagle(SOCKET sock); +int SetReceiveBufferSize(SOCKET sock, int size); +int SetSendBufferSize(SOCKET sock, int size); +int GetReceiveBufferSize(SOCKET sock); +int GetSendBufferSize(SOCKET sock); +int CanReceiveOnSocket(SOCKET sock); +int CanSendOnSocket(SOCKET sock); +int GSISocketSelect(SOCKET theSocket, int* theReadFlag, int* theWriteFlag, int* theExceptFlag); +void SocketStartUp(); +void SocketShutDown(); + +HOSTENT * getlocalhost(void); + +int IsPrivateIP(IN_ADDR * addr); +gsi_u32 gsiGetBroadcastIP(void); + + +#if defined(_PSP) + #define gethostbyaddr(a,b,c) NULL +#endif + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} +#endif + +#endif // __GSSOCKET_H__ + diff --git a/code/gamespy/common/gsPlatformThread.c b/code/gamespy/common/gsPlatformThread.c new file mode 100644 index 00000000..fd329350 --- /dev/null +++ b/code/gamespy/common/gsPlatformThread.c @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if !defined(GSI_NO_THREADS) +#include "gsCommon.h" + + +// Include platform separated functions +#if defined(_X360) + #include "x360/gsThreadX360.c" +#elif defined(_XBOX) + //#include "xbox/gsThreadXBox.c" +#elif defined(_WIN32) + #include "win32/gsThreadWin32.c" +#elif defined(_MACOSX) + #include "macosx/gsThreadMacOSX.c" +#elif defined (_LINUX) + #include "linux/gsThreadLinux.c" +#elif defined(_NITRO) + #include "nitro/gsThreadNitro.c" +#elif defined(_PS2) + #include "ps2/gsThreadPs2.c" +#elif defined(_PS3) +// #include "ps3/gsThreadPS3.c" +#elif defined(_PSP) +// #include "psp/gsThreadPSP.c" +#elif defined(_REVOLUTION) + #include "revolution/gsThreadRevoulution.c" +#else + #error "Missing or unsupported platform" +#endif + + +#if defined(__cplusplus) +extern "C" { +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} +#endif + +#endif // GSI_NO_THREADS diff --git a/code/gamespy/common/gsPlatformThread.h b/code/gamespy/common/gsPlatformThread.h new file mode 100644 index 00000000..85421ea8 --- /dev/null +++ b/code/gamespy/common/gsPlatformThread.h @@ -0,0 +1,197 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __GSPLATFORMTHREAD_H__ +#define __GSPLATFORMTHREAD_H__ + + +#include "gsPlatform.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Thread types +#if defined(_WIN32) + typedef CRITICAL_SECTION GSICriticalSection; + typedef HANDLE GSISemaphoreID; + typedef HANDLE GSIThreadID; + typedef DWORD (WINAPI *GSThreadFunc)(void *arg); + +#elif defined(_PS2) + typedef int GSIThreadID; + typedef int GSISemaphoreID; + typedef struct + { + // A critical section is a re-entrant semaphore + GSISemaphoreID mSemaphore; + GSIThreadID mOwnerThread; + gsi_u32 mEntryCount; // track re-entry + gsi_u32 mPad; // make 16bytes + } GSICriticalSection; + typedef void (*GSThreadFunc)(void *arg); + +#elif defined(_NITRO) + typedef OSMutex GSICriticalSection; + typedef struct + { + OSMutex mLock; + gsi_i32 mValue; + gsi_i32 mMax; + } GSISemaphoreID; + typedef struct + { + OSThread mThread; + void * mStack; + } GSIThreadID; + typedef void (*GSThreadFunc)(void *arg); + +#elif defined(_REVOLUTION) + typedef OSMutex GSICriticalSection; + typedef OSSemaphore GSISemaphoreID; + typedef struct + { + OSThread mThread; + void * mStack; + } GSIThreadID; + typedef void *(*GSThreadFunc)(void *arg); + +#elif defined(_PSP) + // Todo: Test PSP thread code, then remove this define + #define GSI_NO_THREADS + typedef int GSIThreadID; + typedef int GSISemaphoreID; + typedef struct + { + // A critical section is a re-entrant semaphore + GSISemaphoreID mSemaphore; + GSIThreadID mOwnerThread; + gsi_u32 mEntryCount; // track re-entry + gsi_u32 mPad; // make 16bytes + } GSICriticalSection; + typedef void (*GSThreadFunc)(void *arg); + +#elif defined(_PS3) + // Todo: Test PS3 ppu thread code, then remove this define + #define GSI_NO_THREADS + typedef int GSIThreadID; + typedef int GSISemaphoreID; + typedef struct + { + // A critical section is a re-entrant semaphore + GSISemaphoreID mSemaphore; + GSIThreadID mOwnerThread; + gsi_u32 mEntryCount; // track re-entry + gsi_u32 mPad; // make 16bytes + } GSICriticalSection; + typedef void (*GSThreadFunc)(void *arg); + +#elif defined(_UNIX) //_LINUX || _MACOSX + typedef pthread_mutex_t GSICriticalSection; + typedef struct + { + pthread_mutex_t mLock; + gsi_i32 mValue; + gsi_i32 mMax; + } GSISemaphoreID; + typedef struct + { + pthread_t thread; + pthread_attr_t attr; + } GSIThreadID; + typedef void (*GSThreadFunc)(void *arg); + +#else + #define GSI_NO_THREADS +#endif + +#if defined(WIN32) + #define GSI_INFINITE INFINITE +#else + #define GSI_INFINITE (gsi_u32)(-1) +#endif + + +#if !defined(GSI_NO_THREADS) + // The increment/read operations must not be preempted + #if defined(_WIN32) + #define gsiInterlockedIncrement(a) InterlockedIncrement((long*)a) + #define gsiInterlockedDecrement(a) InterlockedDecrement((long*)a) + #elif defined(_PS2) + gsi_u32 gsiInterlockedIncrement(gsi_u32* num); + gsi_u32 gsiInterlockedDecrement(gsi_u32* num); + #elif defined(_PS3) + // TODO - threading in PS3 uses pthreads, just like Linux + #elif defined(_NITRO) + gsi_u32 gsiInterlockedIncrement(gsi_u32* num); + gsi_u32 gsiInterlockedDecrement(gsi_u32* num); + #elif defined(_REVOLUTION) + gsi_u32 gsiInterlockedIncrement(gsi_u32* num); + gsi_u32 gsiInterlockedDecrement(gsi_u32* num); + #elif defined(_UNIX) + gsi_u32 gsiInterlockedIncrement(gsi_u32* num); + gsi_u32 gsiInterlockedDecrement(gsi_u32* num); + #endif + +#else + // Don't worry about concurrancy when GSI_NO_THREADS is defined + #define gsiInterlockedIncrement(a) (++(*a)) + #define gsiInterlockedDecrement(a) (--(*a)) +#endif + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if !defined(GSI_NO_THREADS) + int gsiStartThread(GSThreadFunc aThreadFunc, gsi_u32 theStackSize, void *arg, GSIThreadID* theThreadIdOut); + void gsiCancelThread(GSIThreadID theThreadID); + void gsiExitThread(GSIThreadID theThreadID); + void gsiCleanupThread(GSIThreadID theThreadID); + + // Thread Synchronization - Startup/Shutdown + gsi_u32 gsiHasThreadShutdown(GSIThreadID theThreadID); + + // Thread Synchronization - Critical Section + void gsiInitializeCriticalSection(GSICriticalSection *theCrit); + void gsiEnterCriticalSection(GSICriticalSection *theCrit); + void gsiLeaveCriticalSection(GSICriticalSection *theCrit); + void gsiDeleteCriticalSection(GSICriticalSection *theCrit); + + // Thread Synchronization - Semaphore + GSISemaphoreID gsiCreateSemaphore(gsi_i32 theInitialCount, gsi_i32 theMaxCount, char* theName); + gsi_u32 gsiWaitForSemaphore(GSISemaphoreID theSemaphore, gsi_u32 theTimeoutMs); + void gsiReleaseSemaphore(GSISemaphoreID theSemaphore, gsi_i32 theReleaseCount); + void gsiCloseSemaphore(GSISemaphoreID theSemaphore); + +#else + // NO THREADS - stub everything to unused + #define gsiStartThread(a, b, c, d) (-1) // must return something + #define gsiCancelThread(a) + #define gsiExitThread(a) + #define gsiCleanupThread(a) + + #define gsiHasThreadShutdown(a) (1) // must return something + + #define gsiInitializeCriticalSection(a) + #define gsiEnterCriticalSection(a) + #define gsiLeaveCriticalSection(a) + #define gsiDeleteCriticalSection(a) + + #define gsiCreateSemaphore(a,b,c) (-1) + #define gsiWaitForSemaphore(a,b) (0) + #define gsiReleaseSemaphore(a,b) + #define gsiCloseSemaphore(a) + +#endif // GSI_NO_THREADS + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} +#endif + +#endif // __GSPLATFORMTHREAD_H__ diff --git a/code/gamespy/common/gsPlatformUtil.c b/code/gamespy/common/gsPlatformUtil.c new file mode 100644 index 00000000..69c81e0c --- /dev/null +++ b/code/gamespy/common/gsPlatformUtil.c @@ -0,0 +1,1897 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsCommon.h" +#include "gsPlatformUtil.h" + +// Include platform separated functions +#if defined(_X360) + //#include "x360/gsUtilX360.c" +#elif defined(_XBOX) + //#include "xbox/gsUtilXBox.c" +#elif defined(_WIN32) + #include "win32/gsUtilWin32.c" +#elif defined(_LINUX) + #include "linux/gsUtilLinux.c" +#elif defined(_MACOSX) + #include "macosx/gsUtilMacOSX.c" +#elif defined(_NITRO) + #include "nitro/gsUtilNitro.c" +#elif defined(_PS2) + #include "ps2/gsUtilPs2.c" +#elif defined(_PS3) + #include "ps3/gsUtilPs3.c" +#elif defined(_PSP) + #include "psp/gsUtilPSP.c" +#elif defined(_REVOLUTION) + #include "revolution/gsUtilRevolution.c" +#else + #error "Missing or unsupported platform" +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// ********** ASYNC DNS ********** // + +//struct is used in both threaded and non-threaded versions +typedef struct GSIResolveHostnameInfo +{ + char * hostname; + unsigned int ip; + +#if defined(_WIN32) /*|| defined(_PS2)*/ || defined(_UNIX) || defined (_REVOLUTION) + int finishedResolving; + GSIThreadID threadID; +#endif + +/*#if defined(_PSP) + int finishedResolving; + GSIThreadID threadID; +#endif*/ +} GSIResolveHostnameInfo; + +//////////////////////////////////////////////////////////////////////////////// +// for asynch DNS, must have: +// * platform that supports threaded lookup AND +// * threading enabled +// * and async lookup enabled +//////////////////////////////////////////////////////////////////////////////// +#if (defined(_WIN32) || /*defined(_PS2) ||*/ defined(_UNIX) || defined (_REVOLUTION)) && !defined(GSI_NO_THREADS) && !defined(GSI_NO_ASYNC_DNS) + +//////////////////////////////////////////////////////////////////////////////// +#if defined(_WIN32) /*|| defined(_PS2)*/ + #if defined(_WIN32) + DWORD WINAPI gsiResolveHostnameThread(void * arg) + /*#elif defined(_PS2) + static void gsiResolveHostnameThread(void * arg)*/ + #endif + { + HOSTENT * hostent; + GSIResolveHostnameHandle handle = (GSIResolveHostnameHandle)arg; + + SocketStartUp(); + + #ifdef SN_SYSTEMS + sockAPIregthr(); + #endif + + // do the gethostbyname + hostent = gethostbyname(handle->hostname); + if(hostent) + { + // got the ip + handle->ip = *(unsigned int *)hostent->h_addr_list[0]; + } + else + { + // didn't resolve + handle->ip = GSI_ERROR_RESOLVING_HOSTNAME; + } + + SocketShutDown(); + + // finished resolving + handle->finishedResolving = 1; + + #ifdef SN_SYSTEMS + sockAPIderegthr(); + #endif + + // explicitly exit the thread to free resources + gsiExitThread(handle->threadID); + + #if defined(_WIN32) + return 0; + #endif +} +#endif //defined _WIN32 +//////////////////////////////////////////////////////////////////////////////// + + +#ifdef _REVOLUTION +/////////////////////////////////////////////////////////////////////////////// +static void *gsiResolveHostnameThread(void * arg) +{ + static GSICriticalSection aHostnameCrit; + static int aInitialized = 0; + //SOAddrInfo *aHostAddr; + HOSTENT *aHostAddr; + //int retval; + GSIResolveHostnameHandle handle = (GSIResolveHostnameHandle)arg; + + if (!aInitialized) + { + gsiInitializeCriticalSection(&aHostnameCrit); + aInitialized = 1; + } + gsiEnterCriticalSection(&aHostnameCrit); + + //retval = getaddrinfo(handle->hostname, NULL, NULL, &aHostAddr); + aHostAddr = gethostbyname(handle->hostname); + if (aHostAddr != 0) + { + char * ip; + // first convert to character string for debug output + ip = inet_ntoa(*(in_addr *)aHostAddr->addrList[0]); + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "Resolved host '%s' to ip '%s'\n", handle->hostname, ip); + + handle->ip = inet_addr(ip); + //freeaddrinfo(aHostAddr); + } + else + { + // couldnt reach host - debug output is printed later + handle->ip = GSI_ERROR_RESOLVING_HOSTNAME; + } + + + // finished resolving + handle->finishedResolving = 1; + + gsiLeaveCriticalSection(&aHostnameCrit); +} +#endif // _REVOLUTION +//////////////////////////////////////////////////////////////////////////////// + +// +// Linux/MacOSX implementation of multithreaded DNS lookup +// Uses getaddrinfo instead of gethostbyname - since the latter +// has static declarations and is thus un-safe for pthreads +// +// NOTE: The compiler option "-lpthread" must used for this +#if defined(_UNIX) +//////////////////////////////////////////////////////////////////////////////// +static void gsiResolveHostnameThread(void * arg) +{ + GSIResolveHostnameHandle handle = (GSIResolveHostnameHandle)arg; + struct addrinfo hints, *result = NULL; + int error; + char *ip; + + SocketStartUp(); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + // DNS lookup (works with pthreads) + error = getaddrinfo(handle->hostname, "http", &hints, &result); + + if (!error) + { + // first convert to character string for debug output + ip = inet_ntoa((*(struct sockaddr_in*)result->ai_addr).sin_addr); + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "Resolved host '%s' to ip '%s'\n", handle->hostname, ip); + + // now convert to unsigned int and store it + handle->ip = inet_addr(ip); + + // free the memory used + freeaddrinfo(result); + } + else + { + // couldnt reach host - debug output is printed later + handle->ip = GSI_ERROR_RESOLVING_HOSTNAME; + } + + SocketShutDown(); + + // finished resolving + handle->finishedResolving = 1; + + // explicitly exit the thread to free resources + gsiExitThread(handle->threadID); +} +#endif //_UNIX +//////////////////////////////////////////////////////////////////////////////// + + +int gsiStartResolvingHostname(const char * hostname, GSIResolveHostnameHandle * handle) +{ + GSIResolveHostnameInfo * info; + + //PS2 Threading unsupported in current build - this should never be reached +#if defined(_PS2) + GS_ASSERT_STR(gsi_false, "PS2 Threading unsupported in current version of the SDK\n"); +#endif + + // allocate a handle + info = (GSIResolveHostnameInfo *)gsimalloc(sizeof(GSIResolveHostnameInfo)); + if(!info) + return -1; + + // make a copy of the hostname so the thread has access to it + info->hostname = goastrdup(hostname); + if(!info->hostname) + { + gsifree(info); + return -1; + } + + // not resolved yet + info->finishedResolving = 0; + + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_State, GSIDebugLevel_Comment, + "(Asynchrounous) DNS lookup starting\n"); + + // start the thread + if(gsiStartThread(gsiResolveHostnameThread, (0x1000), info, &info->threadID) == -1) + { + gsifree(info->hostname); + info->hostname = NULL; + gsifree(info); + info = NULL; + return -1; + } + + // set the handle to the info + *handle = info; + + return 0; +} + +void gsiCancelResolvingHostname(GSIResolveHostnameHandle handle) +{ + // cancel the thread + gsiCancelThread(handle->threadID); + + if (handle->hostname) + { + gsifree(handle->hostname); + handle->hostname = NULL; + } + gsifree(handle); + handle = NULL; +} + +unsigned int gsiGetResolvedIP(GSIResolveHostnameHandle handle) +{ + unsigned int ip; + + // check if we haven't finished + if(!handle->finishedResolving) + return GSI_STILL_RESOLVING_HOSTNAME; + + // save the ip + ip = handle->ip; + + // free resources + gsiCleanupThread(handle->threadID); + gsifree(handle->hostname); + gsifree(handle); + handle = NULL; + + return ip; +} + + +#else // if * not a supported platform OR * no threads allowed OR * no async lookup allowed + /////////////////////////////////////////////////////////////////////////////////// + // if !(_WIN32 ||_PS2 || _LINUX || _MACOSX || _REVOLUTION) || GSI_NO_THREADS || GSI_NO_ASYNC_DNS + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// ********** NON-ASYNC DNS ********** // +// +// These are the non-threaded version of the above functions. +// The following platforms have synchronous DNS lookups: +// _NITRO || _XBOX || _X360 || _PS3 || _PS2 || _PSP +/////////////////////////////////////////////////////////////////////////////// + +int gsiStartResolvingHostname(const char * hostname, GSIResolveHostnameHandle * handle) +{ + GSIResolveHostnameInfo * info; + HOSTENT * hostent; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "(NON-Asynchrounous) DNS lookup starting\n"); + + // do the lookup now + hostent = gethostbyname(hostname); + if(hostent == NULL) + return -1; + + // allocate info to store the result + info = (GSIResolveHostnameHandle)gsimalloc(sizeof(GSIResolveHostnameInfo)); + if(!info) + return -1; + + // we already have the ip + info->ip = *(unsigned int *)hostent->h_addr_list[0]; + + // set the handle to the info + *handle = info; + + return 0; +} + +void gsiCancelResolvingHostname(GSIResolveHostnameHandle handle) +{ + gsifree(handle); + handle = NULL; +} + +unsigned int gsiGetResolvedIP(GSIResolveHostnameHandle handle) +{ + // we always do the resolve in the initial call for systems without + // an async version, so we'll always have the IP at this point + unsigned int ip = handle->ip; + gsifree(handle); + handle = NULL; + return ip; +} + +/////////////////////////////////////////////////////////////////////////////// +#endif // synch DNS lookup + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +char * goastrdup(const char *src) +{ + char *res; + if(src == NULL) //PANTS|02.11.00|check for NULL before strlen + return NULL; + res = (char *)gsimalloc(strlen(src) + 1); + if(res != NULL) //PANTS|02.02.00|check for NULL before strcpy + strcpy(res, src); + return res; +} + +unsigned short * goawstrdup(const unsigned short *src) +{ + unsigned short *res; + if(src == NULL) + return NULL; + res = (unsigned short *)gsimalloc((wcslen((wchar_t*)src) + 1) * sizeof(unsigned short)); + if(res != NULL) + wcscpy((wchar_t*)res, (const wchar_t*)src); + return res; +} + +#if !defined(_WIN32) + +char *_strlwr(char *string) +{ + char *hold = string; + while (*string) + { + *string = (char)tolower(*string); + string++; + } + + return hold; +} + +char *_strupr(char *string) +{ + char *hold = string; + while (*string) + { + *string = (char)toupper(*string); + string++; + } + + return hold; +} +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void SocketStartUp() +{ +#if defined(_WIN32) + WSADATA data; + + #if defined(_X360) + XNetStartupParams xnsp; + memset(&xnsp,0,sizeof(xnsp)); + xnsp.cfgSizeOfStruct=sizeof(xnsp); + xnsp.cfgFlags=XNET_STARTUP_BYPASS_SECURITY; + if(0 != XNetStartup(&xnsp)) + { + OutputDebugString("XNetStartup failed\n"); + } + #endif + + // added support for winsock2 + #if (!defined(_XBOX) || defined(_X360)) && (defined(GSI_WINSOCK2) || defined(_X360)) + WSAStartup(MAKEWORD(2,2), &data); + #else + WSAStartup(MAKEWORD(1,1), &data); + #endif + // end added +#endif +} + +void SocketShutDown() +{ +#if defined(_WIN32) + WSACleanup(); + #if defined(_X360) + XNetCleanup(); + #endif +#endif +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef _PS2 +extern int sceCdReadClock(); + +#if !defined(__MWERKS__) && !defined(_PS2) +typedef unsigned char u_char; +#endif + +typedef struct { + u_char stat; /* status */ + u_char second; /* second */ + u_char minute; /* minute */ + u_char hour; /* hour */ + + u_char pad; /* pad */ + u_char day; /* day */ + u_char month; /* month */ + u_char year; /* year */ +} sceCdCLOCK; + +static unsigned long GetTicks() +{ + unsigned long ticks; + asm volatile (" mfc0 %0, $9 " : "=r" (ticks)); + return ticks; +} + +#define DEC(x) (10*(x/16)+(x%16)) +#define _BASE_YEAR 70L +#define _MAX_YEAR 138L +#define _LEAP_YEAR_ADJUST 17L +int _days[] = {-1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364}; + +static time_t _gmtotime_t ( + int yr, /* 0 based */ + int mo, /* 1 based */ + int dy, /* 1 based */ + int hr, + int mn, + int sc + ) +{ + int tmpdays; + long tmptim; + struct tm tb; + + if ( ((long)(yr -= 1900) < _BASE_YEAR) || ((long)yr > _MAX_YEAR) ) + return (time_t)(-1); + + tmpdays = dy + _days[mo - 1]; + if ( !(yr & 3) && (mo > 2) ) + tmpdays++; + + tmptim = (long)yr - _BASE_YEAR; + + tmptim = ( ( ( ( tmptim ) * 365L + + ((long)(yr - 1) >> 2) - (long)_LEAP_YEAR_ADJUST + + (long)tmpdays ) + * 24L + (long)hr ) + * 60L + (long)mn ) + * 60L + (long)sc; + + tb.tm_yday = tmpdays; + tb.tm_year = yr; + tb.tm_mon = mo - 1; + tb.tm_hour = hr; + + return (tmptim >= 0) ? (time_t)tmptim : (time_t)(-1); +} + +time_t time(time_t *timer) +{ + time_t tim; + sceCdCLOCK clocktime; /* defined in libcdvd.h */ + + sceCdReadClock(&clocktime); /* libcdvd.a */ + + tim = _gmtotime_t ( DEC(clocktime.year)+2000, + DEC(clocktime.month), + DEC(clocktime.day), + DEC(clocktime.hour), + DEC(clocktime.minute), + DEC(clocktime.second)); + + if(timer) + *timer = tim; + + return tim; +} + +#endif /* _PS2 */ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_time current_time() //returns current time in milliseconds +{ +#if defined(_WIN32) + return (GetTickCount()); + +#elif defined(_PS2) + unsigned int ticks; + static unsigned int msec = 0; + static unsigned int lastticks = 0; + sceCdCLOCK lasttimecalled; /* defined in libcdvd.h */ + + if(!msec) + { + sceCdReadClock(&lasttimecalled); /* libcdvd.a */ + msec = (unsigned int)(DEC(lasttimecalled.day) * 86400000) + + (unsigned int)(DEC(lasttimecalled.hour) * 3600000) + + (unsigned int)(DEC(lasttimecalled.minute) * 60000) + + (unsigned int)(DEC(lasttimecalled.second) * 1000); + } + + ticks = (unsigned int)GetTicks(); + if(lastticks > ticks) + msec += (unsigned int)(((unsigned int)(-1) - lastticks) + ticks) / 300000; + else + msec += (unsigned int)(ticks-lastticks) / 300000; + lastticks = ticks; + + return msec; + +#elif defined(_UNIX) + struct timeval time; + + gettimeofday(&time, NULL); + return (time.tv_sec * 1000 + time.tv_usec / 1000); + +#elif defined(_NITRO) + assert(OS_IsTickAvailable() == TRUE); + return (gsi_time)OS_TicksToMilliSeconds(OS_GetTick()); + +#elif defined(_PSP) + struct SceRtcTick ticks; + int result = 0; + + result = sceRtcGetCurrentTick(&ticks); + if (result < 0) + { + ScePspDateTime time; + result = sceRtcGetCurrentClock(&time, 0); + if (result < 0) + return 0; // um...error handling? //Nope, should return zero since time cannot be zero + result = sceRtcGetTick(&time, &ticks); + if (result < 0) + return 0; //Nope, should return zero since time cannot be zero + } + + return (gsi_time)(ticks.tick / 1000); + +#elif defined(_PS3) + return (gsi_time)(sys_time_get_system_time()/1000); + +#elif defined(_REVOLUTION) + OSTick aTickNow= OSGetTick(); + gsi_time aMilliseconds = (gsi_time)OSTicksToMilliseconds(aTickNow); + return aMilliseconds; +#else + // unrecognized platform! contact devsupport + assert(0); +#endif + +} + +gsi_time current_time_hires() // returns current time in microseconds +{ +#ifdef _WIN32 +#if (!defined(_M_IX86) || (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 64)) + static LARGE_INTEGER counterFrequency; + static BOOL haveCounterFrequency = FALSE; + static BOOL haveCounter = FALSE; + LARGE_INTEGER count; + + if(!haveCounterFrequency) + { + haveCounter = QueryPerformanceFrequency(&counterFrequency); + haveCounterFrequency = TRUE; + } + + if(haveCounter) + { + if(QueryPerformanceCounter(&count)) + { + return (gsi_time)(count.QuadPart * 1000000 / counterFrequency.QuadPart); + } + } +#endif + + return (current_time() / 1000); +#endif + +#ifdef _PS2 + unsigned int ticks; + static unsigned int msec = 0; + static unsigned int lastticks = 0; + sceCdCLOCK lasttimecalled; /* defined in libcdvd.h */ + + if(!msec) + { + sceCdReadClock(&lasttimecalled); /* libcdvd.a */ + msec = (unsigned int)(DEC(lasttimecalled.day) * 86400000) + + (unsigned int)(DEC(lasttimecalled.hour) * 3600000) + + (unsigned int)(DEC(lasttimecalled.minute) * 60000) + + (unsigned int)(DEC(lasttimecalled.second) * 1000); + msec *= 1000; + } + + ticks = (unsigned int)GetTicks(); + if(lastticks > ticks) + msec += ((sizeof(unsigned int) - lastticks) + ticks) / 300; + else + msec += (unsigned int)(ticks-lastticks) / 300; + lastticks = ticks; + + return msec; +#endif + +#ifdef _PSP + struct SceRtcTick ticks; + int result = 0; + + result = sceRtcGetCurrentTick(&ticks); + if (result < 0) + { + ScePspDateTime time; + result = sceRtcGetCurrentClock(&time, 0); + if (result < 0) + return 0; // um...error handling? //Nope, should return zero since time cannot be zero + result = sceRtcGetTick(&time, &ticks); + if (result < 0) + return 0; //Nope, should return zero since time cannot be zero + } + + return (gsi_time)(ticks.tick); +#endif + +#ifdef _UNIX + struct timeval time; + + gettimeofday(&time, NULL); + return (time.tv_sec * 1000000 + time.tv_usec); +#endif + +#ifdef _NITRO + assert(OS_IsTickAvailable() == TRUE); + return (gsi_time)OS_TicksToMicroSeconds(OS_GetTick()); +#endif + +#ifdef _PS3 + return (gsi_time)sys_time_get_system_time(); +#endif +} + + +void msleep(gsi_time msec) +{ +#if defined(_WIN32) + Sleep(msec); + +#elif defined(_PS2) + #ifdef SN_SYSTEMS + sn_delay((int)msec); + #endif + #ifdef EENET + if(msec >= 1000) + { + sleep(msec / 1000); + msec -= (msec / 1000); + } + if(msec) + usleep(msec * 1000); + #endif + #ifdef INSOCK + DelayThread(msec * 1000); + #endif + +#elif defined(_PSP) + sceKernelDelayThread(msec * 1000); + +#elif defined(_UNIX) + usleep(msec * 1000); + +#elif defined(_NITRO) + OS_Sleep(msec); + +#elif defined(_PS3) + sys_timer_usleep(msec* 1000); +#elif defined (_REVOLUTION) + OSSleepMilliseconds(msec); +#else + assert(0); // missing platform handler, contact devsupport +#endif +} + +////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// +// Cross-platform GSI wrapper time conversion functions +// +// NOTE: some portions of this copied from standard C library +#if defined(_NITRO) || defined(_REVOLUTION) + +// if an error occurs when calling mktime, return -1 +#define MKTIME_ERROR (time_t)(-1) + +// define common conversions for mktime +#define DAY_SEC (24L * 60L * 60L) /* secs in a day */ +#define YEAR_SEC (365L * DAY_SEC) /* secs in a year */ +#define FOUR_YEAR_SEC (1461L * DAY_SEC) /* secs in a 4 year interval */ +#define DEC_SEC 315532800L /* secs in 1970-1979 */ +#define BASE_DOW 4 /* 01-01-70 was a Thursday */ +#define BASE_YEAR 70L /* 1970 is the base year */ +#define LEAP_YEAR_ADJUST 17L /* Leap years 1900 - 1970 */ +#define MAX_YEAR 138L /* 2038 is the max year */ + +// ChkAdd evaluates to TRUE if dest = src1 + src2 has overflowed +#define ChkAdd(dest, src1, src2) ( ((src1 >= 0L) && (src2 >= 0L) \ + && (dest < 0L)) || ((src1 < 0L) && (src2 < 0L) && (dest >= 0L)) ) + +// ChkMul evaluates to TRUE if dest = src1 * src2 has overflowed +#define ChkMul(dest, src1, src2) ( src1 ? (dest/src1 != src2) : 0 ) + +int _lpdays[] = { -1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; +int _days[] = { -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364 }; + +const char _dnames[] = { "SunMonTueWedThuFriSat" }; +/* Month names must be Three character abbreviations strung together */ +const char _mnames[] = { "JanFebMarAprMayJunJulAugSepOctNovDec" }; + +static struct tm tb = { 0 }; /* time block used in SecondsToDate */ + +static char buf[26]; /* buffer used to store string in SecondsToString */ + + +static char * store_dt(char *, int); +static char * store_dt(char *p, int val) +{ + *p++ = (char)(_T('0') + val / 10); + *p++ = (char)(_T('0') + val % 10); + return(p); +} +#endif //_NITRO || _REVOLUTION + +////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// +// GSI equivalent of Standard C-lib "gmtime function" +struct tm * gsiSecondsToDate(const time_t *timp) +{ +#if !defined(_NITRO) && !defined(_REVOLUTION) + + // for all platforms that support the standard C 'gmtime' use that + return gmtime(timp); + +#else + time_t caltim = *timp; /* calendar time to convert */ + int islpyr = 0; /* is-current-year-a-leap-year flag */ + int tmptim; + int *mdays; /* pointer to days or lpdays */ + struct tm *ptb = &tb; + + + if ( caltim < 0L ) + return(NULL); + + /* + * Determine years since 1970. First, identify the four-year interval + * since this makes handling leap-years easy (note that 2000 IS a + * leap year and 2100 is out-of-range). + */ + tmptim = (int)(caltim / FOUR_YEAR_SEC); + caltim -= ((long)tmptim * FOUR_YEAR_SEC); + + /* + * Determine which year of the interval + */ + tmptim = (tmptim * 4) + 70; /* 1970, 1974, 1978,...,etc. */ + + if ( caltim >= YEAR_SEC ) + { + tmptim++; /* 1971, 1975, 1979,...,etc. */ + caltim -= YEAR_SEC; + + if ( caltim >= YEAR_SEC ) + { + tmptim++; /* 1972, 1976, 1980,...,etc. */ + caltim -= YEAR_SEC; + + /* + * Note, it takes 366 days-worth of seconds to get past a leap + * year. + */ + if ( caltim >= (YEAR_SEC + DAY_SEC) ) + { + tmptim++; /* 1973, 1977, 1981,...,etc. */ + caltim -= (YEAR_SEC + DAY_SEC); + } + else + { + /* + * In a leap year after all, set the flag. + */ + islpyr++; + } + } + } + + /* + * tmptim now holds the value for tm_year. caltim now holds the + * number of elapsed seconds since the beginning of that year. + */ + ptb->tm_year = tmptim; + + /* + * Determine days since January 1 (0 - 365). This is the tm_yday value. + * Leave caltim with number of elapsed seconds in that day. + */ + ptb->tm_yday = (int)(caltim / DAY_SEC); + caltim -= (long)(ptb->tm_yday) * DAY_SEC; + + /* + * Determine months since January (0 - 11) and day of month (1 - 31) + */ + if ( islpyr ) + mdays = _lpdays; + else + mdays = _days; + + + for ( tmptim = 1 ; mdays[tmptim] < ptb->tm_yday ; tmptim++ ) ; + + ptb->tm_mon = --tmptim; + + ptb->tm_mday = ptb->tm_yday - mdays[tmptim]; + + /* + * Determine days since Sunday (0 - 6) + */ + ptb->tm_wday = ((int)(*timp / DAY_SEC) + BASE_DOW) % 7; + + /* + * Determine hours since midnight (0 - 23), minutes after the hour + * (0 - 59), and seconds after the minute (0 - 59). + */ + ptb->tm_hour = (int)(caltim / 3600); + caltim -= (long)ptb->tm_hour * 3600L; + + ptb->tm_min = (int)(caltim / 60); + ptb->tm_sec = (int)(caltim - (ptb->tm_min) * 60); + + ptb->tm_isdst = 0; + return( (struct tm *)ptb ); +#endif +} + +////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// +// GSI equivalent of Standard C-lib "mktime function" +time_t gsiDateToSeconds(struct tm *tb) +{ +#if !defined(_NITRO) && !defined(_REVOLUTION) + + // for all platforms that support the standard C 'mktime' use that + return mktime(tb); + +#else + time_t tmptm1, tmptm2, tmptm3; + struct tm *tbtemp; + /* + * First, make sure tm_year is reasonably close to being in range. + */ + if ( ((tmptm1 = tb->tm_year) < BASE_YEAR - 1) || (tmptm1 > MAX_YEAR + 1) ) + return MKTIME_ERROR; + + + /* + * Adjust month value so it is in the range 0 - 11. This is because + * we don't know how many days are in months 12, 13, 14, etc. + */ + + if ( (tb->tm_mon < 0) || (tb->tm_mon > 11) ) { + + /* + * no danger of overflow because the range check above. + */ + tmptm1 += (tb->tm_mon / 12); + + if ( (tb->tm_mon %= 12) < 0 ) { + tb->tm_mon += 12; + tmptm1--; + } + + /* + * Make sure year count is still in range. + */ + if ( (tmptm1 < BASE_YEAR - 1) || (tmptm1 > MAX_YEAR + 1) ) + return MKTIME_ERROR; + } + + /***** HERE: tmptm1 holds number of elapsed years *****/ + + /* + * Calculate days elapsed minus one, in the given year, to the given + * month. Check for leap year and adjust if necessary. + */ + tmptm2 = _days[tb->tm_mon]; + if ( !(tmptm1 & 3) && (tb->tm_mon > 1) ) + tmptm2++; + + /* + * Calculate elapsed days since base date (midnight, 1/1/70, UTC) + * + * + * 365 days for each elapsed year since 1970, plus one more day for + * each elapsed leap year. no danger of overflow because of the range + * check (above) on tmptm1. + */ + tmptm3 = (tmptm1 - BASE_YEAR) * 365L + ((tmptm1 - 1L) >> 2) + - LEAP_YEAR_ADJUST; + + /* + * elapsed days to current month (still no possible overflow) + */ + tmptm3 += tmptm2; + + /* + * elapsed days to current date. overflow is now possible. + */ + tmptm1 = tmptm3 + (tmptm2 = (long)(tb->tm_mday)); + if ( ChkAdd(tmptm1, tmptm3, tmptm2) ) + return MKTIME_ERROR; + + /***** HERE: tmptm1 holds number of elapsed days *****/ + + /* + * Calculate elapsed hours since base date + */ + tmptm2 = tmptm1 * 24L; + if ( ChkMul(tmptm2, tmptm1, 24L) ) + return MKTIME_ERROR; + + tmptm1 = tmptm2 + (tmptm3 = (long)tb->tm_hour); + if ( ChkAdd(tmptm1, tmptm2, tmptm3) ) + return MKTIME_ERROR; + + /***** HERE: tmptm1 holds number of elapsed hours *****/ + + /* + * Calculate elapsed minutes since base date + */ + + tmptm2 = tmptm1 * 60L; + if ( ChkMul(tmptm2, tmptm1, 60L) ) + return MKTIME_ERROR; + + tmptm1 = tmptm2 + (tmptm3 = (long)tb->tm_min); + if ( ChkAdd(tmptm1, tmptm2, tmptm3) ) + return MKTIME_ERROR; + + /***** HERE: tmptm1 holds number of elapsed minutes *****/ + + /* + * Calculate elapsed seconds since base date + */ + + tmptm2 = tmptm1 * 60L; + if ( ChkMul(tmptm2, tmptm1, 60L) ) + return MKTIME_ERROR; + + tmptm1 = tmptm2 + (tmptm3 = (long)tb->tm_sec); + if ( ChkAdd(tmptm1, tmptm2, tmptm3) ) + return MKTIME_ERROR; + + /***** HERE: tmptm1 holds number of elapsed seconds *****/ + + if ( (tbtemp = gsiSecondsToDate(&tmptm1)) == NULL ) + return MKTIME_ERROR; + + + /***** HERE: tmptm1 holds number of elapsed seconds, adjusted *****/ + /***** for local time if requested *****/ + + *tb = *tbtemp; + return (time_t)tmptm1; +#endif +} + +////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// +// GSI equivalent of Standard C-lib "ctime function" +char * gsiSecondsToString(const time_t *timp) +{ +#if !defined(_NITRO) && !defined(_REVOLUTION) + + // for all platforms that support the standard C 'ctime' use that + return ctime(timp); + +#else + char *p = buf; + int day, mon; + int i; + struct tm *ptm; + + ptm = gsiSecondsToDate(timp); /* parse seconds into date structure */ + + p = buf; /* use static buffer */ + + /* copy day and month names into the buffer */ + + day = ptm->tm_wday * 3; /* index to correct day string */ + mon = ptm->tm_mon * 3; /* index to correct month string */ + + for (i=0; i < 3; i++,p++) { + *p = *(_dnames + day + i); + *(p+4) = *(_mnames + mon + i); + } + + *p = _T(' '); /* blank between day and month */ + + p += 4; + + *p++ = _T(' '); + p = store_dt(p, ptm->tm_mday); /* day of the month (1-31) */ + *p++ = _T(' '); + p = store_dt(p, ptm->tm_hour); /* hours (0-23) */ + *p++ = _T(':'); + p = store_dt(p, ptm->tm_min); /* minutes (0-59) */ + *p++ = _T(':'); + p = store_dt(p, ptm->tm_sec); /* seconds (0-59) */ + *p++ = _T(' '); + p = store_dt(p, 19 + (ptm->tm_year/100)); /* year (after 1900) */ + p = store_dt(p, ptm->tm_year%100); + *p++ = _T('\n'); + *p = _T('\0'); + + return ((char *) buf); +#endif +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Cross platform random number generator +#define RANa 16807 // multiplier +#define LONGRAND_MAX 2147483647L // 2**31 - 1 + +static long randomnum = 1; + +static long nextlongrand(long seed) +{ + unsigned + + long lo, hi; + lo = RANa *(unsigned long)(seed & 0xFFFF); + hi = RANa *((unsigned long)seed >> 16); + lo += (hi & 0x7FFF) << 16; + + if (lo > LONGRAND_MAX) + { + lo &= LONGRAND_MAX; + ++lo; + } + lo += hi >> 15; + + if (lo > LONGRAND_MAX) + { + lo &= LONGRAND_MAX; + ++lo; + } + + return(long)lo; +} + +// return next random long +static long longrand(void) +{ + randomnum = nextlongrand(randomnum); + return randomnum; +} + +// to seed it +void Util_RandSeed(unsigned long seed) +{ + // nonzero seed + randomnum = seed ? (long)(seed & LONGRAND_MAX) : 1; +} + +int Util_RandInt(int low, int high) +{ + unsigned int range = (unsigned int)high-low; + int num; + + if (range == 0) + return (low); // Prevent divide by zero + + num = (int)(longrand() % range); + + return(num + low); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/***************************** +UNICODE ENCODING +******************************/ + +static void QuartToTrip(char *quart, char *trip, int inlen) +{ + if (inlen >= 2) + trip[0] = (char)(quart[0] << 2 | quart[1] >> 4); + if (inlen >= 3) + trip[1] = (char)((quart[1] & 0x0F) << 4 | quart[2] >> 2); + if (inlen >= 4) + trip[2] = (char)((quart[2] & 0x3) << 6 | quart[3]); +} + +static void TripToQuart(const char *trip, char *quart, int inlen) +{ + unsigned char triptemp[3]; + int i; + for (i = 0; i < inlen ; i++) + { + triptemp[i] = (unsigned char)trip[i]; + } + while (i < 3) //fill the rest with 0 + { + triptemp[i] = 0; + i++; + } + quart[0] = (char)(triptemp[0] >> 2); + quart[1] = (char)(((triptemp[0] & 3) << 4) | (triptemp[1] >> 4)); + quart[2] = (char)((triptemp[1] & 0x0F) << 2 | (triptemp[2] >> 6)); + quart[3] = (char)(triptemp[2] & 0x3F); + +} + +const char defaultEncoding[] = {'+','/','='}; +const char alternateEncoding[] = {'[',']','_'}; +const char urlSafeEncodeing[] = {'-','_','='}; + +void B64Decode(const char *input, char *output, int inlen, int * outlen, int encodingType) +{ + const char *encoding = NULL; + const char *holdin = input; + int readpos = 0; + int writepos = 0; + char block[4]; + + //int outlen = -1; + //int inlen = (int)strlen(input); + + // 10-31-2004 : Added by Saad Nader + // now supports URL safe encoding + //////////////////////////////////////////////// + switch(encodingType) + { + case 1: + encoding = alternateEncoding; + break; + case 2: + encoding = urlSafeEncodeing; + break; + default: encoding = defaultEncoding; + } + + GS_ASSERT(inlen >= 0); + if (inlen <= 0) + { + if (outlen) + *outlen = 0; + output[0] = '\0'; + return; + } + + // Break at end of string or padding character + while (readpos < inlen && input[readpos] != encoding[2]) + { + // 'A'-'Z' maps to 0-25 + // 'a'-'z' maps to 26-51 + // '0'-'9' maps to 52-61 + // 62 maps to encoding[0] + // 63 maps to encoding[1] + if (input[readpos] >= '0' && input[readpos] <= '9') + block[readpos%4] = (char)(input[readpos] - 48 + 52); + else if (input[readpos] >= 'a' && input[readpos] <= 'z') + block[readpos%4] = (char)(input[readpos] - 71); + else if (input[readpos] >= 'A' && input[readpos] <= 'Z') + block[readpos%4] = (char)(input[readpos] - 65); + else if (input[readpos] == encoding[0]) + block[readpos%4] = 62; + else if (input[readpos] == encoding[1]) + block[readpos%4] = 63; + + // padding or '\0' characters also mark end of input + else if (input[readpos] == encoding[2]) + break; + else if (input[readpos] == '\0') + break; + else + { + // (assert(0)); //bad input data + if (outlen) + *outlen = 0; + output[0] = '\0'; + return; //invaid data + } + + // every 4 bytes, convert QuartToTrip into destination + if (readpos%4==3) // zero based, so (3%4) means four bytes, 0-1-2-3 + { + QuartToTrip(block, &output[writepos], 4); + writepos += 3; + } + readpos++; + } + + // Convert any leftover characters in block + if ((readpos != 0) && (readpos%4 != 0)) + { + // fill block with pad (required for QuartToTrip) + memset(&block[readpos%4], encoding[2], (unsigned int)4-(readpos%4)); + QuartToTrip(block, &output[writepos], readpos%4); + + // output bytes depend on the number of non-pad input bytes + if (readpos%4 == 3) + writepos += 2; + else + writepos += 1; + } + + if (outlen) + *outlen = writepos; + + GSI_UNUSED(holdin); +} + + + +void B64Encode(const char *input, char *output, int inlen, int encodingType) +{ + const char *encoding; + char *holdout = output; + char *lastchar; + int todo = inlen; + + // 10-31-2004 : Added by Saad Nader + // now supports URL safe encoding + //////////////////////////////////////////////// + switch(encodingType) + { + case 1: + encoding = alternateEncoding; + break; + case 2: + encoding = urlSafeEncodeing; + break; + default: encoding = defaultEncoding; + } + +//assume interval of 3 + while (todo > 0) + { + TripToQuart(input, output, min(todo, 3)); + output += 4; + input += 3; + todo -= 3; + } + lastchar = output; + if (inlen % 3 == 1) + lastchar -= 2; + else if (inlen % 3 == 2) + lastchar -= 1; + *output = 0; //null terminate! + while (output > holdout) + { + output--; + if (output >= lastchar) //pad the end + *output = encoding[2]; + else if (*output <= 25) + *output = (char)(*output + 65); + else if (*output <= 51) + *output = (char)(*output + 71); + else if (*output <= 61) + *output = (char)(*output + 48 - 52); + else if (*output == 62) + *output = encoding[0]; + else if (*output == 63) + *output = encoding[1]; + } +} + +int B64DecodeLen(const char *input, int encodingType) +{ + const char *encoding; + const char *holdin = input; + + switch(encodingType) + { + case 1: + encoding = alternateEncoding; + break; + case 2: + encoding = urlSafeEncodeing; + break; + default: encoding = defaultEncoding; + } + + while (*input) + { + if (*input == encoding[2]) + return (input - holdin) / 4 * 3 + (input - holdin - 1) % 4; + input++; + } + + return (input - holdin) / 4 * 3; +} + +void B64InitEncodeStream(B64StreamData *data, const char *input, int len, int encodingType) +{ + data->input = input; + data->len = len; + data->encodingType = encodingType; +} + +gsi_bool B64EncodeStream(B64StreamData *data, char output[4]) +{ + const char *encoding; + char *c; + int i; + + if(data->len <= 0) + return gsi_false; + + // 10-31-2004 : Added by Saad Nader + // now supports URL safe encoding + //////////////////////////////////////////////// + switch(data->encodingType) + { + case 1: + encoding = alternateEncoding; + break; + case 2: + encoding = urlSafeEncodeing; + break; + default: encoding = defaultEncoding; + } + + TripToQuart(data->input, output, min(data->len, 3)); + data->input += 3; + data->len -= 3; + + for(i = 0 ; i < 4 ; i++) + { + c = &output[i]; + if (*c <= 25) + *c = (char)(*c + 65); + else if (*c <= 51) + *c = (char)(*c + 71); + else if (*c <= 61) + *c = (char)(*c + 48 - 52); + else if (*c == 62) + *c = encoding[0]; + else if (*c == 63) + *c = encoding[1]; + } + + if(data->len < 0) + { + output[3] = encoding[2]; + if(data->len == -2) + output[2] = encoding[2]; + } + + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiPadRight(char *cArray, char padChar, int cLength); +char * gsiXxteaAlg(const char *sIn, int nIn, char key[XXTEA_KEY_SIZE], int bEnc, int *nOut); + +void gsiPadRight(char *cArray, char padChar, int cLength) +{ + int diff; + int length = (int)strlen(cArray); + + diff = cLength - length; + memset(&cArray[length], padChar, (size_t)diff); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// The heart of the XXTEA encryption/decryption algorithm. +// +// sIn: Input stream. +// nIn: Input length (bytes). +// key: Key (only first 128 bits are significant). +// bEnc: Encrypt (else decrypt)? +char * gsiXxteaAlg(const char *sIn, int nIn, char key[XXTEA_KEY_SIZE], int bEnc, int *nOut) +{ + int i, p, n1; + unsigned int *k, *v, z, y; + char *oStr = NULL, *pStr = NULL; + char *sIn2 = NULL; + ///////////////////////////////// + // ERROR CHECK! + if (!sIn || !key[0] || nIn == 0) + return NULL; + + // Convert stream length to a round number of 32-bit words + // Convert byte count to 32-bit word count + if (nIn % 4 == 0) // Fix for null terminated strings divisible by 4 + nIn = (nIn/4)+1; + else + nIn = (nIn + 3)/4; + + if ( nIn <= 1 ) // XXTEA requires at least 64 bits + nIn = 2; + + // Load and zero-pad first 16 characters (128 bits) of key + gsiPadRight( key , '\0', XXTEA_KEY_SIZE); + k = (unsigned int *)key; + + // Load and zero-pad entire input stream as 32-bit words + sIn2 = (char *)gsimalloc((size_t)(4 * nIn)); + strcpy(sIn2, sIn); + gsiPadRight( sIn2, '\0', 4*nIn); + v = (unsigned int *)sIn2; + + // Prepare to encrypt or decrypt + n1 = nIn - 1; + z = v[ n1 ]; + y = v[ 0 ]; + i = ( int )( 6 + 52/nIn ); + + if (bEnc == 1) // Encrypt + { + unsigned int sum = 0; + while ( i-- != 0 ) + { + int e; + sum += 0x9E3779B9; + e = ( int )( sum >> 2 ); + for ( p = -1; ++p < nIn; ) + { + y = v[( p < n1 ) ? p + 1 : 0 ]; + z = ( v[ p ] += + ( (( z >> 5 ) ^ ( y << 2 )) + + (( y >> 3 ) ^ ( z << 4 ))) + ^ ( ( sum ^ y ) + + ( k[( p ^ e ) & 3 ] ^ z ))); + } + } + } + else if (bEnc == 0) // Decrypt + { + unsigned int sum = ( unsigned int ) i * 0x9E3779B9; + while ( sum != 0 ) + { + int e = ( int )( sum >> 2 ); + for ( p = nIn; p-- != 0; ) + { + z = v[( p != 0 ) ? p - 1 : n1 ]; + y = ( v[ p ] -= + ( (( z >> 5 ) ^ ( y << 2 )) + + (( y >> 3 ) ^ ( z << 4 ))) + ^ ( ( sum ^ y ) + + ( k[( p ^ e ) & 3 ] ^ z ))); + } + sum -= 0x9E3779B9; + } + } + else return NULL; + // Convert result from 32-bit words to a byte stream + + + oStr = (char *)gsimalloc((size_t)(4 * nIn + 1)); + pStr = oStr; + *nOut = 4 *nIn; + for ( i = -1; ++i < nIn; ) + { + unsigned int q = v[ i ]; + + *pStr++ = (char)(q & 0xFF); + *pStr++ = (char)(( q >> 8 ) & 0xFF); + *pStr++ = (char)(( q >> 16 ) & 0xFF); + *pStr++ = (char)(( q >> 24 ) & 0xFF); + } + *pStr = '\0'; + gsifree(sIn2); + + return oStr; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// XXTEA Encrpyt +// params +// iStr : the input string to be encrypted +// iLength : the length of the input string +// key : the key used to encrypt +char * gsXxteaEncrypt(const char * iStr, int iLength, char key[XXTEA_KEY_SIZE], int *oLength) +{ + return gsiXxteaAlg( iStr, iLength, key, 1, oLength ); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// XXTEA Decrypt +// params +// iStr : the input string to be decrypted +// iLength : the length of the input string +// key : the key used to decrypt +char * gsXxteaDecrypt(const char * iStr, int iLength, char key[XXTEA_KEY_SIZE], int *oLength) +{ + return gsiXxteaAlg( iStr, iLength, key, 0, oLength); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(_DEBUG) + +void gsiCheckStack(void) +{ +#if defined(_NITRO) +#if 1 + OS_CheckStack(OS_GetCurrentThread()); +#elif 1 + static gsi_bool checkFailed = gsi_false; + if(!checkFailed) + { + OSStackStatus status = OS_GetStackStatus(OS_GetCurrentThread()); + if(status != 0) + { + const char * reason; + if(status == OS_STACK_OVERFLOW) + reason = "OVERFLOW"; + else if(status == OS_STACK_ABOUT_TO_OVERFLOW) + reason = "ABOUT TO OVERFLOW"; + else if(status == OS_STACK_UNDERFLOW) + reason = "UNDERFLOW"; + else + reason = "UNKOWN REASON"; + + OS_TPrintf("STACK CHECK FAILED!: %s\n", reason); + + checkFailed = gsi_true; + } + } +#endif +#endif // nitro +} +#endif // _DEBUG + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef SN_SYSTEMS +int GOAGetLastError(SOCKET s) +{ + int val = 0; + int soval = sizeof(val); + if (0 != getsockopt(s,SOL_SOCKET,SO_ERROR,&val,&soval)) + return 0; // getsockopt failed + else + return val; +} +#endif + +#ifdef _NITRO +static const char * GOAGetUniqueID_Internal(void) +{ + static char keyval[17]; + u8 MAC[MAC_ALEN]; + + // check if we already have the Unique ID + if(keyval[0]) + return keyval; + + // get the MAC + IP_GetMacAddr(NULL, MAC); + + // format it + sprintf(keyval, "%02X%02X%02X%02X%02X%02X0000", + MAC[0] & 0xFF, + MAC[1] & 0xFF, + MAC[2] & 0xFF, + MAC[3] & 0xFF, + MAC[4] & 0xFF, + MAC[5] & 0xFF); + + return keyval; +} +#endif + + +#ifdef _PS2 +#ifdef UNIQUEID + +#if defined(EENET) + +#include +// Removed due to updated sony libraries, Saad Nader +//#include +#include + +static const char * GetMAC(void) +{ + static struct sceEENetEtherAddr linkAddress; + struct sceEENetIfname * interfaces; + struct sceEENetIfname * interface; + int num; + int type; + int len; + int i; + const unsigned char * MAC = NULL; + + // get the local interfaces + sceEENetGetIfnames(NULL, &num); + interfaces = (struct sceEENetIfname *)gsimalloc(num * sizeof(struct sceEENetIfname)); + if(!interfaces) + return NULL; + sceEENetGetIfnames(interfaces, &num); + + // loop through the interfaces + for(i = 0 ; i < num ; i++) + { + // the next interface + interface = &interfaces[i]; + //printf("eenet%d: %s\n", i, interface->ifn_name); + + // get the type + len = sizeof(type); + if(sceEENetGetIfinfo(interface->ifn_name, sceEENET_IFINFO_IFTYPE, &type, &len) != 0) + continue; + //printf("eenet%d type: %d\n", i, type); + + // check for ethernet + if(type != sceEENET_IFTYPE_ETHER) + continue; + //printf("eenet%d: ethernet\n", i); + + // get the address + len = sizeof(linkAddress); + if(sceEENetGetIfinfo(interface->ifn_name, sceEENET_IFINFO_MACADDR, &linkAddress, &len) != 0) + continue; + MAC = linkAddress.ether_addr_octet; + //printf("eenet%d: MAC: %02X-%02X-%02X-%02X-%02X-%02X\n", i, MAC[0], MAC[1], MAC[2], MAC[3], MAC[4], MAC[5]); + + break; + } + + // free the interfaces + gsifree(interfaces); + + return MAC; +} + +#elif defined(SN_SYSTEMS) + + static const char * GetMAC(void) + { + static char MAC[6]; + int len = sizeof(MAC); + int rcode; + + // get the MAC + rcode = sndev_get_status(0, SN_DEV_STAT_MAC, MAC, &len); + if((rcode != 0) || (len != 6)) + return NULL; + + return MAC; + } + +#elif defined(INSOCK) + + static const char * GetMAC(void) + { + // Get the MAC address using the interface control + static char MAC[16]; + extern sceSifMClientData gGSIInsockClientData; + extern u_int gGSIInsockSocketBuffer[NETBUFSIZE] __attribute__((aligned(64))); + + int result = sceInetInterfaceControl(&gGSIInsockClientData, &gGSIInsockSocketBuffer, + 1, sceInetCC_GetHWaddr, MAC, sizeof(MAC)); + if (result == sceINETE_OK) + return MAC; + + // error + return NULL; + } + +#endif + +static const char * GOAGetUniqueID_Internal(void) +{ + static char keyval[17]; + const char * MAC; + + // check if we already have the Unique ID + if(keyval[0]) + return keyval; + + // get the MAC + MAC = GetMAC(); + if(!MAC) + { + // error getting the MAC + static char errorMAC[6] = { 1, 2, 3, 4, 5, 6 }; + MAC = errorMAC; + } + + // format it + sprintf(keyval, "%02X%02X%02X%02X%02X%02X0000", + MAC[0] & 0xFF, + MAC[1] & 0xFF, + MAC[2] & 0xFF, + MAC[3] & 0xFF, + MAC[4] & 0xFF, + MAC[5] & 0xFF); + + return keyval; +} + +#endif // UNIQUEID +#endif // _PS2 + + +#if ((defined(_WIN32) && !defined(_XBOX)) || defined(_UNIX)) + +static void GenerateID(char *keyval) +{ + int i; + const char crypttab[63] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; +#ifdef _WIN32 + LARGE_INTEGER l1; + UINT seed; + if (QueryPerformanceCounter(&l1)) + seed = (l1.LowPart ^ l1.HighPart); + else + seed = 0; + Util_RandSeed(seed ^ GetTickCount() ^ (unsigned long)time(NULL) ^ clock()); +#else + Util_RandSeed(time(NULL) ^ clock()); +#endif + for (i = 0; i < 19; i++) + if (i == 4 || i == 9 || i == 14) + keyval[i] = '-'; + else + keyval[i] = crypttab[Util_RandInt(0, 62)]; + keyval[19] = 0; +} + +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif + +#ifdef _WIN32 +#define REG_KEY "Software\\GameSpy\\GameSpy 3D\\Registration" +#endif + +const char * GOAGetUniqueID_Internal(void) +{ + static char keyval[PATH_MAX] = ""; + unsigned int ret; + +#ifdef _WIN32 + int docreate; + HKEY thekey; + DWORD thetype = REG_SZ; + DWORD len = MAX_PATH; + DWORD disp; + + if (RegOpenKeyExA(HKEY_CURRENT_USER, REG_KEY, 0, KEY_ALL_ACCESS, &thekey) != ERROR_SUCCESS) + docreate = 1; + else + docreate = 0; + ret = RegQueryValueExA(thekey, (LPCSTR)"Crypt", 0, &thetype, (LPBYTE)keyval, &len); +#else + FILE *f; + f = fopen("id.bin","r"); + if (!f) + ret = 0; + else + { + ret = fread(keyval,1,19,f); + keyval[ret] = 0; + fclose(f); + } +#endif + + if (ret != 0 || strlen(keyval) != 19)//need to generate a new key + { + GenerateID(keyval); +#ifdef _WIN32 + if (docreate) + { + ret = RegCreateKeyExA(HKEY_CURRENT_USER, REG_KEY, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &thekey, &disp); + } + RegSetValueExA(thekey, (LPCSTR)"Crypt", 0, REG_SZ, (const LPBYTE)keyval, strlen(keyval)+1); +#else + f = fopen("id.bin","w"); + if (f) + { + fwrite(keyval,1,19,f); + fclose(f); + } else + keyval[0] = 0; //don't generate one each time!! +#endif + } + +#ifdef _WIN32 + RegCloseKey(thekey); +#endif + + // Strip out the -'s. + ///////////////////// + memmove(keyval + 4, keyval + 5, 4); + memmove(keyval + 8, keyval + 10, 4); + memmove(keyval + 12, keyval + 15, 4); + keyval[16] = '\0'; + + return keyval; +} + +#endif + +#ifdef _PSP +// Included here so that the implementation can appear in gsPlatformPSP.c +const char * GOAGetUniqueID_Internal(void); +#endif + + +#if (!defined(_PS2) && !defined(_PS3) && !defined(_XBOX) && !defined(_PSP)) || defined(UNIQUEID) +GetUniqueIDFunction GOAGetUniqueID = GOAGetUniqueID_Internal; +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} +#endif diff --git a/code/gamespy/common/gsPlatformUtil.h b/code/gamespy/common/gsPlatformUtil.h new file mode 100644 index 00000000..1e561734 --- /dev/null +++ b/code/gamespy/common/gsPlatformUtil.h @@ -0,0 +1,159 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __GSUTILITY_H__ +#define __GSUTILITY_H__ + + +#include "gsPlatform.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Async DNS lookup + +// async way to resolve a hostname to an IP +typedef struct GSIResolveHostnameInfo * GSIResolveHostnameHandle; +#define GSI_STILL_RESOLVING_HOSTNAME 0 +#define GSI_ERROR_RESOLVING_HOSTNAME 0xFFFFFFFF + +// start resolving a hostname +// returns 0 on success, -1 on error +int gsiStartResolvingHostname(const char * hostname, GSIResolveHostnameHandle * handle); +// cancel a resolve in progress +void gsiCancelResolvingHostname(GSIResolveHostnameHandle handle); +// returns GSI_STILL_RESOLVING if still resolving the hostname +// returns GSI_ERROR_RESOLVING if it was unable to resolve the hostname +// on success, returns the IP of the host in network byte order +unsigned int gsiGetResolvedIP(GSIResolveHostnameHandle handle); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Get rid of compiler warnings when parameters are never used +// (Mainly used in sample apps and callback for platform switches) +#if (defined(__MWERKS__) && !defined(_NITRO)) || defined(WIN32) + #define GSI_UNUSED(x) x +#elif defined(_PS2) || defined(_NITRO) || defined(_PS3) || defined(_MACOSX) + #define GSI_UNUSED(x) {void* y=&x;y=NULL;} +#elif defined(_PSP) +#define GSI_UNUSED(x) (void)x; + +#else + #define GSI_UNUSED(x) +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Cross platform random number generator +void Util_RandSeed(unsigned long seed); // to seed it +int Util_RandInt(int low, int high); // retrieve a random int + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Base 64 encoding (printable characters) +void B64Encode(const char *input, char *output, int inlen, int encodingType); +void B64Decode(const char *input, char *output, int inlen, int * outlen, int encodingType); + +// returns the length of the binary data represented by the base64 input string +int B64DecodeLen(const char *input, int encodingType); + +typedef struct +{ + const char *input; + int len; + int encodingType; +} B64StreamData; + +void B64InitEncodeStream(B64StreamData *data, const char *input, int len, int encodingType); + +// returns gsi_false if the stream has ended +gsi_bool B64EncodeStream(B64StreamData *data, char output[4]); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define XXTEA_KEY_SIZE 17 +gsi_i8 * gsXxteaEncrypt(const gsi_i8 * iStr, gsi_i32 iLength, gsi_i8 key[XXTEA_KEY_SIZE], gsi_i32 *oLength); +gsi_i8 * gsXxteaDecrypt(const gsi_i8 * iStr, gsi_i32 iLength, gsi_i8 key[XXTEA_KEY_SIZE], gsi_i32 *oLength); + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#if defined(_DEBUG) + void gsiCheckStack(void); +#else + #define gsiCheckStack() +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// time functions + +gsi_time current_time(); // milliseconds +gsi_time current_time_hires(); // microseconds +void msleep(gsi_time msec); // milliseconds + +// GSI equivalent of common C-lib time functions +struct tm * gsiSecondsToDate(const time_t *timp); //gmtime +time_t gsiDateToSeconds(struct tm *tb); //mktime +char * gsiSecondsToString(const time_t *timp); //ctime + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Misc utilities + + +#if defined(_NITRO) + time_t time(time_t *timer); + + #define gmtime(t) gsiSecondsToDate(t) + #define ctime(t) gsiSecondsToString(t) + #define mktime(t) gsiDateToSeconds(t) +#elif defined(_REVOLUTION) + time_t gsiTimeInSec(time_t *timer); + struct tm *gsiGetGmTime(time_t *theTime); + char *gsiCTime(time_t *theTime); + #define time(t) gsiTimeInSec(t) + #define gmtime(t) gsiGetGmTime(t) + #define ctime(t) gsiCTime(t) +#else + #include +#endif + + + #ifndef SOMAXCONN + #define SOMAXCONN 5 +#endif + +typedef const char * (* GetUniqueIDFunction)(); + +extern GetUniqueIDFunction GOAGetUniqueID; + +// Prototypes so the compiler won't warn +#ifdef _PS2 +extern int wprintf(const wchar_t*,...); +#endif + + +// 64-bit Integer reads and writes +gsi_i64 gsiStringToInt64(const char *theNumberStr); +void gsiInt64ToString(char theNumberStr[33], gsi_i64 theNumber); +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} +#endif + +#endif //__GSUTILITY_H__ + diff --git a/code/gamespy/common/gsRC4.c b/code/gamespy/common/gsRC4.c new file mode 100644 index 00000000..3d8dbb18 --- /dev/null +++ b/code/gamespy/common/gsRC4.c @@ -0,0 +1,58 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsCommon.h" +#include "gsRC4.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void swap_byte (unsigned char *a, unsigned char *b) +{ + unsigned char swapByte; + + swapByte = *a; + *a = *b; + *b = swapByte; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void RC4Init(RC4Context *context, const unsigned char *key, int len) +{ + int i=0; + unsigned char stateIndex = 0; + unsigned char keyIndex = 0; + + // must supply a key + assert(key != NULL && len != 0); + if (key == NULL || len == 0) + return; + + context->x = 0; + context->y = 0; + + for (i=0; i<256; i++) + context->state[i] = (unsigned char)i; + + for (i=0; i<256; i++) + { + stateIndex = (unsigned char)(stateIndex + context->state[i] + key[keyIndex]); + swap_byte(&context->state[i], &context->state[stateIndex]); + keyIndex = (unsigned char)((keyIndex+1)%len); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void RC4Encrypt(RC4Context *context, const unsigned char *src, unsigned char *dest, int len) +{ + int i = 0; + for (i=0; ix = (unsigned char)(context->x + 1); // ok to wrap around from overflow + context->y = (unsigned char)(context->y + context->state[context->x]); // ditto + swap_byte(&context->state[context->x], &context->state[context->y]); + dest[i] = (unsigned char)(src[i] ^ context->state[(unsigned char)(context->state[context->x]+context->state[context->y])]); + } +} diff --git a/code/gamespy/common/gsRC4.h b/code/gamespy/common/gsRC4.h new file mode 100644 index 00000000..e5c6bc53 --- /dev/null +++ b/code/gamespy/common/gsRC4.h @@ -0,0 +1,34 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __GSRC4_H__ +#define __GSRC4_H__ + + +#include "gsCommon.h" + +#if defined(__cplusplus) +extern "C" +{ +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct RC4Context +{ + unsigned char x; + unsigned char y; + unsigned char state[256]; +} RC4Context; + +void RC4Init(RC4Context *context, const unsigned char *key, int len); +void RC4Encrypt(RC4Context *context, const unsigned char *src, unsigned char *dest, int len); + +// Note: RC4Encrypt with src==dest is OK + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} +#endif +#endif // __GSRC4_H__ diff --git a/code/gamespy/common/gsSHA1.c b/code/gamespy/common/gsSHA1.c new file mode 100644 index 00000000..c4e61061 --- /dev/null +++ b/code/gamespy/common/gsSHA1.c @@ -0,0 +1,390 @@ +/* + * sha1.c + * + * Description: + * This file implements the Secure Hashing Algorithm 1 as + * defined in FIPS PUB 180-1 published April 17, 1995. + * + * The SHA-1, produces a 160-bit message digest for a given + * data stream. It should take about 2**n steps to find a + * message with the same digest as a given message and + * 2**(n/2) to find any two messages with the same digest, + * when n is the digest size in bits. Therefore, this + * algorithm can serve as a means of providing a + * "fingerprint" for a message. + * + * Portability Issues: + * SHA-1 is defined in terms of 32-bit "words". This code + * uses (included via "sha1.h" to define 32 and 8 + * bit unsigned integer types. If your C compiler does not + * support 32 bit unsigned integers, this code is not + * appropriate. + * + * Caveats: + * SHA-1 is designed to work with messages less than 2^64 bits + * long. Although SHA-1 allows a message digest to be generated + * for messages of any number of bits less than 2^64, this + * implementation only works with messages with a length that is + * a multiple of the size of an 8-bit character. + * + */ + +//#include "sha1.h" +#include "gsSHA1.h" + +/* + * Define the SHA1 circular left shift macro + */ +#define SHA1CircularShift(bits,word) \ + (((word) << (bits)) | ((word) >> (32-(bits)))) + +/* Local Function Prototyptes */ +void SHA1PadMessage(SHA1Context *); +void SHA1ProcessMessageBlock(SHA1Context *); + +/* + * SHA1Reset + * + * Description: + * This function will initialize the SHA1Context in preparation + * for computing a new SHA1 message digest. + * + * Parameters: + * context: [in/out] + * The context to reset. + * + * Returns: + * sha Error Code. + * + */ +int SHA1Reset(SHA1Context *context) +{ + if (!context) + { + return shaNull; + } + + context->Length_Low = 0; + context->Length_High = 0; + context->Message_Block_Index = 0; + + context->Intermediate_Hash[0] = 0x67452301; + context->Intermediate_Hash[1] = 0xEFCDAB89; + context->Intermediate_Hash[2] = 0x98BADCFE; + context->Intermediate_Hash[3] = 0x10325476; + context->Intermediate_Hash[4] = 0xC3D2E1F0; + + context->Computed = 0; + context->Corrupted = 0; + + return shaSuccess; +} + +/* + * SHA1Result + * + * Description: + * This function will return the 160-bit message digest into the + * Message_Digest array provided by the caller. + * NOTE: The first octet of hash is stored in the 0th element, + * the last octet of hash in the 19th element. + * + * Parameters: + * context: [in/out] + * The context to use to calculate the SHA-1 hash. + * Message_Digest: [out] + * Where the digest is returned. + * + * Returns: + * sha Error Code. + * + */ +int SHA1Result( SHA1Context *context, + uint8_t Message_Digest[SHA1HashSize]) +{ + int i; + + if (!context || !Message_Digest) + { + return shaNull; + } + + if (context->Corrupted) + { + return context->Corrupted; + } + + if (!context->Computed) + { + SHA1PadMessage(context); + for(i=0; i<64; ++i) + { + /* message may be sensitive, clear it out */ + context->Message_Block[i] = 0; + } + context->Length_Low = 0; /* and clear length */ + context->Length_High = 0; + context->Computed = 1; + + } + + for(i = 0; i < SHA1HashSize; ++i) + { + Message_Digest[i] = (uint8_t)(context->Intermediate_Hash[i>>2] + >> 8 * ( 3 - ( i & 0x03 ) )); + } + + return shaSuccess; +} + +/* + * SHA1Input + * + * Description: + * This function accepts an array of octets as the next portion + * of the message. + * + * Parameters: + * context: [in/out] + * The SHA context to update + * message_array: [in] + * An array of characters representing the next portion of + * the message. + * length: [in] + * The length of the message in message_array + * + * Returns: + * sha Error Code. + * + */ +int SHA1Input( SHA1Context *context, + const uint8_t *message_array, + unsigned length) +{ + if (!length) + { + return shaSuccess; + } + + if (!context || !message_array) + { + return shaNull; + } + + if (context->Computed) + { + context->Corrupted = shaStateError; + + return shaStateError; + } + + if (context->Corrupted) + { + return context->Corrupted; + } + while(length-- && !context->Corrupted) + { + context->Message_Block[context->Message_Block_Index++] = + (uint8_t)(*message_array & 0xFF); + + context->Length_Low += 8; + if (context->Length_Low == 0) + { + context->Length_High++; + if (context->Length_High == 0) + { + /* Message is too long */ + context->Corrupted = 1; + } + } + + if (context->Message_Block_Index == 64) + { + SHA1ProcessMessageBlock(context); + } + + message_array++; + } + + return shaSuccess; +} + +/* + * SHA1ProcessMessageBlock + * + * Description: + * This function will process the next 512 bits of the message + * stored in the Message_Block array. + * + * Parameters: + * None. + * + * Returns: + * Nothing. + * + * Comments: + + * Many of the variable names in this code, especially the + * single character names, were used because those were the + * names used in the publication. + * + * + */ +void SHA1ProcessMessageBlock(SHA1Context *context) +{ + const uint32_t K[] = { /* Constants defined in SHA-1 */ + 0x5A827999, + 0x6ED9EBA1, + 0x8F1BBCDC, + 0xCA62C1D6 + }; + int t; /* Loop counter */ + uint32_t temp; /* Temporary word value */ + uint32_t W[80]; /* Word sequence */ + uint32_t A, B, C, D, E; /* Word buffers */ + + /* + * Initialize the first 16 words in the array W + */ + for(t = 0; t < 16; t++) + { + W[t] = (uint32_t)(context->Message_Block[t * 4] << 24); + W[t] |= (uint32_t)(context->Message_Block[t * 4 + 1] << 16); + W[t] |= (uint32_t)(context->Message_Block[t * 4 + 2] << 8); + W[t] |= (uint32_t)(context->Message_Block[t * 4 + 3]); + } + + for(t = 16; t < 80; t++) + { + W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); + } + + A = context->Intermediate_Hash[0]; + B = context->Intermediate_Hash[1]; + C = context->Intermediate_Hash[2]; + D = context->Intermediate_Hash[3]; + E = context->Intermediate_Hash[4]; + + for(t = 0; t < 20; t++) + { + temp = SHA1CircularShift(5,A) + + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; + E = D; + D = C; + C = SHA1CircularShift(30,B); + + B = A; + A = temp; + } + + for(t = 20; t < 40; t++) + { + temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 40; t < 60; t++) + { + temp = SHA1CircularShift(5,A) + + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 60; t < 80; t++) + { + temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + context->Intermediate_Hash[0] += A; + context->Intermediate_Hash[1] += B; + context->Intermediate_Hash[2] += C; + context->Intermediate_Hash[3] += D; + context->Intermediate_Hash[4] += E; + + context->Message_Block_Index = 0; +} + +/* + * SHA1PadMessage + * + + * Description: + * According to the standard, the message must be padded to an even + * 512 bits. The first padding bit must be a '1'. The last 64 + * bits represent the length of the original message. All bits in + * between should be 0. This function will pad the message + * according to those rules by filling the Message_Block array + * accordingly. It will also call the ProcessMessageBlock function + * provided appropriately. When it returns, it can be assumed that + * the message digest has been computed. + * + * Parameters: + * context: [in/out] + * The context to pad + * ProcessMessageBlock: [in] + * The appropriate SHA*ProcessMessageBlock function + * Returns: + * Nothing. + * + */ + +void SHA1PadMessage(SHA1Context *context) +{ + /* + * Check to see if the current message block is too small to hold + * the initial padding bits and length. If so, we will pad the + * block, process it, and then continue padding into a second + * block. + */ + if (context->Message_Block_Index > 55) + { + context->Message_Block[context->Message_Block_Index++] = 0x80; + while(context->Message_Block_Index < 64) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + + SHA1ProcessMessageBlock(context); + + while(context->Message_Block_Index < 56) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + } + else + { + context->Message_Block[context->Message_Block_Index++] = 0x80; + while(context->Message_Block_Index < 56) + { + + context->Message_Block[context->Message_Block_Index++] = 0; + } + } + + /* + * Store the message length as the last 8 octets + */ + context->Message_Block[56] = (uint8_t)(context->Length_High >> 24); + context->Message_Block[57] = (uint8_t)(context->Length_High >> 16); + context->Message_Block[58] = (uint8_t)(context->Length_High >> 8); + context->Message_Block[59] = (uint8_t)(context->Length_High); + context->Message_Block[60] = (uint8_t)(context->Length_Low >> 24); + context->Message_Block[61] = (uint8_t)(context->Length_Low >> 16); + context->Message_Block[62] = (uint8_t)(context->Length_Low >> 8); + context->Message_Block[63] = (uint8_t)(context->Length_Low); + + SHA1ProcessMessageBlock(context); +} diff --git a/code/gamespy/common/gsSHA1.h b/code/gamespy/common/gsSHA1.h new file mode 100644 index 00000000..03471bc6 --- /dev/null +++ b/code/gamespy/common/gsSHA1.h @@ -0,0 +1,93 @@ +/* + * sha1.h + * + * Description: + * This is the header file for code which implements the Secure + * Hashing Algorithm 1 as defined in FIPS PUB 180-1 published + * April 17, 1995. + * + * Many of the variable names in this code, especially the + * single character names, were used because those were the names + * used in the publication. + * + * Please read the file sha1.c for more information. + * + */ + +#ifndef _SHA1_H_ +#define _SHA1_H_ + +//#include +#include "gsCommon.h" +/* + * If you do not have the ISO standard stdint.h header file, then you + * must typdef the following: + * name meaning + * uint32_t unsigned 32 bit integer + * uint8_t unsigned 8 bit integer (i.e., unsigned char) + * int_least16_t integer of >= 16 bits + * + */ +#ifndef _PS3 + // these common types are defined in sony libs + typedef gsi_u32 uint32_t; + typedef gsi_u8 uint8_t; +#endif + +typedef gsi_i16 int_least16_t; + +#ifndef _SHA_enum_ +#define _SHA_enum_ +enum +{ + shaSuccess = 0, + shaNull, /* Null pointer parameter */ + shaInputTooLong, /* input data too long */ + shaStateError /* called Input after Result */ +}; +#endif +#define SHA1HashSize 20 + +/* + * This structure will hold context information for the SHA-1 + * hashing operation + */ +typedef struct SHA1Context +{ + uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest */ + + uint32_t Length_Low; /* Message length in bits */ + uint32_t Length_High; /* Message length in bits */ + + /* Index into message block array */ + int_least16_t Message_Block_Index; + uint8_t Message_Block[64]; /* 512-bit message blocks */ + + int Computed; /* Is the digest computed? */ + int Corrupted; /* Is the message digest corrupted? */ +} SHA1Context; + +/* + * Function Prototypes + */ + +#if defined(__cplusplus) +extern "C" +{ +#endif + + +int SHA1Reset( SHA1Context *); +int SHA1Input( SHA1Context *, + const uint8_t *, + unsigned int); +int SHA1Result( SHA1Context *, + uint8_t Message_Digest[SHA1HashSize]); + + +#if defined(__cplusplus) +} +#endif // extern "C" + + +#endif diff --git a/code/gamespy/common/gsSSL.c b/code/gamespy/common/gsSSL.c new file mode 100644 index 00000000..36098b2c --- /dev/null +++ b/code/gamespy/common/gsSSL.c @@ -0,0 +1,37 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsSSL.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Don't define the export cipher suites if you can avoid it, they present a security risk +const struct gsSSLCipherSuiteDesc gsSSLCipherSuites[GS_SSL_NUM_CIPHER_SUITES] = +{ + // Since common version of IIS supports these, + // we are safe to require the best + + // Algorithm ID (fixed const), KeyLen, CipherLen, IV Len + { TLS_RSA_WITH_RC4_128_MD5, 16, 16, 00 }, + //{ TLS_RSA_WITH_3DES_EDE_CBC_SHA, 16, 20, 00 }, + + // Use of single DES is questionable + // { TLS_RSA_WITH_DES_CBC_SHA, 00, 00, 00 }, + + // Support for export ciphers poses a security risk + // A hacker can edit the packet stream to use a weak export cipher, + // then crack the session and modify the message MACs + // { TLS_RSA_EXPORT1024_WITH_RC4_56_SHA, 00, 00, 00 }, + // { TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA, 00, 00, 00 }, + // { TLS_RSA_EXPORT_WITH_RC4_40_MD5, 00, 00, 00 }, + // { TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5, 00, 00, 00 }, + + // Plain diffie-helmann not supported + // { TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, 00, 00, 00 }, + // { TLS_DHE_DSS_WITH_DES_CBC_SHA, 00, 00, 00 }, + // { TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA, 00, 00, 00 } +}; + + +const unsigned char gsSslRsaOid[9] = +{ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 }; diff --git a/code/gamespy/common/gsSSL.h b/code/gamespy/common/gsSSL.h new file mode 100644 index 00000000..8bd2e3a0 --- /dev/null +++ b/code/gamespy/common/gsSSL.h @@ -0,0 +1,186 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __GSSSL_H__ +#define __GSSSL_H__ + +#include "../darray.h" +#include "../md5.h" +#include "gsCrypt.h" +#include "gsSHA1.h" +#include "gsRC4.h" + +#if defined(__cplusplus) +extern "C" +{ +#endif + + // SSL common types and defines. Used by HTTP SSL encryption engine + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// SSL v3.0 +#define GS_SSL_VERSION_MAJOR (0x03) +#define GS_SSL_VERSION_MINOR (0x00) + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + // SSL content types +#define GS_SSL_CONTENT_CHANGECIPHERSPEC (0x14) // 20 +#define GS_SSL_CONTENT_ALERT (0x15) // 21 Not sure if this is the correct value +#define GS_SSL_CONTENT_HANDSHAKE (0x16) // 22 +#define GS_SSL_CONTENT_APPLICATIONDATA (0x17) // 23 + + // SSL handshake message types +//#define GS_SSL_HANDSHAKE_HELLOREQUEST (0) +#define GS_SSL_HANDSHAKE_CLIENTHELLO (1) +#define GS_SSL_HANDSHAKE_SERVERHELLO (2) +#define GS_SSL_HANDSHAKE_CERTIFICATE (11) +//#define GS_SSL_HANDSHAKE_SERVERKEYEXCHANGE (12) +//#define GS_SSL_HANDSHAKE_CERTIFICATEREQUEST (13) +#define GS_SSL_HANDSHAKE_SERVERHELLODONE (14) +//#define GS_SSL_HANDSHAKE_CERTIFICATEVERIFY (15) +#define GS_SSL_HANDSHAKE_CLIENTKEYEXCHANGE (16) +#define GS_SSL_HANDSHAKE_FINISHED (20) + +// the largest payload for a single SSL packet, RFC const +// ----> RFC includes MAC and any padding, actual user data must be less +#define GS_SSL_MAX_CONTENTLENGTH ((0x4000) - (0xFF)) + +#ifndef HAVE_CIPHER_SUITES + /* these are the ones used by IE */ + #define TLS_RSA_WITH_RC4_128_MD5 0x04 + #define TLS_RSA_WITH_RCA_128_SHA 0x05 + #define TLS_RSA_WITH_3DES_EDE_CBC_SHA 0x0a + #define TLS_RSA_WITH_DES_CBC_SHA 0x09 + #define TLS_RSA_EXPORT1024_WITH_RC4_56_SHA 0x64 + #define TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA 0x62 + #define TLS_RSA_EXPORT_WITH_RC4_40_MD5 0x03 + #define TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 0x06 + #define TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA 0x13 + #define TLS_DHE_DSS_WITH_DES_CBC_SHA 0x12 + #define TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA 0x63 +#endif + + // These depend on the SSL cipher suite ranges +#define GS_SSL_MAX_MAC_SECRET_SIZE (20) +#define GS_SSL_MAX_SYMMETRIC_KEY_SIZE (16) +#define GS_SSL_MAX_IV_SIZE (16) +#define GS_SSL_NUM_CIPHER_SUITES (1) // cipher suite list defined in gsSSL.c +#define GS_SSL_MASTERSECRET_LEN (48) +#define GS_SSL_PAD_ONE "666666666666666666666666666666666666666666666666" // 48 bytes +#define GS_SSL_PAD_TWO "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\" // 48 bytes +#define GS_SSL_MD5_PAD_LEN (48) +#define GS_SSL_SHA1_PAD_LEN (40) // use only 40 of the 48 bytes +#define GS_SSL_CLIENT_FINISH_VALUE "CLNT" +#define GS_SSL_SERVER_FINISH_VALUE "SRVR" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// SSL instance/session info +typedef struct gsSSL +{ + int sessionLen; + unsigned char sessionData[255]; // up to 256 bytes + unsigned short cipherSuite; + + //DArray certificateArray; + gsCryptRSAKey serverpub; + unsigned char sendSeqNBO[8]; // incrementing sequence number (for messages sent) + unsigned char receiveSeqNBO[8]; // ditto (for messages received) + + // Key buffers + // Actual data may be smaller than array size + unsigned char clientWriteMACSecret[GS_CRYPT_SHA1_HASHSIZE]; + unsigned char clientReadMACSecret [GS_CRYPT_SHA1_HASHSIZE]; + unsigned char clientWriteKey [GS_SSL_MAX_SYMMETRIC_KEY_SIZE]; + unsigned char clientReadKey [GS_SSL_MAX_SYMMETRIC_KEY_SIZE]; + unsigned char clientWriteIV [GS_SSL_MAX_IV_SIZE]; + unsigned char clientReadIV [GS_SSL_MAX_IV_SIZE]; + + // Actual lengths of the above data blocks + int clientWriteMACLen; + int clientReadMACLen; + int clientWriteKeyLen; + int clientReadKeyLen; + int clientWriteIVLen; + int clientReadIVLen; + + RC4Context sendRC4; // initialized ONCE per key exchange + RC4Context recvRC4; // initialized ONCE per key exchange + + // these are unused once the handshake is complete + // todo: dynamically allocate or remove to free space + MD5_CTX finishHashMD5; + SHA1Context finishHashSHA1; + unsigned char serverRandom[32]; // server random for key generation, sent plain text + unsigned char clientRandom[32]; // client random for key generation, sent plain text + unsigned char premastersecret[GS_SSL_MASTERSECRET_LEN]; // client random for key generation, sent encrypted with serverpub + unsigned char mastersecret[GS_SSL_MASTERSECRET_LEN]; + +} gsSSL; + + +// SSL messages (like the ClientHello) are wrapped in a "record" struct +typedef struct gsSSLRecordHeaderMsg +{ + unsigned char contentType; // = GS_SSL_CONTENT_HANDSHAKE; + unsigned char versionMajor; // = GS_SSL_VERSION_MAJOR; + unsigned char versionMinor; // = GS_SSL_VERSION_MINOR; + unsigned char lengthNBO[2]; // length of msg, limited to 2^14 + + // WARNING: lengthNBO can NOT be an unsigned short + // This would create alignment issues from the previous 3 parameters + +} gsSSLRecordHeaderMsg; + +typedef struct gsSSLClientHelloMsg +{ + gsSSLRecordHeaderMsg header; // include the header for easier packing + unsigned char handshakeType; // 0x01 + unsigned char lengthNBO[3]; // 3 byte length, NBO integer! 61 = 0x00 00 3d + unsigned char versionMajor; // = GS_SSL_VERSION_MAJOR; + unsigned char versionMinor; // = GS_SSL_VERSION_MINOR; + unsigned char time[4]; // 4 byte random (spec says set to current unix-time) + unsigned char random[28]; // 28 byte random, total of 32 random bytes + unsigned char sessionIdLen; // how many of the bytes that follow are session info? (def:0) + + // ALIGNMENT: 44 bytes prior to this, alignment should be OK + unsigned short cipherSuitesLength; // 2* number of cipher suites + unsigned short cipherSuites[GS_SSL_NUM_CIPHER_SUITES]; + unsigned char compressionMethodLen; // no standard methods, set to 1 + unsigned char compressionMethodList; // set to 0 +} gsSSLClientHelloMsg; + +typedef struct gsSSLClientKeyExchangeMsg +{ + gsSSLRecordHeaderMsg header; // included here for easier packing + unsigned char handshakeType; // 0x10 + unsigned char lengthNBO[3]; + // The next lengthNBO bytes are the client contribution to the key +} gsSSLClientKeyExchangeMsg; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Information about each cipher suite +typedef struct gsSSLCipherSuiteDesc +{ + int mSuiteID; + int mKeyLen; + int mMACLen; + int mIVLen; +} gsSSLCipherSuiteDesc; + +extern const gsSSLCipherSuiteDesc gsSSLCipherSuites[GS_SSL_NUM_CIPHER_SUITES]; +extern const unsigned char gsSslRsaOid[9]; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // __GSSSL_H__ diff --git a/code/gamespy/common/gsSoap.c b/code/gamespy/common/gsSoap.c new file mode 100644 index 00000000..d28f97b1 --- /dev/null +++ b/code/gamespy/common/gsSoap.c @@ -0,0 +1,280 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// gSOAP Glue +#include "gsCore.h" +#include "gsSoap.h" +#include "gsPlatformThread.h" +#include "gsXML.h" +#include "../ghttp/ghttpASCII.h" + + +// GAMESPY DEVELOPERS -> Use gsiExecuteSoap + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Soap task delegates +static void gsiSoapTaskExecute(void* theTask); +static void gsiSoapTaskCallback(void* theTask, GSTaskResult theResult); +static void gsiSoapTaskCancel(void* theTask); +static gsi_bool gsiSoapTaskCleanup(void* theTask); +static GSTaskResult gsiSoapTaskThink(void* theTask); + +// Http triggered callbacks (don't take action now, wait for task callbacks) +static GHTTPBool gsiSoapTaskHttpCompletedCallback(GHTTPRequest request, GHTTPResult result, + char * buffer, GHTTPByteCount bufferLen, + void * param); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Execute a soap function (this should be the only call made from other SDKs) +GSSoapTask* gsiExecuteSoap(const char* theURL, const char* theService, + GSXmlStreamWriter theRequestSoap, GSSoapCallbackFunc theCallbackFunc, + void* theUserData) +{ + GSSoapTask* aSoapTask = NULL; + GSTask* aCoreTask = NULL; + + aSoapTask = (GSSoapTask*)gsimalloc(sizeof(GSSoapTask)); + if (aSoapTask == NULL) + return NULL; // out of memory + + aSoapTask->mCallbackFunc = theCallbackFunc; + aSoapTask->mCustomFunc = NULL; + aSoapTask->mURL = theURL; + aSoapTask->mService = theService; + aSoapTask->mRequestSoap = theRequestSoap; + aSoapTask->mPostData = NULL; + aSoapTask->mResponseSoap = NULL; + aSoapTask->mResponseBuffer = NULL; + aSoapTask->mUserData = theUserData; + aSoapTask->mRequestResult= (GHTTPResult)0; + aSoapTask->mCompleted = gsi_false; + + aCoreTask = gsiCoreCreateTask(); + if (aCoreTask == NULL) + { + gsifree(aSoapTask); + return NULL; // out of memory + } + + aCoreTask->mCallbackFunc = gsiSoapTaskCallback; + aCoreTask->mExecuteFunc = gsiSoapTaskExecute; + aCoreTask->mThinkFunc = gsiSoapTaskThink; + aCoreTask->mCleanupFunc = gsiSoapTaskCleanup; + aCoreTask->mCancelFunc = gsiSoapTaskCancel; + aCoreTask->mTaskData = (void*)aSoapTask; + + aSoapTask->mCoreTask = aCoreTask; + + gsiCoreExecuteTask(aCoreTask, 0); + + return aSoapTask; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Execute a soap function with a GSSoapCustomFunc that can access the soap +// structure prior to execution. This allows the client to set DIME +// attachments. (The GSSoapCustomFunc parameter could be added to +// gsiExecuteSoap itself as long as existing client code is updated) +GSSoapTask* gsiExecuteSoapCustom(const char* theURL, const char* theService, + GSXmlStreamWriter theRequestSoap, GSSoapCallbackFunc theCallbackFunc, + GSSoapCustomFunc theCustomFunc, void* theUserData) +{ + GSSoapTask* aSoapTask = NULL; + GSTask* aCoreTask = NULL; + + aSoapTask = (GSSoapTask*)gsimalloc(sizeof(GSSoapTask)); + aSoapTask->mCallbackFunc = theCallbackFunc; + aSoapTask->mCustomFunc = theCustomFunc; + aSoapTask->mURL = theURL; + aSoapTask->mService = theService; + aSoapTask->mRequestSoap = theRequestSoap; + aSoapTask->mPostData = NULL; + aSoapTask->mResponseSoap = NULL; + aSoapTask->mResponseBuffer = NULL; + aSoapTask->mUserData = theUserData; + aSoapTask->mRequestResult= (GHTTPResult)0; + aSoapTask->mCompleted = gsi_false; + + aCoreTask = gsiCoreCreateTask(); + aCoreTask->mCallbackFunc = gsiSoapTaskCallback; + aCoreTask->mExecuteFunc = gsiSoapTaskExecute; + aCoreTask->mThinkFunc = gsiSoapTaskThink; + aCoreTask->mCleanupFunc = gsiSoapTaskCleanup; + aCoreTask->mCancelFunc = gsiSoapTaskCancel; + aCoreTask->mTaskData = (void*)aSoapTask; + + aSoapTask->mCoreTask = aCoreTask; + + gsiCoreExecuteTask(aCoreTask, 0); + + return aSoapTask; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Cancels a soap task. +// - Because of network race conditions, the task may complete before it +// can be cancelled. If this happens, the task callback will be triggered +// with status GHTTPRequestCancelled and the result data will be discarded. +void gsiCancelSoap(GSSoapTask * theTask) +{ + GS_ASSERT(theTask != NULL); + + // Still in progress? cancel it! + if (gsi_is_false(theTask->mCompleted)) + gsiCoreCancelTask(theTask->mCoreTask); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + ////////// HTTP CALLBACKS ////////// + +static GHTTPBool gsiSoapTaskHttpCompletedCallback(GHTTPRequest request, GHTTPResult result, + char * buffer, GHTTPByteCount bufferLen, + void * param) +{ + gsi_bool parseResult = gsi_false; + + GSSoapTask* aSoapTask = (GSSoapTask*)param; + aSoapTask->mRequestResult = result; + aSoapTask->mCompleted = gsi_true; + aSoapTask->mResponseBuffer = buffer; + + if (result == GHTTPSuccess) + { + aSoapTask->mResponseSoap = gsXmlCreateStreamReader(); + if (aSoapTask->mResponseSoap == NULL) + { + // OOM! + aSoapTask->mRequestResult = GHTTPOutOfMemory; + } + else + { + parseResult = gsXmlParseBuffer(aSoapTask->mResponseSoap, buffer, (int)bufferLen); + if (gsi_is_false(parseResult)) + { + // Todo: handle multiple error conditions + aSoapTask->mRequestResult = GHTTPBadResponse; + } + } + } + + GSI_UNUSED(request); + + return GHTTPFalse; // don't let http free the buffer +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + ////////// SOAP EXECUTE TASK ////////// + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Checks to see if the soap task has completed +// - return GSTaskResult_InProgress for "keep checking" +// - return anything else for "finished - trigger callback and delete" +static GSTaskResult gsiSoapTaskThink(void* theTask) +{ + // is the request still processing? + GSSoapTask* aSoapTask = (GSSoapTask*)theTask; + if (gsi_is_true(aSoapTask->mCompleted)) + return GSTaskResult_Finished; + else + { + ghttpRequestThink(aSoapTask->mRequestId); + return GSTaskResult_InProgress; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Spawns the soap thread and begins execution +static void gsiSoapTaskExecute(void* theTask) +{ + GSSoapTask* aSoapTask = (GSSoapTask*)theTask; + //int threadID = 0; + + // make sure we aren't reusing a task without first resetting it + GS_ASSERT(gsi_is_false(aSoapTask->mCompleted)); + + aSoapTask->mPostData = ghttpNewPost(); + if (aSoapTask->mPostData == NULL) + { + // OOM: abort task + aSoapTask->mCompleted = gsi_true; + aSoapTask->mRequestResult = GHTTPOutOfMemory; + return; + } + + ghttpPostSetAutoFree(aSoapTask->mPostData, GHTTPFalse); + ghttpPostAddXml(aSoapTask->mPostData, aSoapTask->mRequestSoap); + + // Allow client to further configure soap object if desired + if (aSoapTask->mCustomFunc != NULL) + (aSoapTask->mCustomFunc)(aSoapTask->mPostData, aSoapTask->mUserData); + + + aSoapTask->mRequestId = ghttpGetExA(aSoapTask->mURL, aSoapTask->mService, + NULL, 0, aSoapTask->mPostData, GHTTPFalse, GHTTPFalse, NULL, + gsiSoapTaskHttpCompletedCallback, (void*)aSoapTask); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Called when the soap task needs to be cancelled +static void gsiSoapTaskCancel(void* theTask) +{ + GSSoapTask * soapTask = (GSSoapTask*)theTask; + if (gsi_is_false(soapTask->mCompleted)) + { + if (soapTask->mRequestId >= 0) + ghttpCancelRequest(soapTask->mRequestId); + soapTask->mRequestResult = GHTTPRequestCancelled; + soapTask->mCompleted = gsi_true; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Called when the soap task completes or is cancelled/timed out +static void gsiSoapTaskCallback(void* theTask, GSTaskResult theResult) +{ + // Call the developer callback + GSSoapTask* aSoapTask = (GSSoapTask*)theTask; + + (aSoapTask->mCallbackFunc)(aSoapTask->mRequestResult, aSoapTask->mRequestSoap, + aSoapTask->mResponseSoap, aSoapTask->mUserData); + + GSI_UNUSED(theResult); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// After the soap call has completed, launch a separate cleanup event (see comments) +static gsi_bool gsiSoapTaskCleanup(void *theTask) +{ + GSSoapTask* aSoapTask = (GSSoapTask*)theTask; + + if (aSoapTask->mResponseSoap != NULL) + gsXmlFreeReader(aSoapTask->mResponseSoap); + if (aSoapTask->mResponseBuffer != NULL) + gsifree(aSoapTask->mResponseBuffer); + if (aSoapTask->mPostData != NULL) + ghttpFreePost(aSoapTask->mPostData); // this also frees the request soap xml + gsifree(aSoapTask); + + return gsi_true; +} diff --git a/code/gamespy/common/gsSoap.h b/code/gamespy/common/gsSoap.h new file mode 100644 index 00000000..b7e9bde4 --- /dev/null +++ b/code/gamespy/common/gsSoap.h @@ -0,0 +1,73 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __SOAP_H__ +#define __SOAP_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsCommon.h" +#include "gsCore.h" + +#include "../ghttp/ghttp.h" + +#if defined(__cplusplus) +extern "C" +{ +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef void(*GSSoapCallbackFunc)(GHTTPResult theHTTPResult, GSXmlStreamWriter theRequest, GSXmlStreamReader theResponse, void *theUserData); +typedef void(*GSSoapCustomFunc)(GHTTPPost theSoap, void* theUserData); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct +{ + GSSoapCallbackFunc mCallbackFunc; + GSSoapCustomFunc mCustomFunc; + const char *mURL; + const char *mService; + + GSXmlStreamWriter mRequestSoap; + GSXmlStreamReader mResponseSoap; + + char * mResponseBuffer; // so we can free it later + GHTTPPost mPostData; // so we can free it later + + void * mUserData; + GSTask * mCoreTask; + + GHTTPRequest mRequestId; + GHTTPResult mRequestResult; + gsi_bool mCompleted; +} GSSoapTask; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Execute a soap call (Uses GameSpy core object) +GSSoapTask* gsiExecuteSoap(const char *theURL, const char *theService, + GSXmlStreamWriter theSoapData, GSSoapCallbackFunc theCallbackFunc, + void *theUserData); + +// Alternate version with GSSoapCustomFunc parameter allows client access +// to soap object to set DIME attachments +GSSoapTask* gsiExecuteSoapCustom(const char* theURL, const char* theService, + GSXmlStreamWriter theSoapData, GSSoapCallbackFunc theCallbackFunc, + GSSoapCustomFunc theCustomFunc, void* theUserData); + + +void gsiCancelSoap(GSSoapTask * theTask); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // __SOAP_H__ diff --git a/code/gamespy/common/gsStringUtil.c b/code/gamespy/common/gsStringUtil.c new file mode 100644 index 00000000..166cb6f1 --- /dev/null +++ b/code/gamespy/common/gsStringUtil.c @@ -0,0 +1,683 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Conversion Utility for ASCII, UTF8 and USC2 (Unicode) character sets +// +// See RFC2279 for reference +// +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsCommon.h" +#include "gsStringUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Reads UCS2 character from UTF8String +// +// [in] theUTF8String : UTF8String, doesn't need to be null terminated +// [out] theUCS2Char : The 2 byte UCS2 equivalent +// [in] theMaxLength : Maximum number of *bytes* to read (not UTF8 characters) +// +// return value : The number of bytes read from theUTF8String +// 0 = error when parsing +// +// Remarks: +// If theUTF8String is invalid, theUnicodeChar will be set to '?' +// Function is designed for convenient parsing of UTF8 data streams +// +// Security Concern: +// Because data is routed through an ASCII stream prior to this function being +// called, embedded NULLs are stripped and hence, this function does not check for them +// For example, the UTF-8 byte :1000 0000, would convert to a UCS2 NULL character +// If this appeared in the middle of a stream, it could cause undesired operation +int _ReadUCS2CharFromUTF8String(const UTF8String theUTF8String, UCS2Char* theUnicodeChar, int theMaxLength) +{ +#ifndef _PS2 + assert(theUnicodeChar != NULL); +#endif + + if (theMaxLength == 0) + { + // assert? + *theUnicodeChar = (UCS2Char)REPLACE_INVALID_CHAR; + return 0; // not enough data + } + + // Check for normal ascii range (includes NULL terminator) + if (UTF8_IS_SINGLE_BYTE(theUTF8String[0])) + { + // ASCII, just copy the value + *theUnicodeChar = (UCS2Char)theUTF8String[0]; + return 1; + } + + // Check for 2 byte UTF8 + else if (UTF8_IS_TWO_BYTE(theUTF8String[0])) + { + if (theMaxLength < 2) + { + *theUnicodeChar = (UCS2Char)REPLACE_INVALID_CHAR; + return 0; // not enough data + } + + // Make sure the second byte is valid + if (UTF8_IS_FOLLOW_BYTE(theUTF8String[1])) + { + // Construct 11 bit unicode character + // 5 value bits from first UTF8Byte (:000ABCDE) + // plus 6 value bits from the second UTF8Byte (:00FGHIJK) + // Store as (:0000 0ABC DEFG HIJK) + *theUnicodeChar = (unsigned short)(((theUTF8String[0] & UTF8_TWO_BYTE_MASK) << 6) + + ((theUTF8String[1] & UTF8_FOLLOW_BYTE_MASK))); + return 2; + } + } + + // Check for 3 byte UTF8 + else if (UTF8_IS_THREE_BYTE(theUTF8String[0])) + { + if (theMaxLength < 3) + { + *theUnicodeChar = (UCS2Char)REPLACE_INVALID_CHAR; + return 0; // not enough data + } + + // Make sure the second and third bytes are valid + if (UTF8_IS_FOLLOW_BYTE(theUTF8String[1]) && + UTF8_IS_FOLLOW_BYTE(theUTF8String[2])) + { + // Construct 16 bit unicode character + // 4 value bits from first UTF8Byte (:0000ABCD) + // plus 6 value bits from the second UTF8Byte (:00EFGHIJ) + // plus 6 value bits from the third UTF8Byte (:00KLMNOP) + // Store as (:ABCD EFGH IJKL MNOP) + *theUnicodeChar = (unsigned short)(((theUTF8String[0] & UTF8_THREE_BYTE_MASK) << 12) + + ((theUTF8String[1] & UTF8_FOLLOW_BYTE_MASK) << 6) + + ((theUTF8String[2] & UTF8_FOLLOW_BYTE_MASK))); + return 3; + } + } + + // Invalid character, replace with '?' and return false + *theUnicodeChar = (UCS2Char)REPLACE_INVALID_CHAR; + + // The second byte on could have been the start of a new valid UTF8 character + // so we can only safely discard one invalid character + return 1; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Converts UCS2 (Unicode) character into UTF8String +// +// [in] theUCS2Char : The 2 byte character to convert +// [out] theUTF8String : The 1-3 byte UTF8 equivalent +// +// return value : The length of theUTF8String in bytes +// +// Remarks: +// theUTF8String may be up to 3 bytes, caller is responsible for allocating memory +// theUTF8String is NOT NULL terminated, +int _UCS2CharToUTF8String(UCS2Char theUCS2Char, UTF8String theUTF8String) +{ +#ifndef _PS2 + assert(theUTF8String != NULL); +#endif + + // Screen out simple ascii (includes NULL terminator) + if (theUCS2Char <= 0x7F) + { + // 0-7 bit unicode, copy stright over + theUTF8String[0] = (char)(UTF8ByteType)theUCS2Char; + return 1; + } + else if (theUCS2Char <= 0x07FF) + { + // 8-11 bits unicode, store as two byte UTF8 + // :00000ABC DEFGHIJK + // :110ABCDE 10FGHIJK + theUTF8String[0] = (char)(UTF8ByteType)(UTF8_TWO_BYTE_TAG | (theUCS2Char >> 6)); // Store the upper 5/11 bits as 0x110xxxxx + theUTF8String[1] = (char)(UTF8ByteType)(UTF8_FOLLOW_BYTE_TAG | (theUCS2Char & UTF8_FOLLOW_BYTE_MASK)); // Store the lower 6 bits as 0x10xxxxxx + return 2; + } + else + { + // 12-16 bits unicode, store as three byte UTF8 + // :ABCDEFGH IJKLMNOP + // :1110ABCD 10EFGHIJ 10KLMNOP + theUTF8String[0] = (char)(UTF8ByteType)(UTF8_THREE_BYTE_TAG | (theUCS2Char >> 12)); // Store the upper 4/16 bits as 0x1110xxxx + theUTF8String[1] = (char)(UTF8ByteType)(UTF8_FOLLOW_BYTE_TAG | ((theUCS2Char >> 6) & UTF8_FOLLOW_BYTE_MASK)); // Store the 5th-10th bits as 0x10xxxxxx + theUTF8String[2] = (char)(UTF8ByteType)(UTF8_FOLLOW_BYTE_TAG | ((theUCS2Char) & UTF8_FOLLOW_BYTE_MASK)); // Store the last 6 bits as 0x10xxxxxx + return 3; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert an ASCII string to UTF8 +// +// Since an ASCII string IS a valid UTF8 string, just copy and return +// +// [in] theAsciiString, NULL terminated c-string +// [out] theUTF8String, NULL terminated UTF8String +// +// returns the length of theUTF8String +int AsciiToUTF8String(const char* theAsciiString, UTF8String theUTF8String) +{ + // Allow for NULL here since SDKs allow for NULL string arrays + if (theAsciiString == NULL) + { + *theUTF8String = 0x00; + return 1; + } + else + { + // Copy the string, keeping track of length + unsigned int aLength = 0; + while (*theAsciiString != '\0') + { + *(theUTF8String++) = *(theAsciiString++); + aLength++; + } + + // Append the null + *theUTF8String = '\0'; + aLength++; + + return (int)aLength; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UTF8String to it's ASCII equivalent +// +// [in] theUTF8String, NULL terminated UTF8String +// [out] theAsciiString, NULL terminated c-string +// +// returns the length of theAsciiString +// +// Remarks: +// Unvalid ASCII characters are replaced with '?' +// Memory allocated for theAsciiString may need to be as large as the UTF8String +// UTF8String will be NULL terminated +int UTF8ToAsciiString(const UTF8String theUTF8String, char* theAsciiString) +{ + // Strip non-ascii characters and replace with REPLACE_INVALID_CHAR + const unsigned char* anInStream = (const unsigned char*)theUTF8String; + unsigned int aNumBytesWritten = 0; + + // Allow for NULL here since SDKs allow for NULL string arrays + if (theUTF8String == NULL) + { + *theAsciiString = 0x00; + return 1; + } + + // Keep extracting characters until we get a '\0' + while (*anInStream != '\0') + { + if (UTF8_IS_SINGLE_BYTE(*anInStream)) + theAsciiString[aNumBytesWritten++] = (char)*anInStream; + else + theAsciiString[aNumBytesWritten++] = REPLACE_INVALID_CHAR; + + // move to next character + anInStream++; + } + + // Append the '\0' + theAsciiString[aNumBytesWritten++] = '\0'; + return (int)aNumBytesWritten; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UCS2 (Unicode) string to it's UTF8 equivalent +// +// [in] theUCS2String, double NULL terminated UTF8String +// [out] theUTF8String, NULL terminated c-string +// +// returns the length of theUTF8String +// +// Remarks: +// Memory allocated for theUTF8String may need to be up to 1.5* the size of theUCS2String +// This means that for each UCS2 character, 3 UTF8 characters may be generated +int UCS2ToUTF8String(const UCS2String theUCS2String, UTF8String theUTF8String) +{ + unsigned int aTotalBytesWritten = 0; + unsigned int aUTF8CharLength = 0; + const UCS2Char* anInStream = theUCS2String; + unsigned char* anOutStream = (unsigned char*)theUTF8String; + + // Allow for NULL here since SDKs allow for NULL string parameters + if (theUCS2String == NULL) + { + *anOutStream = 0x00; + return 1; + } + + // Loop until we reach a NULL terminator + while(*anInStream != 0) + { + aUTF8CharLength = (unsigned int)_UCS2CharToUTF8String(*anInStream, (UTF8String)anOutStream); + + // Move out stream to next character position + anOutStream += aUTF8CharLength; + + // Move to next UCS2 character + anInStream++; + + // Record number of bytes written + aTotalBytesWritten += aUTF8CharLength; + } + + // Copy over the null terminator + *anOutStream = '\0'; + aTotalBytesWritten++; + + return (int)aTotalBytesWritten; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UTF8 string to it's UCS2 (Unicode) equivalent +// +// [in] theUTF8String, NULL terminated UTF8String +// [out] theUCS2String, NULL terminated c-string +// +// returns the length of theUCS2String +// +// Remarks: +// Unvalid UTF8 characters are replaced with '?' +// Memory allocated for theAsciiString may need to be as large as the UTF8String +// UTF8String will be NULL terminated +int UTF8ToUCS2String(const UTF8String theUTF8String, UCS2String theUCS2String) +{ + return UTF8ToUCS2StringLen(theUTF8String, theUCS2String, (gsi_i32)strlen(theUTF8String)); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Calculate the size needed to convert a UTF8String to a UCS2String +// +// [in] theUTF8String, NULL terminated UTF8String +// +// returns the length (in UCS2 characters) of theUCS2String that would be created +// +// Remarks: +// Unvalid UTF8 characters are treated as 1 byte +int _UTF8ToUCS2ConversionLengthOnly(const UTF8String theUTF8String) +{ + int length = 0; + const UTF8String theReadPos = theUTF8String; + + assert(theUTF8String != NULL); + if (theUTF8String == NULL) + return 0; + + while (*theReadPos != '\0') + { + // Check for valid two byte string + if (UTF8_IS_TWO_BYTE(theReadPos[0]) && UTF8_IS_FOLLOW_BYTE(theReadPos[1])) + theReadPos += 2; + + // Check for valid three byte string + else if (UTF8_IS_THREE_BYTE(theReadPos[0]) && + UTF8_IS_FOLLOW_BYTE(theReadPos[1]) && + UTF8_IS_FOLLOW_BYTE(theReadPos[2])) + { + theReadPos += 3; + } + // Anything else means one UTF8 character read from the buffer + else + theReadPos++; + + // Increment the length of the UCS2 string + length++; + } + + // don't count the null as a character, this conforms + // with ANSI strlen functions + return length; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Calculate the size needed to convert a UCS2String to a UTF8String +// +// [in] theUCS2String, NULL terminated UCS2String +// +// returns the length of theUTF8String that would be created +// +// Remarks: +// Unvalid UTF8 characters are treated as 1 byte +int _UCS2ToUTF8ConversionLengthOnly(const UCS2String theUCS2String) +{ + int length = 0; + const UCS2String theReadPos = theUCS2String; + assert(theUCS2String != NULL); + while (*theReadPos != 0x0000) + { + // Values <= 0x7F are single byte ascii + if (*theReadPos <= 0x7F) + length++; + // Values > 0x7F and <= 0x07FF are two bytes in UTF8 + else if (*theReadPos <= 0x07FF) + length += 2; + // Anything else is 3 bytes of UTF8 + else + length += 3; + + // Set read pos to right spot (1 more UCS2 Character = 2 bytes) + theReadPos++; + } + + // don't count the null as a character, this conforms + // with ANSI strlen functions + return length; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UTF8String to a UCS2String, allocate space for the UCS2String +// +// [in] theUTF8String, NULL terminated UTF8String +// +// returns the newly allocated UCS2String +// +// Remarks: +// The callee is responsible for freeing the allocated memory block +UCS2String UTF8ToUCS2StringAlloc(const UTF8String theUTF8String) +{ + // Allow for NULL here since SDKs allow for NULL string parameters + if (theUTF8String == NULL) + return NULL; + else + { + // Find the length of the UCS2 string and allocate a block + int newLength = _UTF8ToUCS2ConversionLengthOnly(theUTF8String); + UCS2String aUCS2String = (UCS2String)gsimalloc(sizeof(UCS2Char)*(newLength + 1)); + + // Do the conversion + UTF8ToUCS2String(theUTF8String, aUCS2String); + + // Return the allocated string + return aUCS2String; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UCS2String to a UTF8String, allocate space for the UTF8String +// +// [in] UCS2String, NULL terminated UCS2String +// +// returns the newly allocated UTF8String +// +// Remarks: +// The callee is responsible for freeing the allocated memory block +UTF8String UCS2ToUTF8StringAlloc(const UCS2String theUCS2String) +{ + // Allow for NULL here since SDKs allow for NULL string parameters + if (theUCS2String == NULL) + return NULL; + else + { + // Find the length of the UCS2 string and allocate a block + int newLength = _UCS2ToUTF8ConversionLengthOnly(theUCS2String); + UTF8String aUTF8String = (UTF8String)gsimalloc(sizeof(char)*(newLength + 1)); + + // Do the conversion + UCS2ToUTF8String(theUCS2String, aUTF8String); + + // Return the allocated string + return aUTF8String; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UTF8StringArray to a UCS2StringArray, allocate space for the UCS2Strings +// +// [in] UTF8StringArray, array of NULL terminated UTF8Strings +// [in] theNumStrings, how many strings are in the array +// +// returns the newly allocated UCS2StringArray +// +// Remarks: +// The callee is responsible for freeing the allocated memory block(s) +UCS2String* UTF8ToUCS2StringArrayAlloc(const UTF8String* theUTF8StringArray, int theNumStrings) +{ + // Allow for NULL here since SDKs allow for NULL string arrays + if(theUTF8StringArray == NULL || theNumStrings == 0) + return NULL; + else + { + UCS2String* aUCS2StringArray = (UCS2String*)gsimalloc(sizeof(UCS2String)*theNumStrings); + int stringNum = 0; + while(stringNum < theNumStrings) + { + aUCS2StringArray[stringNum] = UTF8ToUCS2StringAlloc(theUTF8StringArray[stringNum]); + stringNum++; + } + + return aUCS2StringArray; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UCS2StringArray to a UTF8StringArray, allocate space for the UTF8Strings +// +// [in] UCS2StringArray, array of NULL terminated UCS2Strings +// [in] theNumStrings, how many strings are in the array +// +// returns the newly allocated UTF8StringArray +// +// Remarks: +// The callee is responsible for freeing the allocated memory block +UTF8String* UCS2ToUTF8StringArrayAlloc(const UCS2String* theUCS2StringArray, int theNumStrings) +{ + // Allow for NULL here since SDKs allow for NULL string arrays + if (theUCS2StringArray == NULL || theNumStrings == 0) + return NULL; + else + { + UTF8String* aUTF8StringArray = (UTF8String*)gsimalloc(sizeof(UTF8String)*theNumStrings); + int stringNum = 0; + while(stringNum < theNumStrings) + { + aUTF8StringArray[stringNum] = UCS2ToUTF8StringAlloc(theUCS2StringArray[stringNum]); + stringNum++; + } + + return aUTF8StringArray; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UCS2String to an AsciiString +// +// [in] UCS2StringArray, NULL terminated UCS2String +// [in/out] theAsciiString, ascii representation +// +// returns the length of the Ascii string +// +// Remarks: +// callee is responsible for allocating memory for theAsciiString +// Invalid ASCII characters are truncated +// The ASCII buffer must be at least 1/2 the size of the UCS2String +int UCS2ToAsciiString(const UCS2String theUCS2String, char* theAsciiString) +{ + int length = 0; + const UCS2String aReadPos = theUCS2String; + char* aWritePos = theAsciiString; + + assert(theAsciiString != NULL); + + // Allow for NULL here since SDKs allow for NULL string arrays + if (theUCS2String == NULL) + { + *theAsciiString = '\0'; + return 1; + } + + // Convert each character until a '\0' is reached + while(*aReadPos != '\0') + { + (*aWritePos++) = (char)(0x00FF & (*aReadPos++)); + length++; + } + + // append the NULL + *aWritePos = '\0'; + length++; + + return length; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert an ASCII string to a UCS2String +// +// [in] theAsciiString, NULL terminated ASCII string +// [in/out] theUCS2String, UCS2String to be filled with the converted ASCII +// +// returns the length of the unicode string +// +// Remarks: +// The callee is responsible for allocating memory for theUCS2String +// the size returned should always be 2x the size passed in +int AsciiToUCS2String(const char* theAsciiString, UCS2String theUCS2String) +{ + int length = 0; + const char* aReadPos = theAsciiString; + UCS2String aWritePos = theUCS2String; + + assert(theUCS2String != NULL); + + // Allow for NULL here since SDKs allow for NULL string arrays + if (theAsciiString == NULL) + { + *theUCS2String = 0x0000; + return 1; + } + + // Convert each character until a '\0' is reached + while(*aReadPos != '\0') + { + (*aWritePos++) = (unsigned short)(0x00FF & (*aReadPos++)); // copy and strip extra byte + length++; + } + + // append a NULL terminator to the UCS2String + *aWritePos = '\0'; + length++; + + return length; +} + +/* +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UCS2String to a UTF8String with a maximum length +// +// [in] theUCS2String, NULL terminated UCS2String +// [in/out] theUTF8String, The UTF8 equivilent of theUCS2String +// [in] theMaxLength, maximum number of UTF8 characters to write +// +// returns the length of the UTF8String +// +// Remarks: +// The length of theUTF8String will not exceed theMaxLength supplied. +int UCS2ToUTF8StringLength(const UCS2String theUCS2String, UTF8String theUTF8String, int theMaxLength) +{ + return 0; +} +*/ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UTF8String to a UCS2String with a maximum length +// +// [in] theUTF8String, NULL terminated UTF8String +// [in/out] theUCS2String, The UCS2 equivilent of theUTF8String +// [in] theMaxLength, maximum number of UTF8 characters to write +// +// returns the length of the UCS2String +// +// Remarks: +// The length of theUCS2String will not exceed theMaxLength supplied. +int UTF8ToUCS2StringLen(const UTF8String theUTF8String, UCS2String theUCS2String, int theMaxLength) +{ + int aNumCharsWritten = 0; + int aNumBytesRead = 0; + int aTotalBytesRead = 0; + const unsigned char* anInStream = (const unsigned char*)theUTF8String; + UCS2Char* anOutStream= theUCS2String; + + // Allow for NULL here since SDKs allow for NULL string arrays + if (theUTF8String == NULL) + { + *anOutStream = 0x0000; + return 1; + } + + // Loop until we find the NULL terminator + while (*anInStream != '\0' && theMaxLength > aTotalBytesRead) + { + // Convert one character + aNumBytesRead = _ReadUCS2CharFromUTF8String((UTF8String)anInStream, anOutStream, theMaxLength-aTotalBytesRead); + if (aNumBytesRead == 0) + { + // Error, read past end of buffer + theUCS2String[0] = 0x0000; + return 0; + } + aTotalBytesRead += aNumBytesRead; + + // Move InStream position to new data + anInStream += aNumBytesRead; + + // Keep track of characters written + aNumCharsWritten++; + + // Move OutStream to next write position + anOutStream++; + } + + // NULL terminate the UCS2String + *anOutStream = 0x0000; + aNumCharsWritten++; + + return aNumCharsWritten; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} //extern "C" +#endif + + + diff --git a/code/gamespy/common/gsStringUtil.h b/code/gamespy/common/gsStringUtil.h new file mode 100644 index 00000000..eb74f521 --- /dev/null +++ b/code/gamespy/common/gsStringUtil.h @@ -0,0 +1,83 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __STRINGUTIL_H__ +#define __STRINGUTIL_H__ + + +// String utilities used by the SDKs + +#ifdef __cplusplus +extern "C" { +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef _PS2 +#define ALIGNED __attribute__ ((aligned(16))) +#else +#define ALIGNED +#endif + +#define UCS2Char unsigned short +#define UCS2String unsigned short* // null terminated +#define UTF8ByteType unsigned char // For type casting +#define UTF8String char* // may not be NULL terminated when treated as a single character + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define UTF8_FOLLOW_BYTE_TAG 0x80 //:1000 0000 // Identifies 2nd or 3rd byte of UTF8String +#define UTF8_TWO_BYTE_TAG 0xC0 //:1100 0000 // Identifies start of Two-byte UTF8String +#define UTF8_THREE_BYTE_TAG 0xE0 //:1110 0000 // Identifies start of Three-byte UTF8String +#define UTF8_FOUR_BYTE_TAG 0xF0 //:1111 0000 // Unsupported tag, need USC4 to store this + +#define UTF8_FOLLOW_BYTE_MASK 0x3F //:0011 1111 // The value bits in a follow byte +#define UTF8_TWO_BYTE_MASK 0x1F //:0001 1111 // The value bits in a two byte tag +#define UTF8_THREE_BYTE_MASK 0x0F //:0000 1111 // The value bits in a three byte tag + +#define UTF8_IS_THREE_BYTE(a) (((UTF8ByteType)a & UTF8_FOUR_BYTE_TAG)==UTF8_THREE_BYTE_TAG) +#define UTF8_IS_TWO_BYTE(a) (((UTF8ByteType)a & UTF8_THREE_BYTE_TAG)==UTF8_TWO_BYTE_TAG) +#define UTF8_IS_FOLLOW_BYTE(a) (((UTF8ByteType)a & UTF8_TWO_BYTE_TAG)==UTF8_FOLLOW_BYTE_TAG) +#define UTF8_IS_SINGLE_BYTE(a) ((UTF8ByteType)a <= 0x7F) // 0-127 + +#define REPLACE_INVALID_CHAR '?' // Replace invalid UTF8 chars with this + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Prototypes +// '_' denotes internal use functions +int _ReadUCS2CharFromUTF8String (const UTF8String theUTF8String, UCS2Char* theUnicodeChar, int theMaxLength); +int _UCS2CharToUTF8String (UCS2Char theUCS2Char, UTF8String theUTF8String); +int _UCS2ToUTF8ConversionLengthOnly (const UCS2String theUCS2String); +int _UTF8ToUCS2ConversionLengthOnly (const UTF8String theUTF8String); + +// Convert string types +int AsciiToUTF8String(const char* theAsciiString, UTF8String theUTF8String ); +int UTF8ToAsciiString(const UTF8String theUTF8String, char* theAsciiString); +int UCS2ToUTF8String (const UCS2String theUCS2String, UTF8String theUTF8String ); +int UTF8ToUCS2String (const UTF8String theUTF8String, UCS2String theUCS2String ); +int UCS2ToAsciiString(const UCS2String theUCS2String, char* theAsciiString); +int AsciiToUCS2String(const char* theAsciiString, UCS2String theUCS2String ); + +// Convert with maximum buffer length +// similar to strncpy +//int UCS2ToUTF8StringLength(const UCS2String theUCS2String, UTF8String theUTF8String, int theMaxLength); +int UTF8ToUCS2StringLen(const UTF8String theUTF8String, UCS2String theUCS2String, int theMaxLength); + +// Convert a string, allocate space for the new string +UTF8String UCS2ToUTF8StringAlloc(const UCS2String theUCS2String); +UCS2String UTF8ToUCS2StringAlloc(const UTF8String theUTF8String); + +// Convert an array of strings, allocate space for the new strings +UTF8String* UCS2ToUTF8StringArrayAlloc(const UCS2String* theUCS2StringArray, int theNumStrings); +UCS2String* UTF8ToUCS2StringArrayAlloc(const UTF8String* theUTF8StringArray, int theNumStrings); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __STRINGUTIL_H__ + diff --git a/code/gamespy/common/gsUdpEngine.c b/code/gamespy/common/gsUdpEngine.c new file mode 100644 index 00000000..30c7771b --- /dev/null +++ b/code/gamespy/common/gsUdpEngine.c @@ -0,0 +1,1171 @@ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Communication Engine +#include "gsUdpEngine.h" + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Internal Structures + +// Internal representation of a connection +// Also includes a ip and port for mapping to a connection +typedef struct +{ + unsigned int mAddr; + unsigned short mPort; + GT2Connection mConnection; +} GSUdpRemotePeer; + +// Message handler used filter traffic to a specific SDK or part of application +typedef struct +{ + unsigned char mInitialMsg[GS_UDP_MSG_HEADER_LEN]; + unsigned char mHeader[GS_UDP_MSG_HEADER_LEN]; + DArray mPendingConnections; + gsUdpConnClosedCallback mClosed; + gsUdpConnReceivedDataCallback mReceived; + gsUdpConnConnectedCallback mConnected; + gsUdpConnPingCallback mPingReply; + gsUdpErrorCallback mNetworkError; + void *mUserData; +} GSUdpMsgHandler; + +// The internal representation of UDP Communication Engine +typedef struct +{ + GT2Socket mSocket; + DArray mRemotePeers; + DArray mMsgHandlers; + gsi_bool mInitialized; + // Application callbacks for connection that gets + // un-handled messages + gsUdpConnConnectedCallback mAppConnected; + gsUdpConnClosedCallback mAppClosed; + gsUdpConnPingCallback mAppPingReply; + gsUdpConnReceivedDataCallback mAppRecvData; + gsUdpAppConnectAttemptCallback mAppConnAttempt; + // Error callback ? + gsUdpErrorCallback mAppNetworkError; + // Unknown Message + gsUdpUnknownMsgCallback mAppUnknownMessage; + void *mAppUserData; + + // This was any easier way to keep track of pending connections + // It does not rely on an address since it only keeps track of pending + // connections an app makes. + int mAppPendingConnections; + unsigned int mLocalAddr; + unsigned short mLocalPort; +}GSUdpEngineObject; + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Acts as the function to return the singleton +// This function is not exposed. It will only be +// used internally to do any modifications to the +// GSUdpEngineObject +GSUdpEngineObject * gsUdpEngineGetEngine() +{ + static GSUdpEngineObject aUdpObject; + return &aUdpObject; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Message Handler DArray functions +void gsUdpMsgHandlerFree(void *theMsgHandler) +{ + GSUdpMsgHandler *aHandler= (GSUdpMsgHandler *)theMsgHandler; + ArrayFree(aHandler->mPendingConnections); +} + +// Used to find a message handler based on the initial message. +int gsUdpMsgHandlerCompare(const void *theFirstHandler, const void *theSecondHandler) +{ + GSUdpMsgHandler *msgHandler1 = (GSUdpMsgHandler *)theFirstHandler, + *msgHandler2 = (GSUdpMsgHandler *)theSecondHandler; + int initCmp; + initCmp = memcmp(msgHandler1->mInitialMsg, msgHandler2->mInitialMsg, GS_UDP_MSG_HEADER_LEN); + return initCmp; + +} + +// Used to find a message handler based on the header. +int gsUdpMsgHandlerCompare2(const void *theFirstHandler, const void *theSecondHandler) +{ + GSUdpMsgHandler *msgHandler1 = (GSUdpMsgHandler *)theFirstHandler, + *msgHandler2 = (GSUdpMsgHandler *)theSecondHandler; + int headerCmp; + headerCmp = memcmp(msgHandler1->mHeader, msgHandler2->mHeader, GS_UDP_MSG_HEADER_LEN); + return headerCmp; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Remote Peer DArray compare functions + +// Finds a remote peer based on IP and Port +int gsUdpRemotePeerCompare(const void *theFirstPeer, const void *theSecondPeer) +{ + GSUdpRemotePeer *aPeer1 = (GSUdpRemotePeer *)theFirstPeer, + *aPeer2 = (GSUdpRemotePeer *)theSecondPeer; + if (aPeer1->mAddr != aPeer2->mAddr) + return 1; + if (aPeer1->mPort != aPeer2->mPort) + return 1; + + return 0; +} + +// Finds a remote peer based on a GT2Connection +int gsUdpRemotePeerCompare2(const void *theFirstPeer, const void *theSecondPeer) +{ + GSUdpRemotePeer *aPeer1 = (GSUdpRemotePeer *)theFirstPeer, + *aPeer2 = (GSUdpRemotePeer *)theSecondPeer; + if (aPeer1->mConnection != aPeer2->mConnection) + return 1; + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Lets the Message Handler and App know about network errors +void gsUdpSocketError(GT2Socket theSocket) +{ + int i, len; + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Socket error, passing to app and message handlers\n"); + if (aUdp->mAppNetworkError) + aUdp->mAppNetworkError(GS_UDP_NETWORK_ERROR, aUdp->mAppUserData); + len = ArrayLength(aUdp->mMsgHandlers); + for (i = 0; i < len; i++) + { + GSUdpMsgHandler *aMsgHandler = (GSUdpMsgHandler *)ArrayNth(aUdp->mMsgHandlers, i); + if (aMsgHandler->mNetworkError) + aMsgHandler->mNetworkError(GS_UDP_NETWORK_ERROR, aMsgHandler->mUserData); + } + GSI_UNUSED(theSocket); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Lets the App and Message Handlers know a peer left +void gsUdpClosedRoutingCB(GT2Connection theConnection, GT2CloseReason reason) +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + GSUdpRemotePeer aRemotePeer; + int index, len; + GSUdpCloseReason aReason; + char anAddr[GS_IP_ADDR_AND_PORT]; + + if (reason == GT2CommunicationError || reason == GT2SocketError) + aReason = GS_UDP_CLOSED_BY_COMM_ERROR; + else if (reason == GT2NotEnoughMemory) + aReason = GS_UDP_CLOSED_BY_LOW_MEM; + else + aReason = (GSUdpCloseReason)reason; + + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Connection closed to %s\n", gt2AddressToString(gt2GetRemoteIP(theConnection), + gt2GetRemotePort(theConnection), anAddr)); + len = ArrayLength(aUdp->mMsgHandlers); + for (index = 0; index < len; index++) + { + GSUdpMsgHandler *aHandler = (GSUdpMsgHandler *)ArrayNth(aUdp->mMsgHandlers, index); + if (aHandler->mClosed) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Connection closed: passed to message handler\n"); + aHandler->mClosed(gt2GetRemoteIP(theConnection), gt2GetRemotePort(theConnection), aReason, aHandler->mUserData); + } + } + + if (aUdp->mAppClosed) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Connection closed: passed to app\n"); + aUdp->mAppClosed(gt2GetRemoteIP(theConnection), gt2GetRemotePort(theConnection), aReason, aUdp->mAppUserData); + } + + aRemotePeer.mConnection = theConnection; + index = ArraySearch(aUdp->mRemotePeers, &aRemotePeer, gsUdpRemotePeerCompare2, 0, 0); + if (index != NOT_FOUND) + { + ArrayDeleteAt(aUdp->mRemotePeers, index); + } + GSI_UNUSED(anAddr); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// When a peer has accepted a GT2Connection the UDP layer needs to let +// higher level app or message handler know that it accepted the request to +// to message a peer. +void gsUdpConnectedRoutingCB(GT2Connection theConnection, GT2Result theResult, GT2Byte *theMessage, + int theMessageLen) +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + int aIndex, len; + GSUdpErrorCode aCode; + char anAddr[GS_IP_ADDR_AND_PORT]; + + switch(theResult) + { + case GT2NegotiationError: + aCode = GS_UDP_REMOTE_ERROR; + break; + case GT2Rejected: + aCode = GS_UDP_REJECTED; + break; + case GT2TimedOut: + aCode = GS_UDP_TIMED_OUT; + break; + case GT2Success: + aCode = GS_UDP_NO_ERROR; + break; + default: + aCode = GS_UDP_UNKNOWN_ERROR; + break; + } + if (theResult == GT2Rejected) + { + int aRemotePeerIdx; + GSUdpRemotePeer aRemotePeer; + aRemotePeer.mAddr = gt2GetRemoteIP(theConnection); + aRemotePeer.mPort = gt2GetRemotePort(theConnection); + aRemotePeerIdx = ArraySearch(aUdp->mRemotePeers, &aRemotePeer, gsUdpRemotePeerCompare, 0, 0); + if (aRemotePeerIdx != NOT_FOUND) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Connect rejected by %s\n", gt2AddressToString(gt2GetRemoteIP(theConnection), + gt2GetRemotePort(theConnection), anAddr)); + + ArrayDeleteAt(aUdp->mRemotePeers, aRemotePeerIdx); + } + } + + len = ArrayLength(aUdp->mMsgHandlers); + for (aIndex = 0; aIndex < len; aIndex++) + { + int aRemotePeerIdx; + GSUdpRemotePeer aRemotePeer; + GSUdpMsgHandler *aTempHandler = (GSUdpMsgHandler *)ArrayNth(aUdp->mMsgHandlers, aIndex); + + aRemotePeer.mAddr = gt2GetRemoteIP(theConnection); + aRemotePeer.mPort = gt2GetRemotePort(theConnection); + aRemotePeerIdx = ArraySearch(aTempHandler->mPendingConnections, &aRemotePeer, gsUdpRemotePeerCompare, 0, 0); + if (aRemotePeerIdx != NOT_FOUND) + { + if (aTempHandler->mConnected) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Passing connect result to message handler\n"); + aTempHandler->mConnected(gt2GetRemoteIP(theConnection), gt2GetRemotePort(theConnection), + aCode, theResult == GT2Rejected ? gsi_true : gsi_false, aTempHandler->mUserData); + } + ArrayDeleteAt(aTempHandler->mPendingConnections, aRemotePeerIdx); + return; + } + } + + if (aUdp->mAppPendingConnections > 0) + { + if (aUdp->mAppConnected) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Passing connect result to app\n"); + aUdp->mAppConnected(gt2GetRemoteIP(theConnection),gt2GetRemotePort(theConnection), + aCode, theResult == GT2Rejected ? gsi_true : gsi_false, aUdp->mAppUserData); + } + aUdp->mAppPendingConnections--; + } + GSI_UNUSED(theMessage); + GSI_UNUSED(theMessageLen); + GSI_UNUSED(anAddr); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Pings are passed on to higher level app or message handlers +void gsUdpPingRoutingCB(GT2Connection theConnection, int theLatency) +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + int index, len; + char anAddr[GS_IP_ADDR_AND_PORT]; + + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Received ping from %s\n", gt2AddressToString(gt2GetRemoteIP(theConnection), + gt2GetRemotePort(theConnection), anAddr)); + len = ArrayLength(aUdp->mMsgHandlers); + for (index = 0; index < len; index++) + { + GSUdpMsgHandler *aHandler = (GSUdpMsgHandler *)ArrayNth(aUdp->mMsgHandlers, index); + if (aHandler->mPingReply) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Passed to message handler\n"); + aHandler->mPingReply(gt2GetRemoteIP(theConnection), gt2GetRemotePort(theConnection), (unsigned int)theLatency, aHandler->mUserData); + return; + } + } + + if (aUdp->mAppPingReply) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Passed to app\n"); + aUdp->mAppPingReply(gt2GetRemoteIP(theConnection), gt2GetRemotePort(theConnection), (unsigned int)theLatency, aUdp->mAppUserData); + } + GSI_UNUSED(anAddr); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Any data received prompts the UDP layer to first find a higher level +// message handler to handle the data. If there was no message handler +// found, the data is passed to the higher level app. +void gsUdpReceivedRoutingCB(GT2Connection theConnection, GT2Byte *theMessage, int theMessageLen, + GT2Bool reliable) +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + GSUdpMsgHandler aHandler; + int index; + char anAddr[GS_IP_ADDR_AND_PORT]; + + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Received data from %s\n", gt2AddressToString(gt2GetRemoteIP(theConnection), + gt2GetRemotePort(theConnection), anAddr)); + //If there is a handler, pass it to the handler + //The header should not be stripped off + if (theMessageLen >= GS_UDP_MSG_HEADER_LEN) + { + memcpy(aHandler.mHeader, theMessage, GS_UDP_MSG_HEADER_LEN); + + index = ArraySearch(aUdp->mMsgHandlers, &aHandler, gsUdpMsgHandlerCompare2, 0, 0); + if (index != NOT_FOUND) + { + GSUdpMsgHandler *aHandlerFound = (GSUdpMsgHandler *)ArrayNth(aUdp->mMsgHandlers, index); + if (aHandlerFound->mReceived) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Passed to message handler\n"); + aHandlerFound->mReceived(gt2GetRemoteIP(theConnection), gt2GetRemotePort(theConnection), + theMessage + GS_UDP_MSG_HEADER_LEN, (unsigned int)(theMessageLen - GS_UDP_MSG_HEADER_LEN), reliable, aHandlerFound->mUserData); + return; + } + } + } + + if (aUdp->mAppRecvData) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Passed to app\n"); + aUdp->mAppRecvData(gt2GetRemoteIP(theConnection), gt2GetRemotePort(theConnection), theMessage, (unsigned int)theMessageLen, reliable, + aUdp->mAppUserData); + } + GSI_UNUSED(anAddr); +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Messages that are not recognized are passed only to the higher level app +// since message handlers always request to talk to peers +GT2Bool gsUdpUnrecognizedMsgCB(GT2Socket theSocket, unsigned int theIp, unsigned short thePort, GT2Byte * theMessage, + int theMsgLen) +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + char anAddr[GS_IP_ADDR_AND_PORT]; + if (aUdp->mAppUnknownMessage) + { + gsi_bool aRet; + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Unknown message from %s, passing to app\n", gt2AddressToString(theIp, thePort, anAddr)); + aRet = aUdp->mAppUnknownMessage(theIp, thePort, (unsigned char *)theMessage, (unsigned int)theMsgLen, aUdp->mAppUserData); + return aRet ? GT2True : GT2False; + } + + GSI_UNUSED(theSocket); + GSI_UNUSED(anAddr); + return GT2False; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Requests for communication from a peer is handled by first checking if the +// initial message has a message handler registered for it. Otherwise +// the message is passed onto the app. +void gsUdpConnAttemptCB(GT2Socket socket, GT2Connection connection, unsigned int ip, + unsigned short port, int latency, GT2Byte * message, int len) +{ + // Get the message handler for the connection + int index; + GSUdpMsgHandler aHandler; + GSUdpRemotePeer aRemotePeer; + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + char anAddr[GS_IP_ADDR_AND_PORT]; + + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Connection attempt from %s\n", gt2AddressToString(ip, port, anAddr)); + //If there is a handler, automatically accept a connection if the initial message is + //the same as the handler's registered initial message + if (len >= GS_UDP_MSG_HEADER_LEN) + { + memcpy(aHandler.mInitialMsg, message, GS_UDP_MSG_HEADER_LEN); + + aRemotePeer.mAddr = ip; + aRemotePeer.mPort = port; + aRemotePeer.mConnection = connection; + + ArrayAppend(aUdp->mRemotePeers, &aRemotePeer); + index = ArraySearch(aUdp->mMsgHandlers, &aHandler, gsUdpMsgHandlerCompare, 0, 0); + if (index != NOT_FOUND) + { + GT2ConnectionCallbacks aCallbacks; + + aCallbacks.closed = gsUdpClosedRoutingCB; + aCallbacks.connected = gsUdpConnectedRoutingCB; + aCallbacks.ping = gsUdpPingRoutingCB; + aCallbacks.received = gsUdpReceivedRoutingCB; + + + // Automatically accept connections for Message Handlers + gt2Accept(aRemotePeer.mConnection, &aCallbacks); + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Connection attempt auto-accepted for message handler\n"); + return; + } + } + // all other messages go to the app + if (aUdp->mAppConnAttempt) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Connection attempt from %s, asking app to accept/reject\n", gt2AddressToString(ip, port, anAddr)); + aUdp->mAppConnAttempt(ip, port, latency, (unsigned char *)message, (unsigned int)len, aUdp->mAppUserData); + } + else + { + // Reject any un-handled connections or unknown connections + gt2Reject(connection, NULL, 0); + ArrayRemoveAt(aUdp->mRemotePeers, ArrayLength(aUdp->mRemotePeers) -1); + } + GSI_UNUSED(socket); + GSI_UNUSED(anAddr); +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Public functions + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Used to check if the UDP layer needs to be initialized +gsi_bool gsUdpEngineIsInitialized() +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + return aUdp->mInitialized; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Initializes the UDP layer +// A specific port can be used if needed. +// An app must register their callbacks here. +// An App must also call this before starting SDKs since they might start +// the UDP layer without the app having a chance to register its callbacks. +// Any message handler can get the port the UDP layer is using if it did not +// initialize the UDP Layer. +// Use of the UDP Layer requires it to be initialized as is the case with most +// functions below. +GSUdpErrorCode gsUdpEngineInitialize(unsigned short thePort, int theIncomingBufSize, + int theOutgoingBufSize, gsUdpErrorCallback theAppNetworkError, + gsUdpConnConnectedCallback theAppConnected, + gsUdpConnClosedCallback theAppClosed, + gsUdpConnPingCallback theAppPing, + gsUdpConnReceivedDataCallback theAppReceive, + gsUdpUnknownMsgCallback theAppUnownMsg, + gsUdpAppConnectAttemptCallback theAppConnectAttempt, + void *theAppUserData) +{ + int incomingBufferSize, + outgoingBufferSize; + char anAddr[GS_IP_ADDR_AND_PORT]; + GT2Result aGt2Result; + // Grab the single instance of the UDP Communication Engine + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + + // Setup our gt2 buffer sizes for reliable messages + + incomingBufferSize = theIncomingBufSize != 0 ? theIncomingBufSize : GS_UDP_DEFAULT_IN_BUFFSIZE; + outgoingBufferSize = theOutgoingBufSize != 0 ? theOutgoingBufSize : GS_UDP_DEFAULT_OUT_BUFFSIZE; + + // Setup our internal socket that will be shared among more than one application + aUdp->mAppNetworkError = theAppNetworkError; + aUdp->mAppUnknownMessage = theAppUnownMsg; + aUdp->mAppConnected = theAppConnected; + aUdp->mAppClosed = theAppClosed; + aUdp->mAppPingReply = theAppPing; + aUdp->mAppRecvData = theAppReceive; + aUdp->mAppConnAttempt = theAppConnectAttempt; + + // Any port can be used + gt2AddressToString(0, thePort, anAddr); + aGt2Result = gt2CreateSocket(&aUdp->mSocket, anAddr, outgoingBufferSize, incomingBufferSize, + gsUdpSocketError); + if (aGt2Result != GT2Success) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_WarmError, + "[Udp Engine] error creating gt2 socket, error code: %d\n", aGt2Result); + return GS_UDP_NETWORK_ERROR; + } + // We'll need to keep track of connections with an array of GPconnection to address mapping + aUdp->mRemotePeers = ArrayNew(sizeof(GSUdpRemotePeer), 1, NULL); + if (aUdp->mRemotePeers == NULL) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Memory, GSIDebugLevel_HotError, + "[Udp Engine] No more memory!!!\n"); + return GS_UDP_NO_MEMORY; + } + + aUdp->mMsgHandlers = ArrayNew(sizeof(GSUdpMsgHandler), 1, gsUdpMsgHandlerFree); + if (aUdp->mMsgHandlers == NULL) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Memory, GSIDebugLevel_HotError, + "[Udp Engine] No more memory!!!\n"); + return GS_UDP_NO_MEMORY; + } + + // Used by the manager to receive messages from backend services or other clients + // that may not be using gt2 + gt2SetUnrecognizedMessageCallback(aUdp->mSocket, gsUdpUnrecognizedMsgCB); + + // Automatically start listening for all SDKS and the game if game uses UDP Engine + gt2Listen(aUdp->mSocket, gsUdpConnAttemptCB); + aUdp->mLocalAddr = gt2GetLocalIP(aUdp->mSocket); + aUdp->mLocalPort = gt2GetLocalPort(aUdp->mSocket); + aUdp->mAppPendingConnections = 0; + aUdp->mInitialized = gsi_true; + if (theAppUserData) + { + aUdp->mAppUserData = theAppUserData; + } + else + aUdp->mAppUserData = NULL; + return GS_UDP_NO_ERROR; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// Used obtain the peer's state +// theIp and thePort cannot be 0 (Zero) +GSUdpErrorCode gsUdpEngineGetPeerState(unsigned int theIp, unsigned short thePort, GSUdpPeerState *thePeerState) +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + GSUdpRemotePeer aPeer, *aPeerFound; + int index; + GS_ASSERT(aUdp->mInitialized); + GS_ASSERT(theIp); + GS_ASSERT(thePort); + GS_ASSERT(thePeerState != NULL); + if (!aUdp->mInitialized) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_State, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + *thePeerState = GS_UDP_PEER_CLOSED; + return GS_UDP_NOT_INITIALIZED; + } + + aPeer.mAddr = theIp; + aPeer.mPort = thePort; + index = ArraySearch(aUdp->mRemotePeers, &aPeer, gsUdpRemotePeerCompare, 0, 0); + if (index == NOT_FOUND) + { + *thePeerState = GS_UDP_PEER_CLOSED; + return GS_UDP_NO_ERROR; + } + + aPeerFound = (GSUdpRemotePeer *)ArrayNth(aUdp->mRemotePeers, index); + + *thePeerState = (GSUdpPeerState)gt2GetConnectionState(aPeerFound->mConnection); + return GS_UDP_NO_ERROR; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// theIp and thePort cannot be 0 (Zero) +// Starts a request to open a communication channel with another peer based on +// IP and port. +GSUdpErrorCode gsUdpEngineStartTalkingToPeer(unsigned int theIp, unsigned short thePort, + char theInitMsg[GS_UDP_MSG_HEADER_LEN], int timeOut) +{ + char anAddr[GS_IP_ADDR_AND_PORT]; + GSUdpRemotePeer aRemotePeer; + GSUdpMsgHandler aHandler; + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + GT2ConnectionCallbacks aCallbacks; + int index; + GS_ASSERT(aUdp->mInitialized); + GS_ASSERT(theIp); + GS_ASSERT(thePort); + + if (!aUdp->mInitialized) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return GS_UDP_NETWORK_ERROR; + } + + if (theIp == 0 || thePort == 0) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Invalid parameter(s), check ip, port"); + return GS_UDP_PARAMETER_ERROR; + } + + aRemotePeer.mAddr = theIp; // In Network Byte Order for GT2 + aRemotePeer.mPort = thePort; // In Host Byte Order for GT2 + + index = ArraySearch(aUdp->mRemotePeers, &aRemotePeer, gsUdpRemotePeerCompare, 0, 0); + if (index != NOT_FOUND) + { + GSUdpRemotePeer *aPeerFound = (GSUdpRemotePeer *)ArrayNth(aUdp->mRemotePeers, index); + GT2ConnectionState aState = gt2GetConnectionState(aPeerFound->mConnection); + if (aState == GT2Connected) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine is already talking to remote address\n"); + return GS_UDP_ADDRESS_ALREADY_IN_USE; + } + else if (aState == GT2Connecting) + { + memcpy(aHandler.mInitialMsg, theInitMsg, GS_UDP_MSG_HEADER_LEN); + + index = ArraySearch(aUdp->mMsgHandlers, &aHandler, gsUdpMsgHandlerCompare, 0, 0); + if (index != NOT_FOUND) + { + GSUdpMsgHandler *aHandlerFound = (GSUdpMsgHandler *)ArrayNth(aUdp->mMsgHandlers, index); + ArrayAppend(aHandlerFound->mPendingConnections, aPeerFound); + } + } + } + else + { + gt2AddressToString(theIp, thePort, anAddr); + aCallbacks.closed = gsUdpClosedRoutingCB; + aCallbacks.connected = gsUdpConnectedRoutingCB; + aCallbacks.ping = gsUdpPingRoutingCB; + aCallbacks.received = gsUdpReceivedRoutingCB; + + // start the connect without blocking since we want the engine to be as asynchronous as possible + gt2Connect(aUdp->mSocket, &aRemotePeer.mConnection, anAddr, (unsigned char *)theInitMsg, GS_UDP_MSG_HEADER_LEN, timeOut, &aCallbacks, GT2False); + + ArrayAppend(aUdp->mRemotePeers, &aRemotePeer); + + memcpy(aHandler.mInitialMsg, theInitMsg, GS_UDP_MSG_HEADER_LEN); + + index = ArraySearch(aUdp->mMsgHandlers, &aHandler, gsUdpMsgHandlerCompare, 0, 0); + if (index != NOT_FOUND) + { + GSUdpRemotePeer *aRemotePeerPtr = (GSUdpRemotePeer *)ArrayNth(aUdp->mRemotePeers, ArrayLength(aUdp->mRemotePeers) - 1); + GSUdpMsgHandler *aHandlerFound = (GSUdpMsgHandler *)ArrayNth(aUdp->mMsgHandlers, index); + ArrayAppend(aHandlerFound->mPendingConnections, &aRemotePeerPtr); + } + else + { + aUdp->mAppPendingConnections++; + } + } + return GS_UDP_NO_ERROR; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// theIp and thePort cannot be 0 (Zero) +// Accepts a Peer's request for communication +// Should only be used by App +GSUdpErrorCode gsUdpEngineAcceptPeer(unsigned int theIp, unsigned short thePort) +{ + GSUdpRemotePeer aRemotePeer; + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + int index; + GS_ASSERT(aUdp->mInitialized); + GS_ASSERT(theIp); + GS_ASSERT(thePort); + if (!aUdp->mInitialized) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return GS_UDP_NETWORK_ERROR; + } + + if (theIp == 0 || thePort == 0) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Invalid parameter(s), check ip, port\n"); + return GS_UDP_PARAMETER_ERROR; + } + + aRemotePeer.mAddr = theIp; + aRemotePeer.mPort = thePort; + index = ArraySearch(aUdp->mRemotePeers, &aRemotePeer, gsUdpRemotePeerCompare, 0, 0); + if (index != NOT_FOUND) + { + GT2ConnectionCallbacks aCallbacks; + + GSUdpRemotePeer *aPeerFound = (GSUdpRemotePeer *)ArrayNth(aUdp->mRemotePeers, index); + + aCallbacks.closed = gsUdpClosedRoutingCB; + aCallbacks.connected = gsUdpConnectedRoutingCB; + aCallbacks.ping = gsUdpPingRoutingCB; + aCallbacks.received = gsUdpReceivedRoutingCB; + + gt2Accept(aPeerFound->mConnection, &aCallbacks); + } + return GS_UDP_NO_ERROR; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// theIp and thePort cannot be 0 (Zero) +// Rejects a Peer's request for communication +// Should only be used by App +GSUdpErrorCode gsUdpEngineRejectPeer(unsigned int theIp, unsigned short thePort) +{ + GSUdpRemotePeer aRemotePeer; + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + int index; + GS_ASSERT(aUdp->mInitialized); + GS_ASSERT(theIp); + GS_ASSERT(thePort); + if (!aUdp->mInitialized) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return GS_UDP_NETWORK_ERROR; + } + + if (theIp == 0 || thePort == 0) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Invalid parameter(s), check ip, port\n"); + return GS_UDP_PARAMETER_ERROR; + } + + // Find the connection to reject in our array of peers + aRemotePeer.mAddr = theIp; + aRemotePeer.mPort = thePort; + index = ArraySearch(aUdp->mRemotePeers, &aRemotePeer, gsUdpRemotePeerCompare, 0, 0); + if (index != NOT_FOUND) + { + GSUdpRemotePeer *aPeerFound = (GSUdpRemotePeer *)ArrayNth(aUdp->mRemotePeers, index); + gt2Reject(aPeerFound->mConnection, NULL, 0); + ArrayDeleteAt(aUdp->mRemotePeers, index); + } + return GS_UDP_NO_ERROR; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// Sends a message to a peer using IP and Port +// An empty header constitutes the app sending this message. +// theIp and thePort cannot be 0 (Zero) +// +// WARNING: Messages should not be greater than the outgoing buffer size minus the header +// and the 7 byte header for reliable messages (used for internal gt2 operations). Most +// UDP fragmentation occurs if messages are bigger than 1500 bytes. Also, some routers are +// known to drop those packets that are larger than 1500 bytes. The recommended outgoing +// buffer size is the default (1460). So take that, and subtract 16 for message handler header +// and reliable message header (if sending data reliably). +// freeSpace = 1460 - 16 - 7 +GSUdpErrorCode gsUdpEngineSendMessage(unsigned int theIp, unsigned short thePort, + char theHeader[GS_UDP_MSG_HEADER_LEN], unsigned char *theMsg, + unsigned int theMsgLen, gsi_bool theReliable) +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + int aTotalMessageLen, index; + GSUdpRemotePeer aRemotePeer, *aRemotePeerFound; + GT2Byte *fullMessage; + GT2Result aResult; + GS_ASSERT(aUdp->mInitialized); + GS_ASSERT(theIp); + GS_ASSERT(thePort); + if (!aUdp->mInitialized) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return GS_UDP_NETWORK_ERROR; + } + + // Messages being sent with an empty header are treated as app messages + if (!theHeader[0]) + aTotalMessageLen = (int)theMsgLen; + else + aTotalMessageLen = (int)(GS_UDP_MSG_HEADER_LEN + theMsgLen); + + aRemotePeer.mAddr = theIp; + aRemotePeer.mPort = thePort; + + index = ArraySearch(aUdp->mRemotePeers, &aRemotePeer, gsUdpRemotePeerCompare, 0, 0); + if (index == NOT_FOUND) + { + char anAddr[GS_IP_ADDR_AND_PORT]; + gt2AddressToString(theIp, thePort, anAddr); + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_WarmError, + "[Udp Engine] address not found for sending message\n", anAddr); + return GS_UDP_ADDRESS_ERROR; + } + else + { + aRemotePeerFound = (GSUdpRemotePeer *)ArrayNth(aUdp->mRemotePeers, index); + } + + if (aTotalMessageLen > gt2GetOutgoingBufferSize(aRemotePeerFound->mConnection) && theReliable) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_WarmError, + "[Udp Engine] Message Size too large, dropping message\n"); + return GS_UDP_MSG_TOO_BIG; + } + + fullMessage = (GT2Byte *)gsimalloc((unsigned long)aTotalMessageLen); + memcpy(fullMessage, theHeader, GS_UDP_MSG_HEADER_LEN); + memcpy(fullMessage + GS_UDP_MSG_HEADER_LEN, theMsg, theMsgLen); + // Send the message + // reliable messages will be kept in the outgoing buffers till they are sent + aResult = gt2Send(aRemotePeerFound->mConnection, fullMessage, aTotalMessageLen, theReliable); + gsifree(fullMessage); + + if (aResult != GT2Success) + return GS_UDP_SEND_FAILED; + return GS_UDP_NO_ERROR; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// One the UDP Layer is initialized, this function +// should be called every 10-100 ms +// Message handlers already call this which means +// that the app may not have to call this function +GSUdpErrorCode gsUdpEngineThink() +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + GS_ASSERT(aUdp->mInitialized); + if (!aUdp->mInitialized) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return GS_UDP_NETWORK_ERROR; + } + gt2Think(aUdp->mSocket); + return GS_UDP_NO_ERROR; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// Shutdown should only happen if following is met: +// Apps can call this function after they have shutdown message handlers (SDKs), +// and verified there are no message handlers remaining. Calling the function +// gsUdpEngineNoMoreMsgHandlers will do this. +// Message handlers cannot call this function without checking if there no more +// message handlers remaining and if there is no app (gsUdpEngineNoApp). +GSUdpErrorCode gsUdpEngineShutdown() +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + GS_ASSERT(aUdp->mInitialized); + if (!aUdp->mInitialized) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return GS_UDP_NETWORK_ERROR; + } + gt2CloseSocket(aUdp->mSocket); + ArrayFree(aUdp->mMsgHandlers); + ArrayFree(aUdp->mRemotePeers); + aUdp->mInitialized = gsi_false; + return GS_UDP_NO_ERROR; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// Obtains the lower level socket that can be used to perform other operations +// or pass to other SDKs. +SOCKET gsUdpEngineGetSocket() +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + + GS_ASSERT(aUdp->mInitialized); + if (!aUdp->mInitialized) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_HotError, + "[Udp Engine] Engine not initialized\n"); + return INVALID_SOCKET; + } + + return gt2GetSocketSOCKET(aUdp->mSocket); +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// Gets the local IP the UDP layer is bound to. +unsigned int gsUdpEngineGetLocalAddr() +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + + GS_ASSERT(aUdp->mInitialized); + if (!aUdp->mInitialized) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_HotError, + "[Udp Engine] Engine not initialized\n"); + return (unsigned int)-1; + } + return aUdp->mLocalAddr; + +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// Gets the local port the UDP layer is bound to. +unsigned short gsUdpEngineGetLocalPort() +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + + GS_ASSERT(aUdp->mInitialized); + if (!aUdp->mInitialized) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_HotError, + "[Udp Engine] Engine not initialized\n"); + return 0; + } + return aUdp->mLocalPort; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// Message handlers are added using this function +// The initial message and header of a message handler cannot be empty +// However, they can be the same. +// User data can be useful for keeping track of Message Handler +GSUdpErrorCode gsUdpEngineAddMsgHandler(char theInitMsg[GS_UDP_MSG_HEADER_LEN], char theHeader[GS_UDP_MSG_HEADER_LEN], + gsUdpErrorCallback theMsgHandlerError, + gsUdpConnConnectedCallback theMsgHandlerConnected, + gsUdpConnClosedCallback theMsgHandlerClosed, + gsUdpConnPingCallback theMsgHandlerPing, + gsUdpConnReceivedDataCallback theMsgHandlerRecv, + void *theUserData) +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + GSUdpMsgHandler aMsgHandler; + GS_ASSERT(aUdp->mInitialized); + GS_ASSERT(theInitMsg || theInitMsg[0]); + GS_ASSERT(theHeader || theHeader[0]); + if (!aUdp->mInitialized) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return GS_UDP_NETWORK_ERROR; + } + + // setup a message handler that the UDP engine will use to pass connection attempts to + + //check for valid input + if (!theInitMsg[0]) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Invalid init message\n"); + return GS_UDP_PARAMETER_ERROR; + } + + if (!theHeader[0]) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Invalid header\n"); + return GS_UDP_PARAMETER_ERROR; + } + + // This check is not necessary. Some SDKs may not use all callbacks + /*if (!theMsgHandlerError || !theMsgHandlerConnected || !theMsgHandlerClosed + || !theMsgHandlerPing || !theMsgHandlerRecv) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Invalid callback(s)"); + return GS_UDP_PARAMETER_ERROR; + } + */ + aMsgHandler.mClosed = theMsgHandlerClosed; + aMsgHandler.mConnected = theMsgHandlerConnected; + aMsgHandler.mPingReply = theMsgHandlerPing; + aMsgHandler.mReceived = theMsgHandlerRecv; + + aMsgHandler.mNetworkError = theMsgHandlerError; + + memcpy(aMsgHandler.mInitialMsg, theInitMsg, GS_UDP_MSG_HEADER_LEN); + memcpy(aMsgHandler.mHeader, theHeader, GS_UDP_MSG_HEADER_LEN); + + aMsgHandler.mPendingConnections = ArrayNew(sizeof(GSUdpRemotePeer *), 1, NULL); + if (aMsgHandler.mPendingConnections == NULL) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Memory, GSIDebugLevel_HotError, + "[Udp Engine] No more memory!!!\n"); + return GS_UDP_NO_MEMORY; + } + aMsgHandler.mUserData = theUserData; + ArrayAppend(aUdp->mMsgHandlers, &aMsgHandler); + + return GS_UDP_NO_ERROR; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// When a message handler is done or shutting down, the message handler should remove +// itself from the UDP Layer +// The header cannot be empty +GSUdpErrorCode gsUdpEngineRemoveMsgHandler(char theHeader[GS_UDP_MSG_HEADER_LEN]) +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + GSUdpMsgHandler aHandler; + int index; + + GS_ASSERT(aUdp->mInitialized); + GS_ASSERT(theHeader); + if (!aUdp->mInitialized) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return GS_UDP_NETWORK_ERROR; + } + + if (!theHeader || !theHeader[0]) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] invalid or empty header\n"); + return GS_UDP_PARAMETER_ERROR; + } + + memcpy(aHandler.mHeader, theHeader, GS_UDP_MSG_HEADER_LEN); + + index = ArraySearch(aUdp->mMsgHandlers, &aHandler, gsUdpMsgHandlerCompare2, 0, 0); + if (index != NOT_FOUND) + { + ArrayDeleteAt(aUdp->mMsgHandlers, index); + } + return GS_UDP_NO_ERROR; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// Checks to see if there any remaining message handlers. +// returns gsi_false if there are none. +gsi_bool gsUdpEngineNoMoreMsgHandlers() +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + + GS_ASSERT(aUdp->mInitialized); + if (!aUdp->mInitialized) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return gsi_true; + } + + return ArrayLength(aUdp->mMsgHandlers) == 0 ? gsi_true : gsi_false; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// returns gsi_false if there is no app using the UDP Layer +gsi_bool gsUdpEngineNoApp() +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + + GS_ASSERT(aUdp->mInitialized); + if (!aUdp->mInitialized) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return gsi_true; + } + + if (aUdp->mAppClosed || aUdp->mAppConnAttempt || aUdp->mAppConnected || aUdp->mAppNetworkError + || aUdp->mAppPingReply || aUdp->mAppRecvData || aUdp->mAppUnknownMessage) + { + return gsi_false; + } + + return gsi_true; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// theIp and thePort cannot be 0 (Zero) +// Based on an IP and port, the function will return the amount of free space +// of a peer's buffer. +int gsUdpEngineGetPeerOutBufferFreeSpace(unsigned int theIp, unsigned short thePort) +{ + GSUdpEngineObject *aUdp = gsUdpEngineGetEngine(); + GSUdpRemotePeer aRemotePeer, *aRemotePeerFound; + int index; + GS_ASSERT(aUdp->mInitialized); + GS_ASSERT(theIp); + GS_ASSERT(thePort); + if (!aUdp->mInitialized) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return 0; + } + + aRemotePeer.mAddr = theIp; + aRemotePeer.mPort = thePort; + index = ArraySearch(aUdp->mRemotePeers, &aRemotePeer, gsUdpRemotePeerCompare, 0, 0); + if (index != NOT_FOUND) + { + aRemotePeerFound = (GSUdpRemotePeer *)ArrayNth(aUdp->mRemotePeers, index); + return gt2GetOutgoingBufferFreeSpace(aRemotePeerFound->mConnection); + } + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Creates a string address given an IP and port +// IP and port can be 0. +void gsUdpEngineAddrToString(unsigned int theIp, unsigned short thePort, char addrstring[GS_IP_ADDR_AND_PORT]) +{ + gt2AddressToString(theIp, thePort, addrstring); +} diff --git a/code/gamespy/common/gsUdpEngine.h b/code/gamespy/common/gsUdpEngine.h new file mode 100644 index 00000000..07c2b09e --- /dev/null +++ b/code/gamespy/common/gsUdpEngine.h @@ -0,0 +1,180 @@ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +#ifndef __GS_UDP_ENGINE_H__ +#define __GS_UDP_ENGINE_H__ + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Communication Engine +// +#include "gsCommon.h" +#include "../gt2/gt2.h" +#include "../darray.h" + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Constants + +// The UDP Engine should try the MSS (Maximum Segment Size = IP Packet size - IP Header) +// as a default size to keep the packets from being fragmented. Currently 1460 is the +// MSS for windows. Consoles may have a different size. +#define GS_UDP_DEFAULT_OUT_BUFFSIZE 1460 +#define GS_UDP_DEFAULT_IN_BUFFSIZE 1500 + +// a default size for address strings +#define GS_IP_ADDR_AND_PORT 22 + +// A fixed header len. unless we require a bigger size, it will stay this size. +// These need to be used for calculating the free space available on +// the outgoing buffers for an IP and Port which represent the peer. +#define GS_UDP_MSG_HEADER_LEN 16 +#define GS_UDP_RELIABLE_MSG_HEADER 7 + +// The following error codes will be given back to the higher level app +// or message handler. +typedef enum _GSUdpErrorCode +{ + GS_UDP_NO_ERROR, + GS_UDP_NO_MEMORY, + GS_UDP_REJECTED, + GS_UDP_NETWORK_ERROR, + GS_UDP_ADDRESS_ERROR, + GS_UDP_ADDRESS_ALREADY_IN_USE, + GS_UDP_TIMED_OUT, + GS_UDP_REMOTE_ERROR, + GS_UDP_SEND_FAILED, + GS_UDP_INVALID_MESSAGE, + GS_UDP_PARAMETER_ERROR, + GS_UDP_NOT_INITIALIZED, + GS_UDP_MSG_TOO_BIG, + GS_UDP_UNKNOWN_ERROR, + // Add new errors before here + GS_UDP_NUM_ERROR_CODES +} GSUdpErrorCode; + +// Used so that an app or message handler does +// not need to request to start talking to a peer twice +// Also lets higher level app or message handlers know about +// if a communication channel to a peer has been broken. +typedef enum _GSUdpPeerState +{ + GS_UDP_PEER_CONNECTING, + GS_UDP_PEER_CONNECTED, + GS_UDP_PEER_CLOSING, + GS_UDP_PEER_CLOSED, + // Add new connection state before here + GS_UDP_PEER_STATE_NUM +} GSUdpPeerState; + +// When a communication channel to a peer is closed +// the closed callback will let us know why it was closed +typedef enum _GSUdpCloseReason +{ + GS_UDP_CLOSED_LOCALLY, + GS_UDP_CLOSED_REMOTELY, + // errors: + GS_UDP_CLOSED_BY_COMM_ERROR, // An invalid message was received (it was either unexpected or wrongly formatted). + GS_UDP_CLOSED_BY_LOW_MEM, + // Add new reasons before here + GS_UDP_CLOSED_NUM +} GSUdpCloseReason; + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Callbacks + +// Errors to give higher layers feedback +typedef void (*gsUdpErrorCallback)(GSUdpErrorCode theCode, void *theUserData); + + +// app Request attempt callback used to tell registered listeners about connection attempts +typedef void (*gsUdpAppConnectAttemptCallback)(unsigned int theIp, unsigned short thePort, + int theLatency, unsigned char *theInitMsg, + unsigned int theInitMsgLen, void *theUserData); + + +// peer communication channel callback types +typedef void (*gsUdpConnClosedCallback)(unsigned int ip, unsigned short port, GSUdpCloseReason reason, + void *theUserData); +typedef void (*gsUdpConnReceivedDataCallback)(unsigned int ip, unsigned short port, + unsigned char *message, unsigned int messageLength, + gsi_bool reliable, void *theUserData); +typedef void (*gsUdpConnConnectedCallback)(unsigned int ip, unsigned short port, + GSUdpErrorCode error, gsi_bool rejected, + void *theUserData); +typedef void (*gsUdpConnPingCallback)(unsigned int ip, unsigned short port, unsigned int latency, + void *theUserData); + + +// Messages that cannot be interpreted are passed on to the higher level app +typedef gsi_bool (*gsUdpUnknownMsgCallback)(unsigned int ip, unsigned short port, + unsigned char *message, unsigned int messageLength, + void *theUserData); + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Public Functionality +// Initialization and test functions +gsi_bool gsUdpEngineIsInitialized(); +GSUdpErrorCode gsUdpEngineInitialize(unsigned short thePort, int theIncomingBufSize, + int theOutgoingBufSize, gsUdpErrorCallback theAppNetworkError, + gsUdpConnConnectedCallback theAppConnected, + gsUdpConnClosedCallback theAppClosed, + gsUdpConnPingCallback theAppPing, + gsUdpConnReceivedDataCallback theAppReceive, + gsUdpUnknownMsgCallback theAppUnownMsg, + gsUdpAppConnectAttemptCallback theAppConnectAttempt, + void *theAppUserData); +// update and shutdown +GSUdpErrorCode gsUdpEngineThink(); +GSUdpErrorCode gsUdpEngineShutdown(); + +// Connectivity functions +GSUdpErrorCode gsUdpEngineGetPeerState(unsigned int theIp, unsigned short thePort, + GSUdpPeerState *thePeerState); +GSUdpErrorCode gsUdpEngineStartTalkingToPeer(unsigned int theIp, unsigned short thePort, + char theInitMsg[GS_UDP_MSG_HEADER_LEN], int timeOut); +GSUdpErrorCode gsUdpEngineAcceptPeer(unsigned int theIp, unsigned short thePort); +GSUdpErrorCode gsUdpEngineRejectPeer(unsigned int theIp, unsigned short thePort); + +// Sending functionality +// WARNING: Messages should not be greater than the outgoing buffer size minus the header +// and the 7 byte header for reliable messages (used for internal gt2 operations). Most +// UDP fragmentation occurs if messages are bigger than 1500 bytes. Also, some routers are +// known to drop those packets that are larger than 1500 bytes because of set MTU sizes. +// The recommended outgoing buffer size is the default (1460). So take that, and subtract +// 16 for message handler header and reliable message header (if sending data reliably). +// freeSpace = 1460 - 16 - 7 +GSUdpErrorCode gsUdpEngineSendMessage(unsigned int theIp, unsigned short thePort, + char theHeader[GS_UDP_MSG_HEADER_LEN], unsigned char *theMsg, + unsigned int theMsgLen, gsi_bool theReliable); + +// This function should be called for those parts of the code that want specific handling of messages +// Any call to send should include the header registered here. +GSUdpErrorCode gsUdpEngineAddMsgHandler(char theInitMsg[GS_UDP_MSG_HEADER_LEN], + char theHeader[GS_UDP_MSG_HEADER_LEN], + gsUdpErrorCallback theMsgHandlerError, + gsUdpConnConnectedCallback theMsgHandlerConnected, + gsUdpConnClosedCallback theMsgHandlerClosed, + gsUdpConnPingCallback theMsgHandlerPing, + gsUdpConnReceivedDataCallback theMsgHandlerRecv, + void *theUserData); +GSUdpErrorCode gsUdpEngineRemoveMsgHandler(char theHeader[GS_UDP_MSG_HEADER_LEN]); +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Public Utility functionality +SOCKET gsUdpEngineGetSocket(); +void gsUdpEngineAddrToString(unsigned int theIp, unsigned short thePort, + char addrstring[GS_IP_ADDR_AND_PORT]); +unsigned int gsUdpEngineGetLocalAddr(); +unsigned short gsUdpEngineGetLocalPort(); + +// lets the app or message handler know if it is able to shutdown the udp layer +gsi_bool gsUdpEngineNoMoreMsgHandlers(); +gsi_bool gsUdpEngineNoApp(); + +// check the remaining free space on the outgoing buffer for the peer based +// IP and port +int gsUdpEngineGetPeerOutBufferFreeSpace(unsigned int theIp, unsigned short thePort); + +#endif diff --git a/code/gamespy/common/gsXML.c b/code/gamespy/common/gsXML.c new file mode 100644 index 00000000..43185bc5 --- /dev/null +++ b/code/gamespy/common/gsXML.c @@ -0,0 +1,2036 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsPlatform.h" +#include "gsMemory.h" +#include "gsXML.h" +#include "gsStringUtil.h" +#include "../darray.h" + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define GS_XML_INITIAL_ELEMENT_ARRAY_COUNT 32 +#define GS_XML_INITIAL_ATTRIBUTE_ARRAY_COUNT 16 + +#define GS_XML_WHITESPACE "\x20\x09\x0D\x0A" + +#define GS_XML_CHECK(a) { if (gsi_is_false(a)) return gsi_false; } + +#define GS_XML_BASE64_ENCODING_TYPE 0 + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define GS_XML_SOAP_BUFFER_INITIAL_SIZE (1 * 1024) +#define GS_XML_SOAP_BUFFER_INCREMENT_SIZE (1 * 1024) +#define GS_XML_SOAP_INITIAL_NAMESPACE_COUNT 6 + +#define GS_XML_SOAP_HEADER "" +#define GS_XML_SOAP_FOOTER "" +#define GS_XML_SOAP_NAMESPACE_PREFIX "xmlns:" + +#define GS_XML_SOAP_DEFAULT_NAMESPACE_COUNT 4 +const char * GS_XML_SOAP_DEFAULT_NAMESPACES[GS_XML_SOAP_DEFAULT_NAMESPACE_COUNT] = +{ + "SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"", + "SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\"", + "xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", + "xsd=\"http://www.w3.org/2001/XMLSchema\"" +}; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Warning: Do not store pointers to other GSXml* objects within a GSXml object +// Pointers to elements may be invalidate if the DArray's grow +// Instead, store the array index and require that indexes never change +typedef struct GSIXmlString +{ + const gsi_u8 * mData; + int mLen; +} GSIXmlString; + +typedef struct GSIXmlAttribute +{ + GSIXmlString mName; + GSIXmlString mValue; + + int mIndex; + int mParentIndex; +} GSIXmlAttribute; + +typedef struct GSIXmlElement +{ + GSIXmlString mName; + GSIXmlString mValue; // most do not have a value + + int mIndex; + int mParentIndex; +} GSIXmlElement; + +typedef struct GSIXmlStreamReader +{ + DArray mElementArray; + DArray mAttributeArray; + + int mElemReadIndex; // current index + int mValueReadIndex; // current child parsing index +} GSIXmlStreamReader; + +typedef struct GSIXmlStreamWriter +{ + char * mBuffer; + int mLen; + int mCapacity; + gsi_bool mClosed; // footer has been written, append not allowed +} GSIXmlStreamWriter; + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilSkipWhiteSpace(GSIXmlStreamReader * stream, const char * buffer, int len, int * pos); +static gsi_bool gsiXmlUtilParseName (GSIXmlStreamReader * stream, const char * buffer, int len, int * pos, GSIXmlString * strOut); +static gsi_bool gsiXmlUtilParseString (GSIXmlStreamReader * stream, char * buffer, int len, int * pos, GSIXmlString * strOut); +static gsi_bool gsiXmlUtilParseValue (GSIXmlStreamReader * stream, char * buffer, int len, int * pos, GSIXmlString * strOut); +static gsi_bool gsiXmlUtilParseElement (GSIXmlStreamReader * stream, char * buffer, int len, int * pos, int parentIndex); +static gsi_bool gsiXmlUtilTagMatches(const char * matchtag, GSIXmlString * xmlstr); + +// Note: Writes decoded form back into buffer +static gsi_bool gsiXmlUtilDecodeString(char * buffer, int * len); + +static void gsiXmlUtilElementFree(void * elem); +static void gsiXmlUtilAttributeFree(void * elem); + +static gsi_bool gsiXmlUtilWriteChar(GSIXmlStreamWriter * stream, char ch); +static gsi_bool gsiXmlUtilWriteString(GSIXmlStreamWriter * stream, const char * str); +static gsi_bool gsiXmlUtilWriteXmlSafeString(GSIXmlStreamWriter * stream, const char * str); +static gsi_bool gsiXmlUtilGrowBuffer(GSIXmlStreamWriter * stream); +#ifdef GSI_UNICODE +static gsi_bool gsiXmlUtilWriteAsciiString(GSIXmlStreamWriter * stream, const gsi_char * str); +#endif +static gsi_bool gsiXmlUtilWriteUnicodeString(GSIXmlStreamWriter * stream, const unsigned short * str); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static int gsUnicodeStringLen(const unsigned short * str) +{ + const unsigned short * end = str; + while(*end++) + {} + return (end - str - 1); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +GSXmlStreamWriter gsXmlCreateStreamWriter(const char ** namespaces, int count) +{ + GSIXmlStreamWriter * newStream = NULL; + int initialCapacity = GS_XML_SOAP_BUFFER_INCREMENT_SIZE; + int namespaceLen = 0; + int i=0; + + GS_ASSERT((namespaces == NULL && count == 0) || (namespaces != NULL && count != 0)); + + newStream = (GSIXmlStreamWriter*)gsimalloc(sizeof(GSIXmlStreamWriter)); + if (newStream == NULL) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Memory, GSIDebugLevel_HotError, + "Out of memory in gsXmlCreateStreamWriter, needed %d bytes", sizeof(GSIXmlStreamWriter)); + return NULL; // OOM + } + + // do this to prevent an immediately reallocation due to long namespace string + for (i=0; i < GS_XML_SOAP_DEFAULT_NAMESPACE_COUNT; i++) + { + GS_ASSERT(GS_XML_SOAP_DEFAULT_NAMESPACES[i] != NULL); + namespaceLen += strlen(GS_XML_SOAP_NAMESPACE_PREFIX)+1; // +1 for space + namespaceLen += strlen(GS_XML_SOAP_DEFAULT_NAMESPACES[i]); + } + for (i=0; i < count; i++) + { + GS_ASSERT(namespaces[i] != NULL); + namespaceLen += strlen(GS_XML_SOAP_NAMESPACE_PREFIX)+1; // +1 for space + namespaceLen += strlen(namespaces[i]); + } + while (initialCapacity < namespaceLen) + initialCapacity += GS_XML_SOAP_BUFFER_INCREMENT_SIZE; + + // allocate write buffer + newStream->mBuffer = (char*)gsimalloc((size_t)initialCapacity); + if (newStream->mBuffer == NULL) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Memory, GSIDebugLevel_HotError, + "Out of memory in gsXmlCreateStreamWriter, needed %d bytes", initialCapacity); + return NULL; // OOM + } + newStream->mCapacity = initialCapacity; + newStream->mLen = 0; + newStream->mBuffer[0] = '\0'; + newStream->mClosed = gsi_false; + + // write the XML header + if (gsi_is_false(gsiXmlUtilWriteString(newStream, GS_XML_SOAP_HEADER))) + { + gsifree(newStream->mBuffer); + gsifree(newStream); + return NULL; // OOM + } + for (i=0; i < GS_XML_SOAP_DEFAULT_NAMESPACE_COUNT; i++) + { + if (gsi_is_false(gsiXmlUtilWriteChar(newStream, ' ')) || + gsi_is_false(gsiXmlUtilWriteString(newStream, GS_XML_SOAP_NAMESPACE_PREFIX)) || + gsi_is_false(gsiXmlUtilWriteString(newStream, GS_XML_SOAP_DEFAULT_NAMESPACES[i]))) + { + gsifree(newStream->mBuffer); + gsifree(newStream); + return NULL; // OOM + } + } + for (i=0; i < count; i++) + { + if (gsi_is_false(gsiXmlUtilWriteChar(newStream, ' ')) || + gsi_is_false(gsiXmlUtilWriteString(newStream, GS_XML_SOAP_NAMESPACE_PREFIX)) || + gsi_is_false(gsiXmlUtilWriteString(newStream, namespaces[i])) ) + { + gsifree(newStream->mBuffer); + gsifree(newStream); + return NULL; // OOM + } + } + if (gsi_is_false(gsiXmlUtilWriteChar(newStream, '>')) || + gsi_is_false(gsiXmlUtilWriteString(newStream, GS_XML_SOAP_BODY_TAG)) ) + { + gsifree(newStream->mBuffer); + gsifree(newStream); + return NULL; // OOM + } + + return (GSXmlStreamWriter)newStream; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +GSXmlStreamReader gsXmlCreateStreamReader() +{ + GSIXmlStreamReader * newStream = NULL; + + newStream = (GSIXmlStreamReader*)gsimalloc(sizeof(GSIXmlStreamReader)); + if (newStream == NULL) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Memory, GSIDebugLevel_HotError, + "Out of memory in gsXmlCreateStream, needed %d bytes", sizeof(GSIXmlStreamReader)); + return NULL; // OOM + } + + newStream->mElementArray = ArrayNew(sizeof(GSIXmlElement), GS_XML_INITIAL_ELEMENT_ARRAY_COUNT, gsiXmlUtilElementFree); + if (newStream->mElementArray == NULL) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Memory, GSIDebugLevel_HotError, + "Out of memory in gsXmlCreateStream with mElementArray=ArrayNew()"); + gsifree(newStream); + return NULL; // OOM + } + + newStream->mAttributeArray = ArrayNew(sizeof(GSIXmlAttribute), GS_XML_INITIAL_ATTRIBUTE_ARRAY_COUNT, gsiXmlUtilAttributeFree); + if (newStream->mAttributeArray == NULL) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Memory, GSIDebugLevel_HotError, + "Out of memory in gsXmlCreateStream with mElementArray=ArrayNew()"); + ArrayFree(newStream->mElementArray); + gsifree(newStream); + return NULL; // OOM + } + + gsXmlMoveToStart(newStream); + return (GSXmlStreamReader)newStream; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlParseBuffer(GSXmlStreamReader stream, char * data, int len) +{ + GSIXmlStreamReader * reader; + int readPos = 0; + + GS_ASSERT(data != NULL); + GS_ASSERT(len > 0); + + reader = (GSIXmlStreamReader*)stream; + + gsXmlResetReader(stream); + + // Parse the root elements (automatically includes sub-elements) + while(readPos < len) + { + if (gsi_is_false(gsiXmlUtilParseElement(reader, data, len, &readPos, -1))) + return gsi_false; + } + + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsXmlFreeWriter(GSXmlStreamWriter stream) +{ + GSIXmlStreamWriter * writer = (GSIXmlStreamWriter*)stream; + gsifree(writer->mBuffer); + gsifree(writer); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsXmlFreeReader(GSXmlStreamReader stream) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + ArrayFree(reader->mAttributeArray); + ArrayFree(reader->mElementArray); + gsifree(reader); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsXmlResetReader(GSXmlStreamReader stream) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + ArrayClear(reader->mAttributeArray); + ArrayClear(reader->mElementArray); + gsXmlMoveToStart(stream); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlCloseWriter(GSXmlStreamWriter stream) +{ + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + GS_ASSERT(stream != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + if (gsi_is_false(gsiXmlUtilWriteString(writer, GS_XML_SOAP_FOOTER))) + return gsi_false; + + writer->mClosed = gsi_true; + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void gsiXmlUtilElementFree(void * elem) +{ + GSI_UNUSED(elem); + //GSXmlElement * dataPtr = (GSXmlElement*)elem; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void gsiXmlUtilAttributeFree(void * elem) +{ + GSI_UNUSED(elem); + //GSXmlAttribute * dataPtr = (GSXmlAttribute*)elem; + //gsifree(dataPtr); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +const char * gsXmlWriterGetData (GSXmlStreamWriter stream) +{ + GSIXmlStreamWriter * writer = (GSIXmlStreamWriter*)stream; + GS_ASSERT(stream != NULL) + return writer->mBuffer; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +int gsXmlWriterGetDataLength(GSXmlStreamWriter stream) +{ + GSIXmlStreamWriter * writer = (GSIXmlStreamWriter*)stream; + GS_ASSERT(stream != NULL); + return writer->mLen; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilSkipWhiteSpace(GSIXmlStreamReader * stream, const char * buffer, int len, int * pos) +{ + GS_ASSERT(buffer != NULL); + GS_ASSERT(len > 0); + //GS_ASSERT(*pos < len); + + // check if the next character is in the whitespace set + while(*pos < len) + { + if (NULL == strchr(GS_XML_WHITESPACE, buffer[*pos])) + return gsi_true; + (*pos)++; // move to next character + } + + GSI_UNUSED(stream); + return gsi_false; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilParseElement(GSIXmlStreamReader * stream, char * buffer, + int len, int * pos, int parentIndex) +{ + GSIXmlElement newElem; + int startPos = 0; + gsi_bool storeElement = gsi_true; + + GS_ASSERT(stream != NULL); + GS_ASSERT(buffer != NULL); + GS_ASSERT(len > 0); + GS_ASSERT(*pos < len); + + gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos); + if (*pos >= len) + return gsi_true; // legal EOF + + memset(&newElem, 0, sizeof(newElem)); + + // elements must begin with '<' + if (buffer[*pos] != '<') + return gsi_false; + (*pos)++; + if (*pos >= len) + return gsi_false; + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + + // '<' can be followed with '!', '?', '%', '/' special characters + if (buffer[*pos] == '!' || buffer[*pos] == '?' || + buffer[*pos] == '%' || buffer[*pos] == '/') + { + storeElement = gsi_false; + (*pos)++; + startPos = (*pos)-2; + } + else + startPos = (*pos)-1; // store the position of '<' + + // should be a name (type) next + GS_XML_CHECK(gsiXmlUtilParseName(stream, buffer, len, pos, &newElem.mName)); + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + + if (storeElement) + { + GS_ASSERT(newElem.mName.mData != NULL); + newElem.mIndex = ArrayLength(stream->mElementArray); + newElem.mParentIndex = parentIndex; + ArrayAppend(stream->mElementArray, &newElem); + } + + // read attributes (if any) + while (*pos < len && isalnum(buffer[*pos])) + { + // attribute format is name="", e.g. nickname="player1" + GSIXmlAttribute newAttr; + memset(&newAttr, 0, sizeof(newAttr)); + GS_XML_CHECK(gsiXmlUtilParseName(stream, buffer, len, pos, &newAttr.mName)); + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + if (buffer[*pos] != '=') + return gsi_false; + (*pos)++; // skip the '=' + GS_XML_CHECK(gsiXmlUtilParseString(stream, buffer, len, pos, &newAttr.mValue)); + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + + // Store it in the array + if (storeElement) + { + GS_ASSERT(newElem.mName.mData != NULL); + GS_ASSERT(newElem.mIndex != -1); + GS_ASSERT(newAttr.mName.mData != NULL); + GS_ASSERT(newAttr.mValue.mData != NULL); + + newAttr.mIndex = ArrayLength(stream->mAttributeArray); + newAttr.mParentIndex = newElem.mIndex; + ArrayAppend(stream->mAttributeArray, &newAttr); + } + } + + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + + // Check for immediate termination (no value or children) + // non-element tags end with the same character they start with + // element tags ending with '/>' also have no children + if ( (!isalnum(buffer[startPos+1]) && buffer[*pos] == buffer[startPos+1]) + || buffer[*pos] == '/') + { + (*pos)++; + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + if (buffer[*pos] != '>') + return gsi_false; // only legal character here is closing brace + (*pos)++; + return gsi_true; // legal termination + } + + // make sure we've found the end of the start tag + if (buffer[*pos] != '>') + return gsi_false; + (*pos)++; + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + + // check for element value + if (buffer[*pos] != '<') + { + GS_XML_CHECK(gsiXmlUtilParseValue(stream, buffer, len, pos, &newElem.mValue)); + // update the array with the value information + if (storeElement) + ArrayReplaceAt(stream->mElementArray, &newElem, newElem.mIndex); + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + } + + // read child elements and close tag + while (*pos < len) + { + int childStartPos = *pos; + if (buffer[*pos] != '<') + return gsi_false; + (*pos)++; + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + if (buffer[*pos] == '/') + { + // this MUST be a close of the current element + // close tags are in the form: + (*pos)++; + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + if ((*pos)+newElem.mName.mLen >= len) + return gsi_false; // EOF before tag close + if (0 != strncmp((const char*)newElem.mName.mData, &buffer[*pos], (size_t)newElem.mName.mLen)) + return gsi_false; // close tag mismatch + (*pos) += newElem.mName.mLen; + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + if (buffer[*pos] != '>') + return gsi_false; + (*pos)++; + return gsi_true; + } + else + { + if (newElem.mValue.mData != NULL) + return gsi_false; // elements with value cannot have children + + // move read position to start of child element, then parse + *pos = childStartPos; + GS_XML_CHECK(gsiXmlUtilParseElement(stream, buffer, len, pos, newElem.mIndex)); + gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos); + } + } + return gsi_false; // EOF before tag close +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Parse element name +// Must begin with a letter and contains only alphanumeric | '_' | '-' characters +// Element names may consist of two "names", : +static gsi_bool gsiXmlUtilParseName(GSIXmlStreamReader * stream, const char * buffer, + int len, int * pos, GSIXmlString * strOut) +{ + gsi_bool haveNamespace = gsi_false; + GS_ASSERT(buffer != NULL); + GS_ASSERT(len > 0); + + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + + // EOF? + if (*pos >= len) + return gsi_false; + + // first character must be alphanumeric but not a digit + if (!isalnum(buffer[*pos]) || isdigit(buffer[*pos])) + return gsi_false; + + strOut->mData = (gsi_u8*)&buffer[*pos]; + strOut->mLen = 1; + (*pos)++; + + while(*pos < len && NULL==strchr(GS_XML_WHITESPACE, buffer[*pos])) + { + // only alpha numeric and '_' characters are allowed, plus one namespace separator ':' + if (buffer[*pos] == ':') + { + if (gsi_is_true(haveNamespace)) + return gsi_false; // already have a namespace! + haveNamespace = gsi_true; + } + else if ((buffer[*pos] != '_') && (buffer[*pos] != '-') && (!isalnum(buffer[*pos]))) + return gsi_true; // treat all others as a new token example '=' + + strOut->mLen++; + (*pos)++; + } + + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilParseString(GSIXmlStreamReader * stream, char * buffer, + int len, int * pos, GSIXmlString * strOut) +{ + char startCh = '\0'; + char * strStart = NULL; + //gsi_bool hasMarkup = gsi_false; + + + GS_ASSERT(stream != NULL); + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + + // strings may start with either ' or " + startCh = buffer[*pos]; + if (startCh != '\"' && startCh != '\'') + return gsi_false; + + (*pos)++; + strStart = &buffer[*pos]; // remember this for easier processing below + strOut->mLen = 0; + + if (*pos >= len) + return gsi_false; // EOF when looking for string terminator + if (buffer[*pos] == startCh) + { + // empty string ? + strOut->mData = (const gsi_u8*)strStart; + (*pos)++; // skip the terminating character + return gsi_true; + } + + while(buffer[*pos] != startCh) + { + if (*pos >= len) + return gsi_false; // EOF when looking for string terminator + + //if (buffer[*pos] == '&') + //hasMarkup = gsi_true; + + (*pos)++; + strOut->mLen++; + } + (*pos)++; // skip the terminating character + + // decode the string if necessary + if (gsi_is_false(gsiXmlUtilDecodeString(strStart, &strOut->mLen))) + return gsi_false; + + // set the data into strOut + strOut->mData = (const gsi_u8*)strStart; + return gsi_true; + +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Remove '&' markup from the buffer, converts in place +static gsi_bool gsiXmlUtilDecodeString(char * buffer, int * len) +{ + int readPos = 0; + + while(readPos < *len) + { + int charsToRemove = 0; + + // we only want '&' + if (buffer[readPos] != '&') + { + readPos++; + continue; + } + + // check single character switches + if (strncmp(&buffer[readPos], "&", 5)==0) + { + buffer[readPos++] = '&'; + charsToRemove = 5-1; + } + else if (strncmp(&buffer[readPos], """, 6)==0) + { + buffer[readPos++] = '\"'; + charsToRemove = 6-1; + } + else if (strncmp(&buffer[readPos], "'", 6)==0) + { + buffer[readPos++] = '\''; + charsToRemove = 6-1; + } + else if (strncmp(&buffer[readPos], "<", 4)==0) + { + buffer[readPos++] = '<'; + charsToRemove = 4-1; + } + else if (strncmp(&buffer[readPos], ">", 4)==0) + { + buffer[readPos++] = '>'; + charsToRemove = 4-1; + } + else if (strncmp(&buffer[readPos], "&#x", 3)==0) + { + // hex digit + unsigned int digitValue = 0; + //unsigned int digitLength = 0; // 0x00000065 = 1 byte + char ch = ' '; + gsi_bool haveWritten = gsi_false; + int i=0; + unsigned int mask = 0xFF000000; + + char * digitEnd = strchr(&buffer[readPos+3], ';'); + if (digitEnd == NULL) + return gsi_false; // missing ';' + if (digitEnd - &buffer[readPos+3] > 8) + return gsi_false; // too many digits before end + + // scan digits into memory, do this as a block so that ť = 01 65 + sscanf(&buffer[readPos+3], "%08x", &digitValue); + + // write the digit back as a character array + for (i=0; i < 4; i++) + { + ch = (char)((digitValue & mask) >> ((3-i)*8)); // make 0x00006500 into 0x65 + if (haveWritten || ch != 0x00) + { + buffer[readPos++] = ch; + haveWritten = gsi_true; + } + mask = mask >> 8; + } + + // remove everything between the current read position and the semicolon + charsToRemove = digitEnd - &buffer[readPos] + 1; // remove the semicolon + } + else if (strncmp(&buffer[readPos], "&#", 2)==0) + { + // dec digit - like a hex digit, only use atoi instead of sscanf + unsigned int digitValue = 0; + //unsigned int digitLength = 0; // 0x00000065 = 1 byte + char ch = ' '; + gsi_bool haveWritten = gsi_false; + int i=0; + unsigned int mask = 0xFF000000; + + char * digitEnd = strchr(&buffer[readPos+2], ';'); + if (digitEnd == NULL) + return gsi_false; // missing ';' + + // scan digits into memory, do this as a block so that ť = 0165h = 01 65 + digitValue = (unsigned int)atoi(&buffer[readPos+2]); + + // write the digit back as a character array + for (i=0; i < 4; i++) + { + ch = (char)((digitValue & mask) >> ((3-i)*8)); // make 0x00006500 into 0x65 + if (haveWritten || ch != 0x00) + { + buffer[readPos++] = ch; + haveWritten = gsi_true; + } + mask = mask >> 8; + } + + // remove everything between the current read position and the semicolon + charsToRemove = digitEnd - &buffer[readPos] + 1; // remove the semicolon + + } + else + return gsi_false; // unhandle '&' type + + // remove characters by compressing buffer and adding whitespace at the end + // "&&" becomes "&& " after one iteration + memmove(&buffer[readPos], &buffer[readPos+charsToRemove], (size_t)(*len-(readPos+charsToRemove))); + memset(&buffer[*len-charsToRemove], ' ', (size_t)charsToRemove); + (*len) -= charsToRemove; + } + + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Parse an entity value. +// Todo: resolving escaped strings? +static gsi_bool gsiXmlUtilParseValue(GSIXmlStreamReader * stream, char * buffer, + int len, int * pos, GSIXmlString * strOut) +{ + char * strStart = NULL; + + GS_ASSERT(stream != NULL); + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + + if (buffer[*pos] != '<') + { + strStart = &buffer[*pos]; // store this so we can find it later + strOut->mData = (const gsi_u8*)strStart; + } + + while(*pos < len) + { + if (buffer[*pos] == '<') + { + // decode the string if necessary + if (gsi_is_false(gsiXmlUtilDecodeString(strStart, &strOut->mLen))) + return gsi_false; + return gsi_true; // extracted and decoded + } + (*pos)++; + strOut->mLen++; + } + return gsi_false; // EOF before tag end +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteOpenTag(GSXmlStreamWriter stream, const char * namespaceName, + const char * tag) +{ + GSIXmlStreamWriter * writer = (GSIXmlStreamWriter*)stream; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + if ( gsi_is_false(gsiXmlUtilWriteChar(writer, '<')) || + gsi_is_false(gsiXmlUtilWriteString(writer, namespaceName)) || + gsi_is_false(gsiXmlUtilWriteChar(writer, ':')) || + gsi_is_false(gsiXmlUtilWriteString(writer, tag)) || + gsi_is_false(gsiXmlUtilWriteChar(writer, '>')) + ) + { + return gsi_false; + } + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteCloseTag(GSXmlStreamWriter stream, const char * namespaceName, + const char * tag) +{ + GSIXmlStreamWriter * writer = (GSIXmlStreamWriter*)stream; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + if ( gsi_is_false(gsiXmlUtilWriteChar(writer, '<')) || + gsi_is_false(gsiXmlUtilWriteChar(writer, '/')) || + gsi_is_false(gsiXmlUtilWriteString(writer, namespaceName)) || + gsi_is_false(gsiXmlUtilWriteChar(writer, ':')) || + gsi_is_false(gsiXmlUtilWriteString(writer, tag)) || + gsi_is_false(gsiXmlUtilWriteChar(writer, '>')) + ) + { + return gsi_false; + } + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteStringElement(GSXmlStreamWriter stream, const char * namespaceName, + const char * tag, const char * value) +{ + GSIXmlStreamWriter * writer = (GSIXmlStreamWriter*)stream; + int i=0; + int len = 0; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(value != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + // Check legal ASCII characters + // 0x9, 0xA, 0xD + // [0x20-0xFF] + len = (int)strlen(value); + for (i=0; i < len; i++) + { + // only check values less than 0x20 + if ((unsigned char)value[i] < 0x20) + { + if ((unsigned char)value[i] != 0x09 + && ((unsigned char)value[i] != 0x0A) + && ((unsigned char)value[i] != 0x0D) + ) + { + // contains illegal (and unencodable) characters. + return gsi_false; + } + } + } + + if ( gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag)) || + gsi_is_false(gsiXmlUtilWriteXmlSafeString(writer, value)) || + gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag)) + ) + { + return gsi_false; + } + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Converts unicode to ascii strings for writing to XML writer +gsi_bool gsXmlWriteAsciiStringElement(GSXmlStreamWriter stream, const char * namespaceName, + const char * tag, const gsi_char * value) +{ + GSIXmlStreamWriter * writer = (GSIXmlStreamWriter*)stream; + int i=0; + int len = 0; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(value != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + // Check legal ASCII characters + // 0x9, 0xA, 0xD + // [0x20-0xFF] + len = (int)_tcslen(value); + for (i=0; i < len; i++) + { + // only check values less than 0x20 + if ((unsigned char)value[i] < 0x20) + { + if ((unsigned char)value[i] != 0x09 + && ((unsigned char)value[i] != 0x0A) + && ((unsigned char)value[i] != 0x0D) + ) + { + // contains illegal (and unencodable) characters. + return gsi_false; + } + } + } + +#ifdef GSI_UNICODE + //if unicode, write as Ascii string, otherwise it is already in Ascii format + if ( gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag)) || + gsi_is_false(gsiXmlUtilWriteAsciiString(writer, value)) || + gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag)) + ) + { + return gsi_false; + } +#else + if ( gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag)) || + gsi_is_false(gsiXmlUtilWriteXmlSafeString(writer, value)) || + gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag)) + ) + { + return gsi_false; + } +#endif + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteUnicodeStringElement(GSXmlStreamWriter stream, const char * namespaceName, + const char * tag, const unsigned short * value) +{ + GSIXmlStreamWriter * writer = (GSIXmlStreamWriter*)stream; + int i = 0; + int len = 0; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(value != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + // Check legal UNICODE characters + // 0x9, 0xA, 0xD + // [0x20-0xD7FF] + // [xE000-xFFFD] + // [x10000-x10FFFF] (UTF-16, not supported) + len = gsUnicodeStringLen(value); + for (i=0; i < len; i++) + { + // check values less than 0x20 + if (value[i] < 0x0020) + { + if ((value[i] != 0x0009) && (value[i] != 0x0A) && (value[i] != 0x0D)) + return gsi_false; // contains illegal (and unencodable) characters. + } + else if (value[i] > 0xD7FF && value[i] < 0xE000) + return gsi_false; // contains illegal (and unencodable) characters. + else if (value[i] > 0xFFFD) + return gsi_false; // contains illegal (and unencodable) characters. + } + + if ( gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag)) || + gsi_is_false(gsiXmlUtilWriteUnicodeString(writer, value)) || + gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag)) + ) + { + return gsi_false; + } + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteIntElement(GSXmlStreamWriter stream, const char * namespaceName, + const char * tag, gsi_u32 value) +{ + GSIXmlStreamWriter * writer = (GSIXmlStreamWriter*)stream; + char buf[32]; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + sprintf(buf, "%d", value); + + if ( gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag)) || + gsi_is_false(gsiXmlUtilWriteString(writer, buf)) || + gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag)) + ) + { + return gsi_false; + } + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteInt64Element(GSXmlStreamWriter stream, const char * namespaceName, + const char * tag, gsi_i64 value) +{ + GSIXmlStreamWriter * writer = (GSIXmlStreamWriter*)stream; + char buf[33]; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + gsiInt64ToString(buf, value); + + if ( gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag)) || + gsi_is_false(gsiXmlUtilWriteString(writer, buf)) || + gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag)) + ) + { + return gsi_false; + } + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteFloatElement(GSXmlStreamWriter stream, const char * namespaceName, + const char * tag, float value) +{ + GSIXmlStreamWriter * writer = (GSIXmlStreamWriter*)stream; + char buf[32]; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + sprintf(buf, "%f", value); + + if ( gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag)) || + gsi_is_false(gsiXmlUtilWriteString(writer, buf)) || + gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag)) + ) + { + return gsi_false; + } + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// first character of HEX binary is HIGH byte +gsi_bool gsXmlWriteHexBinaryElement(GSXmlStreamWriter stream, const char * namespaceName, const char * tag, const gsi_u8 * data, int len) +{ + GSIXmlStreamWriter * writer = (GSIXmlStreamWriter*)stream; + int pos = 0; + int temp = 0; + + char hex[3]; + hex[2] = '\0'; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(data != NULL); + GS_ASSERT(len > 0); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + if (gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag))) + return gsi_false; + + while (pos < len) + { + temp = data[pos]; // sprintf requires an int parameter for %02x operation + sprintf(hex, "%02x", temp); + pos++; + if (gsi_is_false(gsiXmlUtilWriteChar(writer, hex[0]))) + return gsi_false; + if (gsi_is_false(gsiXmlUtilWriteChar(writer, hex[1]))) + return gsi_false; + } + + if (gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag))) + return gsi_false; + + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteBase64BinaryElement(GSXmlStreamWriter stream, const char * namespaceName, const char * tag, const gsi_u8 * data, int len) +{ + GSIXmlStreamWriter * writer = (GSIXmlStreamWriter*)stream; + B64StreamData streamData; + char b64[5]; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(data != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + if (gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag))) + return gsi_false; + + B64InitEncodeStream(&streamData, (const char*)data, len, GS_XML_BASE64_ENCODING_TYPE); + while(B64EncodeStream(&streamData, b64)) + { + b64[4] = '\0'; + if (gsi_is_false(gsiXmlUtilWriteString(writer, b64))) + return gsi_false; + } + + if (gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag))) + return gsi_false; + + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteDateTimeElement(GSXmlStreamWriter stream, const char * namespaceName, const char * tag, time_t value) +{ + GSIXmlStreamWriter * writer = (GSIXmlStreamWriter*)stream; + char timeString[21]; + struct tm *timePtr; + + // convert the time to a string + timePtr = gsiSecondsToDate(&value); + + sprintf(timeString, "%d-%02d-%02dT%02d:%02d:%02dZ", + timePtr->tm_year + 1900, timePtr->tm_mon + 1, timePtr->tm_mday, + timePtr->tm_hour, timePtr->tm_min, timePtr->tm_sec); + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + if ( gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag)) || + gsi_is_false(gsiXmlUtilWriteString(writer, timeString)) || + gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag)) + ) + { + return gsi_false; + } + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Must reverse byte order and strip leading zeroes +gsi_bool gsXmlWriteLargeIntElement(GSXmlStreamWriter stream, const char * namespaceName, const char * tag, const struct gsLargeInt_s * lint) +{ + GSIXmlStreamWriter * writer = (GSIXmlStreamWriter*)stream; + int readPos = (int)(lint->mLength - 1); + const l_word *readBuf = (const l_word*)lint->mData; + unsigned int temp; + int i; + char hex[3]; + gsi_bool first = gsi_true; + + if (gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag))) + return gsi_false; + + // skip leading zeroes + while(readPos >= 0 && readBuf[readPos] == 0) + readPos--; + + // dump words + for (; readPos >= 0; readPos--) + { + if(gsi_is_true(first)) + { + for(i = 0 ; i < GS_LARGEINT_DIGIT_SIZE_BYTES ; i++) + { + temp = ((lint->mData[readPos] >> (8 * (GS_LARGEINT_DIGIT_SIZE_BYTES - i - 1))) & 0xFF); + if(temp != 0) + break; + } + first = gsi_false; + } + else + { + i = 0; + } + + // loop through each byte from most to least significant + for (; i < GS_LARGEINT_DIGIT_SIZE_BYTES; i++) + { + temp = ((lint->mData[readPos] >> (8 * (GS_LARGEINT_DIGIT_SIZE_BYTES - i - 1))) & 0xFF); + sprintf(hex, "%02x", temp); + if (gsi_is_false(gsiXmlUtilWriteChar(writer, hex[0]))) + return gsi_false; + if (gsi_is_false(gsiXmlUtilWriteChar(writer, hex[1]))) + return gsi_false; + } + } + + if (gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag))) + return gsi_false; + + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilWriteChar(GSIXmlStreamWriter * stream, char ch) +{ + GS_ASSERT(gsi_is_false(stream->mClosed)); + + if (stream->mLen >= stream->mCapacity) + { + if (gsi_is_false(gsiXmlUtilGrowBuffer(stream))) + return gsi_false; // OOM + } + stream->mBuffer[stream->mLen] = ch; + stream->mLen++; + + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilWriteString(GSIXmlStreamWriter * stream, const char * str) +{ + int strLen = 0; + + GS_ASSERT(str != NULL); + GS_ASSERT(gsi_is_false(stream->mClosed)); + + // get URL encoded length + strLen = (int)strlen(str); + if (strLen == 0) + return gsi_true; + + // grow the buffer if necessary + while ((stream->mCapacity - stream->mLen) <= strLen) + { + if (gsi_is_false(gsiXmlUtilGrowBuffer(stream))) + return gsi_false; // OOM + } + + strcpy(&stream->mBuffer[stream->mLen], str); + stream->mLen += strLen; + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef GSI_UNICODE +static gsi_bool gsiXmlUtilWriteAsciiString(GSIXmlStreamWriter * stream, const gsi_char * str) +{ + int strLen = 0; + + GS_ASSERT(str != NULL); + GS_ASSERT(gsi_is_false(stream->mClosed)); + + // get URL encoded length + strLen = (int)_tcslen(str); + if (strLen == 0) + return gsi_true; + + // grow the buffer if necessary + while ((stream->mCapacity - stream->mLen) <= strLen) + { + if (gsi_is_false(gsiXmlUtilGrowBuffer(stream))) + return gsi_false; // OOM + } + + UCS2ToAsciiString(str, &stream->mBuffer[stream->mLen]); + stream->mLen += strLen; + return gsi_true; +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilWriteUnicodeString(GSIXmlStreamWriter * stream, const unsigned short * str) +{ + int strLen = 0; + int pos = 0; + int utf8Len = 0; + char utf8String[4] = { '\0' }; + //gsi_bool result = gsi_false; + + GS_ASSERT(str != NULL); + GS_ASSERT(gsi_is_false(stream->mClosed)); + + strLen = gsUnicodeStringLen(str); + utf8String[3] = '\0'; + + for (pos = 0; pos < strLen; pos++) + { + utf8Len = _UCS2CharToUTF8String(str[pos], utf8String); + utf8String[utf8Len] = '\0'; // null terminate it + if (gsi_is_false(gsiXmlUtilWriteXmlSafeString(stream, utf8String))) + return gsi_false; + } + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilWriteXmlSafeString(GSIXmlStreamWriter * stream, const char * str) +{ + int strLen = 0; + int pos = 0; + gsi_bool result = gsi_false; + + GS_ASSERT(str != NULL); + GS_ASSERT(gsi_is_false(stream->mClosed)); + + strLen = (int)strlen(str); + + for (pos = 0; pos < strLen; pos++) + { + if (str[pos] == '&') + result = gsiXmlUtilWriteString(stream, "&"); + else if (str[pos] == '\'') + result = gsiXmlUtilWriteString(stream, "'"); + else if (str[pos] == '"') + result = gsiXmlUtilWriteString(stream, """); + else if (str[pos] == '<') + result = gsiXmlUtilWriteString(stream, "<"); + else if (str[pos] == '>') + result = gsiXmlUtilWriteString(stream, ">"); + else if (str[pos] == ' ') + result = gsiXmlUtilWriteString(stream, " "); + else if (str[pos] < 0x20 || ((unsigned char)str[pos]) > 0x7F) + { + // write as hex + char numeric[7]; + sprintf(numeric, "&#x%02x;", (unsigned char)str[pos]); + numeric[6] = '\0'; + result = gsiXmlUtilWriteString(stream, numeric); + } + else + result = gsiXmlUtilWriteChar(stream, str[pos]); + + if (gsi_is_false(result)) + return gsi_false; + } + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilGrowBuffer(GSIXmlStreamWriter * stream) +{ + int newCapacity = stream->mCapacity + GS_XML_SOAP_BUFFER_INCREMENT_SIZE; + void* newBuf = NULL; + + newBuf = gsirealloc(stream->mBuffer, (size_t)newCapacity); + if (newBuf == NULL) + return gsi_false; + if (newBuf != stream->mBuffer) + stream->mBuffer = (char*)newBuf; + stream->mCapacity = newCapacity; + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlMoveToStart(GSXmlStreamReader stream) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + reader->mElemReadIndex = -1; // start BEFORE first element + reader->mValueReadIndex = -1; + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Move to next occurance of "matchtag" at any level +gsi_bool gsXmlMoveToNext(GSXmlStreamReader stream, const char * matchtag) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + int i=0; + + for (i=(reader->mElemReadIndex+1); i < ArrayLength(reader->mElementArray); i++) + { + GSIXmlElement * elem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (gsi_is_true(gsiXmlUtilTagMatches(matchtag, &elem->mName))) + { + reader->mElemReadIndex = i; + reader->mValueReadIndex = -1; + return gsi_true; + } + } + // no matching element found + return gsi_false; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Move up one level in tree +gsi_bool gsXmlMoveToParent(GSXmlStreamReader stream) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + + // check for invalid position + if (reader->mElemReadIndex >= ArrayLength(reader->mElementArray) || + reader->mElemReadIndex == -1) + { + return gsi_false; // current position invalid + } + else + { + GSIXmlElement * elem = (GSIXmlElement*)ArrayNth(reader->mElementArray, reader->mElemReadIndex); + if (elem->mParentIndex == -1) + return gsi_false; // current elem is at highest level + if (elem->mParentIndex >= ArrayLength(reader->mElementArray)) + return gsi_false; // parent is invalid! + reader->mElemReadIndex = elem->mParentIndex; + reader->mValueReadIndex = -1; + return gsi_true; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Move to next unit who shares common parent +gsi_bool gsXmlMoveToSibling (GSXmlStreamReader stream, const char * matchtag) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + int i=0; + + int curElemParent = -1; + GSIXmlElement * searchElem = NULL; + + // If the current element is valid use its parent id + if (reader->mElemReadIndex < ArrayLength(reader->mElementArray)) + { + GSIXmlElement * curElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, reader->mElemReadIndex); + curElemParent = curElem->mParentIndex; + } + else + // otherwise search root elements only + curElemParent = -1; + + for (i=(reader->mElemReadIndex+1); i < ArrayLength(reader->mElementArray); i++) + { + searchElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + // if sibling... + if (searchElem->mParentIndex == curElemParent) + { + // check match + if (gsi_is_true(gsiXmlUtilTagMatches(matchtag, &searchElem->mName))) + { + reader->mElemReadIndex = i; + reader->mValueReadIndex = -1; + return gsi_true; + } + } + // bail if we reach a higher brance + if (searchElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + + // no matching element found + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlMoveToChild(GSXmlStreamReader stream, const char * matchtag) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + GSIXmlElement * searchElem = NULL; + int i=0; + + for (i=(reader->mElemReadIndex+1); i < ArrayLength(reader->mElementArray); i++) + { + searchElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchElem->mParentIndex == reader->mElemReadIndex) + { + // check match + if (gsi_is_true(gsiXmlUtilTagMatches(matchtag, &searchElem->mName))) + { + reader->mElemReadIndex = i; + reader->mValueReadIndex = -1; + return gsi_true; + } + } + // check if we've reached a higher branch + // -- we know this when we've reached an element whose + // parent is above our level in the tree + if (searchElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlReadChildAsString(GSXmlStreamReader stream, const char * matchtag, + const char ** valueOut, int * lenOut) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + GSIXmlElement * searchValueElem = NULL; + int i=0; + + // Do we have a valid value position already? + if (reader->mValueReadIndex == -1) + reader->mValueReadIndex = reader->mElemReadIndex; // start at current element + + for (i=(reader->mValueReadIndex+1); i < ArrayLength(reader->mElementArray); i++) + { + searchValueElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchValueElem->mParentIndex == reader->mElemReadIndex) + { + // check match + if (gsi_is_true(gsiXmlUtilTagMatches(matchtag, &searchValueElem->mName))) + { + reader->mValueReadIndex = i; + *valueOut = (const char*)searchValueElem->mValue.mData; + *lenOut = searchValueElem->mValue.mLen; + return gsi_true; + } + } + // bail if we've reached a higher branch + if (searchValueElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/* +gsi_bool gsXmlReadChildAsUnicodeString(GSXmlStreamReader stream, const char * matchtag, + gsi_char ** valueOut, int * lenOut) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + GSIXmlElement * searchValueElem = NULL; + int i=0; + + // Do we have a valid value position already? + if (reader->mValueReadIndex == -1) + reader->mValueReadIndex = reader->mElemReadIndex; // start at current element + + for (i=(reader->mValueReadIndex+1); i < ArrayLength(reader->mElementArray); i++) + { + searchValueElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchValueElem->mParentIndex == reader->mElemReadIndex) + { + // check match + if (gsi_is_true(gsiXmlUtilTagMatches(matchtag, &searchValueElem->mName))) + { + reader->mValueReadIndex = i; + if (searchValueElem->mValue.mData == NULL) + { + *valueOut = NULL; + *lenOut = 0; + return gsi_true; + } + *lenOut = UTF8ToUCS2StringLen((const char *)searchValueElem->mValue.mData, (unsigned short *) *valueOut, searchValueElem->mValue.mLen); + return gsi_true; + } + } + // bail if we've reached a higher branch + if (searchValueElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +}*/ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Same as read child as string, but copies into valueOut and null terminates +gsi_bool gsXmlReadChildAsStringNT(GSXmlStreamReader stream, const char * matchtag, char valueOut[], int maxLen) +{ + const char * strValue = NULL; + int strLen = 0; + + if (gsi_is_false(gsXmlReadChildAsString(stream, matchtag, &strValue, &strLen))) + { + valueOut[0] = '\0'; + return gsi_false; + } + else + { + strncpy(valueOut, strValue, (size_t)min(maxLen, strLen)); + valueOut[min(maxLen-1, strLen)] = '\0'; + return gsi_true; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Same as readChildAsStringNT, but converts Ascii/UTF-8 to USC2 +gsi_bool gsXmlReadChildAsUnicodeStringNT(GSXmlStreamReader stream, const char * matchtag, gsi_char valueOut[], int maxLen) +{ + const char * utf8Value = NULL; + int unicodeLen = 0; + int utf8Len = 0; + + if (gsi_is_false(gsXmlReadChildAsString(stream, matchtag, &utf8Value, &utf8Len))) + { + valueOut[0] = '\0'; + return gsi_false; + } + else + { + // Convert into destination buffer + unicodeLen = UTF8ToUCS2StringLen(utf8Value, (unsigned short *)valueOut, utf8Len); + valueOut[min(maxLen-1, unicodeLen)] = '\0'; + return gsi_true; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlReadChildAsHexBinary(GSXmlStreamReader stream, const char * matchtag, + gsi_u8 valueOut[], int maxLen, int * lenOut) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + GSIXmlElement * searchValueElem = NULL; + int i=0; + + // Do we have a valid value position already? + if (reader->mValueReadIndex == -1) + reader->mValueReadIndex = reader->mElemReadIndex; // start at current element + + for (i=(reader->mValueReadIndex+1); i < ArrayLength(reader->mElementArray); i++) + { + searchValueElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchValueElem->mParentIndex == reader->mElemReadIndex) + { + // check match + if (gsi_is_true(gsiXmlUtilTagMatches(matchtag, &searchValueElem->mName))) + { + // switch endianess, e.g. first character in hexstring is HI byte + gsi_u32 temp = 0; + int writepos = 0; + int readpos = 0; + int bytesleft = min(maxLen*2, searchValueElem->mValue.mLen); + + // special case: zero length value + if (searchValueElem->mValue.mLen == 0 || searchValueElem->mValue.mData == NULL) + { + valueOut[0] = 0; + *lenOut = 0; + return gsi_true; + } + + // Checking length only? + if (valueOut == NULL) + { + *lenOut = searchValueElem->mValue.mLen / 2; + + // note: read position left at this elemtent so next read can record the data + return gsi_true; + } + + // 2 characters of hexbyte = 1 value byte + while(bytesleft > 1) + { + sscanf((char*)(&searchValueElem->mValue.mData[readpos]), "%02x", &temp); // sscanf requires a 4 byte dest + valueOut[writepos] = (gsi_u8)temp; // then we convert to byte, to ensure correct byte order + readpos += 2; + writepos += 1; + bytesleft -= 2; + } + if (bytesleft == 1) + { + sscanf((char*)(&searchValueElem->mValue.mData[readpos]), "%01x", &temp); // sscanf requires a 4 byte dest + valueOut[writepos] = (gsi_u8)temp; // then we convert to byte, to ensure correct byte order + readpos += 1; + writepos += 1; + bytesleft -= 1; + } + if (lenOut != NULL) + *lenOut = writepos; + + reader->mValueReadIndex = i; // mark that this element was read + return gsi_true; + } + } + // bail if we've reached a higher branch + if (searchValueElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlReadChildAsBase64Binary(GSXmlStreamReader stream, const char * matchtag, gsi_u8 valueOut[], int * lenOut) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + GSIXmlElement * searchValueElem = NULL; + int i=0; + + // Do we have a valid value position already? + if (reader->mValueReadIndex == -1) + reader->mValueReadIndex = reader->mElemReadIndex; // start at current element + + for (i=(reader->mValueReadIndex+1); i < ArrayLength(reader->mElementArray); i++) + { + searchValueElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchValueElem->mParentIndex == reader->mElemReadIndex) + { + // check match + if (gsi_is_true(gsiXmlUtilTagMatches(matchtag, &searchValueElem->mName))) + { + if(valueOut) + { + reader->mValueReadIndex = i; + if(searchValueElem->mValue.mData) + B64Decode((char*)searchValueElem->mValue.mData, (char*)valueOut, searchValueElem->mValue.mLen, lenOut, GS_XML_BASE64_ENCODING_TYPE); + else + *lenOut = 0; + } + else + { + if(searchValueElem->mValue.mData) + *lenOut = B64DecodeLen((const char*)searchValueElem->mValue.mData, GS_XML_BASE64_ENCODING_TYPE); + else + *lenOut = 0; + } + return gsi_true; + } + } + // bail if we've reached a higher branch + if (searchValueElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlReadChildAsInt (GSXmlStreamReader stream, const char * matchtag, + int * valueOut) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + GSIXmlElement * searchValueElem = NULL; + int i=0; + + // Do we have a valid value position already? + if (reader->mValueReadIndex == -1) + reader->mValueReadIndex = reader->mElemReadIndex; // start at current element + + for (i=(reader->mValueReadIndex+1); i < ArrayLength(reader->mElementArray); i++) + { + searchValueElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchValueElem->mParentIndex == reader->mElemReadIndex) + { + // check match + if (gsi_is_true(gsiXmlUtilTagMatches(matchtag, &searchValueElem->mName))) + { + reader->mValueReadIndex = i; + if (searchValueElem->mValue.mData == NULL) + return gsi_false; // invalid type! + *valueOut = atoi((const char*)searchValueElem->mValue.mData); + return gsi_true; + } + } + // bail if we've reached a higher branch + if (searchValueElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlReadChildAsInt64(GSXmlStreamReader stream, const char * matchtag, + gsi_i64 * valueOut) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + GSIXmlElement * searchValueElem = NULL; + int i=0; + + // Do we have a valid value position already? + if (reader->mValueReadIndex == -1) + reader->mValueReadIndex = reader->mElemReadIndex; // start at current element + + for (i=(reader->mValueReadIndex+1); i < ArrayLength(reader->mElementArray); i++) + { + searchValueElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchValueElem->mParentIndex == reader->mElemReadIndex) + { + // check match + if (gsi_is_true(gsiXmlUtilTagMatches(matchtag, &searchValueElem->mName))) + { + reader->mValueReadIndex = i; + if (searchValueElem->mValue.mData == NULL) + return gsi_false; // invalid type! + *valueOut = gsiStringToInt64((const char*)searchValueElem->mValue.mData); + return gsi_true; + } + } + // bail if we've reached a higher branch + if (searchValueElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlReadChildAsDateTimeElement (GSXmlStreamReader stream, const char * matchtag, + time_t * valueOut) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + GSIXmlElement * searchValueElem = NULL; + int i=0; + struct tm timePtr; + + + // Do we have a valid value position already? + if (reader->mValueReadIndex == -1) + reader->mValueReadIndex = reader->mElemReadIndex; // start at current element + + for (i=(reader->mValueReadIndex+1); i < ArrayLength(reader->mElementArray); i++) + { + searchValueElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchValueElem->mParentIndex == reader->mElemReadIndex) + { + // check match + if (gsi_is_true(gsiXmlUtilTagMatches(matchtag, &searchValueElem->mName))) + { + reader->mValueReadIndex = i; + if (searchValueElem->mValue.mData == NULL) + return gsi_false; // invalid type! + + // convert the time to from a string to a time struct + sscanf((const char*)searchValueElem->mValue.mData, "%i-%02d-%02dT%02d:%02d:%02d", + &timePtr.tm_year, &timePtr.tm_mon, &timePtr.tm_mday, + &timePtr.tm_hour, &timePtr.tm_min, &timePtr.tm_sec); + + timePtr.tm_year -= 1900; + timePtr.tm_mon -= 1; + timePtr.tm_isdst = -1; + *valueOut = gsiDateToSeconds(&timePtr); + + return gsi_true; + } + } + // bail if we've reached a higher branch + if (searchValueElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlReadChildAsFloat (GSXmlStreamReader stream, const char * matchtag, + float * valueOut) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + GSIXmlElement * searchValueElem = NULL; + int i=0; + + // Do we have a valid value position already? + if (reader->mValueReadIndex == -1) + reader->mValueReadIndex = reader->mElemReadIndex; // start at current element + + for (i=(reader->mValueReadIndex+1); i < ArrayLength(reader->mElementArray); i++) + { + searchValueElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchValueElem->mParentIndex == reader->mElemReadIndex) + { + // check match + if (gsi_is_true(gsiXmlUtilTagMatches(matchtag, &searchValueElem->mName))) + { + reader->mValueReadIndex = i; + if (searchValueElem->mValue.mData == NULL) + return gsi_false; // invalid type! + *valueOut = (float)atof((const char*)searchValueElem->mValue.mData); + return gsi_true; + } + } + // bail if we've reached a higher branch + if (searchValueElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Compare only the text following the namespace character +static gsi_bool gsiXmlUtilTagMatches(const char * matchtag, GSIXmlString * xmlstr) +{ + const char * matchNoNamespace = NULL; + GSIXmlString xmlNoNamespace; + int xmlNamespacePos=0; + + GS_ASSERT(xmlstr != NULL); + + if (matchtag == NULL) + return gsi_true; + if (matchtag[strlen(matchtag)-1] == ':') + return gsi_false; // illegal to end with ':' + + // find post-namespace positions + matchNoNamespace = strchr(matchtag, ':'); + if (matchNoNamespace == NULL) + matchNoNamespace = matchtag; + + while(xmlNamespacePos < xmlstr->mLen && xmlstr->mData[xmlNamespacePos] != ':') + xmlNamespacePos++; + if (xmlNamespacePos == xmlstr->mLen) + xmlNamespacePos=0; + else + xmlNamespacePos++; // add one more to skip over the ':' + xmlNoNamespace.mData = xmlstr->mData + xmlNamespacePos; + xmlNoNamespace.mLen = xmlstr->mLen - xmlNamespacePos; + + // compare strings + if (0 == strncmp(matchNoNamespace, (const char*)xmlNoNamespace.mData, (size_t)xmlNoNamespace.mLen)) + return gsi_true; + else + return gsi_false; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Reads childs as bin-endian hexbinary, then converts to little-endian large int +gsi_bool gsXmlReadChildAsLargeInt(GSXmlStreamReader stream, const char * matchtag, struct gsLargeInt_s * valueOut) +{ + int len = 0; + + // set to zero + memset(valueOut, 0, sizeof(struct gsLargeInt_s)); + + // parse the hexbinary + if (gsi_is_false(gsXmlReadChildAsHexBinary(stream, matchtag, (gsi_u8*)valueOut->mData, GS_LARGEINT_BINARY_SIZE/8*2, &len))) + return gsi_false; + + // save off length + valueOut->mLength = (l_word)(len/GS_LARGEINT_DIGIT_SIZE_BYTES); + if (len%GS_LARGEINT_DIGIT_SIZE_BYTES != 0) + valueOut->mLength++; + + // reverse byte order + if (gsi_is_false(gsLargeIntReverseBytes(valueOut))) + return gsi_false; + else + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Resets the child read position to the first child of the current element +// +gsi_bool gsXmlResetChildReadPosition(GSXmlStreamReader stream) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + reader->mValueReadIndex = -1; // no current child position means start at first + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Count the number of children with the tag +// If the tag is NULL, count all the children +// Only counts direct children, not grandchildren or lower +int gsXmlCountChildren(GSXmlStreamReader stream, const char * matchtag) +{ + GSIXmlStreamReader * reader = (GSIXmlStreamReader*)stream; + GSIXmlElement * searchElem = NULL; + int i=0; + int count=0; + + for (i=(reader->mElemReadIndex+1); i < ArrayLength(reader->mElementArray); i++) + { + searchElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchElem->mParentIndex == reader->mElemReadIndex) + { + // check match + if ((matchtag == NULL) || gsi_is_true(gsiXmlUtilTagMatches(matchtag, &searchElem->mName))) + { + count++; + } + } + // check if we've reached a higher branch + // -- we know this when we've reached an element whose + // parent is above our level in the tree + else if (searchElem->mParentIndex < reader->mElemReadIndex) + break; + } + return count; +} + + + diff --git a/code/gamespy/common/gsXML.h b/code/gamespy/common/gsXML.h new file mode 100644 index 00000000..6f9a0c72 --- /dev/null +++ b/code/gamespy/common/gsXML.h @@ -0,0 +1,127 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __GSXML_H__ +#define __GSXML_H__ + + +#include "gsPlatform.h" +#include "gsLargeInt.h" // so that it can write large ints + + +#if defined(__cplusplus) +extern "C" +{ +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// GameSpy XML parser for soap messages +// Create the stream object and attach to an XML text buffer. +// The stream will not modify the buffer. +// The buffer should not be released until after the stream is destroyed +// +// +// Limitations: +// Processing instructions other than ' + +#define _REENTRANT + +#define PTHREAD_NO_ERROR 0 + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// These functions are unsupported in the current version of the SDK + +gsi_u32 gsiInterlockedIncrement(gsi_u32 * value) +{ + GS_ASSERT_STR(gsi_false, "gsiInterlockIncrement is unsupported for LINUX in the current version of the SDK\n"); + return 1; +} + +gsi_u32 gsiInterlockedDecrement(gsi_u32 * value) +{ + GS_ASSERT_STR(gsi_false, "gsiInterlockIncrement is unsupported for LINUX in the current version of the SDK\n"); + return 1; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +int gsiStartThread(GSThreadFunc func, gsi_u32 theStackSize, void *arg, GSIThreadID * id) +{ + pthread_attr_init(&id->attr); + pthread_attr_setstacksize(&id->attr, theStackSize); + + if (pthread_create(&id->thread, &id->attr, (void *)func, arg) != PTHREAD_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to create thread\r\n"); + return -1; + } + + return 0; +} + +void gsiCancelThread(GSIThreadID id) +{ + //should i destroy the attributes here? + pthread_attr_destroy(&id.attr); + + if (pthread_cancel(id.thread) != PTHREAD_NO_ERROR) { + //there was an error - how should we handle these? or should we? + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to cancel thread\r\n"); + } + //free up memory and set to NULL + + gsifree(&id.thread); +} + +// This must be called from INSIDE the thread you wish to exit +void gsiExitThread(GSIThreadID id) +{ + // detach the thread so that it knows to free resources upon exit + pthread_detach(id.thread); + + // exit thread to free up resources + pthread_exit(NULL); +} + +void gsiCleanupThread(GSIThreadID id) +{ + // destroy any leftover attributes associated with the thread + pthread_attr_destroy(&id.attr); +} + +gsi_u32 gsiHasThreadShutdown(GSIThreadID id) +{ + // pthreads lacks detection mechanism for this + GSI_UNUSED(id); + return 1; +} + +void gsiInitializeCriticalSection(GSICriticalSection *theCrit) +{ + if (pthread_mutex_init(theCrit, NULL) != PTHREAD_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to initialize critical section\r\n"); + } +} + +void gsiEnterCriticalSection(GSICriticalSection *theCrit) +{ + if (pthread_mutex_lock(theCrit) != PTHREAD_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to lock mutex for entering critical section\r\n"); + } +} + +void gsiLeaveCriticalSection(GSICriticalSection *theCrit) +{ + if (pthread_mutex_unlock(theCrit) != PTHREAD_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to unlock mutex for leaving critical section\r\n"); + } +} + +void gsiDeleteCriticalSection(GSICriticalSection *theCrit) +{ + if (pthread_mutex_destroy(theCrit) != PTHREAD_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to destroy mutex\r\n"); + } + theCrit = NULL; +} + +GSISemaphoreID gsiCreateSemaphore(gsi_i32 theInitialCount, gsi_i32 theMaxCount, char* theName) +{ + int result; + GSISemaphoreID semaphore; + + //we can use the default attributes for the mutex by passing NULL + result = pthread_mutex_init(&semaphore.mLock, NULL); + + if (result != PTHREAD_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to create semaphore\r\n"); + } + + semaphore.mValue = theInitialCount; + semaphore.mMax = theMaxCount; + + GSI_UNUSED(theName); + return semaphore; +} + +// Waits for -- and signals -- the semaphore +gsi_u32 gsiWaitForSemaphore(GSISemaphoreID theSemaphore, gsi_u32 theTimeoutMs) +{ + gsi_time startTime = current_time(); + gsi_bool infinite = (theTimeoutMs == GSI_INFINITE)?gsi_true:gsi_false; + + do + { + //try to lock, if it doesn't then its busy + if(pthread_mutex_trylock(&theSemaphore.mLock) == PTHREAD_NO_ERROR) + { + if(theSemaphore.mValue > 0) + { + theSemaphore.mValue--; + pthread_mutex_unlock(&theSemaphore.mLock); + return 1; + } + + pthread_mutex_unlock(&theSemaphore.mLock); + } + + if(theTimeoutMs != 0) + msleep(2); + + } while(gsi_is_true(infinite) || ((current_time() - startTime) < theTimeoutMs)); + + return 0; +} + +// Allow other objects to access the semaphore +void gsiReleaseSemaphore(GSISemaphoreID theSemaphore, gsi_i32 theReleaseCount) +{ + if(pthread_mutex_trylock(&theSemaphore.mLock) != PTHREAD_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to lock semaphore\r\n"); + + GSI_UNUSED(theReleaseCount); + } + else + { + theSemaphore.mValue += theReleaseCount; + if(theSemaphore.mValue > theSemaphore.mMax) + theSemaphore.mValue = theSemaphore.mMax; + + pthread_mutex_unlock(&theSemaphore.mLock); + } +} + +void gsiCloseSemaphore(GSISemaphoreID theSemaphore) +{ + if (pthread_mutex_destroy(&theSemaphore.mLock) != PTHREAD_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to destroy semaphore\r\n"); + GSI_UNUSED(theSemaphore); + } + + //need to free up memory + gsifree(&theSemaphore.mValue); + gsifree(&theSemaphore.mMax); + gsifree(&theSemaphore); +} + + diff --git a/code/gamespy/common/linux/gsUtilLinux.c b/code/gamespy/common/linux/gsUtilLinux.c new file mode 100644 index 00000000..537b7426 --- /dev/null +++ b/code/gamespy/common/linux/gsUtilLinux.c @@ -0,0 +1,17 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gsCommon.h" + +gsi_i64 gsiStringToInt64(const char *theNumberStr) +{ + return atoll(theNumberStr); +} + +void gsiInt64ToString(char theNumberStr[33], gsi_i64 theNumber) +{ + // you want to fit the number! + // give me a valid string! + GS_ASSERT(theNumberStr != NULL); + + sprintf(theNumberStr, "%lld", theNumber); +} \ No newline at end of file diff --git a/code/gamespy/common/macosx/MacOSXCommon.c b/code/gamespy/common/macosx/MacOSXCommon.c new file mode 100644 index 00000000..d4abb55e --- /dev/null +++ b/code/gamespy/common/macosx/MacOSXCommon.c @@ -0,0 +1,47 @@ +#include "../gsCommon.h" +#include "../gsMemory.h" +#include "../gsDebug.h" + +// sample common entry point +extern int test_main(int argc, char ** argp); + +// Debug output +#ifdef GSI_COMMON_DEBUG +static void DebugCallback(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel, const char * theTokenStr, + va_list theParamList) + +{ + GSI_UNUSED(theLevel); + { + static char string[256]; + vsprintf(string, theTokenStr, theParamList); + printf(string); + } + printf("[%s][%s] ", + gGSIDebugCatStrings[theCat], + gGSIDebugTypeStrings[theType]); + + vprintf(theTokenStr, theParamList); +} +#endif + +// Common entry point +int main(int argc, char** argp) +{ + int ret = 0; + // set up memanager + //void *heap = (void*)gsiMemManagedInit(); + +#ifdef GSI_COMMON_DEBUG + // Set up debugging + gsSetDebugCallback(DebugCallback); + gsSetDebugLevel(GSIDebugCat_All, GSIDebugType_All, GSIDebugLevel_Verbose); +#endif + + ret = test_main(argc, argp); + + //gsiMemManagedClose(heap); + + return ret; +} diff --git a/code/gamespy/common/macosx/changelog.txt b/code/gamespy/common/macosx/changelog.txt new file mode 100644 index 00000000..132e9c76 --- /dev/null +++ b/code/gamespy/common/macosx/changelog.txt @@ -0,0 +1,15 @@ +Changelog for: MacOSX Common Code +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +06-03-2005 1.00.03 SN RELEASE Releasing to developer site. +04-28-2005 1.00.03 SN RELEASE Releasing to developer site. +04-04-2005 1.00.03 SN RELEASE Releasing to developer site. +09-30-2004 1.00.03 DES FEATURE Makefile.common can now include defines set in SDK Makefiles. + DES FEATURE Added optional optimization flags to Makefile.common. +09-16-2004 1.00.02 SN RELEASE Releasing to developer site. +08-27-2004 1.00.02 DES FEATURE Makefile also builds unicode versions (where applicable) + DES FEATURE Added a Makefile.common that us used by all other OSX makefiles +08-25-2004 1.00.01 DES FEATURE Added a makefile to build all the test apps on MacOS X. +08-24-2004 1.00.00 DES OTHER Changelog started diff --git a/code/gamespy/common/macosx/gsThreadMacOSX.c b/code/gamespy/common/macosx/gsThreadMacOSX.c new file mode 100644 index 00000000..e0522a5b --- /dev/null +++ b/code/gamespy/common/macosx/gsThreadMacOSX.c @@ -0,0 +1,213 @@ +// +// Mac OSX Threading Support (pthreads) +// *same as Linux* +// +// NOTE: when implementing this make sure the "-lpthread" compiler option is used +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gsPlatformUtil.h" +#include "../gsPlatformThread.h" +#include "../gsAssert.h" +#include "../gsDebug.h" +#include + +#define _REENTRANT + +#define PTHREAD_NO_ERROR 0 + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// These functions are unsupported in the current version of the SDK + +gsi_u32 gsiInterlockedIncrement(gsi_u32 * value) +{ + GS_ASSERT_STR(gsi_false, "gsiInterlockIncrement is unsupported for Mac OSX in the current version of the SDK\n"); + return 1; +} + +gsi_u32 gsiInterlockedDecrement(gsi_u32 * value) +{ + GS_ASSERT_STR(gsi_false, "gsiInterlockIncrement is unsupported for Mac OSX in the current version of the SDK\n"); + return 1; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +int gsiStartThread(GSThreadFunc func, gsi_u32 theStackSize, void *arg, GSIThreadID * id) +{ + pthread_attr_init(&id->attr); + pthread_attr_setstacksize(&id->attr, theStackSize); + + if (pthread_create(&id->thread, &id->attr, (void *)func, arg) != PTHREAD_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to create thread\r\n"); + return -1; + } + + return 0; +} + +void gsiCancelThread(GSIThreadID id) +{ + //should i destroy the attributes here? + pthread_attr_destroy(&id.attr); + + if (pthread_cancel(id.thread) != PTHREAD_NO_ERROR) { + //there was an error - how should we handle these? or should we? + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to cancel thread\r\n"); + } + //free up memory and set to NULL + + gsifree(&id.thread); +} + +// This must be called from INSIDE the thread you wish to exit +void gsiExitThread(GSIThreadID id) +{ + // detach the thread so that it knows to free resources upon exit + pthread_detach(id.thread); + + // exit thread to free up resources + pthread_exit(NULL); +} + +void gsiCleanupThread(GSIThreadID id) +{ + // destroy any leftover attributes associated with the thread + pthread_attr_destroy(&id.attr); +} + +gsi_u32 gsiHasThreadShutdown(GSIThreadID id) +{ + // pthreads lacks detection mechanism for this + GSI_UNUSED(id); + return 1; +} + +void gsiInitializeCriticalSection(GSICriticalSection *theCrit) +{ + if (pthread_mutex_init(theCrit, NULL) != PTHREAD_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to initialize critical section\r\n"); + } +} + +void gsiEnterCriticalSection(GSICriticalSection *theCrit) +{ + if (pthread_mutex_lock(theCrit) != PTHREAD_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to lock mutex for entering critical section\r\n"); + } +} + +void gsiLeaveCriticalSection(GSICriticalSection *theCrit) +{ + if (pthread_mutex_unlock(theCrit) != PTHREAD_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to unlock mutex for leaving critical section\r\n"); + } +} + +void gsiDeleteCriticalSection(GSICriticalSection *theCrit) +{ + if (pthread_mutex_destroy(theCrit) != PTHREAD_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to destroy mutex\r\n"); + } + theCrit = NULL; +} + +GSISemaphoreID gsiCreateSemaphore(gsi_i32 theInitialCount, gsi_i32 theMaxCount, char* theName) +{ + int result; + GSISemaphoreID semaphore; + + //we can use the default attributes for the mutex by passing NULL + result = pthread_mutex_init(&semaphore.mLock, NULL); + + if (result != PTHREAD_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to create semaphore\r\n"); + } + + semaphore.mValue = theInitialCount; + semaphore.mMax = theMaxCount; + + GSI_UNUSED(theName); + return semaphore; +} + +// Waits for -- and signals -- the semaphore +gsi_u32 gsiWaitForSemaphore(GSISemaphoreID theSemaphore, gsi_u32 theTimeoutMs) +{ + gsi_time startTime = current_time(); + gsi_bool infinite = (theTimeoutMs == GSI_INFINITE)?gsi_true:gsi_false; + + do + { + //try to lock, if it doesn't then its busy + if(pthread_mutex_trylock(&theSemaphore.mLock) == PTHREAD_NO_ERROR) + { + if(theSemaphore.mValue > 0) + { + theSemaphore.mValue--; + pthread_mutex_unlock(&theSemaphore.mLock); + return 1; + } + + pthread_mutex_unlock(&theSemaphore.mLock); + } + + if(theTimeoutMs != 0) + msleep(2); + + } while(gsi_is_true(infinite) || ((current_time() - startTime) < theTimeoutMs)); + + return 0; +} + +// Allow other objects to access the semaphore +void gsiReleaseSemaphore(GSISemaphoreID theSemaphore, gsi_i32 theReleaseCount) +{ + if(pthread_mutex_trylock(&theSemaphore.mLock) != PTHREAD_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to lock semaphore\r\n"); + + GSI_UNUSED(theReleaseCount); + } + else + { + theSemaphore.mValue += theReleaseCount; + if(theSemaphore.mValue > theSemaphore.mMax) + theSemaphore.mValue = theSemaphore.mMax; + + pthread_mutex_unlock(&theSemaphore.mLock); + } +} + +void gsiCloseSemaphore(GSISemaphoreID theSemaphore) +{ + if (pthread_mutex_destroy(&theSemaphore.mLock) != PTHREAD_NO_ERROR) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to destroy semaphore\r\n"); + GSI_UNUSED(theSemaphore); + } + + //need to free up memory + gsifree(&theSemaphore.mValue); + gsifree(&theSemaphore.mMax); + gsifree(&theSemaphore); +} + + diff --git a/code/gamespy/common/macosx/gsUtilMacOSX.c b/code/gamespy/common/macosx/gsUtilMacOSX.c new file mode 100644 index 00000000..a5e038cc --- /dev/null +++ b/code/gamespy/common/macosx/gsUtilMacOSX.c @@ -0,0 +1,17 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gsCommon.h" + +gsi_i64 gsiStringToInt64(const char *theNumberStr) +{ + return atoll(theNumberStr); +} + +void gsiInt64ToString(char theNumberStr[33], gsi_i64 theNumber) +{ + // you want to fit the number! + // give me a valid string! + GS_ASSERT(theNumberStr != NULL); + + sprintf(theNumberStr, "%lld", theNumber); +} \ No newline at end of file diff --git a/code/gamespy/common/nitro/backup.c b/code/gamespy/common/nitro/backup.c new file mode 100644 index 00000000..6b5200dc --- /dev/null +++ b/code/gamespy/common/nitro/backup.c @@ -0,0 +1,185 @@ +#include "..\nonport.h" +#include "backup.h" +#include "screen.h" + +//#define SUPPORT_FLASH +//#define SUPPORT_FRAM + +static const CARDBackupType BackupTypes[] = +{ + CARD_BACKUP_TYPE_EEPROM_4KBITS, + CARD_BACKUP_TYPE_EEPROM_64KBITS, + CARD_BACKUP_TYPE_EEPROM_512KBITS, +#if defined(SUPPORT_FLASH) + CARD_BACKUP_TYPE_FLASH_2MBITS, +#endif +#if defined(SUPPORT_FRAM) + CARD_BACKUP_TYPE_FRAM_256KBITS, +#endif +}; +static const char * BackupTypeDescriptions[] = +{ + "EEPROM 4 kb", + "EEPROM 64 kb", + "EEPROM 512 kb", +#if defined(SUPPORT_FLASH) + "FLASH 2 mb", +#endif +#if defined(SUPPORT_FRAM) + "FRAM 256 kb" +#endif +}; +static const int NumBackupTypes = (sizeof(BackupTypes) / sizeof(BackupTypes[0])); +static const CARDBackupType NullBackupType = CARD_BACKUP_TYPE_NOT_USE; + +static CARDBackupType InstalledBackupType; +static int InstalledBackupTypeIndex; + +static u16 LockID; + +static BOOL TestForBackupType(int index) +{ + const CARDBackupType type = BackupTypes[index]; + u32 totalSize; + BOOL result; + u8 buffer; + + Printf("Testing for %s\n", BackupTypeDescriptions[index]); + + CARD_LockBackup(LockID); + { + CARD_IdentifyBackup(type); + + totalSize = CARD_GetBackupTotalSize(); + + // set the buffer + buffer = (u8)(rand() & 0xFF); + + // write the buffer to the card + if(CARD_IsBackupEeprom()) + result = CARD_WriteAndVerifyEeprom(totalSize - 1, &buffer, 1); + else if(CARD_IsBackupFlash()) + result = CARD_WriteAndVerifyFlash(totalSize - 1, &buffer, 1); + else if(CARD_IsBackupFram()) + result = CARD_WriteAndVerifyFram(totalSize - 1, &buffer, 1); + } + CARD_UnlockBackup(LockID); + + return result; +} + +static void DetermineBackupType(void) +{ + int i; + + // determine what, if any, backup type is installed + for(i = 0 ; i < NumBackupTypes ; i++) + { + if(TestForBackupType(i) == TRUE) + { + InstalledBackupType = BackupTypes[i]; + InstalledBackupTypeIndex = i; + return; + } + } + + InstalledBackupType = NullBackupType; + InstalledBackupTypeIndex = -1; +} + +void BackupInit(void) +{ + // get an id for locking the card + s32 lockID = OS_GetLockID(); + if(lockID == OS_LOCK_ID_ERROR) + OS_Panic("OS_GetLockID() failed\n"); + LockID = (u16)lockID; + + // figure out the backup type + DetermineBackupType(); + + // make sure we have the right type identified + if(BackupExists()) + { + CARD_LockBackup(LockID); + { + CARD_IdentifyBackup(InstalledBackupType); + } + CARD_UnlockBackup(LockID); + } + + // show what we detected + if(BackupExists()) + Printf("Backup type: %s\n", BackupTypeDescriptions[InstalledBackupTypeIndex]); + else + Printf("No backup card detected\n"); +} + +BOOL BackupExists(void) +{ + return (InstalledBackupType != NullBackupType)?TRUE:FALSE; +} + +static const int StartPos = 32; + +BOOL WriteToBackup(const void * src, int len) +{ + BOOL result; + + if(!BackupExists()) + return FALSE; + + CARD_LockBackup(LockID); + + if(CARD_IsBackupEeprom()) + result = CARD_WriteAndVerifyEeprom(StartPos, src, (u32)len); + else if(CARD_IsBackupFlash()) + result = CARD_WriteAndVerifyFlash(StartPos, src, (u32)len); + else if(CARD_IsBackupFram()) + result = CARD_WriteAndVerifyFram(StartPos, src, (u32)len); + else + result = FALSE; + + CARD_UnlockBackup(LockID); + + if(result == FALSE) + { + u32 code = CARD_GetResultCode(); + Printf("CARD-WRITE-FAILURE: %d\n", code); + while(1) + ; + } + + return result; +} + +BOOL ReadFromBackup(void * dst, int len) +{ + BOOL result; + + if(!BackupExists()) + return FALSE; + + CARD_LockBackup(LockID); + + if(CARD_IsBackupEeprom()) + result = CARD_ReadEeprom(StartPos, dst, (u32)len); + else if(CARD_IsBackupFlash()) + result = CARD_ReadFlash(StartPos, dst, (u32)len); + else if(CARD_IsBackupFram()) + result = CARD_ReadFram(StartPos, dst, (u32)len); + else + result = FALSE; + + CARD_UnlockBackup(LockID); + + if(result == FALSE) + { + u32 code = CARD_GetResultCode(); + Printf("CARD-READ-FAILURE: %d\n", code); + while(1) + ; + } + + return result; +} \ No newline at end of file diff --git a/code/gamespy/common/nitro/backup.h b/code/gamespy/common/nitro/backup.h new file mode 100644 index 00000000..b5a7854d --- /dev/null +++ b/code/gamespy/common/nitro/backup.h @@ -0,0 +1,11 @@ +#ifndef _BACKUP_H_ +#define _BACKUP_H_ + +void BackupInit(void); + +BOOL BackupExists(void); + +BOOL WriteToBackup(const void * src, int len); +BOOL ReadFromBackup(void * dst, int len); + +#endif \ No newline at end of file diff --git a/code/gamespy/common/nitro/changelog.txt b/code/gamespy/common/nitro/changelog.txt new file mode 100644 index 00000000..fabc5db8 --- /dev/null +++ b/code/gamespy/common/nitro/changelog.txt @@ -0,0 +1,28 @@ +Changelog for: Nitro (Nintendo DS) Common Code +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +05-17-2006 1.00.05 DES UPDATE Updated to use gsTestMain + DES FEATURE Added gmtime and ctime + DES FEATURE Added gsiInterlockedIncrement/Decrement +11-17-2005 1.00.04 DES FIX Updated sample project and common workspace. + DES CLEANUP Now using SOC_ instead of SO_. +09-21-2005 1.00.03 DES FEATURE Added support for more backup types + DES FIX Updated thread support + DES FIX nitrosample now uses the gmtest gamename +06-03-2005 1.00.02 SN RELEASE Releasing to developer site. +04-28-2005 1.00.02 SN RELEASE Releasing to developer site. +04-27-2005 1.00.02 DES RELEASE Limited release to Nintendo DS developers. +04-27-2005 1.00.02 DES CLEANUP The backup code now only checks for flash backup + if SUPPORT_FLASH is defined. + DES FEATURE Cosmetic changes to nitrosample and launcher code. + DES FIX Changed SDK_IRQ_STACKSIZE in nitro.lcf files to 4096. + DES FEATURE printf()'s can now be directed to the screen and/or the debugger. + DES FEATURE Updates to work with the latest NitroInet. + DES FEATURE Added nitrocommon.cww, workspace for CW with all test apps. +04-11-2005 1.00.01 DES CLEANUP Removed 232-bit WEP key, which is no longer supported by Nitro. + DES FEATURE Show a default network config if no backup memory is detected. + DES FIX Fixed bug with showing the failure reason in nitrosample. + DES FIX Fixed bug in handling Peer errors in nitrosample. +04-04-2005 1.00.00 DES OTHER Changelog started diff --git a/code/gamespy/common/nitro/font.c b/code/gamespy/common/nitro/font.c new file mode 100644 index 00000000..58613576 --- /dev/null +++ b/code/gamespy/common/nitro/font.c @@ -0,0 +1,587 @@ +/*---------------------------------------------------------------------------* + Project: NitroSDK - RTC - demos + File: font.c + + Copyright 2003,2004 Nintendo. All rights reserved. + + These coded instructions, statements, and computer programs contain + proprietary information of Nintendo of America Inc. and/or Nintendo + Company Ltd., and are protected by Federal copyright law. They may + not be disclosed to third parties or copied or duplicated in any form, + in whole or in part, without the prior written consent of Nintendo. + + $Log: font.c,v $ + Revision 1.2 2004/11/02 07:19:52 terui + Fixed spelling mistakes in comments. + + Revision 1.1 2004/05/12 02:38:49 terui + initial upload + + $NoKeywords: $ + *---------------------------------------------------------------------------*/ + +#include "font.h" + + +/*---------------------------------------------------------------------------* + Character data + *---------------------------------------------------------------------------*/ +const u32 d_CharData[ 8 * 256 ] = +{ + 0x00000000,0x00000000,0x00000000,0x00000000, // 0000h + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x01010010,0x01010010,0x00000110, // 0001h + 0x00011010,0x01100010,0x00000010,0x00000010, + 0x00000000,0x01011010,0x01010010,0x00010010, // 0002h + 0x00100010,0x00100010,0x00100001,0x00100001, + 0x00000000,0x01010001,0x01010001,0x01111111, // 0003h + 0x00000001,0x00000001,0x00000001,0x01111110, + 0x00000000,0x01010000,0x01111111,0x00100000, // 0004h + 0x00100000,0x00010000,0x00001000,0x00000110, + 0x00000000,0x01010000,0x01010100,0x00001010, // 0005h + 0x00010001,0x00100001,0x01000000,0x00000000, + 0x00000000,0x01011000,0x01011000,0x01111111, // 0006h + 0x00001000,0x00101010,0x01001010,0x01001001, + 0x00000000,0x01010010,0x01101111,0x01010010, // 0007h + 0x00010010,0x00010010,0x00010010,0x00001001, + 0x00000000,0x01010010,0x01011111,0x00000100, // 0008h + 0x00011111,0x00001000,0x00000001,0x00011110, + 0x00000000,0x01010000,0x01011000,0x00000110, // 0009h + 0x00000001,0x00000110,0x00011000,0x00100000, + 0x00000000,0x01010000,0x01111101,0x00010001, // 000ah + 0x00010001,0x00010001,0x00010001,0x00001010, + 0x00000000,0x01010000,0x01011110,0x00100000, // 000bh + 0x00000000,0x00000001,0x00000001,0x00111110, + 0x00000000,0x01010100,0x01011111,0x00001000, // 000ch + 0x00010000,0x00000001,0x00000001,0x00011110, + 0x00000000,0x01010001,0x01010001,0x00000001, // 000dh + 0x01000001,0x01000001,0x00100010,0x00011100, + 0x00000000,0x01010000,0x01111111,0x00011000, // 000eh + 0x00010100,0x00010100,0x00011000,0x00001100, + 0x00000000,0x01010010,0x01111111,0x00010010, // 000fh + 0x00010010,0x00000010,0x00000010,0x00111100, + 0x00000000,0x00001110,0x01010100,0x01010010, // 0010h + 0x00111111,0x00000100,0x00000100,0x00011000, + 0x00000000,0x01010100,0x01011111,0x00000100, // 0011h + 0x01110100,0x00000010,0x00001010,0x01110010, + 0x00000000,0x01010100,0x01011111,0x00000010, // 0012h + 0x00011110,0x00100001,0x00100000,0x00011110, + 0x00000000,0x01010000,0x01011100,0x00100011, // 0013h + 0x01000000,0x01000000,0x00100000,0x00011100, + 0x00000000,0x01010000,0x01111111,0x00010000, // 0014h + 0x00001000,0x00001000,0x00001000,0x00110000, + 0x00000000,0x01010010,0x01010010,0x00001100, // 0015h + 0x00000010,0x00000001,0x00000001,0x00111110, + 0x00000000,0x01010001,0x01111101,0x00010001, // 0016h + 0x00010001,0x00111001,0x01010101,0x00011001, + 0x00000000,0x01010100,0x01010011,0x01110010, // 0017h + 0x00010001,0x00010001,0x00001010,0x00000100, + 0x00000000,0x01011110,0x01011000,0x00000100, // 0018h + 0x00101001,0x01010001,0x01010001,0x00001100, + 0x00000000,0x01010000,0x01011100,0x00010010, // 0019h + 0x00010010,0x00100001,0x01000000,0x00000000, + 0x00000000,0x01011101,0x01010001,0x00111101, // 001ah + 0x00010001,0x00011001,0x00110101,0x00001001, + 0x00000000,0x01110001,0x01011101,0x00110001, // 001bh + 0x00010001,0x00111001,0x01010101,0x00011001, + 0x00000000,0x01110100,0x01010011,0x00110010, // 001ch + 0x00010001,0x00010001,0x00001010,0x00000100, + 0x00000000,0x01101110,0x01011000,0x00100100, // 001dh + 0x00101001,0x01010001,0x01010001,0x00001100, + 0x00000000,0x01110000,0x01011100,0x00110010, // 001eh + 0x00010010,0x00100001,0x01000000,0x00000000, + 0x00000000,0x01111101,0x01010001,0x00111101, // 001fh + 0x00010001,0x00011001,0x00110101,0x00001001, + 0x00000000,0x00000000,0x00000000,0x00000000, // 0020h + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00001000,0x00001000,0x00001000, // 0021h + 0x00001000,0x00001000,0x00000000,0x00001000, + 0x00000000,0x01101100,0x01001000,0x00100100, // 0022h + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00100100,0x01111111,0x00100100, // 0023h + 0x00100100,0x01111111,0x00010010,0x00010010, + 0x00000000,0x00001000,0x01111110,0x00001001, // 0024h + 0x00111110,0x01001000,0x00111111,0x00001000, + 0x00000000,0x01000010,0x00100101,0x00010010, // 0025h + 0x00001000,0x00100100,0x01010010,0x00100001, + 0x00000000,0x00001110,0x00010001,0x00001001, // 0026h + 0x01000110,0x00101001,0x00110001,0x01001110, + 0x00000000,0x00011000,0x00010000,0x00001000, // 0027h + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x01110000,0x00001000,0x00000100, // 0028h + 0x00000100,0x00000100,0x00001000,0x01110000, + 0x00000000,0x00000111,0x00001000,0x00010000, // 0029h + 0x00010000,0x00010000,0x00001000,0x00000111, + 0x00000000,0x00001000,0x01001001,0x00101010, // 002ah + 0x00011100,0x00101010,0x01001001,0x00001000, + 0x00000000,0x00001000,0x00001000,0x00001000, // 002bh + 0x01111111,0x00001000,0x00001000,0x00001000, + 0x00000000,0x00000000,0x00000000,0x00000000, // 002ch + 0x00000000,0x00001100,0x00001000,0x00000100, + 0x00000000,0x00000000,0x00000000,0x00000000, // 002dh + 0x01111111,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, // 002eh + 0x00000000,0x00000000,0x00000000,0x00001100, + 0x00000000,0x01000000,0x00100000,0x00010000, // 002fh + 0x00001000,0x00000100,0x00000010,0x00000001, + 0x00000000,0x00111110,0x01000001,0x01000001, // 0030h + 0x01000001,0x01000001,0x01000001,0x00111110, + 0x00000000,0x00011100,0x00010000,0x00010000, // 0031h + 0x00010000,0x00010000,0x00010000,0x00010000, + 0x00000000,0x00111110,0x01000001,0x01000000, // 0032h + 0x00111110,0x00000001,0x00000001,0x01111111, + 0x00000000,0x00111110,0x01000001,0x01000000, // 0033h + 0x00111110,0x01000000,0x01000001,0x00111110, + 0x00000000,0x00100000,0x00110000,0x00101000, // 0034h + 0x00100100,0x00100010,0x01111111,0x00100000, + 0x00000000,0x01111111,0x00000001,0x00111111, // 0035h + 0x01000000,0x01000000,0x01000001,0x00111110, + 0x00000000,0x00111110,0x00000001,0x00111111, // 0036h + 0x01000001,0x01000001,0x01000001,0x00111110, + 0x00000000,0x01111111,0x00100000,0x00100000, // 0037h + 0x00010000,0x00010000,0x00001000,0x00001000, + 0x00000000,0x00111110,0x01000001,0x01000001, // 0038h + 0x00111110,0x01000001,0x01000001,0x00111110, + 0x00000000,0x00111110,0x01000001,0x01000001, // 0039h + 0x01000001,0x01111110,0x01000000,0x00111110, + 0x00000000,0x00000000,0x00001100,0x00000000, // 003ah + 0x00000000,0x00000000,0x00001100,0x00000000, + 0x00000000,0x00000000,0x00001100,0x00000000, // 003bh + 0x00000000,0x00001100,0x00001000,0x00000100, + 0x00000000,0x01100000,0x00011000,0x00000110, // 003ch + 0x00000001,0x00000110,0x00011000,0x01100000, + 0x00000000,0x00000000,0x01111111,0x00000000, // 003dh + 0x00000000,0x00000000,0x01111111,0x00000000, + 0x00000000,0x00000011,0x00001100,0x00110000, // 003eh + 0x01000000,0x00110000,0x00001100,0x00000011, + 0x00000000,0x00111110,0x01000001,0x01000001, // 003fh + 0x00110000,0x00001000,0x00000000,0x00001000, + 0x00000000,0x00011100,0x00100010,0x01001001, // 0040h + 0x01010101,0x01010101,0x01010101,0x00111010, + 0x00000000,0x00001000,0x00010100,0x00010100, // 0041h + 0x00100010,0x00111110,0x01000001,0x01000001, + 0x00000000,0x00111111,0x01000001,0x01000001, // 0042h + 0x00111111,0x01000001,0x01000001,0x00111111, + 0x00000000,0x00111100,0x01000010,0x00000001, // 0043h + 0x00000001,0x00000001,0x01000010,0x00111100, + 0x00000000,0x00011111,0x00100001,0x01000001, // 0044h + 0x01000001,0x01000001,0x00100001,0x00011111, + 0x00000000,0x01111111,0x00000001,0x00000001, // 0045h + 0x01111111,0x00000001,0x00000001,0x01111111, + 0x00000000,0x01111111,0x00000001,0x00000001, // 0046h + 0x00111111,0x00000001,0x00000001,0x00000001, + 0x00000000,0x00111100,0x01000010,0x00000001, // 0047h + 0x01111001,0x01000001,0x01000010,0x00111100, + 0x00000000,0x01000001,0x01000001,0x01000001, // 0048h + 0x01111111,0x01000001,0x01000001,0x01000001, + 0x00000000,0x00111110,0x00001000,0x00001000, // 0049h + 0x00001000,0x00001000,0x00001000,0x00111110, + 0x00000000,0x01000000,0x01000000,0x01000000, // 004ah + 0x01000001,0x01000001,0x00100010,0x00011100, + 0x00000000,0x01100001,0x00011001,0x00000101, // 004bh + 0x00000011,0x00000101,0x00011001,0x01100001, + 0x00000000,0x00000001,0x00000001,0x00000001, // 004ch + 0x00000001,0x00000001,0x00000001,0x01111111, + 0x00000000,0x01000001,0x01100011,0x01010101, // 004dh + 0x01001001,0x01000001,0x01000001,0x01000001, + 0x00000000,0x01000001,0x01000011,0x01000101, // 004eh + 0x01001001,0x01010001,0x01100001,0x01000001, + 0x00000000,0x00011100,0x00100010,0x01000001, // 004fh + 0x01000001,0x01000001,0x00100010,0x00011100, + 0x00000000,0x00111111,0x01000001,0x01000001, // 0050h + 0x00111111,0x00000001,0x00000001,0x00000001, + 0x00000000,0x00011100,0x00100010,0x01000001, // 0051h + 0x01000001,0x01011001,0x00100010,0x01011100, + 0x00000000,0x00111111,0x01000001,0x01000001, // 0052h + 0x00111111,0x01000001,0x01000001,0x01000001, + 0x00000000,0x00111110,0x01000001,0x00000001, // 0053h + 0x00111110,0x01000000,0x01000001,0x00111110, + 0x00000000,0x01111111,0x00001000,0x00001000, // 0054h + 0x00001000,0x00001000,0x00001000,0x00001000, + 0x00000000,0x01000001,0x01000001,0x01000001, // 0055h + 0x01000001,0x01000001,0x00100010,0x00011100, + 0x00000000,0x01000001,0x01000001,0x00100010, // 0056h + 0x00100010,0x00010100,0x00010100,0x00001000, + 0x00000000,0x01000001,0x01000001,0x01000001, // 0057h + 0x01001001,0x01010101,0x01100011,0x01000001, + 0x00000000,0x01000001,0x00100010,0x00010100, // 0058h + 0x00001000,0x00010100,0x00100010,0x01000001, + 0x00000000,0x01000001,0x00100010,0x00010100, // 0059h + 0x00001000,0x00001000,0x00001000,0x00001000, + 0x00000000,0x01111111,0x00100000,0x00010000, // 005ah + 0x00001000,0x00000100,0x00000010,0x01111111, + 0x00000000,0x01111100,0x00000100,0x00000100, // 005bh + 0x00000100,0x00000100,0x00000100,0x01111100, + 0x00000000,0x00100010,0x00010100,0x00111110, // 005ch + 0x00001000,0x00111110,0x00001000,0x00001000, + 0x00000000,0x00011111,0x00010000,0x00010000, // 005dh + 0x00010000,0x00010000,0x00010000,0x00011111, + 0x00000000,0x00001000,0x00010100,0x00100010, // 005eh + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, // 005fh + 0x00000000,0x00000000,0x00000000,0x01111111, + 0x00000000,0x00010000,0x00001000,0x00011000, // 0060h + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00011110,0x00100001, // 0061h + 0x00111110,0x00100001,0x00100001,0x01011110, + 0x00000000,0x00000001,0x00000001,0x00111111, // 0062h + 0x01000001,0x01000001,0x01000001,0x00111111, + 0x00000000,0x00000000,0x00111100,0x01000010, // 0063h + 0x00000001,0x00000001,0x01000010,0x00111100, + 0x00000000,0x01000000,0x01000000,0x01111110, // 0064h + 0x01000001,0x01000001,0x01000001,0x01111110, + 0x00000000,0x00000000,0x00111110,0x01000001, // 0065h + 0x01111111,0x00000001,0x01000001,0x00111110, + 0x00000000,0x00110000,0x00001000,0x00001000, // 0066h + 0x01111111,0x00001000,0x00001000,0x00001000, + 0x00000000,0x00000000,0x01111110,0x01000001, // 0067h + 0x01000001,0x01111110,0x01000000,0x00111110, + 0x00000000,0x00000001,0x00000001,0x00000001, // 0068h + 0x00111111,0x01000001,0x01000001,0x01000001, + 0x00000000,0x00001000,0x00000000,0x00001000, // 0069h + 0x00001000,0x00001000,0x00001000,0x00001000, + 0x00000000,0x00100000,0x00000000,0x00100000, // 006ah + 0x00100000,0x00100001,0x00100001,0x00011110, + 0x00000000,0x00000001,0x00000001,0x01100001, // 006bh + 0x00011001,0x00000111,0x00011001,0x01100001, + 0x00000000,0x00001000,0x00001000,0x00001000, // 006ch + 0x00001000,0x00001000,0x00001000,0x00001000, + 0x00000000,0x00000000,0x00110111,0x01001001, // 006dh + 0x01001001,0x01001001,0x01001001,0x01001001, + 0x00000000,0x00000000,0x00111111,0x01000001, // 006eh + 0x01000001,0x01000001,0x01000001,0x01000001, + 0x00000000,0x00000000,0x00011100,0x00100010, // 006fh + 0x01000001,0x01000001,0x00100010,0x00011100, + 0x00000000,0x00000000,0x00111101,0x01000011, // 0070h + 0x01000001,0x01000011,0x00111101,0x00000001, + 0x00000000,0x00000000,0x01011110,0x01100001, // 0071h + 0x01000001,0x01100001,0x01011110,0x01000000, + 0x00000000,0x00000000,0x00110001,0x00001101, // 0072h + 0x00000011,0x00000001,0x00000001,0x00000001, + 0x00000000,0x00000000,0x00111110,0x01000001, // 0073h + 0x00001110,0x00110000,0x01000001,0x00111110, + 0x00000000,0x00000100,0x00000100,0x01111111, // 0074h + 0x00000100,0x00000100,0x00000100,0x01111000, + 0x00000000,0x00000000,0x01000001,0x01000001, // 0075h + 0x01000001,0x01000001,0x01000001,0x01111110, + 0x00000000,0x00000000,0x01000001,0x01000001, // 0076h + 0x00100010,0x00100010,0x00010100,0x00001000, + 0x00000000,0x00000000,0x01000001,0x01000001, // 0077h + 0x01001001,0x00101010,0x00101010,0x00010100, + 0x00000000,0x00000000,0x00100001,0x00010010, // 0078h + 0x00001100,0x00001100,0x00010010,0x00100001, + 0x00000000,0x00000000,0x01000001,0x01000001, // 0079h + 0x00100010,0x00011100,0x00001000,0x00000110, + 0x00000000,0x00000000,0x00111111,0x00010000, // 007ah + 0x00001000,0x00000100,0x00000010,0x00111111, + 0x00000000,0x00001000,0x00011110,0x01100100, // 007bh + 0x00011000,0x00100100,0x00000100,0x01111000, + 0x00000000,0x00000000,0x00011110,0x00000100, // 007ch + 0x00011110,0x00110101,0x00101101,0x00010010, + 0x00000000,0x00000000,0x00000000,0x00010001, // 007dh + 0x00100001,0x00100001,0x00000001,0x00000010, + 0x00000000,0x00000000,0x00011100,0x00000000, // 007eh + 0x00011110,0x00100000,0x00100000,0x00011100, + 0x00000000,0x00000000,0x00011100,0x00000000, // 007fh + 0x00111110,0x00010000,0x00001100,0x00110010, + 0x00000000,0x00000000,0x00000100,0x00101111, // 0080h + 0x01000100,0x00011110,0x00100101,0x00010110, + 0x00000000,0x00000000,0x00001010,0x00011110, // 0081h + 0x00101011,0x00100010,0x00010100,0x00000100, + 0x00000000,0x00000000,0x00001000,0x00011101, // 0082h + 0x00101011,0x00101001,0x00011001,0x00000100, + 0x00000000,0x00000000,0x00001000,0x00111000, // 0083h + 0x00001000,0x00011110,0x00101001,0x00000110, + 0x00000000,0x00000000,0x00000000,0x00011100, // 0084h + 0x00100011,0x00100000,0x00100000,0x00011100, + 0x00000000,0x00000110,0x01001001,0x00110000, // 0085h + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000100,0x00111111,0x00000100, // 0086h + 0x00111110,0x01010101,0x01001101,0x00100110, + 0x00000000,0x00000000,0x00100001,0x01000001, // 0087h + 0x01000001,0x01000001,0x00000001,0x00000010, + 0x00000000,0x00111100,0x00000000,0x00111110, // 0088h + 0x01000000,0x01000000,0x00100000,0x00011100, + 0x00000000,0x00011100,0x00000000,0x00111110, // 0089h + 0x00010000,0x00001000,0x00010100,0x01100010, + 0x00000000,0x00100100,0x01011111,0x00000100, // 008ah + 0x00111110,0x01000101,0x01000101,0x00100010, + 0x00000000,0x00100010,0x01001111,0x01010010, // 008bh + 0x01010010,0x00010010,0x00010010,0x00001001, + 0x00000000,0x00000100,0x00111110,0x00001000, // 008ch + 0x00111110,0x00010000,0x00000010,0x00111100, + 0x00000000,0x00100000,0x00011000,0x00000110, // 008dh + 0x00000001,0x00000110,0x00011000,0x00100000, + 0x00000000,0x00100000,0x01111101,0x00100001, // 008eh + 0x00100001,0x00100001,0x00100001,0x00010010, + 0x00000000,0x00011110,0x00100000,0x00000000, // 008fh + 0x00000000,0x00000001,0x00000001,0x00111110, + 0x00000000,0x00001000,0x01111111,0x00010000, // 0090h + 0x00100000,0x00000010,0x00000010,0x00111100, + 0x00000000,0x00000001,0x00000001,0x00000001, // 0091h + 0x01000001,0x01000001,0x00100010,0x00011100, + 0x00000000,0x00010000,0x01111111,0x00011000, // 0092h + 0x00010100,0x00010100,0x00011000,0x00001100, + 0x00000000,0x00100010,0x01111111,0x00100010, // 0093h + 0x00100010,0x00000010,0x00000010,0x01111100, + 0x00000000,0x00111100,0x00010000,0x00001100, // 0094h + 0x01111111,0x00001000,0x00001000,0x00110000, + 0x00000000,0x00000100,0x00011111,0x00000100, // 0095h + 0x01110100,0x00000010,0x00001010,0x01110010, + 0x00000000,0x00001000,0x01111111,0x00000100, // 0096h + 0x00111100,0x01000010,0x01000000,0x00111100, + 0x00000000,0x00000000,0x00011100,0x00100011, // 0097h + 0x01000000,0x01000000,0x00100000,0x00011100, + 0x00000000,0x01111111,0x00010000,0x00001000, // 0098h + 0x00001000,0x00001000,0x00001000,0x00110000, + 0x00000000,0x00000010,0x00110010,0x00001100, // 0099h + 0x00000010,0x00000001,0x00000001,0x00111110, + 0x00000000,0x00100100,0x01001111,0x01000010, // 009ah + 0x00010001,0x00111100,0x00010010,0x00001100, + 0x00000000,0x00000010,0x01111010,0x01000010, // 009bh + 0x00000010,0x00000010,0x00001010,0x01110010, + 0x00000000,0x00100010,0x00111110,0x01010010, // 009ch + 0x01001011,0x01101101,0x01010101,0x00110010, + 0x00000000,0x00110010,0x01001011,0x01000110, // 009dh + 0x01000110,0x01110010,0x01001011,0x00110010, + 0x00000000,0x00011100,0x00101010,0x01001001, // 009eh + 0x01001001,0x01000101,0x01000101,0x00110010, + 0x00000000,0x00100001,0x01111101,0x00100001, // 009fh + 0x00100001,0x00111001,0x01100101,0x00011001, + 0x00000000,0x00000100,0x00100011,0x01100010, // 00a0h + 0x00100001,0x00100001,0x00010010,0x00001100, + 0x00000000,0x00000000,0x00000000,0x00000000, // 00a1h + 0x00000000,0x00000100,0x00001010,0x00000100, + 0x00000000,0x01110000,0x00010000,0x00010000, // 00a2h + 0x00010000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, // 00a3h + 0x00001000,0x00001000,0x00001000,0x00001110, + 0x00000000,0x00000000,0x00000000,0x00000000, // 00a4h + 0x00000000,0x00000010,0x00000100,0x00000100, + 0x00000000,0x00000000,0x00000000,0x00011000, // 00a5h + 0x00011000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x01111111,0x01000000,0x01111111, // 00a6h + 0x01000000,0x01000000,0x00100000,0x00011100, + 0x00000000,0x00000000,0x00111111,0x00100000, // 00a7h + 0x00010100,0x00001100,0x00000100,0x00000010, + 0x00000000,0x00000000,0x00100000,0x00100000, // 00a8h + 0x00010000,0x00001111,0x00001000,0x00001000, + 0x00000000,0x00000000,0x00000100,0x00111111, // 00a9h + 0x00100001,0x00100000,0x00010000,0x00001100, + 0x00000000,0x00000000,0x00000000,0x00111110, // 00aah + 0x00001000,0x00001000,0x00001000,0x01111111, + 0x00000000,0x00000000,0x00010000,0x00111111, // 00abh + 0x00011000,0x00010100,0x00010010,0x00011001, + 0x00000000,0x00000000,0x00000010,0x00111111, // 00ach + 0x00100010,0x00010010,0x00000100,0x00000100, + 0x00000000,0x00000000,0x00000000,0x00111110, // 00adh + 0x00100000,0x00100000,0x00100000,0x01111111, + 0x00000000,0x00000000,0x00111110,0x00100000, // 00aeh + 0x00111110,0x00100000,0x00100000,0x00111110, + 0x00000000,0x00000000,0x00100101,0x00101010, // 00afh + 0x00101010,0x00100000,0x00010000,0x00001110, + 0x00000000,0x00000000,0x00000000,0x00000000, // 00b0h + 0x01111111,0x00000000,0x00000000,0x00000000, + 0x00000000,0x01111111,0x01000000,0x00101000, // 00b1h + 0x00011000,0x00001000,0x00001000,0x00000100, + 0x00000000,0x01000000,0x00100000,0x00011000, // 00b2h + 0x00010111,0x00010000,0x00010000,0x00010000, + 0x00000000,0x00001000,0x01111111,0x01000001, // 00b3h + 0x01000001,0x01000000,0x00100000,0x00011000, + 0x00000000,0x00000000,0x00111110,0x00001000, // 00b4h + 0x00001000,0x00001000,0x00001000,0x01111111, + 0x00000000,0x00100000,0x01111111,0x00110000, // 00b5h + 0x00101000,0x00100100,0x00100010,0x00110001, + 0x00000000,0x00000100,0x01111111,0x01000100, // 00b6h + 0x01000100,0x01000100,0x01000010,0x00100001, + 0x00000000,0x00000100,0x00111111,0x00001000, // 00b7h + 0x01111111,0x00010000,0x00010000,0x00010000, + 0x00000000,0x01111100,0x01000100,0x01000100, // 00b8h + 0x01000010,0x01000000,0x00100000,0x00011000, + 0x00000000,0x00000010,0x01111110,0x00100010, // 00b9h + 0x00100001,0x00100000,0x00010000,0x00001100, + 0x00000000,0x01111110,0x01000000,0x01000000, // 00bah + 0x01000000,0x01000000,0x01000000,0x01111110, + 0x00000000,0x00100010,0x01111111,0x00100010, // 00bbh + 0x00100010,0x00100000,0x00010000,0x00001100, + 0x00000000,0x00000011,0x00000100,0x01000011, // 00bch + 0x01000100,0x00100000,0x00011000,0x00000111, + 0x00000000,0x01111111,0x01000000,0x00100000, // 00bdh + 0x00010000,0x00011000,0x00100100,0x01000011, + 0x00000000,0x00000010,0x01111111,0x01000010, // 00beh + 0x00100010,0x00000010,0x00000010,0x01111100, + 0x00000000,0x01000001,0x01000010,0x01000000, // 00bfh + 0x00100000,0x00100000,0x00011000,0x00000110, + 0x00000000,0x01111110,0x01000010,0x01001110, // 00c0h + 0x01110001,0x01000000,0x00100000,0x00011000, + 0x00000000,0x01100000,0x00011110,0x00010000, // 00c1h + 0x01111111,0x00010000,0x00010000,0x00001100, + 0x00000000,0x01000101,0x01001010,0x01001010, // 00c2h + 0x01000000,0x00100000,0x00010000,0x00001110, + 0x00000000,0x00111110,0x00000000,0x01111111, // 00c3h + 0x00010000,0x00010000,0x00001000,0x00000110, + 0x00000000,0x00000010,0x00000010,0x00000110, // 00c4h + 0x00011010,0x01100010,0x00000010,0x00000010, + 0x00000000,0x00010000,0x00010000,0x01111111, // 00c5h + 0x00010000,0x00010000,0x00001000,0x00000110, + 0x00000000,0x00000000,0x00111110,0x00000000, // 00c6h + 0x00000000,0x00000000,0x00000000,0x01111111, + 0x00000000,0x01111110,0x01000000,0x01000100, // 00c7h + 0x00101000,0x00010000,0x00101000,0x01000110, + 0x00000000,0x00001000,0x01111111,0x00100000, // 00c8h + 0x00010000,0x00011100,0x01101011,0x00001000, + 0x00000000,0x01000000,0x01000000,0x01000000, // 00c9h + 0x00100000,0x00100000,0x00011000,0x00000111, + 0x00000000,0x00010010,0x00100010,0x00100010, // 00cah + 0x01000010,0x01000010,0x01000001,0x01000001, + 0x00000000,0x00000001,0x00000001,0x01111111, // 00cbh + 0x00000001,0x00000001,0x00000001,0x01111110, + 0x00000000,0x01111111,0x01000000,0x01000000, // 00cch + 0x01000000,0x00100000,0x00010000,0x00001110, + 0x00000000,0x00000000,0x00000100,0x00001010, // 00cdh + 0x00010001,0x00100001,0x01000000,0x00000000, + 0x00000000,0x00001000,0x00001000,0x01111111, // 00ceh + 0x00001000,0x00101010,0x01001010,0x01001001, + 0x00000000,0x01111111,0x01000000,0x01000000, // 00cfh + 0x00100010,0x00010100,0x00001000,0x00010000, + 0x00000000,0x00001110,0x01110000,0x00001110, // 00d0h + 0x01110000,0x00000110,0x00011000,0x01100000, + 0x00000000,0x00001000,0x00001000,0x00000100, // 00d1h + 0x00000100,0x00100010,0x01000010,0x01111111, + 0x00000000,0x01000000,0x01000000,0x00100100, // 00d2h + 0x00101000,0x00010000,0x00101100,0x01000011, + 0x00000000,0x01111111,0x00000100,0x01111111, // 00d3h + 0x00000100,0x00000100,0x00000100,0x01111000, + 0x00000000,0x00000010,0x01111111,0x01000010, // 00d4h + 0x00100010,0x00010100,0x00000100,0x00000100, + 0x00000000,0x00000000,0x00111110,0x00100000, // 00d5h + 0x00100000,0x00100000,0x00100000,0x01111111, + 0x00000000,0x01111110,0x01000000,0x01000000, // 00d6h + 0x01111110,0x01000000,0x01000000,0x01111110, + 0x00000000,0x00111110,0x00000000,0x01111111, // 00d7h + 0x01000000,0x01000000,0x00100000,0x00011100, + 0x00000000,0x01000010,0x01000010,0x01000010, // 00d8h + 0x01000010,0x01000000,0x00100000,0x00011000, + 0x00000000,0x00001010,0x00001010,0x00001010, // 00d9h + 0x01001010,0x01001010,0x00101010,0x00011001, + 0x00000000,0x00000010,0x00000010,0x01000010, // 00dah + 0x01000010,0x00100010,0x00010010,0x00001110, + 0x00000000,0x01111111,0x01000001,0x01000001, // 00dbh + 0x01000001,0x01000001,0x01000001,0x01111111, + 0x00000000,0x01111111,0x01000001,0x01000001, // 00dch + 0x01000000,0x01000000,0x00100000,0x00011100, + 0x00000000,0x01000011,0x01000100,0x01000000, // 00ddh + 0x01000000,0x00100000,0x00010000,0x00001111, + 0x00000000,0x00000000,0x00000000,0x00000000, // 00deh + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000, // 00dfh + 0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00011110,0x00001000,0x00000100, // 00e0h + 0x00101001,0x01010001,0x01010001,0x00001100, + 0x00000000,0x00000000,0x00001100,0x00010010, // 00e1h + 0x00010010,0x00100001,0x01000000,0x00000000, + 0x00000000,0x01111101,0x00100001,0x01111101, // 00e2h + 0x00100001,0x00111001,0x01100101,0x00011001, + 0x00000000,0x00111100,0x00010000,0x00111100, // 00e3h + 0x00010000,0x00011100,0x00110010,0x00001100, + 0x00000000,0x00001110,0x00101000,0x00101000, // 00e4h + 0x00111110,0x01100101,0x00100101,0x00010010, + 0x00000000,0x00000100,0x00101111,0x01000100, // 00e5h + 0x00000110,0x01000101,0x01000101,0x00111110, + 0x00000000,0x00100010,0x00100010,0x00111110, // 00e6h + 0x01010010,0x01010101,0x01001101,0x00100110, + 0x00000000,0x00000100,0x00011111,0x00000010, // 00e7h + 0x00011111,0x01000010,0x01000010,0x00111100, + 0x00000000,0x00010010,0x00111110,0x01010011, // 00e8h + 0x01000010,0x00100100,0x00000100,0x00000100, + 0x00000000,0x00001000,0x00111101,0x01001011, // 00e9h + 0x01001001,0x01001001,0x00111000,0x00000100, + 0x00000000,0x00001000,0x00111000,0x00001000, // 00eah + 0x00001000,0x00011110,0x00101001,0x00000110, + 0x00000000,0x00011000,0x00100000,0x00000100, // 00ebh + 0x00111010,0x01000110,0x01000000,0x00111000, + 0x00000000,0x01000010,0x01000010,0x01000010, // 00ech + 0x01000110,0x01000000,0x00100000,0x00011000, + 0x00000000,0x00111110,0x00010000,0x00111100, // 00edh + 0x01000011,0x01001100,0x01010010,0x00111100, + 0x00000000,0x00100010,0x00110011,0x00101010, // 00eeh + 0x00100110,0x00100010,0x00100011,0x01000010, + 0x00000000,0x00111110,0x00010000,0x00111100, // 00efh + 0x01000011,0x01000000,0x01000010,0x00111100, + 0x00000000,0x00000010,0x00111011,0x01000110, // 00f0h + 0x01000010,0x01000011,0x01000010,0x00110010, + 0x00000000,0x00000100,0x00000100,0x00000010, // 00f1h + 0x01000110,0x01000101,0x01000101,0x00111001, + 0x00000000,0x01010100,0x01111111,0x00100100, // 00f2h + 0x00100100,0x00100100,0x00100010,0x00010001, + 0x00000000,0x01010100,0x01011111,0x00000100, // 00f3h + 0x00111111,0x00001000,0x00001000,0x00001000, + 0x00000000,0x01011110,0x01100010,0x00100010, // 00f4h + 0x00100001,0x00100000,0x00010000,0x00001100, + 0x00000000,0x01010010,0x01111110,0x00100010, // 00f5h + 0x00100001,0x00100000,0x00010000,0x00001100, + 0x00000000,0x01010000,0x01111111,0x00100000, // 00f6h + 0x00100000,0x00100000,0x00100000,0x00111111, + 0x00000000,0x01010010,0x01010010,0x00111111, // 00f7h + 0x00010010,0x00010000,0x00010000,0x00001100, + 0x00000000,0x01010011,0x01010100,0x00100011, // 00f8h + 0x00100100,0x00010000,0x00001000,0x00000111, + 0x00000000,0x01010000,0x01011111,0x00010000, // 00f9h + 0x00001000,0x00001100,0x00010010,0x00100001, + 0x00000000,0x01010010,0x01111111,0x00100010, // 00fah + 0x00010010,0x00000010,0x00000010,0x00111100, + 0x00000000,0x01010001,0x01010010,0x00100000, // 00fbh + 0x00100000,0x00010000,0x00001000,0x00000110, + 0x00000000,0x01011110,0x01010010,0x00100110, // 00fch + 0x00111001,0x00100000,0x00010000,0x00001100, + 0x00000000,0x01010000,0x01011110,0x00010000, // 00fdh + 0x01111111,0x00010000,0x00010000,0x00001100, + 0x00000000,0x00100101,0x01001010,0x00101010, // 00feh + 0x00100000,0x00010000,0x00001000,0x00000111, + 0x00000000,0x01011110,0x01010000,0x00111111, // 00ffh + 0x00001000,0x00001000,0x00001000,0x00000110 +}; + + +/*---------------------------------------------------------------------------* + Palette data + *---------------------------------------------------------------------------*/ +const u32 d_PaletteData[ 8 * 16 ] = +{ + 0x00000000, 0x00000000, 0x00000000, 0x00000000, // black + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x001f0000, 0x00000000, 0x00000000, 0x00000000, // red + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x03e00000, 0x00000000, 0x00000000, 0x00000000, // green + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x7c000000, 0x00000000, 0x00000000, 0x00000000, // blue + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x03ff0000, 0x00000000, 0x00000000, 0x00000000, // yellow + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x7c1f0000, 0x00000000, 0x00000000, 0x00000000, // purple + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x7fe00000, 0x00000000, 0x00000000, 0x00000000, // light blue + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00180000, 0x00000000, 0x00000000, 0x00000000, // dark red + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x03000000, 0x00000000, 0x00000000, 0x00000000, // dark green + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x60000000, 0x00000000, 0x00000000, 0x00000000, // dark blue + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x03180000, 0x00000000, 0x00000000, 0x00000000, // dark yellow + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x60180000, 0x00000000, 0x00000000, 0x00000000, // dark purple + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x63000000, 0x00000000, 0x00000000, 0x00000000, // dark light blue + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x56b50000, 0x00000000, 0x00000000, 0x00000000, // gray + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x2d6b0000, 0x00000000, 0x00000000, 0x00000000, // dark gray + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x7fff0000, 0x00000000, 0x00000000, 0x00000000, // white + 0x00000000, 0x00000000, 0x00000000, 0x00000000 +}; + +/*---------------------------------------------------------------------------* + End of file + *---------------------------------------------------------------------------*/ diff --git a/code/gamespy/common/nitro/font.h b/code/gamespy/common/nitro/font.h new file mode 100644 index 00000000..f13b225d --- /dev/null +++ b/code/gamespy/common/nitro/font.h @@ -0,0 +1,44 @@ +/*---------------------------------------------------------------------------* + Project: NitroSDK - RTC - demos + File: font.h + + Copyright 2003,2004 Nintendo. All rights reserved. + + These coded instructions, statements, and computer programs contain + proprietary information of Nintendo of America Inc. and/or Nintendo + Company Ltd., and are protected by Federal copyright law. They may + not be disclosed to third parties or copied or duplicated in any form, + in whole or in part, without the prior written consent of Nintendo. + + $Log: font.h,v $ + Revision 1.1 2004/05/12 02:39:13 terui + initial upload + + $NoKeywords: $ + *---------------------------------------------------------------------------*/ + +#ifndef FONT_H_ +#define FONT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*===========================================================================*/ + +#include + +extern const u32 d_CharData[ 8 * 256 ]; +extern const u32 d_PaletteData[ 8 * 16 ]; + +/*===========================================================================*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* FONT_H_ */ + +/*---------------------------------------------------------------------------* + End of file + *---------------------------------------------------------------------------*/ diff --git a/code/gamespy/common/nitro/gsSocketNitro.c b/code/gamespy/common/nitro/gsSocketNitro.c new file mode 100644 index 00000000..bd97b2ea --- /dev/null +++ b/code/gamespy/common/nitro/gsSocketNitro.c @@ -0,0 +1,194 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(_NITRO) + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// static variables +static int GSINitroErrno; + +// prototypes of static functions +static int CheckRcode(int rcode, int errCode); + +#define NITRO_SOCKET_ERROR -1 + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static int CheckRcode(int rcode, int errCode) +{ + if(rcode >= 0) + return rcode; + GSINitroErrno = rcode; + return errCode; +} + +int socket(int pf, int type, int protocol) +{ + int rcode = SOC_Socket(pf, type, protocol); + return CheckRcode(rcode, INVALID_SOCKET); +} +int closesocket(SOCKET sock) +{ + int rcode = SOC_Close(sock); + return CheckRcode(rcode, NITRO_SOCKET_ERROR); +} +int shutdown(SOCKET sock, int how) +{ + int rcode = SOC_Shutdown(sock, how); + return CheckRcode(rcode, NITRO_SOCKET_ERROR); +} +int bind(SOCKET sock, const SOCKADDR* addr, int len) +{ + SOCKADDR localAddr; + int rcode; + + // with nitro, don't bind to 0, just start using the port + if(((const SOCKADDR_IN*)addr)->port == 0) + return 0; + + memcpy(&localAddr, addr, sizeof(SOCKADDR)); + localAddr.len = (u8)len; + + rcode = SOC_Bind(sock, &localAddr); + return CheckRcode(rcode, NITRO_SOCKET_ERROR); +} + +int connect(SOCKET sock, const SOCKADDR* addr, int len) +{ + SOCKADDR remoteAddr; + int rcode; + + memcpy(&remoteAddr, addr, sizeof(SOCKADDR)); + remoteAddr.len = (u8)len; + + rcode = SOC_Connect(sock, &remoteAddr); + return CheckRcode(rcode, NITRO_SOCKET_ERROR); +} +int listen(SOCKET sock, int backlog) +{ + int rcode = SOC_Listen(sock, backlog); + return CheckRcode(rcode, NITRO_SOCKET_ERROR); +} +SOCKET accept(SOCKET sock, SOCKADDR* addr, int* len) +{ + int rcode; + addr->len = (u8)*len; + rcode = SOC_Accept(sock, addr); + *len = addr->len; + return CheckRcode(rcode, NITRO_SOCKET_ERROR); +} + +int recv(SOCKET sock, char* buf, int len, int flags) +{ + int rcode = SOC_Recv(sock, buf, len, flags); + return CheckRcode(rcode, NITRO_SOCKET_ERROR); +} +int recvfrom(SOCKET sock, char* buf, int len, int flags, SOCKADDR* addr, int* fromlen) +{ + int rcode; + addr->len = (u8)*fromlen; + rcode = SOC_RecvFrom(sock, buf, len, flags, addr); + *fromlen = addr->len; + return CheckRcode(rcode, NITRO_SOCKET_ERROR); +} +SOCKET send(SOCKET sock, const char* buf, int len, int flags) +{ + int rcode = SOC_Send(sock, buf, len, flags); + return CheckRcode(rcode, NITRO_SOCKET_ERROR); +} +SOCKET sendto(SOCKET sock, const char* buf, int len, int flags, const SOCKADDR* addr, int tolen) +{ + SOCKADDR remoteAddr; + int rcode; + + memcpy(&remoteAddr, addr, sizeof(SOCKADDR)); + remoteAddr.len = (u8)tolen; + + rcode = SOC_SendTo(sock, buf, len, flags, &remoteAddr); + return CheckRcode(rcode, NITRO_SOCKET_ERROR); +} + +int getsockopt(SOCKET sock, int level, int optname, char* optval, int* optlen) +{ + int rcode = SOC_GetSockOpt(sock, level, optname, optval, optlen); + return CheckRcode(rcode, NITRO_SOCKET_ERROR); +} +SOCKET setsockopt(SOCKET sock, int level, int optname, const char* optval, int optlen) +{ + int rcode = SOC_SetSockOpt(sock, level, optname, optval, optlen); + return CheckRcode(rcode, NITRO_SOCKET_ERROR); +} + +int getsockname(SOCKET sock, SOCKADDR* addr, int* len) +{ + int rcode; + addr->len = (u8)*len; + rcode = SOC_GetSockName(sock, addr); + *len = addr->len; + return CheckRcode(rcode, NITRO_SOCKET_ERROR); +} + +unsigned long inet_addr(const char* name) +{ + int rcode; + SOInAddr addr; + rcode = SOC_InetAtoN(name, &addr); + if(rcode == FALSE) + return INADDR_NONE; + return addr.addr; +} + +int GOAGetLastError(SOCKET sock) +{ + GSI_UNUSED(sock); + return GSINitroErrno; +} + + + +int GSISocketSelect(SOCKET theSocket, int* theReadFlag, int* theWriteFlag, int* theExceptFlag) +{ + SOPollFD pollFD; + int rcode; + + pollFD.fd = theSocket; + pollFD.events = 0; + if(theReadFlag != NULL) + pollFD.events |= SOC_POLLRDNORM; + if(theWriteFlag != NULL) + pollFD.events |= SOC_POLLWRNORM; + pollFD.revents = 0; + + rcode = SOC_Poll(&pollFD, 1, 0); + if(rcode < 0) + return NITRO_SOCKET_ERROR; + + if(theReadFlag != NULL) + { + if((rcode > 0) && (pollFD.revents & (SOC_POLLRDNORM|SOC_POLLHUP))) + *theReadFlag = 1; + else + *theReadFlag = 0; + } + if(theWriteFlag != NULL) + { + if((rcode > 0) && (pollFD.revents & SOC_POLLWRNORM)) + *theWriteFlag = 1; + else + *theWriteFlag = 0; + } + if(theExceptFlag != NULL) + { + if((rcode > 0) && (pollFD.revents & SOC_POLLERR)) + *theExceptFlag = 1; + else + *theExceptFlag = 0; + } + return rcode; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // _NITRO \ No newline at end of file diff --git a/code/gamespy/common/nitro/gsThreadNitro.c b/code/gamespy/common/nitro/gsThreadNitro.c new file mode 100644 index 00000000..515009d4 --- /dev/null +++ b/code/gamespy/common/nitro/gsThreadNitro.c @@ -0,0 +1,155 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gsPlatformThread.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u32 gsiInterlockedIncrement(gsi_u32 * value) +{ + OSIntrMode state = OS_DisableInterrupts_IrqAndFiq(); + gsi_u32 ret = ++(*value); + OS_RestoreInterrupts_IrqAndFiq(state); + + // return "ret" rather than "value" here b/c + // value may be modified by another thread + // before we can return it + return ret; +} + +gsi_u32 gsiInterlockedDecrement(gsi_u32 * value) +{ + OSIntrMode state = OS_DisableInterrupts_IrqAndFiq(); + gsi_u32 ret = --(*value); + OS_RestoreInterrupts_IrqAndFiq(state); + + // return "ret" rather than "value" here b/c + // value may be modified by another thread + // before we can return it + return ret; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +int gsiStartThread(GSThreadFunc aThreadFunc, gsi_u32 theStackSize, void *arg, GSIThreadID* theThreadIdOut) +{ + if(theStackSize & 0x3) + { + theStackSize += 0x4; + theStackSize &= ~0x3; + } + + theThreadIdOut->mStack = gsimemalign(4, theStackSize); + + OS_CreateThread(&theThreadIdOut->mThread, aThreadFunc, arg, theThreadIdOut->mStack, theStackSize, 15); + OS_WakeupThreadDirect(&theThreadIdOut->mThread); + + return 0; +} + +void gsiCancelThread(GSIThreadID theThreadID) +{ + OS_DestroyThread(&theThreadID.mThread); + if(theThreadID.mStack) + { + gsifree(theThreadID.mStack); + theThreadID.mStack = NULL; + } +} + +void gsiCleanupThread(GSIThreadID theThreadID) +{ + OS_DestroyThread(&theThreadID.mThread); + if(theThreadID.mStack) + { + gsifree(theThreadID.mStack); + theThreadID.mStack = NULL; + } +} + +gsi_u32 gsiHasThreadShutdown(GSIThreadID theThreadID) +{ + BOOL shutdown = OS_IsThreadTerminated(&theThreadID.mThread); + + if(shutdown == TRUE) + return 1; + return 0; +} + +void gsiInitializeCriticalSection(GSICriticalSection *theCrit) +{ + OS_InitMutex(theCrit); +} + +void gsiEnterCriticalSection(GSICriticalSection *theCrit) +{ + OS_LockMutex(theCrit); +} + +void gsiLeaveCriticalSection(GSICriticalSection *theCrit) +{ + OS_UnlockMutex(theCrit); +} + +void gsiDeleteCriticalSection(GSICriticalSection *theCrit) +{ + GSI_UNUSED(theCrit); +} + +GSISemaphoreID gsiCreateSemaphore(gsi_i32 theInitialCount, gsi_i32 theMaxCount, char* theName) +{ + GSISemaphoreID semaphore; + + OS_InitMutex(&semaphore.mLock); + + semaphore.mValue = theInitialCount; + semaphore.mMax = theMaxCount; + + GSI_UNUSED(theName); + + return semaphore; +} + +gsi_u32 gsiWaitForSemaphore(GSISemaphoreID theSemaphore, gsi_u32 theTimeoutMs) +{ + gsi_time startTime = current_time(); + gsi_bool infinite = (theTimeoutMs == GSI_INFINITE)?gsi_true:gsi_false; + + do + { + if(OS_TryLockMutex(&theSemaphore.mLock) == TRUE) + { + if(theSemaphore.mValue > 0) + { + theSemaphore.mValue--; + OS_UnlockMutex(&theSemaphore.mLock); + return 1; + } + + OS_UnlockMutex(&theSemaphore.mLock); + } + + if(theTimeoutMs != 0) + msleep(2); + + } while(gsi_is_true(infinite) || ((current_time() - startTime) < theTimeoutMs)); + + return 0; +} + +void gsiReleaseSemaphore(GSISemaphoreID theSemaphore, gsi_i32 theReleaseCount) +{ + OS_LockMutex(&theSemaphore.mLock); + + theSemaphore.mValue += theReleaseCount; + if(theSemaphore.mValue > theSemaphore.mMax) + theSemaphore.mValue = theSemaphore.mMax; + + OS_UnlockMutex(&theSemaphore.mLock); +} + +void gsiCloseSemaphore(GSISemaphoreID theSemaphore) +{ + GSI_UNUSED(theSemaphore); +} diff --git a/code/gamespy/common/nitro/gsTimerNitro.c b/code/gamespy/common/nitro/gsTimerNitro.c new file mode 100644 index 00000000..3efd1f6a --- /dev/null +++ b/code/gamespy/common/nitro/gsTimerNitro.c @@ -0,0 +1,19 @@ +// NITRO +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gsCommon.h" + +// note that this doesn't return the standard time() value +// because the DS doesn't know what timezone it's in +time_t time(time_t *timer) +{ + time_t t; + + assert(OS_IsTickAvailable() == TRUE); + t = (time_t)OS_TicksToSeconds(OS_GetTick()); + + if(timer) + *timer = t; + + return t; +} \ No newline at end of file diff --git a/code/gamespy/common/nitro/gsUtilNitro.c b/code/gamespy/common/nitro/gsUtilNitro.c new file mode 100644 index 00000000..c85744e7 --- /dev/null +++ b/code/gamespy/common/nitro/gsUtilNitro.c @@ -0,0 +1,18 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gsCommon.h" + +gsi_i64 gsiStringToInt64(const char *theNumberStr) +{ + return atoll(theNumberStr); +} + + +void gsiInt64ToString(char theNumberStr[33], gsi_i64 theNumber) +{ + // you want to fit the number! + // give me a valid string! + GS_ASSERT(theNumberStr != NULL); + + sprintf(theNumberStr, "%lld", theNumber); +} \ No newline at end of file diff --git a/code/gamespy/common/nitro/key.c b/code/gamespy/common/nitro/key.c new file mode 100644 index 00000000..dcecb91a --- /dev/null +++ b/code/gamespy/common/nitro/key.c @@ -0,0 +1,76 @@ +#include "..\nonport.h" +#include "key.h" + +#define KEY_REPEAT_START 25 // Number of frames before start of key repeat +#define KEY_REPEAT_SPAN 10 // Number of frames of key repeat interval + +static KeyInformation KeyInfo; + +/*---------------------------------------------------------------------------* + Name: KeyRead + + Description: Edit key input information + Detect pressing trigger, releasing trigger, and pressing hold repeat + *---------------------------------------------------------------------------*/ +const KeyInformation * KeyRead(void) +{ + static u16 repeat_count[12]; + int i; + u16 r; + + r = PAD_Read(); + KeyInfo.trg = 0x0000; + KeyInfo.up = 0x0000; + KeyInfo.rep = 0x0000; + + for( i = 0 ; i < 12 ; i ++ ) + { + if( r & ( 0x0001 << i ) ) + { + if( !( KeyInfo.cnt & ( 0x0001 << i ) ) ) + { + KeyInfo.trg |= ( 0x0001 << i ); // Pressing trigger input + repeat_count[ i ] = 1; + } + else + { + if( repeat_count[ i ] > KEY_REPEAT_START ) + { + KeyInfo.rep |= ( 0x0001 << i ); // Pressing hold repeat + repeat_count[ i ] = KEY_REPEAT_START - KEY_REPEAT_SPAN; + } + else + { + repeat_count[ i ] ++; + } + } + } + else + { + if( KeyInfo.cnt & ( 0x0001 << i ) ) + { + KeyInfo.up |= ( 0x0001 << i ); // Releasing trigger input + } + } + } + KeyInfo.cnt = r; // Unprocessed key input + + return &KeyInfo; +} + +void WaitForA(void) +{ + while(1) + { + SVC_WaitVBlankIntr(); + KeyRead(); + if(KEY_A_PRESSED(&KeyInfo)) + break; + } +} + +void KeyInit(void) +{ + // empty call of key input information acquisition (pushing the A button in IPL) + KeyRead(); +} \ No newline at end of file diff --git a/code/gamespy/common/nitro/key.h b/code/gamespy/common/nitro/key.h new file mode 100644 index 00000000..3c04105e --- /dev/null +++ b/code/gamespy/common/nitro/key.h @@ -0,0 +1,28 @@ +#ifndef _KEY_H_ +#define _KEY_H_ + +// Key input information +typedef struct KeyInformation +{ + u16 cnt; // Unprocessed input value + u16 trg; // Pressing trigger input + u16 up; // Releasing trigger input + u16 rep; // Pressing hold repeat input +} KeyInformation; + +void KeyInit(void); + +const KeyInformation * KeyRead(void); + +#define KEY_A_PRESSED(info) (((info)->trg | (info)->rep) & PAD_BUTTON_A) +#define KEY_B_PRESSED(info) (((info)->trg | (info)->rep) & PAD_BUTTON_B) +#define KEY_X_PRESSED(info) (((info)->trg | (info)->rep) & PAD_BUTTON_X) +#define KEY_Y_PRESSED(info) (((info)->trg | (info)->rep) & PAD_BUTTON_Y) +#define KEY_UP_PRESSED(info) (((info)->trg | (info)->rep) & PAD_KEY_UP) +#define KEY_DOWN_PRESSED(info) (((info)->trg | (info)->rep) & PAD_KEY_DOWN) +#define KEY_LEFT_PRESSED(info) (((info)->trg | (info)->rep) & PAD_KEY_LEFT) +#define KEY_RIGHT_PRESSED(info) (((info)->trg | (info)->rep) & PAD_KEY_RIGHT) + +void WaitForA(void); + +#endif \ No newline at end of file diff --git a/code/gamespy/common/nitro/main.c b/code/gamespy/common/nitro/main.c new file mode 100644 index 00000000..4905c5f1 --- /dev/null +++ b/code/gamespy/common/nitro/main.c @@ -0,0 +1,122 @@ +#include "..\nonport.h" +#include "screen.h" +#include "key.h" +#include "wireless.h" +#include "touch.h" +#include "backup.h" + +static void Startup(void) +{ +/* System */ + // init the OS system - internally initializes: + // arena - OS_InitArenaEx() + // communication system between preprocessors - PXI_Init() + // lock system - OS_InitLock() + // IRQ interrupt tables - OS_InitIrqTable() + // exception display system - OS_InitException() + // both WRAMs to the ARM7 - MI_SetWramBank() + // V count alarm system - OS_InitVAlarm() + // thread system - OS_InitThread() + // reset system - OS_InitReset() + // Game Pak library - CTRDG_Init() + // Card library - CARD_Init() + // power control system - PM_Init() + OS_Init(); + +/* Time */ + // init the system tick count + OS_InitTick(); + + // init the alarm + // this is needed for OS_Sleep() + OS_InitAlarm(); + +/* RTC */ + RTC_Init(); + +/* FIFO */ + PXI_InitFifo(); + +/* Screen */ + ScreenInit(); + Printf("Screen initialized\n"); + + SetTopScreenLineCentered(SCREEN_HEIGHT / 2, SCWhite, "Starting GameSpy Sample"); + +/* Keys */ + KeyInit(); + Printf("Input initialized\n"); + +/* Touch */ + TouchInit(); + Printf("Touch initialized\n"); + +/* Interrupts */ + OS_EnableIrq(); + OS_EnableInterrupts(); + Printf("Interrupts initialized\n"); + +/* Heap */ + { + u32 nHeapAdrs = 0; + u32 nHeapSize = 1 * 1024 * 1024; + OSHeapHandle hHeap; + + OS_SetMainArenaLo(OS_InitAlloc(OS_ARENA_MAIN, OS_GetMainArenaLo(), OS_GetMainArenaHi(), 1)); + nHeapAdrs = (u32)OS_AllocFromMainArenaLo(nHeapSize, 32); + hHeap = OS_CreateHeap(OS_ARENA_MAIN, (void*)(nHeapAdrs), (void*)(nHeapAdrs + nHeapSize)); + OS_SetCurrentHeap(OS_ARENA_MAIN, hHeap); + } + Printf("Heap initialized\n"); + +/* Backup */ + BackupInit(); + +/* Wireless */ + WirelessInit(); +} + +static void Shutdown(void) +{ + // close down wireless + Printf("Wireless cleanup\n"); + WirelessCleanup(); + +// ClearScreens(); +// SetTopScreenLineCentered(SCREEN_HEIGHT / 2, SCWhite, "GameSpy Sample Shutdown"); +// SVC_WaitVBlankIntr(); + + // terminate the os system + Printf("Terminating OS\n"); + OS_Terminate(); +} + +extern int test_main(int argc, char ** argv); + +static void Run(void) +{ + SetPrintMode(PRINT_TO_SCREEN|PRINT_TO_DEBUGGER); + + Printf("\n"); + Printf("GameSpy Test App Starting\n"); + Printf("-------------------------\n"); + + test_main(0, NULL); + + Printf("------------------------\n"); + Printf("GameSpy Test App Exiting\n"); + + SetPrintMode(PRINT_TO_DEBUGGER); +} + +void NitroMain(void) +{ + // startup everything we need + Startup(); + + // do stuff + Run(); + + // shutdown the system + Shutdown(); +} \ No newline at end of file diff --git a/code/gamespy/common/nitro/menu.c b/code/gamespy/common/nitro/menu.c new file mode 100644 index 00000000..6b3ddb81 --- /dev/null +++ b/code/gamespy/common/nitro/menu.c @@ -0,0 +1,410 @@ +#include "../nonport.h" +#include "menu.h" +#include "screen.h" +#include "key.h" +#include "touch.h" + +static MenuScreen Screen; + +static MenuScreenConfiguration * NextMenuScreenConfiguration; + +static MenuScreenConfiguration MenuExitScreenConfiguration; + +static void NewMenuScreen(MenuScreenConfiguration * configuration) +{ + int count; + + memset(&Screen, 0, sizeof(MenuScreen)); + Screen.configuration = configuration; + Screen.listSelection = -1; + count = 0; + while(configuration->choices[count].text && (count < MAX_CHOICES)) + count++; + Screen.numChoices = count; +} + +static void ShowMenuScreen(void) +{ + MenuScreenConfiguration * configuration = Screen.configuration; + MenuScreenChoice * choices = configuration->choices; + int options = configuration->options; + + const ScreenColor normalColor = SCGray; + const ScreenColor highlightColor = SCWhite; + const ScreenColor disabledColor = SCDarkGray; + const int topTextLine = 10; + const int animationLine = (topTextLine + 2); + const int extraTextLine = (animationLine + 2); + const int keyboardTextLine = 1; + const int startKeyboardRowsLine = 3; + const int numKeyboardRows = 3; + const int numListRows = 5; + const int startListLine = 1; + const int startChoicesLine = (SCREEN_HEIGHT - (Screen.numChoices * 2)); + const char keyboardRows[numKeyboardRows][32] = + { + "1 2 3 4 5 6 7 8 9 0 - + = _ . < ", + " A B C D E F G H I J K L M N O ", + " P Q R S T U V W X Y Z space " + }; + const char listBorder[32] = "--------------------"; + const int spacePos = (strchr(keyboardRows[2], 's') - keyboardRows[2]); + const char animationChars[] = "-*"; + const gsi_time animationTickTime = 500; + + BOOL done = FALSE; + int choiceIndex; + BOOL keyboard; + BOOL touching; + BOOL list; + BOOL animated; + int x, y; + int touchingLine; + int touchingPos; + int choiceLine; + int keyboardLine; + BOOL wasTouching = FALSE; + int wasTouchingChoice = -1; + ScreenColor color; + int i; + int range; + char touchingChar; + char wasTouchingChar = 0; + int keyboardPos; + int numListItems; + int listScroll = 0; + BOOL wasTouchingUp = FALSE; + BOOL wasTouchingDown = FALSE; + int listItem; + const char * choiceSelection; + int animationCount = 0; + gsi_time lastAnimationTime = current_time(); + gsi_time now; + char animationText[] = "-"; + + // is it animated? + animated = (options & SCREEN_OPTION_ANIMATED)?TRUE:FALSE; + + // is there a keyboard? + keyboard = (options & SCREEN_OPTION_KEYBOARD)?TRUE:FALSE; + + // is there a list? + list = (options & SCREEN_OPTION_LIST)?TRUE:FALSE; + + // we can't have a list and a keyboard + if(keyboard && list) + OS_Panic("Can't have a keyboard and a list on a menu screen\n"); + + // call the init func + if(configuration->initFunc) + { + configuration->initFunc(); + if(NextMenuScreenConfiguration) + return; + } + + // if there is a keyboard, get the initial position + if(keyboard) + keyboardPos = (int)strlen(Screen.keyboardText); + + // loop until we're done + while(!done) + { + // wait for a screen update to complete + SVC_WaitVBlankIntr(); + + // call the think func + if(configuration->thinkFunc) + { + configuration->thinkFunc(); + if(NextMenuScreenConfiguration) + return; + } + + // count the number of list items + if(list) + { + for(numListItems = 0 ; numListItems < MAX_LIST_STRINGS ; numListItems++) + { + if(Screen.list[numListItems][0] == '\0') + break; + } + } + + // check for a touch + touching = GetTouch(&x, &y); + if(touching) + { + // figure out which line we're touching + touchingLine = (y / (TOUCH_Y_RANGE / SCREEN_HEIGHT)); + + // figure out which position we're touching + touchingPos = (x / (TOUCH_X_RANGE / SCREEN_WIDTH)); + } + else if (wasTouching) + { + // check for touching a choice + if(wasTouchingChoice != -1) + { + choiceSelection = choices[wasTouchingChoice].text; + wasTouchingChoice = -1; + + // call the chose func + if(configuration->choseFunc) + { + configuration->choseFunc(choiceSelection); + if(NextMenuScreenConfiguration) + return; + } + } + // check for touching up on the list + else if(wasTouchingUp) + { + if(listScroll > 0) + listScroll--; + } + // check for touching down on the list + else if(wasTouchingDown) + { + if(listScroll < (numListItems - numListRows)) + listScroll++; + } + // check for touching a keyboard char + else if(wasTouchingChar != 0) + { + // check for backspace + if(wasTouchingChar == '<') + { + if(keyboardPos > 0) + { + keyboardPos--; + Screen.keyboardText[keyboardPos] = '\0'; + } + } + else + { + if(keyboardPos < MAX_KEYBOARD_TEXT_LEN) + { + Screen.keyboardText[keyboardPos] = wasTouchingChar; + keyboardPos++; + Screen.keyboardText[keyboardPos] = '\0'; + } + } + } + } + wasTouching = touching; + + // clear both screens + ClearScreens(); + + // show the title on the top screen + SetTopScreenLineCentered(topTextLine, SCYellow, configuration->topScreenText); + + // show animation + if(animated) + { + now = current_time(); + if((now - lastAnimationTime) >= animationTickTime) + { + animationCount++; + animationCount %= strlen(animationChars); + lastAnimationTime += animationTickTime; + } + + animationText[0] = animationChars[animationCount]; + + SetTopScreenLineCentered(animationLine, SCYellow, animationText); + } + + // show extra text + for(i = 0 ; i < MAX_EXTRA_TEXT_STRINGS ; i++) + { + if(options & SCREEN_OPTION_EXTRAS_CENTERED) + SetTopScreenLineCentered(extraTextLine + i, SCWhite, Screen.extraText[i]); + else + SetTopScreenLine(extraTextLine + i, SCWhite, Screen.extraText[i]); + } + + // show the list + if(list) + { + // clear touching vars + wasTouchingUp = FALSE; + wasTouchingDown = FALSE; + + // show "up" if needed + if(listScroll > 0) + { + if(touching && (touchingLine == startListLine)) + { + wasTouchingUp = TRUE; + color = highlightColor; + } + else + { + color = normalColor; + } + SetBottomScreenLineCentered(startListLine, color, "up"); + } + + // show the top line + SetBottomScreenLineCentered(startListLine + 1, normalColor, listBorder); + + // show the list items + for(i = 0 ; i < numListRows ; i++) + { + listItem = (listScroll + i); + + if(listItem >= numListItems) + break; + + // is this item being touched? + if(touching && (touchingLine == (startListLine + 2 + i))) + Screen.listSelection = listItem; + + // set the color + if(Screen.listSelection == listItem) + color = highlightColor; + else + color = normalColor; + + // show the item + SetBottomScreenLineCentered(startListLine + 2 + i, color, Screen.list[listScroll + i]); + } + + // show the bottom line + SetBottomScreenLineCentered(startListLine + numListRows + 2, normalColor, listBorder); + + // show "down" if needed + if(listScroll < (numListItems - numListRows)) + { + if(touching && (touchingLine == (startListLine + numListRows + 3))) + { + wasTouchingDown = TRUE; + color = highlightColor; + } + else + { + color = normalColor; + } + SetBottomScreenLineCentered(startListLine + numListRows + 3, color, "down"); + } + } + + // show a keyboard + if(keyboard) + { + // show the text + SetBottomScreenLine(keyboardTextLine, SCGreen, Screen.keyboardText); + + // clear touching var + wasTouchingChar = 0; + + // loop through the keyboard rows + for(i = 0 ; i < numKeyboardRows ; i++) + { + // get the line to show this row on + keyboardLine = (startKeyboardRowsLine + i); + + // check if we're touching this row + if(touching && (touchingLine == keyboardLine)) + { + // get the char we're touching + touchingChar = keyboardRows[i][touchingPos]; + + // handle touching 'space' specially + if(islower(touchingChar)) + { + wasTouchingChar = ' '; + + touchingPos = spacePos; + range = 5; + } + else + { + if(touchingChar != ' ') + wasTouchingChar = touchingChar; + + range = 1; + } + + // show the keyboard row with the selection highlighted + SetBottomScreenLineHighlight( + keyboardLine, normalColor, keyboardRows[i], + touchingPos, range, highlightColor); + } + else + { + // show the keyboard row + SetBottomScreenLine(keyboardLine, normalColor, keyboardRows[i]); + } + } + } + + // show the choices + wasTouchingChoice = -1; + for(choiceIndex = 0 ; choiceIndex < Screen.numChoices ; choiceIndex++) + { + // this is the line to show this choice on + choiceLine = (startChoicesLine + (choiceIndex * 2)); + + // check if we're touching this choice + if((choices[choiceIndex].options & CHOICE_OPTION_DISABLED) || + ((choices[choiceIndex].options & CHOICE_OPTION_NEEDS_LIST_SELECTION) && (Screen.listSelection == -1))) + { + color = disabledColor; + } + else if(touching && (touchingLine == choiceLine)) + { + color = highlightColor; + wasTouchingChoice = choiceIndex; + } + else + { + color = normalColor; + } + + // show the line + SetBottomScreenLineCentered(choiceLine, color, choices[choiceIndex].text); + } + } +} + +void StartMenuScreen(MenuScreenConfiguration * configuration) +{ + assert(configuration != NULL); + + // set the initial next screen + SetNextMenuScreen(configuration); + + // loop while there is a next screen to show + while(NextMenuScreenConfiguration != &MenuExitScreenConfiguration) + { + // setup the new screen + NewMenuScreen(NextMenuScreenConfiguration); + + // clear the next screen setting + SetNextMenuScreen(NULL); + + // show the menu screen + ShowMenuScreen(); + + // clear the display + ClearScreens(); + } +} + +void SetNextMenuScreen(MenuScreenConfiguration * configuration) +{ + NextMenuScreenConfiguration = configuration; +} + +void ExitMenu(void) +{ + SetNextMenuScreen(&MenuExitScreenConfiguration); +} + +MenuScreen * GetMenuScreen(void) +{ + return &Screen; +} \ No newline at end of file diff --git a/code/gamespy/common/nitro/menu.h b/code/gamespy/common/nitro/menu.h new file mode 100644 index 00000000..ec8df39d --- /dev/null +++ b/code/gamespy/common/nitro/menu.h @@ -0,0 +1,65 @@ +#ifndef _MENU_H_ +#define _MENU_H_ + +// various length defines +#define MAX_CHOICE_STRING_LEN 32 +#define MAX_TOP_SCREEN_TEXT 32 +#define MAX_CHOICES 10 +#define MAX_LIST_STRINGS 16 +#define MAX_LIST_STRING_LEN 32 +#define MAX_KEYBOARD_TEXT_LEN 32 +#define MAX_EXTRA_TEXT_STRINGS 10 +#define MAX_EXTRA_TEXT_STRING_LEN 32 + +// options for screens +#define SCREEN_OPTION_ANIMATED 0x1 +#define SCREEN_OPTION_KEYBOARD 0x2 +#define SCREEN_OPTION_LIST 0x4 +#define SCREEN_OPTION_EXTRAS_CENTERED 0x8 + +// options for screen choices +#define CHOICE_OPTION_NEEDS_LIST_SELECTION 0x1 +#define CHOICE_OPTION_DISABLED 0x2 + +// a single choice on a screen +typedef struct MenuScreenChoice +{ + const char * text; + int options; +} MenuScreenChoice; + +// an instance of a screen +typedef struct MenuScreen +{ + struct MenuScreenConfiguration * configuration; + char extraText[MAX_EXTRA_TEXT_STRINGS][MAX_EXTRA_TEXT_STRING_LEN + 1]; + char list[MAX_LIST_STRINGS][MAX_LIST_STRING_LEN + 1]; + int listSelection; // default -1, no selection + char keyboardText[MAX_KEYBOARD_TEXT_LEN + 1]; + int numChoices; +} MenuScreen; + +// the static configuration for a screen +typedef struct MenuScreenConfiguration +{ + const char * topScreenText; + MenuScreenChoice choices[MAX_CHOICES]; + void (* initFunc)(void); + void (* choseFunc)(const char * choice); + void (* thinkFunc)(void); + int options; +} MenuScreenConfiguration; + +// call this to start showing a menu, pass in the initial screen to show +void StartMenuScreen(MenuScreenConfiguration * configuration); + +// call this from any of the configuration callback funcs to set the next screen to show +void SetNextMenuScreen(MenuScreenConfiguration * configuration); + +// call this to exit the menu +void ExitMenu(void); + +// gets the current menu screen +MenuScreen * GetMenuScreen(void); + +#endif \ No newline at end of file diff --git a/code/gamespy/common/nitro/nitrocommon.cww b/code/gamespy/common/nitro/nitrocommon.cww new file mode 100644 index 00000000..c9a2625e --- /dev/null +++ b/code/gamespy/common/nitro/nitrocommon.cww @@ -0,0 +1,439 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]> + + + + -1 + 0 + true + nitrosample\nitrosample.mcp + + 604 + 352 + + + 400 + 372 + + + 1 + 0 + 0 + 59420 + 0.162898 + 378 + + 29705908 + 42137564 + + + + + -1 + 0 + ..\..\serverbrowsing\sbctest\sbnitrocw\sbnitrocw.mcp + + 458 + 315 + + + 400 + 372 + + + 1 + 0 + 2 + 59420 + 0.193501 + 378 + + 0 + 0 + + + + + -1 + 0 + ..\..\qr2\qr2csample\qr2nitrocw\qr2nitrocw.mcp + + 285 + 274 + + + 400 + 379 + + + 1 + 0 + 1 + 59420 + 0.141566 + 378 + + 0 + 0 + + + + + -1 + 0 + ..\..\pt\pttestc\ptnitrocw\ptnitrocw.mcp + + 457 + 317 + + + 392 + 344 + + + 1 + 1 + 4 + 59420 + 0.165193 + 378 + + 0 + 0 + + + + + -1 + 0 + ..\..\Peer\peerc\peernitrocw\peernitrocw.mcp + + 457 + 317 + + + 392 + 344 + + + 1 + 1 + 3 + 59420 + 0.172138 + 378 + + 0 + 0 + + + + + -1 + 0 + ..\..\natneg\simpletest\natnegnitrocw\natnegnitrocw.mcp + + 457 + 317 + + + 392 + 344 + + + 1 + 0 + 4 + 59420 + 0.174097 + 378 + + 0 + 0 + + + + + -1 + 0 + ..\..\gt2\gt2testc\gt2nitrocw\gt2nitrocw.mcp + + 457 + 317 + + + 392 + 344 + + + 1 + 0 + 5 + 59420 + 0.152819 + 378 + + 0 + 0 + + + + + -1 + 0 + ..\..\gstats\statstest\gstatsnitrocw\gstatsnitrocw.mcp + + 457 + 317 + + + 392 + 344 + + + 1 + 1 + 1 + 59420 + 0.140610 + 378 + + 0 + 0 + + + + + -1 + 0 + ..\..\gstats\persisttest\persistnitrocw\persistnitrocw.mcp + + 457 + 317 + + + 392 + 344 + + + 1 + 1 + 2 + 59420 + 0.191763 + 378 + + 0 + 0 + + + + + -1 + 0 + ..\..\GP\gptestc\gpnitrocw\gpnitrocw.mcp + + 457 + 317 + + + 392 + 344 + + + 1 + 0 + 3 + 59420 + 0.175118 + 378 + + 0 + 0 + + + + + -1 + 0 + ..\..\ghttp\ghttpc\ghttpnitrocw\ghttpnitrocw.mcp + + 457 + 317 + + + 392 + 344 + + + 1 + 1 + 0 + 59420 + 0.163171 + 378 + + 0 + 0 + + + + + -1 + 0 + ..\..\Chat\chatc\chatnitrocw\chatnitrocw.mcp + + 457 + 317 + + + 392 + 344 + + + 1 + 1 + 5 + 59420 + 0.167125 + 378 + + 29736291 + 974026480 + + + + + 1073741824 + 35 + + 540 + 75 + + + 387 + 279 + + + 0 + + + + + + + + + + + GlobalSession, cpuARMLittle, osCWDS + C:\gamespy\GOA\GP\gptestc\gpnitrocw\bin\ARM9-TS\Debug\main.nef + 1 + + + + -2147483648 + 34 + + 4 + 23 + + + 528 + 334 + + + 0 + + + + + + + + + + + + + -2147483648 + 37 + + 4 + 22 + + + 358 + 327 + + + 0 + + + + + + + + + + + + + -2147483648 + 24 + + 6 + 81 + + + 566 + 477 + + + 0 + + + + + + + + + + + + diff --git a/code/gamespy/common/nitro/nitrosample/Nitro.lcf b/code/gamespy/common/nitro/nitrosample/Nitro.lcf new file mode 100644 index 00000000..aedd7f17 --- /dev/null +++ b/code/gamespy/common/nitro/nitrosample/Nitro.lcf @@ -0,0 +1,509 @@ +#--------------------------------------------------------------------------- +# Project: NitroSDK - tools - makelcf +# File: ARM9-TS.lcf.template +# +# Copyright 2003-2006 Nintendo. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# proprietary information of Nintendo of America Inc. and/or Nintendo +# Company Ltd., and are protected by Federal copyright law. They may +# not be disclosed to third parties or copied or duplicated in any form, +# in whole or in part, without the prior written consent of Nintendo. +# +# $Log: ARM9-TS.lcf.template,v $ +# Revision 1.37 07/20/2006 07:29:12 kitase_hirotake +# Added descriptions explaining the version section. +# +# Revision 1.36 2006/07/18 11:11:01 yasu +# There was an error in the method for obtaining the size +# of the OVERLAY region when checking for overflow of the TCM region using check.xTCM. +# Stopped adding the OVERLAY region because the correct calculation method is unknown at this time. +# +# Revision 1.35 2006/05/10 03:19:47 yasu +# Added support for the CodeWarrior 2.x overlay expansion +# +# Revision 1.34 04/06/2006 09:02:36 kitase_hirotake +# support for .itcm.bss and .dtcm.bss +# +# Revision 1.33 2006/03/30 23:59:22 yasu +# changed creation year +# +# Revision 1.32 2006/03/29 13:14:22 yasu +# support for overlays in CWVER 2.x +# +# Revision 1.31 11/24/2005 01:16:47 yada +# change start address of mainEX arena from 0x2400000 to 0x23e0000 +# +# Revision 1.30 2005/09/02 04:14:22 yasu +# Old symbols were redefined so they can be used even under SDK2.2 +# +# Revision 1.29 2005/08/31 09:34:57 yasu +# Corrected a problem where code would not function normally when using section names such as section_BSS +# +# Revision 1.28 2005/08/26 11:22:16 yasu +# overlay support for ITCM/DTCM +# +# Revision 1.27 2005/06/20 12:29:20 yasu +# Changed Surffix to Suffix +# +# Revision 1.26 06/14/2005 09:03:42 yada +# fix around minus value of SDK_STACKSIZE +# +# Revision 1.25 04/13/2005 12:51:00 terui +# Change SDK_AUTOLOAD.DTCM.START 0x027c0000 -> 0x027e0000 +# +# Revision 1.24 03/30/2005 00:02:14 yosizaki +# fix copyright header. +# +# Revision 1.23 2005/03/25 12:54:59 yasu +# Include .version section +# +# Revision 1.22 2004/10/03 02:00:56 yasu +# Output component file list for compstatic tool +# +# Revision 1.21 2004/09/27 05:28:21 yasu +# Support .sinit +# +# Revision 1.20 2004/09/09 11:49:20 yasu +# Support compstatic in default +# +# Revision 1.19 2004/09/06 06:40:00 yasu +# Add labels for digest +# +# Revision 1.18 2004/08/20 06:19:59 yasu +# DTCM moves to 0x027c0000 at default +# +# Revision 1.17 2004/08/02 10:38:53 yasu +# Add autoload-done callback address in overlaydefs +# +# Revision 1.16 2004/07/26 02:22:32 yasu +# Change DTCM address to 0x023c0000 +# +# Revision 1.15 2004/07/26 00:08:27 yasu +# Fix label of exception table +# +# Revision 1.14 2004/07/24 05:42:25 yasu +# Set default values for SDK_AUTOGEN_xTCM_START +# +# Revision 1.13 2004/07/23 11:32:14 yasu +# Define labels for __exception_table_start__ and _end__ +# +# Revision 1.12 2004/07/12 12:21:08 yasu +# Check size of ITCM/DTCM +# +# Revision 1.11 2004/07/10 04:10:26 yasu +# Support command 'Library' +# +# Revision 1.10 2004/07/02 08:13:02 yasu +# Support OBJECT( ) +# +# Revision 1.9 07/01/2004 12:54:38 yasu +# support ITCM/DTCM/WRAM autoload +# +# Revision 1.8 07/01/2004 10:41:46 yasu +# support autoload +# +# Revision 1.7 06/02/2004 07:35:37 yasu +# Set libsyscall.a in FORCE_ACTIVE +# Put NitroMain at the top of ROM image +# +# Revision 1.6 06/02/2004 04:56:28 yasu +# Change to fit to new ROM map of TS +# +# Revision 1.5 2004/06/01 06:12:00 miya +# add padding at top of ROM image. +# +# Revision 1.4 04/26/2004 12:16:48 yasu +# add KEEP_SECTIONS +# +# Revision 1.3 04/20/2004 07:41:32 yasu +# Set STATICINIT instead of .ctor temporarily +# +# Revision 1.2 04/14/2004 07:16:42 yasu +# add ALIGN(32) for convenience to handle cache line +# +# Revision 1.1 04/06/2004 01:59:54 yasu +# newly added +# +# $NoKeywords: $ +#--------------------------------------------------------------------------- +MEMORY +{ + main (RWX) : ORIGIN = 0x02000000, LENGTH = 0x0 > main.sbin + ITCM (RWX) : ORIGIN = 0x01ff8000, LENGTH = 0x0 >> main.sbin + DTCM (RWX) : ORIGIN = 0x027e0000, LENGTH = 0x0 >> main.sbin + binary.AUTOLOAD_INFO (RWX) : ORIGIN = 0, LENGTH = 0x0 >> main.sbin + binary.STATIC_FOOTER (RWX) : ORIGIN = 0, LENGTH = 0x0 >> main.sbin + + main_defs (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 > main_defs.sbin + main_table (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 > main_table.sbin + dummy.MAIN_EX (RW) : ORIGIN = 0x023e0000, LENGTH = 0x0 + arena.MAIN (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 + arena.MAIN_EX (RW) : ORIGIN = AFTER(dummy.MAIN_EX), LENGTH = 0x0 + arena.ITCM (RW) : ORIGIN = AFTER(ITCM), LENGTH = 0x0 + arena.DTCM (RW) : ORIGIN = AFTER(DTCM), LENGTH = 0x0 + binary.MODULE_FILES (RW) : ORIGIN = 0x0, LENGTH = 0x0 > component.files + check.ITCM (RWX) : ORIGIN = 0x0, LENGTH = 0x08000 > itcm.check + check.DTCM (RW) : ORIGIN = 0x0, LENGTH = 0x04000 > dtcm.check +} + +FORCE_ACTIVE +{ + SVC_SoftReset +} + +KEEP_SECTION +{ + .sinit +} + +SECTIONS +{ + ############################ STATIC ################################# + .main: + { + ALIGNALL(4); . = ALIGN(32); # Fit to cache line + + # + # TEXT BLOCK: READ ONLY + # + SDK_STATIC_START =.; + SDK_STATIC_TEXT_START =.; + #:::::::::: text/rodata + libsyscall.a (.text) + crt0.o (.text) + crt0.o (.rodata) + # + # Added .version section. + # The info in this section will be used for lotcheck. + # Make sure it remains at this locale. + # + * (.version) + OBJECT(NitroMain,*) + GROUP(ROOT) (.text) + . = ALIGN(4); + * (.exception) + . = ALIGN(4); + SDK_STATIC_ETABLE_START =.; + EXCEPTION + SDK_STATIC_ETABLE_END =.; + . = ALIGN(4); + GROUP(ROOT) (.init) + . = ALIGN(4); + GROUP(ROOT) (.rodata) + . = ALIGN(4); + + SDK_STATIC_SINIT_START =.; + #:::::::::: ctor + GROUP(ROOT) (.ctor) + GROUP(ROOT) (.sinit) + WRITEW 0; + #:::::::::: ctor + SDK_STATIC_SINIT_END =.; + + #:::::::::: text/rodata + . = ALIGN(32); + SDK_STATIC_TEXT_END =.; + + # + # DATA BLOCK: READ WRITE + # + SDK_STATIC_DATA_START =.; + #:::::::::: data + GROUP(ROOT) (.sdata) + . = ALIGN(4); + GROUP(ROOT) (.data) + . = ALIGN(4); + SDK_OVERLAY_DIGEST =.; + # NO DIGEST + SDK_OVERLAY_DIGEST_END =.; + #:::::::::: data + . = ALIGN(32); + SDK_STATIC_DATA_END =.; + SDK_STATIC_END =.; + + SDK_STATIC_TEXT_SIZE = SDK_STATIC_TEXT_END - SDK_STATIC_TEXT_START; + SDK_STATIC_DATA_SIZE = SDK_STATIC_DATA_END - SDK_STATIC_DATA_START; + SDK_STATIC_SIZE = SDK_STATIC_END - SDK_STATIC_START; + __sinit__ = SDK_STATIC_SINIT_START; # for static initializer + __exception_table_start__ = SDK_STATIC_ETABLE_START; # for exception table + __exception_table_end__ = SDK_STATIC_ETABLE_END; # for exception table + } > main + + .main.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_STATIC_BSS_START =.; + #:::::::::: bss + GROUP(ROOT) (.sbss) + . = ALIGN(4); + GROUP(ROOT) (.bss) + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_STATIC_BSS_END = .; + SDK_STATIC_BSS_SIZE = SDK_STATIC_BSS_END - SDK_STATIC_BSS_START; + + } >> main + + + ############################ AUTOLOADS ############################## + SDK_AUTOLOAD.ITCM.START = 0x01ff8000; + SDK_AUTOLOAD.ITCM.END = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD.ITCM.BSS_END = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD.ITCM.SIZE = 0; + SDK_AUTOLOAD.ITCM.BSS_SIZE = 0; + SDK_AUTOLOAD.DTCM.START = 0x027e0000; + SDK_AUTOLOAD.DTCM.END = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD.DTCM.BSS_END = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD.DTCM.SIZE = 0; + SDK_AUTOLOAD.DTCM.BSS_SIZE = 0; + SDK_AUTOLOAD_START = SDK_STATIC_END; + SDK_AUTOLOAD_SIZE = 0; + SDK_AUTOLOAD_NUMBER = 2; + + .ITCM: + { + ALIGNALL(4); . = ALIGN(32); + + # + # TEXT BLOCK: READ ONLY + # + SDK_AUTOLOAD_ITCM_ID =0; + SDK_AUTOLOAD.ITCM.ID =0; + SDK_AUTOLOAD.ITCM.START =.; + SDK_AUTOLOAD.ITCM.TEXT_START =.; + #:::::::::: text/rodata + . = ALIGN(4); + * (.itcm) + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: text/rodata + SDK_AUTOLOAD.ITCM.TEXT_END =.; + + # + # DATA BLOCK: READ WRITE BLOCK + # + SDK_AUTOLOAD.ITCM.DATA_START =.; + #:::::::::: data + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: data + . = ALIGN(32); + SDK_AUTOLOAD.ITCM.DATA_END =.; + SDK_AUTOLOAD.ITCM.END =.; + + SDK_AUTOLOAD.ITCM.TEXT_SIZE = SDK_AUTOLOAD.ITCM.TEXT_END - SDK_AUTOLOAD.ITCM.TEXT_START; + SDK_AUTOLOAD.ITCM.DATA_SIZE = SDK_AUTOLOAD.ITCM.DATA_END - SDK_AUTOLOAD.ITCM.DATA_START; + SDK_AUTOLOAD.ITCM.SIZE = SDK_AUTOLOAD.ITCM.END - SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SDK_AUTOLOAD.ITCM.SIZE; + + } > ITCM + + .ITCM.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_AUTOLOAD.ITCM.BSS_START = .; + #:::::::::: bss + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + * (.itcm.bss) + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_AUTOLOAD.ITCM.BSS_END = .; + + SDK_AUTOLOAD.ITCM.BSS_SIZE = SDK_AUTOLOAD.ITCM.BSS_END - SDK_AUTOLOAD.ITCM.BSS_START; + + } >> ITCM + + .DTCM: + { + ALIGNALL(4); . = ALIGN(32); + + # + # TEXT BLOCK: READ ONLY + # + SDK_AUTOLOAD_DTCM_ID =1; + SDK_AUTOLOAD.DTCM.ID =1; + SDK_AUTOLOAD.DTCM.START =.; + SDK_AUTOLOAD.DTCM.TEXT_START =.; + #:::::::::: text/rodata + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: text/rodata + SDK_AUTOLOAD.DTCM.TEXT_END =.; + + # + # DATA BLOCK: READ WRITE BLOCK + # + SDK_AUTOLOAD.DTCM.DATA_START =.; + #:::::::::: data + . = ALIGN(4); + . = ALIGN(4); + * (.dtcm) + . = ALIGN(4); + #:::::::::: data + . = ALIGN(32); + SDK_AUTOLOAD.DTCM.DATA_END =.; + SDK_AUTOLOAD.DTCM.END =.; + + SDK_AUTOLOAD.DTCM.TEXT_SIZE = SDK_AUTOLOAD.DTCM.TEXT_END - SDK_AUTOLOAD.DTCM.TEXT_START; + SDK_AUTOLOAD.DTCM.DATA_SIZE = SDK_AUTOLOAD.DTCM.DATA_END - SDK_AUTOLOAD.DTCM.DATA_START; + SDK_AUTOLOAD.DTCM.SIZE = SDK_AUTOLOAD.DTCM.END - SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SDK_AUTOLOAD.DTCM.SIZE; + + } > DTCM + + .DTCM.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_AUTOLOAD.DTCM.BSS_START = .; + #:::::::::: bss + . = ALIGN(4); + . = ALIGN(4); + * (.dtcm.bss) + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_AUTOLOAD.DTCM.BSS_END = .; + + SDK_AUTOLOAD.DTCM.BSS_SIZE = SDK_AUTOLOAD.DTCM.BSS_END - SDK_AUTOLOAD.DTCM.BSS_START; + + } >> DTCM + + + SDK_AUTOLOAD_ITCM_START = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD_ITCM_END = SDK_AUTOLOAD.ITCM.END; + SDK_AUTOLOAD_ITCM_BSS_END = SDK_AUTOLOAD.ITCM.BSS_END; + SDK_AUTOLOAD_ITCM_SIZE = SDK_AUTOLOAD.ITCM.SIZE; + SDK_AUTOLOAD_ITCM_BSS_SIZE = SDK_AUTOLOAD.ITCM.BSS_SIZE; + SDK_AUTOLOAD_DTCM_START = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD_DTCM_END = SDK_AUTOLOAD.DTCM.END; + SDK_AUTOLOAD_DTCM_BSS_END = SDK_AUTOLOAD.DTCM.BSS_END; + SDK_AUTOLOAD_DTCM_SIZE = SDK_AUTOLOAD.DTCM.SIZE; + SDK_AUTOLOAD_DTCM_BSS_SIZE = SDK_AUTOLOAD.DTCM.BSS_SIZE; + + ############################ AUTOLOAD_INFO ########################## + .binary.AUTOLOAD_INFO: + { + WRITEW ADDR(.ITCM); + WRITEW SDK_AUTOLOAD.ITCM.SIZE; + WRITEW SDK_AUTOLOAD.ITCM.BSS_SIZE; + WRITEW ADDR(.DTCM); + WRITEW SDK_AUTOLOAD.DTCM.SIZE; + WRITEW SDK_AUTOLOAD.DTCM.BSS_SIZE; + } > binary.AUTOLOAD_INFO + + SDK_AUTOLOAD_LIST = SDK_AUTOLOAD_START + SDK_AUTOLOAD_SIZE; + SDK_AUTOLOAD_LIST_END = SDK_AUTOLOAD_START + SDK_AUTOLOAD_SIZE + SIZEOF(.binary.AUTOLOAD_INFO); + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SIZEOF(.binary.AUTOLOAD_INFO); + + ############################ STATIC_FOOTER ########################## + .binary.STATIC_FOOTER: + { + WRITEW 0xdec00621; # LE(0x2106C0DE) = NITRO CODE + WRITEW _start_ModuleParams - ADDR(.main); + WRITEW 0; # NO DIGEST + } > binary.STATIC_FOOTER + + ############################ OVERLAYS ############################### + SDK_OVERLAY_NUMBER = 0; + + + ############################ MAIN EX ################################## + # MAIN EX Area + .dummy.MAIN_EX: + { + . = ALIGN(32); + } > dummy.MAIN_EX + + ############################ ARENA ################################## + .arena.MAIN: + { + . = ALIGN(32); + SDK_SECTION_ARENA_START =.; + } > arena.MAIN + + .arena.MAIN_EX: + { + . = ALIGN(32); + SDK_SECTION_ARENA_EX_START =.; + } > arena.MAIN_EX + + .arena.ITCM: + { + . = ALIGN(32); + SDK_SECTION_ARENA_ITCM_START =.; + } > arena.ITCM + + .arena.DTCM: + { + . = ALIGN(32); + SDK_SECTION_ARENA_DTCM_START =.; + } > arena.DTCM + + ############################ OVERLAYDEFS ############################ + .main_defs: + { + ### main module information + WRITEW ADDR(.main); # load address + WRITEW _start; # entry address + WRITEW SDK_STATIC_SIZE + SDK_AUTOLOAD_SIZE; # size of module + WRITEW _start_AutoloadDoneCallback; # callback autoload done + + ### overlay filename + + } > main_defs + + + ############################ OVERLAYTABLE ########################### + .main_table: + { + + } > main_table + + + ############################ OTHERS ################################# + SDK_MAIN_ARENA_LO = SDK_SECTION_ARENA_START; + SDK_IRQ_STACKSIZE = 4096; # allocated in DTCM + SDK_SYS_STACKSIZE = 0; # when 0 means all remains of DTCM + + # Module filelist + .binary.MODULE_FILES: + { + WRITES ("main.sbin"); + WRITES ("main_defs.sbin"); + WRITES ("main_table.sbin"); + } > binary.MODULE_FILES + + # ITCM/DTCM size checker => check AUTOLOAD_ITCM/DTCM + .check.ITCM: + { + . = . + SDK_AUTOLOAD_ITCM_SIZE + SDK_AUTOLOAD_ITCM_BSS_SIZE; + } > check.ITCM + + SDK_SYS_STACKSIZE_SIGN = (SDK_SYS_STACKSIZE < 0x80000000) * 2 - 1; + .check.DTCM: + { + . = . + SDK_AUTOLOAD_DTCM_SIZE + SDK_AUTOLOAD_DTCM_BSS_SIZE; + . = . + SDK_IRQ_STACKSIZE + SDK_SYS_STACKSIZE * SDK_SYS_STACKSIZE_SIGN; + } > check.DTCM + +} diff --git a/code/gamespy/common/nitro/nitrosample/ROM-TS.rsf b/code/gamespy/common/nitro/nitrosample/ROM-TS.rsf new file mode 100644 index 00000000..cec9e1db --- /dev/null +++ b/code/gamespy/common/nitro/nitrosample/ROM-TS.rsf @@ -0,0 +1,116 @@ +#---------------------------------------------------------------------------- +# Project: NitroSDK - include +# File: ROM-TS.lsf +# +# Copyright 2003-2005 Nintendo. All rights reserved. +# +# These coded insructions, statements, and computer programs contain +# proprietary information of Nintendo of America Inc. and/or Nintendo +# Company Ltd., and are protected by Federal copyright law. They may +# not be disclosed to third parties or copied or duplicated in any form, +# in whole or in part, without the prior written consent of Nintendo. +# +# $Log: ROM-TS.rsf,v $ +# Revision 1.6 2005/04/05 23:52:58 yosizaki +# fix copyright date. +# +# Revision 1.5 2005/04/05 12:16:10 yosizaki +# support RomSpeedType parameter. +# +# Revision 1.4 2004/09/21 02:18:49 yasu +# Add default banner +# +# Revision 1.3 2004/09/09 11:39:09 yasu +# Unified ROM-TS and ROM-TS-C, also ROM-TEG and ROM-TEG-C +# +# Revision 1.2 2004/05/26 12:03:38 yasu +# add :r option to get basename for supporting IDE with makerom +# +# Revision 1.1 2004/04/06 01:59:59 yasu +# newly added +# +# $NoKeywords: $ +#---------------------------------------------------------------------------- +# +# Nitro ROM SPEC FILE +# + +Arm9 +{ + Static "$(MAKEROM_ARM9:r).sbin$(COMPSUFFIX9)" + OverlayDefs "$(MAKEROM_ARM9:r)_defs.sbin$(COMPSUFFIX9)" + OverlayTable "$(MAKEROM_ARM9:r)_table.sbin$(COMPSUFFIX9)" + Elf "$(MAKEROM_ARM9:r).nef" +} + +Arm7 +{ + Static "$(MAKEROM_ARM7:r).sbin$(COMPSUFFIX7)" + OverlayDefs "$(MAKEROM_ARM7:r)_defs.sbin$(COMPSUFFIX7)" + OverlayTable "$(MAKEROM_ARM7:r)_table.sbin$(COMPSUFFIX7)" + Elf "$(MAKEROM_ARM7:r).nef" +} + +Property +{ + ### + ### Settings for FinalROM + ### + #### BEGIN + # + # TITLE NAME: Your product name within 12bytes + # + #TitleName "YourAppName" + + # + # MAKER CODE: Your company ID# in 2 ascii words + # issued by NINTENDO + # + #MakerCode "00" + + # + # REMASTER VERSION: Mastering version + # + #RomVersion 0 + + # + # ROM SPEED TYPE: [MROM/1TROM/UNDEFINED] + # + RomSpeedType $(MAKEROM_ROMSPEED) + + # + # ROM SIZE: in bit [64M/128M/256M/512M/1G/2G] + # + #RomSize 128M + #RomSize 256M + + # + # ROM PADDING: TRUE if finalrom + # + #RomFootPadding TRUE + + # + # ROM HEADER TEMPLATE: Provided to every product by NINTENDO + # + #RomHeaderTemplate ./etc/rom_header.template.sbin + + # + # BANNER FILE: generated from Banner Spec File + # + #BannerFile ./etc/myGameBanner.bnr + BannerFile $(NITROSDK_ROOT)/include/nitro/specfiles/default.bnr + + ### + ### + ### + #### END +} + +RomSpec +{ + Offset 0x00000000 + Segment ALL + HostRoot $(MAKEROM_ROMROOT) + Root / + File $(MAKEROM_ROMFILES) +} diff --git a/code/gamespy/common/nitro/nitrosample/nitrosample.c b/code/gamespy/common/nitro/nitrosample/nitrosample.c new file mode 100644 index 00000000..c129025f --- /dev/null +++ b/code/gamespy/common/nitro/nitrosample/nitrosample.c @@ -0,0 +1,606 @@ +#include "../../nonport.h" +#include "../../peer/peer.h" +#include "../../peer/peerMain.h" +#include "menu.h" +#include "screen.h" + +/************************************************************************/ +/* Sample Menu */ +/************************************************************************/ + +static const char mscHostServer[] = "Host Server"; +static const char mscListServers[] = "List Servers"; +static const char mscExit[] = "Exit"; +static const char mscStartHosting[] = "Start Hosting"; +static const char mscCancel[] = "Cancel"; +static const char mscStopHosting[] = "Stop Hosting"; +static const char mscViewHostInfo[] = "View Host Info"; +static const char mscMainMenu[] = "Main Menu"; +static const char mscBack[] = "Back"; + +static void CheckingBackendAvailabilityInit(void); +static void CheckingBackendAvailabilityChose(const char * choice); +static void CheckingBackendAvailabilityThink(void); + +static MenuScreenConfiguration msCheckingBackendAvailability = +{ + "Checking Backend Availability", + { + { mscCancel } + }, + CheckingBackendAvailabilityInit, + CheckingBackendAvailabilityChose, + CheckingBackendAvailabilityThink, + SCREEN_OPTION_ANIMATED +}; + +static void BackendUnavailableChose(const char * choice); + +static MenuScreenConfiguration msBackendUnavailable = +{ + "Backend Unavailable", + { + { mscExit } + }, + NULL, + BackendUnavailableChose +}; + +static void FailedToIntializePeerSDKChose(const char * choice); + +static MenuScreenConfiguration msFailedToIntializePeerSDK = +{ + "Failed to Initialize Peer SDK", + { + { mscExit } + }, + NULL, + FailedToIntializePeerSDKChose +}; + +static void HostOrListChose(const char * choice); + +static MenuScreenConfiguration msHostOrList = +{ + "Host or List", + { + { mscHostServer }, + { mscListServers }, + { mscExit } + }, + NULL, + HostOrListChose +}; + +static void ChooseHostingNameInit(void); +static void ChooseHostingNameChose(const char * choice); + +static MenuScreenConfiguration msChooseHostingName = +{ + "Choose Hosting Name", + { + { mscStartHosting }, + { mscCancel} + }, + ChooseHostingNameInit, + ChooseHostingNameChose, + NULL, + SCREEN_OPTION_KEYBOARD +}; + +static void HostingServerInit(void); +static void HostingServerChose(const char * choice); +static void HostingServerThink(void); + +static MenuScreenConfiguration msHostingServer = +{ + "Hosting Server", + { + { mscStopHosting } + }, + HostingServerInit, + HostingServerChose, + HostingServerThink, + SCREEN_OPTION_EXTRAS_CENTERED +}; + +static void ErrorStartingHostInit(void); +static void ErrorStartingHostChose(const char * choice); + +static MenuScreenConfiguration msErrorStartingHost = +{ + "Error Starting Host", + { + { mscMainMenu } + }, + ErrorStartingHostInit, + ErrorStartingHostChose +}; + +static void ListingHostsInit(void); +static void ListingHostsChose(const char * choice); +static void ListingHostsThink(void); + +static MenuScreenConfiguration msListingHosts = +{ + "Listing Hosts", + { + { mscViewHostInfo, CHOICE_OPTION_NEEDS_LIST_SELECTION }, + { mscMainMenu } + }, + ListingHostsInit, + ListingHostsChose, + ListingHostsThink, + SCREEN_OPTION_LIST | SCREEN_OPTION_ANIMATED +}; + +static void ErrorListingHostsInit(void); +static void ErrorListingHostsChose(const char * choice); + +static MenuScreenConfiguration msErrorListingHosts = +{ + "Error Listing Hosts", + { + { mscMainMenu } + }, + ErrorListingHostsInit, + ErrorListingHostsChose +}; + +static void ViewHostInfoInit(void); +static void ViewHostInfoChose(const char * choice); + +static MenuScreenConfiguration msViewHostInfo = +{ + "View Host Info", + { + { mscBack } + }, + ViewHostInfoInit, + ViewHostInfoChose, + NULL, + SCREEN_OPTION_EXTRAS_CENTERED +}; + +/************************************************************************/ +/* Sample Funcs */ +/************************************************************************/ + + +#define GAMENAME "gmtest" +#define SECRETKEY "HA6zkS" + +#define MAX_HOSTNAME_LEN MAX_KEYBOARD_TEXT_LEN + +static char Hostname[MAX_HOSTNAME_LEN + 1] = "Nitro Host"; +static char FailureReason[MAX_EXTRA_TEXT_STRINGS][MAX_EXTRA_TEXT_STRING_LEN + 1]; +static qr2_error_t QR2AddError; +static BOOL ListingError; +static PEER Peer; +static SBServer SelectedServer; +static int Mapname; +static int Gametype; + +static const char Mapnames[][64] = { "Rome", "London", "New York" }; +static const char Gametypes[][64] = { "Deathmatch", "Teamplay", "1 on 1" }; +static const int NumMapnames = (sizeof(Mapnames) / sizeof(Mapnames[0])); +static const int NumGametypes = (sizeof(Gametypes) / sizeof(Gametypes[0])); + +static gsi_u32 DebugLog2(gsi_u32 level) +{ + gsi_u32 total = 0; + while (level > 1) + { + level = level >> 1; + total++; + } + return total; +} + +static void DebugCallback(GSIDebugCategory category, GSIDebugType type, + GSIDebugLevel level, const char * format, va_list params) +{ + // Output line prefix + Printf("[%s][%s][%s] ", + gGSIDebugCatStrings[category], + gGSIDebugTypeStrings[type], + gGSIDebugLevelStrings[DebugLog2(level)]); + + // Output to file + VPrintf(format, + params); +} + +int main(int argc, char * argv) +{ + gsSetDebugCallback(DebugCallback); + gsSetDebugLevel(GSIDebugCat_All, GSIDebugType_All, GSIDebugLevel_Verbose); + + GSI_UNUSED(argc); + GSI_UNUSED(argv); + + StartMenuScreen(&msCheckingBackendAvailability); + + return 0; +} + +static const char * QR2ErrorToString(qr2_error_t result) +{ + if(result == e_qrnoerror) + return "no error"; + if(result == e_qrwsockerror) + return "socket error"; + if(result == e_qrbinderror) + return "bind error"; + if(result == e_qrdnserror) + return "dns lookup error"; + if(result == e_qrconnerror) + return "nat error"; + if(result == e_qrnochallengeerror) + return "no challenge received"; + return "unknown error"; +} + +static void SetFailureReason(const char * reason) +{ + int i; + const char * line; + const char * nextLine; + size_t len; + + memset(FailureReason, 0, sizeof(FailureReason)); + + nextLine = reason; + + for(i = 0 ; i < MAX_EXTRA_TEXT_STRINGS ; i++) + { + line = nextLine; + if(line[0] == '\0') + return; + + nextLine = strchr(line, '\n'); + if(nextLine && ((nextLine - line) < MAX_EXTRA_TEXT_STRING_LEN)) + { + len = (size_t)(nextLine - line); + nextLine++; + } + else if(strlen(line) >= MAX_EXTRA_TEXT_STRING_LEN) + { + len = MAX_EXTRA_TEXT_STRING_LEN; + nextLine = (line + MAX_EXTRA_TEXT_STRING_LEN); + } + else + { + len = strlen(line); + nextLine = ""; + } + + memcpy(FailureReason[i], line, len); + FailureReason[i][len] = '\0'; + } +} + +static void KeyListCallback(PEER peer, qr2_key_type type, qr2_keybuffer_t buffer, void * param) +{ + switch(type) + { + case key_server: + qr2_keybuffer_add(buffer, HOSTNAME_KEY); + qr2_keybuffer_add(buffer, MAPNAME_KEY); + qr2_keybuffer_add(buffer, GAMETYPE_KEY); + break; + case key_player: + break; + case key_team: + break; + } + + GSI_UNUSED(peer); + GSI_UNUSED(param); +} + +static void ServerKeyCallback(PEER peer, int key, qr2_buffer_t buffer, void * param) +{ + switch(key) + { + case HOSTNAME_KEY: + qr2_buffer_add(buffer, Hostname); + break; + case MAPNAME_KEY: + qr2_buffer_add(buffer, Mapnames[Mapname]); + break; + case GAMETYPE_KEY: + qr2_buffer_add(buffer, Gametypes[Gametype]); + break; + default: + qr2_buffer_add(buffer, _T("")); + } + + GSI_UNUSED(peer); + GSI_UNUSED(param); +} + +static void AddErrorCallback(PEER peer, qr2_error_t error, gsi_char * errorString, void * param) +{ + QR2AddError = error; + + SetFailureReason(errorString); + + GSI_UNUSED(peer); + GSI_UNUSED(param); +} + +static void ListingGamesCallback(PEER peer, PEERBool success, const gsi_char * name, SBServer server, + PEERBool staging, int msg, int progress, void * param) +{ + int num; + int i; + MenuScreen * screen = GetMenuScreen(); + piConnection * connection = (piConnection *)peer; + + if(success == PEERFalse) + { + ListingError = PEERTrue; + return; + } + + num = SBServerListCount(&connection->gameList); + + for(i = 0 ; i < MAX_LIST_STRINGS ; i++) + { + if(i < num) + { + server = SBServerListNth(&connection->gameList, i); + strncpy(screen->list[i], SBServerGetStringValue(server, "hostname", "[no name]"), MAX_LIST_STRING_LEN + 1); + screen->list[i][MAX_LIST_STRING_LEN] = '\0'; + } + else + { + screen->list[i][0] = '\0'; + } + } + + GSI_UNUSED(name); + GSI_UNUSED(staging); + GSI_UNUSED(msg); + GSI_UNUSED(progress); + GSI_UNUSED(param); +} + +/************************************************************************/ +/* Sample Menu Funcs */ +/************************************************************************/ + +static void CheckingBackendAvailabilityInit(void) +{ + GSIStartAvailableCheck(GAMENAME); +} +static void CheckingBackendAvailabilityChose(const char * choice) +{ + if(choice == mscCancel) + ExitMenu(); +} +static void CheckingBackendAvailabilityThink(void) +{ + GSIACResult result = GSIAvailableCheckThink(); + PEERCallbacks callbacks; + + if(result == GSIACWaiting) + return; + + if(result != GSIACAvailable) + { + SetNextMenuScreen(&msBackendUnavailable); + return; + } + + memset(&callbacks, 0, sizeof(PEERCallbacks)); + callbacks.qrKeyList = KeyListCallback; + callbacks.qrServerKey = ServerKeyCallback; + callbacks.qrAddError = AddErrorCallback; + + Peer = peerInitialize(&callbacks); + if(!Peer) + { + SetNextMenuScreen(&msFailedToIntializePeerSDK); + return; + } + + if(!peerSetTitle(Peer, GAMENAME, SECRETKEY, GAMENAME, SECRETKEY, 0, 10, PEERTrue, NULL, NULL)) + { + peerShutdown(Peer); + SetNextMenuScreen(&msFailedToIntializePeerSDK); + return; + } + + SetNextMenuScreen(&msHostOrList); +} + +static void BackendUnavailableChose(const char * choice) +{ + if(choice == mscExit) + ExitMenu(); +} + +static void FailedToIntializePeerSDKChose(const char * choice) +{ + if(choice == mscExit) + ExitMenu(); +} + +static void HostOrListChose(const char * choice) +{ + if(choice == mscHostServer) + SetNextMenuScreen(&msChooseHostingName); + else if(choice == mscListServers) + SetNextMenuScreen(&msListingHosts); + else if(choice == mscExit) + { + peerShutdown(Peer); + ExitMenu(); + } +} + +static void ChooseHostingNameInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + + strncpy(screen->keyboardText, Hostname, MAX_KEYBOARD_TEXT_LEN + 1); + screen->keyboardText[MAX_KEYBOARD_TEXT_LEN] = '\0'; +} +static void ChooseHostingNameChose(const char * choice) +{ + MenuScreen * screen = GetMenuScreen(); + + strncpy(Hostname, screen->keyboardText, MAX_HOSTNAME_LEN + 1); + Hostname[MAX_HOSTNAME_LEN] = '\0'; + + if(choice == mscStartHosting) + SetNextMenuScreen(&msHostingServer); + else if(choice == mscCancel) + SetNextMenuScreen(&msHostOrList); +} + +static void HostingServerInit(void) +{ + PEERBool result; + MenuScreen * screen = GetMenuScreen(); + int line = 0; + + QR2AddError = e_qrnoerror; + + srand((unsigned int)time(NULL)); + Mapname = (rand() % NumMapnames); + Gametype = (rand() % NumGametypes); + + result = peerStartReporting(Peer); + + if(result == PEERFalse) + { + SetFailureReason("Failed to initialize hosting"); + SetNextMenuScreen(&msErrorStartingHost); + return; + } + + snprintf(screen->extraText[line++], MAX_EXTRA_TEXT_STRING_LEN + 1, "Mapname: %s", Mapnames[Mapname]); + snprintf(screen->extraText[line++], MAX_EXTRA_TEXT_STRING_LEN + 1, "Gametype: %s", Gametypes[Gametype]); +} +static void HostingServerChose(const char * choice) +{ + if(choice == mscStopHosting) + { + peerStopGame(Peer); + SetNextMenuScreen(&msHostOrList); + } +} +static void HostingServerThink(void) +{ + peerThink(Peer); + + if(QR2AddError != e_qrnoerror) + { + char text[256]; + + peerStopGame(Peer); + snprintf(text, sizeof(text), "Failed to initialize hosting:\n%s", QR2ErrorToString(QR2AddError)); + SetFailureReason(text); + SetNextMenuScreen(&msErrorStartingHost); + } +} + +static void ErrorStartingHostInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + + memcpy(screen->extraText, FailureReason, sizeof(FailureReason)); +} +static void ErrorStartingHostChose(const char * choice) +{ + if(choice == mscMainMenu) + SetNextMenuScreen(&msHostOrList); +} + +static void ListingHostsInit(void) +{ + const unsigned char keys[] = { HOSTNAME_KEY, MAPNAME_KEY, GAMETYPE_KEY }; + ListingError = FALSE; + + peerStartListingGames(Peer, keys, sizeof(keys), NULL, ListingGamesCallback, NULL); +} +static void ListingHostsChose(const char * choice) +{ + if(choice == mscViewHostInfo) + { + MenuScreen * screen = GetMenuScreen(); + piConnection * connection = (piConnection *)Peer; + + SelectedServer = SBServerListNth(&connection->gameList, screen->listSelection); + SetNextMenuScreen(&msViewHostInfo); + } + else if(choice == mscMainMenu) + { + peerStopListingGames(Peer); + SetNextMenuScreen(&msHostOrList); + } +} +static void ListingHostsThink(void) +{ + peerThink(Peer); + + if(ListingError == TRUE) + { + peerStopListingGames(Peer); + SetFailureReason("Failed to list hosts"); + SetNextMenuScreen(&msErrorListingHosts); + } +} + +static void ErrorListingHostsInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + + memcpy(screen->extraText, FailureReason, sizeof(FailureReason)); +} +static void ErrorListingHostsChose(const char * choice) +{ + if(choice == mscMainMenu) + SetNextMenuScreen(&msHostOrList); +} + +static void ViewHostInfoInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + int line = 0; + + // hostname + strcpy(screen->extraText[line++], "[hostname]"); + snprintf(screen->extraText[line++], MAX_EXTRA_TEXT_STRING_LEN, + " %s", SBServerGetStringValue(SelectedServer, "hostname", "N/A")); + + // public address + strcpy(screen->extraText[line++], "[public address]"); + snprintf(screen->extraText[line++], MAX_EXTRA_TEXT_STRING_LEN, + " %s:%d", + SBServerGetPublicAddress(SelectedServer), SBServerGetPublicQueryPort(SelectedServer)); + + // private address + strcpy(screen->extraText[line++], "[private address]"); + snprintf(screen->extraText[line++], MAX_EXTRA_TEXT_STRING_LEN, + " %s:%d", + SBServerGetPrivateAddress(SelectedServer), SBServerGetPrivateQueryPort(SelectedServer)); + + // mapname + strcpy(screen->extraText[line++], "[mapname]"); + snprintf(screen->extraText[line++], MAX_EXTRA_TEXT_STRING_LEN, + " %s", SBServerGetStringValue(SelectedServer, "mapname", "N/A")); + + // gametype + strcpy(screen->extraText[line++], "[gametype]"); + snprintf(screen->extraText[line++], MAX_EXTRA_TEXT_STRING_LEN, + " %s", SBServerGetStringValue(SelectedServer, "gametype", "N/A")); +} +static void ViewHostInfoChose(const char * choice) +{ + if(choice == mscBack) + SetNextMenuScreen(&msListingHosts); +} diff --git a/code/gamespy/common/nitro/screen.c b/code/gamespy/common/nitro/screen.c new file mode 100644 index 00000000..03404cc3 --- /dev/null +++ b/code/gamespy/common/nitro/screen.c @@ -0,0 +1,237 @@ +#include "..\nonport.h" +#include "screen.h" +#include "font.h" + +static u16 gTopScreen[SCREEN_HEIGHT * SCREEN_WIDTH]; +static u16 gBottomScreen[SCREEN_HEIGHT * SCREEN_WIDTH]; +static int gPos; +static int gPrintMode = PRINT_TO_DEBUGGER; + +static void VBlankIntr(void) +{ + // Reflect virtual screen to VRAM + DC_FlushRange( gTopScreen , sizeof( gTopScreen ) ); + DC_FlushRange( gBottomScreen , sizeof( gBottomScreen ) ); + GX_LoadBG0Scr( gTopScreen , 0 , sizeof( gTopScreen ) ); + GXS_LoadBG0Scr( gBottomScreen , 0 , sizeof( gBottomScreen ) ); + + // Set IRQ check flag + OS_SetIrqCheckFlag( OS_IE_V_BLANK ); +} + +void ClearTopScreen(void) +{ + MI_CpuClearFast( (void*)gTopScreen , sizeof( gTopScreen ) ); +// DC_FlushRange( gTopScreen , sizeof( gTopScreen ) ); +} + +void ClearBottomScreen(void) +{ + MI_CpuClearFast( (void*)gBottomScreen , sizeof( gBottomScreen ) ); +// DC_FlushRange( gBottomScreen , sizeof( gBottomScreen ) ); +} + +void ClearScreens(void) +{ + ClearTopScreen(); + ClearBottomScreen(); +} + +static void ScrollTopScreen(void) +{ + int i; + for(i = 0 ; i < (SCREEN_HEIGHT - 1) ; i++) + MI_CpuCopyFast((void*)&gTopScreen[SCREEN_WIDTH*(i+1)], (void*)&gTopScreen[SCREEN_WIDTH*i], SCREEN_WIDTH*sizeof(gTopScreen[0])); + MI_CpuClearFast((void*)&gTopScreen[(SCREEN_HEIGHT-1)*SCREEN_WIDTH], SCREEN_WIDTH*sizeof(gTopScreen[0])); +} + +void PrintChar(char c) +{ + u8 palette = 0xf; + + if(c == '\r') + return; + + if(gPos == SCREEN_WIDTH) + { + ScrollTopScreen(); + gPos = 0; + if(c == '\n') + return; + } + + if(c == '\n') + { + gPos = SCREEN_WIDTH; + } + else + { + gTopScreen[((SCREEN_HEIGHT - 1) * SCREEN_WIDTH) + gPos] = (u16)((palette << 12) | c); + gPos++; + } +} + +void Printf(const char* format, ...) +{ + va_list vlist; + + va_start(vlist, format); + VPrintf(format, vlist); + va_end(vlist); +} + +void VPrintf(const char* format, va_list args) +{ + if(gPrintMode & PRINT_TO_SCREEN) + { + static char text[2048]; + int i; + + vsnprintf(text, sizeof(text) - 1, format, args); + text[sizeof(text) - 1] = '\0'; + + for(i = 0 ; text[i] ; i++) + PrintChar(text[i]); + + SVC_WaitVBlankIntr(); + } + if(gPrintMode & PRINT_TO_DEBUGGER) + { + OS_VPrintf(format, args); + } +} + +void SetPrintMode(int mode) +{ + gPrintMode = mode; +} + +static void SetScreenLine(u16 * screen, int line, int offset, ScreenColor color, const char * text, int pos, int range, ScreenColor posColor) +{ + u8 palette = (u8)color; + u8 posPalette = (u8)posColor; + u16 val; + int i; + char c; + BOOL inRange; + + MI_CpuClearFast((void*)&screen[line*SCREEN_WIDTH], SCREEN_WIDTH*sizeof(gTopScreen[0])); + + for(i = 0 ; i < SCREEN_WIDTH ; i++) + { + c = text[i]; + + if(c == '\0') + break; + + inRange = ((i >= pos) && (i < (pos + range)))?TRUE:FALSE; + + val = (u16)(((inRange?posPalette:palette) << 12) | c); + screen[(line * SCREEN_WIDTH) + offset + i] = val; + } +} + +static void SetScreenLineCentered(u16 * screen, int line, ScreenColor color, const char * text) +{ + int len; + int offset; + + len = (int)strlen(text); + if(len < SCREEN_WIDTH) + offset = (((SCREEN_WIDTH - len) & ~1) / 2); + else + offset = 0; + + SetScreenLine(screen, line, offset, color, text, 0, 0, color); +} + +void SetTopScreenLine(int line, ScreenColor color, const char * text) +{ + SetScreenLine(gTopScreen, line, 0, color, text, 0, 0, color); +} + +void SetTopScreenLineCentered(int line, ScreenColor color, const char * text) +{ + SetScreenLineCentered(gTopScreen, line, color, text); +} + +void SetTopScreenLineHighlight(int line, ScreenColor color, const char * text, int pos, int range, ScreenColor posColor) +{ + SetScreenLine(gTopScreen, line, 0, color, text, pos, range, posColor); +} + +void SetBottomScreenLine(int line, ScreenColor color, const char * text) +{ + SetScreenLine(gBottomScreen, line, 0, color, text, 0, 0, color); +} + +void SetBottomScreenLineCentered(int line, ScreenColor color, const char * text) +{ + SetScreenLineCentered(gBottomScreen, line, color, text); +} + +void SetBottomScreenLineHighlight(int line, ScreenColor color, const char * text, int pos, int range, ScreenColor posColor) +{ + SetScreenLine(gBottomScreen, line, 0, color, text, pos, range, posColor); +} + +void ScreenInit(void) +{ + // init the graphics engine + GX_Init(); + + // turn off the display engine output + GX_DispOff(); + GXS_DispOff(); + + // setup the display memory + GX_SetBankForLCDC( GX_VRAM_LCDC_ALL ); + MI_CpuClearFast( (void*)HW_LCDC_VRAM , HW_LCDC_VRAM_SIZE ); + GX_DisableBankForLCDC(); + MI_CpuFillFast( (void*)HW_OAM,192 , HW_OAM_SIZE ); + MI_CpuClearFast( (void*)HW_PLTT , HW_PLTT_SIZE ); + MI_CpuFillFast( (void*)HW_DB_OAM , 192,HW_DB_OAM_SIZE ); + MI_CpuClearFast( (void*)HW_DB_PLTT , HW_DB_PLTT_SIZE ); + + // clear the screens + ClearScreens(); + + // 2D display setup for displaying character string + //g2 + GX_SetBankForBG( GX_VRAM_BG_128_A ); + G2_SetBG0Control(GX_BG_SCRSIZE_TEXT_256x256 , + GX_BG_COLORMODE_16 , + GX_BG_SCRBASE_0xf800 , // SCR base block 31 + GX_BG_CHARBASE_0x00000 , // CHR base block 0 + GX_BG_EXTPLTT_01 ); + G2_SetBG0Priority( 0 ); + G2_BG0Mosaic( FALSE ); + GX_SetGraphicsMode( GX_DISPMODE_GRAPHICS , GX_BGMODE_0,GX_BG0_AS_2D ); + GX_SetVisiblePlane( GX_PLANEMASK_BG0 ); + GX_LoadBG0Char( d_CharData , 0 , sizeof( d_CharData ) ); + GX_LoadBGPltt( d_PaletteData , 0 , sizeof( d_PaletteData ) ); + GX_LoadBG0Scr( gTopScreen , 0 , sizeof( gTopScreen ) ); + //g2s + GX_SetBankForSubBG( GX_VRAM_SUB_BG_128_C ); + G2S_SetBG0Control(GX_BG_SCRSIZE_TEXT_256x256 , + GX_BG_COLORMODE_16 , + GX_BG_SCRBASE_0xf800 , // SCR base block 31 + GX_BG_CHARBASE_0x00000 , // CHR base block 0 + GX_BG_EXTPLTT_01 ); + G2S_SetBG0Priority( 0 ); + G2S_BG0Mosaic( FALSE ); + GXS_SetGraphicsMode( GX_BGMODE_0 ); + GXS_SetVisiblePlane( GX_PLANEMASK_BG0 ); + GXS_LoadBG0Char( d_CharData , 0 , sizeof( d_CharData ) ); + GXS_LoadBGPltt( d_PaletteData , 0 , sizeof( d_PaletteData ) ); + GXS_LoadBG0Scr( gBottomScreen , 0 , sizeof( gBottomScreen ) ); + + // Interrupt setup + OS_SetIrqFunction( OS_IE_V_BLANK , VBlankIntr ); + OS_EnableIrqMask( OS_IE_V_BLANK ); + GX_VBlankIntr( TRUE ); + + // Start LCD display + GX_DispOn(); + GXS_DispOn(); +} \ No newline at end of file diff --git a/code/gamespy/common/nitro/screen.h b/code/gamespy/common/nitro/screen.h new file mode 100644 index 00000000..1099d93c --- /dev/null +++ b/code/gamespy/common/nitro/screen.h @@ -0,0 +1,50 @@ +#ifndef _SCREEN_H_ +#define _SCREEN_H_ + +#define SCREEN_WIDTH 32 +#define SCREEN_HEIGHT 24 + +typedef enum +{ + SCBlack, + SCRed, + SCGreen, + SCBlue, + SCYellow, + SCPurple, + SCLightBlue, + SCDarkRed, + SCDarkGreen, + SCDarkBlue, + SCDarkYellow, + SCDarkPurple, + SCDarkLightBlue, + SCGray, + SCDarkGray, + SCWhite +} ScreenColor; + +// can be combined +#define PRINT_TO_SCREEN 1 // default +#define PRINT_TO_DEBUGGER 2 + +void ScreenInit(void); + +void ClearTopScreen(void); +void ClearBottomScreen(void); +void ClearScreens(void); + +void PrintChar(char c); +void Printf(const char* format, ...); +void VPrintf(const char* format, va_list args); +void SetPrintMode(int mode); + +void SetTopScreenLine(int line, ScreenColor color, const char * text); +void SetTopScreenLineCentered(int line, ScreenColor color, const char * text); +void SetTopScreenLineHighlight(int line, ScreenColor color, const char * text, int pos, int range, ScreenColor posColor); + +void SetBottomScreenLine(int line, ScreenColor color, const char * text); +void SetBottomScreenLineCentered(int line, ScreenColor color, const char * text); +void SetBottomScreenLineHighlight(int line, ScreenColor color, const char * text, int pos, int range, ScreenColor posColor); + +#endif \ No newline at end of file diff --git a/code/gamespy/common/nitro/touch.c b/code/gamespy/common/nitro/touch.c new file mode 100644 index 00000000..8c06b7cb --- /dev/null +++ b/code/gamespy/common/nitro/touch.c @@ -0,0 +1,44 @@ +#include "../nonport.h" +#include "touch.h" + +void TouchInit(void) +{ + TPCalibrateParam calibrate; + + TP_Init(); + + if(!TP_GetUserInfo(&calibrate)) + OS_Panic("Failed to initialize touch panel"); + + TP_SetCalibrateParam(&calibrate); +} + +// 0 <= x <= 255 +// 0 <= y <= 191 +BOOL GetTouch(int * x, int * y) +{ + TPData data; + u32 result; + + result = TP_RequestCalibratedSampling(&data); + + if((result != 0) || (data.touch == TP_TOUCH_OFF)) + return FALSE; + + if(x) + *x = data.x; + if(y) + *y = data.y; + + return TRUE; +} + +void WaitForTouch(void) +{ + while(1) + { + SVC_WaitVBlankIntr(); + if(GetTouch(NULL, NULL)) + break; + } +} \ No newline at end of file diff --git a/code/gamespy/common/nitro/touch.h b/code/gamespy/common/nitro/touch.h new file mode 100644 index 00000000..fa1b41d5 --- /dev/null +++ b/code/gamespy/common/nitro/touch.h @@ -0,0 +1,15 @@ +#ifndef _TOUCH_H_ +#define _TOUCH_H_ + +#define TOUCH_X_RANGE 256 +#define TOUCH_Y_RANGE 192 + +void TouchInit(void); + +// 0 <= x <= 255 +// 0 <= y <= 191 +BOOL GetTouch(int * x, int * y); + +void WaitForTouch(void); + +#endif \ No newline at end of file diff --git a/code/gamespy/common/nitro/wireless.c b/code/gamespy/common/nitro/wireless.c new file mode 100644 index 00000000..7eb07363 --- /dev/null +++ b/code/gamespy/common/nitro/wireless.c @@ -0,0 +1,1541 @@ +#include "../nonport.h" +#include "wireless.h" +#include "screen.h" +#include "menu.h" +#include "backup.h" +#include "touch.h" + +#define USE_DHCP +#define SUPPORT_SAVED_CONFIGS + +#define AUTO_CONNECT +//static u8 MyBSSID[IW_BSSID_SIZE] = { 0x00, 0x0f, 0x66, 0x82, 0x38, 0x70 }; +static u8 MyBSSID[IW_BSSID_SIZE] = { 0x00, 0x09, 0x5b, 0x3f, 0x4b, 0xfb }; +static u8 MyWEP[IW_WEP_SIZE] = { WM_WEPMODE_NO }; + +/************************************************************************/ +/* Wireless Menu */ +/************************************************************************/ + +static const char mscUseSelectedConfiguration[] = "Use Selected Configuration"; +static const char mscCreateNewConfiguration[] = "Create New Configuration"; +static const char mscRenameSelectedConfiguration[] = "Rename Selected Configuration"; +static const char mscDeleteSelectedConfiguration[] = "Delete Selected Configuration"; +static const char mscExit[] = "Exit"; +static const char mscYes[] = "Yes"; +static const char mscNo[] = "No"; +static const char mscOK[] = "OK"; +static const char mscCancel[] = "Cancel"; +static const char mscRename[] = "Rename"; +static const char mscConnectToSelectedNetwork[] = "Connect to Selected Network"; +static const char mscViewNetworkInformation[] = "View Network Information"; +static const char mscBack[] = "Back"; +static const char mscEnter[] = "Enter"; +static const char mscUseKeyType[] = "Use Key Type"; +static const char mscRetry[] = "Retry"; +static const char mscContinue[] = "Continue"; +static const char mscSave[] = "Save"; +static const char mscDontSave[] = "Don't Save"; + +static void InitializingWirelessInit(void); +static void InitializingWirelessThink(void); +static void InitializingWirelessChose(const char * choice); + +static MenuScreenConfiguration msInitializingWireless = +{ + "Initializing Wireless", + { + { mscCancel } + }, + InitializingWirelessInit, + InitializingWirelessChose, + InitializingWirelessThink, + SCREEN_OPTION_ANIMATED +}; + +static void FailedToInitializeWirelessInit(void); +static void FailedToInitializeWirelessChose(const char * choice); + +static MenuScreenConfiguration msFailedToInitializeWireless = +{ + "Failed to Initialize Wireless", + { + { mscOK } + }, + FailedToInitializeWirelessInit, + FailedToInitializeWirelessChose, + NULL, + SCREEN_OPTION_EXTRAS_CENTERED +}; + +static void SelectNetworkConfigurationInit(void); +static void SelectNetworkConfigurationChose(const char * choice); + +static MenuScreenConfiguration msSelectNetworkConfiguration = +{ + "Select Network Configuration", + { + { mscUseSelectedConfiguration, CHOICE_OPTION_NEEDS_LIST_SELECTION }, + { mscCreateNewConfiguration }, + { mscRenameSelectedConfiguration, CHOICE_OPTION_NEEDS_LIST_SELECTION }, + { mscDeleteSelectedConfiguration, CHOICE_OPTION_NEEDS_LIST_SELECTION }, + { mscExit } + }, + SelectNetworkConfigurationInit, + SelectNetworkConfigurationChose, + NULL, + SCREEN_OPTION_LIST +}; + +static void RenameConfigurationInit(void); +static void RenameConfigurationChose(const char * choice); + +static MenuScreenConfiguration msRenameConfiguration = +{ + "Rename Configuration", + { + { mscRename }, + { mscCancel } + }, + RenameConfigurationInit, + RenameConfigurationChose, + NULL, + SCREEN_OPTION_KEYBOARD | SCREEN_OPTION_EXTRAS_CENTERED +}; + +static void ConfirmDeleteInit(void); +static void ConfirmDeleteChose(const char * choice); + +static MenuScreenConfiguration msConfirmDelete = +{ + "Confirm Delete", + { + { mscYes }, + { mscNo } + }, + ConfirmDeleteInit, + ConfirmDeleteChose, + NULL, + SCREEN_OPTION_EXTRAS_CENTERED +}; + +static void ConfigurationDeletedChose(const char * choice); + +static MenuScreenConfiguration msConfigurationDeleted = +{ + "Configuration Deleted", + { + { mscOK } + }, + NULL, + ConfigurationDeletedChose +}; + +static void SearchingForNetworksInit(void); +static void SearchingForNetworksThink(void); +static void SearchingForNetworksChose(const char * choice); + +static MenuScreenConfiguration msSearchingForNetworks = +{ + "Searching for Networks", + { + { mscConnectToSelectedNetwork, CHOICE_OPTION_NEEDS_LIST_SELECTION }, + { mscViewNetworkInformation, CHOICE_OPTION_NEEDS_LIST_SELECTION }, + { mscCancel } + }, + SearchingForNetworksInit, + SearchingForNetworksChose, + SearchingForNetworksThink, + SCREEN_OPTION_ANIMATED | SCREEN_OPTION_LIST +}; + +static void SearchingForSavedNetworkInit(void); +static void SearchingForSavedNetworkThink(void); +static void SearchingForSavedNetworkChose(const char * choice); +static MenuScreenConfiguration msSearchingForSavedNetwork = +{ + "Searching for Saved Network", + { + { mscCancel } + }, + SearchingForSavedNetworkInit, + SearchingForSavedNetworkChose, + SearchingForSavedNetworkThink, + SCREEN_OPTION_ANIMATED +}; + +static void SearchFailedInit(void); +static void SearchFailedChose(const char * choice); + +static MenuScreenConfiguration msSearchFailed = +{ + "Search Failed", + { + { mscOK } + }, + SearchFailedInit, + SearchFailedChose, + NULL, + SCREEN_OPTION_EXTRAS_CENTERED +}; + +static void NetworkInformationInit(void); +static void NetworkInformationChose(const char * choice); + +static MenuScreenConfiguration msNetworkInformation = +{ + "Network Information", + { + { mscBack } + }, + NetworkInformationInit, + NetworkInformationChose, + NULL +}; + +static void SelectSecurityTypeInit(void); +static void SelectSecurityTypeChose(const char * choice); + +static MenuScreenConfiguration msSelectSecurityType = +{ + "Select Security Type", + { + { mscUseKeyType, CHOICE_OPTION_NEEDS_LIST_SELECTION }, + { mscBack } + }, + SelectSecurityTypeInit, + SelectSecurityTypeChose, + NULL, + SCREEN_OPTION_LIST +}; + +static void EnterSecurityKeyInit(void); +static void EnterSecurityKeyChose(const char * choice); + +static MenuScreenConfiguration msEnterSecurityKey = +{ + "Enter Security (WEP) Key", + { + { mscEnter }, + { mscBack } + }, + EnterSecurityKeyInit, + EnterSecurityKeyChose, + NULL, + SCREEN_OPTION_KEYBOARD +}; + +static void BadSecurityKeyEnteredInit(void); +static void BadSecurityKeyEnteredChose(const char * choice); + +static MenuScreenConfiguration msBadSecurityKeyEntered = +{ + "Bad Security Key Entered", + { + { mscBack } + }, + BadSecurityKeyEnteredInit, + BadSecurityKeyEnteredChose, + NULL, + SCREEN_OPTION_EXTRAS_CENTERED +}; + +static void ConnectingToNetworkInit(void); +static void ConnectingToNetworkChose(const char * choice); +static void ConnectingToNetworkThink(void); + +static MenuScreenConfiguration msConnectingToNetwork = +{ + "Connecting to Network", + { + { mscCancel } + }, + ConnectingToNetworkInit, + ConnectingToNetworkChose, + ConnectingToNetworkThink, + SCREEN_OPTION_ANIMATED +}; + +static void FailedToConnectToNetworkInit(void); +static void FailedToConnectToNetworkChose(const char * choice); + +static MenuScreenConfiguration msFailedToConnectToNetwork = +{ + "Failed to Connect to Network", + { + { mscBack } + }, + FailedToConnectToNetworkInit, + FailedToConnectToNetworkChose, + NULL, + SCREEN_OPTION_EXTRAS_CENTERED +}; + +static void ConnectingToInternetInit(void); +static void ConnectingToInternetChose(const char * choice); +static void ConnectingToInternetThink(void); + +static MenuScreenConfiguration msConnectingToInternet = +{ + "Connecting to Internet", + { + { mscCancel } + }, + ConnectingToInternetInit, + ConnectingToInternetChose, + ConnectingToInternetThink, + SCREEN_OPTION_ANIMATED +}; + +static void FailedToConnectToInternetInit(void); +static void FailedToConnectToInternetChose(const char * choice); + +static MenuScreenConfiguration msFailedToConnectToInternet = +{ + "Failed to Connect to Internet", + { + { mscBack } + }, + FailedToConnectToInternetInit, + FailedToConnectToInternetChose, + NULL, + SCREEN_OPTION_EXTRAS_CENTERED +}; + +static void ConnectedToInternetInit(void); +static void ConnectedToInternetChose(const char * choice); + +static MenuScreenConfiguration msConnectedToInternet = +{ + "Connected to Internet", + { + { mscContinue } + }, + ConnectedToInternetInit, + ConnectedToInternetChose +}; + +static void SaveNetworkConfigurationInit(void); +static void SaveNetworkConfigurationChose(const char * choice); + +static MenuScreenConfiguration msSaveNetworkConfiguration = +{ + "Save Network Configuration", + { + { mscSave}, + { mscDontSave } + }, + SaveNetworkConfigurationInit, + SaveNetworkConfigurationChose, + NULL, + SCREEN_OPTION_KEYBOARD +}; + +static void ConfigurationSavedChose(const char * choice); + +static MenuScreenConfiguration msConfigurationSaved = +{ + "Configuration Saved", + { + { mscContinue } + }, + NULL, + ConfigurationSavedChose +}; + +static MenuScreenConfiguration msCancelled = +{ + "Wireless Setup Cancelled" +}; + +static MenuScreenConfiguration msFailed = +{ + "Wireless Setup Failed" +}; + +/************************************************************************/ +/* Wireless Funcs */ +/************************************************************************/ + +#define APLIST_COUNT 8 + +#define MAX_SAVED_CONFIGS 3 +#define MAX_CONFIG_NAME_LEN 32 + +#define BSSDESC_MAX 16 + +static const char MagicString[] = "GOA-SAVED-CONFIGS"; +static const size_t MagicStringLen = sizeof(MagicString); + +typedef struct SavedConfig +{ + char name[MAX_CONFIG_NAME_LEN + 1]; + u8 bssid[IW_BSSID_SIZE]; + u8 wep[IW_WEP_SIZE]; +} SavedConfig; + +typedef struct SavedFile +{ + char magic[MagicStringLen]; + u8 numConfigs; + SavedConfig configs[MAX_SAVED_CONFIGS]; +} SavedFile; + +static SavedFile SavedSettings; + +static u8 IWBuffer[IW_WORK_SIZE] ATTRIBUTE_ALIGN(32); + +static u8 BSSID[IW_BSSID_SIZE]; + +static u8 WEPAny[IW_WEP_SIZE]; +static char WEPEntered[256]; +static u8 WEPKey[IW_WEP_SIZE]; + +static unsigned long BSSDescBuffer[IW_BSS_SIZE * BSSDESC_MAX / 4]; + +static IWConfig WirelessConfig = +{ + 3, + BSSDescBuffer, + IW_BSS_SIZE * BSSDESC_MAX, + 0 +}; + +static WMBssDesc APList[APLIST_COUNT]; +static int APIndex; +static int WEPType; + +static char FailureReason[MAX_EXTRA_TEXT_STRING_LEN + 1]; + +static int SelectedConfig; + +static BOOL NewConfig; + +static gsi_bool KeepPolling; + +static void * soAlloc(u32 name, s32 size) +{ + OSIntrMode nOSIntrMode; + void * alloc = NULL; + + GSI_UNUSED(name); + + nOSIntrMode = OS_DisableInterrupts(); + alloc = OS_Alloc((u32)size); + OS_RestoreInterrupts(nOSIntrMode); + + return alloc; +} + +static void soFree(u32 name, void * ptr, s32 size) +{ + OSIntrMode nOSIntrMode; + + GSI_UNUSED(name); + GSI_UNUSED(size); + + nOSIntrMode = OS_DisableInterrupts(); + OS_Free(ptr); + OS_RestoreInterrupts(nOSIntrMode); +} + +SOConfig SocketsConfig = +{ + SOC_VENDOR_NINTENDO, // vendor + SOC_VERSION, // version + + soAlloc, // alloc + soFree, // free + +#if defined(USE_DHCP) + SOC_FLAG_DHCP, // flag + SOC_HtoNl(SOC_INADDR_ANY), // addr + SOC_HtoNl(SOC_INADDR_ANY), // netmask + SOC_HtoNl(SOC_INADDR_ANY), // router + SOC_HtoNl(SOC_INADDR_ANY), // dns1 + SOC_HtoNl(SOC_INADDR_ANY), // dns2 +#else + 0, // flag + SOC_HtoNl(0xC0A8000C), // addr 192.168. 0. 12 + SOC_HtoNl(0xFFFFFF00), // netmask 255.255.255. 0 + SOC_HtoNl(0xC0A80001), // router 192.168. 0. 1 + SOC_HtoNl(0xC0A80001), // dns1 192.168. 0. 1 + SOC_HtoNl(SOC_INADDR_ANY), // dns2 +#endif + 4096, // timeWaitBuffer + 4096, // reassemblyBuffer + 0, // maximum transmission unit size + + // TCP + 0, // default TCP receive window size (default 2 x MSS) + 0, // default TCP total retransmit timeout value (default 100 sec) + + // PPP + NULL, + NULL, + + // PPPoE + NULL, + + // DHCP + "NintendoDS", // DHCP host name + 50, // TCP total retransmit times (default 4) + + // UDP + 0, // default UDP send buffer size (default 1472) + 0 // defualt UDP receive buffer size (default 4416) +}; + +static const char * IWResultToString(int result) +{ + if(result == IW_RESULT_SUCCESS) + return "success"; + if(result == IW_RESULT_FAILURE) + return "failure"; + if(result == IW_RESULT_PROGRESS) + return "progress"; + if(result == IW_RESULT_ACCEPT) + return "accept"; + if(result == IW_RESULT_REJECT) + return "reject"; + if(result == IW_RESULT_WMDISABLE) + return "wmdisable"; + if(result == IW_RESULT_MEMORYERROR) + return "memoryerror"; + if(result == IW_RESULT_FATALERROR) + return "fatalerror"; + return "unknown result"; +} + +static const char * IWNotifyToString(int notify) +{ + if(notify == IW_NOTIFY_COMMON) + return "common"; + if(notify == IW_NOTIFY_STARTUP) + return "startup"; + if(notify == IW_NOTIFY_CLEANUP) + return "cleanup"; + if(notify == IW_NOTIFY_SEARCH_START) + return "search_start"; + if(notify == IW_NOTIFY_SEARCH_END) + return "search_end"; + if(notify == IW_NOTIFY_CONNECT) + return "connect"; + if(notify == IW_NOTIFY_DISCONNECT) + return "disconnect"; + if(notify == IW_NOTIFY_FOUND) + return "found"; + if(notify == IW_NOTIFY_BEACON_LOST) + return "beacon_lost"; + if(notify == IW_NOTIFY_FATALERROR) + return "fatalerror"; + return "unknown notify"; +} + +static const char * IWPhaseToString(int phase) +{ + if(phase == IW_PHASE_NULL) + return "null"; + if(phase == IW_PHASE_WAIT) + return "wait"; + if(phase == IW_PHASE_WAIT_IDLE) + return "wait_idle"; + if(phase == IW_PHASE_IDLE) + return "idle"; + if(phase == IW_PHASE_IDLE_WAIT) + return "idle_wait"; + if(phase == IW_PHASE_IDLE_SCAN) + return "idle_scan"; + if(phase == IW_PHASE_SCAN) + return "scan"; + if(phase == IW_PHASE_SCAN_IDLE) + return "scan_idle"; + if(phase == IW_PHASE_IDLE_LINK) + return "idle_link"; + if(phase == IW_PHASE_LINK) + return "link"; + if(phase == IW_PHASE_LINK_IDLE) + return "link_idle"; + if(phase == IW_PHASE_FATALERROR) + return "fatalerror"; + return "unknown phase"; +} + +static void IW_Callback(IWNotice * arg) +{ + GSI_UNUSED(arg); + OS_TPrintf("Notify: %s | Result: %s\n", IWNotifyToString(arg->notify), IWResultToString(arg->result)); +} + +static gsi_bool IW_CheckResult(int result, const char * funcName) +{ + assert(funcName); + + OS_Printf("%s: %s\n", funcName, IWResultToString(result)); + switch(result) + { + case IW_RESULT_SUCCESS: + case IW_RESULT_PROGRESS: + case IW_RESULT_ACCEPT: + return gsi_true; + case IW_RESULT_FAILURE: + case IW_RESULT_REJECT: + OS_Printf(" IW_Phase = %s\n", IWPhaseToString(IW_GetPhase())); + case IW_RESULT_WMDISABLE: + case IW_RESULT_MEMORYERROR: + case IW_RESULT_FATALERROR: + default: + snprintf(FailureReason, sizeof(FailureReason), "%s result: %s", funcName, IWResultToString(result)); + return gsi_false; + } +} + +static gsi_bool IW_Poll(int polling, int success, const char * funcName, gsi_bool * keepPolling) +{ + int phase; + + assert(funcName); + + phase = IW_GetPhase(); + if(phase == polling) + { + if(keepPolling) + *keepPolling = gsi_true; + return gsi_true; + } + + if(keepPolling) + *keepPolling = gsi_false; + + if(phase == success) + return gsi_true; + + snprintf(FailureReason, sizeof(FailureReason), "%s phase: %s", funcName, IWPhaseToString(phase)); + return gsi_false; +} + +static void LoadSettings(void) +{ + if(BackupExists()) + { + // load the saved settings from the backup + ReadFromBackup(&SavedSettings, sizeof(SavedSettings)); + } + else + { + // use a default when there isn't a backup device + // this makes debugging with the dev kit easier + SavedConfig config; + strcpy(config.name, "Default WEP"); + memcpy(config.bssid, MyBSSID, sizeof(MyBSSID)); + memcpy(config.wep, MyWEP, sizeof(MyWEP)); + + memcpy(SavedSettings.magic, MagicString, MagicStringLen); + SavedSettings.numConfigs = 1; + memcpy(&SavedSettings.configs[0], &config, sizeof(SavedConfig)); + } + + if(memcmp(SavedSettings.magic, MagicString, MagicStringLen) != 0) + SavedSettings.numConfigs = 0; + + SavedSettings.numConfigs = (u8)min(SavedSettings.numConfigs, MAX_SAVED_CONFIGS); +} + +static void SaveSettings(void) +{ + if(BackupExists()) + { + memcpy(SavedSettings.magic, MagicString, MagicStringLen); + WriteToBackup(&SavedSettings, sizeof(SavedSettings)); + } +} + +void WirelessInit(void) +{ + // load wireless settings + LoadSettings(); + + // start the menu + StartMenuScreen(&msInitializingWireless); +} + +void WirelessCleanup(void) +{ + int result; + + SOC_Cleanup(); + + result = IW_Disconnect(); + IW_CheckResult(result, "IW_Disconnect"); + + do + { + if(!IW_Poll(IW_PHASE_LINK_IDLE, IW_PHASE_IDLE, "IW_Disconnect", &KeepPolling)) + OS_TPrintf("Failuring waiting for wait phase\n"); + } while(KeepPolling); + + result = IW_Cleanup(); + IW_CheckResult(result, "IW_Cleanup"); + + do + { + if(!IW_Poll(IW_PHASE_IDLE_WAIT, IW_PHASE_WAIT, "IW_Cleanup", &KeepPolling)) + OS_TPrintf("Failuring waiting for wait phase\n"); + } while(KeepPolling); + + result = IW_Exit(); + IW_CheckResult(result, "IW_Exit"); +} + +/************************************************************************/ +/* Wireless Menu Funcs */ +/************************************************************************/ + +static void InitializingWirelessInit(void) +{ + int result; + + // init the wireless library + result = IW_Init(IWBuffer, sizeof(IWBuffer)); + if(!IW_CheckResult(result, "IW_Init")) + { + SetNextMenuScreen(&msFailedToInitializeWireless); + return; + } + + // startup wireless + result = IW_Startup(&WirelessConfig, IW_Callback); + if(!IW_CheckResult(result, "IW_Startup")) + { + SetNextMenuScreen(&msFailedToInitializeWireless); + return; + } + + KeepPolling = gsi_true; +} + +static void InitializingWirelessChose(const char * choice) +{ + if(choice == mscCancel) + SetNextMenuScreen(&msCancelled); +} + +static void InitializingWirelessThink(void) +{ + // poll the phase + if(KeepPolling) + { + if(!IW_Poll(IW_PHASE_WAIT_IDLE, IW_PHASE_IDLE, "IW_Startup", &KeepPolling)) + { + SetNextMenuScreen(&msFailedToInitializeWireless); + return; + } + if(KeepPolling) + return; + } + +#if defined(AUTO_CONNECT) + { + memcpy(WEPKey, MyWEP, sizeof(WEPKey)); + memcpy(BSSID, MyBSSID, WM_SIZE_BSSID); + NewConfig = FALSE; + SetNextMenuScreen(&msSearchingForSavedNetwork); + return; + } +#endif + + // we're done with the startup +#if defined(SUPPORT_SAVED_CONFIGS) + if(SavedSettings.numConfigs > 0) + SetNextMenuScreen(&msSelectNetworkConfiguration); + else +#endif + SetNextMenuScreen(&msSearchingForNetworks); +} + +static void FailedToInitializeWirelessInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + + strncpy(screen->extraText[0], FailureReason, MAX_EXTRA_TEXT_STRING_LEN + 1); + screen->extraText[0][MAX_EXTRA_TEXT_STRING_LEN] = '\0'; +} + +static void FailedToInitializeWirelessChose(const char * choice) +{ + if(choice == mscOK) + SetNextMenuScreen(&msFailed); +} + +static void SelectNetworkConfigurationInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + int i; + + for(i = 0 ; i < SavedSettings.numConfigs ; i++) + { + strncpy(screen->list[i], SavedSettings.configs[i].name, MAX_LIST_STRING_LEN); + screen->list[i][MAX_LIST_STRING_LEN - 1] = '\0'; + } +} + +static void SelectNetworkConfigurationChose(const char * choice) +{ + MenuScreen * screen = GetMenuScreen(); + + SelectedConfig = screen->listSelection; + + if(choice == mscUseSelectedConfiguration) + { + memcpy(WEPKey, SavedSettings.configs[SelectedConfig].wep, sizeof(WEPKey)); + memcpy(BSSID, SavedSettings.configs[SelectedConfig].bssid, WM_SIZE_BSSID); + NewConfig = FALSE; + //SetNextMenuScreen(&msConnectingToNetwork); + SetNextMenuScreen(&msSearchingForSavedNetwork); + } + else if(choice == mscCreateNewConfiguration) + { + SetNextMenuScreen(&msSearchingForNetworks); + } + else if(choice == mscRenameSelectedConfiguration) + { + SetNextMenuScreen(&msRenameConfiguration); + } + else if(choice == mscDeleteSelectedConfiguration) + { + SetNextMenuScreen(&msConfirmDelete); + } + else if(choice == mscExit) + { + SetNextMenuScreen(&msCancelled); + } +} + +static void RenameConfigurationInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + + strncpy(screen->extraText[0], SavedSettings.configs[SelectedConfig].name, MAX_EXTRA_TEXT_STRING_LEN); + screen->extraText[0][MAX_EXTRA_TEXT_STRING_LEN - 1] = '\0'; + + strncpy(screen->keyboardText, SavedSettings.configs[SelectedConfig].name, MAX_KEYBOARD_TEXT_LEN); + screen->keyboardText[MAX_KEYBOARD_TEXT_LEN - 1] = '\0'; +} + +static void RenameConfigurationChose(const char * choice) +{ + MenuScreen * screen = GetMenuScreen(); + + if(choice == mscRename) + { + strncpy(SavedSettings.configs[SelectedConfig].name, screen->keyboardText, MAX_CONFIG_NAME_LEN); + SavedSettings.configs[SelectedConfig].name[MAX_CONFIG_NAME_LEN - 1] = '\0'; + + SaveSettings(); + + SetNextMenuScreen(&msSelectNetworkConfiguration); + } + else if(choice == mscCancel) + { + SetNextMenuScreen(&msSelectNetworkConfiguration); + } +} + +static void ConfirmDeleteInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + + strncpy(screen->extraText[0], SavedSettings.configs[SelectedConfig].name, MAX_EXTRA_TEXT_STRING_LEN); + screen->extraText[0][MAX_EXTRA_TEXT_STRING_LEN - 1] = '\0'; +} + +static void ConfirmDeleteChose(const char * choice) +{ + int i; + + if(choice == mscYes) + { + SavedSettings.numConfigs--; + for(i = SelectedConfig ; i < SavedSettings.numConfigs ; i++) + memcpy(&SavedSettings.configs[i], &SavedSettings.configs[i + 1], sizeof(SavedConfig)); + + SaveSettings(); + + SetNextMenuScreen(&msConfigurationDeleted); + } + else if(choice == mscNo) + { + SetNextMenuScreen(&msSelectNetworkConfiguration); + } +} + +static void ConfigurationDeletedChose(const char * choice) +{ + if(choice == mscOK) + { + if(SavedSettings.numConfigs > 0) + SetNextMenuScreen(&msSelectNetworkConfiguration); + else + SetNextMenuScreen(&msSearchingForNetworks); + } +} + +static void SearchingForNetworksInit(void) +{ + int result; + + // search for access points + result = IW_Search(IW_BSSID_ANY, IW_ESSID_ANY, IW_OPT_CHANNEL_ALL|IW_OPT_SCANTYPE_PASSIVE); + if(!IW_CheckResult(result, "IW_Search")) + { + SetNextMenuScreen(&msSearchFailed); + return; + } + + KeepPolling = gsi_true; +} + +static void SearchingForNetworksChose(const char * choice) +{ + MenuScreen * screen = GetMenuScreen(); + + APIndex = screen->listSelection; + + if(choice == mscViewNetworkInformation) + { + SetNextMenuScreen(&msNetworkInformation); + return; + } + + // we're connecting or cancelling, so stop searching + IW_Search(NULL, NULL, 0); + + if(choice == mscConnectToSelectedNetwork) + { + SetNextMenuScreen(&msSelectSecurityType); + } + else if(choice == mscCancel) + { +#if defined(SUPPORT_SAVED_CONFIGS) + if(SavedSettings.numConfigs > 0) + SetNextMenuScreen(&msSelectNetworkConfiguration); + else +#endif + SetNextMenuScreen(&msCancelled); + } +} + +static void SearchingForNetworksThink(void) +{ + MenuScreen * screen; + int i; + int count; + WMBssDesc * desc; + + if(KeepPolling) + { + // poll the search + if(!IW_Poll(IW_PHASE_IDLE_SCAN, IW_PHASE_SCAN, "IW_Search", &KeepPolling)) + { + IW_Search(NULL, NULL, 0); + SetNextMenuScreen(&msSearchFailed); + return; + } + if(KeepPolling) + return; + } + + // get the screen + screen = GetMenuScreen(); + + // lock the AP descriptions + IW_LockBssDesc(1); + + // get a count + count = IW_CountBssDesc(); + count = min(count, APLIST_COUNT); + + // copy the access points + for(i = 0 ; i < count ; i++) + { + desc = IW_PointBssDesc(i); + if(!desc) + { + count = i; + break; + } + + memcpy(&APList[i], desc, sizeof(WMBssDesc)); + } + + // unlock the APs + IW_LockBssDesc(0); + + // fill the screen list + for(i = 0 ; i < MAX_LIST_STRINGS ; i++) + { + if(i < count) + { + if(APList[i].ssidLength > 0) + snprintf(screen->list[i], MAX_LIST_STRING_LEN, "%s", APList[i].ssid); + else + strcpy(screen->list[i], "[no name]"); + } + else + { + screen->list[i][0] = '\0'; + } + } +} + +static void SearchingForSavedNetworkInit(void) +{ + int result; + + // search for access points + result = IW_Search(BSSID, IW_ESSID_ANY, IW_OPT_CHANNEL_ALL|IW_OPT_SCANTYPE_PASSIVE); + if(!IW_CheckResult(result, "IW_Search")) + { + SetNextMenuScreen(&msSearchFailed); + return; + } + + KeepPolling = gsi_true; +} + +static void SearchingForSavedNetworkChose(const char * choice) +{ + if(choice == mscCancel) + { + // stop searching + IW_Search(NULL, NULL, 0); + + SetNextMenuScreen(&msSelectNetworkConfiguration); + } +} + +static void SearchingForSavedNetworkThink(void) +{ + int count; + WMBssDesc * desc = NULL; + + if(KeepPolling) + { + // poll the search + if(!IW_Poll(IW_PHASE_IDLE_SCAN, IW_PHASE_SCAN, "IW_Search", &KeepPolling)) + { + IW_Search(NULL, NULL, 0); + SetNextMenuScreen(&msSearchFailed); + return; + } + if(KeepPolling) + return; + } + // lock the AP descriptions + IW_LockBssDesc(1); + + // get a count + count = IW_CountBssDesc(); + + // if we found it, copy it + if(count > 0) + { + desc = IW_PointBssDesc(0); + if(desc) + { + memcpy(&APList[0], desc, sizeof(WMBssDesc)); + APIndex = 0; + } + } + + // unlock the APs + IW_LockBssDesc(0); + + // check if we found it + if(desc) + { + // stop searching + IW_Search(NULL, NULL, 0); + + // connect + SetNextMenuScreen(&msConnectingToNetwork); + } +} + +static void SearchFailedInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + + strncpy(screen->extraText[0], FailureReason, MAX_EXTRA_TEXT_STRING_LEN + 1); + screen->extraText[0][MAX_EXTRA_TEXT_STRING_LEN] = '\0'; +} + +static void SearchFailedChose(const char * choice) +{ + if(choice == mscOK) + { +#if defined(SUPPORT_SAVED_CONFIGS) + if(SavedSettings.numConfigs > 0) + SetNextMenuScreen(&msSelectNetworkConfiguration); + else +#endif + SetNextMenuScreen(&msFailed); + } +} + +static void NetworkInformationInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + + // BSSID / MAC + snprintf(screen->extraText[0], MAX_EXTRA_TEXT_STRING_LEN, + " MAC: %02X:%02X:%02X:%02X:%02X:%02X", + APList[APIndex].bssid[0], + APList[APIndex].bssid[1], + APList[APIndex].bssid[2], + APList[APIndex].bssid[3], + APList[APIndex].bssid[4], + APList[APIndex].bssid[5]); + + // channel + snprintf(screen->extraText[1], MAX_EXTRA_TEXT_STRING_LEN, + " Channel: %d", + APList[APIndex].channel); +} + +static void NetworkInformationChose(const char * choice) +{ + if(choice == mscBack) + SetNextMenuScreen(&msSearchingForNetworks); +} + +static void SelectSecurityTypeInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + + strcpy(screen->list[0], "No WEP key"); + strcpy(screen->list[1], "40/64 bit key"); + strcpy(screen->list[2], "104 bit key"); + strcpy(screen->list[3], "128 bit key"); +} + +static void SelectSecurityTypeChose(const char * choice) +{ + MenuScreen * screen = GetMenuScreen(); + + WEPType = screen->listSelection; + + if(choice == mscUseKeyType) + { + if(WEPType == WM_WEPMODE_NO) + { + memset(WEPKey, 0, sizeof(WEPKey)); + memcpy(BSSID, APList[APIndex].bssid, WM_SIZE_BSSID); + NewConfig = TRUE; + SetNextMenuScreen(&msConnectingToNetwork); + } + else + { + SetNextMenuScreen(&msEnterSecurityKey); + } + } + else if(choice == mscBack) + { + SetNextMenuScreen(&msSearchingForNetworks); + } +} + +static int HexToInt(char hex) +{ + switch(hex) + { + case '0': + return 0; + case '1': + return 1; + case '2': + return 2; + case '3': + return 3; + case '4': + return 4; + case '5': + return 5; + case '6': + return 6; + case '7': + return 7; + case '8': + return 8; + case '9': + return 9; + case 'A': + return 10; + case 'B': + return 11; + case 'C': + return 12; + case 'D': + return 13; + case 'E': + return 14; + case 'F': + return 15; + } + + return -1; +} + +static void EnterSecurityKeyInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + + strncpy(screen->keyboardText, WEPEntered, sizeof(screen->keyboardText)); + screen->keyboardText[sizeof(screen->keyboardText) - 1] = '\0'; +} + +static void EnterSecurityKeyChose(const char * choice) +{ + MenuScreen * screen = GetMenuScreen(); + + // save off the key as entered + strncpy(WEPEntered, screen->keyboardText, sizeof(WEPEntered)); + WEPEntered[sizeof(WEPEntered) - 1] = '\0'; + + if(choice == mscEnter) + { + int i; + int len; + int correctLen; + int index; + int val[2]; + char badChar; + // get the key len + len = (int)strlen(screen->keyboardText); + + // get the correct len for the type + if(WEPType == WM_WEPMODE_40BIT) + correctLen = (40 / 4); + else if(WEPType == WM_WEPMODE_104BIT) + correctLen = (104 / 4); + else if(WEPType == WM_WEPMODE_128BIT) + correctLen = (128 / 4); + else + correctLen = -1; + + // check the len + if(len != correctLen) + { + snprintf(FailureReason, sizeof(FailureReason), "Bad length (%d instead of %d)", len, correctLen); + SetNextMenuScreen(&msBadSecurityKeyEntered); + return; + } + + // convert the keyboard text to hex + memset(WEPKey, 0, sizeof(WEPKey)); + WEPKey[0] = (u8)WEPType; + index = 2; + for(i = 0 ; i < len ; i += 2) + { + // convert the next two chars to ints + val[0] = HexToInt(screen->keyboardText[i]); + val[1] = HexToInt(screen->keyboardText[i + 1]); + + // check the values + if((val[0] == -1) || (val[1] == -1)) + { + if(val[0] == -1) + badChar = screen->keyboardText[i]; + else + badChar = screen->keyboardText[i + 1]; + snprintf(FailureReason, sizeof(FailureReason), "Bad value ('%c' instead of hex)", badChar); + SetNextMenuScreen(&msBadSecurityKeyEntered); + return; + } + + // enter them in the WEP array + WEPKey[index++] = (u8)((val[0] << 4) | val[1]); + } + + memcpy(BSSID, APList[APIndex].bssid, WM_SIZE_BSSID); + NewConfig = TRUE; + SetNextMenuScreen(&msConnectingToNetwork); + } + else if (choice == mscBack) + { + SetNextMenuScreen(&msSelectSecurityType); + } +} + +static void BadSecurityKeyEnteredInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + + strncpy(screen->extraText[0], FailureReason, MAX_EXTRA_TEXT_STRING_LEN + 1); + screen->extraText[0][MAX_EXTRA_TEXT_STRING_LEN] = '\0'; +} + +static void BadSecurityKeyEnteredChose(const char * choice) +{ + if(choice == mscBack) + SetNextMenuScreen(&msEnterSecurityKey); +} + +static void ConnectingToNetworkInit(void) +{ + int result; + + // make sure we're idle + OS_TPrintf("Waiting for idle...\n"); + while(IW_GetPhase() != IW_PHASE_IDLE) + ; + + // connect + result = IW_Connect(&APList[APIndex], WEPKey, IW_OPT_POWER_FULL); + if(!IW_CheckResult(result, "IW_Connect")) + { + SetNextMenuScreen(&msFailedToConnectToNetwork); + return; + } + + KeepPolling = gsi_true; +} + +static void ConnectingToNetworkChose(const char * choice) +{ + if(choice == mscCancel) + { + // disconnect + IW_Disconnect(); + +#if defined(SUPPORT_SAVED_CONFIGS) + if(SavedSettings.numConfigs > 0) + SetNextMenuScreen(&msSelectNetworkConfiguration); + else +#endif + SetNextMenuScreen(&msSearchingForNetworks); + } +} + +static void ConnectingToNetworkThink(void) +{ + if(KeepPolling) + { + // poll the connect + if(!IW_Poll(IW_PHASE_IDLE_LINK, IW_PHASE_LINK, "IW_Connect", &KeepPolling)) + { + IW_Disconnect(); + SetNextMenuScreen(&msFailedToConnectToNetwork); + return; + } + if(KeepPolling) + return; + } + + // we're connected to the network + SetNextMenuScreen(&msConnectingToInternet); +} + +static void FailedToConnectToNetworkInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + + strncpy(screen->extraText[0], FailureReason, MAX_EXTRA_TEXT_STRING_LEN + 1); + screen->extraText[0][MAX_EXTRA_TEXT_STRING_LEN] = '\0'; +} + +static void FailedToConnectToNetworkChose(const char * choice) +{ + if(choice == mscBack) + { +#if defined(SUPPORT_SAVED_CONFIGS) + if(SavedSettings.numConfigs > 0) + SetNextMenuScreen(&msSelectNetworkConfiguration); + else +#endif + SetNextMenuScreen(&msSearchingForNetworks); + } +} + +static void ConnectingToInternetInit(void) +{ + int result; + BOOL linkStatus = FALSE; + + // init the sockets library + SOC_Init(); + + // get the IP link + do + { + IP_GetLinkState(NULL, &linkStatus); + } + while(!linkStatus); + + // startup the sockets library + result = SOC_Startup(&SocketsConfig); + if(result < 0) + { + snprintf(FailureReason, sizeof(FailureReason), "SOC_Startup failed: %d", result); + SetNextMenuScreen(&msFailedToConnectToInternet); + return; + } +} + +static void ConnectingToInternetChose(const char * choice) +{ + if(choice == mscCancel) + { + SOC_Cleanup(); + IW_Disconnect(); + +#if defined(SUPPORT_SAVED_CONFIGS) + if(SavedSettings.numConfigs > 0) + SetNextMenuScreen(&msSelectNetworkConfiguration); + else +#endif + SetNextMenuScreen(&msSearchingForNetworks); + } +} + +static void ConnectingToInternetThink(void) +{ + u32 localIP; + + // check if we have been allocated an IP + localIP = (u32)SOC_GetHostID(); + if(localIP != SOC_HtoNl(SOC_INADDR_ANY)) + { +#if !defined(AUTO_CONNECT) + SetNextMenuScreen(&msConnectedToInternet); +#else + u8 ip[IP_ALEN]; + SOInAddr dns1, dns2; + u8 mac[MAC_ALEN]; + + IP_GetAddr(NULL, ip); + OS_Printf("IP: %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]); + + IP_GetGateway (NULL, ip); + OS_Printf("Gateway: %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]); + + SOC_GetResolver(&dns1, &dns2); + OS_Printf("DNS1: %s\n", inet_ntoa(dns1)); + OS_Printf("DNS2: %s\n", inet_ntoa(dns2)); + + IP_GetMacAddr(NULL, mac); + OS_Printf("MAC: %02X-%02X-%02X-%02X-%02X-%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + ExitMenu(); +#endif + } +} + +static void FailedToConnectToInternetInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + + strncpy(screen->extraText[0], FailureReason, MAX_EXTRA_TEXT_STRING_LEN + 1); + screen->extraText[0][MAX_EXTRA_TEXT_STRING_LEN] = '\0'; +} + +static void FailedToConnectToInternetChose(const char * choice) +{ + if(choice == mscBack) + { +#if defined(SUPPORT_SAVED_CONFIGS) + if(SavedSettings.numConfigs > 0) + SetNextMenuScreen(&msSelectNetworkConfiguration); + else +#endif + SetNextMenuScreen(&msSearchingForNetworks); + } +} + +static void ConnectedToInternetInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + u8 ip[IP_ALEN]; + SOInAddr dns1, dns2; + u8 mac[MAC_ALEN]; + int line = 0; + + IP_GetAddr(NULL, ip); + snprintf(screen->extraText[line++], MAX_EXTRA_TEXT_STRING_LEN, + " IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + + IP_GetAlias(NULL, ip); + snprintf(screen->extraText[line++], MAX_EXTRA_TEXT_STRING_LEN, + " Alias: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + + IP_GetBroadcastAddr(NULL, ip); + snprintf(screen->extraText[line++], MAX_EXTRA_TEXT_STRING_LEN, + " Broadcast: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + + IP_GetNetmask (NULL, ip); + snprintf(screen->extraText[line++], MAX_EXTRA_TEXT_STRING_LEN, + " Netmask: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + + IP_GetGateway (NULL, ip); + snprintf(screen->extraText[line++], MAX_EXTRA_TEXT_STRING_LEN, + " Gateway: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + + SOC_GetResolver(&dns1, &dns2); + snprintf(screen->extraText[line++], MAX_EXTRA_TEXT_STRING_LEN, + " DNS1: %s", inet_ntoa(dns1)); + snprintf(screen->extraText[line++], MAX_EXTRA_TEXT_STRING_LEN, + " DNS2: %s", inet_ntoa(dns2)); + + IP_GetMacAddr(NULL, mac); + snprintf(screen->extraText[line++], MAX_EXTRA_TEXT_STRING_LEN, + " MAC: %02X-%02X-%02X-%02X-%02X-%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} + +static void ConnectedToInternetChose(const char * choice) +{ + if(choice == mscContinue) + { +#if defined(SUPPORT_SAVED_CONFIGS) + if(BackupExists() && NewConfig && (SavedSettings.numConfigs < MAX_SAVED_CONFIGS)) + SetNextMenuScreen(&msSaveNetworkConfiguration); + else +#endif + ExitMenu(); + } +} + +static void SaveNetworkConfigurationInit(void) +{ + MenuScreen * screen = GetMenuScreen(); + + strncpy(screen->keyboardText, (const char *)APList[APIndex].ssid, MAX_KEYBOARD_TEXT_LEN); + screen->keyboardText[MAX_KEYBOARD_TEXT_LEN - 1] = '\0'; +} + +static void SaveNetworkConfigurationChose(const char * choice) +{ + MenuScreen * screen = GetMenuScreen(); + + if(choice == mscSave) + { + memcpy(SavedSettings.configs[SavedSettings.numConfigs].bssid, BSSID, IW_BSSID_SIZE); + memcpy(SavedSettings.configs[SavedSettings.numConfigs].wep, WEPKey, IW_WEP_SIZE); + strncpy(SavedSettings.configs[SavedSettings.numConfigs].name, screen->keyboardText, MAX_CONFIG_NAME_LEN); + SavedSettings.configs[SavedSettings.numConfigs].name[MAX_CONFIG_NAME_LEN - 1] = '\0'; + SavedSettings.numConfigs++; + SaveSettings(); + + SetNextMenuScreen(&msConfigurationSaved); + } + else if(choice == mscDontSave) + { + ExitMenu(); + } +} + +static void ConfigurationSavedChose(const char * choice) +{ + if(choice == mscContinue) + ExitMenu(); +} diff --git a/code/gamespy/common/nitro/wireless.h b/code/gamespy/common/nitro/wireless.h new file mode 100644 index 00000000..18734eaa --- /dev/null +++ b/code/gamespy/common/nitro/wireless.h @@ -0,0 +1,7 @@ +#ifndef _WIRELESS_H_ +#define _WIRELESS_H_ + +void WirelessInit(void); +void WirelessCleanup(void); + +#endif \ No newline at end of file diff --git a/code/gamespy/common/ps2/changelog.txt b/code/gamespy/common/ps2/changelog.txt new file mode 100644 index 00000000..c5199f9a --- /dev/null +++ b/code/gamespy/common/ps2/changelog.txt @@ -0,0 +1,47 @@ +Changelog for: PS2 Common Code +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +06-03-2005 1.05.19 SN RELEASE Releasing to developer site. +04-28-2005 1.05.19 SN RELEASE Releasing to developer site. +04-04-2005 1.05.19 SN RELEASE Releasing to developer site. +09-16-2004 1.05.19 SN RELEASE Releasing to developer site. +08-05-2004 1.05.19 SN RELEASE Releasing to developer site. +07-23-2004 1.05.19 SN FIX Updated code with explicit casts to remove implicit cast error + when compiling at highest level and warnings treated as errors.(ps2pad.c) +07-19-2004 1.05.18 SN FIX Updated code with explicit casts to remove implicit cast error + when compiling at highest level and warnings treated as errors.(ps2common.c) +07-14-2004 1.05.17 DES CLEANUP Updated and unified IRX module loading code. + FEATURE Added loading of IRX modules needed by GV SPU2 code. +06-22-2004 1.05.16 BED FEATURE Added PS2 Insock support +06-22-2004 1.05.15 DES FEATURE Add loading of Logitech video library for EyeToy support in GV. +06-08-2003 1.05.14 DES RELEASE Limited developer release (for GV) +04-22-2004 1.05.14 DES FEATURE Added ps2pad.c/h for getting input events from the controller. +10-21-2003 1.05.13 BED RELEASE Releasing to developer site. (UNIQUE NICK AND UNICODE SUPPORT) + BED FIX Removed misc compiler warnings on strict setting. +10-09-2003 1.05.13 BED FIX DHCP override settings defined but not used. Now passed into init call. +07-24-2003 1.05.12 DES RELEASE Releasing to developer site. +07-24-2003 1.05.12 DES FEATURE Added a Makefile for building all projects with gcc. + FEATURE Added a ProDG workspace that includes all the sample app projects. + FEATURE Added a CodeWarrior workspace that includes all the sample app projects. +07-23-2003 1.05.11 DES FIX Removed unneeded headers from ps2common.c which were causing warnings. + CLEANUP Removed EENet 2.6.0 compatibility. + CLEANUP Changed most printf()s in ps2common.c to scePrintf(). +07-21-2003 1.05.10 DES FIX Updated the gcc Makefile.common to automatically build the + ent_cnf.irx when making for EENet. +07-17-2003 1.05.09 DES FEATURE Put PS2 test app common Makefile code into Makefile.common. + It is then included from all the SDK-specific Makefiles. +07-16-2003 1.05.08 DES FIX EENet will no longer lockup if it can't load an irx module. + CLEANUP Removed Cisco NFT support. +02-25-2003 1.05.07 DES CLEANUP Changed sceEENetInit() for 2.6.1 EENet compatibility. + EENET_260 can be defined for 2.6.0 compatibility. +01-02-2003 1.05.06 DES FIX Added needed defines for modem support. + CLEANUP Removed references to ent_cnf.h and ent_cnf.irx. +12-18-2002 1.05.05 DES CLEANUP Updated SN Systems stack support. +12-13-2002 1.05.04 DES FEATURE Added PS2 eenet stack support. +12-05-2002 1.05.03 DES CLEANUP General cleanup + FEATURE Initial PS2 eenet stack support +11-20-2002 1.05.02 DES FEATURE Updated to use new Unique ID code. +11-14-2002 1.05.01 DES OTHER Updated the IOPIMAGE define to IOPRP255.IMG from IOPRP250.IMG +09-25-2002 1.05.00 DDW OTHER Changelog started diff --git a/code/gamespy/common/ps2/cw/LinkSegment_PS2.lcf b/code/gamespy/common/ps2/cw/LinkSegment_PS2.lcf new file mode 100644 index 00000000..bed58f27 --- /dev/null +++ b/code/gamespy/common/ps2/cw/LinkSegment_PS2.lcf @@ -0,0 +1,161 @@ +# =========================================================================== +# LinkSegment_PS2.lcf ©1999-2000 Metrowerks Inc. All rights reserved. +# =========================================================================== +# +# linker command file for PS2 +# +# 06/09/2000 kashima, put dummy nop after .text section +# 06/09/2000 kashima, change default entry address +# 02/10/2000 kashima, add ALIGNALL before each section +# 01/15/2000 kashima, support overlay by lcf generator +# 11/03/1999 kashima, for SDK 1.10 +# 10/22/1999 kashima, change file extention from txt to lcf +# 10/20/1999 kashima, put all sections into main +# 10/12/1999 kashima, separate each sections +# 09/14/1999 kashima, +# +# =========================================================================== + +MEMORY +{ + main (RWX) : ORIGIN = 0x100000, LENGTH = 0x0 + heap (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 +} + +#FORCE_ACTIVE +#{ +#} + +KEEP_SECTION +{ + .vutext, + .vudata, + .vubss +} + +#REF_INCLUDE +#{ + +SECTIONS +{ + # define for crt0 + _heap_size = -1; + _stack = -1; + _stack_size = 0x00100000; + # define for lcf + _align_segment = 0x80; + + .main : + { + # text sections + . = ALIGN(0x80); + crt0.s (.text) + . = ALIGN(0x10); + ALIGNALL(0x8); + GROUP(ROOT) (.text) + WRITEW 0x0; # text section patch for EE pipeline + WRITEW 0x0; # text section patch for EE pipeline + . = ALIGN(0x80); + ALIGNALL(0x8); + GROUP(ROOT) (.vutext) + + # .reginfo + crt0.s (.reginfo) + + # data sections + __data_start = .; + . = ALIGN(0x80); + ALIGNALL(0x8); + GROUP(ROOT) (.data) + . = ALIGN(0x80); + ALIGNALL(0x8); + GROUP(ROOT) (.vudata) + . = ALIGN(0x80); + ALIGNALL(0x8); + GROUP(ROOT) (.rodata) + . = ALIGN(0x80); + ALIGNALL(0x8); + GROUP(ROOT) (.rdata) + + # static initializers + . = ALIGN(0x10); + ALIGNALL(0x4); + GROUP(ROOT) (.init) + . = ALIGN(0x10); + ALIGNALL(0x4); + __static_init = .; + GROUP(ROOT) (.ctor) + __static_init_end = .; + + # .vtables + . = ALIGN(0x10); + ALIGNALL(0x4); + * (.vtables) + + # exception table + . = ALIGN(0x10); + __exception_table_start__ = .; + __exception_table_end__ = .; + + # addresses for each overlay module + . = ALIGN(0x10); + _overlay_group_addresses = .; + WRITEW ADDR(.main); # + + __data_end = .; + __data_size = __data_end - __data_start; + + # the address of gp register + _gp = ALIGN(128) + 0x7FF0; + + # literal + . = ALIGN(0x80); + ALIGNALL(0x8); + LITERAL + + # small data sections + . = ALIGN(0x80); + ALIGNALL(0x4); + * (.sdata) + . = ALIGN(0x80); + _fbss = .; + ALIGNALL(0x4); + * (.sbss) + ALIGNALL(0x4); + * (.scommon) + ALIGNALL(0x4); + * (SCOMMON) + + # bss sections + __bss_start = .; + . = ALIGN(0x80); + ALIGNALL(0x8); + GROUP(ROOT) (.bss) + ALIGNALL(0x8); + GROUP(ROOT) (.common) + ALIGNALL(0x8); + GROUP(ROOT) (COMMON) + . = ALIGN(0x80); + ALIGNALL(0x8); + GROUP(ROOT) (.vubss) + ALIGNALL(0x8); + __bss_end = .; + __bss_size = __bss_end - __bss_start; + + . = ALIGN(_align_segment); + + } > main + + .heap : + { + # to get the address for heap + end = .; + _end = .; + } > heap + + +} + +# =========================================================================== +# end of lcf (auto-generated by pre-linker for PS2) +# =========================================================================== diff --git a/code/gamespy/common/ps2/cw/PREFIX_PS2_DEBUG_TC296.h b/code/gamespy/common/ps2/cw/PREFIX_PS2_DEBUG_TC296.h new file mode 100644 index 00000000..e28c2144 --- /dev/null +++ b/code/gamespy/common/ps2/cw/PREFIX_PS2_DEBUG_TC296.h @@ -0,0 +1,12 @@ +// =========================================================================== +// PREFIX_PS2_DEBUG_TC296.h (c) 2001 Metrowerks Inc. All rights reserved. +// =========================================================================== +// +// 08/02/2001 rcampana Created for support of GCC toolchain 2.96 + +#include + +#pragma divbyzerocheck on /* break if divided by zero */ + +#define DEBUG /* just for debugging */ + diff --git a/code/gamespy/common/ps2/cw/PREFIX_PS2_RELEASE_TC296.h b/code/gamespy/common/ps2/cw/PREFIX_PS2_RELEASE_TC296.h new file mode 100644 index 00000000..b2643417 --- /dev/null +++ b/code/gamespy/common/ps2/cw/PREFIX_PS2_RELEASE_TC296.h @@ -0,0 +1,11 @@ +// =========================================================================== +// PREFIX_PS2_TC296.h ©2000-2001 Metrowerks Inc. All rights reserved. +// =========================================================================== +// +// 08/02/2001 rcampana Created for support of GCC toolchain 2.96 + +#include + +#define NDEBUG /* just for debugging */ + +#pragma cats off \ No newline at end of file diff --git a/code/gamespy/common/ps2/cw/cw.cww b/code/gamespy/common/ps2/cw/cw.cww new file mode 100644 index 00000000..f3df402f --- /dev/null +++ b/code/gamespy/common/ps2/cw/cw.cww @@ -0,0 +1,364 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]> + + + + -1 + 0 + true + ..\..\Chat\chatc\chatps2cw\chatps2cw.mcp + + 30 + 30 + + + 400 + 200 + + + 1 + 0 + 0 + 59420 + 1.000000 + 425 + + -2 + -2 + + + + + -1 + 0 + ..\..\ghttp\ghttpc\ghttpps2cw\ghttpps2cw.mcp + + 30 + 30 + + + 400 + 200 + + + 1 + 0 + 0 + 59420 + 1.000000 + 425 + + -2 + -2 + + + + + -1 + 0 + ..\..\GP\gptestc\gpps2cw\gpps2cw.mcp + + 30 + 30 + + + 400 + 200 + + + 1 + 0 + 0 + 59420 + 1.000000 + 425 + + -2 + -2 + + + + + -1 + 0 + ..\..\gstats\statstest\gstatsps2cw\gstatsps2cw.mcp + + 30 + 30 + + + 400 + 200 + + + 1 + 0 + 0 + 59420 + 1.000000 + 425 + + -2 + -2 + + + + + -1 + 0 + ..\..\gstats\persisttest\persistps2cw\persistps2cw.mcp + + 30 + 30 + + + 400 + 200 + + + 1 + 0 + 0 + 59420 + 1.000000 + 425 + + -2 + -2 + + + + + -1 + 0 + ..\..\gt2\gt2testc\gt2ps2cw\gt2ps2cw.mcp + + 30 + 30 + + + 400 + 200 + + + 1 + 0 + 0 + 59420 + 1.000000 + 425 + + -2 + -2 + + + + + -1 + 0 + ..\..\NATNEG\simpletest\natnegps2cw\natnegps2cw.mcp + + 30 + 30 + + + 400 + 200 + + + 1 + 0 + 0 + 59420 + 1.000000 + 425 + + -2 + -2 + + + + + -1 + 0 + ..\..\peer\peerc\peerps2cw\peerps2cw.mcp + + 30 + 30 + + + 400 + 200 + + + 1 + 0 + 0 + 59420 + 1.000000 + 425 + + -2 + -2 + + + + + -1 + 0 + ..\..\pt\pttestc\ptps2cw\ptps2cw.mcp + + 30 + 30 + + + 400 + 200 + + + 1 + 0 + 0 + 59420 + 1.000000 + 425 + + -2 + -2 + + + + + -1 + 0 + ..\..\QR2\qr2csample\qr2ps2cw\qr2ps2cw.mcp + + 30 + 30 + + + 400 + 200 + + + 1 + 0 + 0 + 59420 + 1.000000 + 425 + + -2 + -2 + + + + + -1 + 0 + ..\..\SERVERBROWSING\SBCTEST\sbps2cw\sbps2cw.mcp + + 30 + 30 + + + 400 + 200 + + + 1 + 0 + 0 + 59420 + 1.000000 + 425 + + -2 + -2 + + + + + -1 + 0 + ..\..\Voice\voicetest\voicetestps2cw\voicetestps2cw.mcp + + 30 + 30 + + + 400 + 200 + + + 1 + 0 + 0 + 59420 + 1.000000 + 425 + + -2 + -2 + + + + + -1 + 0 + ..\..\Voice\voicebench\voicebenchps2cw\voicebenchps2cw.mcp + + 30 + 30 + + + 400 + 200 + + + 1 + 0 + 0 + 59420 + 1.000000 + 425 + + -2 + -2 + + + + diff --git a/code/gamespy/common/ps2/cw/cwprefix_eenet_debug.h b/code/gamespy/common/ps2/cw/cwprefix_eenet_debug.h new file mode 100644 index 00000000..131b541e --- /dev/null +++ b/code/gamespy/common/ps2/cw/cwprefix_eenet_debug.h @@ -0,0 +1,6 @@ +#include "PREFIX_PS2_DEBUG_TC296.h" + +#define EENET +#define _DEBUG +//#define GSI_UNICODE +//#pragma ushort_wchar_t on \ No newline at end of file diff --git a/code/gamespy/common/ps2/cw/cwprefix_eenet_debug_uniqueid.h b/code/gamespy/common/ps2/cw/cwprefix_eenet_debug_uniqueid.h new file mode 100644 index 00000000..264b4f63 --- /dev/null +++ b/code/gamespy/common/ps2/cw/cwprefix_eenet_debug_uniqueid.h @@ -0,0 +1,5 @@ +#include "PREFIX_PS2_DEBUG_TC296.h" + +#define EENET +#define _DEBUG +#define UNIQUEID \ No newline at end of file diff --git a/code/gamespy/common/ps2/cw/cwprefix_eenet_release.h b/code/gamespy/common/ps2/cw/cwprefix_eenet_release.h new file mode 100644 index 00000000..c7649701 --- /dev/null +++ b/code/gamespy/common/ps2/cw/cwprefix_eenet_release.h @@ -0,0 +1,5 @@ +#include "PREFIX_PS2_RELEASE_TC296.h" + +#define EENET +//#define GSI_UNICODE +//#pragma ushort_wchar_t on \ No newline at end of file diff --git a/code/gamespy/common/ps2/cw/cwprefix_eenet_release_uniqueid.h b/code/gamespy/common/ps2/cw/cwprefix_eenet_release_uniqueid.h new file mode 100644 index 00000000..996d90e5 --- /dev/null +++ b/code/gamespy/common/ps2/cw/cwprefix_eenet_release_uniqueid.h @@ -0,0 +1,4 @@ +#include "PREFIX_PS2_RELEASE_TC296.h" + +#define EENET +#define UNIQUEID \ No newline at end of file diff --git a/code/gamespy/common/ps2/cw/cwprefix_insock_debug.h b/code/gamespy/common/ps2/cw/cwprefix_insock_debug.h new file mode 100644 index 00000000..3fbc9793 --- /dev/null +++ b/code/gamespy/common/ps2/cw/cwprefix_insock_debug.h @@ -0,0 +1,6 @@ +#include "PREFIX_PS2_DEBUG_TC296.h" + +#define INSOCK +#define _DEBUG +//#define GSI_UNICODE +//#pragma ushort_wchar_t on \ No newline at end of file diff --git a/code/gamespy/common/ps2/cw/cwprefix_insock_debug_uniqueid.h b/code/gamespy/common/ps2/cw/cwprefix_insock_debug_uniqueid.h new file mode 100644 index 00000000..f1811925 --- /dev/null +++ b/code/gamespy/common/ps2/cw/cwprefix_insock_debug_uniqueid.h @@ -0,0 +1,5 @@ +#include "PREFIX_PS2_DEBUG_TC296.h" + +#define INSOCK +#define _DEBUG +#define UNIQUEID \ No newline at end of file diff --git a/code/gamespy/common/ps2/cw/cwprefix_insock_release.h b/code/gamespy/common/ps2/cw/cwprefix_insock_release.h new file mode 100644 index 00000000..46220a81 --- /dev/null +++ b/code/gamespy/common/ps2/cw/cwprefix_insock_release.h @@ -0,0 +1,5 @@ +#include "PREFIX_PS2_RELEASE_TC296.h" + +#define INSOCK +//#define GSI_UNICODE +//#pragma ushort_wchar_t on \ No newline at end of file diff --git a/code/gamespy/common/ps2/cw/cwprefix_insock_release_uniqueid.h b/code/gamespy/common/ps2/cw/cwprefix_insock_release_uniqueid.h new file mode 100644 index 00000000..1db3bb29 --- /dev/null +++ b/code/gamespy/common/ps2/cw/cwprefix_insock_release_uniqueid.h @@ -0,0 +1,4 @@ +#include "PREFIX_PS2_RELEASE_TC296.h" + +#define INSOCK +#define UNIQUEID \ No newline at end of file diff --git a/code/gamespy/common/ps2/cw/cwprefix_snsystems_debug.h b/code/gamespy/common/ps2/cw/cwprefix_snsystems_debug.h new file mode 100644 index 00000000..81b86d14 --- /dev/null +++ b/code/gamespy/common/ps2/cw/cwprefix_snsystems_debug.h @@ -0,0 +1,6 @@ +#include "PREFIX_PS2_DEBUG_TC296.h" + +#define SN_SYSTEMS +#define _DEBUG +//#define GSI_UNICODE +//#pragma ushort_wchar_t on \ No newline at end of file diff --git a/code/gamespy/common/ps2/cw/cwprefix_snsystems_debug_uniqueid.h b/code/gamespy/common/ps2/cw/cwprefix_snsystems_debug_uniqueid.h new file mode 100644 index 00000000..83f1f9d0 --- /dev/null +++ b/code/gamespy/common/ps2/cw/cwprefix_snsystems_debug_uniqueid.h @@ -0,0 +1,5 @@ +#include "PREFIX_PS2_DEBUG_TC296.h" + +#define SN_SYSTEMS +#define _DEBUG +#define UNIQUEID \ No newline at end of file diff --git a/code/gamespy/common/ps2/cw/cwprefix_snsystems_release.h b/code/gamespy/common/ps2/cw/cwprefix_snsystems_release.h new file mode 100644 index 00000000..6f4e4955 --- /dev/null +++ b/code/gamespy/common/ps2/cw/cwprefix_snsystems_release.h @@ -0,0 +1,5 @@ +#include "PREFIX_PS2_RELEASE_TC296.h" + +#define SN_SYSTEMS +//#define GSI_UNICODE +//#pragma ushort_wchar_t on \ No newline at end of file diff --git a/code/gamespy/common/ps2/cw/cwprefix_snsystems_release_uniqueid.h b/code/gamespy/common/ps2/cw/cwprefix_snsystems_release_uniqueid.h new file mode 100644 index 00000000..42c0791f --- /dev/null +++ b/code/gamespy/common/ps2/cw/cwprefix_snsystems_release_uniqueid.h @@ -0,0 +1,4 @@ +#include "PREFIX_PS2_RELEASE_TC296.h" + +#define SN_SYSTEMS +#define UNIQUEID \ No newline at end of file diff --git a/code/gamespy/common/ps2/ent_cnf/cw/LinkSegment_PS2IOP.lcf b/code/gamespy/common/ps2/ent_cnf/cw/LinkSegment_PS2IOP.lcf new file mode 100644 index 00000000..bc865d96 --- /dev/null +++ b/code/gamespy/common/ps2/ent_cnf/cw/LinkSegment_PS2IOP.lcf @@ -0,0 +1,78 @@ +#=========================================================================== +# LinkSegment_PS2IOP.lcf ©1999-2003 Metrowerks Inc. All rights reserved. +#=========================================================================== +# +# Linker command file for PS2 IOP +# +#=========================================================================== + +MEMORY +{ + .text (RWX) : ORIGIN = 0x00000000, LENGTH = 0x00 + .data (RW) : ORIGIN = AFTER(.text), LENGTH = 0x00 + .bss (RW) : ORIGIN = AFTER(.data), LENGTH = 0X00 +} + +FORCE_ACTIVE{Module} + +SECTIONS +{ + .text : + { + . = ALIGN(0x10); + _ftext = .; + + # begin text section + * (.text) + * (.common) + * (.scommon) + # end of text section + + # align so that size of segment is 16 byte aligned + . = ALIGN(0x10); + _etext = .; + etext = .; + } > .text + + .data : + { + . = ALIGN(0x10); + _fdata = .; + + # start data section + * (.rodata) + * (.data) + _gp = ALIGN(0x10) + 0x7FF0; + * (.sdata) + # literal + LITERAL + + # end data section + + + # align so that size of segment is 16 byte aligned + . = ALIGN(0x10); + _edata = .; + edata = .; + + } > .data + + .bss : + { + . = ALIGN(0x10); + _fbss = .; + + # start bss section + * (.bss) + * (.sbss) + # end bss section + + # align so that size of segment is 16 byte aligned + . = ALIGN(0x10); + end = .; + _end = .; + + } > .bss +} + +#=========================================================================== diff --git a/code/gamespy/common/ps2/ent_cnf/cw/PREFIX_PS2IOP_DEBUG.h b/code/gamespy/common/ps2/ent_cnf/cw/PREFIX_PS2IOP_DEBUG.h new file mode 100644 index 00000000..b810f12b --- /dev/null +++ b/code/gamespy/common/ps2/ent_cnf/cw/PREFIX_PS2IOP_DEBUG.h @@ -0,0 +1,13 @@ +// =========================================================================== +// PREFIX_PS2_DEBUG.h ©2000 Metrowerks Inc. All rights reserved. +// =========================================================================== +// +// 07/18/2000 james combs, for SDK1.6 + + +#include + +#pragma divbyzerocheck on /* break if divided by zero */ + +#define DEBUG /* just for debugging */ + diff --git a/code/gamespy/common/ps2/ent_cnf/cw/PREFIX_PS2IOP_RELEASE.h b/code/gamespy/common/ps2/ent_cnf/cw/PREFIX_PS2IOP_RELEASE.h new file mode 100644 index 00000000..3e650b58 --- /dev/null +++ b/code/gamespy/common/ps2/ent_cnf/cw/PREFIX_PS2IOP_RELEASE.h @@ -0,0 +1,9 @@ +// =========================================================================== +// PREFIX_PS2_RELEASE.h ©2000 Metrowerks Inc. All rights reserved. +// =========================================================================== +// +// 07/18/2000 james combs, for SDK1.6 + +#include + +#define NDEBUG \ No newline at end of file diff --git a/code/gamespy/common/ps2/ent_cnf/prodg/ent_cnf.dsp b/code/gamespy/common/ps2/ent_cnf/prodg/ent_cnf.dsp new file mode 100644 index 00000000..a012bfd4 --- /dev/null +++ b/code/gamespy/common/ps2/ent_cnf/prodg/ent_cnf.dsp @@ -0,0 +1,119 @@ +# Microsoft Developer Studio Project File - Name="ent_cnf" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=ent_cnf - Win32 PS2 IOP Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "ent_cnf.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "ent_cnf.mak" CFG="ent_cnf - Win32 PS2 IOP Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "ent_cnf - Win32 PS2 IOP Debug" (based on "Win32 (x86) Application") +!MESSAGE "ent_cnf - Win32 PS2 IOP Release" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/Gamespy/GOA/ps2common/ent_cnf/prodg", UXEDAAAA" +# PROP Scc_LocalPath "." +CPP=snCl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "ent_cnf - Win32 PS2 IOP Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "ent_cnf___Win32_PS2_IOP_Debug" +# PROP BASE Intermediate_Dir "ent_cnf___Win32_PS2_IOP_Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "ent_cnf___Win32_PS2_IOP_Debug" +# PROP Intermediate_Dir "ent_cnf___Win32_PS2_IOP_Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /GZ /c +# ADD CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\sample\libeenet\ent_cnf" /D "SN_TARGET_PS2_IOP" /Fo"PS2_IOP_Debug/" /FD -G0 /debug /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.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 /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 eenetctl.ilb netcnf.ilb C:\usr\local\sce\iop\sample\libeenet\ent_cnf\ent_cnfent.o /nologo /pdb:none /debug /machine:IX86 /out:"c:\usr\local\sce\iop\sample\libeenet\ent_cnf\ent_cnf.irx" /D:SN_TARGET_PS2_IOP +# Begin Special Build Tool +SOURCE="$(InputPath)" +PreLink_Cmds=ioplibgen C:\usr\local\sce\iop\sample\libeenet\ent_cnf\ent_cnf.tbl -e C:\usr\local\sce\iop\sample\libeenet\ent_cnf\ent_cnfent.s ps2cc -iop -o C:\usr\local\sce\iop\sample\libeenet\ent_cnf\ent_cnfent.o C:\usr\local\sce\iop\sample\libeenet\ent_cnf\ent_cnfent.s +# End Special Build Tool + +!ELSEIF "$(CFG)" == "ent_cnf - Win32 PS2 IOP Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "ent_cnf___Win32_PS2_IOP_Release" +# PROP BASE Intermediate_Dir "ent_cnf___Win32_PS2_IOP_Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "ent_cnf___Win32_PS2_IOP_Release" +# PROP Intermediate_Dir "ent_cnf___Win32_PS2_IOP_Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /GZ /c +# ADD CPP /nologo /w /W0 /O2 /I "C:\usr\local\sce\ee\sample\libeenet\ent_cnf" /D "SN_TARGET_PS2_IOP" /Fo"PS2_IOP_Release/" /FD -G0 /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.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 /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 eenetctl.ilb netcnf.ilb C:\usr\local\sce\iop\sample\libeenet\ent_cnf\ent_cnfent.o /nologo /pdb:none /machine:IX86 /out:"c:\usr\local\sce\iop\sample\libeenet\ent_cnf\ent_cnf.irx" /D:SN_TARGET_PS2_IOP +# Begin Special Build Tool +SOURCE="$(InputPath)" +PreLink_Cmds=ioplibgen C:\usr\local\sce\iop\sample\libeenet\ent_cnf\ent_cnf.tbl -e C:\usr\local\sce\iop\sample\libeenet\ent_cnf\ent_cnfent.s ps2cc -iop -o C:\usr\local\sce\iop\sample\libeenet\ent_cnf\ent_cnfent.o C:\usr\local\sce\iop\sample\libeenet\ent_cnf\ent_cnfent.s +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "ent_cnf - Win32 PS2 IOP Debug" +# Name "ent_cnf - Win32 PS2 IOP Release" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=c:\usr\local\sce\iop\sample\libeenet\ent_cnf\ent_cnf.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=c:\usr\local\sce\iop\sample\libeenet\ent_cnf\ioptypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\prodg\PS2_in_VC.h +# End Source File +# End Group +# End Target +# End Project diff --git a/code/gamespy/common/ps2/ent_cnf/prodg/ent_cnf.dsw b/code/gamespy/common/ps2/ent_cnf/prodg/ent_cnf.dsw new file mode 100644 index 00000000..3a68ef0a --- /dev/null +++ b/code/gamespy/common/ps2/ent_cnf/prodg/ent_cnf.dsw @@ -0,0 +1,37 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "ent_cnf"=.\ent_cnf.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/ps2common/ent_cnf/prodg", UXEDAAAA + . + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/ps2common/ent_cnf/prodg", UXEDAAAA + . + end source code control +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/code/gamespy/common/ps2/gsSocketPs2.c b/code/gamespy/common/ps2/gsSocketPs2.c new file mode 100644 index 00000000..f99d509c --- /dev/null +++ b/code/gamespy/common/ps2/gsSocketPs2.c @@ -0,0 +1,78 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gscommon.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// INSOCK +#if defined(INSOCK) +#define INSOCK_MAX_UDP_BUFSIZE 8000000 // default max +#define INSOCK_MAX_TCP_BUFSIZE 32000 + +extern sceSifMClientData gGSIInsockClientData; +extern u_int gGSIInsockSocketBuffer[NETBUFSIZE] __attribute__((aligned(64))); + +// NOT FULLY IMPLEMENTED +int SetReceiveBufferSize(SOCKET sock, int size) +{return -1; GSI_UNUSED(sock); GSI_UNUSED(size); } + +// NOT FULLY IMPLEMENTED +int SetSendBufferSize(SOCKET sock, int size) +{return -1; GSI_UNUSED(sock); GSI_UNUSED(size); } + +int GetReceiveBufferSize(SOCKET sock) +{return NETBUFSIZE; GSI_UNUSED(sock); } + +int GetSendBufferSize(SOCKET sock) +{return NETBUFSIZE; GSI_UNUSED(sock); } + +// Poll socket for Send, Recv and Except +int GSISocketSelect(SOCKET theSocket, int* theReadFlag, int* theWriteFlag, int* theExceptFlag) +{ + int result = 0; + sceInetPollFd_t aPollFdSet; + + // Init the flags to 0 + if ((theReadFlag != NULL)) + *theReadFlag = 0; + if ((theWriteFlag != NULL)) + *theWriteFlag = 0; + if ((theExceptFlag != NULL)) + *theExceptFlag = 0; + + // Setup the fd set + aPollFdSet.cid = theSocket; // the socket + aPollFdSet.events = 0; // events in + aPollFdSet.revents = 0; // events out + + if (theReadFlag != NULL) aPollFdSet.events |= sceINET_POLLIN; + if (theWriteFlag != NULL) aPollFdSet.events |= sceINET_POLLOUT; + if (theExceptFlag != NULL) aPollFdSet.events |= sceINET_POLLERR; + + // Poll the fds + // 1 fds, 0 ms timeout + result = sceInsockPoll(&aPollFdSet, 1, 0); + if (result > 0) + { + // If the Flag is valid, set the return value + if ((theReadFlag != NULL)) + *theReadFlag = (aPollFdSet.revents & sceINET_POLLIN) ? 1:0; + if ((theWriteFlag != NULL)) + *theWriteFlag = (aPollFdSet.revents & sceINET_POLLOUT) ? 1:0; + if ((theExceptFlag != NULL)) + *theExceptFlag = (aPollFdSet.revents & sceINET_POLLERR) ? 1:0; + } + return result; +} + +// shutdown needs to have a timeout that can be done +// right before shutting down +int gsiShutdown(SOCKET s, int how) +{ + // set the shutdown timeout to thirty milliseconds based on most games running + // thirty frames per second (33ms rounded down to 30) + sceInsockSetShutdownTimeout(s, 30); + return sceInsockShutdown(s, how); +} +#endif \ No newline at end of file diff --git a/code/gamespy/common/ps2/gsThreadPs2.c b/code/gamespy/common/ps2/gsThreadPs2.c new file mode 100644 index 00000000..89594dbb --- /dev/null +++ b/code/gamespy/common/ps2/gsThreadPs2.c @@ -0,0 +1,204 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gscommon.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u32 gsiInterlockedIncrement(gsi_u32 * value) +{ + int interrupt = DI(); + int ret = ++(*value); + if (interrupt) + EI(); + + // return "ret" rather than "value" here b/c + // value may be modified by another thread + // before we can return it + return ret; +} + +gsi_u32 gsiInterlockedDecrement(gsi_u32 * value) +{ + int interrupt = DI(); + int ret = --(*value); + if (interrupt) + EI(); + + // return "ret" rather than "value" here b/c + // value may be modified by another thread + // before we can return it + return ret; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiInitializeCriticalSection(GSICriticalSection *theCrit) +{ + theCrit->mSemaphore = gsiCreateSemaphore(1, 1, NULL); + theCrit->mOwnerThread = 0; + theCrit->mEntryCount = 0; +} +void gsiEnterCriticalSection(GSICriticalSection *theCrit) +{ + // If we're not already in it, wait for it + if (GetThreadId() != theCrit->mOwnerThread) + { + gsiWaitForSemaphore(theCrit->mSemaphore, 0); + theCrit->mOwnerThread = GetThreadId(); + } + + // Increment entry count + theCrit->mEntryCount++; +} +void gsiLeaveCriticalSection(GSICriticalSection *theCrit) +{ + // We must be the owner? (assert?) + if (GetThreadId() != theCrit->mOwnerThread) + { + assert(GetThreadId() == theCrit->mOwnerThread); + return; + } + + // Release semaphore + theCrit->mEntryCount--; + if (theCrit->mEntryCount == 0) + { + theCrit->mOwnerThread = 0; + gsiReleaseSemaphore(theCrit->mSemaphore, 1); + } +} + +void gsiDeleteCriticalSection(GSICriticalSection *theCrit) +{ + gsiCloseSemaphore(theCrit->mSemaphore); +} + +gsi_u32 gsiHasThreadShutdown(GSIThreadID theThreadID) +{ + struct ThreadParam aStatus; + ReferThreadStatus(theThreadID, &aStatus); + if (aStatus.status == THS_DORMANT) + return 1; // dead + else + return 0; // still kicking; +} + +GSISemaphoreID gsiCreateSemaphore(gsi_i32 theInitialCount, gsi_i32 theMaxCount, char* theName) +{ + struct SemaParam aParam; + int aSemaphore = 0; + + aParam.initCount = theInitialCount; + aParam.maxCount = theMaxCount; + + aSemaphore = CreateSema(&aParam); + if (aSemaphore < 0) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to create semaphore\r\n"); + } + + GSI_UNUSED(theName); + + return aSemaphore; +} + +gsi_u32 gsiWaitForSemaphore(GSISemaphoreID theSemaphore, gsi_u32 theTimeoutMs) +{ + int result = WaitSema(theSemaphore); + return (gsi_u32)result; + + GSI_UNUSED(theTimeoutMs); +} + +void gsiReleaseSemaphore(GSISemaphoreID theSemaphore, gsi_i32 theReleaseCount) +{ + while (theReleaseCount-- > 0) + SignalSema(theSemaphore); + //ReleaseSemaphore(theSemaphore, theReleaseCount, NULL); +} + +void gsiCloseSemaphore(GSISemaphoreID theSemaphore) +{ + DeleteSema(theSemaphore); +} + +int gsiStartThread(GSThreadFunc func, gsi_u32 theStackSize, void *arg, GSIThreadID *id) +{ + const unsigned int stackSize = theStackSize; + const int threadPriority = 3; + struct ThreadParam param; + void * stack; + int threadID; + + // allocate a stack + stack = gsimemalign(16, stackSize); + if(!stack) + return -1; + + // setup the thread parameters + param.entry = func; + param.stack = stack; + param.stackSize = (int)stackSize; + param.gpReg = &_gp; + param.initPriority = threadPriority; + + // create the thread + threadID = CreateThread(¶m); + if(threadID == -1) + { + gsifree(stack); + return -1; + } + + // start the thread + if(StartThread(threadID, arg) == -1) + { + DeleteThread(threadID); + gsifree(stack); + return -1; + } + + // store the id + *id = threadID; + + // Note: This was added to prevent PS2 lockups when starting multiple threads + // The PS2 would block for approx 5 seconds + msleep(1); + + return 0; +} + +void gsiCancelThread(GSIThreadID id) +{ + void* aStack = NULL; + + // get the stack ptr + struct ThreadParam aThreadParam; + ReferThreadStatus(id, &aThreadParam); + aStack = (void*)aThreadParam.stack; + + // terminate the thread + TerminateThread(id); + + // delete the thread + DeleteThread(id); + + //free the stack + gsifree(aStack); +} + +// This must be called from INSIDE the thread you wish to exit +void gsiExitThread(GSIThreadID id) +{ + // TODO: does PS2 need to explicitly EXIT a thread like win32/linux? + GSI_UNUSED(id); +} + +void gsiCleanupThread(GSIThreadID id) +{ + // same as cancel (terminates just to be sure) + gsiCancelThread(id); +} \ No newline at end of file diff --git a/code/gamespy/common/ps2/gsUtilPs2.c b/code/gamespy/common/ps2/gsUtilPs2.c new file mode 100644 index 00000000..71b10415 --- /dev/null +++ b/code/gamespy/common/ps2/gsUtilPs2.c @@ -0,0 +1,15 @@ +#include "../gsCommon.h" + +gsi_i64 gsiStringToInt64(const char *theNumberStr) +{ + return atol(theNumberStr); +} + +void gsiInt64ToString(char theNumberStr[33], gsi_i64 theNumber) +{ + // you want to fit the number! + // give me a valid string! + GS_ASSERT(theNumberStr != NULL); + + sprintf(theNumberStr, "%ld", theNumber); +} \ No newline at end of file diff --git a/code/gamespy/common/ps2/prodg/PS2_in_VC.h b/code/gamespy/common/ps2/prodg/PS2_in_VC.h new file mode 100644 index 00000000..c1aa1d86 --- /dev/null +++ b/code/gamespy/common/ps2/prodg/PS2_in_VC.h @@ -0,0 +1,45 @@ +// +// The purpose of this file is to, as much as possible, facilitate the +// compilation of the PS2 GNU files by the VC6 compiler. Not all problems +// can be solved via this file. So far, a couple of hacks are required +// directly within the headers themselves. +// + +// +// This will nullify all of the GNU compiler's __attribute__ things. +// +#define __attribute__(a) + +// +// This will prevent an Endian not defined error. +// +#define __IEEE_LITTLE_ENDIAN + +// +// This will prevent a size_t redefinition error. +// +#define __size_t__ + +// +// This will prevent errors by headers which use u_long128. Obviously +// the type defined below is only 32 bits, but (1) this is what the real +// headers seem to do in the GNU compiler and more importantly (2) it works! +// +typedef unsigned int u_long128; + + +#define long _int64 + +#define INLINE __inline + +#define size_t int + +#define __asm__ asm + +#define __volatile__ + +#define asm(a) asm() + +#define inline + +#define volatile diff --git a/code/gamespy/common/ps2/prodg/prodg.dsw b/code/gamespy/common/ps2/prodg/prodg.dsw new file mode 100644 index 00000000..5a3e33c3 --- /dev/null +++ b/code/gamespy/common/ps2/prodg/prodg.dsw @@ -0,0 +1,229 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "chatps2prodg"=..\..\Chat\chatc\chatps2prodg\chatps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/Chat/chatc/chatps2prodg", DSEDAAAA + ..\..\chat\chatc\chatps2prodg + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "ghttpps2prodg"=..\..\ghttp\ghttpc\ghttpps2prodg\ghttpps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/ghttp/ghttpc/ghttpps2prodg", JSEDAAAA + ..\..\ghttp\ghttpc\ghttpps2prodg + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "gpps2prodg"=..\..\GP\gptestc\gpps2prodg\gpps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/GP/gptestc/gpps2prodg", PSEDAAAA + ..\..\gp\gptestc\gpps2prodg + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "gstatsps2prodg"=..\..\gstats\statstest\gstatsps2prodg\gstatsps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/gstats/statstest/gstatsps2prodg", VSEDAAAA + ..\..\gstats\statstest\gstatsps2prodg + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "gt2ps2prodg"=..\..\gt2\gt2testc\gt2ps2prodg\gt2ps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/gt2/gt2testc/gt2ps2prodg", XREDAAAA + ..\..\gt2\gt2testc\gt2ps2prodg + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "gvps2prodg"=..\..\Voice2\Voice2Test\gvps2prodg\gvps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/Voice2/Voice2Test/gvps2prodg", XPODAAAA + ..\..\voice2\voice2test\gvps2prodg + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "natnegps2prodg"=..\..\NATNEG\simpletest\natnegps2prodg\natnegps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/natneg/simpletest/natnegps2prodg", HTEDAAAA + ..\..\natneg\simpletest\natnegps2prodg + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "peerps2prodg"=..\..\peer\peerc\peerps2prodg\peerps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/Peer/peerc/peerps2prodg", NTEDAAAA + ..\..\peer\peerc\peerps2prodg + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "persistps2prodg"=..\..\gstats\persisttest\persistps2prodg\persistps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/gstats/persisttest/persistps2prodg", BTEDAAAA + ..\..\gstats\persisttest\persistps2prodg + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "ptps2prodg"=..\..\pt\pttestc\ptps2prodg\ptps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/pt/pttestc/ptps2prodg", TTEDAAAA + ..\..\pt\pttestc\ptps2prodg + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "qr2ps2prodg"=..\..\QR2\qr2csample\qr2ps2prodg\qr2ps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/qr2/qr2csample/qr2ps2prodg", ZTEDAAAA + ..\..\qr2\qr2csample\qr2ps2prodg + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "sbps2prodg"=..\..\SERVERBROWSING\SBCTEST\sbps2prodg\sbps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/serverbrowsing/sbctest/sbps2prodg", FUEDAAAA + ..\..\serverbrowsing\sbctest\sbps2prodg + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "voice2benchps2prodg"=..\..\Voice2\voice2bench\voice2benchps2prodg\voice2benchps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + $/Gamespy/GOA/Voice2/voice2bench/voice2benchps2prodg + ..\..\voice2\voice2bench\voice2benchps2prodg + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/ps2common/prodg", DVEDAAAA + . + end source code control +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/code/gamespy/common/ps2/prodg/ps2.lk b/code/gamespy/common/ps2/prodg/ps2.lk new file mode 100644 index 00000000..826c59f7 --- /dev/null +++ b/code/gamespy/common/ps2/prodg/ps2.lk @@ -0,0 +1,88 @@ +; SN Systems default linker script for PS2. +; Updated for libraries 1.6.0 by Gil + +; Default libraries. (Supply others on the command line.) + + inclib libc.a + inclib libkernl.a + inclib libgcc.a + inclib libm.a + + +; The heap size and stack details are defined here. + +_heap_size equ 0xffffffff +_stack equ 0xffffffff +_stack_size equ 0x00100000 + + +; Groups represent entries in the output ELF's program headers table. +; Each contains one or more sections. +; A group only appears in the PHDRS table if it is named and has +; nonzero size. + + +; This group is for the program's code and initialised data. + + org 0x00100000 + +text group + + sectalign 8 + section .text,text + section .vutext,text + + sectalign 16 + section .data,text + section.128 .vudata,text + section .rodata,text + section .rdata,text + section .gcc_except_table,text + +; Collect everything else which is part of the image here. +; (Subsequent section directives get a chance to collect contents first.) + + section *,text + +; Set the GP register's value. +; The total size of these sections (from .lit8 to .scommon) cannot +; exceed 64K. + +_gp equ __lit8_obj+0x7FF0 + + section .lit8,text + section .lit4,text + section .sdata,text + + +; This group is for uninitialised data + +bss group bss + + +; This is the start marker for the startup code's zeroing routine. + +_fbss equ _bss_obj + +; These sections are to be zeroised by crt0.o. + + section .sbss,bss + section .scommon,bss + section .bss,bss + section.128 .vubss,bss + + +; This is crt0.o's marker for the start of the heap. + +_end equ _bss_objend + + +; This group is for the scratchpad. + + org 0x70000000 + +spad group + + sectalign 4 + + section .spad,spad diff --git a/code/gamespy/common/ps2/ps2common.c b/code/gamespy/common/ps2/ps2common.c new file mode 100644 index 00000000..248a7659 --- /dev/null +++ b/code/gamespy/common/ps2/ps2common.c @@ -0,0 +1,876 @@ +#include "../gsCommon.h" + +// One of the following network devices must be defined +#if 0 +#define T10K_ETHERNET +#endif +#if 0 +#define USB_ETHERNET +#endif +#if 0 +#define USB_ETHERNET_WITH_PPPOE +#endif +#if 1 +#define HDD_ETHERNET +#endif +#if 0 +#define HDD_ETHERNET_WITH_PPPOE +#endif +#if 0 +#define MODEM +#endif + +// attempts to load a module +static int load_module(const char * filename, int args, const char * argp) +{ + int result; + + result = sceSifLoadModule(filename, args, argp); + if(result < 0) + { + const char * errorString; + if(result == -100) + errorString = "Called from exception handler / interrupt handler"; + else if(result == -200) + errorString = "Resident library required by loaded module does not exist"; + else if(result == -201) + errorString = "Object file format is invalid"; + else if(result == -203) + errorString = "Specified file was not found"; + else if(result == -204) + errorString = "Error occurred when reading file"; + else if(result == -400) + errorString = "Insufficient memory"; + else if(result == -0x10000) + errorString = "Binding to the IOP module failed."; + else if(result == -0x10001) + errorString = "RPC to the IOP failed."; + else if(result == -0x10004) + errorString = "The IOP module version does not match."; + else + errorString = "Unknown error"; + scePrintf("FAILED TO LOAD MODULE: %s\n", filename); + scePrintf("\t(%d:%s)\n", result, errorString); + while(1) + msleep(1); + } + + return result; +} + +#define SCEROOT "host0:/usr/local/sce/" +#define MODROOT SCEROOT "iop/modules/" +#define APPROOT SCEROOT "conf/neticon/english/" + +// SN Systems stack +#ifdef SN_SYSTEMS + +// these only need to be set if using DHCP +// up to four DNS servers can be specified, and the list must be terminated with an empty string +#define SNPS2_IP_ADDR "0.0.0.0" +#define SNPS2_SUB_MSK "0.0.0.0" +#define SNPS2_GATEWAY "0.0.0.0" +static const sn_char* dns_servers[] = { "" }; + +static void load_network_modules() +{ + // load the TCP/IP stack module +#ifndef _DEBUG + load_module(MODROOT "snstkrel.irx", 0, NULL); +#else + const char iop_params[] + = SNPS2_IP_ADDR "\x00" SNPS2_SUB_MSK "\x00" SNPS2_GATEWAY; + + load_module(MODROOT "snstkdbg.irx", sizeof(iop_params), (char*) iop_params); +#endif + + // load device specific module(s) +#if defined(T10K_ETHERNET) + load_module(MODROOT "sndrv000.irx", 0, NULL); + +#elif defined(USB_ETHERNET) + load_module(MODROOT "usbd.irx", 0, NULL); + #if defined(USB_LUCENT) + load_module(MODROOT "sndrv002.irx", 0, NULL); + #elif defined(USB_CONEXANT) + load_module(MODROOT "sndrv003.irx", 0, NULL); + #else + load_module(MODROOT "sndrv001.irx", 0, NULL); + #endif + +#elif defined(USB_ETHERNET_WITH_PPPOE) + load_module(MODROOT "usbd.irx", 0, NULL); + load_module(MODROOT "sndrv200.irx", 0, NULL); + load_module(MODROOT "sndrv201.irx", 0, NULL); + +#elif defined(HDD_ETHERNET) + load_module(MODROOT "dev9.irx", 0, NULL); + load_module(MODROOT "sndrv100.irx", 0, NULL); + load_module(MODROOT "smap.irx", 0, NULL); + +#elif defined(HDD_ETHERNET_WITH_PPPOE) + load_module(MODROOT "dev9.irx", 0, NULL); + load_module(MODROOT "sndrv200.irx", 0, NULL); + load_module(MODROOT "sndrv202.irx", 0, NULL); + load_module(MODROOT "smap.irx", 0, NULL); + +#elif defined(MODEM) + load_module(MODROOT "dev9.irx", 0, NULL); + load_module(MODROOT "sndrv101.irx", 0, NULL); + load_module(MODROOT "spduart.irx", 0, NULL); +#endif +} + +static sn_int32 do_initialisation(void) +{ + sn_int32 result; + sn_int32 device_attached; + sn_int16 idVendor; + sn_int16 idProduct; + sn_int32 stack_state; + sndev_set_ether_ip_type params; + struct hostent * host; + sn_bool got_ip; + struct in_addr addr; + + // load network modules + load_network_modules(); + + // init the socket API + scePrintf("Initializing the sockets API\n"); + result = sockAPIinit(1); + if(result != 0) + { + scePrintf("sockAPIinit() failed %d\n", result); + return result; + } + + // register this thread with the socket API + result = sockAPIregthr(); + if(result != 0) + { + scePrintf("sockAPIregthr() failed %d\n", result); + return result; + } + + // wait for the device adaptor to be attached + scePrintf("Waiting for network device to be initialized\n"); + device_attached = SN_DEV_TYPE_NONE; + while(device_attached == SN_DEV_TYPE_NONE) + { + // check if attached + result = sndev_get_attached(0, &device_attached, &idVendor, &idProduct); + if(result != 0) + { + scePrintf("sndev_get_attached() failed %d\n", result); + return result; + } + + // if nothing attached, give it a rest before trying again + if(device_attached == SN_DEV_TYPE_NONE) + sn_delay(10); + } + + scePrintf("Device ready (idVendor=0x%04X idProduct=0x%04X)\n", ((int)idVendor) & 0xFFFF, ((int)idProduct) & 0xFFFF); + + // set the DNS servers + result = sntc_set_dns_server_list((const sn_char**)dns_servers); + if(result != 0) + { + scePrintf("sntc_set_dns_server_list() failed %d\n", result); + return result; + } + + // set the IP, subnet mask, and gateway (all 0's for DHCP) + inet_aton(SNPS2_IP_ADDR, (struct in_addr*)¶ms.ip_addr); + inet_aton(SNPS2_SUB_MSK, (struct in_addr*)¶ms.sub_mask); + inet_aton(SNPS2_GATEWAY, (struct in_addr*)¶ms.gateway); + result = sndev_set_options(0, SN_DEV_SET_ETHER_IP, ¶ms, sizeof(params)); + if(result != 0) + { + scePrintf("sndev_set_options() failed %d\n", result); + return result; + } + + // start the stack + scePrintf("Starting the TCP/IP stack\n"); + result = sn_stack_state(SN_STACK_STATE_START, &stack_state); + if(result != 0) + { + scePrintf("sn_stack_sate() failed %d\n", result); + return result; + } + + // wait for the stack to come up + while(sn_socket_api_ready() == SN_FALSE) + sn_delay(100); + + // wait for a non-zero IP (for DHCP) + scePrintf("Waiting for DHCP-supplied IP\n"); + got_ip = SN_FALSE; + do + { + // get the local host info + host = gethostbyname(LOCAL_NAME); + if(host && host->h_addr_list[0]) + { + // copy the IP + memcpy(&addr, host->h_addr_list[0], sizeof(addr)); + if(addr.s_addr) + { + got_ip = SN_TRUE; + scePrintf("DHCP allocated IP addr %s\n", inet_ntoa(addr)); + } + } + + // don't hog the CPU + if(got_ip == SN_FALSE) + sn_delay(100); + } + while(got_ip == SN_FALSE); + + return 0; +} + +static void do_shutdown(void) +{ + int result; + int stack_state; + + // stopping the stack + result = sn_stack_state(SN_STACK_STATE_STOP, &stack_state); + if(result != 0) + { + scePrintf("sn_stack_sate() failed %d\n", result); + return; + } + + // deregister this thread with the socket API + sockAPIderegthr(); +} +#endif // SN_SYSTEMS + +#ifdef EENET + +#ifdef __MWERKS__ +#include "/ee/sample/libeenet/ent_cnf/ent_cnf.h" +#else +#include "/usr/local/sce/ee/sample/libeenet/ent_cnf/ent_cnf.h" +#endif + +#if defined(USB_ETHERNET) +#define USR_CONF_NAME "Combination4" +#elif defined(USB_ETHERNET_WITH_PPPOE) +#define USR_CONF_NAME "Combination5" +#elif defined(HDD_ETHERNET) +#define USR_CONF_NAME "Combination6" +#elif defined(HDD_ETHERNET_WITH_PPPOE) +#define USR_CONF_NAME "Combination7" +#elif defined(MODEM) +#define USR_CONF_NAME "" +#endif + +#define MOD_NETCNF MODROOT "netcnf.irx" +#define MOD_EENETCTL MODROOT "eenetctl.irx" +#define MOD_DEV9 MODROOT "dev9.irx" +#define MOD_USBD MODROOT "usbd.irx" +#define MOD_ENT_DEVM MODROOT "ent_devm.irx" +#define MOD_ENT_SMAP MODROOT "ent_smap.irx" +#define MOD_ENT_ETH MODROOT "ent_eth.irx" +#define MOD_ENT_PPP MODROOT "ent_ppp.irx" +#define MOD_ENT_CNF SCEROOT "iop/sample/libeenet/ent_cnf/ent_cnf.irx" +#define MOD_MODEMDRV "" + +#define INETCTL_ARG "-no_auto" "\0" "-no_decode" +#define NETCNF_ICON APPROOT "SYS_NET.ICO" +#define NETCNF_ICONSYS APPROOT "icon.sys" +#define NETCNF_ARG "icon=" NETCNF_ICON "\0" "iconsys=" NETCNF_ICONSYS +#define MODEMDRV_ARG "" + +#define NET_DB SCEROOT "conf/net/net.db" + +#define EENET_MEMSIZE (512 * 1024) +#define EENET_TPL 32 +#define EENET_APP_PRIO 48 + +static int eenetctl_mid; +static int ent_cnf_mid; +static int ent_devm_mid; +#if defined(HDD_ETHERNET) || defined(HDD_ETHERNET_WITH_PPPOE) +#define EENET_IFNAME "smap0" +static int ent_smap_mid; +#endif +#if defined(USB_ETHERNET) || defined(USB_ETHERNET_WITH_PPPOE) +#define EENET_IFNAME "eth0" +static int ent_eth_mid; +#endif +#if defined(MODEM) +#define EENET_IFNAME "ppp0" +static int ent_ppp_mid; +static int modem_mid; +#endif + +static void * eenet_pool; + +static int sema_id; +static int event_flag = 0; +#define Ev_Attach 0x01 +#define Ev_UpCompleted 0x02 +#define Ev_DownCompleted 0x04 +#define Ev_DetachCompleted 0x08 + +#define WaitEvent(event) \ + while(1){ \ + WaitSema(sema_id); \ + if(event_flag & (event)) \ + break; \ + } + +static void event_handler(const char *ifname, int af, int type) +{ + scePrintf("event_handler: event happened. af = %d, type = %d\n", af, type); + + switch(type) + { + case sceEENETCTL_IEV_Attach: + if(strcmp(ifname, EENET_IFNAME)) + break; + event_flag |= Ev_Attach; + SignalSema(sema_id); + break; + case sceEENETCTL_IEV_UpCompleted: + event_flag |= Ev_UpCompleted; + SignalSema(sema_id); + break; + case sceEENETCTL_IEV_DownCompleted: + event_flag |= Ev_DownCompleted; + SignalSema(sema_id); + break; + case sceEENETCTL_IEV_DetachCompleted: + if(strcmp(ifname, EENET_IFNAME)) + break; + event_flag |= Ev_DetachCompleted; + SignalSema(sema_id); + break; + } + + return; +} + +static int create_sema(int init_count, int max_count){ + struct SemaParam sema_param; + int sema_id; + + memset(&sema_param, 0, sizeof(struct SemaParam)); + sema_param.initCount = init_count; + sema_param.maxCount = max_count; + sema_id = CreateSema(&sema_param); + + return sema_id; +} + +// ent_cnf sifrpc num +#define ENT_CNF_SIFRPC_NUM 0x0a31108e + +// code +#define ENT_CNF_SIFRPC_LOAD_CONFIG 1 +#define ENT_CNF_SIFRPC_SET_CONFIG 2 +#define ENT_CNF_SIFRPC_SET_SIFCMD 3 + +// utility macro +#define ee_rpc_size(size) ((size + 15) & ~15) +#define iop_rpc_size(size) ((size + 3) & ~3) + +#define NETBUFSIZE 512 +#define RPCSIZE NETBUFSIZE * 4 + +static sceSifClientData cd; +static u_int rpc_buffer[NETBUFSIZE] __attribute__((aligned(64))); + +int ent_cnf_init(void) +{ + int i; + + /* bind rpc */ + while(1){ + if (sceSifBindRpc(&cd, ENT_CNF_SIFRPC_NUM, 0) < 0) { + scePrintf("ent_cnf: sceSifBindRpc failed.\n"); + while(1) {}; + } + if (cd.serve != 0) break; + i = 0x10000; + while(i --) {}; + } + + return 0; +} + +int ent_cnf_load_config(const char *fname, const char *usr_name) +{ + int ret, len; + + strcpy((char *)rpc_buffer, fname); + len = (int)strlen(fname) + 1; + strcpy((char *)rpc_buffer + len, usr_name); + len += (int)strlen(usr_name) + 1; + + ret = sceSifCallRpc(&cd, ENT_CNF_SIFRPC_LOAD_CONFIG, 0, + (void *)rpc_buffer, ee_rpc_size(len), + (void *)rpc_buffer, ee_rpc_size(sizeof(u_int)), 0, 0); + if(ret < 0){ + scePrintf("ent_cnf: RPC call in ent_cnf_load_config() failed.\n"); + return -1; + } + + return (int)rpc_buffer[0]; +} + +int ent_cnf_set_config(void) +{ + int ret; + + ret = sceSifCallRpc(&cd, ENT_CNF_SIFRPC_SET_CONFIG, 0, + NULL, 0, (void *)rpc_buffer, ee_rpc_size(sizeof(u_int)), 0, 0); + if(ret < 0){ + scePrintf("ent_cnf: RPC call in ent_cnf_set_config() failed.\n"); + return -1; + } + + return (int)rpc_buffer[0]; +} + +static void load_network_modules() +{ + ent_devm_mid = load_module(MOD_ENT_DEVM, 0, NULL); + load_module(MOD_NETCNF, sizeof(NETCNF_ARG), NETCNF_ARG); + eenetctl_mid = load_module(MOD_EENETCTL, 0, NULL); + ent_cnf_mid = load_module(MOD_ENT_CNF, 0, NULL); +#if defined( HDD_ETHERNET ) || defined( HDD_ETHERNET_WITH_PPPOE ) + load_module(MOD_DEV9, 0, NULL); + ent_smap_mid = load_module(MOD_ENT_SMAP, 0, NULL); + //PreparePowerOff(); +#endif +#if defined( USB_ETHERNET ) || defined( USB_ETHERNET_WITH_PPPOE ) + load_module(MOD_USBD, 0, NULL); + ent_eth_mid = load_module(MOD_ENT_ETH, 0, NULL); +#endif +#if defined( MODEM ) + ent_ppp_mid = load_module(MOD_ENT_PPP, 0, NULL); + modem_mid = load_module(MOD_MODEMDRV, sizeof(MODEMDRV_ARG), MODEMDRV_ARG); +#endif +} + +static int do_initialisation(void) +{ + int rcode; + + // create a semaphore + sema_id = create_sema(0, 1); + if(sema_id < 0) + { + scePrintf("create_sema() failed.\n"); + return -1; + } + + // load network modules + load_network_modules(); + + // allocate memory for EENet + //eenet_pool = gsimemalign(64, EENET_MEMSIZE); + eenet_pool = memalign(64, EENET_MEMSIZE); + if(eenet_pool == NULL) + return -1; + + // initialize eenet + rcode = sceEENetInit(eenet_pool, EENET_MEMSIZE, EENET_TPL, 8192, EENET_APP_PRIO); + + + if(rcode < 0) + { + scePrintf("sceEENetInit failed (%d)\n", rcode); + return -1; + } + +#ifdef _DEBUG + sceEENetSetLogLevel(EENET_LOG_DEBUG); +#endif + + // init eenetctl.a + rcode = sceEENetCtlInit(8192, 32, 8192, 32, 8192, 32, 1, 0); + if(rcode < 0) + { + scePrintf("sceEENetCtlInit failed (%d)\n", rcode); + return -1; + } + + // add event handler + rcode = sceEENetCtlRegisterEventHandler(event_handler); + if(rcode < 0) + { + scePrintf("sceEENetCtlRegisterEventHandler failed (%d)\n", rcode); + return -1; + } + + // init ent_cnf + rcode = ent_cnf_init(); + if(rcode < 0) + { + scePrintf("ent_cnf_init failed (%d)\n", rcode); + return -1; + } + + // register network interface driver +#if defined( HDD_ETHERNET ) || defined( HDD_ETHERNET_WITH_PPPOE ) + rcode = sceEENetDeviceSMAPReg(8192, 8192); + if(rcode < 0) + { + scePrintf("sceEENetDeviceSMAPReg failed (%d)\n", rcode); + return -1; + } +#endif +#if defined( USB_ETHERNET ) || defined( USB_ETHERNET_WITH_PPPOE ) + rcode = sceEENetDeviceETHReg(8192, 8192); + if(rcode < 0) + { + scePrintf("sceEENetDeviceETHReg failed (%d)\n", rcode); + return -1; + } +#endif +#if defined( MODEM ) + rcode = sceEENetDevicePPPReg(8192, 8192); + if(rcode < 0) + { + scePrintf("sceEENetDevicePPPReg failed (%d)\n", rcode); + return -1; + } +#endif + + // wait until target interface is attached + WaitEvent(Ev_Attach); + + // load network configuration + rcode = ent_cnf_load_config(NET_DB, USR_CONF_NAME); + if(rcode < 0) + { + scePrintf("ent_cnf_load_config failed (%d)\n", rcode); + return -1; + } + + // set network configuration + rcode = ent_cnf_set_config(); + if(rcode < 0) + { + scePrintf("ent_cnf_set_config failed (%d)\n", rcode); + return -1; + } + + // wait for interface initialization to complete + WaitEvent(Ev_UpCompleted); + + return 0; +} + +static void do_shutdown(void) +{ + int rcode; + + // bring down the interface + rcode = sceEENetCtlDownInterface(EENET_IFNAME); + if(rcode < 0) + { + scePrintf("sceEENetCtlDownInterface failed (%d)\n", rcode); + return; + } + + // wait for the termination to complete + WaitEvent(Ev_DownCompleted); + + // unload the driver module +#if defined( HDD_ETHERNET ) || defined( HDD_ETHERNET_WITH_PPPOE ) + rcode = sceEENetDeviceSMAPUnreg(); + if(rcode < 0) + { + scePrintf("sceEENetDeviceSMAPUnreg failed (%d)\n", rcode); + return; + } +#endif +#if defined( USB_ETHERNET ) || defined( USB_ETHERNET_WITH_PPPOE ) + rcode = sceEENetDeviceETHUnreg(); + if(rcode < 0) + { + scePrintf("sceEENetDeviceETHUnreg failed (%d)\n", rcode); + return; + } +#endif +#if defined( MODEM ) + rcode = sceEENetDevicePPPUnreg(); + if(rcode < 0) + { + scePrintf("sceEENetDevicePPPUnreg failed (%d)\n", rcode); + return; + } +#endif + + // wait for the detach to complete + WaitEvent(Ev_DetachCompleted); + + // cancel the event handler + rcode = sceEENetCtlUnregisterEventHandler(event_handler); + if(rcode < 0) + { + scePrintf("sceEENetCtlUnRegisterEventHandler failed (%d)\n", rcode); + return; + } + + // eenetctl.a termination processing + rcode = sceEENetCtlTerm(); + if(rcode < 0) + { + scePrintf("sceEENetCtlTerm failed (%d)\n", rcode); + return; + } + + // terminate eenet + rcode = sceEENetTerm(); + if(rcode < 0) + { + scePrintf("sceEENetTerm failed (%d)\n", rcode); + return; + } + + // stop and unload modules +#if defined( HDD_ETHERNET ) || defined( HDD_ETHERNET_WITH_PPPOE ) + sceSifStopModule(ent_smap_mid, 0, NULL, &rcode); + sceSifUnloadModule(ent_smap_mid); +#endif +#if defined( USB_ETHERNET ) || defined( USB_ETHERNET_WITH_PPPOE ) + sceSifStopModule(ent_eth_mid, 0, NULL, &rcode); + sceSifUnloadModule(ent_eth_mid); +#endif +#if defined( MODEM ) + sceSifStopModule(modem_mid, 0, NULL, &rcode); + sceSifUnloadModule(modem_mid); + + sceSifStopModule(ent_ppp_mid, 0, NULL, &rcode); + sceSifUnloadModule(ent_ppp_mid); +#endif + + sceSifStopModule(ent_cnf_mid, 0, NULL, &rcode); + sceSifUnloadModule(ent_cnf_mid); + + sceSifStopModule(eenetctl_mid, 0, NULL, &rcode); + sceSifUnloadModule(eenetctl_mid); + + sceSifStopModule(ent_devm_mid, 0, NULL, &rcode); + sceSifUnloadModule(ent_devm_mid); + + // free the EENet pool + if(eenet_pool != NULL) + free(eenet_pool); +} + +#endif + +#ifdef INSOCK + +// These are also used in nonport.c to obtain the MAC address +sceSifMClientData gGSIInsockClientData; +u_int gGSIInsockSocketBuffer[NETBUFSIZE] __attribute__((aligned(64))); + + +// IRX paths +#define IOP_MOD_INET MODROOT "inet.irx" +#define IOP_MOD_AN986 MODROOT "an986.irx" +#define IOP_MOD_USBD MODROOT "usbd.irx" +#define IOP_MOD_NETCNF MODROOT "netcnf.irx" +#define IOP_MOD_INETCTL MODROOT "inetctl.irx" +#define IOP_MOD_MSIFRPC MODROOT "msifrpc.irx" +#define IOP_MOD_DEV9 MODROOT "dev9.irx" +#define IOP_MOD_SMAP MODROOT "smap.irx" +#define IOP_MOD_PPP MODROOT "ppp.irx" +#define IOP_MOD_PPPOE MODROOT "pppoe.irx" +#define IOP_MOD_LIBNET MODROOT "libnet.irx" +#define IOP_MOD_NETCNFIF MODROOT "netcnfif.irx" +#define IOP_MOD_INETLOG SCEROOT "iop/util/inet/inetlog.irx" +#define IOP_MOD_MODEMDRV "" +#define NET_DB SCEROOT "conf/net/net.db" +#define INET_ARG "debug=18" +#define INETCTL_ARG "-no_auto" "\0" "-no_decode" +#define LIBNET_ARG "-verbose" +#define NETCNF_ICON APPROOT "SYS_NET.ICO" +#define NETCNF_ICONSYS APPROOT "icon.sys" +#define NETCNF_ARG "icon=" NETCNF_ICON "\0" "iconsys=" NETCNF_ICONSYS +#define MODEMDRV_ARG "" + +#if defined( USB_ETHERNET ) +#define USR_CONF_NAME "Combination4" +#elif defined( USB_ETHERNET_WITH_PPPOE ) +#define USR_CONF_NAME "Combination5" +#elif defined( HDD_ETHERNET ) +#define USR_CONF_NAME "Combination6" +#elif defined( HDD_ETHERNET_WITH_PPPOE ) +#define USR_CONF_NAME "Combination7" +#elif defined( MODEM ) +#define USR_CONF_NAME "" +#endif + +static int do_initialisation(void) +{ + int result; + int i; + int if_id[sceLIBNET_MAX_INTERFACE]; + sceInetAddress_t myaddr; + + sceSifInitRpc( 0 ); + + load_module( IOP_MOD_INET, 0, NULL ); + load_module( IOP_MOD_NETCNF, sizeof( NETCNF_ARG ), NETCNF_ARG ); + load_module( IOP_MOD_INETCTL, sizeof( INETCTL_ARG ), INETCTL_ARG ); + +#if defined( USB_ETHERNET ) || defined( USB_ETHERNET_WITH_PPPOE ) + load_module( IOP_MOD_USBD, 0, NULL ); + load_module( IOP_MOD_AN986, 0, NULL ); +#endif + +#if defined( HDD_ETHERNET ) || defined( HDD_ETHERNET_WITH_PPPOE ) + load_module( IOP_MOD_DEV9, 0, NULL ); + load_module( IOP_MOD_SMAP, 0, NULL ); +#endif + +#if defined( USB_ETHERNET_WITH_PPPOE ) || defined( HDD_ETHERNET_WITH_PPPOE ) + load_module( IOP_MOD_PPP, 0, NULL ); + load_module( IOP_MOD_PPPOE, 0, NULL ); +#endif + +#if defined( MODEM ) + load_module( IOP_MOD_PPP, 0, NULL ); + load_module( IOP_MOD_USBD, 0, NULL ); + load_module( IOP_MOD_MODEMDRV, sizeof( MODEMDRV_ARG ), MODEMDRV_ARG ); +#endif + + load_module( IOP_MOD_MSIFRPC, 0, NULL ); + load_module( IOP_MOD_LIBNET, sizeof( LIBNET_ARG ), LIBNET_ARG ); + load_module( IOP_MOD_NETCNFIF, 0, NULL ); + +#if defined( HDD_ETHERNET ) || defined( HDD_ETHERNET_WITH_PPPOE ) +// PreparePowerOff(); +#endif + + // Initialize Libnet + sceSifMInitRpc(0); + + result = sceInsockSetSifMBindRpcValue(NETBUFSIZE, sceLIBNET_STACKSIZE, sceLIBNET_PRIORITY); + if (result < 0) + return result; + + result = sceLibnetInitialize(&gGSIInsockClientData, NETBUFSIZE, sceLIBNET_STACKSIZE, sceLIBNET_PRIORITY); + if (result < 0) + return result; + + result = sceLibnetRegisterHandler(&gGSIInsockClientData, gGSIInsockSocketBuffer); + if (result < 0) + return result; + + result = load_set_conf_extra(&gGSIInsockClientData, gGSIInsockSocketBuffer, NET_DB, USR_CONF_NAME, sceLIBNETF_AUTO_UPIF); + if (result < 0) + return result; + + result = sceLibnetWaitGetAddress( &gGSIInsockClientData, gGSIInsockSocketBuffer, if_id, sceLIBNET_MAX_INTERFACE, &myaddr, sceLIBNETF_AUTO_UPIF ); + if (result < 0) + return result; + + for ( i = 0; i < sceLIBNET_MAX_INTERFACE; i++ ) { + if ( if_id[ i ] == 0 ) { + break; + } + scePrintf( "interface: %d\n", if_id[ i ] ); + } + + return 0; +} + +static void do_shutdown(void) +{ + // Release the network interface + down_interface(&gGSIInsockClientData, gGSIInsockSocketBuffer, 0); + + // Shutdown libnet + libnet_term(&gGSIInsockClientData); + + sceSifMExitRpc(); +} + +#endif // INSOCK + +#ifdef GSI_VOICE +void load_voice_modules(void); // prototype so codewarrior will be happy +void load_voice_modules(void) +{ + // this is the maximum size of a raw frame, in bytes + char lgaudArgs[] = "maxstream=512\n"; + + // load the usb module + load_module("host0:/usr/local/sce/iop/modules/usbd.irx", 0, NULL); + + // load the lgAud module + // see the lgAud documentation for info on optional loading parameters + load_module("host0:/usr/local/sce/iop/modules/lgaud.irx", sizeof(lgaudArgs), lgaudArgs); + + // load the lgVid module + // see the lgVid documentation for info on optional loading parameters + load_module("host0:/usr/local/sce/iop/modules/lgvid.irx", 0, NULL); + + // load the SPU2 modules + load_module("host0:/usr/local/sce/iop/modules/libsd.irx", 0, NULL); + load_module("host0:/usr/local/sce/iop/modules/sdrdrv.irx", 0, NULL); +} +#endif + +// New hooks required by crt0.s +#if !defined(__MWERKS__) +int _init(){ return 0; } +int _fini(){ return 0; } + + #if (__GNUC__ >= 3) + int __main(int argc, char ** argp){ GSI_UNUSED(argp); GSI_UNUSED(argc); return 0; } + #endif +#endif + +extern int test_main(int argc, char ** argp); +int main(int argc, char ** argp) +{ + int result = 0; + + GSI_UNUSED(argc); + GSI_UNUSED(argp); + + printf("\nGameSpy Test App Initializing\n" + "----------------------------------\n"); + + // init RPC + sceSifInitRpc(0); + + // initialize the stack + result = (int)do_initialisation(); + if(result) + { + printf("Initialization failed\n"); + return result; + } + +#ifdef GSI_VOICE + load_voice_modules(); +#endif + + // start the actual program + printf("\nGameSpy Test App Starting\n" + "----------------------------------\n"); + test_main(argc, argp); + + // do any needed cleanup + do_shutdown(); + printf("\nGameSpy Test App Exiting\n" + "----------------------------------\n"); + + return 0; +} diff --git a/code/gamespy/common/ps2/ps2pad.c b/code/gamespy/common/ps2/ps2pad.c new file mode 100644 index 00000000..0b8ebe24 --- /dev/null +++ b/code/gamespy/common/ps2/ps2pad.c @@ -0,0 +1,214 @@ +#include "ps2pad.h" +#include +#include +#include + +#define BUTTON_LEFT(d) ((d[2] & (1 << (3+4)))==0) +#define BUTTON_DOWN(d) ((d[2] & (1 << (2+4)))==0) +#define BUTTON_RIGHT(d) ((d[2] & (1 << (1+4)))==0) +#define BUTTON_UP(d) ((d[2] & (1 << (0+4)))==0) + +#define BUTTON_START(d) ((d[2] & (1 << 3))==0) +#define BUTTON_RSTICK(d) ((d[2] & (1 << 2))==0) +#define BUTTON_LSTICK(d) ((d[2] & (1 << 1))==0) +#define BUTTON_SELECT(d) ((d[2] & (1 << 0))==0) + +#define BUTTON_SQUARE(d) ((d[3] & (1 << (3+4)))==0) +#define BUTTON_X(d) ((d[3] & (1 << (2+4)))==0) +#define BUTTON_CIRCLE(d) ((d[3] & (1 << (1+4)))==0) +#define BUTTON_TRI(d) ((d[3] & (1 << (0+4)))==0) + +#define BUTTON_R1(d) ((d[3] & (1 << 3))==0) +#define BUTTON_L1(d) ((d[3] & (1 << 2))==0) +#define BUTTON_R2(d) ((d[3] & (1 << 1))==0) +#define BUTTON_L2(d) ((d[3] & (1 << 0))==0) + +static int term_id = 0; + +static u_long128 mPadDMABuf[scePadDmaBufferMax] __attribute__((aligned (64))); +static int mPadState; +static int mPadPhase; + +static unsigned char mActDirect[6]; +static unsigned char mActAlign[6]; + +static unsigned short PadReadData(int events[NumPadEvents]) +{ + static unsigned char rdata_prev[32]; + unsigned char rdata[32]; + unsigned short tpad = 0; + unsigned short paddata = 0; + static unsigned short rpad = 0; + + if (scePadRead(0,0,rdata) == 0) + return 0; + + if (rdata[0] == 0) + { + paddata = (unsigned short)(0xffff ^ ((rdata[2]<<8)|rdata[3])); + + tpad = (unsigned short)(paddata & ~rpad); + rpad = paddata; + + term_id = (rdata[1]>>4); + + events[PadLeft] = (BUTTON_LEFT(rdata) && !BUTTON_LEFT(rdata_prev)); + events[PadDown] = (BUTTON_DOWN(rdata) && !BUTTON_DOWN(rdata_prev)); + events[PadRight] = (BUTTON_RIGHT(rdata) && !BUTTON_RIGHT(rdata_prev)); + events[PadUp] = (BUTTON_UP(rdata) && !BUTTON_UP(rdata_prev)); + events[PadStart] = (BUTTON_START(rdata) && !BUTTON_START(rdata_prev)); + events[PadRightStick] = (BUTTON_RSTICK(rdata) && !BUTTON_RSTICK(rdata_prev)); + events[PadLeftStick] = (BUTTON_LSTICK(rdata) && !BUTTON_LSTICK(rdata_prev)); + events[PadSelect] = (BUTTON_SELECT(rdata) && !BUTTON_SELECT(rdata_prev)); + events[PadSquare] = (BUTTON_SQUARE(rdata) && !BUTTON_SQUARE(rdata_prev)); + events[PadX] = (BUTTON_X(rdata) && !BUTTON_X(rdata_prev)); + events[PadCircle] = (BUTTON_CIRCLE(rdata) && !BUTTON_CIRCLE(rdata_prev)); + events[PadTriangle] = (BUTTON_TRI(rdata) && !BUTTON_TRI(rdata_prev)); + events[PadR1] = (BUTTON_R1(rdata) && !BUTTON_R1(rdata_prev)); + events[PadL1] = (BUTTON_L1(rdata) && !BUTTON_L1(rdata_prev)); + events[PadR2] = (BUTTON_R2(rdata) && !BUTTON_R2(rdata_prev)); + events[PadL2] = (BUTTON_L2(rdata) && !BUTTON_L2(rdata_prev)); + } + + // store current data + memcpy(rdata_prev, rdata, 32); + + return (tpad); +} + +#define ROOT_DIR "host0:/usr/local/sce/iop/modules/" +int PadInit(void) +{ + int i; + + mPadPhase = 0; + mPadState = 0; + + for (i=0; i <6; i++) + { + mActDirect[i] = 0; + mActAlign[i] = 0; + } + + // Load serial io module + if (0 >= sceSifLoadModule(ROOT_DIR "sio2man.irx", 0, NULL)) + return 0; + + // Load control pad module + if (0 >= sceSifLoadModule(ROOT_DIR "padman.irx", 0, NULL)) + return 0; + + // Misc init + sceDmaReset(1); // reset DMA + sceGsResetPath(); // reset GS + sceGsSyncPath(0, 0); // wait for completion + + // Open the pad port + scePadInit(0); + if (0 == scePadPortOpen(0, 0, mPadDMABuf)) + return 0; // couldn't initialize pad + + return 1; +} + +void PadReadInput(int events[NumPadEvents]) +{ + static int id = 0; + int exid; + int i; + + for(i = 0 ; i < NumPadEvents ; i++) + events[i] = 0; + + // check buttons or some junk + mPadState = scePadGetState(0, 0); + //if (mPadState >= 0 && state <= 7) + // scePadStateIntToStr(state, buf); // get error string + if (mPadState == scePadStateDiscon) + mPadPhase = 0; // lost pad + + switch(mPadPhase) + { + case 0: + { + // Wait until stable or need to find ctp1 + if (mPadState != scePadStateStable && mPadState != scePadStateFindCTP1) + break; + + // Get controller ID + id = scePadInfoMode(0, 0, InfoModeCurID, 0); + if (id == 0) + break; + + // Is there an extended ID? + exid = scePadInfoMode(0,0, InfoModeCurExID, 0); + if (exid > 0) + id = exid; + + if (id == 40) + // Special processing for "standard" controller + mPadPhase = 40; + else if (id == 7) + // Special processing for "analog" controller + mPadPhase = 70; + else + // Skip to end of setup + mPadPhase = 99; + break; + } + + // 1st step special processing for "standard" controller + case 40: + if (scePadInfoMode(0, 0, InfoModeCurExID, 0)==0) + { + // Skip to end if this was set from an extended ID + mPadPhase = 99; + break; + } + + // Set main mode + if (scePadSetMainMode(0, 0, 1, 0)==1) + mPadPhase++; + break; + + // 2nd step special processing for "standard" controller + case 41: + if (scePadGetReqState(0,0) == scePadReqStateFaild) + mPadPhase--; // failed, go back a phase + if (scePadGetReqState(0,0) == scePadReqStateComplete) + mPadPhase = 0; // completed, go back to beginning to try again + break; + + // 1st step special processing for "analog" controller + case 70: + if (scePadInfoAct(0, 0, -1, 0)==0) + mPadPhase = 99; // done + mActAlign[0] = 0; + mActAlign[1] = 1; + for (i=2; i<6; i++) + mActAlign[i] = 0xff; + if (scePadSetActAlign(0, 0, mActAlign)) + mPadPhase++; + break; + + // 2nd step special processing for "analog" controller + case 71: + if (scePadGetReqState(0,0) == scePadReqStateFaild) + mPadPhase--; + if (scePadGetReqState(0,0) == scePadReqStateComplete) + mPadPhase = 99; // finished, jump to end + + case 99: + default: + if (mPadState == scePadStateStable || mPadState == scePadStateFindCTP1) + { + // read button states + //unsigned short pad = ReadPadData(); + PadReadData(events); + + if (term_id != id) + mPadPhase = 0; + } + break; + }; +} diff --git a/code/gamespy/common/ps2/ps2pad.h b/code/gamespy/common/ps2/ps2pad.h new file mode 100644 index 00000000..4346039d --- /dev/null +++ b/code/gamespy/common/ps2/ps2pad.h @@ -0,0 +1,30 @@ +#ifndef __PS2PAD_H__ +#define __PS2PAD_H__ + +#include "../../../nonport.h" + +typedef enum +{ + PadLeft, + PadDown, + PadRight, + PadUp, + PadStart, + PadRightStick, + PadLeftStick, + PadSelect, + PadSquare, + PadX, + PadCircle, + PadTriangle, + PadR1, + PadL1, + PadR2, + PadL2, + NumPadEvents +} PadEvents; + +int PadInit(void); +void PadReadInput(int events[NumPadEvents]); + +#endif diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/CellConfiguration.h b/code/gamespy/common/ps3/SpeexSpursTaskManager/CellConfiguration.h new file mode 100644 index 00000000..411c9da0 --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/CellConfiguration.h @@ -0,0 +1,18 @@ +/* [SCE CONFIDENTIAL DOCUMENT] + * PLAYSTATION(R)3 SPU Optimized Bullet Physics Library (http://bulletphysics.com) + * Copyright (C) 2007 Sony Computer Entertainment Inc. + * All Rights Reserved. + */ + +#ifndef __CELL_CONFIGURATION_H +#define __CELL_CONFIGURATION_H + +#undef SCE_CONTROL_CONSOLE + +#ifdef WIN32 +#define EXPORT_SYM __declspec( dllexport ) +#else +#define EXPORT_SYM +#endif + +#endif diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/CellVectorMath.h b/code/gamespy/common/ps3/SpeexSpursTaskManager/CellVectorMath.h new file mode 100644 index 00000000..01e2645f --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/CellVectorMath.h @@ -0,0 +1,25 @@ +#ifndef __CELL_VECTORMATH_H__ +#define __CELL_VECTORMATH_H__ + +#ifndef __CELLOS_LV2__ +class vec_float4 +{ + float x, y, z, w; +} +#ifdef __GNUC__ +__attribute__ ((aligned (16))); +#else +__declspec(align(16)); +#endif // __GNUC__ +#endif // __CELLOS_LV2__ + +#ifdef __CELLOS_LV2__ +#include "vectormath_soa.h" +#else +#include "vectormath_scalar/vectormath_aos.h" +#endif // __CELLOS_LV2__ + +using namespace Vectormath; +using namespace Vectormath::Aos; + +#endif /* __CELL_VECTORMATH_H__ */ diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/PS3Types.h b/code/gamespy/common/ps3/SpeexSpursTaskManager/PS3Types.h new file mode 100644 index 00000000..d11567cf --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/PS3Types.h @@ -0,0 +1,7 @@ +#ifndef PS3_TYPES_H +#define PS3_TYPES_H + +//#include "CellVectorMath.h" + + +#endif //PS3_TYPES_H diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/SPUAssert.h b/code/gamespy/common/ps3/SpeexSpursTaskManager/SPUAssert.h new file mode 100644 index 00000000..fb58827e --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/SPUAssert.h @@ -0,0 +1,44 @@ +#ifndef __SPU_ASSERT_H__ +#define __SPU_ASSERT_H__ + +// Author: Sauce +// 1/18/2006 +// Better assert on SPU side, but it assumes spu_printf works. + +#ifdef _DEBUG + +#ifdef __CELLOS_LV2__ +#include +#define SPU_ASSERT(cond) do { if (__builtin_expect(!(cond), 0)) { spu_printf("SPU: Assertion failed! Expression: " #cond "\n in %s at " __FILE__ ":%i\n", __FUNCTION__, __LINE__); spu_hcmpeq((cond), 0); } } while (0) +#else // __CELLOS_LV2__ +#define SPU_ASSERT(cond) assert(cond) +#endif //__CELLOS_LV2__ + +#else // _DEBUG + +#ifdef __CELLOS_LV2__ +#include +#define SPU_ASSERT(cond) do { if (__builtin_expect(!(cond), 0)) { spu_printf("SPU: Assertion failed! Expression: " #cond "\n in %s at " __FILE__ ":%i\n", __FUNCTION__, __LINE__); spu_hcmpeq((cond), 0); } } while (0) +// Sauce +// Later on we'll want no asserts in release builds +//#define SPU_ASSERT(cond) do {} while (0) +#else // __CELLOS_LV2__ +#define SPU_ASSERT(cond) assert(cond) +#endif // __CELLOS_LV2__ + +#endif // _DEBUG + +// Usage: +// SPU_COMPILE_TIME_ASSERT(sizeof(MyStructure) <= 128); +// Gives the following error message if it fails: +// error: size of array `spu_compile_time_assert_failed' is negative +#define SPU_COMPILE_TIME_ASSERT(cond) extern char spu_compile_time_assert_failed[cond ? 1 : -1] + +// Usage: +// SPU_NAMED_COMPILE_TIME_ASSERT(MyStructure_is_more_than_128_bytes, sizeof(MyStructure) <= 128); +// Gives the following error message if it fails: +// error: size of array `MyStructure_is_more_than_128_bytes' is negative +#define SPU_NAMED_COMPILE_TIME_ASSERT(name, cond) extern char name[cond ? 1 : -1] + + +#endif diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/SpeexSpursTask/SpuSpeexTaskMain.cpp b/code/gamespy/common/ps3/SpeexSpursTaskManager/SpeexSpursTask/SpuSpeexTaskMain.cpp new file mode 100644 index 00000000..b301ef29 --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/SpeexSpursTask/SpuSpeexTaskMain.cpp @@ -0,0 +1,515 @@ +// Copyright 2007 GameSpy Industries, Inc +// +// Speex SPURS Task +// Encode supported +// Decode supported +// + +#define __STDC_CONSTANT_MACROS + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "SPUAssert.h" + +#include "spursSupportInterface.h" +#include "SpursSpeexTaskManager.h" +#include "SpuFakeDma.h" +#include "SpursSpeexCInterface.h" + +#include + +#include +#include "LibSN_SPU.h" + +#define GVRate_8KHz 8000 +#define GVRate_16KHz 16000 + +// use this for data read in from each task request +SpursSpeexTaskDesc gviSpursSpeexTaskDesc; + +// internal data used by speex functions +SpeexBits gviSpursSpeexBits; + +char gviSpursSpeexStateBuffer[SPEEX_ENCODER_STATE_BUFFER_SIZE] POST_ALIGN(128); +char gviSpursSpeexBitsBuffer[MAX_BYTES_PER_FRAME] POST_ALIGN(16); + +void spuDebugPrintf(const char *fmt, ...) +{ + #ifdef SPU_VERBOSE_DEBUGGING + char debugOut[256]; + va_list args; + va_start(args, fmt); + vsprintf(debugOut, fmt, args); + va_end(args); + spu_printf(debugOut); + #endif +} + +void gviSpursSpeexEncoderInitialize(SpursSpeexTaskOutput *spuOutput) +{ + void *gviSpeexEncoderState; + int sampleRate = gviSpursSpeexTaskDesc.mSamplesPerSecond; + int quality = gviSpursSpeexTaskDesc.mQuality; + int rate; + int bitsPerFrame; + + //spuDebugPrintf("[Speex][SPU] sample rate: %d, quality: %d\n", sampleRate, quality); + + // create a new encoder state + if (sampleRate == GVRate_8KHz) + gviSpeexEncoderState = speex_encoder_init(&speex_nb_mode, gviSpursSpeexStateBuffer); + else if (sampleRate == GVRate_16KHz) + gviSpeexEncoderState = speex_encoder_init(&speex_wb_mode, gviSpursSpeexStateBuffer); + else + { + //spuDebugPrintf("[Speex][SPU] Initializing Speex failed\n"); + spuOutput->mSpeexReturnCode = -2; + return; + } + + if(!gviSpeexEncoderState) + { + //spuDebugPrintf("[Speex][SPU] Initializing Speex failed\n"); + spuOutput->mSpeexReturnCode = -3; + return; + } + + //spuDebugPrintf("[Speex][SPU] Done getting speex mode\n"); + //spuDebugPrintf("[Speex][SPU] encoder state addr: 0x%8x\n", gviSpeexEncoderState); + + // set the sampling rate + speex_encoder_ctl(gviSpeexEncoderState, SPEEX_SET_SAMPLING_RATE, &sampleRate); + + // Get the samples per frame setting. + speex_encoder_ctl(gviSpeexEncoderState, SPEEX_GET_FRAME_SIZE, &spuOutput->mSpeexSamplesPerFrame); + + // set the quality + speex_encoder_ctl(gviSpeexEncoderState, SPEEX_SET_QUALITY, &quality); + + // (re)initialize the bits struct + speex_bits_init_buffer(&gviSpursSpeexBits,gviSpursSpeexBitsBuffer,sizeof(gviSpursSpeexBitsBuffer)); + + //spuDebugPrintf("[Speex][SPU] speex bits addr: 0x%8x\n", gviSpeexBits); + + // get the bit rate + speex_encoder_ctl(gviSpeexEncoderState, SPEEX_GET_BITRATE, &rate); + + //spuDebugPrintf("[Speex][SPU] Done with customizing options for speex\n"); + + // convert to bits per frame + bitsPerFrame = (rate / (sampleRate / spuOutput->mSpeexSamplesPerFrame)); + + // convert to bytes per frame and store, round up to allocate more space than needed. + spuOutput->mSpeexEncodedFrameSize = (bitsPerFrame / 8); + if (bitsPerFrame % 8) + spuOutput->mSpeexEncodedFrameSize++; + + // we're now initialized + spuOutput->mSpeexInitialized = 1; + + //spuDebugPrintf("[Speex][SPU] Done with initing speex\n"); + + spuOutput->mSpeexReturnCode = 0; +} + +void gviSpursSpeexDecoderInitialize(SpursSpeexTaskOutput *spuTaskOut) +{ + void * decoder = gviSpursSpeexStateBuffer; + int perceptualEnhancement = 1; + + // create a new decoder state + if (gviSpursSpeexTaskDesc.mSamplesPerSecond == GVRate_8KHz) + speex_decoder_init(&speex_nb_mode, decoder); + else if (gviSpursSpeexTaskDesc.mSamplesPerSecond == GVRate_16KHz) + speex_decoder_init(&speex_wb_mode, decoder); + else + { + //spuDebugPrintf("[Speex][SPU] Error: invalid sample rate!\n"); + spuTaskOut->mSpeexReturnCode = -1; + } + + if(!decoder) + { + //spuDebugPrintf("[Speex][SPU] Error: initializing decoder failed!\n"); + spuTaskOut->mSpeexReturnCode = -2; + } + + // turn on the perceptual enhancement + speex_decoder_ctl(decoder, SPEEX_SET_ENH, &perceptualEnhancement); + + spuTaskOut->mSpeexReturnCode = 0; +} + +void gviSpursSpeexEncode(SpursSpeexTaskOutput *spuTaskOut) +{ + short *inBuffer; + float *speexBuffer; + char *outBuffer; + unsigned int i; + spuTaskOut->mSpeexEncodedFrameSize = 0; + spuTaskOut->mSpeexInitialized = 1; + spuTaskOut->mSpeexSamplesPerFrame = 0; + spuTaskOut->mSpeexReturnCode = 0; + spuTaskOut->mSpeexOutBufferSize = 0; + + speexBuffer = (float *)memalign(16, gviSpursSpeexTaskDesc.mInputBufferSize * sizeof(float)); + inBuffer = (short *)memalign(16, gviSpursSpeexTaskDesc.mInputBufferSize * sizeof(short)); + outBuffer = (char *)memalign(16, gviSpursSpeexTaskDesc.mOutputBufferSize); + + memset(speexBuffer, 0, gviSpursSpeexTaskDesc.mInputBufferSize * sizeof(float)); + memset(inBuffer, 0, gviSpursSpeexTaskDesc.mInputBufferSize * sizeof(short)); + memset(outBuffer, 0, gviSpursSpeexTaskDesc.mOutputBufferSize); + + cellDmaGet(inBuffer, (uint64_t)gviSpursSpeexTaskDesc.mInputBuffer, gviSpursSpeexTaskDesc.mInputBufferSize * sizeof(short), DMA_TAG(1), 0,0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + // convert the input to floats for encoding + for(i = 0 ; i < gviSpursSpeexTaskDesc.mInputBufferSize ; i++) + speexBuffer[i] = inBuffer[i]; + + // (re)initialize the bits struct + speex_bits_init_buffer(&gviSpursSpeexBits,gviSpursSpeexBitsBuffer,sizeof(gviSpursSpeexBitsBuffer)); + + // flush the bits + speex_bits_reset(&gviSpursSpeexBits); + + // encode the frame + speex_encode(gviSpursSpeexStateBuffer, speexBuffer, &gviSpursSpeexBits); + // write the bits to the output + spuTaskOut->mSpeexOutBufferSize = speex_bits_write(&gviSpursSpeexBits, (char *)outBuffer, gviSpursSpeexTaskDesc.mEncodedFrameSize); + //spuDebugPrintf("[Speex][SPU] transferring data back, output size should be: %d\n", gviSpursSpeexTaskDesc.mOutputBufferSize>16?gviSpursSpeexTaskDesc.mOutputBufferSize:16); + cellDmaPut(outBuffer, (uint64_t)gviSpursSpeexTaskDesc.mOutputBuffer, gviSpursSpeexTaskDesc.mOutputBufferSize, DMA_TAG(1), 0, 0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + //spuDebugPrintf("[Speex][SPU] done transferring data back\n"); + free(speexBuffer); + free(inBuffer); + free(outBuffer); + spuTaskOut->mSpeexReturnCode = 0; +} + +void gviSpursSpeexDecodeAdd(SpursSpeexTaskOutput *spuTaskOut) +{ + char *inBuffer; + float *speexBuffer; + short *outBuffer; + int rcode; + unsigned int i; + + //spuDebugPrintf("[Speex][SPU] allocating buffers for decoding\n"); + speexBuffer = (float *)memalign(16, gviSpursSpeexTaskDesc.mOutputBufferSize * sizeof(float)); + outBuffer = (short *)memalign(16, gviSpursSpeexTaskDesc.mOutputBufferSize * sizeof(short)); + inBuffer = (char *)memalign(16, gviSpursSpeexTaskDesc.mInputBufferSize); + + memset(speexBuffer, 0, gviSpursSpeexTaskDesc.mOutputBufferSize * sizeof(float)); + memset(outBuffer, 0, gviSpursSpeexTaskDesc.mOutputBufferSize); + memset(inBuffer, 0, gviSpursSpeexTaskDesc.mInputBufferSize * sizeof(short)); + + + //spuDebugPrintf("[Speex][SPU] done allocating, getting input data, inbuffer size: %d\n", gSpuSampleTaskDesc.mInputBufferSize); + cellDmaGet(inBuffer, (uint64_t)gviSpursSpeexTaskDesc.mInputBuffer, gviSpursSpeexTaskDesc.mInputBufferSize, DMA_TAG(1), 0,0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + // spuDebugPrintf("[Speex][SPU] done getting input data, preparing for speex to decode\n"); + // read the data into the bits + // (re)initialize the bits struct + speex_bits_init_buffer(&gviSpursSpeexBits,gviSpursSpeexBitsBuffer,sizeof(gviSpursSpeexBitsBuffer)); + + speex_bits_read_from(&gviSpursSpeexBits, (char *)inBuffer, gviSpursSpeexTaskDesc.mEncodedFrameSize); + + // decode it + rcode = speex_decode((void *)gviSpursSpeexStateBuffer, &gviSpursSpeexBits, speexBuffer); + assert(rcode == 0); + //spuDebugPrintf("[Speex][SPU] done with speex decode\n"); + // convert the output from floats + for(i = 0 ; i < gviSpursSpeexTaskDesc.mOutputBufferSize ; i++) + outBuffer[i] = (short)speexBuffer[i]; + + //spuDebugPrintf("[Speex][SPU] transferring data back\n"); + cellDmaPut(outBuffer, (uint64_t)gviSpursSpeexTaskDesc.mOutputBuffer, gviSpursSpeexTaskDesc.mOutputBufferSize * sizeof(short), DMA_TAG(1), 0, 0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + //spuDebugPrintf("[Speex][SPU] done transferring data back\n"); + free(speexBuffer); + free(inBuffer); + free(outBuffer); + spuTaskOut->mSpeexReturnCode = 0; +} + +void gviSpursSpeexDecodeSet(SpursSpeexTaskOutput *spuTaskOut) +{ + char *inBuffer; + float *speexBuffer; + short *outBuffer; + int rcode; + unsigned int i; + + speexBuffer = (float *)memalign(16, gviSpursSpeexTaskDesc.mOutputBufferSize * sizeof(float)); + outBuffer = (short *)memalign(16, gviSpursSpeexTaskDesc.mOutputBufferSize * sizeof(short)); + inBuffer = (char *)memalign(16, gviSpursSpeexTaskDesc.mInputBufferSize); + + memset(speexBuffer, 0, gviSpursSpeexTaskDesc.mOutputBufferSize * sizeof(float)); + memset(inBuffer, 0, gviSpursSpeexTaskDesc.mOutputBufferSize * sizeof(short)); + memset(outBuffer, 0, gviSpursSpeexTaskDesc.mInputBufferSize); + + cellDmaGet(inBuffer, (uint64_t)gviSpursSpeexTaskDesc.mInputBuffer, gviSpursSpeexTaskDesc.mInputBufferSize, DMA_TAG(1), 0,0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + // read the data into the bits + speex_bits_read_from(&gviSpursSpeexBits, (char *)inBuffer, gviSpursSpeexTaskDesc.mEncodedFrameSize); + + // decode it + rcode = speex_decode((void *)gviSpursSpeexStateBuffer, &gviSpursSpeexBits, speexBuffer); + assert(rcode == 0); + + // convert the output from floats + for(i = 0 ; i < gviSpursSpeexTaskDesc.mOutputBufferSize ; i++) + // Expanded to remove warnings in VS2K5 + outBuffer[i] = (short)speexBuffer[i]; + + cellDmaPut(outBuffer, (uint64_t)gviSpursSpeexTaskDesc.mOutputBuffer, gviSpursSpeexTaskDesc.mOutputBufferSize * sizeof(short), DMA_TAG(1), 0, 0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + free(speexBuffer); + free(inBuffer); + free(outBuffer); + spuTaskOut->mSpeexReturnCode = 0; +} + +void procesEncodeInit(unsigned int uiPtr) +{ + SpursSpeexTaskOutput spuOutput; + + //spuDebugPrintf("[Speex][SPU] CMD_SAMPLE_TASK_ENCODE_INIT_COMMAND\n"); + cellDmaGet(&gviSpursSpeexTaskDesc, uiPtr, sizeof(SpursSpeexTaskDesc), DMA_TAG(1), 0, 0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + if (gviSpursSpeexTaskDesc.mDebugPause) + { + snPause(); + } + + gviSpursSpeexEncoderInitialize(&spuOutput); + if (spuOutput.mSpeexReturnCode < 0) + { + spuDebugPrintf("[Speex][SPU] failed to initialize encoder, ret = %d\n", spuOutput.mSpeexReturnCode); + } + + //spuDebugPrintf("[Speex][SPU] done with initializing things for speex, now returning data via DMA put\n"); + + //printGlobalTaskDescData(); + + cellDmaPut(&spuOutput, (uint64_t)gviSpursSpeexTaskDesc.mSpeexTaskOutput, sizeof(SpursSpeexTaskOutput), DMA_TAG(1), + 0, 0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + //spuDebugPrintf("[Speex][SPU] task dma done\n"); + + cellDmaLargePut(gviSpursSpeexStateBuffer, (uint64_t)gviSpursSpeexTaskDesc.mSpeexStateBuffer, SPEEX_ENCODER_STATE_BUFFER_SIZE, DMA_TAG(1), 0,0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + //spuDebugPrintf("[Speex][SPU] buffer dma done\n"); +} + +void processDecodeInit(unsigned int uiPtr) +{ + SpursSpeexTaskOutput spuOutput; + cellDmaGet(&gviSpursSpeexTaskDesc, uiPtr, sizeof(SpursSpeexTaskDesc), DMA_TAG(1), 0, 0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + //spuDebugPrintf("[Speex][SPU] CMD_SAMPLE_TASK_DECODE_INIT_COMMAND\n"); + + if (gviSpursSpeexTaskDesc.mDebugPause) + { + snPause(); + } + + gviSpursSpeexDecoderInitialize(&spuOutput); + + if (spuOutput.mSpeexReturnCode < 0) + { + spuDebugPrintf("[Speex][SPU] failed to initialize decoder, ret = %d\n", spuOutput.mSpeexReturnCode); + } + + cellDmaPut(&spuOutput, (uint64_t)gviSpursSpeexTaskDesc.mSpeexTaskOutput, sizeof(SpursSpeexTaskOutput), DMA_TAG(1), + 0, 0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + cellDmaLargePut(gviSpursSpeexStateBuffer, (uint64_t)gviSpursSpeexTaskDesc.mSpeexStateBuffer, + gviSpursSpeexTaskDesc.mSpeexStateBufferSize, DMA_TAG(1), 0,0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + //spuDebugPrintf("[Speex][SPU] buffer dma done\n"); +} + +void processEncode(unsigned int uiPtr) +{ + SpursSpeexTaskOutput spuOutput; + cellDmaGet(&gviSpursSpeexTaskDesc, uiPtr, sizeof(SpursSpeexTaskDesc), DMA_TAG(1), 0, 0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + spuDebugPrintf("[Speex][SPU] CMD_SAMPLE_TASK_ENCODE_COMMAND\n"); + + if (gviSpursSpeexTaskDesc.mDebugPause) + { + snPause(); + } + cellDmaLargeGet(gviSpursSpeexStateBuffer, (uint64_t)gviSpursSpeexTaskDesc.mSpeexStateBuffer, SPEEX_ENCODER_STATE_BUFFER_SIZE, DMA_TAG(1), 0,0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + gviSpursSpeexEncode(&spuOutput); + + if (spuOutput.mSpeexReturnCode < 0) + { + spuDebugPrintf("SPU: failed to encode, ret = %d\n", spuOutput.mSpeexReturnCode); + } + + cellDmaPut(&spuOutput, (uint64_t)gviSpursSpeexTaskDesc.mSpeexTaskOutput, sizeof(SpursSpeexTaskOutput), DMA_TAG(1), + 0, 0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + cellDmaLargePut(gviSpursSpeexStateBuffer, (uint64_t)gviSpursSpeexTaskDesc.mSpeexStateBuffer, SPEEX_ENCODER_STATE_BUFFER_SIZE, DMA_TAG(1), 0,0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + spuDebugPrintf("[Speex][SPU] buffer dma done\n"); +} + + +void processDecodeAdd(unsigned int uiPtr) +{ + SpursSpeexTaskOutput spuOutput; + cellDmaGet(&gviSpursSpeexTaskDesc, uiPtr, sizeof(SpursSpeexTaskDesc), DMA_TAG(1), 0, 0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + //spuDebugPrintf("[Speex][SPU] CMD_SAMPLE_TASK_DECODEADD_COMMAND\n"); + + if (gviSpursSpeexTaskDesc.mDebugPause) + { + snPause(); + } + + cellDmaLargeGet(gviSpursSpeexStateBuffer, (uint64_t)gviSpursSpeexTaskDesc.mSpeexStateBuffer, gviSpursSpeexTaskDesc.mSpeexStateBufferSize, DMA_TAG(1), 0,0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + gviSpursSpeexDecodeAdd(&spuOutput); + + if (spuOutput.mSpeexReturnCode < 0) + { + spuDebugPrintf("SPU: failed to decode, ret = %d\n", spuOutput.mSpeexReturnCode); + } + + cellDmaPut(&spuOutput, (uint64_t)gviSpursSpeexTaskDesc.mSpeexTaskOutput, sizeof(SpursSpeexTaskOutput), DMA_TAG(1), + 0, 0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + cellDmaLargePut(gviSpursSpeexStateBuffer, (uint64_t)gviSpursSpeexTaskDesc.mSpeexStateBuffer, SPEEX_DECODER_STATE_BUFFER_SIZE, DMA_TAG(1), 0,0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + //spuDebugPrintf("[Speex][SPU] done sending back state buffer\n"); +} + +void processDecodeSet(unsigned int uiPtr) +{ + SpursSpeexTaskOutput spuOutput; + cellDmaGet(&gviSpursSpeexTaskDesc, uiPtr, sizeof(SpursSpeexTaskDesc), DMA_TAG(1), 0, 0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + //spuDebugPrintf("[Speex][SPU] CMD_SAMPLE_TASK_DECODESET_COMMAND\n"); + + if (gviSpursSpeexTaskDesc.mDebugPause) + { + snPause(); + } + cellDmaLargeGet(gviSpursSpeexStateBuffer, (uint64_t)gviSpursSpeexTaskDesc.mSpeexStateBuffer, SPEEX_DECODER_STATE_BUFFER_SIZE, DMA_TAG(1), 0,0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + gviSpursSpeexDecodeSet(&spuOutput); + + if (spuOutput.mSpeexReturnCode < 0) + { + spuDebugPrintf("SPU: failed to encode, ret = %d\n", spuOutput.mSpeexReturnCode); + } + + cellDmaPut(&spuOutput, (uint64_t)gviSpursSpeexTaskDesc.mSpeexTaskOutput, sizeof(SpursSpeexTaskOutput), DMA_TAG(1), + 0, 0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + cellDmaLargePut(gviSpursSpeexStateBuffer, (uint64_t)gviSpursSpeexTaskDesc.mSpeexStateBuffer, SPEEX_DECODER_STATE_BUFFER_SIZE, DMA_TAG(1), 0,0); + cellDmaWaitTagStatusAll(DMA_MASK(1)); + + //spuDebugPrintf("[Speex][SPU] buffer dma done\n"); +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +void cellSpursMain(qword argTask, uint64_t argTaskset) +{ + // grab the arguments to extract the command + CellSPURSArgument args={uiQWord : (vec_uint4) argTask}; + + unsigned int uiCommand, uiArg1=0, uiArg2=0; + //int spuId = cellSpursGetCurrentSpuId(); + //int taskId = cellSpursGetTaskId(); + + //spuDebugPrintf("[Speex][SPU] taskid: %d, spuId: %d\n", taskId, spuId); + + // grab the command and arguments to be processed below + uiCommand=args.uiCommand; + uiArg1=args.uiArgument0; + uiArg2=args.uiArgument1; + + uint64_t uiPtr = uiArg1; + + switch(uiCommand) + { + + case SPEEX_TASK_ENCODE_INIT_COMMAND: + { + // cleaner this way + procesEncodeInit(uiPtr); + + sendResponseToPPUAndExit(args.ppuResponseQueue, (uint32_t)uiArg2, 0); + break; + } + + case SPEEX_TASK_ENCODE_COMMAND: + { + processEncode(uiPtr); + sendResponseToPPUAndExit(args.ppuResponseQueue, (uint32_t)uiArg2, 0); + break; + } + + case SPEEX_TASK_DECODE_INIT_COMMAND: + { + processDecodeInit(uiPtr); + sendResponseToPPUAndExit(args.ppuResponseQueue, (uint32_t)uiArg2, 0); + break; + } + case SPEEX_TASK_DECODEADD_COMMAND: + { + processDecodeAdd(uiPtr); + sendResponseToPPUAndExit(args.ppuResponseQueue, (uint32_t)uiArg2, 0); + break; + } + + case SPEEX_TASK_DECODESET_COMMAND: + { + processDecodeSet(uiPtr); + sendResponseToPPUAndExit(args.ppuResponseQueue, (uint32_t)uiArg2, 0); + break; + } + + default: + { + //spuDebugPrintf("SPURS Sample:unknown case in switch uiCommand: %x uiArg1 %x uiArg2 %x\n",uiCommand,uiArg1,uiArg2); + } + + } +} + + diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/SpuDoubleBuffer.h b/code/gamespy/common/ps3/SpeexSpursTaskManager/SpuDoubleBuffer.h new file mode 100644 index 00000000..2a333e23 --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/SpuDoubleBuffer.h @@ -0,0 +1,106 @@ +#ifndef DOUBLE_BUFFER_H +#define DOUBLE_BUFFER_H + +#include "SpuFakeDma.h" + + +///DoubleBuffer +template +class DoubleBuffer +{ +#ifdef __CELLOS_LV2__ + T m_buffer0[size] __attribute__ ((aligned (128))); + T m_buffer1[size] __attribute__ ((aligned (128))); +#else + T m_buffer0[size]; + T m_buffer1[size]; +#endif + + T *m_frontBuffer; + T *m_backBuffer; + + unsigned int m_dmaTag; + bool m_dmaPending; +public: + bool isPending() const { return m_dmaPending;} + DoubleBuffer(); + + void init (); + + // dma get and put commands + void backBufferDmaGet(uint64_t ea, unsigned int numBytes, unsigned int tag); + void backBufferDmaPut(uint64_t ea, unsigned int numBytes, unsigned int tag); + + // gets pointer to a buffer + T *getFront(); + T *getBack(); + + // if back buffer dma was started, wait for it to complete + // then move back to front and vice versa + T *swapBuffers(); +}; + +template +DoubleBuffer::DoubleBuffer() +{ + init (); +} + +template +void DoubleBuffer::init() +{ + this->m_dmaPending = false; + this->m_frontBuffer = &this->m_buffer0[0]; + this->m_backBuffer = &this->m_buffer1[0]; +} + +template +void +DoubleBuffer::backBufferDmaGet(uint64_t ea, unsigned int numBytes, unsigned int tag) +{ + m_dmaPending = true; + m_dmaTag = tag; + cellDmaLargeGet(m_backBuffer, ea, numBytes, tag, 0, 0); +} + +template +void +DoubleBuffer::backBufferDmaPut(uint64_t ea, unsigned int numBytes, unsigned int tag) +{ + m_dmaPending = true; + m_dmaTag = tag; + cellDmaLargePut(m_backBuffer, ea, numBytes, tag, 0, 0); +} + +template +T * +DoubleBuffer::getFront() +{ + return m_frontBuffer; +} + +template +T * +DoubleBuffer::getBack() +{ + return m_backBuffer; +} + +template +T * +DoubleBuffer::swapBuffers() +{ + if (m_dmaPending) + { + cellDmaWaitTagStatusAll(1< +#include + +#define DMA_TAG(xfer) (xfer + 1) +#define DMA_MASK(xfer) (1 << DMA_TAG(xfer)) + +#elif defined (WIN32) + +#define DMA_TAG(a) (a) +#define DMA_MASK(a) (a) + + +/// cellDmaLargeGet Win32 replacements for Cell DMA to allow simulating most of the SPU code (just memcpy) +int cellDmaLargeGet(void *ls, uint64_t ea, uint32_t size, uint32_t tag, uint32_t tid, uint32_t rid); +int cellDmaGet(void *ls, uint64_t ea, uint32_t size, uint32_t tag, uint32_t tid, uint32_t rid); +/// cellDmaLargePut Win32 replacements for Cell DMA to allow simulating most of the SPU code (just memcpy) +int cellDmaLargePut(const void *ls, uint64_t ea, uint32_t size, uint32_t tag, uint32_t tid, uint32_t rid); +/// cellDmaWaitTagStatusAll Win32 replacements for Cell DMA to allow simulating most of the SPU code (just memcpy) +void cellDmaWaitTagStatusAll(int ignore); +#endif //WIN32 + +///stallingUnalignedDmaSmallGet internally uses DMA_TAG(1) +int stallingUnalignedDmaSmallGet(void *ls, uint64_t ea, uint32_t size); + +#endif //FAKE_DMA_H diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/SpuSpeexTaskOutput.h b/code/gamespy/common/ps3/SpeexSpursTaskManager/SpuSpeexTaskOutput.h new file mode 100644 index 00000000..9c0d0a99 --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/SpuSpeexTaskOutput.h @@ -0,0 +1,33 @@ + + +#ifndef __SPEEX_TASK_OUTPUT_H +#define __SPEEX_TASK_OUTPUT_H + +#define POST_ALIGN(x) __attribute__((aligned (x))) + +#ifdef __cplusplus +extern "C" { +#endif + +#define SPEEX_ENCODER_WB_BUFFERSIZE 32256 +#define SPEEX_ENCODER_NB_BUFFERSIZE 32288 +#define SPEEX_ENCODER_STATE_BUFFER_SIZE (SPEEX_ENCODER_NB_BUFFERSIZE + SPEEX_ENCODER_WB_BUFFERSIZE+128) +#define SPEEX_DECODER_NB_BUFFERSIZE 16832 +#define SPEEX_DEOCDER_WB_BUFFERSIZE 24192 +#define SPEEX_DECODER_STATE_BUFFER_SIZE (SPEEX_DECODER_NB_BUFFERSIZE+SPEEX_DEOCDER_WB_BUFFERSIZE+128) +//#define SPEEX_STATE_ + ///pure output, any input is in SpuSampleTaskDesc +struct SpursSpeexTaskOutput +{ + int mSpeexInitialized; + int mSpeexSamplesPerFrame; + int mSpeexEncodedFrameSize; + int mSpeexOutBufferSize; + int mSpeexReturnCode; +} POST_ALIGN(128); + +#ifdef __cplusplus +} +#endif + +#endif //__SPEEX_TASK_OUTPUT_H diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/SpursSpeexCInterface.cpp b/code/gamespy/common/ps3/SpeexSpursTaskManager/SpursSpeexCInterface.cpp new file mode 100644 index 00000000..47d64db8 --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/SpursSpeexCInterface.cpp @@ -0,0 +1,102 @@ +#include "spursConfiguration.h" +#include "SpursSpeexCInterface.h" +#include +#include "SpursSpeexTaskManager.h" +#include "spursSupportInterface.h" +#include + +SpursSpeexTaskManager* gSpursSpeexTaskManager = 0; +SpursSupportInterface* gSpursSupport = 0; +const unsigned int MAX_SPURS_SPEEX_TASKS=1; + + +///initialize SPURS +int initializeSpursSampleTask() +{ + gSpursSupport = new SpursSupportInterface(); + + gSpursSpeexTaskManager = new SpursSpeexTaskManager(gSpursSupport,MAX_SPURS_SPEEX_TASKS); + return gSpursSpeexTaskManager->initialize(); +} + +///not finished, need to pass proper data +int issueSampleTaskEncodeInit(int quality, int samplesPerFrame, SpursSpeexTaskOutput *taskOutput, char *userAllocatedSpeexBuffer, int userAllocatedSpeexBufferSize) +{ + btAssert(gSpursSpeexTaskManager!=0); + btAssert(gSpursSupport!=0); + + return gSpursSpeexTaskManager->issueEncodeInitTask(quality, samplesPerFrame, taskOutput,userAllocatedSpeexBuffer,userAllocatedSpeexBufferSize); + + //printf("issueSampleTaskEncodeInit called\n"); +} + +///submit some work to SPURS +int issueSampleTaskEncode(short* inBuffer, int inBufferSize, int encodedFrameSize, char *outBuffer, int outBufferSize, + struct SpursSpeexTaskOutput *taskOuput, char *userAllocatedSpeexBuffer, int userAllocatedSpeexBufferSize ) +{ + btAssert(gSpursSpeexTaskManager!=0); + btAssert(gSpursSupport!=0); + + return gSpursSpeexTaskManager->issueEncodeTask(inBuffer, inBufferSize, encodedFrameSize, outBuffer, outBufferSize, taskOuput, + userAllocatedSpeexBuffer,userAllocatedSpeexBufferSize); + + //printf("issueSampleTaskEncode called\n"); +} + +int issueSampleTaskDecodeAdd(char *decoderStateBuffer, int decoderStateBufferSize, char *inBuffer, int inBufferSize, int encodedFrameSize, + short* outBuffer, int outBufferSize, struct SpursSpeexTaskOutput *taskOutput) +{ + btAssert(gSpursSpeexTaskManager!=0); + btAssert(gSpursSupport!=0); + + return gSpursSpeexTaskManager->issueDecodeAddTask(decoderStateBuffer, decoderStateBufferSize, inBuffer, inBufferSize, encodedFrameSize, + outBuffer, outBufferSize, taskOutput); + + //printf("issueSampleTaskDecode called\n"); +} + +int issueSampleTaskDecodeSet(char *decoderStateBuffer, int decoderStateBufferSize, char *inBuffer, int inBufferSize, int encodedFrameSize, + short* outBuffer, int outBufferSize, struct SpursSpeexTaskOutput *taskOutput) +{ + btAssert(gSpursSpeexTaskManager!=0); + btAssert(gSpursSupport!=0); + + return gSpursSpeexTaskManager->issueDecodeSetTask(decoderStateBuffer, decoderStateBufferSize, inBuffer, inBufferSize, encodedFrameSize, + outBuffer, outBufferSize, taskOutput); + + //printf("issueSampleTaskDecode called\n"); +} + +int issueSampleTaskDecodeInit(char *decoderStateBuffer, int decoderStateBufferSize, int sampleRate, struct SpursSpeexTaskOutput *taskOutput) +{ + btAssert(gSpursSpeexTaskManager!=0); + btAssert(gSpursSupport!=0); + + return gSpursSpeexTaskManager->issueDecodeInitTask(decoderStateBuffer, decoderStateBufferSize, sampleRate, taskOutput); + + //printf("issueSampleTaskDecode called\n"); +} + + +///wait for the work to be finished +/* +int flushSampleTask() +{ + btAssert(gSpursSpeexTaskManager!=0); + btAssert(gSpursSupport!=0); + + //printf("flushSampleTask called\n"); +} +*/ + +///shutdown SPURS +int shutdownSpursTask() +{ + btAssert(gSpursSpeexTaskManager!=0); + btAssert(gSpursSupport!=0); + delete gSpursSpeexTaskManager; + delete gSpursSupport; + if (spursConfiguration_terminate() != 0) + return -1; + return 0; +} diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/SpursSpeexCInterface.h b/code/gamespy/common/ps3/SpeexSpursTaskManager/SpursSpeexCInterface.h new file mode 100644 index 00000000..3785165c --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/SpursSpeexCInterface.h @@ -0,0 +1,54 @@ +#ifndef SPU_TASK_C_INTERFACE_H +#define SPU_TASK_C_INTERFACE_H + +//#include +#include "SpuSpeexTaskOutput.h" + +#define PL_DECLARE_HANDLE(name) typedef struct name##__ { int unused; } *name + +typedef float plReal; +typedef plReal plVector3[3]; +typedef plReal plQuaternion[4]; + +#ifdef __cplusplus +extern "C" { +#endif + +#define GVI_REMAINING_BYTES 3 + + +///initialize SPURS +int initializeSpursSampleTask(); + +///submit some work to SPURS +int issueSampleTaskEncode(short* inBuffer, int inBufferSize, int encodedFrameSize, char *outBuffer, int outBufferSize, + struct SpursSpeexTaskOutput *taskOuput, char *userAllocatedSpeexBuffer, + int userAllocatedSpeexBufferSize); + +///not finished, need to pass proper data +int issueSampleTaskEncodeInit(int quality, int samplesPerFrame, struct SpursSpeexTaskOutput *taskOutput, + char *userAllocatedSpeexBuffer, int userAllocatedSpeexBufferSize); +///not finished, need to pass proper data +int issueSampleTaskDecodeAdd(char *decoderStateBuffer, int decoderStateBufferSize, char *inBuffer, int inBufferSize, int encodedFrameSize, + short* outBuffer, int outBufferSize, struct SpursSpeexTaskOutput *taskOutput); +int issueSampleTaskDecodeSet(char *decoderStateBuffer, int decoderStateBufferSize, char *inBuffer, int inBufferSize, int encodedFrameSize, + short* outBuffer, int outBufferSize, struct SpursSpeexTaskOutput *taskOutput); +///not finished, need to pass proper data +int issueSampleTaskDecodeInit(char *decoderStateBuffer, int decoderStateBufferSize, int sampleRate, struct SpursSpeexTaskOutput *taskOutput); + + + + +///wait for the work to be finished +//int flushSampleTask(); + +///shutdown SPURS +int shutdownSpursTask(); + +///used to pass into SPURS + +#ifdef __cplusplus +} +#endif + +#endif //SPU_TASK_C_INTERFACE_H diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/SpursSpeexTaskManager.cpp b/code/gamespy/common/ps3/SpeexSpursTaskManager/SpursSpeexTaskManager.cpp new file mode 100644 index 00000000..1923db73 --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/SpursSpeexTaskManager.cpp @@ -0,0 +1,273 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2007 Erwin Coumans http://bulletphysics.com + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +//#define __CELLOS_LV2__ 1 + +#define USE_SAMPLE_PROCESS 1 +#ifdef USE_SAMPLE_PROCESS + + +#include "spursThreadSupportInterface.h" + +//#include "SPUAssert.h" +#include + + +#include "SpursSpeexTaskManager.h" + + +#include + + +void SampleThreadFunc(void* userPtr,void* lsMemory) +{ + //do nothing + printf("hello world\n"); +} + +void* SamplelsMemoryFunc() +{ + //don't create local store memory, just return 0 + return 0; +} + + + +extern "C" +{ + extern char SPU_SAMPLE_ELF_SYMBOL[]; +}; + + + + + +SpursSpeexTaskManager::SpursSpeexTaskManager(spursThreadSupportInterface* threadInterface, unsigned int maxNumOutstandingTasks) +:m_threadInterface(threadInterface), +m_maxNumOutstandingTasks(maxNumOutstandingTasks) +{ + + m_taskBusy.resize(m_maxNumOutstandingTasks); + mSpursSpeexTaskDesc.resize(m_maxNumOutstandingTasks); + + for (int i = 0; (unsigned int)i < m_maxNumOutstandingTasks; i++) + { + m_taskBusy[i] = false; + } + m_numBusyTasks = 0; + m_currentTask = 0; + + m_initialized = false; +} + +SpursSpeexTaskManager::~SpursSpeexTaskManager() +{ + m_threadInterface->stopSPU(); +} + + + +int SpursSpeexTaskManager::initialize() +{ +#ifdef DEBUG_SPU_TASK_SCHEDULING + printf("SpuSampleTaskProcess::initialize()\n"); +#endif //DEBUG_SPU_TASK_SCHEDULING + + for (int i = 0; (unsigned int)i < m_maxNumOutstandingTasks; i++) + { + m_taskBusy[i] = false; + } + m_numBusyTasks = 0; + m_currentTask = 0; + m_initialized = true; + + if (m_threadInterface->startSPU() != 0) + { + return -1; + } + return 0; +} + + + +int SpursSpeexTaskManager::issueEncodeInitTask( int theQuality,int theGviSpeexSamplesPerSecond, SpursSpeexTaskOutput *taskOutput, char *userAllocatedSpeexBuffer,int userAllocatedSpeexBufferSize ) +{ + m_taskBusy[m_currentTask] = true; + m_numBusyTasks++; + + SpursSpeexTaskDesc& taskDesc = mSpursSpeexTaskDesc[m_currentTask]; + taskDesc.mSpeexStateBuffer = userAllocatedSpeexBuffer; + taskDesc.mSpeexStateBufferSize = userAllocatedSpeexBufferSize; + + taskDesc.mQuality = theQuality; + taskDesc.mSamplesPerSecond = theGviSpeexSamplesPerSecond; + taskDesc.mSpeexTaskOutput = taskOutput; + if (issueTask(taskDesc,SPEEX_TASK_ENCODE_INIT_COMMAND) != 0) + return -1; + return 0; +} + +int SpursSpeexTaskManager::issueEncodeTask(int16_t * inBuffer, int inBufferSize, int encodedFrameSize, char *outBuffer, + int outBufferSize, SpursSpeexTaskOutput *taskOuput,char *userAllocatedSpeexBuffer, + int userAllocatedSpeexBufferSize ) +{ + m_taskBusy[m_currentTask] = true; + m_numBusyTasks++; + + SpursSpeexTaskDesc& taskDesc = mSpursSpeexTaskDesc[m_currentTask]; + taskDesc.mSpeexStateBuffer = userAllocatedSpeexBuffer; + taskDesc.mSpeexStateBufferSize = userAllocatedSpeexBufferSize; + taskDesc.mEncodedFrameSize = encodedFrameSize; + taskDesc.mInputBuffer = inBuffer; + taskDesc.mInputBufferSize = inBufferSize; + taskDesc.mOutputBuffer = outBuffer; + taskDesc.mOutputBufferSize = outBufferSize; + taskDesc.mSpeexTaskOutput = taskOuput; + if (issueTask(taskDesc,SPEEX_TASK_ENCODE_COMMAND) != 0) + return -1; + return 0; +} + +int SpursSpeexTaskManager::issueDecodeAddTask(char *decoderStateBuffer, int decoderStateBufferSize, char *inBuffer, int inBufferSize, int encodedFrameSize, + short* outBuffer, int outBufferSize, struct SpursSpeexTaskOutput *taskOutput) +{ + m_taskBusy[m_currentTask] = true; + m_numBusyTasks++; + + SpursSpeexTaskDesc& taskDesc = mSpursSpeexTaskDesc[m_currentTask]; + taskDesc.mSpeexStateBuffer = decoderStateBuffer; + taskDesc.mSpeexStateBufferSize = decoderStateBufferSize; + taskDesc.mEncodedFrameSize = encodedFrameSize; + taskDesc.mInputBuffer = inBuffer; + taskDesc.mInputBufferSize = inBufferSize; + taskDesc.mOutputBuffer = outBuffer; + taskDesc.mOutputBufferSize = outBufferSize; + taskDesc.mSpeexTaskOutput = taskOutput; + if (issueTask(taskDesc,SPEEX_TASK_DECODEADD_COMMAND) != 0) + return -1; + return 0; +} + +int SpursSpeexTaskManager::issueDecodeSetTask(char *decoderStateBuffer, int decoderStateBufferSize, char *inBuffer, int inBufferSize, int encodedFrameSize, + short* outBuffer, int outBufferSize, struct SpursSpeexTaskOutput *taskOutput ) +{ + m_taskBusy[m_currentTask] = true; + m_numBusyTasks++; + + SpursSpeexTaskDesc& taskDesc = mSpursSpeexTaskDesc[m_currentTask]; + taskDesc.mSpeexStateBuffer = decoderStateBuffer; + taskDesc.mSpeexStateBufferSize = decoderStateBufferSize; + taskDesc.mEncodedFrameSize = encodedFrameSize; + taskDesc.mInputBuffer = inBuffer; + taskDesc.mInputBufferSize = inBufferSize; + taskDesc.mOutputBuffer = outBuffer; + taskDesc.mOutputBufferSize = outBufferSize; + taskDesc.mSpeexTaskOutput = taskOutput; + if (issueTask(taskDesc,SPEEX_TASK_DECODESET_COMMAND) != 0) + return -1; + return 0; +} + +int SpursSpeexTaskManager::issueDecodeInitTask( char *decoderStateBuffer, int decoderStateBufferSize, int sampleRate, struct SpursSpeexTaskOutput *taskOutput ) +{ + m_taskBusy[m_currentTask] = true; + m_numBusyTasks++; + + SpursSpeexTaskDesc& taskDesc = mSpursSpeexTaskDesc[m_currentTask]; + taskDesc.mSpeexStateBuffer = decoderStateBuffer; + taskDesc.mSpeexStateBufferSize = decoderStateBufferSize; + taskDesc.mSamplesPerSecond = sampleRate; + taskDesc.mSpeexTaskOutput = taskOutput; + if (issueTask(taskDesc,SPEEX_TASK_DECODE_INIT_COMMAND) != 0) + return -1; + return 0; +} + + +int SpursSpeexTaskManager::issueTask( SpursSpeexTaskDesc& taskDesc,uint32_t uiCommand ) +{ +#ifdef DEBUG_SPU_TASK_SCHEDULING + printf("SpuSampleTaskProcess::issueTask (m_currentTask= %d\)n", m_currentTask); +#endif //DEBUG_SPU_TASK_SCHEDULING + + //some bookkeeping to recognize finished tasks + taskDesc.mTaskId = m_currentTask; + + if (m_threadInterface->sendRequest(uiCommand, (uint32_t) &taskDesc, m_currentTask) != 0) + { + m_taskBusy[m_currentTask] = false; + m_numBusyTasks--; + return -1; + } + + // if all tasks busy, wait for spu event to clear the task. + if (m_numBusyTasks >= m_maxNumOutstandingTasks) + { + unsigned int taskId; + unsigned int outputSize; + + if (m_threadInterface->waitForResponse(&taskId, &outputSize) != 0) + { + return -2; + } + + //printf("PPU: after issue, received event: %u %d\n", taskId, outputSize); + //postProcess(taskId, outputSize); + m_taskBusy[taskId] = false; + m_numBusyTasks--; + } + + // find new task buffer + for (unsigned int i = 0; i < m_maxNumOutstandingTasks; i++) + { + if (!m_taskBusy[i]) + { + m_currentTask = i; + break; + } + } + return 0; +} + +///Optional PPU-size post processing for each task +// void SpuSampleTaskProcess::postProcess(int taskId, int outputSize) +// { +// +// } + + +int SpursSpeexTaskManager::flush() +{ +#ifdef DEBUG_SPU_TASK_SCHEDULING + printf("\nSpuCollisionTaskProcess::flush()\n"); +#endif //DEBUG_SPU_TASK_SCHEDULING + + // all tasks are issued, wait for all tasks to be complete + while(m_numBusyTasks > 0) + { + // Consolidating SPU code + unsigned int taskId; + unsigned int outputSize; + + if (m_threadInterface->waitForResponse(&taskId, &outputSize) != 0) + return -1; + //printf("PPU: flushing, received event: %u %d\n", taskId, outputSize); + //postProcess(taskId, outputSize); + m_taskBusy[taskId] = false; + m_numBusyTasks--; + } + return 0; +} +#endif //USE_SAMPLE_PROCESS diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/SpursSpeexTaskManager.h b/code/gamespy/common/ps3/SpeexSpursTaskManager/SpursSpeexTaskManager.h new file mode 100644 index 00000000..887a5167 --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/SpursSpeexTaskManager.h @@ -0,0 +1,122 @@ +// Gamespy Technology +// NOTE: this code has been provided by Sony for usage in Speex SPURS Manager + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2007 Erwin Coumans http://bulletphysics.com + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SPU_SAMPLE_TASK_PROCESS_H +#define SPU_SAMPLE_TASK_PROCESS_H + +#include + + +#include "spursPlatformDefinitions.h" + +#include + +#include "spursAlignedObjectArray.h" + +#include "SpuSpeexTaskOutput.h" + +///SpuSampleTaskDesc +struct SpursSpeexTaskDesc +{ + SpursSpeexTaskDesc() + :mDebugPause(false) + { + + } + int mQuality; + int mSamplesPerSecond; + int mEncodedFrameSize; + void *mInputBuffer; // make it + unsigned int mInputBufferSize; + + SpursSpeexTaskOutput *mSpeexTaskOutput; + void *mOutputBuffer; + unsigned int mOutputBufferSize; + + char *mSpeexStateBuffer; + unsigned int mSpeexStateBufferSize; + bool mDebugPause; + + uint16_t mTaskId; + //uint16_t _padding_[3]; //padding to make this multiple of 16 bytes +} POST_ALIGN(128); + +//just add your commands here, try to keep them globally unique for debugging purposes +#define SPEEX_TASK_ENCODE_COMMAND 10 +#define SPEEX_TASK_ENCODE_INIT_COMMAND 11 +#define SPEEX_TASK_DECODEADD_COMMAND 12 +#define SPEEX_TASK_DECODESET_COMMAND 13 +#define SPEEX_TASK_DECODE_INIT_COMMAND 14 + + + +/// SpuSampleTaskProcess handles SPU processing of collision pairs. +/// When PPU issues a task, it will look for completed task buffers +/// PPU will do postprocessing, dependent on workunit output (not likely) +class SpursSpeexTaskManager +{ + // track task buffers that are being used, and total busy tasks + spursAlignedObjectArray m_taskBusy; + spursAlignedObjectArraymSpursSpeexTaskDesc; + + unsigned int m_numBusyTasks; + + // the current task and the current entry to insert a new work unit + unsigned int m_currentTask; + + bool m_initialized; + + //void postProcess(int taskId, int outputSize); + + class spursThreadSupportInterface* m_threadInterface; + + unsigned int m_maxNumOutstandingTasks; + + + int issueTask(SpursSpeexTaskDesc& taskDesc,uint32_t uiCommand); + + +public: + SpursSpeexTaskManager(spursThreadSupportInterface* threadInterface, unsigned int maxNumOutstandingTasks); + + ~SpursSpeexTaskManager(); + + ///call initialize in the beginning of the frame, before addCollisionPairToTask + int initialize(); + + int issueEncodeTask(int16_t * inBuffer, int inBufferSize, int encodedFrameSize, char *outBuffer, int outBufferSize, + SpursSpeexTaskOutput *taskOuput,char *m_userAllocatedSpeexBuffer,int userAllocatedSpeexBufferSize); + + int issueEncodeInitTask(int theQuality,int theGviSpeexSamplesPerSecond, SpursSpeexTaskOutput *taskOutput, char *m_userAllocatedSpeexBuffer, + int userAllocatedSpeexBufferSize); + + int issueDecodeAddTask(char *decoderStateBuffer, int decoderStateBufferSize, char *inBuffer, int inBufferSize, int encodedFrameSize, + short* outBuffer, int outBufferSize, struct SpursSpeexTaskOutput *taskOutput); + + int issueDecodeSetTask(char *decoderStateBuffer, int decoderStateBufferSize, char *inBuffer, int inBufferSize, int encodedFrameSize, + short* outBuffer, int outBufferSize, struct SpursSpeexTaskOutput *taskOutput); + + int issueDecodeInitTask(char *decoderStateBuffer, int decoderStateBufferSize, int sampleRate, struct SpursSpeexTaskOutput *taskOutput); + + ///call flush to submit potential outstanding work to SPUs and wait for all involved SPUs to be finished + int flush(); +}; + + +#endif // SPU_SAMPLE_TASK_PROCESS_H + diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/spursAlignedAllocator.cpp b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursAlignedAllocator.cpp new file mode 100644 index 00000000..20221a87 --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursAlignedAllocator.cpp @@ -0,0 +1,38 @@ +// Gamespy Technology +// NOTE: this code has been provided by Sony for usage in Speex SPURS Manager + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "spursAlignedAllocator.h" + +#include + +int numAllocs = 0; +int numFree = 0; + +void* spursAlignedAlloc (int size, int alignment) +{ + numAllocs++; + return memalign(alignment, size); +} + +void spursAlignedFree (void* ptr) +{ + numFree++; + free(ptr); +} + + diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/spursAlignedAllocator.h b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursAlignedAllocator.h new file mode 100644 index 00000000..97f1c7fa --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursAlignedAllocator.h @@ -0,0 +1,81 @@ +// Gamespy Technology +// NOTE: this code has been provided by Sony for usage in Speex SPURS Manager + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef BT_ALIGNED_ALLOCATOR +#define BT_ALIGNED_ALLOCATOR + +///we probably replace this with our own aligned memory allocator +///so we replace _aligned_malloc and _aligned_free with our own +///that is better portable and more predictable + +void* spursAlignedAlloc (int size, int alignment); + +void spursAlignedFree (void* ptr); + + +typedef int size_type; + + +template < typename T , unsigned Alignment > +class spursAlignedAllocator { + + typedef spursAlignedAllocator< T , Alignment > self_type; + +public: + + //just going down a list: + spursAlignedAllocator() {} + /* + btAlignedAllocator( const self_type & ) {} + */ + + template < typename Other > + spursAlignedAllocator( const spursAlignedAllocator< Other , Alignment > & ) {} + + typedef const T* const_pointer; + typedef const T& const_reference; + typedef T* pointer; + typedef T& reference; + typedef T value_type; + + pointer address ( reference ref ) const { return &ref; } + const_pointer address ( const_reference ref ) const { return &ref; } + pointer allocate ( size_type n , const_pointer * hint = 0 ) { + (void)hint; + return reinterpret_cast< pointer >(spursAlignedAlloc( sizeof(value_type) * n , Alignment )); + } + void construct ( pointer ptr , const value_type & value ) { new (ptr) value_type( value ); } + void deallocate( pointer ptr ) { + spursAlignedFree( reinterpret_cast< void * >( ptr ) ); + } + void destroy ( pointer ptr ) { ptr->~value_type(); } + + + template < typename O > struct rebind { + typedef spursAlignedAllocator< O , Alignment > other; + }; + template < typename O > + self_type & operator=( const spursAlignedAllocator< O , Alignment > & ) { return *this; } + + friend bool operator==( const self_type & , const self_type & ) { return true; } +}; + + + +#endif //BT_ALIGNED_ALLOCATOR + diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/spursAlignedObjectArray.h b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursAlignedObjectArray.h new file mode 100644 index 00000000..af13d3b5 --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursAlignedObjectArray.h @@ -0,0 +1,370 @@ +// Gamespy Technology +// NOTE: this code has been provided by Sony for usage in Speex SPURS Manager + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + + +#ifndef BT_OBJECT_ARRAY__ +#define BT_OBJECT_ARRAY__ + +#include "spursScalar.h" // has definitions like SIMD_FORCE_INLINE +#include "spursAlignedAllocator.h" + +///If the platform doesn't support placement new, you can disable BT_USE_PLACEMENT_NEW +///then the btAlignedObjectArray doesn't support objects with virtual methods, and non-trivial constructors/destructors +///You can enable BT_USE_MEMCPY, then swapping elements in the array will use memcpy instead of operator= +///see discussion here: http://continuousphysics.com/Bullet/phpBB2/viewtopic.php?t=1231 and +///http://www.continuousphysics.com/Bullet/phpBB2/viewtopic.php?t=1240 + +#define BT_USE_PLACEMENT_NEW 1 +//#define BT_USE_MEMCPY 1 //disable, because it is cumbersome to find out for each platform where memcpy is defined. It can be in or or otherwise... + +#ifdef BT_USE_MEMCPY +#include +#include +#endif //BT_USE_MEMCPY + +#ifdef BT_USE_PLACEMENT_NEW +#include //for placement new +#endif //BT_USE_PLACEMENT_NEW + + +///btAlignedObjectArray uses a subset of the stl::vector interface for its methods +///It is developed to replace stl::vector to avoid STL alignment issues to add SIMD/SSE data +template +//template +class spursAlignedObjectArray +{ + spursAlignedAllocator m_allocator; + + int m_size; + int m_capacity; + T* m_data; + + protected: + SIMD_FORCE_INLINE int allocSize(int size) + { + return (size ? size*2 : 1); + } + SIMD_FORCE_INLINE void copy(int start,int end, T* dest) + { + int i; + for (i=start;i size()) + { + reserve(newsize); + } +#ifdef BT_USE_PLACEMENT_NEW + for (int i=curSize;i + void downHeap(T *pArr, int k, int n,L CompareFunc) + { + /* PRE: a[k+1..N] is a heap */ + /* POST: a[k..N] is a heap */ + + T temp = pArr[k - 1]; + /* k has child(s) */ + while (k <= n/2) + { + int child = 2*k; + + if ((child < n) && CompareFunc(pArr[child - 1] , pArr[child])) + { + child++; + } + /* pick larger child */ + if (CompareFunc(temp , pArr[child - 1])) + { + /* move child up */ + pArr[k - 1] = pArr[child - 1]; + k = child; + } + else + { + break; + } + } + pArr[k - 1] = temp; + } /*downHeap*/ + + void swap(int index0,int index1) + { +#ifdef BT_USE_MEMCPY + char temp[sizeof(T)]; + memcpy(temp,&m_data[index0],sizeof(T)); + memcpy(&m_data[index0],&m_data[index1],sizeof(T)); + memcpy(&m_data[index1],temp,sizeof(T)); +#else + T temp = m_data[index0]; + m_data[index0] = m_data[index1]; + m_data[index1] = temp; +#endif //BT_USE_PLACEMENT_NEW + + } + + template + void heapSort(L CompareFunc) + { + /* sort a[0..N-1], N.B. 0 to N-1 */ + int k; + int n = m_size; + for (k = n/2; k > 0; k--) + { + downHeap(m_data, k, n, CompareFunc); + } + + /* a[1..N] is now a heap */ + while ( n>=1 ) + { + swap(0,n-1); /* largest of a[0..n-1] */ + + + n = n - 1; + /* restore a[1..i-1] heap */ + downHeap(m_data, 1, n, CompareFunc); + } + } + + ///non-recursive binary search, assumes sorted array + int findBinarySearch(const T& key) const + { + int first = 0; + int last = size(); + + //assume sorted array + while (first <= last) { + int mid = (first + last) / 2; // compute mid point. + if (key > m_data[mid]) + first = mid + 1; // repeat search in top half. + else if (key < m_data[mid]) + last = mid - 1; // repeat search in bottom half. + else + return mid; // found it. return position ///// + } + return size(); // failed to find key + } + + + int findLinearSearch(const T& key) const + { + int index=size(); + int i; + + for (i=0;i + +#ifdef __cplusplus +extern "C" { +#endif + +#define CELL_SPURS_DEFAULT_SPU_COUNT 1 + +// "-Wl,--whole-archive -lprof_stub -Wl,--no-whole-archive" + +enum CellSpursReturn { + CELL_SPURS_OK=0, + CELL_SPURS_EBUSY, + CELL_SPURS_EINVAL, + CELL_SPURS_EMISC +}; + +/** + * \brief This class controls the SPU usage of SPURS + * + * There are three ways to initialize SPU usage. + * + * The first way is to initialize SPURS yourself, and to pass in a pointer to + * SPURS as well as priorities for the use of the SPUs, using initWithSpurs. + * This is good if you intend to use SPURS elsewhere in your code, the management + * can be shared across all processes. + * + * The second way is to control the number of SPUs used by SPURS with + * initWithSpuCount. Software will create its own instance of SPURS when it needs + * it using that maximum number of SPUs. + * + * If you do neither of the two, Software will create its own instance of SPURS when + * it needs it using CELL_SPURS_DEFAULT_SPU_COUNT SPUs. + * + * terminate() can be used to either detatch from an existing SPURS or to + * terminate the Software-created SPURS. Note that this will only work if all collision + * scenes have been destroyed, otherwise SPURS would still be needed. + * + * Creating a new scene will cause SPURS to re-initialize. + * + * isSpursInitialized can be used to query whether software is currently using SPURS. + * + **/ + +/** + * \brief Initializes SPUs given a pre-configured SPURS. + * \param[in] pSpurs A pointer to SPURS + * \param[in] iSPUCount The number of SPUs + * \param[in] auiPriorities The priorities for the code to use + * \return Return is: + * CELL_SPURS_OK on success + * CELL_SPURS_EBUSY if SPU usage has already been initialized + * CELL_SPURS_EINVAL if the priorities or SPURS pointer is invalid. + */ +int spursConfiguration_initWithSpurs(CellSpurs *pSpurs, int iSPUCount, uint8_t auiPriorities[8]); + +/** + * \brief Sets the number of SPUs to be used by a software-initialized SPURS. + * \param[in] iSPUCount A valid value is in the range 1-6 + * \return Return is: + * CELL_SPURS_OK on success + * CELL_SPURS_EBUSY if SPU usage has already been initialized + * CELL_SPURS_EINVAL if iSPUCount is out of range or if SPURS couldn't be + * initialized to that many SPUs. + */ +int spursConfiguration_initWithSpuCount(int iSPUCount); + +/** + * \brief Terminates (or disconnects from) SPURS. + * \return Return is: + * CELL_SPURS_OK if SPURS terminates ok, or if it was previously terminated/ + * never initialized. + * CELL_SPURS_EBUSY if there are existing Scenes which would need SPURS. + */ +int spursConfiguration_terminate(); + +/** + * \brief Queries whether SPU usage has been initialized. + * \return True if initialized. + */ +bool spursConfiguration_isSpursInitialized(); + +#ifdef __cplusplus +} +#endif + +#endif //__CELL_SPU_CONFIG diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/spursPlatformDefinitions.h b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursPlatformDefinitions.h new file mode 100644 index 00000000..2d00593a --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursPlatformDefinitions.h @@ -0,0 +1,13 @@ +#ifndef TYPE_DEFINITIONS_H +#define TYPE_DEFINITIONS_H + +////////////////////////////////////////////////////////////////////////// +// Don't need printf in this sample +///Playstation 3 Cell SDK +//#include + +#include +#include +#include //for memcpy + +#endif //TYPE_DEFINITIONS_H diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/spursScalar.h b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursScalar.h new file mode 100644 index 00000000..e766ca34 --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursScalar.h @@ -0,0 +1,172 @@ +// Gamespy Technology +// NOTE: this code has been provided by Sony for usage in Speex SPURS Manager + +/* +Copyright (c) 2003-2006 Gino van den Bergen / Erwin Coumans http://continuousphysics.com/Bullet/ + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + + + +#ifndef SIMD___SCALAR_H +#define SIMD___SCALAR_H + +#include + +#include +#include +#include + +#define SIMD_FORCE_INLINE inline +#define ATTRIBUTE_ALIGNED16(a) a __attribute__ ((aligned (16))) +#ifndef assert +#include +#endif +#define btAssert assert +//btFullAssert is optional, slows down a lot +#define btFullAssert(x) + +////////////////////////////////////////////////////////////////////////// +// removed since only ps3 used +/* +/// older compilers (gcc 3.x) and Sun needs double version of sqrt etc. +/// exclude Apple Intel (i's assumed to be a Macbook or new Intel Dual Core Processor) +#if defined (__sun) || defined (__sun__) || defined (__sparc) || (defined (__APPLE__) && ! defined (__i386__)) +//use slow double float precision operation on those platforms +#ifndef BT_USE_DOUBLE_PRECISION +#define BT_FORCE_DOUBLE_FUNCTIONS +#endif +#endif +*/ + +////////////////////////////////////////////////////////////////////////// +// removed since only ps3 used +/* +#if defined(BT_USE_DOUBLE_PRECISION) +typedef double btScalar; +#else +typedef float btScalar; +#endif +*/ + +////////////////////////////////////////////////////////////////////////// +// removed since not needed +/* +#if defined(BT_USE_DOUBLE_PRECISION) || defined(BT_FORCE_DOUBLE_FUNCTIONS) + +SIMD_FORCE_INLINE btScalar btSqrt(btScalar x) { return sqrt(x); } +SIMD_FORCE_INLINE btScalar btFabs(btScalar x) { return fabs(x); } +SIMD_FORCE_INLINE btScalar btCos(btScalar x) { return cos(x); } +SIMD_FORCE_INLINE btScalar btSin(btScalar x) { return sin(x); } +SIMD_FORCE_INLINE btScalar btTan(btScalar x) { return tan(x); } +SIMD_FORCE_INLINE btScalar btAcos(btScalar x) { return acos(x); } +SIMD_FORCE_INLINE btScalar btAsin(btScalar x) { return asin(x); } +SIMD_FORCE_INLINE btScalar btAtan(btScalar x) { return atan(x); } +SIMD_FORCE_INLINE btScalar btAtan2(btScalar x, btScalar y) { return atan2(x, y); } +SIMD_FORCE_INLINE btScalar btExp(btScalar x) { return exp(x); } +SIMD_FORCE_INLINE btScalar btLog(btScalar x) { return log(x); } +SIMD_FORCE_INLINE btScalar btPow(btScalar x,btScalar y) { return pow(x,y); } + +#else + +SIMD_FORCE_INLINE btScalar btSqrt(btScalar x) { return sqrtf(x); } +SIMD_FORCE_INLINE btScalar btFabs(btScalar x) { return fabsf(x); } +SIMD_FORCE_INLINE btScalar btCos(btScalar x) { return cosf(x); } +SIMD_FORCE_INLINE btScalar btSin(btScalar x) { return sinf(x); } +SIMD_FORCE_INLINE btScalar btTan(btScalar x) { return tanf(x); } +SIMD_FORCE_INLINE btScalar btAcos(btScalar x) { return acosf(x); } +SIMD_FORCE_INLINE btScalar btAsin(btScalar x) { return asinf(x); } +SIMD_FORCE_INLINE btScalar btAtan(btScalar x) { return atanf(x); } +SIMD_FORCE_INLINE btScalar btAtan2(btScalar x, btScalar y) { return atan2f(x, y); } +SIMD_FORCE_INLINE btScalar btExp(btScalar x) { return expf(x); } +SIMD_FORCE_INLINE btScalar btLog(btScalar x) { return logf(x); } +SIMD_FORCE_INLINE btScalar btPow(btScalar x,btScalar y) { return powf(x,y); } + +#endif +*/ + + +////////////////////////////////////////////////////////////////////////// +// removed since not necessary +/* +#define SIMD_2_PI btScalar(6.283185307179586232) +#define SIMD_PI (SIMD_2_PI * btScalar(0.5)) +#define SIMD_HALF_PI (SIMD_2_PI * btScalar(0.25)) +#define SIMD_RADS_PER_DEG (SIMD_2_PI / btScalar(360.0)) +#define SIMD_DEGS_PER_RAD (btScalar(360.0) / SIMD_2_PI) + +#ifdef BT_USE_DOUBLE_PRECISION +#define SIMD_EPSILON DBL_EPSILON +#define SIMD_INFINITY DBL_MAX +#else +#define SIMD_EPSILON FLT_EPSILON +#define SIMD_INFINITY FLT_MAX +#endif + +SIMD_FORCE_INLINE btScalar btAtan2Fast(btScalar y, btScalar x) +{ + btScalar coeff_1 = SIMD_PI / 4.0f; + btScalar coeff_2 = 3.0f * coeff_1; + btScalar abs_y = btFabs(y); + btScalar angle; + if (x >= 0.0f) { + btScalar r = (x - abs_y) / (x + abs_y); + angle = coeff_1 - coeff_1 * r; + } else { + btScalar r = (x + abs_y) / (abs_y - x); + angle = coeff_2 - coeff_1 * r; + } + return (y < 0.0f) ? -angle : angle; +} + +SIMD_FORCE_INLINE bool btFuzzyZero(btScalar x) { return btFabs(x) < SIMD_EPSILON; } + +SIMD_FORCE_INLINE bool btEqual(btScalar a, btScalar eps) { + return (((a) <= eps) && !((a) < -eps)); +} +SIMD_FORCE_INLINE bool btGreaterEqual (btScalar a, btScalar eps) { + return (!((a) <= eps)); +} +*/ + +/*SIMD_FORCE_INLINE btScalar btCos(btScalar x) { return cosf(x); } +SIMD_FORCE_INLINE btScalar btSin(btScalar x) { return sinf(x); } +SIMD_FORCE_INLINE btScalar btTan(btScalar x) { return tanf(x); } +SIMD_FORCE_INLINE btScalar btAcos(btScalar x) { return acosf(x); } +SIMD_FORCE_INLINE btScalar btAsin(btScalar x) { return asinf(x); } +SIMD_FORCE_INLINE btScalar btAtan(btScalar x) { return atanf(x); } +SIMD_FORCE_INLINE btScalar btAtan2(btScalar x, btScalar y) { return atan2f(x, y); } +*/ + + +////////////////////////////////////////////////////////////////////////// +// removed since not necessary +/* +SIMD_FORCE_INLINE int btIsNegative(btScalar x) { + return x < btScalar(0.0) ? 1 : 0; +} + +SIMD_FORCE_INLINE btScalar btRadians(btScalar x) { return x * SIMD_RADS_PER_DEG; } +SIMD_FORCE_INLINE btScalar btDegrees(btScalar x) { return x * SIMD_DEGS_PER_RAD; } + +#define BT_DECLARE_HANDLE(name) typedef struct name##__ { int unused; } *name + +#ifndef btFsel +SIMD_FORCE_INLINE btScalar btFsel(btScalar a, btScalar b, btScalar c) +{ + return a >= 0 ? b : c; +} +#endif +#define btFsels(a,b,c) (btScalar)btFsel(a,b,c) +*/ + +#endif //SIMD___SCALAR_H diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/spursSupportInterface.cpp b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursSupportInterface.cpp new file mode 100644 index 00000000..4b7a4275 --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursSupportInterface.cpp @@ -0,0 +1,553 @@ +#include "spursConfiguration.h" +#include "spursSupportInterface.h" +#include +#include +#include +#include +#include +#include +#include + +#define SCE_EXTERNAL_RELEASE + +// for CELL_FS_MAX_FS_PATH_LENGTH +#include + +static uint32_t _g_uiNextEventQueueKey=0x92400ABDUL; +uint32_t _SpursSupportGetUniqueEventQueueKey() { + return _g_uiNextEventQueueKey++; +} + +sys_event_queue_t _g_SpuPrintfEventQueue; +sys_ppu_thread_t _g_SpursPrintfThread; + +void _SpursPrintfThreadMain(uint64_t arg); + +#define SPURS_PPU_THREAD_PRIO 1001 +#define SPU_PRINTF_EVENT_QUEUE_SIZE 8 +#define SPU_PRINTF_EVENT_QUEUE_PORT 0x1 +#define SPU_PRINTF_THREAD_PRIO 1001 +#define SPU_PRINTF_THREAD_STACK_SIZE (64 * 1024) + +/* +typedef struct BulletSpursElf { + const char *pcElfName; + void *pvElfImage; +} BulletSpursElf; +*/ + +#ifdef _DEBUG +extern char _binary_spu_SpeexSpursTaskDebug_elf_start[]; +void *g_SpursTaskElfStart = _binary_spu_SpeexSpursTaskDebug_elf_start; +#else +extern char _binary_spu_SpeexSpursTaskRelease_elf_start[]; +void *g_SpursTaskElfStart = _binary_spu_SpeexSpursTaskRelease_elf_start; +#endif //#ifdef _DEBUG + + +/* +BulletSpursElf _g_aSPURSElfs[]={ +#ifdef _DEBUG + {"test.elf", _binary_spu_PS3_SPURS_SpeexDebug_elf_start} +#else + {"test.elf", _binary_spu_PS3_SPURS_SpeexRelease_elf_start} +#endif +}; +*/ + +/* +#ifndef SCE_EXTERNAL_RELEASE +// Here's the search path for the SPURS programs: +char *_g_apcSPURSELFSearchPaths[]={ + "/app_home/", + "/app_home/../../lib/", // works with samples + "/app_home/../../../lib/", // works with tutorials + "/app_home/../../../../lib/", + "/app_home/../../../../../lib/", +}; +unsigned int _g_uiSPURSELFSearchPathCount=sizeof(_g_apcSPURSELFSearchPaths)/sizeof(char *); +#endif // SCE_EXTERNAL_RELEASE +*/ + +// E The priority for the SPU thread group used by SPURS +#define SPURS_SPU_THREAD_PRIORITY 200 + +static CellSpurs *g_spursInstance=0; +static CellSpursTaskset g_spursTaskSet __attribute__ ((aligned(128))); + +static int g_iDefaultSPUCount=CELL_SPURS_DEFAULT_SPU_COUNT; +static bool g_bSpursInitialized=false; +static bool g_bUserSpursGiven=false; +static uint32_t g_uiSpursReferenceCounter=0; + +/** + * E This is the main function of the spu_printf service thread. + * It listens for messages and calls the printf handler when it gets them, + * then notifies the SPU of completion by using a mailbox. + */ +//UPDATE: this function is not currently being used in the speex task or spurs manager +void _SpursPrintfThreadMain(uint64_t arg) +{ + sys_event_t event; + int iReturn; + + // E For unused parameter warnings + (void) arg; + + while (1) { + iReturn=sys_event_queue_receive(_g_SpuPrintfEventQueue, &event, + SYS_NO_TIMEOUT); + + if (iReturn!=CELL_OK) { + fprintf(stderr, "Event queue receive wasn't successful: %i\n", + iReturn); + exit(-1); + } + + iReturn=spu_thread_printf(event.data1, event.data3); + sys_spu_thread_write_spu_mb(event.data1, iReturn); + } +} + +/* +void *loadBulletImage(const char *pcFilename) { + void *pvElf; + FILE *pfInputFile=fopen(pcFilename, "rb"); + uint64_t uiFileSize; + + if (!pfInputFile) { + return 0; + } + + fseek(pfInputFile, 0, SEEK_END); + uiFileSize=ftell(pfInputFile); + fseek(pfInputFile, 0, SEEK_SET); + pvElf=memalign(128, uiFileSize); + + if (!pvElf) { + printf("Cannot allocate memory for file %s\n", pcFilename); + fclose(pfInputFile); + return 0; + } + + fread(pvElf, 1, uiFileSize, pfInputFile); + fclose(pfInputFile); + + return pvElf; +} + +void unloadBulletSpursElfs() { +#ifdef SCE_EXTERNAL_RELEASE + return; +#endif + + for (int iELF=0; iELF6) + { + return CELL_SPURS_EINVAL; + } + + return initializeSpursTaskSet(pSpurs, iSPUCount, auiPriorities); +} + +/** + * Sets the number of SPUs to be used by a Bullet-initialized SPURS. + * Valid iSPUCount is in the range 1-6. + * Return is: + * CELL_SPURS_OK on success + * CELL_SPURS_EBUSY if SPU usage has already been initialized + * CELL_SPURS_EINVAL if iSPUCount is out of range or if SPURS couldn't be + * initialized to that many SPUs. + */ +int spursConfiguration_initWithSpuCount(int iSPUCount) +{ + if (g_bSpursInitialized) + { + return CELL_SPURS_EBUSY; + } + + if (iSPUCount<1 || iSPUCount>6) + { + return CELL_SPURS_EINVAL; + } + + return initializeSpursTaskSet(0, iSPUCount, 0); +} + +/** + * Terminates (or disconnects from) SPURS. + * Return is: + * CELL_SPURS_OK if SPURS terminates ok, or if it was previously terminated/ + * never initialized. + * CELL_SPURS_EBUSY if there are existing Scenes which would need SPURS. + */ +int spursConfiguration_terminate() +{ + if (!g_bSpursInitialized) + { + return CELL_SPURS_OK; + } + + if (g_uiSpursReferenceCounter) + { + return CELL_SPURS_EBUSY; + } + int iReturn; + bool bUserSpurs=g_bUserSpursGiven; + + g_bSpursInitialized=false; + g_bUserSpursGiven=false; + + iReturn=cellSpursShutdownTaskset(&g_spursTaskSet); + if (iReturn!=CELL_OK) + { + return -1; + //fprintf(stderr, "Bullet: Error shutting down SPURS task set: %i\n", iReturn); + } + + iReturn=cellSpursJoinTaskset(&g_spursTaskSet); + if (iReturn!=CELL_OK) + { + return -2; + //fprintf(stderr, "Bullet: Error joining SPURS task set: %i\n", iReturn); + } + + if (!bUserSpurs) + { + int iReturn=cellSpursFinalize(g_spursInstance); + + if (iReturn!=CELL_OK) + { + return -3; + //fprintf(stderr, "Bullet: Error shutting down SPURS: %d\n", iReturn); + } + + free(g_spursInstance); + } + + g_spursInstance=0; + + // Not loading SPURS task from file anymore + //unloadBulletSpursElfs(); +// printf code is commented out for the time being +//#warning TODO: Clean up SPU printf thread. + + return CELL_SPURS_OK; +} + +/** + * Queries whether SPU usage has been initialized. + * Return is: + * true if initialized. + */ +bool spursConfiguration_isSpursInitialized() +{ + return g_bSpursInitialized; +} + + +SpursSupportInterface::SpursSupportInterface() +{ + //assert(elfId < SPU_ELF_LAST); + //m_elfId=elfId; + m_bQueueInitialized=false; + + cellAtomicIncr32(&g_uiSpursReferenceCounter); +} + +SpursSupportInterface::~SpursSupportInterface() +{ + stopSPU(); + + cellAtomicDecr32(&g_uiSpursReferenceCounter); +} + +int SpursSupportInterface::startSPU() +{ + int iReturn; + + if (!m_bQueueInitialized) + { + if (checkSpursTaskSet() != CELL_SPURS_OK) + { + return -1; + } + + iReturn=cellSpursQueueInitialize(&g_spursTaskSet, + &m_responseQueue, m_aResponseBuffer, sizeof(CellSPURSArgument), + CELL_SPURS_RESPONSE_QUEUE_SIZE, CELL_SPURS_QUEUE_SPU2PPU); + if (iReturn!=CELL_OK) + { + return -2; + } + + iReturn=cellSpursQueueAttachLv2EventQueue(&m_responseQueue); + if (iReturn!=CELL_OK) + { + return -3; + } + + m_bQueueInitialized=true; + } + return 0; +} + +int SpursSupportInterface::stopSPU() +{ + if (m_bQueueInitialized) + { + int iReturn=cellSpursQueueDetachLv2EventQueue(&m_responseQueue); + if (iReturn != CELL_OK) + return -1; + m_bQueueInitialized=false; + } + return 0; +} + +int SpursSupportInterface::sendRequest(uint32_t uiCommand, uint32_t uiArgument0, uint32_t uiArgument1) +{ + int iReturn; + CellSpursTaskId taskId; + CellSPURSArgument arguments; + + arguments.ppuResponseQueue=&m_responseQueue; + arguments.uiCommand=uiCommand; + arguments.uiArgument0=uiArgument0; + arguments.uiArgument1=uiArgument1; + + if (checkSpursTaskSet() != CELL_SPURS_OK) + { + return -1; + } + + iReturn=cellSpursCreateTask(&g_spursTaskSet, &taskId, g_SpursTaskElfStart, NULL, 0, 0, &arguments.spursArgument); + if (iReturn!=CELL_OK) + { + return -2; + } + + return 0; +} + + +/** + * Wait for the SPU to send an event back to our event queue. + */ +int SpursSupportInterface::waitForResponse(unsigned int *puiArgument0, unsigned int *puiArgument1) +{ + CellSPURSArgument response __attribute__((aligned(16))); + int iReturn; + + iReturn=cellSpursQueuePop(&m_responseQueue, (void *) &response); + + if (iReturn!=CELL_OK) + { + return -1; + } + + if (puiArgument0) + *puiArgument0=response.uiArgument0; + + if (puiArgument1) + *puiArgument1=response.uiArgument1; + return 0; +} + diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/spursSupportInterface.h b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursSupportInterface.h new file mode 100644 index 00000000..a71f1461 --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursSupportInterface.h @@ -0,0 +1,133 @@ +/* [SCE CONFIDENTIAL DOCUMENT] + * PLAYSTATION(R)3 SPU Optimized Bullet Physics Library (http://bulletphysics.com) + * Copyright (C) 2007 Sony Computer Entertainment Inc. + * All Rights Reserved. + */ + + +#ifndef __SPURS_SUPPORT_INTERFACE_H +#define __SPURS_SUPPORT_INTERFACE_H + +#include +#include "spursUtilityMacros.h" +#include "spursThreadSupportInterface.h" + +#ifdef __SPU__ +#include + +#include + +#if CELL_SDK_VERSION < 0x081000 +#define CELL_SPURS_TASK_ERROR_AGAIN CELL_SPURS_EAGAIN +#define CELL_SPURS_TASK_ERROR_BUSY CELL_SPURS_EBUSY +#endif // CELL_SDK_VERSION < 0x081000 + +#else // __SPU__ +#include + +#endif // __SPU__ + +#define CELL_SPURS_RESPONSE_QUEUE_SIZE 128 + +/** + * Note: + * The order of elements in this enum are important, that's why each one is explicitly + * given a value. They will correspond to the .elf names/addresses that will be + * loaded into SPURS. + * Mixing up these values will cause the wrong code to execute, for instance, the + * solver may be asked to do a collision detection job. + */ +////////////////////////////////////////////////////////////////////////// +// only one type of SPURS Task ELF +// typedef enum { +// // SPU_ELF_MID_PHASE=0, +// // SPU_ELF_SOLVER, +// SPU_ELF_SPEEX, +// SPU_ELF_LAST, +// } CellSpursElfId_t; + +typedef union CellSPURSArgument +{ + struct + { + CELL_PPU_POINTER(CellSpursQueue) ppuResponseQueue; + uint32_t uiCommand; + uint32_t uiArgument0; + uint32_t uiArgument1; + }; + +#if __PPU__ + CellSpursTaskArgument spursArgument; +#elif __SPU__ + vec_uint4 uiQWord; +#endif +} CellSPURSArgument __attribute__((aligned(16))); + +#if __SPU__ +#include "SPUAssert.h" +#include + +static inline void sendResponseToPPU(uint32_t ppuQueueEA, uint32_t uiArgument0, + uint32_t uiArgument1, int iTag=1) { + CellSPURSArgument response + __attribute__ ((aligned(16))); + + response.uiArgument0=uiArgument0; + response.uiArgument1=uiArgument1; + + int iReturn; + do { + iReturn=cellSpursQueueTryPushBegin(ppuQueueEA, &response, iTag); + } while (iReturn == CELL_SPURS_TASK_ERROR_AGAIN || + iReturn == CELL_SPURS_TASK_ERROR_BUSY); + + SPU_ASSERT((iReturn == CELL_OK) && "Error writing to SPURS queue."); + + cellSpursQueuePushEnd(ppuQueueEA, iTag); + +} + +static inline void sendResponseToPPUAndExit(uint32_t ppuQueueEA, uint32_t uiArgument0, + uint32_t uiArgument1, int iTag=1) { + CellSPURSArgument response + __attribute__ ((aligned(16))); + + response.uiArgument0=uiArgument0; + response.uiArgument1=uiArgument1; + + int iReturn; + do { + iReturn=cellSpursQueueTryPushBegin(ppuQueueEA, &response, iTag); + } while (iReturn == CELL_SPURS_TASK_ERROR_AGAIN || + iReturn == CELL_SPURS_TASK_ERROR_BUSY); + + SPU_ASSERT((iReturn == CELL_OK) && "Error writing to SPURS queue."); + + cellSpursQueuePushEnd(ppuQueueEA, iTag); + + cellSpursExit(); +} +#elif __PPU__ // not __SPU__ + +class SpursSupportInterface : public spursThreadSupportInterface +{ +public: + SpursSupportInterface(); + ~SpursSupportInterface(); + int sendRequest(uint32_t uiCommand, uint32_t uiArgument0, uint32_t uiArgument1=0); + int waitForResponse(unsigned int *puiArgument0, unsigned int *puiArgument1); + int startSPU(); + int stopSPU(); + +protected: + //CellSpursElfId_t m_elfId; + void *m_spursTaskAddress; + CellSpursQueue m_responseQueue __attribute__((aligned(128))); + CellSPURSArgument m_aResponseBuffer[CELL_SPURS_RESPONSE_QUEUE_SIZE] __attribute__((aligned(16))); + + bool m_bQueueInitialized; +}; +#endif // __SPU__ / __PPU__ + + +#endif // CELL_SPURS_SUPPORT_H diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/spursThreadSupportInterface.cpp b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursThreadSupportInterface.cpp new file mode 100644 index 00000000..411b57d9 --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursThreadSupportInterface.cpp @@ -0,0 +1,21 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2007 Erwin Coumans http://bulletphysics.com + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "spursThreadSupportInterface.h" + +spursThreadSupportInterface::~spursThreadSupportInterface() +{ + +} diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/spursUtilityMacros.h b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursUtilityMacros.h new file mode 100644 index 00000000..3e348517 --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursUtilityMacros.h @@ -0,0 +1,21 @@ +#ifndef __CELL_UTILITY_MACROS_H +#define __CELL_UTILITY_MACROS_H + +// This type pretends to be a pointer to a type on the PPU, and +// just an unsigned int on the SPU. +#ifndef CELL_PPU_POINTER +#define CELL_PPU_PTR_TYPE uint32_t + +#ifdef __SPU__ +#define CELL_PPU_POINTER(x) CELL_PPU_PTR_TYPE +#else // __SPU__ +#define CELL_PPU_POINTER(x) x * + +// Hope we never switch away from 32 bits... +// But this is here just in case... +//NX_COMPILE_TIME_ASSERT(sizeof(void *)==sizeof(CELL_PPU_PTR_TYPE)); +#endif // __SPU__ +#endif // CELL_PPU_POINTER + +#endif +// end __CELL_UTILITY_MACROS_H diff --git a/code/gamespy/common/ps3/SpeexSpursTaskManager/spursthreadsupportinterface.h b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursthreadsupportinterface.h new file mode 100644 index 00000000..a9a1627e --- /dev/null +++ b/code/gamespy/common/ps3/SpeexSpursTaskManager/spursthreadsupportinterface.h @@ -0,0 +1,43 @@ +// Gamespy Technology +// NOTE: this code has been provided by Sony for usage in Speex SPURS Manager + +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2007 Erwin Coumans http://bulletphysics.com + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef THREAD_SUPPORT_INTERFACE_H +#define THREAD_SUPPORT_INTERFACE_H + +#include "spursPlatformDefinitions.h" + +class spursThreadSupportInterface +{ +public: + + virtual ~spursThreadSupportInterface(); + +///send messages to SPUs + virtual int sendRequest(uint32_t uiCommand, uint32_t uiArgument0, uint32_t uiArgument1) =0; + +///check for messages from SPUs + virtual int waitForResponse(unsigned int *puiArgument0, unsigned int *puiArgument1) =0; + +///start the spus (can be called at the beginning of each frame, to make sure that the right SPU program is loaded) + virtual int startSPU() =0; + +///tell the task scheduler we are done with the SPU tasks + virtual int stopSPU()=0; +}; + +#endif //THREAD_SUPPORT_INTERFACE_H diff --git a/code/gamespy/common/ps3/gsSocketPS3.c b/code/gamespy/common/ps3/gsSocketPS3.c new file mode 100644 index 00000000..45345c0e --- /dev/null +++ b/code/gamespy/common/ps3/gsSocketPS3.c @@ -0,0 +1,43 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gsCommon.h" +#include "../gsPlatformSocket.h" +#include + + + +// ToDo: Move this to PS3 implemenation +HOSTENT * getlocalhost(void) +{ // Global storage + + #define MAX_IPS 10 + static HOSTENT localhost; + static char * aliases = NULL; + static char * ipPtrs[MAX_IPS + 1]; + static unsigned int ips [MAX_IPS]; + int r; + union CellNetCtlInfo gCellNetInfo; + // Todo: support mutliple ip's. + + // initialize the host + localhost.h_name = "localhost"; + localhost.h_aliases = &aliases; + localhost.h_addrtype = AF_INET; + localhost.h_length = sizeof(unsigned int); + localhost.h_addr_list = ipPtrs; + ipPtrs[0] = (char *)&ips[0]; + ipPtrs[1] = NULL; + + // to do, cache this, and do this once at init. + r = cellNetCtlGetInfo( CELL_NET_CTL_INFO_IP_ADDRESS,&gCellNetInfo ); + if (r == CELL_OK) + { + ips[0] = inet_addr(gCellNetInfo.ip_address); + + + return &localhost; + } + else + return NULL; + +} diff --git a/code/gamespy/common/ps3/gsUtilPS3.c b/code/gamespy/common/ps3/gsUtilPS3.c new file mode 100644 index 00000000..3f556313 --- /dev/null +++ b/code/gamespy/common/ps3/gsUtilPS3.c @@ -0,0 +1,76 @@ +#if defined(_PS3) + +#include +#include +#include +#include + +#include "../gsCommon.h" + +// This needs to be set during interface start +extern int gNetInterfaceID; + +#if defined(_PS3) && defined(UNIQUEID) + static const char * GetMAC(void) + { + // Get the MAC address using the interface control + static union CellNetCtlInfo gCellNetInfo; + int r; + // Get MAC + // to do, cache this, and do this once at init. + r = cellNetCtlGetInfo( CELL_NET_CTL_INFO_ETHER_ADDR,&gCellNetInfo ); + if (r == CELL_OK) + { + + return (const char *)&gCellNetInfo.ether_addr; + }// else error + return NULL; + } + + static const char * GOAGetUniqueID_Internal(void) + { + static char keyval[17]; + const char * MAC; + + // check if we already have the Unique ID + if(keyval[0]) + return keyval; + + // get the MAC + MAC = GetMAC(); + if(!MAC) + { + // error getting the MAC + static char errorMAC[6] = { 1, 2, 3, 4, 5, 6 }; + MAC = errorMAC; + } + + // format it + sprintf(keyval, "%02X%02X%02X%02X%02X%02X0000", + MAC[0] & 0xFF, + MAC[1] & 0xFF, + MAC[2] & 0xFF, + MAC[3] & 0xFF, + MAC[4] & 0xFF, + MAC[5] & 0xFF); + + return keyval; + } +#endif + +gsi_i64 gsiStringToInt64(const char *theNumberStr) +{ + return atoll(theNumberStr); +} + +void gsiInt64ToString(char theNumberStr[33], gsi_i64 theNumber) +{ + // you want to fit the number! + // give me a valid string! + GS_ASSERT(theNumberStr != NULL); + + sprintf(theNumberStr, "%lld", theNumber); +} +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // _PS3 only diff --git a/code/gamespy/common/ps3/ps3common.c b/code/gamespy/common/ps3/ps3common.c new file mode 100644 index 00000000..04057e07 --- /dev/null +++ b/code/gamespy/common/ps3/ps3common.c @@ -0,0 +1,248 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Common code for GameSpy samples +// Note: This code is not intended to be used in a retail program +// +// Portions taken from PS3 sample applications. Please refer to Sony +// documentation and samples for application startup procedures. +#include +#include +#include +#include + +#include "../gsPlatform.h" +#include "../gsPlatformUtil.h" +#include "../gsMemory.h" +// entry point for GameSpy samples +extern int test_main(int argc, char ** argp); + +#define PRIO (1001) +#define HOST_NAME "localhost" + + +// * added due to PS3 SDK 092.00x and above - all stub libraries require you to load the module +// * for it to be properly linked +gsi_bool _LoadModules() +{ + cellSysmoduleInitialize(); + + if (cellSysmoduleLoadModule(CELL_SYSMODULE_NET) != CELL_OK) + { + printf("cellSysmoduleLoadModule(CELL_SYSMODULE_NET) failed\n"); + return gsi_false; + } + if (cellSysmoduleLoadModule(CELL_SYSMODULE_IO) != CELL_OK) + { + printf("cellSysmoduleLoadModule(CELL_SYSMODULE_IO) failed\n"); + return gsi_false; + } + if (cellSysmoduleLoadModule(CELL_SYSMODULE_USBD) != CELL_OK) + { + printf("cellSysmoduleLoadModule(CELL_SYSMODULE_USBD) failed\n"); + return gsi_false; + } + if (cellSysmoduleLoadModule(CELL_SYSMODULE_SYSUTIL_NP) != CELL_OK) + { + printf("cellSysmoduleLoadModule(CELL_SYSMODULE_SYSUTIL_NP) failed\n"); + return gsi_false; + } + if (cellSysmoduleLoadModule(CELL_SYSMODULE_RTC) != CELL_OK) + { + printf("cellSysmoduleLoadModule(CELL_SYSMODULE_RTC) failed\n"); + return gsi_false; + } + + return gsi_true; +} + +// unloads modules to free up memory +void _UnloadModules() +{ + cellSysmoduleUnloadModule(CELL_SYSMODULE_NET); + cellSysmoduleUnloadModule(CELL_SYSMODULE_IO); + cellSysmoduleUnloadModule(CELL_SYSMODULE_USBD); + cellSysmoduleUnloadModule(CELL_SYSMODULE_SYSUTIL_NP); + cellSysmoduleUnloadModule(CELL_SYSMODULE_RTC); + + cellSysmoduleFinalize(); +} + + +gsi_bool _NetworkInit() +{ + int r = sys_net_initialize_network(); + + if (r!=CELL_OK) + { + printf("ccNetworkInitializeNetwork: sys_net_initialize_network() failed: %i\n",r); + return gsi_false; + } + + + return (r==0); +} + + + +gsi_bool _NetworkStart() +{ + int iReturn, iNetworkState; + + /*iReturn = cellSysutilInit(); + if (iReturn < 0) + { + printf("cellSysutilInit() failed(%x)\n", iReturn); + return gsi_false; + }*/ + + iReturn = cellNetCtlInit(); + if (iReturn!=CELL_OK) + { + printf("ccNetworkInitializeNetwork: cellNetCtlInit() failed: %i\n", + iReturn); + return gsi_false; + } + + + while (1) + { + iReturn=cellNetCtlGetState(&iNetworkState); + + + if (iReturn!=CELL_OK) + { + printf("ccNetworkInitializeNetwork: cellNetCtlGetState() failed: %i\n", + iReturn); + return gsi_false; + } + + if (iNetworkState!=CELL_NET_CTL_STATE_IPObtained) + { + sys_timer_usleep(100 * 1000); + } + else + { + break; + } + }; + + return gsi_true; +} + +void _NetworkStop() +{ + cellNetCtlTerm(); + //cellSysutilShutdown(); +} + +void _NetworkClose() +{ + sys_net_finalize_network(); +} + +void * gsiMemManagedInit() +{ + // Init the GSI memory manager (optional - for limiting GSI mem usage) +#if defined GSI_MEM_MANAGED +#define aMemoryPoolSize (1024*1024*8) + char *aMemoryPool = calloc(aMemoryPoolSize,32); + if(aMemoryPool == NULL) + { + printf("Failed to create memory pool - aborting\r\n"); + return NULL; + } + else + { + gsMemMgrContext c = gsMemMgrCreate(gsMemMgrContext_Default, "Default",aMemoryPool, aMemoryPoolSize); + GSI_UNUSED(c); + } + return aMemoryPool; +#else + return NULL; +#endif + +} + +void gsiMemManagedClose(void * aMemoryPool) +{ +#if defined(GSI_MEM_MANAGED) + // Optional - Dump memory leaks + + gsi_u32 MemAvail = gsMemMgrMemAvailGet (gsMemMgrContext_Default); + gsi_u32 MemUsed = gsMemMgrMemUsedGet (gsMemMgrContext_Default); + gsi_u32 HwMark = gsMemMgrMemHighwaterMarkGet (gsMemMgrContext_Default); + + printf("MemAvail %u: MemUsed%u MemHWMark %u\n", MemAvail,MemUsed,HwMark); + gsMemMgrDumpStats(); + gsMemMgrDumpAllocations(); + gsMemMgrValidateMemoryPool(); + gsMemMgrDestroy(gsMemMgrContext_Default); + free(aMemoryPool); +#endif +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +int main(int argc, char ** argp) +{ + //sys_pid_t pid = 0; + //int result = 0; + //int count = 0; + //int id_list[10]; + //int i=0; + void *heap; + printf("\nGameSpy Test App Initializing\n" + "----------------------------------\n"); +/* + // spawn IO module + if (sys_process_spawn(&pid, "brio.elf", NULL, 0, PRIO, 0) != SUCCEEDED) { + printf("sys_process_spawn(brio.elf) failed\n"); + return (0); + } +*/ + + // load required modules + if(!_LoadModules()) + { + printf("_LoadModules() failed\n"); + return (0); + } + + // initialize network using hardcoded settings above + if(!_NetworkInit()) + { + printf("_NetworkInit() failed\n"); + return (0); + } + + if(!_NetworkStart()) + { + printf("_NetworkStart() failed\n"); + return (0); + } + + heap = gsiMemManagedInit(); + // start the actual program + printf("\nGameSpy Test App Starting\n" + "----------------------------------\n"); + test_main(argc, argp); + + // do any needed cleanup + printf("\nGameSpy Test App Exiting\n" + "----------------------------------\n"); + gsiMemManagedClose(heap); + // close network + _NetworkStop(); + _NetworkClose(); + + // unload modules + _UnloadModules(); + + // don't exit into never never land and lose our tty output. + while(1) + { + msleep(100); + } + return 0; +} + diff --git a/code/gamespy/common/psp/gsSocketPSP.c b/code/gamespy/common/psp/gsSocketPSP.c new file mode 100644 index 00000000..afa60e0c --- /dev/null +++ b/code/gamespy/common/psp/gsSocketPSP.c @@ -0,0 +1,350 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../gsCommon.h" +#include "../gsPlatformSocket.h" + + +#if(0) // enable after remove from platform socket +int SetSockBlocking(SOCKET sock, int isblocking) +{ + int rcode; + unsigned long argp; + + if(isblocking) + argp = 0; + else + argp = 1; + + rcode = setsockopt(sock, SCE_NET_INET_SOL_SOCKET, SCE_NET_INET_SO_NBIO, &argp, sizeof(argp)); + + if(rcode == 0) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "SetSockBlocking: Set socket %d to %s\r\n", (unsigned int)sock, isblocking ? "blocking":"non-blocking"); + return 1; + } + + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "SetSockBlocking failed: tried to set socket %d to %s\r\n", (unsigned int)sock, isblocking ? "blocking":"non-blocking"); + return 0; +} +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define MAX_IPS 5 +struct hostent* gsSocketGetHostByName(const char* name) +{ + // parameter check + GS_ASSERT(name); + { + int result = 0; + int resolverID = 0; + // mj taking this off the stack 04/05/06 + #define GetHostByNameBufferSize 1024 + char *buf = gsimalloc(GetHostByNameBufferSize);//[GetHostByNameBufferSize]; // PSP documentation recommends 1024 + struct in_addr ip; + + static struct hostent ahostent; + static char * aliases = NULL; + static char * ipPtrs[MAX_IPS + 1]; + static unsigned int ips[MAX_IPS]; + + GS_ASSERT(buf); + + ahostent.h_name = ""; + ahostent.h_aliases = &aliases; + ahostent.h_addrtype = AF_INET; + ahostent.h_addr_list = ipPtrs; + + result = sceNetResolverCreate(&resolverID, buf, GetHostByNameBufferSize); + if (result < 0) + { + gsifree(buf); + return NULL; + } + // this will block until completed + result = sceNetResolverStartNtoA(resolverID, name, &ip, GSI_RESOLVER_TIMEOUT, GSI_RESOLVER_RETRY); + sceNetResolverDelete(resolverID); // delete right away, result is stored in ip + if (result < 0) + { + gsifree(buf); + return NULL; + } + ahostent.h_length = sizeof(struct in_addr); + memcpy(&ips[0], &ip, sizeof(struct in_addr)); + ahostent.h_addr_list[0] = (char*)&ips[0]; + ahostent.h_addr_list[1] = NULL; + + { + const char * out = sceNetInetInetNtop(SCE_NET_INET_AF_INET, &ip, buf, sizeof(buf)); + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Notice, + "gsSocketGetHostByName: %s = %s\r\n", name, out?out:"void"); + } + gsifree(buf); + return &ahostent; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +const char* gsSocketInetNtoa(struct in_addr in) +{ + static char buf[sizeof("XXX.XXX.XXX.XXX")]; + sceNetInetInetNtop(SCE_NET_INET_AF_INET, &in, buf, sizeof(buf)); + return buf; +} + +HOSTENT * getlocalhost(void) +{ + #define MAX_IPS 5 + static HOSTENT localhost; + static char * aliases = NULL; + static char * ipPtrs[MAX_IPS + 1]; + static unsigned int ips[MAX_IPS]; + int result = 0; + gsi_u32 addr; + + static union SceNetApctlInfo info; + + localhost.h_name = "localhost"; + localhost.h_aliases = &aliases; + localhost.h_addrtype = AF_INET; + localhost.h_length = 0; + localhost.h_addr_list = (char **)ipPtrs; + ipPtrs[0] = (char *)&ips[0]; + ipPtrs[1] = NULL; + ips[0] = 0; + + result = sceNetApctlGetInfo(SCE_NET_APCTL_INFO_IP_ADDRESS, &info); + if (result < 0) + { + printf("getlocalhost sceNetApctlGetInfo returned %d\r\n", result); + return NULL; + } + + // fill in the hostent structure + addr = inet_addr(info.ip_address); // NBO + memcpy(&ips[0], &addr, sizeof(addr)); // still NBO + localhost.h_length = (gsi_u16)sizeof(addr); + + return &localhost; +} + + +// ******* PSP AdHoc Socket Code ******************** +// See SampleAPP SDK PSP gsNetwork.c code for sample, and network start-up code +#define RXBUFLEN 1024 + +#ifdef GSI_ADHOC +void NetAdhocMacGet (char *mac) +{ + int ret = sceWlanGetEtherAddr((struct SceNetEtherAddr *)mac); +} + + +int _NetworkAdHocSocketCreate(gsi_u16 port) +{ + int ret, id; + struct SceWlanEtherAddr tmp_local; + static struct SceNetEtherAddr addr; + // Get local MAC address + ret = sceWlanGetEtherAddr(&tmp_local); + if (ret < 0) + { + // Error handling + return -1;// INVALID_SOCKET + } + memcpy(&addr, &tmp_local.addr,sizeof(struct SceNetEtherAddr)); + #ifdef _PSPNET_LOG + printf("Create Adhoc Socket: MAC:%x,%x,%x,%x,%x,%x\n",(gsi_u32)tmp_local.addr[0],(gsi_u32)tmp_local.addr[1],(gsi_u32)tmp_local.addr[2],(gsi_u32)tmp_local.addr[3],(gsi_u32)tmp_local.addr[4],(gsi_u32)tmp_local.addr[5]); + #endif + + // Set socket buffer to 8192 bytes + id = sceNetAdhocPdpCreate(&addr, port, RXBUFLEN, 0); + if (id < 0) + { + // SCE_ERROR_NET_ADHOC_INVALID_ADDR + // ToDo: Error handling + return -1;// INVALID_SOCKET + } + return id; +} + +void _NetworkAdHocSocketDestroy(int socket) +{ + int ret; + ret = sceNetAdhocPdpDelete(socket,0); + if (ret < 0) + { + // ToDo: Error handling + } +} + +int _NetworkAdHocSocketBroadcast( int socketid, + const void *data, + int len, + int flags, + gsi_u16 dest_port + ) +{ + const static gsi_u8 broadcast_addr[SCE_NET_ETHER_ADDR_LEN] = {0xff}; + int ret = sceNetAdhocPdpSend( + socketid, // id Socket ID + broadcast_addr, // daddr Destination MAC address + dest_port, // dport Destination port number + data, // data Pointer to send data + len, // len Length of send data + 10, // timeout Timeout (µsec) + flags // flag Send options + ); + + // todo: translate return value to GSI specific + return ret; +} + + +int _NetworkAdHocSocketSendTo( int socketid, + const void *data, + int len, + int flags, + const char *dest_addr, + gsi_u16 dest_port + ) +{ +#if(0) // test + const static gsi_u8 broadcast_addr[SCE_NET_ETHER_ADDR_LEN] = {0xff}; + int ret = sceNetAdhocPdpSend( + socketid, // id Socket ID + broadcast_addr, // daddr Destination MAC address + dest_port, // dport Destination port number + data, // data Pointer to send data + len, // len Length of send data + 10, // timeout Timeout (µsec) + flags // flag Send options + ); +#else + + int ret = sceNetAdhocPdpSend( + socketid, // id Socket ID + dest_addr, // daddr Destination MAC address + dest_port, // dport Destination port number + data, // data Pointer to send data + len, // len Length of send data + 10, // timeout Timeout (µsec) + flags // flag Send options + ); +#endif + #ifdef _PSPNET_LOG + printf("AdHoc SendTo: MAC:%x,%x,%x,%x,%x,%x port=%d Len=%d\n",(gsi_u32)dest_addr[0],(gsi_u32)dest_addr[1],(gsi_u32)dest_addr[2],(gsi_u32)dest_addr[3],(gsi_u32)dest_addr[4],(gsi_u32)dest_addr[5],dest_port,len); + #endif + // todo: translate return value to GSI specific + return ret; +} + +// return 0 if no data, -1 if error, >0 if data to read +int _NetworkAdHocCanReceiveOnSocket(int socket_id) +{ + int ret; + struct SceNetAdhocPdpStat stat; + int stat_buflen; + + /* + // get SceNetAdhocPdpStat + next Pointer to next entry in list (NULL indicates end) + id Socket ID + laddr Local address + lport Local port number + rcv_sb_cc Size of data in receive buffer + */ + + stat_buflen = sizeof(struct SceNetAdhocPdpStat); + ret = sceNetAdhocGetPdpStat(&stat_buflen, (void *)&stat); + if (ret < 0) + { + // Error Condition + + if(ret == SCE_ERROR_NET_ADHOC_INVALID_ARG) + { + printf("sceNetAdhocGetPdpStat() failed. SCE_ERROR_NET_ADHOC_INVALID_ARG ret = 0x%x\n", ret); + } + else + if(ret == SCE_ERROR_NET_ADHOC_NOT_INITIALIZED) + { + printf("sceNetAdhocGetPdpStat() failed. SCE_ERROR_NET_ADHOC_NOT_INITIALIZED ret = 0x%x\n", ret); + } + else + { + printf("sceNetAdhocGetPdpStat() failed. ret = 0x%x\n", ret); + } + return -1; + } + + if (stat_buflen == 0 || stat.id != socket_id) + { + printf("sceNetAdhocGetPdpStat() invalid data.\n"); + return 0; + } + + return stat.rcv_sb_cc; // valid date to read +} + +// return length if successful +// <=0 on error +int _NetworkAdHocSocketRecv(int socket_id, + char *buf, + int bufferlen, + int flags, + char *saddr, //struct SceNetEtherAddr = char[6]; + u_int16 *sport + ) +{ + int ret = sceNetAdhocPdpRecv( + socket_id, // id Socket ID + saddr, // saddr Sender’s MAC address + sport, // sport Sender port number + buf, // buf Pointer to receive buffer + &bufferlen, // len Receive buffer size (IN), receive data length (OUT) + 0, // timeout Timeout (µsec) + SCE_NET_ADHOC_F_NONBLOCK// flag Receive options + ); + + // translate return values to gsi standard + if (ret < 0) + { + return ret; + } + if (ret == (int)SCE_ERROR_NET_ADHOC_WOULD_BLOCK) + { + // no data, just return + return SCE_OK; + } + #ifdef _PSPNET_LOG + if(ret >= 0) + printf("AdHoc Recv From: MAC:%x,%x,%x,%x,%x,%x: port=%d len:%d\n",saddr[0],saddr[1],saddr[2],saddr[3],saddr[4],saddr[5],*sport,bufferlen); + #endif + + + return bufferlen; +} +#endif + diff --git a/code/gamespy/common/psp/gsUtilPSP.c b/code/gamespy/common/psp/gsUtilPSP.c new file mode 100644 index 00000000..4363a51a --- /dev/null +++ b/code/gamespy/common/psp/gsUtilPSP.c @@ -0,0 +1,275 @@ +#if defined(_PSP) +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gsCommon.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if !defined(GSI_NO_THREADS) + +static void gsiResolveHostnameThread(void * arg) +{ + int result = 0; + int resolverID = 0; + char buf[1024]; // PSP documentation recommends 1024 + in_addr addr; + GSIResolveHostnameHandle * info = (GSIResolveHostnameHandle)arg; + + result = sceNetResolverCreate(&resolverID, buf, sizeof(buf)); + if (result < 0) + { + // failed to create resolver, did you call sceNetResolverInit() ? + info->ip = GSI_ERROR_RESOLVING_HOSTNAME; + return -1; + } + else + { + // this will block until completed + result = sceNetResolverStartNtoA(resolverID, info->hostname, &addr, &info->ip, GSI_RESOLVER_TIMEOUT, GSI_RESOLVER_RETRY); + if (result < 0) + info->ip = GSI_ERROR_RESOLVING_HOSTNAME; + sceNetResolverDelete(resolverID); + } +} + +int gsiStartResolvingHostname(const char * hostname, GSIResolveHostnameHandle * handle) +{ + GSIResolveHostnameInfo * info; + + // allocate a handle + info = (GSIResolveHostnameInfo *)gsimalloc(sizeof(GSIResolveHostnameInfo)); + if(!info) + return -1; + + // make a copy of the hostname so the thread has access to it + info->hostname = goastrdup(hostname); + if(!info->hostname) + { + gsifree(info); + return -1; + } + + // not resolved yet + info->finishedResolving = 0; + + // start the thread + if(gsiStartThread(gsiResolveHostnameThread, (0x1000), info, &info->threadID) == -1) + { + gsifree(info->hostname); + gsifree(info); + return -1; + } + + // set the handle to the info + *handle = info; + + return 0; +} + +void gsiCancelResolvingHostname(GSIResolveHostnameHandle handle) +{ + if (0 == handle->finishedResolving) + { + sceNetResolverStop(handle->resolverID); // safe to call from separate thread + gsiCancelThread(handle->threadID); + } +} + +unsigned int gsiGetResolvedIP(GSIResolveHostnameHandle handle) +{ + return handle->ip; +} + +#endif // (GSI_NO_THREADS) + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(UNIQUEID) + +static const char * GetMAC(void) +{ + static struct SceNetEtherAddr mac; + int result = 0; + + result = sceNetGetLocalEtherAddr(&mac); + if (result == 0) + return (char *)&mac.data; + else + return NULL; +} + +const char * GOAGetUniqueID_Internal(void) +{ + static char keyval[17]; + const char * MAC; + + // check if we already have the Unique ID + if(keyval[0]) + return keyval; + + // get the MAC + MAC = GetMAC(); + if(!MAC) + { + // error getting the MAC + static char errorMAC[6] = { 1, 2, 3, 4, 5, 6 }; + MAC = errorMAC; + } + + // format it + sprintf(keyval, "%02X%02X%02X%02X%02X%02X0000", + MAC[0] & 0xFF, + MAC[1] & 0xFF, + MAC[2] & 0xFF, + MAC[3] & 0xFF, + MAC[4] & 0xFF, + MAC[5] & 0xFF); + + return keyval; +} + +#endif + +//NEED These again since MAX INTEGRAL BITS is not defined +#define GSI_MIN_I64 LONG_LONG_MIN +#define GSI_MAX_I64 LONG_LONG_MAX +#define GSI_MAX_U64 ULONG_LONG_MAX +/* flag values */ +#define FL_UNSIGNED 1 /* strtouq called */ +#define FL_NEG 2 /* negative sign found */ +#define FL_OVERFLOW 4 /* overflow occured */ +#define FL_READDIGIT 8 /* we've read at least one correct digit */ + +gsi_i64 gsiStringToInt64(const char *theNumberStr) +{ + const char *p; + char c; + gsi_i64 number; + unsigned digval; + gsi_u64 maxval; + int flags = 0; + // Added for compatibility reasons + int ibase = 10; + + p = theNumberStr; // p is our scanning pointer + number = 0; // start with zero + + // read char + c = *p++; + + // skip whitespace + while ( isspace(c) ) + c = *p++; + + + if (c == '-') { + flags |= FL_NEG; // remember minus sign + c = *p++; + } + // skip sign + else if (c == '+') + c = *p++; + + if (ibase == 0) + { + // determine base free-lance, based on first two chars of + // string + if (c != '0') + ibase = 10; + else if (*p == 'x' || *p == 'X') + ibase = 16; + else + ibase = 8; + } + + if (ibase == 16) + { + // we might have 0x in front of number; remove if there + if (c == '0' && (*p == 'x' || *p == 'X')) { + ++p; + c = *p++; /* advance past prefix */ + } + } + + // if our number exceeds this, we will overflow on multiply + maxval = GSI_MAX_U64 / ibase; + + + // exit in middle of loop + for (;;) + { + // convert c to value + if ( isdigit(c) ) + digval = c - '0'; + else if ( isalpha(c) ) + digval = toupper(c) - 'A' + 10; + else + break; + + // exit loop if bad digit found + if (digval >= (unsigned)ibase) + break; + + /* record the fact we have read one digit */ + flags |= FL_READDIGIT; + + // we now need to compute number = number * base + digval, + // but we need to know if overflow occurred. This requires + // a tricky pre-check. + + if (number < maxval || (number == maxval && + (gsi_u64)digval <= GSI_MAX_U64 % ibase)) + { + // we won't overflow, go ahead and multiply + number = number * ibase + digval; + } + else + { + // we have overflowed, set the flag + flags |= FL_OVERFLOW; + break; + } + + c = *p++; /* read next digit */ + } + + --p; /* point to place that stopped scan */ + + if (!(flags & FL_READDIGIT)) { + /* no number there; return 0 and point to beginning of + string */ + number = 0L; /* return 0 */ + } + else if ( (flags & FL_OVERFLOW) || + ( !(flags & FL_UNSIGNED) && + ( ( (flags & FL_NEG) && (number > GSI_MIN_I64) ) || + ( !(flags & FL_NEG) && (number > GSI_MAX_I64) ) ) ) ) + { + /* overflow or signed overflow occurred */ + errno = ERANGE; + if ( flags & FL_NEG ) + number = GSI_MIN_I64; + else + number = GSI_MAX_I64; + } + + if (flags & FL_NEG) + /* negate result if there was a neg sign */ + number = -number; + + return number; /* done. */ +} + +void gsiInt64ToString(char theNumberStr[33], gsi_i64 theNumber) +{ + // you want to fit the number! + // give me a valid string! + GS_ASSERT(theNumberStr != NULL); + + sprintf(theNumberStr, "%lld", theNumber); +} +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // _PSP only diff --git a/code/gamespy/common/psp/pspcommon.c b/code/gamespy/common/psp/pspcommon.c new file mode 100644 index 00000000..3015b591 --- /dev/null +++ b/code/gamespy/common/psp/pspcommon.c @@ -0,0 +1,416 @@ +#if defined(_PSP) +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gsCommon.h" + +#include +#include +#include + +/******** THIS FILE IS FOR USE BY THE GAMESPY SAMPLE APPLICATIONS ********/ + +// Portions taken from Sony PSP http_get sample have been modified to suite the +// sample/demonstration of the Gamespy SDKs +// This code is not intended to be used in a shipping title +// (e.g. You should have your own network setup code which supports other configurations) + + +// Code modified from http_get used to export information about this module +SCE_MODULE_INFO(PspCommon, 0, 1, 1); + +// Paths setup for specific modules and libraries used by the Tool +#define DEVKIT_PATH "host0:/usr/local/devkit/" +#define MODULE_PATH DEVKIT_PATH "module/" +#define PSPNET_AP_DIALOG_DUMMY_PRX MODULE_PATH "pspnet_ap_dialog_dummy.prx" + +// Memory pool size for network stack and other stacks +#define PSPNET_POOLSIZE (256 * 1024) +#define CALLOUT_TPL 32 +#define NETINTR_TPL 32 +#define SCE_APCTL_HANDLER_STACKSIZE (1024 * 1) +#define SCE_APCTL_STACKSIZE (SCE_NET_APCTL_LEAST_STACK_SIZE + SCE_APCTL_HANDLER_STACKSIZE) +#define SCE_APCTL_PRIO 40 + +#define AP_DIALOG_DUMMY_WAIT_TIME (1000 * 1000) + +#define PSPNET_APCTLHDLR 0x80 +#define PSPNET_APDUMDLG_STARTED 0x40 +#define PSPNET_CONNECTED 0x20 +// If necessary, use this to select a specific router by SSID. +#ifndef AUTO_SELECT_ROUTER +#define YOUR_WIRELESS_ROUTER_NAME "PubServ DLink" +#endif + +// function prototypes +int gsiPspLoadRequiredModules(void); +int gsiPspUnloadRequiredModules(void); + +// static vars used for disconnect warnings and setting up network connection +static int gDisconnected = 0; +static struct SceNetApDialogDummyParam gApDialogDummyParam; + +// Globals +// the sce kernel detects this global variable and sets the heapsize from it +// see devkit\src\crt0\kernel_bridge.c(232-253) +int sce_newlib_heap_kb_size = 1000; +SceUID gPspnetApDialogDummyModid = 0; +int gPspnetApctlHandlerId = -1; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Used to start needed kernel model +static SceUID gsiPspLoadModule(const char *path) +{ + SceUID modid = 0; + int ret = 0, mresult; + + ret = sceKernelLoadModule(path, 0, NULL); + if(ret < 0) + { + printf("sceKernelLoadModule() failed. ret = 0x%x\n", ret); + return ret; + } + modid = ret; + + ret = sceKernelStartModule(modid, 0, 0, &mresult, NULL); + if(ret < 0) + { + printf("sceKernelStartModule() failed. ret = 0x%x\n", ret); + ret = sceKernelUnloadModule(modid); + if (ret < 0) + printf("sceKernelUnloadModule() failed. ret = 0x%x\n", ret); + return ret; + } + ret = modid; + + return ret; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Used to stop kernel model +static int gsiPspUnloadModule(SceUID modid) +{ + int ret = 0; + + ret = sceKernelStopModule(modid, 0, NULL, NULL, NULL); + if (ret < 0) + { + printf("sceKernelStopModule() failed. ret = 0x%x\n", ret); + return ret; + } + + ret = sceKernelUnloadModule(modid); + if (ret < 0) + { + printf("sceKernelUnloadModule() failed. ret = 0x%x\n", ret); + return ret; + } + + return ret; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// used to start up network library modules +int gsiPspLoadRequiredModules() +{ + int ret = 0; + + ret = sceUtilityLoadModule(SCE_UTILITY_MODULE_NET_COMMON); + if(ret < 0) + { + printf("sceUtilityLoadModule(SCE_UTILITY_MODULE_NET_COMMON) failed. ret = 0x%x\n", ret); + gsiPspUnloadRequiredModules(); + return ret; + } + + ret = sceUtilityLoadModule(SCE_UTILITY_MODULE_NET_INET); + if(ret < 0) + { + printf("sceUtilityLoadModule(SCE_UTILITY_MODULE_NET_INET) failed. ret = 0x%x\n", ret); + gsiPspUnloadRequiredModules(); + return ret; + } + + ret = gsiPspLoadModule(PSPNET_AP_DIALOG_DUMMY_PRX); + if(ret < 0) + { + printf("load_module %s failed. ret = 0x%x\n",PSPNET_AP_DIALOG_DUMMY_PRX, ret); + gsiPspUnloadRequiredModules(); + return ret; + } + gPspnetApDialogDummyModid = ret; + + return ret; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Used to shutdown network library modules +int gsiPspUnloadRequiredModules() +{ + int ret = 0; + + + ret = gsiPspUnloadModule(gPspnetApDialogDummyModid); + if (ret < 0) + { + printf("unload_module() failed. ret = 0x%x\n", ret); + return ret; + } + + ret = sceUtilityUnloadModule(SCE_UTILITY_MODULE_NET_INET); + if (ret < 0) + { + printf("sceUtilityUnloadModule(SCE_UTILITY_MODULE_NET_INET) failed. ret = 0x%x\n", ret); + } + + ret = sceUtilityUnloadModule(SCE_UTILITY_MODULE_NET_COMMON); + if (ret < 0) + { + printf("sceUtilityUnloadModule(SCE_UTILITY_MODULE_NET_COMMON) failed. ret = 0x%x\n", ret); + } + return ret; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// AP network connection handler used to detect if psp was disconnected +void gsiPspApctlHandler(int prev_state, int new_state, int event, int error_code, void *arg) +{ + (void)arg; + + if(new_state == SCE_NET_APCTL_STATE_Disconnected) + { + if(event == SCE_NET_APCTL_EVENT_DISCONNECT_REQ) + gDisconnected = 1; + if(prev_state == SCE_NET_APCTL_STATE_IPObtained && + event == SCE_NET_APCTL_EVENT_ERROR) + printf("Apctl error happened, error = 0x%x\n", error_code); + } +} + +int gsiPspnetDisconnect() +{ + int ret; + + gDisconnected = 0; + + ret = sceNetApctlDisconnect(); + if(ret < 0) + { + printf("sceNetApctlDisconnect() failed. ret = 0x%x\n", ret); + return ret; + } + + while(gDisconnected == 0) + sceKernelDelayThread(AP_DIALOG_DUMMY_WAIT_TIME); + + sceNetApDialogDummyTerm(); + if (gPspnetApctlHandlerId >= 0) + { + sceNetApctlDelHandler(gPspnetApctlHandlerId); + } + + return ret; +} + +void gsiPspnetStop() +{ + // turn off all services if they are available + sceNetApctlTerm(); + + sceNetResolverTerm(); + + sceNetInetTerm(); + + sceNetTerm(); +} + +// Error Handler used to stop network libraries in case of failure +void gsiPspnetErrorHandler(int shutDownServices) +{ + + if (shutDownServices & PSPNET_CONNECTED) + gsiPspnetDisconnect(); + + if (shutDownServices & PSPNET_APDUMDLG_STARTED) + sceNetApDialogDummyTerm(); + + if (shutDownServices & PSPNET_APCTLHDLR) + if(gPspnetApctlHandlerId >= 0) + sceNetApctlDelHandler(gPspnetApctlHandlerId); + + gsiPspnetStop(); +} + +// +int gsiPspStartNetworkModules() +{ + struct SceNetApDialogDummyStateInfo ap_dialog_dummy_state; + union SceNetApctlInfo apctl_info; + int ret; + // error bit starts at 1 and is shifted left with one added + int serviceToShutDown = 0; + + ret = sceNetInit(PSPNET_POOLSIZE, CALLOUT_TPL, 0, + NETINTR_TPL, 0); + if(ret < 0) + { + printf("sceNetInit() failed. ret = 0x%x\n", ret); + return ret; + } + + + ret = sceNetInetInit(); + if(ret < 0) + { + printf("sceNetInetInit() failed. ret = 0x%x\n", ret); + gsiPspnetStop(); + return ret; + } + + + ret = sceNetResolverInit(); + if(ret < 0) + { + printf("sceNetResolverInit() failed. ret = 0x%x\n", ret); + gsiPspnetStop(); + return ret; + } + + + ret = sceNetApctlInit(SCE_APCTL_STACKSIZE, SCE_APCTL_PRIO); + if(ret < 0) + { + printf("sceNetApctlInit() failed. ret = 0x%x\n", ret); + gsiPspnetStop(); + return ret; + } + + + ret = sceNetApctlAddHandler(gsiPspApctlHandler, NULL); + if(ret < 0) + { + printf("sceNetApctlAddHandler() failed. ret = 0x%x\n", ret); + gsiPspnetStop(); + return ret; + } + gPspnetApctlHandlerId = ret; + + serviceToShutDown = serviceToShutDown | PSPNET_APCTLHDLR; + ret = sceNetApDialogDummyInit(); + if(ret < 0) + { + printf("sceNetApDialogDummyInit() failed. ret = 0x%x\n", ret); + gsiPspnetErrorHandler(serviceToShutDown); // called with 31 + return ret; + } + + serviceToShutDown = serviceToShutDown | PSPNET_APDUMDLG_STARTED; + /* check Wireless LAN switch */ + ret = sceWlanGetSwitchState(); + if(ret == SCE_WLAN_SWITCH_STATE_OFF){ + printf("Wireless LAN switch has been turned off.\n"); + gsiPspnetErrorHandler(serviceToShutDown); // called with 63 + return ret; + } + + + memset(&gApDialogDummyParam, 0, sizeof(gApDialogDummyParam)); +#ifndef AUTO_SELECT_ROUTER + strcpy(gApDialogDummyParam.ssid, YOUR_WIRELESS_ROUTER_NAME); +#endif + ret = sceNetApDialogDummyConnect(&gApDialogDummyParam); + if(ret < 0) + { + printf("sceNetApDialogDummyConnect() failed. ret = 0x%x\n", ret); + gsiPspnetErrorHandler(serviceToShutDown); // called with 63 + return ret; + } + + while(1){ + ret = sceNetApDialogDummyGetState(&ap_dialog_dummy_state); + if(ret == 0){ + if(ap_dialog_dummy_state.state == SceNetApDialogDummyState_Connected || + ap_dialog_dummy_state.state == SceNetApDialogDummyState_Disconnected) + break; + } + sceKernelDelayThread(AP_DIALOG_DUMMY_WAIT_TIME); + } + + if(ap_dialog_dummy_state.state == SceNetApDialogDummyState_Disconnected) + { + printf("failed to Join or Get IP addr. error = 0x%x\n", ap_dialog_dummy_state.error_code); + gsiPspnetErrorHandler(serviceToShutDown); // called with 63 + return ap_dialog_dummy_state.error_code; + } + + serviceToShutDown = serviceToShutDown | PSPNET_CONNECTED; + ret = sceNetApctlGetInfo(SCE_NET_APCTL_INFO_IP_ADDRESS, &apctl_info); + if(ret < 0) + { + printf("sceNetApctlGetInfo() failed. ret = 0x%x\n", ret); + gsiPspnetErrorHandler(serviceToShutDown); // called with 127 + return ret; + } + printf("obtained IP addr: %s\n", apctl_info.ip_address); + return ret; +} + +// sample entry point +extern int test_main(int argc, char ** argp); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Common PSP entry point +int main(int argc, char** argp) +{ + int ret = 0; + + + // Load the modules required to do basic TCP/IP networking + ret = gsiPspLoadRequiredModules(); + if(ret < 0) + { + printf("gsiPspLoadRequiredModules failed. See previous return value. return = 0x%x\n", + ret); + return SCE_KERNEL_EXIT_SUCCESS; + } + + + // Start up PSPNET libraries + ret = gsiPspStartNetworkModules(); + if (ret < 0) + { + printf("gsiPspStartNetworkModules failed See previous return value. return = 0x%x\n", ret); + return ret; + } + + + /////////////////////////////////// + // Call the application entry point + ret = test_main(argc, argp); + + ret = gsiPspnetDisconnect(); + if (ret < 0) + { + printf("gsiPspnetDisconnect failed See previous return value. return = 0x%x\n", ret); + return ret; + } + + // Shut down the PSPNET network library + gsiPspnetStop(); + + ret = gsiPspUnloadRequiredModules(); + if(ret < 0) + return SCE_KERNEL_EXIT_SUCCESS; + + return SCE_KERNEL_EXIT_SUCCESS; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // _PSP only diff --git a/code/gamespy/common/revolution/gsSocketRevolution.c b/code/gamespy/common/revolution/gsSocketRevolution.c new file mode 100644 index 00000000..0b587ea2 --- /dev/null +++ b/code/gamespy/common/revolution/gsSocketRevolution.c @@ -0,0 +1,197 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(_REVOLUTION) + +// include the revolution socket header +#include "../gsPlatform.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// static variables +static int GSIRevolutionErrno; + +// prototypes of static functions +static int CheckRcode(int rcode, int errCode); + +#define REVOlUTION_SOCKET_ERROR -1 + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// parses rcode into a generic -1 error if an error has occured +static int CheckRcode(int rcode, int errCode) +{ + if(rcode >= 0) + return rcode; + GSIRevolutionErrno = rcode; + return errCode; +} + +int socket(int pf, int type, int protocol) +{ + int rcode = SOSocket(pf, type, 0); + GSI_UNUSED(protocol); + return CheckRcode(rcode, INVALID_SOCKET); +} +int closesocket(SOCKET sock) +{ + int rcode = SOClose(sock); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +int shutdown(SOCKET sock, int how) +{ + int rcode = SOShutdown(sock, how); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +int bind(SOCKET sock, const SOCKADDR* addr, int len) +{ + SOCKADDR localAddr; + int rcode; + + // with Revolution, don't bind to 0, just start using the port + if(((const SOCKADDR_IN*)addr)->port == 0) + return 0; + + memcpy(&localAddr, addr, sizeof(SOCKADDR)); + localAddr.len = (u8)len; + + rcode = SOBind(sock, &localAddr); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} + +int connect(SOCKET sock, const SOCKADDR* addr, int len) +{ + SOCKADDR remoteAddr; + int rcode; + + memcpy(&remoteAddr, addr, sizeof(SOCKADDR)); + remoteAddr.len = (u8)len; + + rcode = SOConnect(sock, &remoteAddr); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +int listen(SOCKET sock, int backlog) +{ + int rcode = SOListen(sock, backlog); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +SOCKET accept(SOCKET sock, SOCKADDR* addr, int* len) +{ + int rcode; + addr->len = (u8)*len; + rcode = SOAccept(sock, addr); + *len = addr->len; + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} + +int recv(SOCKET sock, char* buf, int len, int flags) +{ + int rcode = SORecv(sock, buf, len, flags); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +int recvfrom(SOCKET sock, char* buf, int len, int flags, SOCKADDR* addr, int* fromlen) +{ + int rcode; + addr->len = (u8)*fromlen; + rcode = SORecvFrom(sock, buf, len, flags, addr); + *fromlen = addr->len; + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +SOCKET send(SOCKET sock, const char* buf, int len, int flags) +{ + int rcode = SOSend(sock, buf, len, flags); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +SOCKET sendto(SOCKET sock, const char* buf, int len, int flags, const SOCKADDR* addr, int tolen) +{ + SOCKADDR remoteAddr; + int rcode; + + memcpy(&remoteAddr, addr, sizeof(SOCKADDR)); + remoteAddr.len = (u8)tolen; + + rcode = SOSendTo(sock, buf, len, flags, &remoteAddr); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} + +int getsockopt(SOCKET sock, int level, int optname, char* optval, int* optlen) +{ + int rcode = SOGetSockOpt(sock, level, optname, optval, optlen); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +SOCKET setsockopt(SOCKET sock, int level, int optname, const char* optval, int optlen) +{ + int rcode = SOSetSockOpt(sock, level, optname, optval, optlen); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} + +int getsockname(SOCKET sock, SOCKADDR* addr, int* len) +{ + int rcode; + addr->len = (u8)*len; + rcode = SOGetSockName(sock, addr); + *len = addr->len; + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} + +unsigned long inet_addr(const char* name) +{ + int rcode; + SOInAddr addr; + rcode = SOInetAtoN(name, &addr); + if(rcode == FALSE) + return INADDR_NONE; + return addr.addr; +} + +int GOAGetLastError(SOCKET sock) +{ + GSI_UNUSED(sock); + return GSIRevolutionErrno; +} + +int GSISocketSelect(SOCKET theSocket, int* theReadFlag, int* theWriteFlag, int* theExceptFlag) +{ + SOPollFD pollFD; + int rcode; + + pollFD.fd = theSocket; + pollFD.events = 0; + if(theReadFlag != NULL) + pollFD.events |= SO_POLLRDNORM; + if(theWriteFlag != NULL) + pollFD.events |= SO_POLLWRNORM; + pollFD.revents = 0; + + rcode = SOPoll(&pollFD, 1, 0); + if(rcode < 0) + return REVOlUTION_SOCKET_ERROR; + + if(theReadFlag != NULL) + { + if((rcode > 0) && (pollFD.revents & (SO_POLLRDNORM|SO_POLLHUP))) + *theReadFlag = 1; + else + *theReadFlag = 0; + } + if(theWriteFlag != NULL) + { + if((rcode > 0) && (pollFD.revents & SO_POLLWRNORM)) + *theWriteFlag = 1; + else + *theWriteFlag = 0; + } + if(theExceptFlag != NULL) + { + if((rcode > 0) && (pollFD.revents & SO_POLLERR)) + *theExceptFlag = 1; + else + *theExceptFlag = 0; + } + return rcode; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // _REVOLUTION \ No newline at end of file diff --git a/code/gamespy/common/revolution/gsThreadRevoulution.c b/code/gamespy/common/revolution/gsThreadRevoulution.c new file mode 100644 index 00000000..9a9808d9 --- /dev/null +++ b/code/gamespy/common/revolution/gsThreadRevoulution.c @@ -0,0 +1,183 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gsCommon.h" + + +// Begin of Threading for Revolution +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u32 gsiInterlockedIncrement(gsi_u32 * value) +{ + BOOL enabled = OSDisableInterrupts(); + + gsi_u32 ret = ++(*value); + OSRestoreInterrupts(enabled); + + // return "ret" rather than "value" here b/c + // value may be modified by another thread + // before we can return it + return ret; +} + +gsi_u32 gsiInterlockedDecrement(gsi_u32 * value) +{ + BOOL state = OSDisableInterrupts(); + gsi_u32 ret = --(*value); + OSRestoreInterrupts(state); + + // return "ret" rather than "value" here b/c + // value may be modified by another thread + // before we can return it + return ret; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +int gsiStartThread(GSThreadFunc aThreadFunc, gsi_u32 theStackSize, void *arg, GSIThreadID* theThreadIdOut) +{ + char *aStackBase; + if(theStackSize % 32 != 0) + { + OSRoundUp32B(theStackSize); + } + + theThreadIdOut->mStack = gsimalloc(theStackSize); + aStackBase = (char *)theThreadIdOut->mStack; + aStackBase += theStackSize; + + OSCreateThread(&theThreadIdOut->mThread, aThreadFunc, arg, (void *)aStackBase, + theStackSize, 16, OS_THREAD_ATTR_DETACH); + + OSResumeThread(&theThreadIdOut->mThread); + + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiCancelThread(GSIThreadID theThreadID) +{ + OSCancelThread(&theThreadID.mThread); + if(theThreadID.mStack) + { + gsifree(theThreadID.mStack); + theThreadID.mStack = NULL; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiCleanupThread(GSIThreadID theThreadID) +{ + + if (!OSIsThreadTerminated(&theThreadID.mThread)) + OSCancelThread(&theThreadID.mThread); + if(theThreadID.mStack) + { + gsifree(theThreadID.mStack); + theThreadID.mStack = NULL; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u32 gsiHasThreadShutdown(GSIThreadID theThreadID) +{ + BOOL shutdown = OSIsThreadTerminated(&theThreadID.mThread); + + if(shutdown == TRUE) + return 1; + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiInitializeCriticalSection(GSICriticalSection *theCrit) +{ + OSInitMutex(theCrit); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiEnterCriticalSection(GSICriticalSection *theCrit) +{ + OSLockMutex(theCrit); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiLeaveCriticalSection(GSICriticalSection *theCrit) +{ + OSUnlockMutex(theCrit); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiDeleteCriticalSection(GSICriticalSection *theCrit) +{ + GSI_UNUSED(theCrit); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +GSISemaphoreID gsiCreateSemaphore(gsi_i32 theInitialCount, gsi_i32 theMaxCount, char* theName) +{ + GSISemaphoreID semaphore; + + OSInitSemaphore(&semaphore, theInitialCount); + + GSI_UNUSED(theName); + GSI_UNUSED(theMaxCount); + + return semaphore; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u32 gsiWaitForSemaphore(GSISemaphoreID theSemaphore, gsi_u32 theTimeoutMs) +{ + gsi_u32 retval = (unsigned int)OSWaitSemaphore(&theSemaphore); + GSI_UNUSED(theTimeoutMs); + return retval; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiReleaseSemaphore(GSISemaphoreID theSemaphore, gsi_i32 theReleaseCount) +{ + OSSignalSemaphore(&theSemaphore); + GSI_UNUSED(theReleaseCount); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiCloseSemaphore(GSISemaphoreID theSemaphore) +{ + GSI_UNUSED(theSemaphore); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiExitThread(GSIThreadID theThreadID) +{ + GSI_UNUSED(theThreadID); +} + + + +// End of Threading for Revolution +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/code/gamespy/common/revolution/gsUtilRevolution.c b/code/gamespy/common/revolution/gsUtilRevolution.c new file mode 100644 index 00000000..d0a34000 --- /dev/null +++ b/code/gamespy/common/revolution/gsUtilRevolution.c @@ -0,0 +1,108 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gsCommon.h" +#include "../gsPlatformUtil.h" + +void gsiRevolutionSleep(u32 msec); +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static OSAlarm gAlarm; +static OSThreadQueue gQueue; +static BOOL gQueueInitialized; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static const char * GOAGetUniqueID_Internal(void) +{ + static char keyval[17]; + u8 aMac[ETH_ALEN]; + int aMacLen; + + // check if we already have the Unique ID + if(keyval[0]) + return keyval; + + aMacLen = ETH_ALEN; + SOGetInterfaceOpt (NULL, SO_SOL_CONFIG, SO_CONFIG_MAC_ADDRESS, + aMac, &aMacLen); + + // format it + sprintf(keyval, "%02X%02X%02X%02X%02X%02X0000", + aMac[0] & 0xFF, + aMac[1] & 0xFF, + aMac[2] & 0xFF, + aMac[3] & 0xFF, + aMac[4] & 0xFF, + aMac[5] & 0xFF); + + return keyval; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Time Functions +static char GSIMonthNames[12][3] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; +static char GSIWeekDayNames[7][3] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + +time_t gsiTimeInSec(time_t *timer) +{ + time_t t = 0; + t = (gsi_i32)OSTicksToSeconds(OSGetTime()); + + if (timer) + *timer = t; + + return t; +} + + +struct tm *gsiGetGmTime(time_t *theTime) +{ + static struct tm aTimeStruct; + static struct tm *aRetVal = &aTimeStruct; + OSCalendarTime aCalTimeStruct; + + OSTicksToCalendarTime(*theTime, &aCalTimeStruct); + + aRetVal->tm_sec = aCalTimeStruct.sec; + aRetVal->tm_min = aCalTimeStruct.min; + aRetVal->tm_hour = aCalTimeStruct.hour; + aRetVal->tm_mday = aCalTimeStruct.mday; + aRetVal->tm_mon = aCalTimeStruct.mon; + aRetVal->tm_year = aCalTimeStruct.year - 1900; + aRetVal->tm_wday = aCalTimeStruct.wday; + aRetVal->tm_yday = 0; + aRetVal->tm_isdst = 0; + return aRetVal; +} + +char *gsiCTime(time_t *theTime) +{ + static char str[26]; + struct tm *ptm = gsiGetGmTime(theTime); + + // e.g.: "Wed Jan 02 02:03:55 1980\n\0" + sprintf(str, "%s %s %02d %02d:%02d:%02d %d\n", + GSIWeekDayNames[ptm->tm_wday], + GSIMonthNames[ptm->tm_mon], ptm->tm_mday, + ptm->tm_hour, ptm->tm_min, ptm->tm_sec, + ptm->tm_year + 1900); + + return str; +} + +gsi_i64 gsiStringToInt64(const char *theNumberStr) +{ + return atoll(theNumberStr); +} + +void gsiInt64ToString(char theNumberStr[33], gsi_i64 theNumber) +{ + // you want to fit the number! + // give me a valid string! + GS_ASSERT(theNumberStr != NULL); + + sprintf(theNumberStr, "%lld", theNumber); +} \ No newline at end of file diff --git a/code/gamespy/common/revolution/revolutionCommon.c b/code/gamespy/common/revolution/revolutionCommon.c new file mode 100644 index 00000000..221f849b --- /dev/null +++ b/code/gamespy/common/revolution/revolutionCommon.c @@ -0,0 +1,240 @@ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// (C) Gamespy Industries +// +// Commonly shared network startup code +// WARNING: Please do not use this code as a basis for +// Game Network startup code. This is only for testing +// purposes. +// +// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +#include "../gsCommon.h" +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +static NCDIfConfig gIfConfig; +static NCDIpConfig gIpConfig; +static OSMutex gOsMemMutex; +static MEMHeapHandle heapHandleSocket = NULL; + +#define SOCKET_HEAPSIZE_DEFAULT (1024*128) + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +static void SetDefaultIfConfig( NCDIfConfig* theIfConfig ) +{ + (void)memset( theIfConfig, 0, sizeof( NCDIfConfig ) ); + + theIfConfig->selectedMedia = NCD_IF_SELECT_WIRED; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +static void SetDefaultIpConfig( NCDIpConfig* theIpConfig ) +{ + + (void)memset( theIpConfig, 0, sizeof( NCDIpConfig ) ); + + theIpConfig->useDhcp = TRUE; + + theIpConfig->adjust.maxTransferUnit = 1300; // Value can be 1460 depending on network library + theIpConfig->adjust.tcpRetransTimeout = 100; + theIpConfig->adjust.dhcpRetransCount = 4; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Memory functions used by the network library + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +static void* _Alloc(unsigned long theName, long theSize) +{ + void* a_memory_p = NULL; + + (void)theName; + + if (0 < theSize) + { + //OSLockMutex(&gOsMemMutex); + //a_memory_p = OSAlloc((u32)theSize); + //OSUnlockMutex(&gOsMemMutex); + + // 02OCT07 BED: MEM2 was initialized with MEM_HEAP_OPT_THREAD_SAFE + // so we don't need to manually lock it + a_memory_p = MEMAllocFromExpHeapEx( heapHandleSocket, (u32) theSize, 32 ); + } + + return a_memory_p; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +static void _Free(unsigned long theName, void* theMemoryPointer, long theSize) +{ + GSI_UNUSED(theName); + + if (theMemoryPointer && 0 < theSize) + { + //OSLockMutex(&gOsMemMutex); + //OSFree(theMemoryPointer); + //OSUnlockMutex(&gOsMemMutex); + + // 02OCT07 BED: MEM2 was initialized with MEM_HEAP_OPT_THREAD_SAFE + // so we don't need to manually lock it + MEMFreeToExpHeap( heapHandleSocket, theMemoryPointer); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// StartNetwork +// +BOOL StartNetwork() +{ + s32 rc; + + OSInitMutex(&gOsMemMutex); + + // Start up the network interface USB device + SetDefaultIfConfig( &gIfConfig ); + OSReport( "NCDSetIfConfig() ..." ); + rc = NCDSetIfConfig( &gIfConfig ); + if( rc != NCD_RESULT_SUCCESS ) + { + OSReport( "failed (%d)\n", rc ); + return FALSE; + } + OSReport( "success\n" ); + + + SetDefaultIpConfig( &gIpConfig ); + OSReport( "NCDSetIpConfig() ..." ); + rc = NCDSetIpConfig( &gIpConfig ); + if( rc != NCD_RESULT_SUCCESS ) + { + OSReport( "failed (%d)\n", rc ); + return FALSE; + } + OSReport( "success\n" ); + + + // Waiting for the network interface to come with the + // configuration previously set. + OSReport( "NCDIsInterfaceDecided() " ); + while( NCDIsInterfaceDecided() == FALSE ) + { + NCDSleep( OSMillisecondsToTicks( 10 ) ); + } + OSReport( "success\n" ); + + + // Finished bringing up network interface + // Now initializing the socket library + OSReport( "SOInit() ..." ); + { + SOLibraryConfig soLibConfig; + + (void)memset(&soLibConfig, 0, sizeof(soLibConfig)); + soLibConfig.alloc = _Alloc; + soLibConfig.free = _Free; + + rc = SOInit(&soLibConfig); + if( rc != SO_SUCCESS ) + { + switch(rc) + { + case SO_EALREADY: + OSReport("failed (SO_EALREADY)\n"); + break; + case SO_EINVAL: + OSReport("failed (SO_EINVAL)\n"); + break; + case SO_ENOMEM: + OSReport("failed (SO_ENOMEM)\n"); + break; + default: + OSReport("failed (%d)\n", rc); + }; + return FALSE; + } + } + OSReport( "success\n" ); + + + //Now Starting the sockets library + OSReport( "SOStartup() " ); + rc = SOStartup(); + if( rc != SO_SUCCESS ) + { + OSReport( "failed (%d)\n", rc ); + return FALSE; + } + OSReport( "success\n" ); + + // Getting the IP address of the local machine via DHCP + while( SOGetHostID() == 0 ) + { + NCDSleep( OSMillisecondsToTicks( 10 ) ); + } + { + SOInAddr addr; + addr.addr = (u32)SOGetHostID(); + OSReport( "IP address = %s\n", SOInetNtoA( addr ) ); + } + return TRUE; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// StopNetwork +// +void StopNetwork() +{ + (void)SOCleanup(); +} + +extern int test_main(int argc, char **argv); +void main(int argc, char **argv) +{ +// init 128k memory for sockets + void* arenaLo; + void* arenaHi; + + DEMOInit(NULL); // Init the OS, game pad, graphics and video. + + // Initialize heap for socket allocations + // Nintendo samples say this must be in MEM2 + arenaLo = OSGetMEM2ArenaLo(); + arenaHi = OSGetMEM2ArenaHi(); + + if ((u32) arenaHi - (u32) arenaLo < SOCKET_HEAPSIZE_DEFAULT) + OSHalt("Insufficient memory in MEM2 for socket lib. Check SOCKET_HEAPSIZE_DEFAULT"); + heapHandleSocket = MEMCreateExpHeapEx( arenaLo, SOCKET_HEAPSIZE_DEFAULT, MEM_HEAP_OPT_THREAD_SAFE ); + if (heapHandleSocket == MEM_HEAP_INVALID_HANDLE) + OSHalt("MEMCreateExpHeapEx failed.\n"); + OSSetArenaLo((u8*)arenaLo + SOCKET_HEAPSIZE_DEFAULT); + + // Start the network + if (!StartNetwork()) + OSHalt("Network was not started\n"); + OSReport("=======================================================================\n"); + OSReport("Gamespy:// Starting App\n"); + test_main(argc, argv); + OSReport("=======================================================================\n"); + OSReport("Gamespy:// Ending App\n"); + StopNetwork(); +} \ No newline at end of file diff --git a/code/gamespy/common/win32/Win32Common.c b/code/gamespy/common/win32/Win32Common.c new file mode 100644 index 00000000..49c13137 --- /dev/null +++ b/code/gamespy/common/win32/Win32Common.c @@ -0,0 +1,98 @@ +#include "../gsCommon.h" +#include "../gsMemory.h" +#include "../gsDebug.h" +#include +// Debug output +#ifdef GSI_COMMON_DEBUG + static void DebugCallback(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel, const char * theTokenStr, + va_list theParamList) + + { + GSI_UNUSED(theLevel); + { + static char string[256]; + vsprintf(string, theTokenStr, theParamList); + OutputDebugString(string); + } + printf("[%s][%s] ", + gGSIDebugCatStrings[theCat], + gGSIDebugTypeStrings[theType]); + + vprintf(theTokenStr, theParamList); + } +#endif + +#if (_MSC_VER <= 1300) + //extern added for vc6 compatability. + extern void * __cdecl _aligned_malloc(size_t size, size_t boundary); + extern void __cdecl _aligned_free(void * memblock); +#endif +void * gsiMemManagedInit() +{ +// Init the GSI memory manager (optional - for limiting GSI mem usage) +#if defined GSI_MEM_MANAGED + #define aMemoryPoolSize (1024*1024*4) + char *aMemoryPool = (char *)_aligned_malloc(aMemoryPoolSize,64); + if(aMemoryPool == NULL) + { + printf("Failed to create memory pool - aborting\r\n"); + return NULL; + } + { + gsMemMgrCreate(gsMemMgrContext_Default, "Default",aMemoryPool, aMemoryPoolSize); + } + return aMemoryPool; +#else + return NULL; +#endif + +} + +void gsiMemManagedClose(void * theMemoryPool) +{ + #if defined(GSI_MEM_MANAGED) + // Optional - Dump memory leaks + + gsi_u32 MemAvail = gsMemMgrMemAvailGet (gsMemMgrContext_Default); + gsi_u32 MemUsed = gsMemMgrMemUsedGet (gsMemMgrContext_Default); + gsi_u32 HwMark = gsMemMgrMemHighwaterMarkGet (gsMemMgrContext_Default); + + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Memory, GSIDebugLevel_Comment, + "MemAvail %u: MemUsed%u MemHWMark %u\n", MemAvail,MemUsed,HwMark); + gsMemMgrDumpStats(); + gsMemMgrDumpAllocations(); + gsMemMgrValidateMemoryPool(); + gsMemMgrDestroy(gsMemMgrContext_Default); + #endif + _aligned_free(theMemoryPool); +} + + +// sample common entry point +extern int test_main(int argc, char ** argp); + + +// Common entry point +int __cdecl main(int argc, char** argp) +{ + int ret = 0; + // set up memanager + void *heap = gsiMemManagedInit(); + + #ifdef GSI_COMMON_DEBUG + // Set up debugging + gsSetDebugCallback(DebugCallback); + gsSetDebugLevel(GSIDebugCat_All, GSIDebugType_All, GSIDebugLevel_Verbose); + #endif + + ret = test_main(argc, argp); + + gsiMemManagedClose(heap); + + return ret; +} + + + + diff --git a/code/gamespy/common/win32/changelog.txt b/code/gamespy/common/win32/changelog.txt new file mode 100644 index 00000000..825c2d1f --- /dev/null +++ b/code/gamespy/common/win32/changelog.txt @@ -0,0 +1,14 @@ +Changelog for: Win32 Common Code +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +09-15-2006 1.00.02 DES FIX Always call CreateSemaphoreA +09-21-2005 1.00.01 DES FEATURE Updated workspace +06-03-2005 1.00.00 SN RELEASE Releasing to developer site. +04-28-2005 1.00.00 SN RELEASE Releasing to developer site. +04-04-2005 1.00.00 SN RELEASE Releasing to developer site. +09-16-2004 1.00.00 SN RELEASE Releasing to developer site. +08-26-2004 1.00.00 DES OTHER Changelog started + FEATURE Added a workspace for building all the C test apps. + diff --git a/code/gamespy/common/win32/gsSocketWin32.c b/code/gamespy/common/win32/gsSocketWin32.c new file mode 100644 index 00000000..9622b31f --- /dev/null +++ b/code/gamespy/common/win32/gsSocketWin32.c @@ -0,0 +1,3 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + diff --git a/code/gamespy/common/win32/gsThreadWin32.c b/code/gamespy/common/win32/gsThreadWin32.c new file mode 100644 index 00000000..8bcb1d78 --- /dev/null +++ b/code/gamespy/common/win32/gsThreadWin32.c @@ -0,0 +1,87 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gsPlatformUtil.h" +#include "../gsPlatformThread.h" +#include "../gsAssert.h" +#include "../gsDebug.h" + + + +void gsiInitializeCriticalSection(GSICriticalSection *theCrit) { InitializeCriticalSection(theCrit); } +void gsiEnterCriticalSection (GSICriticalSection *theCrit) { EnterCriticalSection(theCrit); } +void gsiLeaveCriticalSection (GSICriticalSection *theCrit) { LeaveCriticalSection(theCrit); } +void gsiDeleteCriticalSection (GSICriticalSection *theCrit) { DeleteCriticalSection(theCrit); } + +gsi_u32 gsiHasThreadShutdown(GSIThreadID theThreadID) +{ + DWORD result = WaitForSingleObject(theThreadID, 0); + if (result == WAIT_ABANDONED || result == WAIT_OBJECT_0) + return 1; // thread is dead + else + return 0; // keep waiting +} + +GSISemaphoreID gsiCreateSemaphore(gsi_i32 theInitialCount, gsi_i32 theMaxCount, char* theName) +{ + GSISemaphoreID aSemaphore = CreateSemaphoreA(NULL, theInitialCount, theMaxCount, theName); + if (aSemaphore == NULL) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to create semaphore\r\n"); + } + return aSemaphore; +} + +// Waits for -- and signals -- the semaphore +gsi_u32 gsiWaitForSemaphore(GSISemaphoreID theSemaphore, gsi_u32 theTimeoutMs) +{ + DWORD result = WaitForSingleObject((HANDLE)theSemaphore, (DWORD)theTimeoutMs); + return (gsi_u32)result; +} + +// Allow other objects to access the semaphore +void gsiReleaseSemaphore(GSISemaphoreID theSemaphore, gsi_i32 theReleaseCount) +{ + ReleaseSemaphore(theSemaphore, theReleaseCount, NULL); +} + +void gsiCloseSemaphore(GSISemaphoreID theSemaphore) +{ + CloseHandle(theSemaphore); +} + + +int gsiStartThread(GSThreadFunc func, gsi_u32 theStackSize, void *arg, GSIThreadID * id) +{ + HANDLE handle; + DWORD threadID; + + // create the thread + handle = CreateThread(NULL, theStackSize, func, arg, 0, &threadID); + if(handle == NULL) + return -1; + + // store the id + *id = handle; + + return 0; +} + +void gsiCancelThread(GSIThreadID id) +{ + //TODO: is TerminateThread causing lost resources? + //should this be terminated with "failure" exit status? + TerminateThread(id, 0); +} + +void gsiExitThread(GSIThreadID id) +{ + GSI_UNUSED(id); +} + +void gsiCleanupThread(GSIThreadID id) +{ + CloseHandle(id); +} + + diff --git a/code/gamespy/common/win32/gsUtilWin32.c b/code/gamespy/common/win32/gsUtilWin32.c new file mode 100644 index 00000000..84dd8166 --- /dev/null +++ b/code/gamespy/common/win32/gsUtilWin32.c @@ -0,0 +1,19 @@ +#include "../gsCommon.h" + +gsi_i64 gsiStringToInt64(const char *theNumberStr) +{ + return _atoi64(theNumberStr); +} + +void gsiInt64ToString(char theNumberStr[33], gsi_i64 theNumber) +{ + // you want to fit the number! + // give me a valid string! + GS_ASSERT(theNumberStr != NULL); + +#if _MSC_VER > 1300 + sprintf(theNumberStr, "%lld", theNumber); +#else + sprintf(theNumberStr, "%I64d", theNumber); +#endif +} diff --git a/code/gamespy/common/x360/X360Common.c b/code/gamespy/common/x360/X360Common.c new file mode 100644 index 00000000..73586221 --- /dev/null +++ b/code/gamespy/common/x360/X360Common.c @@ -0,0 +1,93 @@ +#include "../gsCommon.h" +#include "../gsMemory.h" +#include "../gsDebug.h" +#include +// Debug output +#ifdef GSI_COMMON_DEBUG + static void DebugCallback(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel, const char * theTokenStr, + va_list theParamList) + + { + GSI_UNUSED(theLevel); + { + static char string[256]; + vsprintf(string, theTokenStr, theParamList); + OutputDebugString(string); + } + } +#endif + +#if (_MSC_VER <= 1300) + //extern added for vc6 compatability. + extern void * __cdecl _aligned_malloc(size_t size, int boundary); + extern void * __cdecl _aligned_free(void * memblock); +#endif +void * gsiMemManagedInit() +{ +// Init the GSI memory manager (optional - for limiting GSI mem usage) +#if defined GSI_MEM_MANAGED + #define aMemoryPoolSize (1024*1024*4) + char *aMemoryPool = _aligned_malloc(aMemoryPoolSize,64); + if(aMemoryPool == NULL) + { + printf("Failed to create memory pool - aborting\r\n"); + return NULL; + } + { + gsMemMgrCreate(gsMemMgrContext_Default, "Default",aMemoryPool, aMemoryPoolSize); + } + return aMemoryPool; +#else + return NULL; +#endif + +} + +void gsiMemManagedClose(void * theMemoryPool) +{ + #if defined(GSI_MEM_MANAGED) + // Optional - Dump memory leaks + + gsi_u32 MemAvail = gsMemMgrMemAvailGet (gsMemMgrContext_Default); + gsi_u32 MemUsed = gsMemMgrMemUsedGet (gsMemMgrContext_Default); + gsi_u32 HwMark = gsMemMgrMemHighwaterMarkGet (gsMemMgrContext_Default); + + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Memory, GSIDebugLevel_Comment, + "MemAvail %u: MemUsed%u MemHWMark %u\n", MemAvail,MemUsed,HwMark); + gsMemMgrDumpStats(); + gsMemMgrDumpAllocations(); + gsMemMgrValidateMemoryPool(); + + #endif + _aligned_free(theMemoryPool); +} + + +// sample common entry point +extern int test_main(int argc, char ** argp); + + +// Common entry point +int __cdecl main(int argc, char** argp) +{ + int ret = 0; + // set up memanager + void *heap = gsiMemManagedInit(); + + #ifdef GSI_COMMON_DEBUG + // Set up debugging + gsSetDebugCallback(DebugCallback); + gsSetDebugLevel(GSIDebugCat_All, GSIDebugType_All, GSIDebugLevel_Verbose); + #endif + + ret = test_main(argc, argp); + + gsiMemManagedClose(heap); + + return ret; +} + + + + diff --git a/code/gamespy/common/x360/gsSocketX360.c b/code/gamespy/common/x360/gsSocketX360.c new file mode 100644 index 00000000..d3ee7036 --- /dev/null +++ b/code/gamespy/common/x360/gsSocketX360.c @@ -0,0 +1,52 @@ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(_X360) +char * inet_ntoa(IN_ADDR in_addr) +{ + static char buffer[16]; + sprintf(buffer, "%d.%d.%d.%d", in_addr.S_un.S_un_b.s_b1, in_addr.S_un.S_un_b.s_b2, + in_addr.S_un.S_un_b.s_b3, in_addr.S_un.S_un_b.s_b4); + return buffer; +} + +struct hostent * gethostbyname(const char* name) +{ + XNDNS *pxndns; + static HOSTENT host; + HOSTENT *rvalue; + + if(XNetDnsLookup(name, NULL, &pxndns) != 0) + return NULL; + + while (pxndns->iStatus == WSAEINPROGRESS) + { + msleep(5); + } + + if ((pxndns->iStatus == 0) && (pxndns->cina > 0)) + { + static char * ipPtrs[2]; + static IN_ADDR ip; + + host.h_name = (char*)name; + host.h_aliases = NULL; + host.h_addrtype = AF_INET; + host.h_length = (gsi_u16)sizeof(IN_ADDR); + host.h_addr_list = (gsi_i8 **)ipPtrs; + + ip = pxndns->aina[0]; + ipPtrs[0] = (char *)&ip; + ipPtrs[1] = NULL; + + rvalue = &host; + } + else + { + rvalue = NULL; + } + XNetDnsRelease(pxndns); + + return rvalue; +} +#endif \ No newline at end of file diff --git a/code/gamespy/common/x360/gsThreadX360.c b/code/gamespy/common/x360/gsThreadX360.c new file mode 100644 index 00000000..28106743 --- /dev/null +++ b/code/gamespy/common/x360/gsThreadX360.c @@ -0,0 +1,87 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gsPlatformUtil.h" +#include "../gsPlatformThread.h" +#include "../gsAssert.h" +#include "../gsDebug.h" + + + +void gsiInitializeCriticalSection(GSICriticalSection *theCrit) { InitializeCriticalSection(theCrit); } +void gsiEnterCriticalSection (GSICriticalSection *theCrit) { EnterCriticalSection(theCrit); } +void gsiLeaveCriticalSection (GSICriticalSection *theCrit) { LeaveCriticalSection(theCrit); } +void gsiDeleteCriticalSection (GSICriticalSection *theCrit) { DeleteCriticalSection(theCrit); } + +gsi_u32 gsiHasThreadShutdown(GSIThreadID theThreadID) +{ + DWORD result = WaitForSingleObject(theThreadID, 0); + if (result == WAIT_ABANDONED || result == WAIT_OBJECT_0) + return 1; // thread is dead + else + return 0; // keep waiting +} + +GSISemaphoreID gsiCreateSemaphore(gsi_i32 theInitialCount, gsi_i32 theMaxCount, char* theName) +{ + GSISemaphoreID aSemaphore = CreateSemaphoreA(NULL, theInitialCount, theMaxCount, theName); + if (aSemaphore == NULL) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Failed to create semaphore\r\n"); + } + return aSemaphore; +} + +// Waits for -- and signals -- the semaphore +gsi_u32 gsiWaitForSemaphore(GSISemaphoreID theSemaphore, gsi_u32 theTimeoutMs) +{ + DWORD result = WaitForSingleObject((HANDLE)theSemaphore, (DWORD)theTimeoutMs); + return (gsi_u32)result; +} + +// Allow other objects to access the semaphore +void gsiReleaseSemaphore(GSISemaphoreID theSemaphore, gsi_i32 theReleaseCount) +{ + ReleaseSemaphore(theSemaphore, theReleaseCount, NULL); +} + +void gsiCloseSemaphore(GSISemaphoreID theSemaphore) +{ + CloseHandle(theSemaphore); +} + + +int gsiStartThread(GSThreadFunc func, gsi_u32 theStackSize, void *arg, GSIThreadID * id) +{ + HANDLE handle; + DWORD threadID; + + // create the thread + handle = CreateThread(NULL, theStackSize, func, arg, 0, &threadID); + if(handle == NULL) + return -1; + + // store the id + *id = handle; + + return 0; +} + +void gsiCancelThread(GSIThreadID id) +{ + //there is no way to terminate a thread + //we should move to always signalling threads to close + GSI_UNUSED(id); +} + +void gsiExitThread(GSIThreadID id) +{ + GSI_UNUSED(id); +} + +void gsiCleanupThread(GSIThreadID id) +{ + CloseHandle(id); +} + + diff --git a/code/gamespy/common/xbox/gsSocketXbox.c b/code/gamespy/common/xbox/gsSocketXbox.c new file mode 100644 index 00000000..41ab7978 --- /dev/null +++ b/code/gamespy/common/xbox/gsSocketXbox.c @@ -0,0 +1,12 @@ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(_XBOX) +char * inet_ntoa(IN_ADDR in_addr) +{ + static char buffer[16]; + sprintf(buffer, "%d.%d.%d.%d", in_addr.S_un.S_un_b.s_b1, in_addr.S_un.S_un_b.s_b2, + in_addr.S_un.S_un_b.s_b3, in_addr.S_un.S_un_b.s_b4); + return buffer; +} +#endif \ No newline at end of file diff --git a/code/gamespy/darray.c b/code/gamespy/darray.c new file mode 100644 index 00000000..3db363e2 --- /dev/null +++ b/code/gamespy/darray.c @@ -0,0 +1,377 @@ +/* + * + * File: darray.c + * --------------- + * David Wright + * 10/8/98 + * + * See darray.h for function descriptions + */ +#include +#include +#include "darray.h" + +#ifdef _MFC_MEM_DEBUG +#define _CRTDBG_MAP_ALLOC 1 +#include +#endif + + +#define DEF_GROWBY 8 + +#ifdef _NO_NOPORT_H_ + #define gsimalloc malloc + #define gsifree free + #define gsirealloc realloc + #include "common/gsAssert.h" +#else + #include "nonport.h" //for gsimalloc/realloc/free/GS_ASSERT +#endif + + +// STRUCTURES +struct DArrayImplementation +{ + int count, capacity; + int elemsize; + int growby; + ArrayElementFreeFn elemfreefn; + void *list; //array of elements +}; + +// PROTOTYPES +static void *mylsearch(const void *key, void *base, int count, int size, + ArrayCompareFn comparator); +static void *mybsearch(const void *elem, void *base, int num, int elemsize, + ArrayCompareFn comparator, int *found); +// FUNCTIONS + +/* FreeElement + * Frees the element at position N in the array + */ +static void FreeElement(DArray array, int n) +{ + if (array->elemfreefn != NULL) + array->elemfreefn(ArrayNth(array,n)); +} + +/* ArrayGrow + * Reallocates the array to a new size, incresed by growby + */ +static void ArrayGrow(DArray array) +{ + GS_ASSERT(array->elemsize) // sanity check -mj Oct 31st + array->capacity += array->growby; + array->list = gsirealloc(array->list, (size_t) array->capacity * array->elemsize); + GS_ASSERT(array->list); +} + +/* SetElement + * Sets the element at pos to the contents of elem + */ +static void SetElement(DArray array, const void *elem, int pos) +{ + GS_ASSERT(array) // safety check -mj Oct 31st + GS_ASSERT(elem) + GS_ASSERT(array->elemsize) + + memcpy(ArrayNth(array,pos), elem, (size_t)array->elemsize); +} + +DArray ArrayNew(int elemSize, int numElemsToAllocate, + ArrayElementFreeFn elemFreeFn) +{ + DArray array; + + array = (DArray) gsimalloc(sizeof(struct DArrayImplementation)); + GS_ASSERT(array); + GS_ASSERT(elemSize); + if (numElemsToAllocate == 0) + numElemsToAllocate = DEF_GROWBY; + array->count = 0; + array->capacity = numElemsToAllocate;; + array->elemsize = elemSize; + array->growby = numElemsToAllocate; + array->elemfreefn = elemFreeFn; + if (array->capacity != 0) + { + array->list = gsimalloc((size_t)array->capacity * array->elemsize); + GS_ASSERT(array->list); + } else + array->list = NULL; + + return array; +} + +void ArrayFree(DArray array) +{ + int i; + + GS_ASSERT(array); + for (i = 0; i < array->count; i++) + { + FreeElement(array, i); + } + // mj to do: move these asserts into gsi_free. maybe, depends on whether user overloads them + GS_ASSERT(array->list) + GS_ASSERT(array) + gsifree(array->list); + gsifree(array); +} + +void *ArrayGetDataPtr(DArray array) +{ + GS_ASSERT(array); + return array->list; +} + +void ArraySetDataPtr(DArray array, void *ptr, int count, int capacity) +{ + int i; + + GS_ASSERT(array); + if (array->list != NULL) + { + for (i = 0; i < array->count; i++) + { + FreeElement(array, i); + } + gsifree(array->list); + } + array->list = ptr; + array->count = count; + array->capacity = capacity; + +} + + +int ArrayLength(const DArray array) +{ + GS_ASSERT(array) + return array->count; +} + +void *ArrayNth(DArray array, int n) +{ + // 2004.Nov.16.JED - modified GS_ASSERT to include "if" to add robustness + GS_ASSERT( (n >= 0) && (n < array->count)); + if( ! ((n >= 0) && (n < array->count)) ) + return NULL; + + return (char *)array->list + array->elemsize*n; +} + +/* ArrayAppend + * Just do an Insert at the end of the array + */ +void ArrayAppend(DArray array, const void *newElem) +{ + GS_ASSERT(array); + if(array) + ArrayInsertAt(array, newElem, array->count); +} + +void ArrayInsertAt(DArray array, const void *newElem, int n) +{ + GS_ASSERT (array) + GS_ASSERT ( (n >= 0) && (n <= array->count)); + + if (array->count == array->capacity) + ArrayGrow(array); + array->count++; + if (n < array->count - 1) //if we aren't appending + memmove(ArrayNth(array, n+1), ArrayNth(array,n), + (size_t)(array->count - 1 - n) * array->elemsize); + SetElement(array, newElem, n); +} + +void ArrayInsertSorted(DArray array, const void *newElem, ArrayCompareFn comparator) +{ + int n; + void *res; + int found; + + GS_ASSERT (array) + GS_ASSERT (comparator); + + res=mybsearch(newElem, array->list, array->count, array->elemsize, comparator, &found); + n = (((char *)res - (char *)array->list) / array->elemsize); + ArrayInsertAt(array, newElem, n); +} + + +void ArrayRemoveAt(DArray array, int n) +{ + GS_ASSERT (array) + GS_ASSERT( (n >= 0) && (n < array->count)); + + if (n < array->count - 1) //if not last element + memmove(ArrayNth(array,n),ArrayNth(array,n+1), + (size_t)(array->count - 1 - n) * array->elemsize); + array->count--; +} + +void ArrayDeleteAt(DArray array, int n) +{ + GS_ASSERT (array) + GS_ASSERT ( (n >= 0) && (n < array->count)); + + FreeElement(array,n); + ArrayRemoveAt(array, n); +} + + +void ArrayReplaceAt(DArray array, const void *newElem, int n) +{ + GS_ASSERT (array) + GS_ASSERT ( (n >= 0) && (n < array->count)); + + FreeElement(array, n); + SetElement(array, newElem,n); +} + + +void ArraySort(DArray array, ArrayCompareFn comparator) +{ + GS_ASSERT (array) + qsort(array->list, (size_t)array->count, (size_t)array->elemsize, comparator); +} + +//GS_ASSERT will be raised by ArrayNth if fromindex out of range +int ArraySearch(DArray array, const void *key, ArrayCompareFn comparator, + int fromIndex, int isSorted) +{ + void *res; + int found = 1; + if (!array || array->count == 0) + return NOT_FOUND; + + if (isSorted) + res=mybsearch(key, ArrayNth(array,fromIndex), + array->count - fromIndex, array->elemsize, comparator, &found); + else + res=mylsearch(key, ArrayNth(array, fromIndex), + array->count - fromIndex, array->elemsize, comparator); + if (res != NULL && found) + return (((char *)res - (char *)array->list) / array->elemsize); + else + return NOT_FOUND; +} + + +void ArrayMap(DArray array, ArrayMapFn fn, void *clientData) +{ + int i; + + GS_ASSERT (array) + GS_ASSERT(fn); + + for (i = 0; i < array->count; i++) + fn(ArrayNth(array,i), clientData); + +} + +void ArrayMapBackwards(DArray array, ArrayMapFn fn, void *clientData) +{ + int i; + + GS_ASSERT(fn); + + for (i = (array->count - 1) ; i >= 0 ; i--) + fn(ArrayNth(array,i), clientData); + +} + +void * ArrayMap2(DArray array, ArrayMapFn2 fn, void *clientData) +{ + int i; + void * pcurr; + + GS_ASSERT(fn); + GS_ASSERT(clientData); + + for (i = 0; i < array->count; i++) + { + pcurr = ArrayNth(array,i); + if(!fn(pcurr, clientData)) + return pcurr; + } + + return NULL; +} + +void * ArrayMapBackwards2(DArray array, ArrayMapFn2 fn, void *clientData) +{ + int i; + void * pcurr; + + GS_ASSERT(fn); + GS_ASSERT(clientData); + + for (i = (array->count - 1) ; i >= 0 ; i--) + { + pcurr = ArrayNth(array,i); + if(!fn(pcurr, clientData)) + return pcurr; + } + + return NULL; +} + +void ArrayClear(DArray array) +{ + int i; + + // This could be more optimal! + ////////////////////////////// + for(i = (ArrayLength(array) - 1) ; i >= 0 ; i--) + ArrayDeleteAt(array, i); +} + +/* mylsearch + * Implementation of a standard linear search on an array, since we + * couldn't use lfind + */ +static void *mylsearch(const void *key, void *base, int count, int size, + ArrayCompareFn comparator) +{ + int i; + GS_ASSERT(key); + GS_ASSERT(base); + for (i = 0; i < count; i++) + { + if (comparator(key, (char *)base + size*i) == 0) + return (char *)base + size*i; + } + return NULL; +} + +/* mybsearch + * Implementation of a bsearch, since its not available on all platforms + */ +static void *mybsearch(const void *elem, void *base, int num, int elemsize, ArrayCompareFn comparator, int *found) +{ + int L, H, I, C; + + GS_ASSERT(elem); + GS_ASSERT(base); + GS_ASSERT(found); + + L = 0; + H = num - 1; + *found = 0; + while (L <= H) + { + I = (L + H) >> 1; + C = comparator(((char *)base) + I * elemsize,elem); + if (C == 0) + *found = 1; + if (C < 0) + L = I + 1; + else + { + H = I - 1; + } + } + return ((char *)base) + L * elemsize; +} diff --git a/code/gamespy/darray.h b/code/gamespy/darray.h new file mode 100644 index 00000000..ad84b13f --- /dev/null +++ b/code/gamespy/darray.h @@ -0,0 +1,317 @@ +#ifndef _DARRAY_H +#define _DARRAY_H + +/* File: darray.h + * -------------- + * Defines the interface for the DynamicArray ADT. + * The DArray allows the client to store any number of elements of any desired + * base type and is appropriate for a wide variety of storage problems. It + * supports efficient element access, and appending/inserting/deleting elements + * as well as optional sorting and searching. In all cases, the DArray imposes + * no upper bound on the number of elements and deals with all its own memory + * management. The client specifies the size (in bytes) of the elements that + * will be stored in the array when it is created. Thereafter the client and + * the DArray can refer to elements via (void*) ptrs. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Type: DArray + * ---------------- + * Defines the DArray type itself. The client can declare variables of type + * DArray, but these variables must be initialized with the result of ArrayNew. + * The DArray is implemented with pointers, so all client copies in variables + * or parameters will be "shallow" -- they will all actually point to the + * same DArray structure. Only calls to ArrayNew create new arrays. + * The struct declaration below is "incomplete"- the implementation + * details are literally not visible in the client .h file. + */ +typedef struct DArrayImplementation *DArray; + + +/* ArrayCompareFn + * -------------- + * ArrayCompareFn is a pointer to a client-supplied function which the + * DArray uses to sort or search the elements. The comparator takes two + * (const void*) pointers (these will point to elements) and returns an int. + * The comparator should indicate the ordering of the two elements + * using the same convention as the strcmp library function: + * If elem1 is "less than" elem2, return a negative number. + * If elem1 is "greater than" elem2, return a positive number. + * If the two elements are "equal", return 0. + */ +#if defined(WIN32) + typedef int (__cdecl *ArrayCompareFn)(const void *elem1, const void *elem2); +#else + typedef int (*ArrayCompareFn)(const void *elem1, const void *elem2); +#endif + + +/* ArrayMapFn + * ---------- + * ArrayMapFn defines the space of functions that can be used to map over + * the elements in a DArray. A map function is called with a pointer to + * the element and a client data pointer passed in from the original + * caller. + */ +typedef void (*ArrayMapFn)(void *elem, void *clientData); + +/* ArrayMapFn2 + * ----------_ + * Same as ArrayMapFn, but can return 0 to stop the mapping. + * Used by ArrayMap2 + */ +typedef int (*ArrayMapFn2)(void *elem, void *clientData); + + /* ArrayElementFreeFn + * ------------------ + * ArrayElementFreeFn defines the space of functions that can be used as the + * clean-up function for an element as it is deleted from the array + * or when the entire array of elements is freed. The cleanup function is + * called with a pointer to an element about to be deleted. + */ +typedef void (*ArrayElementFreeFn)(void *elem); + + +/* ArrayNew + * -------- + * Creates a new DArray and returns it. There are zero elements in the array. + * to start. The elemSize parameter specifies the number of bytes that a single + * element of this array should take up. For example, if you want to store + * elements of type Binky, you would pass sizeof(Binky) as this parameter. + * An assert is raised if the size is not greater than zero. + * + * The numElemsToAllocate parameter specifies the initial allocated length + * of the array, as well as the dynamic reallocation increment for when the + * array grows. Rather than growing the array one element at a time as + * elements are added (which is rather inefficient), you will grow the array + * in chunks of numElemsToAllocate size. The "allocated length" is the number + * of elements that have been allocated, the "logical length" is the number of + * those slots actually being currently used. + * + * A new array is initially allocated to the size of numElemsToAllocate, the + * logical length is zero. As elements are added, those allocated slots fill + * up and when the initial allocation is all used, grow the array by another + * numElemsToAllocate elements. You will continue growing the array in chunks + * like this as needed. Thus the allocated length will always be a multiple + * of numElemsToAllocate. Don't worry about using realloc to shrink the array + * allocation if many elements are deleted from the array. It turns out that + * many implementations of realloc don't even pay attention to such a request + * so there is little point in asking. Just leave the array over-allocated. + * + * The numElemsToAllocate is the client's opportunity to tune the resizing + * behavior for their particular needs. If constructing large arrays, + * specifying a large allocation chunk size will result in fewer resizing + * operations. If using small arrays, a small allocation chunk size will + * result in less space going unused. If the client passes 0 for + * numElemsToAllocate, the implementation will use the default value of 8. + * + * The elemFreeFn is the function that will be called on an element that + * is about to be deleted (using ArrayDeleteAt) or on each element in the + * array when the entire array is being freed (using ArrayFree). This function + * is your chance to do any deallocation/cleanup required for the element + * (such as freeing any pointers contained in the element). The client can pass + * NULL for the cleanupFn if the elements don't require any handling on free. + */ +DArray ArrayNew(int elemSize, int numElemsToAllocate, + ArrayElementFreeFn elemFreeFn); + + + + /* ArrayFree + * ---------- + * Frees up all the memory for the array and elements. It DOES NOT + * automatically free memory owned by pointers embedded in the elements. + * This would require knowledge of the structure of the elements which the + * DArray does not have. However, it will iterate over the elements calling + * the elementFreeFn earlier supplied to ArrayNew and therefore, the client, + * who knows what the elements are, can do the appropriate deallocation of any + * embedded pointers through that function. After calling this, the value of + * what array is pointing to is undefined. + */ +void ArrayFree(DArray array); + + +/* ArrayLength + * ----------- + * Returns the logical length of the array, i.e. the number of elements + * currently in the array. Must run in constant time. + */ +int ArrayLength(const DArray array); + + +/* ArrayNth + * -------- + * Returns a pointer to the element numbered n in the specified array. + * Numbering begins with 0. An assert is raised if n is less than 0 or greater + * than the logical length minus 1. Note this function returns a pointer into + * the DArray's element storage, so the pointer should be used with care. + * This function must operate in constant time. + * + * We could have written the DArray without this sort of access, but it + * is useful and efficient to offer it, although the client needs to be + * careful when using it. In particular, a pointer returned by ArrayNth + * becomes invalid after any calls which involve insertion, deletion or + * sorting the array, as all of these may rearrange the element storage. + */ +void *ArrayNth(DArray array, int n); + + +/* ArrayAppend + * ----------- + * Adds a new element to the end of the specified array. The element is + * passed by address, the element contents are copied from the memory pointed + * to by newElem. Note that right after this call, the new element will be + * the last in the array; i.e. its element number will be the logical length + * minus 1. This function must run in constant time (neglecting + * the memory reallocation time which may be required occasionally). + */ +void ArrayAppend(DArray array, const void *newElem); + +/* ArrayInsertAt + * ------------- + * Inserts a new element into the array, placing it at the position n. + * An assert is raised if n is less than 0 or greater than the logical length. + * The array elements after position n will be shifted over to make room. The + * element is passed by address, the new element's contents are copied from + * the memory pointed to by newElem. This function runs in linear time. + */ +void ArrayInsertAt(DArray array, const void *newElem, int n); + +/* ArrayInsertSorted + * ------------- + * Inserts a new element into the array, placing it at the position indicated by + * a binary search of the array using comparator. + * The array MUST be sorted prior to calling InsertSorted. + * Note that if you only ever call InsertSorted, the array will always be sorted. + */ +void ArrayInsertSorted(DArray array, const void *newElem, ArrayCompareFn comparator); + + /* ArrayDeleteAt + * ------------- + * Deletes the element numbered n from the array. Before being removed, + * the elemFreeFn that was supplied to ArrayNew will be called on the element. + * An assert is raised if n is less than 0 or greater than the logical length + * minus one. All the elements after position n will be shifted over to fill + * the gap. This function runs in linear time. It does not shrink the + * allocated size of the array when an element is deleted, the array just + * stays over-allocated. + */ +void ArrayDeleteAt(DArray array, int n); + + /* ArrayDeleteAt + * ------------- + * Removes the element numbered n from the array. The element will not be freed + * before being removed. All the elements after position n will be shifted over to fill + * the gap. This function runs in linear time. It does not shrink the + * allocated size of the array when an element is deleted, the array just + * stays over-allocated. + */ +void ArrayRemoveAt(DArray array, int n); + +/* ArrayReplaceAt + * ------------- + * Overwrites the element numbered n from the array with a new value. Before + * being overwritten, the elemFreeFn that was supplied to ArrayNew is called + * on the old element. Then that position in the array will get a new value by + * copying the new element's contents from the memory pointed to by newElem. + * An assert is raised if n is less than 0 or greater than the logical length + * minus one. None of the other elements are affected or rearranged by this + * operation and the size of the array remains constant. This function must + * operate in constant time. + */ +void ArrayReplaceAt(DArray array, const void *newElem, int n); + + +/* ArraySort + * --------- + * Sorts the specified array into ascending order according to the supplied + * comparator. The numbering of the elements will change to reflect the + * new ordering. An assert is raised if the comparator is NULL. + */ +void ArraySort(DArray array, ArrayCompareFn comparator); + + +#define NOT_FOUND -1 // returned when a search fails to find the key + +/* ArraySearch + * ----------- + * Searches the specified array for an element whose contents match + * the element passed as the key. Uses the comparator argument to test + * for equality. The "fromIndex" parameter controls where the search + * starts looking from. If the client desires to search the entire array, + * they should pass 0 as the fromIndex. The function will search from + * there to the end of the array. The "isSorted" parameter allows the client + * to specify that the array is already in sorted order, and thus it uses a + * faster binary search. If isSorted is false, a simple linear search is + * used. If a match is found, the position of the matching element is returned + * else the function returns NOT_FOUND. Calling this function does not + * re-arrange or change contents of DArray or modify the key in any way. + * An assert is raised if fromIndex is less than 0 or greater than + * the logical length (although searching from logical length will never + * find anything, allowing this case means you can search an entirely empty + * array from 0 without getting an assert). An assert is raised if the + * comparator is NULL. + */ +int ArraySearch(DArray array, const void *key, ArrayCompareFn comparator, + int fromIndex, int isSorted); + + +/* ArrayMap + * ----------- + * Iterates through each element in the array in order (from element 0 to + * element n-1) and calls the function fn for that element. The function is + * called with the address of the array element and the clientData pointer. + * The clientData value allows the client to pass extra state information to + * the client-supplied function, if necessary. If no client data is required, + * this argument should be NULL. An assert is raised if map function is NULL. + */ +void ArrayMap(DArray array, ArrayMapFn fn, void *clientData); + +/* ArrayMapBackwards + * ----------- + * Same as ArrayMap, but goes through the array from end to front. This + * makes it safe to free elements during the mapping. + */ +void ArrayMapBackwards(DArray array, ArrayMapFn fn, void *clientData); + +/* ArrayMap2 + * ----------- + * Same as ArrayMap, but allows the mapping to be stopped by returning 0 + * from the mapping function. If the mapping was stopped, the element + * it was stopped at will be returned. If it wasn't stopped, then NULL + * will be returned. + */ +void * ArrayMap2(DArray array, ArrayMapFn2 fn, void *clientData); + +/* ArrayMapBackwards2 + * ------------ + * Goes through the array backwards, and allows you to stop the mapping. + */ +void * ArrayMapBackwards2(DArray array, ArrayMapFn2 fn, void *clientData); + +/* ArrayClear + * ----------- + * Deletes all elements in the array, but without freeing the array. + */ +void ArrayClear(DArray array); + +/* ArrayGetDataPtr + * ----------- + * Obtain the pointer to the actual data storage + */ +void *ArrayGetDataPtr(DArray array); + +/* ArraySetDataPtr + * ----------- + * Set the pointer to the actual data storage, which must be allocated with malloc + */ +void ArraySetDataPtr(DArray array, void *ptr, int count, int capacity); + +#ifdef __cplusplus +} +#endif + +#endif //_DARRAY_ diff --git a/code/gamespy/gcd.cmake b/code/gamespy/gcd.cmake new file mode 100644 index 00000000..0becb460 --- /dev/null +++ b/code/gamespy/gcd.cmake @@ -0,0 +1,115 @@ +cmake_minimum_required(VERSION 3.1) + +project(gcd) + +set(CURRENT_DIR "code/gamespy") + +include_directories("${CURRENT_DIR}/common") +include_directories("${CURRENT_DIR}/gcdkey") + +file(GLOB SRCS_common +"${CURRENT_DIR}/*.c" +"${CURRENT_DIR}/common/*.c" +) + +if(UNIX) + file(GLOB SRCS_platform "${CURRENT_DIR}/common/linux/*.c") +elseif(WIN32) + file(GLOB SRCS_platform "${CURRENT_DIR}/common/win32/*.c") +elseif(APPLE) + file(GLOB SRCS_platform "${CURRENT_DIR}/common/macosx/*.c") +endif() + +SET(SRCS_common ${SRCS_common} ${SRCS_platform}) + +file(GLOB SRCS_gcdkey +"${CURRENT_DIR}/gcdkey/*.c" +) + +file(GLOB_RECURSE SRCS_ghttp +"${CURRENT_DIR}/gcdkey/*.c" +) + +file(GLOB SRCS_GP +"${CURRENT_DIR}/GP/*.c" +) + +file(GLOB_RECURSE SRCS_gstats +"${CURRENT_DIR}/gstats/*.c" +) + +file(GLOB SRCS_natneg +"${CURRENT_DIR}/natneg/*.c" +) + +file(GLOB SRCS_pinger +"${CURRENT_DIR}/pinger/*.c" +) + +file(GLOB SRCS_pt +"${CURRENT_DIR}/pt/*.c" +) + +file(GLOB SRCS_qr2 +"${CURRENT_DIR}/qr2/*.c" +) + +file(GLOB SRCS_sake +"${CURRENT_DIR}/sake/*.c" +) + +file(GLOB SRCS_sc +"${CURRENT_DIR}/sc/*.c" +) + +file(GLOB SRCS_serverbrowsing +"${CURRENT_DIR}/serverbrowsing/*.c" +) + +file(GLOB_RECURSE SRCS_webservices +"${CURRENT_DIR}/webservices/*.c" +) + +add_library(gcd_common STATIC ${SRCS_common}) +add_library(gcd_key STATIC ${SRCS_gcdkey}) +add_library(gcd_gp STATIC ${SRCS_GP}) +add_library(gcd_gstats STATIC ${SRCS_gstats}) +add_library(gcd_natneg STATIC ${SRCS_natneg}) +add_library(gcd_pinger STATIC ${SRCS_pinger}) +add_library(gcd_pt STATIC ${SRCS_pt}) +add_library(gcd_qr2 STATIC ${SRCS_qr2}) +add_library(gcd_sake STATIC ${SRCS_sake}) +add_library(gcd_sc STATIC ${SRCS_sc}) +add_library(gcd_serverbrowsing STATIC ${SRCS_serverbrowsing}) +add_library(gcd_webservices STATIC ${SRCS_webservices}) +add_library(gcd INTERFACE) + +set_property(TARGET gcd_common gcd_key PROPERTY POSITION_INDEPENDENT_CODE ON) + +if(UNIX) + add_compile_definitions(_LINUX=1) +endif(UNIX) + +set(DEPENDENT_LIBS + gcd_common + gcd_key + gcd_gp + gcd_gstats + gcd_natneg + gcd_pinger + gcd_pt + gcd_qr2 + gcd_sake + gcd_sc + gcd_serverbrowsing + gcd_webservices +) + +target_link_libraries(gcd INTERFACE ${DEPENDENT_LIBS}) + +foreach(LIB ${DEPENDENT_LIBS}) + target_link_libraries(${LIB} PUBLIC gcd) +endforeach() + +#target_compile_definitions(gcd PRIVATE _CRT_SECURE_NO_WARNINGS) +#set_property(TARGET gcd PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/code/gamespy/gcdkey/CdkeyGen/gcdkeygen.c b/code/gamespy/gcdkey/CdkeyGen/gcdkeygen.c new file mode 100644 index 00000000..22f72afe --- /dev/null +++ b/code/gamespy/gcdkey/CdkeyGen/gcdkeygen.c @@ -0,0 +1,227 @@ +/****** +gcdkeygen.c +GameSpy CDKey SDK CD Key Generation / Validation Example + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + + This source is simply an example of how you might generate + CD Keys and how you would validate them on the client side. + + You can use this algorithm, or a derivation thereof, or your own, + but always make sure that: + 1. Your valid keys are a SMALL subset of the possible keys + 2. The distribution of valid keys within the full set is even, but + not regular. + + Please see the GameSpy CDKey SDK documentation for more + information + +******/ + +#include +#include +#include +#include +#include + +static void Util_RandSeed(unsigned long seed); +static int Util_RandInt(int low, int high); + + +#define MINKEYSEED 0x0000100000 +#define MAXKEYSEED 0xFFFFFFFFFF +/* +The CD Key is in the form: +RSSS-SSSS-SSSR-CCCC +Where +R = random chars +S = bytes of the seed (two S's per byte) +C = bytes of the check (two C's per byte) + +The seed of the keys is a 40 bit number. If you generate 1 million +keys, then the chances of guessing a valid key (even knowing the algorithm) +is about 1 in 1.09 million. +You can easily take this number into the trillions by using the full 64 bits +(of course it makes your CD Key longer, unless you use base 26 numbers) + +In addition to the seed, we use a 16 bit check value. The chances of guessing +a mathematically correct key, without knowing the algorithm, is 1 in 65 thousand. +You could make that 1 in 4 billion by using a 32 bit check value. However, even +if a key is mathematically correct, it is not necessarily valid. + +It is important that the key generation algorithm actually pick out random +valid keys out of the entire range of keys. If it picks according to a non-random +algorithm (for example, every 1000000'th seed) then you can figure out all +the valid CD Keys from 1 valid one. + +The generate keys function is designed to generate all the keys you will +ever need in 1 shot, if you use it twice you may end up with key collisions +(although the probability is VERY low). You can change this by using different +min/max key seeds each time you run it. +*/ +void DoGenerateKeys() +{ + char resp[128] = ""; + char key[128]; + char hexstr[20]; + int keyct; + unsigned int interval; + __int64 offset; + __int64 base = MINKEYSEED; + __int64 seed; + short check; + FILE *f; + + Util_RandSeed(time(NULL) ^ GetTickCount()); + printf("How many keys would you like? "); + gets(resp); + + keyct = atoi(resp); + + offset = (MAXKEYSEED - MINKEYSEED) / keyct; + if (offset > 0xFFFFFFFF) //too big for a uint.. too few keys really + interval = 0xFFFFFFFF; + else + interval = (unsigned int)offset; + + + f = fopen("keys.txt","w"); + if (!f) + return; + while (keyct != 0) + { + seed = base + Util_RandInt(0,interval); //pick a number between this base and the next + base += offset; //move up the base + //note: a 1 way xform on seed at this point would increase security + //print a 40 bit hex number to the string + sprintf(hexstr,"%.10I64x",seed); + + check = ((short)(seed % 65535)) ^ 0x9249; //these are "secret" check calculation values + + sprintf(&key[11],"%.8x",check); //add the check as the last 4 chars (but it prints 8 chars) + key[0] = '0' + (char)(Util_RandInt(0,9)); //first character is random + strncpy(&key[1],hexstr,3); //add first 3 chars of seed + key[4] = '-'; + strncpy(&key[5],&hexstr[3],4); //next 4 chars of seed + key[9] = '-'; + strncpy(&key[10],&hexstr[7],3); //last 3 chars of seed + key[13] = '0' + (char)(Util_RandInt(0,9)); //this character is random + key[14] = '-'; + + fprintf(f,"%s\n",key); //print it to the key file + keyct--; + } + fclose(f); + printf("Keys output to keys.txt\n"); +} + +int ValidateKey(char *key) +{ + __int64 seed; + char hexstr[20] = "0x"; + int check; + short realcheck; + + //extract the seed value + strncpy(hexstr + 2,key + 1,3); + strncpy(hexstr + 5, key + 5, 4); + strncpy(hexstr + 9, key + 10, 3); + hexstr[12] = 0; + sscanf(hexstr,"%I64x",&seed); + + //extract the check value + strncpy(hexstr + 2, key + 15, 4); + hexstr[6] = 0; + sscanf(hexstr, "%x",&check); + + //calc the real check value + realcheck = ((short)(seed % 65535)) ^ 0x9249; + return ((short)check == realcheck); +} + +void DoValidateKeys() +{ + char resp[128] = ""; + + do + { + printf("Enter a CD Key to validate, or [ENTER] to quit: "); + gets(resp); + if (!resp[0]) + return; + if (ValidateKey(resp)) + printf("Key Validated\n"); + else + printf("Invalid Key\n"); + + } while (1); + + +} + +int main(int argc, char **argv) +{ + char resp[10]; + //display a menu + printf("What would you like to do?\n\t1. Generate CD Keys\n\t2. Validate a CD Key\n:"); + gets(resp); + if (resp[0] == '1') + DoGenerateKeys(); + else + DoValidateKeys(); +} + +/*********** +Random Number Generation Code +***********/ +#define RANa 16807 /* multiplier */ +#define LONGRAND_MAX 2147483647L /* 2**31 - 1 */ + +static long randomnum = 1; + +/************** +** FUNCTIONS ** +**************/ +static long nextlongrand(long seed) +{ + unsigned long lo, hi; + + lo = RANa * (long)(seed & 0xFFFF); + hi = RANa * (long)((unsigned long)seed >> 16); + lo += (hi & 0x7FFF) << 16; + if (lo > LONGRAND_MAX) + { + lo &= LONGRAND_MAX; + ++lo; + } + lo += hi >> 15; + if (lo > LONGRAND_MAX) + { + lo &= LONGRAND_MAX; + ++lo; + } + return (long)lo; +} + +static long longrand(void) /* return next random long */ +{ + randomnum = nextlongrand(randomnum); + return randomnum; +} + +static void Util_RandSeed(unsigned long seed) /* to seed it */ +{ + randomnum = seed ? (seed & LONGRAND_MAX) : 1; /* nonzero seed */ +} + +static int Util_RandInt(int low, int high) +{ + int range = high-low; + int num = longrand() % range; + + return(num + low); +} diff --git a/code/gamespy/gcdkey/changelog.txt b/code/gamespy/gcdkey/changelog.txt new file mode 100644 index 00000000..7ccc5b5a --- /dev/null +++ b/code/gamespy/gcdkey/changelog.txt @@ -0,0 +1,78 @@ +Changelog for: GameSpy CDKey SDK +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 1.08.00 RMV RELEASE Released to Developer Site +12-06-2007 1.07.01 SAH OTHER Added NatNeg to ServerTestQR2 projects to remove compiler errors +08-06-2007 1.07.00 RMV RELEASE Released to Developer Site +07-16-2007 1.06.02 RMV FIX Added win32common.c to ClientTest/ServerTest Project to fix compilation error + 1.06.02 RMV OTHER Changed cd key used by ServerTestQR2 to be different than that used by ClientTest +07-11-2007 1.06.02 RMV FIX Fixed typecasting, possibly uninitialized variable and unreferenced variable warnings in test projects +12-18-2006 1.06.01 SAH FIX Fixed a bug in the ServerTest app causing an incorrect hint value to be passed to the client +12-15-2006 1.06.00 MJW RELEASE Released to Developer Site +12-12-2006 1.05.12 SAH FIX Changed all references in ServerTestQR2 of PRODUCTID to GAMEID +10-05-2006 1.05.11 SAH FIX Updated MacOSX Makefile +08-24-2006 1.05.10 SAH FIX Fixed VC7 project file +08-02-2006 1.05.09 SAH RELEASE Releasing to developer site +07-31-2006 1.05.09 SAH FIX Fixed Linux makefile +07-24-2006 1.05.08 SAH OTHER Added Host self-validation check to the gcd/qr2server sample app +05-31-2006 1.05.07 SAH RELEASE Releasing to developer site + SAH FIX Fixed Linux makefile +05-25-2006 1.05.06 SAH FIX Changed keyserver_qr2 to have natNegotiate set to 1 +04-25-2006 1.05.05 SAH RELEASE Releasing to developer site +04-24-2006 1.05.05 SAH FIX Commented out unused gsiIsSocketError check in **server.c tests +01-27-2006 1.05.04 SN RELEASE Releasing to developer site +12-16-2005 1.05.04 SN OTHER Moved projects to subdirectories + Reorganized each project to have latest common code +11-14-2005 1.05.03 DES FIX Updates for OSX. +08-17-2005 1.05.02 SN FIX Fixed method parameter causing C++ compiler errors for computing responses +08-02-2005 1.05.01 BED FIX Renamed "skey" parameter of RefreshAuthCallbackFn to "hint" for consistency +07-28-2005 1.05.00 SN RELEASE Releasing to developer site. +06-05-2005 1.05.00 SN FIX Fixed the newline bug in the client sample causing all CD Keys to be invalid +06-03-2005 1.05.00 SN RELEASE Releasing to developer site. +05-26-2005 1.05.00 BED RELEASE Releasing to developer site. +05-17-2005 1.05.00 BED FEATURE Added challenge proof to "ison" messages. +05-05-2005 1.04.19 BED OTHER Updated projects to use new common folder +05-29-2005 1.04.18 SN OTHER Created Visual Studio .NET projects +04-28-2005 1.04.18 SN RELEASE Releasing to developer site. +04-04-2005 1.04.18 SN RELEASE Releasing to developer site. +11-25-2004 1.04.18 SN FIX Added const qualifiers to function parameters not modified +11-19-2004 1.04.17 SN RELEASE Releasing to developer site. +11-18-2004 1.04.17 BED FIX Fixed buffer overflow vulnerability in gcdkeys.c +09-16-2004 1.04.16 SN RELEASE Releasing to developer site. +08-27-2004 1.04.16 DES CLEANUP Removed MacOS style includes + DES CLEANUP Updated Win32 project configurations + DES CLEANUP Fixed warnings under OSX + DES CLEANUP Removed legacy QRCDKEY_INTEGRATION code and sample + DES CLEANUP Updated OSX Makefiles +08-24-2004 1.04.15 DES CLEANUP Removed references to MacOS. +08-05-2004 1.04.14 SN RELEASE Releasing to developer site. +02-03-2003 1.04.14 BED RELEASE Releasing to developer site. +02-02-2004 1.04.13 DES FIX Fixed code that assumed there was a second backslash if a first was found. +11-10-2003 1.04.12 DES RELEASE Releasing to developer site. +11-07-2003 1.04.12 DES FIX Updated the linux Makefile. +11-04-2003 1.04.11 DES FEATURE Added availability check code. +10-21-2003 1.04.10 BED RELEASE Releasing to developer site. (UNIQUE NICK AND UNICODE SUPPORT) +09-30-2003 1.04.10 BED FIX Parameters named "productid" are now called "gameid" to avoid confusion. +08-21-2003 1.04.09 DES RELEASE Releasing to developer site. +07-25-2003 1.04.09 DES FIX Removed usage of gets in the clienttext to get rid of Linux warnings. +07-24-2003 1.04.08 DES RELEASE Releasing to developer site. +07-23-2003 1.04.08 BED CLEANUP General cleanup to remove "no newline at end of file" warnings. +07-16-2003 1.04.07 DES FEATURE The server sample will try a range of ports until it gets a successful bind. + FEATURE The client can specify which port to connect to on the server. +05-20-2003 1.04.06 DES FIX Fixed gcd_shutdown to not close the socket if using CDKey integration. +04-22-2003 1.04.05 DES RELEASE Releasing to developer site. + FIX Updated to use the new name of the global QR2 structure. + FIX Metrowerks for Win32 is no longer falsely identified as MacOS. +03-03-2003 1.04.04 DES CLEANUP General cleanup to remove warnings. +01-06-2003 1.04.03 DES FEATURE Added integration with QR2, including a new test project, gcdkeyserver_qr2. +12-19-2002 1.04.02 DES RELEASE Releasing to developer site. +12-19-2002 1.04.02 DES CLEANUP Removed assert.h include. +12-16-2002 1.04.01 DES FIX Set listen calls in samples to use SOMAXCONN for the backlog paramter. +10-24-2002 1.04.00 DES RELEASE Release on developer site with updated documentation +10-22-2002 1.04.00 DES RELEASE Release on developer site +10-21-2002 1.04.00 DDW FEATURE Added ability to support multiple products simultaneously + Games must now pass productID to the authenticate and disconnect functions + The productID is now passed in the authentication callback +09-25-2002 1.03.00 DDW OTHER Changelog started diff --git a/code/gamespy/gcdkey/gcdkeyc.c b/code/gamespy/gcdkey/gcdkeyc.c new file mode 100644 index 00000000..027e3206 --- /dev/null +++ b/code/gamespy/gcdkey/gcdkeyc.c @@ -0,0 +1,69 @@ +/****** +gcdkeyc.c +GameSpy CDKey SDK Client Code + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + + Please see the GameSpy CDKey SDK documentation for more + information + +******/ +#include "../md5.h" +#include "gcdkeyc.h" +#include +#include +#include +#include + +#define RAWSIZE 512 + +#ifdef __cplusplus +extern "C" { +#endif + + // method = 0 for normal auth response from game server + // method = 1 for reauth response originating from keymaster +void gcd_compute_response(char *cdkey, char *challenge, char response[RESPONSE_SIZE], CDResponseMethod method) +{ + char rawout[RAWSIZE]; + unsigned int anyrandom; + char randstr[9]; + + + /* check to make sure we weren't passed a huge cd key/challenge */ + if (strlen(cdkey) * 2 + strlen(challenge) + 8 >= RAWSIZE) + { + strcpy(response,"CD Key or challenge too long"); + return; + } + + /* make sure we are randomized */ + srand((unsigned int)time(NULL) ^ 0x33333333); + /* Since RAND_MAX is 16 bit on many systems, make sure we get a 32 bit number */ + anyrandom = (rand() << 16 | rand()); + sprintf(randstr,"%.8x",anyrandom); + + /* auth response = MD5(cdkey + random mod 0xffff + challenge) */ + /* reauth response = MD5(challenge + random mode 0xffff + cdkey) */ + if (method == 0) + sprintf(rawout, "%s%d%s",cdkey, anyrandom % 0xFFFF , challenge ); + else + sprintf(rawout, "%s%d%s",challenge, anyrandom % 0xFFFF, cdkey); + + /* do the cd key md5 */ + MD5Digest((unsigned char *)cdkey, strlen(cdkey), response); + /* add the random value */ + strcpy(&response[32], randstr); + /* do the response md5 */ + MD5Digest((unsigned char *)rawout, strlen(rawout), &response[40]); +} + + +#ifdef __cplusplus +} +#endif + diff --git a/code/gamespy/gcdkey/gcdkeyc.h b/code/gamespy/gcdkey/gcdkeyc.h new file mode 100644 index 00000000..f3b1994d --- /dev/null +++ b/code/gamespy/gcdkey/gcdkeyc.h @@ -0,0 +1,40 @@ +/****** +gcdkeyc.h +GameSpy CDKey SDK Client Header + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + + Please see the GameSpy CDKey SDK documentation for more + information + +******/ + +#ifndef _GOACDKEYC_H_ +#define _GOACDKEYC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define RESPONSE_SIZE 73 + +typedef enum +{ + CDResponseMethod_NEWAUTH, // method = 0 for normal auth + CDResponseMethod_REAUTH // method = 1 for ison proof +} CDResponseMethod; + + +void gcd_compute_response(char *cdkey, char *challenge,/*out*/ char response[73], CDResponseMethod method); + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/code/gamespy/gcdkey/gcdkeys.c b/code/gamespy/gcdkey/gcdkeys.c new file mode 100644 index 00000000..7f880865 --- /dev/null +++ b/code/gamespy/gcdkey/gcdkeys.c @@ -0,0 +1,961 @@ +/****** +gcdkeys.c +GameSpy CDKey SDK Server Code + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + + Please see the GameSpy CDKey SDK documentation for more + information + +******/ + +/******** +INCLUDES +********/ + +#include "gcdkeys.h" +#include "../common/gsCommon.h" +#include "../common/gsAvailable.h" +#include "../common/gsDebug.h" +#include + +#ifdef GUSE_ASSERTS + #define gassert(a) assert(a) +#else + #define gassert(a) +#endif + +#define BUFSIZE 512 + +#ifdef __cplusplus +extern "C" { +#endif + +/******** +DEFINES +********/ +#define VAL_PORT 29910 +/* #define VAL_ADDR "key.gamespy.com" */ +/*#define VAL_ADDR "204.182.161.103"*/ +#define VAL_TIMEOUT 2000 +#define VAL_RETRIES 2 +#define INBUF_LEN 1024 +#define MAX_PRODUCTS 4 +#define MAX_KEEP_ALIVE_INTERVAL 20000 + +#define MAXPENDING_REAUTH 5 // prevent memory growth from spammed reauths. +#define REAUTH_LIFESPAN 5000 // prevent memory growth from unanswered reauths. +#define PROOF_TXT 'p','r','o','o','f' +#define IGNORED_TXT 's','e','e','d' + +/******** +TYPEDEFS +********/ +typedef enum {cs_sentreq, cs_gotok, cs_gotnok, cs_done} gsclientstate_t; + +typedef struct gsnode_s +{ + void *object; + struct gsnode_s *next, *prev; +} gsnode_t; + +typedef struct gsclient_s +{ + int localid; + char hkey[33]; + int sesskey; + int ip; + unsigned long sttime; + int ntries; + gsclientstate_t state; + void *instance; + AuthCallBackFn authfn; + RefreshAuthCallBackFn refreshauthfn; + char *errmsg; + char *reqstr; + int reqlen; + gsnode_t reauthq; +} gsclient_t; + +typedef struct gsreauth_s +{ + int sesskey; + char challenge[33]; + struct sockaddr_in fromaddr; + gsi_time starttime; +} gsreauth_t; + +typedef struct gsproduct_s +{ + int pid; + gsnode_t clientq; +} gsproduct_t; + + +/******** +GLOBALS +********/ +char gcd_hostname[64] = ""; + +/******** +PROTOTYPES +********/ +static void send_auth_req(gsproduct_t *prod, gsclient_t *client, const char *challenge, const char *response); +static void resend_auth_req(gsclient_t *client); +static void send_keep_alive(); +static void send_disconnect_req(gsproduct_t *prod, gsclient_t *client); +static void cdkey_process_buf(char *buf, int len, struct sockaddr *fromaddr); +static void process_oks(char *buf, int isok); +static void process_ison(char *buf, struct sockaddr_in *fromaddr); +static void process_ucount(char *buf, struct sockaddr_in *fromaddr); +static void send_uon(int skey, const char* ignored, const char* proof, struct sockaddr_in *fromaddr); +static void free_client_node(gsnode_t *node); + +static int get_sockaddrin(char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent); +static void xcode_buf(char *buf, int len); +static char *value_for_key(const char *s, const char *key); + + +static void add_to_queue(gsnode_t *t, gsnode_t *que); +static gsnode_t *remove_from_queue(gsnode_t *t, gsnode_t *que); +static int gcd_init_common(int gameid); +static int init_incoming_socket(); +static gsproduct_t *find_product(int gameid); + +/******** +VARS +********/ +static SOCKET sock = INVALID_SOCKET; +static unsigned short localport = 0; +static char enc[9]; /* used for xor encoding */ +static struct sockaddr_in valaddr; + +static int numproducts = 0; +gsproduct_t products[MAX_PRODUCTS]; + +/****************************************************************************/ +/* PUBLIC FUNCTIONS */ +/****************************************************************************/ + + +int gcd_init(int gameid) +{ + int ret; + const char defaulthost[] = {'k','e','y','.','g','a','m','e','s','p','y','.','c','o','m','\0'}; //key.gamespy.com + + // check if the backend is available + if(__GSIACResult != GSIACAvailable) + return -1; + + if (sock == INVALID_SOCKET) //hasn't been initialized yet + { + /* set up the UDP socket */ + SocketStartUp(); + ret = init_incoming_socket(); + if (ret < 0) + return ret; + + if (gcd_hostname[0] == 0) + strcpy(gcd_hostname, defaulthost); + get_sockaddrin(gcd_hostname,VAL_PORT,&valaddr,NULL); + } + + + return gcd_init_common(gameid); + +} + + +#ifdef QR2CDKEY_INTEGRATION +extern struct qr2_implementation_s static_qr2_rec; +int gcd_init_qr2(qr2_t qrec, int gameid) +{ + // check if the backend is available + if(__GSIACResult != GSIACAvailable) + return -1; + + if (qrec == NULL) + qrec = &static_qr2_rec; + + localport = (unsigned short)-1; /* we don't process any incoming data ourselves - it gets passed from the QR SDK */ + + sock = qrec->hbsock; + qrec->cdkeyprocess = cdkey_process_buf; + /* grab the outgoing address from the QR SDK */ + memset(&valaddr,0,sizeof(struct sockaddr_in)); + valaddr.sin_family = AF_INET; + valaddr.sin_port = htons((unsigned short)VAL_PORT); + valaddr.sin_addr.s_addr = qrec->hbaddr.sin_addr.s_addr; + return gcd_init_common(gameid); + +} + +#endif + + +void gcd_shutdown(void) +{ + int i; + /* Make sure everyone is disconnected */ + for (i = 0 ; i < numproducts ; i++) + gcd_disconnect_all(products[i].pid); + if(localport != (unsigned short)-1) + { + closesocket(sock); + SocketShutDown(); + } + sock = INVALID_SOCKET; + numproducts = 0; +} + + +void gcd_authenticate_user(int gameid, int localid, unsigned int userip, const char *challenge, + const char *response, AuthCallBackFn authfn, RefreshAuthCallBackFn refreshfn, void *instance) +{ + gsnode_t *node; + gsclient_t *client; + char hkey[33]; + char *errmsg = NULL; + char badcdkey_t[] = {'B','a','d',' ','C','D',' ','K','e','y','\0'}; //Bad CD Key + char keyinuse_t[] = {'C','D',' ','K','e','y',' ','i','n',' ','u','s','e','\0'}; //CD Key in use + gsproduct_t *prod = find_product(gameid); + + gassert(prod); + if (prod == NULL) + return; + + /* get the hashed key */ + strncpy(hkey, response, 32); + hkey[32] = 0; + + /* if response is bogus, lets kill them */ + if (strlen(response) < 72) + errmsg = goastrdup(badcdkey_t); + + /* First, scan the current list for the same, or similar client */ + node = &prod->clientq; + while ((node = node->next) != NULL) + { + /* make sure the localid isn't being reused + Change this code if you want to allow multiple users with the same CD Key on the + same server */ + gsclient_t* client = (gsclient_t*)node->object; + gassert(client->localid != localid); + if (strcmp(hkey, client->hkey) == 0) + { /* they appear to be on already!! */ + errmsg = goastrdup(keyinuse_t); + break; + } + } + + /* Create a new client */ + client = (gsclient_t *)gsimalloc(sizeof(gsclient_t)); + gassert(client); + client->localid = localid; + client->ip = (int)userip; + client->instance = instance; + client->errmsg = NULL; + client->reqstr = NULL; + client->authfn = authfn; + client->refreshauthfn = refreshfn; + client->reauthq.next = NULL; + client->reauthq.object = NULL; + client->reauthq.prev = NULL; + strcpy(client->hkey, hkey); + node = (gsnode_t *)gsimalloc(sizeof(gsnode_t)); + gassert(node); + node->object = (void*)client; + add_to_queue(node, &prod->clientq); + + if (errmsg != NULL) + { /* there was already and error, mark them to die */ + client->state = cs_gotnok; + client->errmsg = errmsg; + } else /* They aren't on this server, lets check the validation server */ + send_auth_req(prod, client,challenge, response); +} + +void gcd_process_reauth(int gameid, int localid, int skey, const char *response) +{ + // find the pending reauth attempt + gsnode_t *clientnode; + gsnode_t *reauthnode; + gsproduct_t *prod = find_product(gameid); + + gassert(prod); + if (prod == NULL) + return; + + // find the client for this gameid + clientnode = &prod->clientq; + while ((clientnode = clientnode->next) != NULL) + { + gsclient_t *client = (gsclient_t*)clientnode->object; + if (client->localid == localid) + { + // find the reauth info for this client/skey + reauthnode = &client->reauthq; + while((reauthnode = reauthnode->next) != NULL) + { + gsreauth_t *reauth = (gsreauth_t*)reauthnode->object; + if (reauth->sesskey == skey) + { + // send the proof to the keymaster + send_uon(skey, "", response, &reauth->fromaddr); + remove_from_queue(reauthnode, &client->reauthq); + gsifree(reauthnode->object); + gsifree(reauthnode); + return; + } + } + } + } +} + +// utility to free memory associated with a client node +static void free_client_node(gsnode_t *node) +{ + if (node) + { + gsclient_t* client = (gsclient_t*)node->object; + if (client) + { + if (client->reqstr != NULL) + gsifree(client->reqstr); + if (client->errmsg != NULL) + gsifree(client->errmsg); + + // free auth nodes + while (client->reauthq.next != NULL) + { + gsnode_t* authNode = remove_from_queue(client->reauthq.next, &client->reauthq); + gsifree(authNode->object); + gsifree(authNode); + } + gsifree(client); + } + gsifree(node); + } + return; +} + +void gcd_disconnect_user(int gameid, int localid) +{ + gsnode_t *node; + gsproduct_t *prod = find_product(gameid); + + gassert(prod); + if (prod == NULL) + return; + + /* First, scan the list for the client*/ + node = &prod->clientq; + while ((node = node->next) != NULL) + { + gsclient_t* client = (gsclient_t*)node->object; + if (client->localid == localid) + { + send_disconnect_req(prod, client); + remove_from_queue(node, &prod->clientq); + free_client_node(node); + return; + } + } + /* No client found -- we should never get here! + But we may if you call disconnect_user during an negative authentication + (they are already removed) */ + +} + +void gcd_disconnect_all(int gameid) +{ + gsnode_t *node; + + gsproduct_t *prod = find_product(gameid); + + gassert(prod); + if (prod == NULL) + return; + + /* Clear the entire list */ + node = &prod->clientq; + while ((node = node->next) != NULL) + { + gsclient_t* client = (gsclient_t*)node->object; + send_disconnect_req(prod, client); + remove_from_queue(node, &prod->clientq); + free_client_node(node); + node = &prod->clientq; + } +} + +char *gcd_getkeyhash(int gameid, int localid) +{ + + gsproduct_t *prod = find_product(gameid); + gsnode_t *node; + + gassert(prod); + if (prod == NULL) + return ""; + + node = &prod->clientq; + + /* Scan the list for the client*/ + while ((node = node->next) != NULL) + { + gsclient_t* client = (gsclient_t*)node->object; + if (client->localid == localid) + return client->hkey; + } + return ""; +} + +void gcd_think(void) +{ + static char indata[INBUF_LEN]; + struct sockaddr_in saddr; + int saddrlen = sizeof(saddr); + fd_set set; + struct timeval timeout = {0,0}; + int error; + int i; + gsnode_t *node, *oldnode; + char validated_t[] = {'V','a','l','i','d','a','t','e','d','\0'}; //Validated + char timeout_t[] = {'V','a','l','i','d','a','t','i','o','n',' ','T','i','m','e','o','u','t','\0'}; //Validation Timeout + + gassert (sock != INVALID_SOCKET); + /* First, check for data on the socket and process commands */ + + if (localport != (unsigned short)-1) /* don't check if we are getting data from the QR SDK instead */ + { + FD_ZERO ( &set ); + FD_SET ( sock, &set ); + while (1) + { + error = select(FD_SETSIZE, &set, NULL, NULL, &timeout); + if (gsiSocketIsError(error) || 0 == error) + break; + /* else we have data */ + error = recvfrom(sock, indata, INBUF_LEN - 1, 0, (struct sockaddr *)&saddr, &saddrlen); + if (gsiSocketIsNotError(error)) + { + indata[error] = '\0'; + cdkey_process_buf(indata, error, (struct sockaddr *)&saddr); + } + } + } + + send_keep_alive(); + + for (i = 0 ; i < numproducts ; i++) + { + /* Next, update the status of any clients and make callbacks */ + node = &products[i].clientq; + while ((node = node->next) != NULL) + { + gsclient_t* client = (gsclient_t*)node->object; + switch (client->state) + { + case cs_sentreq: + if (current_time() < client->sttime + VAL_TIMEOUT) + break; /* keep waiting */ + if (client->ntries <= VAL_RETRIES) + { /* resend */ + resend_auth_req(client); + break; + } /* else, go ahead an auth them, the val server timed out */ + case cs_gotok: + /* if authorized or they timed out with no response, just auth them */ + client->authfn(products[i].pid, client->localid, 1, + client->state == cs_gotok ? validated_t : timeout_t, + client->instance); + client->state = cs_done; + gsifree(client->reqstr); + client->reqstr = NULL; + break; + case cs_gotnok: + /* remove them first, in case the user calls disconnect */ + oldnode = node; + node = node->prev; + remove_from_queue(oldnode, &products[i].clientq); + + client->authfn(products[i].pid, client->localid, 0, + client->errmsg == NULL ? "" : client->errmsg, + client->instance); + free_client_node(oldnode); + break; + case cs_done: + // check pending reauth timeouts + if (client->reauthq.next != NULL) + { + // always look at "next" because we may remove nodes + gsnode_t* authnode = &client->reauthq; + while(authnode->next != NULL) + { + gsreauth_t* authdata = (gsreauth_t*)authnode->next->object; + gsi_time now = current_time(); + if ((now - authdata->starttime) > REAUTH_LIFESPAN) + { + gsDebugFormat(GSIDebugCat_CDKey, GSIDebugType_Misc, GSIDebugLevel_Notice, + "Removing timed out reauth request [localid: %d, from: %s\r\n", + client->localid, inet_ntoa(authdata->fromaddr.sin_addr)); + + // timed out, delete it + remove_from_queue(authnode->next, &client->reauthq); + gsifree(authdata); + gsifree(authnode->next); + authnode->next = NULL; + } + else + authnode = authnode->next; + } + } + break; + default: + break; + } + } + } +} + + +/****************************************************************************/ +/* UTIL FUNCTIONS */ +/****************************************************************************/ +static void cdkey_process_buf(char *buf, int len, struct sockaddr *fromaddr) +{ + char tok[32]; + char *pos; + char uok_t[] = {'u','o','k','\0'}; //uok + char unok_t[] = {'u','n','o','k','\0'}; //unok + char ison_t[] = {'i','s','o','n','\0'}; //ison + char ucount_t[] = {'u','c','o','u','n','t','\0'}; //ucount + xcode_buf(buf, len); + + tok[0] = 0; + if (buf[0] == '\\') + { + pos = strchr(buf+1,'\\'); + if (pos && (pos - buf <= 32)) /* right size token */ + { + strncpy(tok, buf+1,pos-buf-1); + tok[pos-buf-1] = 0; + } + } + if (!tok[0]) + return; /* bad command */ + + if (!strcmp(tok, uok_t)) + { + process_oks(buf, 1); + } + else if (!strcmp(tok, unok_t)) + { + process_oks(buf, 0); + } + else if (!strcmp(tok, ison_t)) + { + process_ison(buf, (struct sockaddr_in *)fromaddr); + } + else if (!strcmp(tok, ucount_t)) + { + process_ucount(buf, (struct sockaddr_in *)fromaddr); + } + else + { + send_keep_alive(); + return; /* bad command */ + } + send_keep_alive(); +} + +static int init_incoming_socket() +{ + int ret; + struct sockaddr_in saddr; + int saddrlen; + + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock == INVALID_SOCKET) + return -1; + get_sockaddrin(NULL,0,&saddr,NULL); + ret = bind(sock, (struct sockaddr *)&saddr, sizeof(saddr)); + if (gsiSocketIsError(ret)) + return -1; + + saddrlen = sizeof(saddr); + ret = getsockname(sock,(struct sockaddr *)&saddr, &saddrlen); + if (gsiSocketIsError(ret)) + return -1; + localport = saddr.sin_port; + + return 0; +} + +static int gcd_init_common(int gameid) +{ + gsproduct_t *prod; + gassert(numproducts < MAX_PRODUCTS); + if (numproducts >= MAX_PRODUCTS) + return -1; //too many products + prod = &products[numproducts++]; + prod->pid = gameid; + prod->clientq.next = NULL; + prod->clientq.prev = NULL; + prod->clientq.object = NULL; + srand((unsigned int)current_time()); + enc[0]='g';enc[1]='a';enc[2]='m';enc[3]='e'; + enc[4]='s';enc[5]='p';enc[6]='y';enc[7]=0; + return 0; +} +static gsclient_t *find_client(char *keyhash, int sesskey, int* productid) +{ + gsnode_t *node; + int i; + + for (i = 0 ; i < numproducts ; i++) + { + node = &products[i].clientq; + while ((node = node->next) != NULL) + { + gsclient_t* client = (gsclient_t*)node->object; + if (strcmp(keyhash, client->hkey) == 0 && (sesskey == -1 || client->sesskey == sesskey)) + { + if (productid != NULL) + *productid = products[i].pid; + return client; + } + } + } + return NULL; +} + +static void process_oks(char *buf, int isok) +{ + int sesskey; + char keyhash[33]; + gsclient_t *client; + const char skey_t[] = {'s','k','e','y','\0'}; //skey + const char cd_t[] = {'c','d','\0'}; //cd + const char errmsg_t[] = {'e','r','r','m','s','g','\0'}; //errmsg + +/* Samples +\uok\\cd\fe6667736f0c8ed7ff5cd9c0e74f\skey\2342 +\unok\\cd\fe6667736f0c8ed7ff5cd9c0e74f\skey\23423\errmsg\Already playing on xyz server */ + sesskey = atoi(value_for_key(buf,skey_t)); + strncpy(keyhash,value_for_key(buf,cd_t),32); + keyhash[32] = 0; + + client = find_client(keyhash, sesskey, NULL); + if (!client) + return; + if (client->sesskey != sesskey) /* bad session key */ + return; + if (client->state == cs_done) /* too late to do anything! */ + return; + if (isok) + client->state = cs_gotok; + else + { + client->state = cs_gotnok; + client->errmsg = goastrdup(value_for_key(buf,errmsg_t)); + } + +} +static void process_ucount(char *buf, struct sockaddr_in *fromaddr) +{ + char outbuf[64]; + char ucountformat[] = {'\\','u','c','o','u','n','t','\\','%','d','\0'}; //\\ucount\\%d + char pid_t[] = {'p','i','d','\0'}; //pid + gsnode_t *node; + int count = 0; + int len; + gsproduct_t *prod; + char *pos = value_for_key(buf, pid_t); + if (pos[0] == 0 && numproducts > 0) //not present.. use the first product + prod = &products[0]; + else + prod = find_product(atoi(pos)); + if (prod != NULL) + { + node = &prod->clientq; + while ((node = node->next) != NULL) + { + count++; + } + } + len = sprintf(outbuf, ucountformat, count); + xcode_buf(outbuf, len); + sendto(sock, outbuf, len, 0, (struct sockaddr *)fromaddr, sizeof(struct sockaddr_in)); +} + +static void send_uon(int skey, const char* ignored, const char* proof, struct sockaddr_in *fromaddr) +{ + char outbuf[256]; + int len; + const char uonformat[] = {'\\','u','o','n','\\','\\','s','k','e','y','\\','%','d','\\',IGNORED_TXT,'\\','%','s','\\',PROOF_TXT,'\\','%','s','\0'}; //\\uon\\\\skey\\%d\\seed\\%s\\proof\\%s +/* \ison\\cd\fe6667736f0c8ed7ff5cd9c0e74f\skey\32423 */ +/* \uon\\skey\32423\seed\\proof\ OR \un\skey\32423\proof\fe6667736f0c8ed7ff5cd9c0e74f OR \uoff\\skey\32423 */ + + // seed is ignored by server + len = snprintf(outbuf, 255, uonformat,skey, ignored, proof); + outbuf[255] = '\0'; // snprintf doesn't null terminate in some cases + xcode_buf(outbuf, len); + sendto(sock, outbuf, len, 0, (struct sockaddr *)fromaddr, sizeof(struct sockaddr_in)); + + gsDebugFormat(GSIDebugCat_CDKey, GSIDebugType_Network, GSIDebugLevel_Notice, + "Sent uon response (ison) to %s. (proof: %s)\r\n", + inet_ntoa(fromaddr->sin_addr), proof); +} + +static void send_uoff(int skey, struct sockaddr_in *fromaddr) +{ + char outbuf[64]; + int len; + const char uoffformat[] = {'\\','u','o','f','f','\\','\\','s','k','e','y','\\','%','d','\0'}; //\\uoff\\\\skey\\%d + len = sprintf(outbuf, uoffformat,skey); + xcode_buf(outbuf, len); + sendto(sock, outbuf, len, 0, (struct sockaddr *)fromaddr, sizeof(struct sockaddr_in)); +} + +static int get_queue_size(gsnode_t* node) +{ + int count = 0; + if (!node) + return 0; + if (node->object) // starting from a valid node + count++; + while (node->next != NULL) + { + count++; + node = node->next; + } + return count; +} + +static void process_ison(char *buf, struct sockaddr_in *fromaddr) +{ + int sesskey; + int productid; + char* proofchallenge; + + gsclient_t *client; + + const char proofchallenge_t[] = {'p','c','h','\0'}; // proof challenge + const char skey_t[] = {'s','k','e','y','\0'}; //skey + const char cd_t[] = {'c','d','\0'}; //cd + + sesskey = atoi(value_for_key(buf,skey_t)); + proofchallenge = value_for_key(buf,proofchallenge_t); + if ( (client = find_client(value_for_key(buf,cd_t), -1, &productid)) != NULL + && (client->state == cs_done)) /* If they are connected, return on */ + { + // check the queue size to prevent memory growth (from malicious reauth requests) + int count = get_queue_size(&client->reauthq); + if (count < MAXPENDING_REAUTH) + { + // store the sesskey and fromaddr so we can respond with proof later + gsnode_t* node = (gsnode_t*)gsimalloc(sizeof(gsnode_t)); + gsreauth_t* reauthdata = (gsreauth_t*)gsimalloc(sizeof(gsreauth_t)); + gassert(node); + gassert(reauthdata); + + memcpy(reauthdata->challenge, proofchallenge, 32); + memcpy(&reauthdata->fromaddr, fromaddr, sizeof(struct sockaddr_in)); + reauthdata->sesskey = sesskey; + reauthdata->starttime = current_time(); + node->object = (void*)reauthdata; + add_to_queue(node, &client->reauthq); + + // send normal ison right away, later we'll followup with proof + // owatagusiam is ignored by server + send_uon(sesskey, "owatagusiam", "0", fromaddr); + + // notify developer that we need proof of "ison" + client->refreshauthfn(productid, client->localid, sesskey, proofchallenge, client->instance); + } + } + else + { + send_uoff(sesskey, fromaddr); + } +} + +static void send_disconnect_req(gsproduct_t *prod, gsclient_t *client) +{ + char buf[BUFSIZE]; + int len; + const char discformat[] = {'\\','d','i','s','c','\\','\\','p','i','d','\\','%','d','\\','c','d','\\','%','s','\\','i','p','\\','%','d','\0'}; //\\disc\\\\pid\\%d\\cd\\%s\\ip\\%d + +/* \disc\\pid\12\cd\fe6667736f0c8ed7ff5cd9c0e74f\ip\2342342 */ + len = sprintf(buf,discformat, + prod->pid, client->hkey,client->ip); + xcode_buf(buf, len); + sendto(sock, buf, len, 0, (struct sockaddr *)&valaddr, sizeof(valaddr)); +} + +static void send_auth_req(gsproduct_t *prod, gsclient_t *client, const char *challenge, const char *response) +{ + char buf[BUFSIZE]; + int len; + const char authformat[] = {'\\','a','u','t','h','\\','\\','p','i','d','\\','%','d','\\','c','h','\\','%','s','\\','r','e','s','p','\\','%','s','\\','i','p','\\','%','d','\\','s','k','e','y','\\','%','d','\\','r','e','q','p','r','o','o','f','\\','1','\\','\0'}; //\\auth\\\\pid\\%d\\ch\\%s\\resp\\%s\\ip\\%d\\skey\\%d\\reqproof\\1 + + client->state = cs_sentreq; + client->sesskey = (unsigned int)(rand() ^ current_time()) % 16384; + client->sttime = current_time(); + client->ntries = 1; +/* \auth\\pid\12\ch\efx3232\resp\fe6667736f0c8ed7ff5cd9c0e74f98fd69e4da39560b82f40a628522ed10f0165c1d44a0\ip\2342342\skey\132432 */ + len = snprintf(buf, BUFSIZE, authformat, + prod->pid, challenge, response, client->ip, client->sesskey); + buf[BUFSIZE-1] = '\0'; // sometimes snprintf doesn't null terminate + xcode_buf(buf, len); + sendto(sock, buf, len, 0, (struct sockaddr *)&valaddr, sizeof(valaddr)); + /* save a copy for resends */ + client->reqstr = (char *)gsimalloc(len); + memmove(client->reqstr, buf, len); + client->reqlen = len; +} + +static void resend_auth_req(gsclient_t *client) +{ + client->sttime = current_time(); + client->ntries++; + sendto(sock, client->reqstr, client->reqlen, 0, (struct sockaddr *)&valaddr, sizeof(valaddr)); +} + +static void send_keep_alive() +{ + static gsi_time lastKeepAliveSent = 0; + static const char *keepAlive = "\\ka\\\0"; + char buf[BUFSIZE]; + if (lastKeepAliveSent == 0) + lastKeepAliveSent = current_time(); + if (current_time() > lastKeepAliveSent + MAX_KEEP_ALIVE_INTERVAL) + { + strcpy(buf, keepAlive); + xcode_buf(buf, strlen(keepAlive)); + sendto(sock, buf, strlen(keepAlive), 0, (struct sockaddr *)&valaddr, sizeof(struct sockaddr_in)); + lastKeepAliveSent = current_time(); + } +} +/* value_for_key: this returns a value for a certain key in s, where s is a string +containing key\value pairs. If the key does not exist, it returns "" +Note: the value is stored in a common buffer. If you want to keep it, make a copy! */ +static char *value_for_key(const char *s, const char *key) +{ + static int valueindex; + char *pos,*pos2; + char slash_t[] = {'\\','\0'}; + char keyspec[256]; + static char value[2][256]; + + valueindex ^= 1; + strcpy(keyspec, slash_t); + strcat(keyspec,key); + strcat(keyspec,slash_t); + pos = strstr(s,keyspec); + if (!pos) + return ""; + pos += strlen(keyspec); + pos2 = value[valueindex]; + while (*pos && *pos != '\\' && (pos2 - value[valueindex] < 200)) + *pos2++ = *pos++; + *pos2 = '\0'; + return value[valueindex]; +} + +/* simple xor encoding */ +static void xcode_buf(char *buf, int len) +{ + int i; + char *pos = enc; + + for (i = 0 ; i < len ; i++) + { + buf[i] ^= *pos++; + if (*pos == 0) + pos = enc; + } +} + +/* Return a sockaddrin for the given host (numeric or DNS) and port) +Returns the hostent in savehent if it is not NULL */ +static int get_sockaddrin(char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent) +{ + struct hostent *hent = NULL; + char broadcast_t[] = {'2','5','5','.','2','5','5','.','2','5','5','.','2','5','5','\0'}; //255.255.255.255 + + memset(saddr,0,sizeof(struct sockaddr_in)); + saddr->sin_family = AF_INET; + saddr->sin_port = htons((unsigned short)port); + if (host == NULL) + saddr->sin_addr.s_addr = INADDR_ANY; + else + saddr->sin_addr.s_addr = inet_addr(host); + + if (saddr->sin_addr.s_addr == INADDR_NONE && strcmp(host,broadcast_t) != 0) + { + hent = gethostbyname(host); + if (!hent) + return 0; + saddr->sin_addr.s_addr = *(u_long *)hent->h_addr_list[0]; + } + if (savehent != NULL) + *savehent = hent; + return 1; +} + +static gsproduct_t *find_product(int gameid) +{ + int i; + for (i = 0 ; i < numproducts ; i++) + if (products[i].pid == gameid) + return &products[i]; + return NULL; +} + + +/*********** +Linked List Code +***********/ + + +/******* +add_to_queue +*******/ +static void add_to_queue(gsnode_t *t, gsnode_t *que) +{ + while(que->next) + que=que->next; + que->next = t; + t->prev = que; + t->next = NULL; +} + +/******* +remove_from_queue + +if NULL is given as first parameter, top list item is popped off + +item that is removed is returned, or NULL if not found +*******/ +static gsnode_t *remove_from_queue(gsnode_t *t, gsnode_t *que) +{ + + if(!t) t = que->next; + if(!t) return(NULL); + t->prev->next = t->next; + if(t->next) + t->next->prev = t->prev; + + return(t); +} + +#ifdef __cplusplus +} +#endif diff --git a/code/gamespy/gcdkey/gcdkeys.h b/code/gamespy/gcdkey/gcdkeys.h new file mode 100644 index 00000000..32579a0d --- /dev/null +++ b/code/gamespy/gcdkey/gcdkeys.h @@ -0,0 +1,124 @@ +/****** +gcdkeys.h +GameSpy CDKey SDK Server Header + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + + Please see the GameSpy CDKey SDK documentation for more + information + +******/ + + +#ifndef _GOACDKEYS_H_ +#define _GOACDKEYS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define GUSE_ASSERTS + +/***** +QR2CDKEY_INTEGRATION: This define controls where the functions needed to integrate +the networking of the Query & Reporting 2 SDK and CDKey SDKs are available. +If you intend to use the integration option for these SDKs, you must uncomment the +define below, or provide it as an option to your compiler. +*******/ +#define QR2CDKEY_INTEGRATION + +typedef void (*AuthCallBackFn)(int gameid, int localid, int authenticated, char *errmsg, void *instance); +typedef void (*RefreshAuthCallBackFn)(int gameid, int localid, int hint, char *challenge, void *instance); + +/* The hostname of the validation server. +If the app resolves the hostname, an +IP can be stored here before calling +gcd_init */ +extern char gcd_hostname[64]; + +/******** +gcd_init +Initializes the Server API and creates the socket +Should only be called once (unless gcd_shutdown has been called) +*********/ +int gcd_init(int gameid); + +/******** +gcd_init_qr2 +Initializes the Server API and integrates the networking of the CDKey SDK +with the Query & Reporting 2 SDK. +You must initialize the Query & Reporting 2 SDK with qr2_init or qr2_init_socket +prior to calling this. If you are using multiple instances of the QR2 SDK, you +can pass the specific instance information in via the "qrec" argument. Otherwise +you can simply pass in NULL. +*********/ +#ifdef QR2CDKEY_INTEGRATION + +#include "../qr2/qr2.h" +int gcd_init_qr2(qr2_t qrec, int gameid); + +#endif + +/******** +gcd_shutdown +Frees the socket and client structures +Also calls gcd_disconnect_all to make sure all users are signaled as offline +*********/ +void gcd_shutdown(void); + +/******** +gcd_authenticate_user +Creates a new client and sends a request for authorization to the +validation server. +*********/ +void gcd_authenticate_user(int gameid, int localid, unsigned int userip, const char *challenge, + const char *response, AuthCallBackFn authfn, RefreshAuthCallBackFn refreshfn, void *instance); + +/******** +gcd_authenticate_user +Creates a new client and sends a request for authorization to the +validation server. +*********/ +void gcd_process_reauth(int gameid, int localid, int hint, const char *response); + + +/******** +gcd_disconnect_user +Notify the validation server that a user has disconnected +*********/ +void gcd_disconnect_user(int gameid, int localid); + + +/******** +gcd_disconnect_all +Calls gcd_disconnect_user for each user still online (shortcut) +*********/ +void gcd_disconnect_all(int gameid); + +/******** +gcd_think +Processes any pending data from the validation server +and calls the callback to indicate whether a client was +authorized or not +*********/ +void gcd_think(void); + +/******** +gcd_getkeyhash +Returns the key hash for the given user. This hash will always +be the same for that users, which makes it good for banning or +tracking of users (used with the Tracking/Stats SDK). Returns +an empty string if that user isn't connected. +*********/ +char *gcd_getkeyhash(int gameid, int localid); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/code/gamespy/ghttp/changelog.txt b/code/gamespy/ghttp/changelog.txt new file mode 100644 index 00000000..b6ba926f --- /dev/null +++ b/code/gamespy/ghttp/changelog.txt @@ -0,0 +1,156 @@ +Changelog for: GameSpy HTTP SDK +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 1.11.00 RMV RELEASE Released to Developer Site +12-12-2007 1.10.05 RMV FIX Updated ghttpStreamEx call in ghttpc.c to use a URL requiring Nintendo certification, since it + uses REVOEXSSL +11-27-2007 1.10.04 SAH FIX Fixed bug where hostname lookup thread wasn't cancelled if request was + SAH CLEANUP Moved extern "c" block below includes to prevent linker errors +11-12-2007 1.10.03 SAH FIX Incorporated Nintendo suggestions for Built-in RevoEX SSL Certification +10-08-2007 1.10.02 BED RELEASE Limited Release +10-03-2007 1.10.02 BED FIX Switched use of GS_ASSERT(0) with GS_FAIL() +10-01-2007 1.10.01 BED FEATURE Added RevoEX SSL support +08-06-2007 1.10.00 RMV RELEASE Released to Developer Site +07-18-2007 1.09.03 SAH FIX Rolled back version of ghttpProcess to use stack allocation - DS changes were causing socket errors. +07-16-2007 1.09.02 RMV FIX Fixed URL for getting header in ghttpc +07-11-2007 1.09.02 RMV FIX Fixed ghttpc/ghttpmfc Project files to get rid of Unicode warnings and fixed other compiler warnings +04-19-2007 1.09.01 SAH FIX Fixed ghiDoReceivingHeaders/File to support DS limited stack size - uses heap allocation now. +03-05-2007 1.09.00 SAH RELEASE Released to Developer Site +03-02-2007 1.08.06 SN FIX Fixed some compiler warnings for code warrior +02-22-2007 1.08.05 SN FIX Changed parts of code that used strdup to use goastrdup +01-22-2007 1.08.04 SAH FIX Fixed unicode-specific bug for PostAddFileFromMemory +01-16-2007 1.08.03 DES FEATURE Added X360 support +01-04-2007 1.08.02 BED FIX Fixed PS3 Byte order bug in encryption algorithm +12-19-2006 1.08.01 BED FEATURE Added support for Unicode file names when saving to disk. +12-15-2006 1.08.00 MJW RELEASE Released to Developer Site +11-28-2006 1.07.49 SAH FIX Fixed previous change, postWaitContinue set to true for soap messages only +11-13-2006 1.07.48 SAH FIX Rolled back the 1.07.37 change, set postWaitContinue to FALSE. +11-10-2006 1.07.47 JR RELEASE Limited release +10-23-2006 1.07.47 DES RELEASE Limited release +10-05-2006 1.07.47 SAH FIX Updated MacOSX Makefile +09-28-2006 1.07.46 SAH FIX Fixed PS3 project to work with PS3 095.00x SDK; changed included libaries in linker input. +08-24-2006 1.07.45 SAH FIX Fixed VC7 project file +08-04-2006 1.07.44 SN FIX Fixed asynchronous DNS lookup code: removed unnecessary memory allocation +08-02-2006 1.07.43 SAH RELEASE Releasing to developer site +07-31-2006 1.07.43 SAH FIX Fixed PS3 project file - added post-build step to create *.SELF for execution +07-25-2006 1.07.42 SAH FIX Fixed NITRO project, include for crt0.o now above others so it add correct file +07-07-2006 1.07.41 SAH FIX Fixed HTTP DNS cleanup - needed to explicitly free handle pointer memory +07-06-2006 1.07.40 SAH FIX Fixed PSP project file to not explicitly include the PSP linker file +07-05-2006 1.07.39 SAH FIX Fixed postWaitContinue functionality to remain false when waiting + SAH FIX Fixed log call define +06-30-2006 1.07.38 SAH FIX Fixed Linux makefile +06-30-2006 1.07.37 SAH FIX Uncommented postWaitContinue if statement (caused extra newline sent in posts) + SAH FIX Fixed Nitro project file & linker command file to work with CW 2.0/NitroSDK 3.1 +06-20-2006 1.07.36 SAH FEATURE Added asynchronous DNS lookup for LINUX (pthreads) + SAH OTHER Added some more debugging statements for failed host lookups + SAH FIX Fixed Linux makefile - added "-lpthread" compiler option for asynch DNS +06-09-2006 1.07.35 SAH FEATURE Added asynchronous DNS lookup support for WIN32 (multi-threaded) +05-31-2006 1.07.34 SAH RELEASE Releasing to developer site +05-25-2006 1.07.33 SAH FIX Added GSI_UNUSED calls to get rid of PSP warnings + SAH FIX Changed PSP project warning levels + SAH FIX Fixed PS3 project to compile with 084_001 SDK +05-24-2006 1.07.32 SAH FIX Uncommented secure page test in gptestc.c +05-19-2006 1.07.31 SAH FIX Added gsTestMain.c to nitro CodeWarrior Project +05-18-2006 1.07.30 BED FIX Fixed bug with byte alignment for SSL on DS/PS3 + BED FIX Fixed bug where a line was mistakenly removed from previous version. +05-18-2006 1.07.29 DES RELEASE Limited developer release +05-18-2006 1.07.29 SAH FIX Added a check in ghiDoSend to ensure buffer != NULL and len != 0 +05-15-2006 1.07.28 SAH FIX Added "PS3 Release" configuration to project +05-08-2006 1.07.27 SAH FIX Fixed PS2 Project file (was missing gsXML) +05-02-2006 1.07.26 BED FIX Removed "expect continue" functionality that (may have) caused issues with some UK web proxies. +04-25-2006 1.07.25 SAH RELEASE Releasing to developer site +04-24-2006 1.07.25 SAH FIX added typecasts, fixed #includes to get rid of warnings in CW +04-20-2006 1.07.24 SAH FIX got rid of unused variables, switched GSI_USUNUSED above the returns +04-19-2006 1.07.23 SAH FIX Bill switched some byte ordering for PS3 compatibility +04-18-2006 1.07.22 SAH FIX Added || defined(_PS3) for PS3 support +04-13-2006 1.07.21 SAH FIX Replaced all (rcode == SOCKET_ERROR) w/ (gsiSocketIsError(rcode)) +02-21-2006 1.07.20 BED OTHER Adjusted debug output level for http continue. +01-26-2006 1.07.19 SN FIX Added psp to the test_main definition + SN OTHER Added and updated psp prodg project and solution +01-26-2006 1.07.18 BED FIX Added error checking between REQUEST and POST. + BED OTHER HTTPS requests now default to GameSpy SSL encryptor. + BED FIX Fixed an efficiency bug with plain text posts. +01-20-2006 1.07.17 BED FEATURE Added support for DIME attachments. +01-13-2006 1.07.16 BED FEATURE Added ghttpPostAddXml to support http soap requests. +12-16-2005 1.07.15 SN OTHER Cleaned up project files and added missing common code if any +11-17-2005 1.07.15 DES FIX Updated Nitro makefile. +11-14-2005 1.07.14 DES FIX Updated the makefiles and project files. + DES FIX Fixed a few minor HTTPS bugs. + DES FEATURE Updated MFC sample to work with HTTPS. +09-23-2005 1.07.13 DES FEATURE Updated DS support +07-28-2005 1.07.12 SN RELEASE Releasing to developer site. +07-28-2005 1.07.12 SN FEATURE Added HTTP SSL common code to projects +06-03-2005 1.07.11 SN RELEASE Releasing to developer site. +06-01-2004 1.07.11 SN FIX Fixed the function ghiParseStatus to find the status string after it checks for correct header information. +05-09-2005 1.07.10 BED FEATURE Added ghttpCloseRequest to handle graceful shutdowns +05-08-2005 1.07.09 DES FIX CompletedCallback now gets the file length for Save and Stream requests. +05-05-2005 1.07.08 BED FIX Updated project files to use new common folder +04-29-2005 1.07.07 SN OTHER Created Visual Studio .NET projects +04-28-2005 1.07.07 SN RELEASE Releasing to developer site. +04-27-2005 1.07.07 DES RELEASE Limited release to Nintendo DS developers. +04-25-2005 1.07.07 DES FEATURE Added debug logging. + DES CLEANUP Removed old commented-out code. + DES CLEANUP General cleanup of ghttpc. +04-12-2005 1.07.06 BED FIX Fixed bug where User-Agent header could appear twice. +04-04-2005 1.07.05 SN RELEASE Releasing to developer site. +03-29-2005 1.07.05 DES FIX Fixed warnings for internal functions +03-14-2005 1.07.04 DES FEATURE Nintendo DS support +01-12-2004 1.07.03 BED FIX Was missing a #ifdef around matrixssl.h +12-20-2004 1.07.02 BED FIX Fixed ghttpGetHeaders with SSL support. +12-06-2004 1.07.01 BED FEATURE Added initial support for SSL connections. +11-19-2004 1.07.00 DDW FEATURE Added internal support for persistent server connections +09-16-2004 1.06.25 SN RELEASE Releasing to developer site. +08-27-2004 1.06.25 DES CLEANUP Removed MacOS style includes + DES CLEANUP General Unicode cleanup + DES CLEANUP Updated Win32 project configurations + DES CLEANUP Fixed warnings under OSX + DES CLEANUP Updated OSX Makefile +08-13-2004 1.06.24 SN FIX Changed asserts in ghiParseProxyServer to reflect the correct checks on parameters +08-04-2004 1.06.23 SN RELEASE Releasing to developer site. +07-19-2004 1.06.23 SN FIX Updated code with explicit casts to remove implicit cast error + when compiling at highest level and warnings treated as errors. +06-18-2004 1.06.22 BED RELEASE Releasing to developer site. +06-17-2004 1.06.22 BED FIX ghttpc sample no longer builds UNICODE mode by default + BED FEATURE Added PS2 Insock support +01-26-2004 1.06.21 BED FEATURE Added the ability to set a "proxy override" on each request. +11-10-2003 1.06.20 DES RELEASE Releasing to developer site. +11-07-2003 1.06.20 DES FIX Updated the linux makefile. +10-21-2003 1.06.19 BED RELEASE Releasing to developer site. (UNIQUE NICK AND UNICODE SUPPORT) +10-20-2003 1.06.19 BED FEATURE Added ghttpSetMaxRecvTime() to prevent async downloads from blocking in special cases +01-05-2003 1.06.18 JED FEATURE Added download size checking and GHTTPFileToBig error code +09-30-2003 1.06.17 BED FEATURE Modified sample app to check (result < 0) instead of (result == -1) to handle additional error codes. +09-16-2003 1.06.16 JED FIX Added stringutil.c/h incudes to ghttp.dsp +09-08-2003 1.06.15 BED FEATURE Added wrapper for UNICODE support. (Two byte UNICODE converted to single byte ASCII) + BED FEATURE Added new return status "GHTTPFileIncomplete" to notify of a partial or interrupted download + BED FEATURE Added extra error return codes per JED request. To enable these define GHTTP_EXTENDEDERROR +07-24-2003 1.06.14 DES RELEASE Releasing to developer site. +07-18-2003 1.06.14 BED FEATURE Added CodeWarrior (PS2) sample project file. + BED CLEANUP General cleanup to remove CodeWarrior warnings. +07-17-2003 1.06.13 DES CLEANUP Cleaned up the PS2 Makefile, it now uses Makefile.commmon. +07-16-2003 1.06.12 DES FIX Changed a __mips64 checks to a _PS2 check. + BED FEATURE Added ProDG sample project files. +07-15-2003 1.06.11 DES RELEASE Releasing to developer site. +07-10-2003 1.06.11 BED FIX Added newline to end of file to prevent compiler warning. +05-09-2003 1.06.10 DES CLEANUP Removed Dreamcast support. + FIX Metrowerks for Win32 is no longer falsely identified as MacOS. +04-15-2003 1.06.09 JED CLEANUP More cleanup to remove a few DevStudio Level4 warnings +03-03-2003 1.06.08 DES CLEANUP General cleanup to remove warnings. +01-28-2003 1.06.07 DES FIX Fixed a bug which could cause a request to fail if a 1xx (Continue) + status response was received (common when posting). + CLEANUP Changed posting code to send no more than 32K in a single send() call. + Win98 was having trouble dealing with large blocks of data. +01-23-2003 1.06.06 DES FIX Replaced a malloc with gsimalloc. +01-15-2003 1.06.05 DES FIX Fixed a crashing bug involving parsing received data that started + with the last byte of a chunk header. +12-19-2002 1.06.04 DES RELEASE Releasing to developer site. +12-19-2002 1.06.04 DES CLEANUP Removed assert.h include. +12-16-2002 1.06.03 DES CLEANUP Removed call to GOAClearSocketError. +12-13-2002 1.06.02 DES FEATURE Added PS2 eenet stack support. + CLEANUP Cleaned up code to remove PS2 compiler warnings. +11-14-2002 1.06.01 DES FIX Send "Connection: close" in request header to be 1.1 compliant + FIX Fixed bug with reading end of chunked header + OTHER Removed "#if 1" block from around chunked transfer code + OTHER Each connection gets a unique ID (for debugging, not exposed in API) +09-25-2002 1.06.00 DDW OTHER Changelog started diff --git a/code/gamespy/ghttp/ghttp.h b/code/gamespy/ghttp/ghttp.h new file mode 100644 index 00000000..8e72c503 --- /dev/null +++ b/code/gamespy/ghttp/ghttp.h @@ -0,0 +1,644 @@ + /* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GHTTP_H_ +#define _GHTTP_H_ + +#include + +#include "../common/gsCommon.h" +#include "../common/gsXML.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef GSI_UNICODE +#define ghttpGet ghttpGetA +#define ghttpGetEx ghttpGetExA +#define ghttpSave ghttpSaveA +#define ghttpSaveEx ghttpSaveExA +#define ghttpStream ghttpStreamA +#define ghttpStreamEx ghttpStreamExA +#define ghttpHead ghttpHeadA +#define ghttpHeadEx ghttpHeadExA +#define ghttpPost ghttpPostA +#define ghttpPostEx ghttpPostExA +#define ghttpPostAddString ghttpPostAddStringA +#define ghttpPostAddFileFromDisk ghttpPostAddFileFromDiskA +#define ghttpPostAddFileFromMemory ghttpPostAddFileFromMemoryA +#else +#define ghttpGet ghttpGetW +#define ghttpGetEx ghttpGetExW +#define ghttpSave ghttpSaveW +#define ghttpSaveEx ghttpSaveExW +#define ghttpStream ghttpStreamW +#define ghttpStreamEx ghttpStreamExW +#define ghttpHead ghttpHeadW +#define ghttpHeadEx ghttpHeadExW +#define ghttpPost ghttpPostW +#define ghttpPostEx ghttpPostExW +#define ghttpPostAddString ghttpPostAddStringW +#define ghttpPostAddFileFromDisk ghttpPostAddFileFromDiskW +#define ghttpPostAddFileFromMemory ghttpPostAddFileFromMemoryW +#endif + +// Boolean. +/////////// +typedef enum +{ + GHTTPFalse, + GHTTPTrue +} GHTTPBool; + +// ByteCount. +///////////// +#if (GSI_MAX_INTEGRAL_BITS >= 64) +typedef gsi_i64 GHTTPByteCount; +#else +typedef gsi_i32 GHTTPByteCount; +#endif + +// The current state of an http request. +//////////////////////////////////////// +typedef enum +{ + GHTTPSocketInit, // Socket creation and initialization. + GHTTPHostLookup, // Resolving hostname to IP (asynchronously if possible). + GHTTPLookupPending, // Asychronous DNS lookup pending. + GHTTPConnecting, // Waiting for socket connect to complete. + GHTTPSecuringSession, // Setup secure channel. + GHTTPSendingRequest, // Sending the request. + GHTTPPosting, // Positing data (skipped if not posting). + GHTTPWaiting, // Waiting for a response. + GHTTPReceivingStatus, // Receiving the response status. + GHTTPReceivingHeaders, // Receiving the headers. + GHTTPReceivingFile // Receiving the file. +} GHTTPState; + +// The result of an http request. +///////////////////////////////// +typedef enum +{ + GHTTPSuccess, // 0: Successfully retrieved file. + GHTTPOutOfMemory, // 1: A memory allocation failed. + GHTTPBufferOverflow, // 2: The user-supplied buffer was too small to hold the file. + GHTTPParseURLFailed, // 3: There was an error parsing the URL. + GHTTPHostLookupFailed, // 4: Failed looking up the hostname. + GHTTPSocketFailed, // 5: Failed to create/initialize/read/write a socket. + GHTTPConnectFailed, // 6: Failed connecting to the http server. + GHTTPBadResponse, // 7: Error understanding a response from the server. + GHTTPRequestRejected, // 8: The request has been rejected by the server. + GHTTPUnauthorized, // 9: Not authorized to get the file. + GHTTPForbidden, // 10: The server has refused to send the file. + GHTTPFileNotFound, // 11: Failed to find the file on the server. + GHTTPServerError, // 12: The server has encountered an internal error. + GHTTPFileWriteFailed, // 13: An error occured writing to the local file (for ghttpSaveFile[Ex]). + GHTTPFileReadFailed, // 14: There was an error reading from a local file (for posting files from disk). + GHTTPFileIncomplete, // 15: Download started but was interrupted. Only reported if file size is known. + GHTTPFileToBig, // 16: The file is to big to be downloaded (size exceeds range of interal data types) + GHTTPEncryptionError, // 17: Error with encryption engine. + GHTTPRequestCancelled // 18: User requested cancel and/or graceful close. +} GHTTPResult; + +// Encryption engines +typedef enum +{ + GHTTPEncryptionEngine_None, + GHTTPEncryptionEngine_GameSpy, // must add /common/gsSSL.h and /common/gsSSL.c to project + GHTTPEncryptionEngine_MatrixSsl, // must define MATRIXSSL and include matrixssl source files + GHTTPEncryptionEngine_RevoEx, // must define REVOEXSSL and include RevoEX SSL source files + + GHTTPEncryptionEngine_Default // Will use GameSpy unless another engine is defined + // using MATRIXSSL or REVOEXSSL +} GHTTPEncryptionEngine; + +// Represents an http file request. +/////////////////////////////////// +typedef int GHTTPRequest; + +// Invalid GHTTPRequest values represent an error +/////////////////////////////////// +#ifdef GHTTP_EXTENDEDERROR + typedef enum + { + GHTTPErrorStart = -8, + GHTTPFailedToOpenFile, + GHTTPInvalidPost, + GHTTPInsufficientMemory, + GHTTPInvalidFileName, + GHTTPInvalidBufferSize, + GHTTPInvalidURL, + GHTTPUnspecifiedError = -1 + } GHTTPRequestError; +#else + // Backwards compatibility, developers may have relied on -1 as the only error code + typedef enum + { + GHTTPErrorStart = -1, + GHTTPFailedToOpenFile = -1, + GHTTPInvalidPost = -1, + GHTTPInsufficientMemory = -1, + GHTTPInvalidFileName = -1, + GHTTPInvalidBufferSize = -1, + GHTTPInvalidURL = -1, + GHTTPUnspecifiedError = -1 + } GHTTPRequestError; +#endif + +#define IS_GHTTP_ERROR(x) (x<0) + +// Data that can be posted to the server. +// Don't try to access this object directly, +// use the ghttpPost*() functions. +//////////////////////////////////////////// +typedef struct GHIPost * GHTTPPost; + + +// Called with updates on the current state of the request. +// The buffer should not be accessed once this callback returns. +// If ghttpGetFile[Ex] was used, buffer contains all of the data that has been +// received so far, and bufferSize is the total number of bytes received. +// If ghttpSaveFile[Ex] was used, buffer only contains the most recent data +// that has been received. This same data is saved to the file. The buffer +// will not be valid after this callback returns. +// If ghttpStreamFileEx was used, buffer only contains the most recent data +// that has been received. This data will be lost once the callback +// returns, and should be copied if it needs to be saved. bufferSize +// is the number of bytes in the current block of data. +////////////////////////////////////////////////////////////////////////////// +typedef void (* ghttpProgressCallback) +( + GHTTPRequest request, // The request. + GHTTPState state, // The current state of the request. + const char * buffer, // The file's bytes so far, NULL if state[:port]". If port is omitted, 80 will be used. +// If server is NULL or "", no proxy server will be used. +// This should not be called while there are any current requests. +////////////////////////////////////////////////////////////////// +GHTTPBool ghttpSetProxy +( + const char * server +); + +// Sets a proxy server for a specific request. The address should be of the +// form "[:port]". If port is omitted, 80 will be used. +// If server is NULL or "", no proxy server will be used. +////////////////////////////////////////////////////////////////// +GHTTPBool ghttpSetRequestProxy +( + GHTTPRequest request, + const char * server +); + +// Used to start/stop throttling an existing connection. +// This may not be as efficient as starting a request +// with the desired setting. +//////////////////////////////////////////////////////// +void ghttpSetThrottle +( + GHTTPRequest request, + GHTTPBool throttle +); + +// Used to adjust the throttle settings. +//////////////////////////////////////// +void ghttpThrottleSettings +( + int bufferSize, // The number of bytes to get each receive. + gsi_time timeDelay // How often to receive data, in milliseconds. +); + +// Used to throttle based on time, not on bandwidth +// Prevents recv-loop blocking on ultrafast connections without directly limiting transfer rate +//////////////////////////////////////// +void ghttpSetMaxRecvTime +( + GHTTPRequest request, + gsi_time maxRecvTime +); + +// Creates a new post object, which is used to represent data to send to +// the web server as part of a request. +// After getting the post object, use the ghttpPostAdd*() functions +// to add data to the object, and ghttPostSetCallback() to add a +// callback to monitor the progress of the data upload. +// By default post objects automatically free themselves after posting. +// To use the same post with more than one request, set auto-free to false, +// then use ghttpFreePost to free it _after_ every request its being used +// in is _completed_. +// Returns NULL on error. +/////////////////////////////////////////////////////////////////////////// +GHTTPPost ghttpNewPost +( + void +); + +// Sets a post object's auto-free flag. +// By default post objects automatically free themselves after posting. +// To use the same post with more than one request, set auto-free to false, +// then use ghttpFreePost to free it _after_ every request its being used +// in is _completed_. +/////////////////////////////////////////////////////////////////////////// +void ghttpPostSetAutoFree +( + GHTTPPost post, + GHTTPBool autoFree +); + +// Frees a post object. +/////////////////////// +void ghttpFreePost +( + GHTTPPost post // The post object to free. +); + +// Adds a string to the post object. +//////////////////////////////////// +GHTTPBool ghttpPostAddString +( + GHTTPPost post, // The post object to add to. + const gsi_char * name, // The name to attach to this string. + const gsi_char * string // The actual string. +); + +// Adds a disk file to the post object. +// The reportFilename is what is reported to the server as the filename. +// If NULL or empty, the filename will be used (including any possible path). +// The contentType is the MIME type to report for this file. +// If NULL, "application/octet-stream" is used. +// The file isn't read from until the data is actually sent to the server. +// Returns false for any error. +///////////////////////////////////////////////////////////////////////////// +GHTTPBool ghttpPostAddFileFromDisk +( + GHTTPPost post, // The post object to add to. + const gsi_char * name, // The name to attach to this file. + const gsi_char * filename, // The name (and possibly path) to the file to upload. + const gsi_char * reportFilename,// The filename given to the web server. + const gsi_char * contentType // The MIME type for this file. +); + +// Adds a file, in memory, to the post object. +// The reportFilename is what is reported to the server as the filename. +// Cannot be NULL or empty. +// The contentType is the MIME type to report for this file. +// If NULL, "application/octet-stream" is used. +// The data is NOT copied off in this call. The data pointer is read from +// as the data is actually sent to the server. The pointer must remain +// valid during requests. +// Returns false for any error. +////////////////////////////////////////////////////////////////////////// +GHTTPBool ghttpPostAddFileFromMemory +( + GHTTPPost post, // The post object to add to. + const gsi_char * name, // The name to attach to this string. + const char * buffer, // The data to send. + int bufferLen, // The number of bytes of data to send. + const gsi_char * reportFilename, // The filename given to the web server. + const gsi_char * contentType // The MIME type for this file. +); + +// Adds an XML SOAP object to the post object. +// See ghttpNewSoap and other Soap related functions +// Content-Type = text/xml +// The most common use of this function is to add ghttpSoap data +GHTTPBool ghttpPostAddXml +( + GHTTPPost post, + GSXmlStreamWriter xmlSoap +); + +// Called during requests to let the app know how much of the post +// data has been uploaded. +////////////////////////////////////////////////////////////////// +typedef void (* ghttpPostCallback) +( + GHTTPRequest request, // The request. + int bytesPosted, // The number of bytes of data posted so far. + int totalBytes, // The total number of bytes being posted. + int objectsPosted, // The total number of data objects uploaded so far. + int totalObjects, // The total number of data objects to upload. + void * param // User-data. +); + +// Set the callback for a post object. +////////////////////////////////////// +void ghttpPostSetCallback +( + GHTTPPost post, // The post object to set the callback on. + ghttpPostCallback callback, // The callback to call when using this post object. + void * param // User-data passed to the callback. +); + +// Use ssl encryption engine +GHTTPBool ghttpSetRequestEncryptionEngine +( + GHTTPRequest request, + GHTTPEncryptionEngine engine +); + + +// These are defined for backwards compatibility with the "file" function names. +//////////////////////////////////////////////////////////////////////////////// +#define ghttpGetFile(a, b, c, d) ghttpGet(a, b, c, d) +#define ghttpGetFileEx(a, b, c, d, e, f, g, h, i, j) ghttpGetEx(a, b, c, d, e, f, g, h, i, j) +#define ghttpSaveFile(a, b, c, d, e) ghttpSave(a, b, c, d, e) +#define ghttpSaveFileEx(a, b, c, d, e, f, g, h, i) ghttpSaveEx(a, b, c, d, e, f, g, h, i) +#define ghttpStreamFile(a, b, c, d, e) ghttpStream(a, b, c, d, e) +#define ghttpStreamFileEx(a, b, c, d, e, f, g, h) ghttpStreamEx(a, b, c, d, e, f, g, h) +#define ghttpHeadFile(a, b, c, d) ghttpHead(a, b, c, d) +#define ghttpHeadFileEx(a, b, c, d, e, f, g) ghttpHeadEx(a, b, c, d, e, f, g) + +// This ASCII version needs to be define even in UNICODE mode +GHTTPRequest ghttpGetA +( + const char * URL, // The URL for the file ("http://host.domain[:port]/path/filename"). + GHTTPBool blocking, // If true, this call doesn't return until the file has been recevied. + ghttpCompletedCallback completedCallback, // Called when the file has been received. + void * param // User-data to be passed to the callbacks. +); +#define ghttpGetFileA(a, b, c, d) ghttpGetA(a, b, c, d) + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/ghttp/ghttpASCII.h b/code/gamespy/ghttp/ghttpASCII.h new file mode 100644 index 00000000..6c427e06 --- /dev/null +++ b/code/gamespy/ghttp/ghttpASCII.h @@ -0,0 +1,270 @@ + /* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +// ASCII PROTOTYPES FOR USE IN UNICODE MODE +// INCLUDED TO SILENCE CODEWARRIOR WARNINGS +#ifndef _GHTTPASCII_H_ +#define _GHTTPASCII_H_ + +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Get a file from an http server. +// Returns GHTTPRequestError if an error occurs. +////////////////////////////////// +GHTTPRequest ghttpGetA +( + const char * URL, // The URL for the file ("http://host.domain[:port]/path/filename"). + GHTTPBool blocking, // If true, this call doesn't return until the file has been recevied. + ghttpCompletedCallback completedCallback, // Called when the file has been received. + void * param // User-data to be passed to the callbacks. +); + +// Get a file from an http server. +// Returns GHTTPRequestError if an error occurs. +// Allows an optional user-supplied buffer to be used, +// optional extra http headers, +// and an optional progress callback. +// The optional headers must be 0 or more HTTP headers, +// each terminated by a CR-LF pair (0xD, 0xA). +// If using a user-supplied buffer: +// set buffer to the buffer to use, +// set bufferSize to the size of the buffer in bytes. +// To have the library allocate a buffer: +// set buffer to NULL, set bufferSize to 0 +/////////////////////////////////////////////////////// +GHTTPRequest ghttpGetExA +( + const char * URL, // The URL for the file ("http://host.domain[:port]/path/filename"). + const char * headers, // Optional headers to pass with the request. Can be NULL or "". + char * buffer, // Optional user-supplied buffer. Set to NULL to have one allocated. + int bufferSize, // The size of the user-supplied buffer in bytes. 0 if buffer is NULL. + GHTTPPost post, // Optional data to be posted. + GHTTPBool throttle, // If true, throttle this connection's download speed. + GHTTPBool blocking, // If true, this call doesn't return until the file has been recevied. + ghttpProgressCallback progressCallback, // Called periodically with progress updates. + ghttpCompletedCallback completedCallback, // Called when the file has been received. + void * param // User-data to be passed to the callbacks. +); + +// Gets a file and saves it to disk. +// Returns GHTTPRequestError if an error occurs. +//////////////////////////////////// +GHTTPRequest ghttpSaveA +( + const char * URL, // The URL for the file ("http://host.domain[:port]/path/filename"). + const char * filename, // The path and name to store the file as locally. + GHTTPBool blocking, // If true, this call doesn't return until the file has been recevied. + ghttpCompletedCallback completedCallback, // Called when the file has been received. + void * param // User-data to be passed to the callbacks. +); + +// Gets a file and saves it to disk. +// Returns GHTTPRequestError if an error occurs. +// Allows optional extra http headers and +// an optional progress callback. +///////////////////////////////////////// +GHTTPRequest ghttpSaveExA +( + const char * URL, // The URL for the file ("http://host.domain[:port]/path/filename"). + const char * filename, // The path and name to store the file as locally. + const char * headers, // Optional headers to pass with the request. Can be NULL or "". + GHTTPPost post, // Optional data to be posted. + GHTTPBool throttle, // If true, throttle this connection's download speed. + GHTTPBool blocking, // If true, this call doesn't return until the file has been recevied. + ghttpProgressCallback progressCallback, // Called periodically with progress updates. + ghttpCompletedCallback completedCallback, // Called when the file has been received. + void * param // User-data to be passed to the callbacks. +); + +// Streams a file from an http server. +// Returns GHTTPRequestError if an error occurs. +////////////////////////////////////// +GHTTPRequest ghttpStreamA +( + const char * URL, // The URL for the file ("http://host.domain[:port]/path/filename"). + GHTTPBool blocking, // If true, this call doesn't return until the file has finished streaming. + ghttpProgressCallback progressCallback, // Called whenever new data is received. + ghttpCompletedCallback completedCallback, // Called when the file has finished streaming. + void * param // User-data to be passed to the callbacks. +); + +// Streams a file from an http server. +// Returns GHTTPRequestError if an error occurs. +// Allows optional extra http headers. +////////////////////////////////////// +GHTTPRequest ghttpStreamExA +( + const char * URL, // The URL for the file ("http://host.domain[:port]/path/filename"). + const char * headers, // Optional headers to pass with the request. Can be NULL or "". + GHTTPPost post, // Optional data to be posted. + GHTTPBool throttle, // If true, throttle this connection's download speed. + GHTTPBool blocking, // If true, this call doesn't return until the file has finished streaming. + ghttpProgressCallback progressCallback, // Called whenever new data is received. + ghttpCompletedCallback completedCallback, // Called when the file has finished streaming. + void * param // User-data to be passed to the callbacks. +); + +// Does a file request without actually getting the file. +// Use this to check the headers returned by a server when a request is made. +// Returns GHTTPRequestError if an error occurs. +///////////////////////////////////////////////////////////////////////////// +GHTTPRequest ghttpHeadA +( + const char * URL, // The URL for the file ("http://host.domain[:port]/path/filename"). + GHTTPBool blocking, // If true, this call doesn't return until finished + ghttpCompletedCallback completedCallback, // Called when the request has finished. + void * param // User-data to be passed to the callbacks. +); + +// Does a file request without actually getting the file. +// Use this to check the headers returned by a server when a request is made. +// Returns GHTTPRequestError if an error occurs. +// Allows optional extra http headers. +///////////////////////////////////////////////////////////////////////////// +GHTTPRequest ghttpHeadExA +( + const char * URL, // The URL for the file ("http://host.domain[:port]/path/filename"). + const char * headers, // Optional headers to pass with the request. Can be NULL or "". + GHTTPBool throttle, // If true, throttle this connection's download speed. + GHTTPBool blocking, // If true, this call doesn't return until finished + ghttpProgressCallback progressCallback, // Called whenever new data is received. + ghttpCompletedCallback completedCallback, // Called when the request has finished. + void * param // User-data to be passed to the callbacks. +); + +// Does an HTTP POST, which can be used to upload data to a web server. +// The post parameter must be a valid GHTTPPost, setup with the data to be uploaded. +// No data will be returned from this request. If data is needed, use one of the +// ghttp*FileEx() functions, and pass in a GHTTPPost object. +// Returns GHTTPRequestError if an error occurs. +/////////////////////////////////////////////////////////////////////////////////// +GHTTPRequest ghttpPostA +( + const char * URL, // The URL for the file ("http://host.domain[:port]/path/filename"). + GHTTPPost post, // The data to be posted. + GHTTPBool blocking, // If true, this call doesn't return until finished + ghttpCompletedCallback completedCallback, // Called when the file has finished streaming. + void * param // User-data to be passed to the callbacks. +); + +// Does an HTTP POST, which can be used to upload data to a web server. +// The post parameter must be a valid GHTTPPost, setup with the data to be uploaded. +// No data will be returned from this request. If data is needed, use one of the +// ghttp*FileEx() functions, and pass in a GHTTPPost object. +// Returns GHTTPRequestError if an error occurs. +// Allows optional extra http headers and +// an optional progress callback. +/////////////////////////////////////////////////////////////////////////////////// +GHTTPRequest ghttpPostExA +( + const char * URL, // The URL for the file ("http://host.domain[:port]/path/filename"). + const char * headers, // Optional headers to pass with the request. Can be NULL or "". + GHTTPPost post, // The data to be posted. + GHTTPBool throttle, // If true, throttle this connection's download speed. + GHTTPBool blocking, // If true, this call doesn't return until finished + ghttpProgressCallback progressCallback, // Called whenever new data is received. + ghttpCompletedCallback completedCallback, // Called when the file has finished streaming. + void * param // User-data to be passed to the callbacks. +); + + +// Gets the status code and status string for a request. +// A pointer to the status string is returned, or NULL on error. +// Only valid if the GHTTPState for this request +// is greater than GHTTPReceivingStatus. +//////////////////////////////////////////////////////////////// +const char * ghttpGetResponseStatus +( + GHTTPRequest request, // The request to get the response state of. + int * statusCode // If not NULL, the status code is stored here. +); + +// Gets headers returned by the http server. +// Only valid if the GHTTPState for this +// request is GHTTPReceivingFile. +//////////////////////////////////////////// +const char * ghttpGetHeaders +( + GHTTPRequest request +); + +// Gets the URL for a given request. +//////////////////////////////////// +const char * ghttpGetURL +( + GHTTPRequest request +); + +// Sets a proxy server address. The address should be of the +// form "[:port]". If port is omitted, 80 will be used. +// If server is NULL or "", no proxy server will be used. +// This should not be called while there are any current requests. +////////////////////////////////////////////////////////////////// +GHTTPBool ghttpSetProxyA +( + const char * server +); + +// Adds a string to the post object. +//////////////////////////////////// +GHTTPBool ghttpPostAddStringA +( + GHTTPPost post, // The post object to add to. + const char * name, // The name to attach to this string. + const char * string // The actual string. +); + +// Adds a disk file to the post object. +// The reportFilename is what is reported to the server as the filename. +// If NULL or empty, the filename will be used (including any possible path). +// The contentType is the MIME type to report for this file. +// If NULL, "application/octet-stream" is used. +// The file isn't read from until the data is actually sent to the server. +// Returns false for any error. +///////////////////////////////////////////////////////////////////////////// +GHTTPBool ghttpPostAddFileFromDiskA +( + GHTTPPost post, // The post object to add to. + const char * name, // The name to attach to this file. + const char * filename, // The name (and possibly path) to the file to upload. + const char * reportFilename,// The filename given to the web server. + const char * contentType // The MIME type for this file. +); + +// Adds a file, in memory, to the post object. +// The reportFilename is what is reported to the server as the filename. +// Cannot be NULL or empty. +// The contentType is the MIME type to report for this file. +// If NULL, "application/octet-stream" is used. +// The data is NOT copied off in this call. The data pointer is read from +// as the data is actually sent to the server. The pointer must remain +// valid during requests. +// Returns false for any error. +////////////////////////////////////////////////////////////////////////// +GHTTPBool ghttpPostAddFileFromMemoryA +( + GHTTPPost post, // The post object to add to. + const char * name, // The name to attach to this string. + const char * buffer, // The data to send. + int bufferLen, // The number of bytes of data to send. + const char * reportFilename, // The filename given to the web server. + const char * contentType // The MIME type for this file. +); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/ghttp/ghttpBuffer.c b/code/gamespy/ghttp/ghttpBuffer.c new file mode 100644 index 00000000..a70ad673 --- /dev/null +++ b/code/gamespy/ghttp/ghttpBuffer.c @@ -0,0 +1,561 @@ + /* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "ghttpBuffer.h" +#include "ghttpConnection.h" +#include "ghttpMain.h" +#include "ghttpCommon.h" +#include "../common/gsCrypt.h" +#include "../common/gsSSL.h" + + +// Resize the buffer. +///////////////////// +GHTTPBool ghiResizeBuffer +( + GHIBuffer * buffer, + int sizeIncrement +) +{ + char * tempPtr; + int newSize; + + assert(buffer); + assert(sizeIncrement > 0); + assert(buffer->fixed == GHTTPFalse); // implied by sizeIncrement > 0 + + // Check args. + ////////////// + if(!buffer) + return GHTTPFalse; + if(sizeIncrement <= 0) + return GHTTPFalse; + + // Reallocate with the bigger size. + /////////////////////////////////// + newSize = (buffer->size + sizeIncrement); + tempPtr = (char *)gsirealloc(buffer->data, (unsigned int)newSize); + if(!tempPtr) + return GHTTPFalse; + + // Set the new info. + //////////////////// + buffer->data = tempPtr; + buffer->size = newSize; + + return GHTTPTrue; +} + +GHTTPBool ghiInitBuffer +( + struct GHIConnection * connection, + GHIBuffer * buffer, + int initialSize, + int sizeIncrement +) +{ + GHTTPBool bResult; + + assert(connection); + assert(buffer); + assert(initialSize > 0); + assert(sizeIncrement > 0); + + // Check args. + ////////////// + if(!connection) + return GHTTPFalse; + if(!buffer) + return GHTTPFalse; + if(initialSize <= 0) + return GHTTPFalse; + if(sizeIncrement <= 0) + return GHTTPFalse; + + // Init the struct. + /////////////////// + buffer->connection = connection; + buffer->data = NULL; + buffer->size = 0; + buffer->len = 0; + buffer->pos = 0; + buffer->sizeIncrement = sizeIncrement; + buffer->fixed = GHTTPFalse; + buffer->dontFree = GHTTPFalse; + buffer->readOnly = GHTTPFalse; + + // Do the initial resize. + ///////////////////////// + bResult = ghiResizeBuffer(buffer, initialSize); + if(!bResult) + return GHTTPFalse; + + // Start with an empty string. + ////////////////////////////// + *buffer->data = '\0'; + + return GHTTPTrue; +} + +GHTTPBool ghiInitFixedBuffer +( + struct GHIConnection * connection, + GHIBuffer * buffer, + char * userBuffer, + int size +) +{ + assert(connection); + assert(buffer); + assert(userBuffer); + assert(size > 0); + + // Check args. + ////////////// + if(!connection) + return GHTTPFalse; + if(!buffer) + return GHTTPFalse; + if(!userBuffer) + return GHTTPFalse; + if(size <= 0) + return GHTTPFalse; + + // Init the struct. + /////////////////// + buffer->connection = connection; + buffer->data = userBuffer; + buffer->size = size; + buffer->len = 0; + buffer->pos = 0; + buffer->sizeIncrement = 0; + buffer->fixed = GHTTPTrue; + buffer->dontFree = GHTTPTrue; + buffer->readOnly = GHTTPFalse; + + // Start with an empty string. + ////////////////////////////// + *buffer->data = '\0'; + + return GHTTPTrue; +} + +GHTTPBool ghiInitReadOnlyBuffer +( + struct GHIConnection * connection, // The connection. + GHIBuffer * buffer, // The buffer to init. + const char * userBuffer, // The user-buffer to use. + int size // The size of the buffer. +) +{ + assert(connection); + assert(buffer); + assert(userBuffer); + assert(size > 0); + + // Check args. + ////////////// + if(!connection) + return GHTTPFalse; + if(!buffer) + return GHTTPFalse; + if(!userBuffer) + return GHTTPFalse; + if(size <= 0) + return GHTTPFalse; + + // Init the struct. + /////////////////// + buffer->connection = connection; + buffer->data = (char*)userBuffer; // cast away const + buffer->size = size; + buffer->pos = 0; + buffer->sizeIncrement = 0; + buffer->fixed = GHTTPTrue; + buffer->dontFree = GHTTPTrue; + buffer->readOnly = GHTTPTrue; + + // Start with user supplied data + ////////////////////////////// + buffer->len = size; + + return GHTTPTrue; +} + +void ghiFreeBuffer +( + GHIBuffer * buffer +) +{ + assert(buffer); + + // Check args. + ////////////// + if(!buffer) + return; + if(!buffer->data) + return; + + // Cleanup the struct. + ////////////////////// + if(!buffer->dontFree) + gsifree(buffer->data); + memset(buffer, 0, sizeof(GHIBuffer)); +} + +GHTTPBool ghiAppendDataToBuffer +( + GHIBuffer * buffer, + const char * data, + int dataLen +) +{ + GHTTPBool bResult; + int newLen; + + assert(buffer); + assert(data); + assert(dataLen >= 0); + + // Check args. + ////////////// + if(!buffer) + return GHTTPFalse; + if(!data) + return GHTTPFalse; + if(dataLen < 0) + return GHTTPFalse; + if (buffer->readOnly) + return GHTTPFalse; + + // Get the string length if needed. + /////////////////////////////////// + if(dataLen == 0) + dataLen = (int)strlen(data); + + // Get the new length. + ////////////////////// + newLen = (buffer->len + dataLen); + + // Make sure the array is big enough. + ///////////////////////////////////// + while(newLen >= buffer->size) + { + // Check for a fixed buffer. + //////////////////////////// + if(buffer->fixed) + { + buffer->connection->completed = GHTTPTrue; + buffer->connection->result = GHTTPBufferOverflow; + return GHTTPFalse; + } + + bResult = ghiResizeBuffer(buffer, buffer->sizeIncrement); + if(!bResult) + { + buffer->connection->completed = GHTTPTrue; + buffer->connection->result = GHTTPOutOfMemory; + return GHTTPFalse; + } + } + + // Add the data. + //////////////// + memcpy(buffer->data + buffer->len, data, (unsigned int)dataLen); + buffer->len = newLen; + buffer->data[buffer->len] = '\0'; + return GHTTPTrue; +} + + +// Use sparingly. This function wraps the data in an SSL record. +GHTTPBool ghiEncryptDataToBuffer +( + GHIBuffer * buffer, + const char * data, + int dataLen +) +{ + GHIEncryptionResult result; + int bufSpace = 0; + int pos = 0; + + assert(buffer); + assert(data); + assert(dataLen >= 0); + + // Check args. + ////////////// + if(!buffer) + return GHTTPFalse; + if(!data) + return GHTTPFalse; + if(dataLen < 0) + return GHTTPFalse; + if (buffer->readOnly) + return GHTTPFalse; + + // Switch to plain text append when not using SSL + if (buffer->connection->encryptor.mEngine == GHTTPEncryptionEngine_None || + buffer->connection->encryptor.mSessionEstablished == GHTTPFalse) + { + return ghiAppendDataToBuffer(buffer, data, dataLen); + } + + // Get the string length if needed. + /////////////////////////////////// + if(dataLen == 0) + dataLen = (int)strlen(data); + if (dataLen == 0) + return GHTTPTrue; // no data and strlen == 0 + bufSpace = buffer->size - buffer->len; + + do + { + int fragmentLen = min(dataLen, GS_SSL_MAX_CONTENTLENGTH); + + // Call the encryptor function + // bufSize is reduced by the number of bytes written + result = buffer->connection->encryptor.mEncryptFunc(buffer->connection, &buffer->connection->encryptor, + &data[pos], dataLen, + &buffer->data[buffer->len], &bufSpace); + if (result == GHIEncryptionResult_BufferTooSmall) + { + if (ghiResizeBuffer(buffer, buffer->sizeIncrement) == GHTTPFalse) + return GHTTPFalse; + bufSpace = buffer->size - buffer->len; + } + else if (result == GHIEncryptionResult_Success) + { + // update data and buffer positions + pos += fragmentLen; + buffer->len = buffer->size - bufSpace; + } + else + { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "ghiEncryptDataToBuffer encountered unhandled return code: %d\r\n", result); + return GHTTPFalse; + } + } while(pos < dataLen); + + return GHTTPTrue; +} + +GHTTPBool ghiAppendHeaderToBuffer +( + GHIBuffer * buffer, + const char * name, + const char * value +) +{ + if(!ghiAppendDataToBuffer(buffer, name, 0)) + return GHTTPFalse; + if(!ghiAppendDataToBuffer(buffer, ": ", 2)) + return GHTTPFalse; + if(!ghiAppendDataToBuffer(buffer, value, 0)) + return GHTTPFalse; + if(!ghiAppendDataToBuffer(buffer, CRLF, 2)) + return GHTTPFalse; + + return GHTTPTrue; +} + +GHTTPBool ghiAppendCharToBuffer +( + GHIBuffer * buffer, + int c +) +{ + GHTTPBool bResult; + assert(buffer); + + // Check args. + ////////////// + if(!buffer) + return GHTTPFalse; + if (buffer->readOnly) + return GHTTPFalse; + + // Make sure the array is big enough. + ///////////////////////////////////// + if((buffer->len + 1) >= buffer->size) + { + // Check for a fixed buffer. + //////////////////////////// + if(buffer->fixed) + { + buffer->connection->completed = GHTTPTrue; + buffer->connection->result = GHTTPBufferOverflow; + return GHTTPFalse; + } + + bResult = ghiResizeBuffer(buffer, buffer->sizeIncrement); + if(!bResult) + { + buffer->connection->completed = GHTTPTrue; + buffer->connection->result = GHTTPOutOfMemory; + return GHTTPFalse; + } + } + + // Add the char. + //////////////// + buffer->data[buffer->len] = (char)(c & 0xFF); + buffer->len++; + buffer->data[buffer->len] = '\0'; + + return GHTTPTrue; +} + +GHTTPBool ghiAppendIntToBuffer +( + GHIBuffer * buffer, + int i +) +{ + char intValue[16]; + + sprintf(intValue, "%d", i); + + return ghiAppendDataToBuffer(buffer, intValue, 0); +} + +void ghiResetBuffer +( + GHIBuffer * buffer +) +{ + assert(buffer); + + buffer->len = 0; + buffer->pos = 0; + + // Start with an empty string. + ////////////////////////////// + if (!buffer->readOnly) + *buffer->data = '\0'; +} + +GHTTPBool ghiSendBufferedData +( + struct GHIConnection * connection +) +{ + int rcode; + int writeFlag; + int exceptFlag; + char * data; + int len; + + // Loop while we can send. + ////////////////////////// + do + { + rcode = GSISocketSelect(connection->socket, NULL, &writeFlag, &exceptFlag); + if((gsiSocketIsError(rcode)) || ((rcode == 1) && exceptFlag)) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPSocketFailed; + if(gsiSocketIsError(rcode)) + connection->socketError = GOAGetLastError(connection->socket); + else + connection->socketError = 0; + return GHTTPFalse; + } + if((rcode < 1) || !writeFlag) + { + // Can't send anything. + /////////////////////// + return GHTTPTrue; + } + + // Figure out what, and how much, to send. + ////////////////////////////////////////// + data = (connection->sendBuffer.data + connection->sendBuffer.pos); + len = (connection->sendBuffer.len - connection->sendBuffer.pos); + + // Do the send. + /////////////// + rcode = ghiDoSend(connection, data, len); + if(gsiSocketIsError(rcode)) + return GHTTPFalse; + + // Update the position. + /////////////////////// + connection->sendBuffer.pos += rcode; + } + while(connection->sendBuffer.pos < connection->sendBuffer.len); + + return GHTTPTrue; +} + + +// Read data from a buffer +GHTTPBool ghiReadDataFromBuffer +( + GHIBuffer * bufferIn, // the GHIBuffer to read from + char bufferOut[], // the raw buffer to write to + int * len // max number of bytes to append, becomes actual length written +) +{ + int bytesAvailable = 0; + int bytesToCopy = 0; + + + // Verify parameters + assert(bufferIn != NULL); + assert(len != NULL); + if (*len == 0) + return GHTTPFalse; + + // Make sure the bufferIn isn't emtpy + bytesAvailable = (int)bufferIn->len - bufferIn->pos; + if (bytesAvailable <= 0) + return GHTTPFalse; + + // Calculate the actual number of bytes to copy + bytesToCopy = min(*len-1, bytesAvailable); + + // Copy the bytes + memcpy(bufferOut, bufferIn->data + bufferIn->pos, (size_t)bytesToCopy); + bufferOut[bytesToCopy] = '\0'; + *len = bytesToCopy; + + // Adjust the bufferIn read position + bufferIn->pos += bytesToCopy; + return GHTTPTrue; +} + + +// Read data from a buffer with a garunteed length +GHTTPBool ghiReadDataFromBufferFixed +( + GHIBuffer * bufferIn, // the GHIBuffer to read from + char bufferOut[], // the raw buffer to write to + int bytesToCopy // number of bytes to read +) +{ + // Verify parameters + assert(bufferIn != NULL); + if (bytesToCopy == 0) + return GHTTPTrue; + + // Make sure the bufferIn isn't too small + if (bufferIn->len < bytesToCopy) + return GHTTPFalse; + + // Copy the bytes + memcpy(bufferOut, bufferIn->data + bufferIn->pos, (size_t)bytesToCopy); + + // Adjust the bufferIn read position + bufferIn->pos += bytesToCopy; + return GHTTPTrue; +} diff --git a/code/gamespy/ghttp/ghttpBuffer.h b/code/gamespy/ghttp/ghttpBuffer.h new file mode 100644 index 00000000..3aea4a3e --- /dev/null +++ b/code/gamespy/ghttp/ghttpBuffer.h @@ -0,0 +1,170 @@ + /* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GHTTPBUFFER_H_ +#define _GHTTPBUFFER_H_ + +#include "ghttpMain.h" +#include "ghttpEncryption.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// A data buffer. +///////////////// +typedef struct GHIBuffer +{ + struct GHIConnection * connection; // The connection. + char * data; // The actual bytes of data. + int size; // The number of bytes allocated for data. + int len; // The number of actual data bytes filled in. + int pos; // A marker to keep track of position. + int sizeIncrement; // How much to increment the buffer by when needed. + GHTTPBool fixed; // If true, don't resize the buffer. + GHTTPBool dontFree; // Don't free the data when the buffer is cleaned up. + GHTTPBool readOnly; // Read Only, write operations will fail +} GHIBuffer; + +// Initializes a buffer and allocates the initial data bytes. +// The initialSize and sizeIncrement must both be >0. +///////////////////////////////////////////////////////////// +GHTTPBool ghiInitBuffer +( + struct GHIConnection * connection, // The connection. + GHIBuffer * buffer, // The buffer to init. + int initialSize, // The initial size of the buffer. + int sizeIncrement // The size increment for the buffer. +); + +// Initializes a fixed-size buffer. This will not get resized. +/////////////////////////////////////////////////////////////// +GHTTPBool ghiInitFixedBuffer +( + struct GHIConnection * connection, // The connection. + GHIBuffer * buffer, // The buffer to init. + char * userBuffer, // The user-buffer to use. + int size // The size of the buffer. +); + +// Initializes a read-only fixed-size buffer. This will not get resized. +/////////////////////////////////////////////////////////////// +GHTTPBool ghiInitReadOnlyBuffer +( + struct GHIConnection * connection, // The connection. + GHIBuffer * buffer, // The buffer to init. + const char * userBuffer, // The user-buffer to use. + int size // The size of the buffer. +); + +// Free's a buffer's allocated memory (does +// not free the actual GHIBuffer structure). +//////////////////////////////////////////// +void ghiFreeBuffer +( + GHIBuffer * buffer +); + +// Appends data to the buffer. +// If data is a NUL-terminated string, 0 can be +// used for dataLen to use the length of the string. +//////////////////////////////////////////////////// +GHTTPBool ghiAppendDataToBuffer +( + GHIBuffer * buffer, // The buffer to append to. + const char * data, // The data to append. + int dataLen // The number of bytes of data to append, 0 for NUL-terminated string. +); + +// Appends data to the buffer, wrapped in an SSL record +// If data is a NUL-terminated string, 0 can be +// used for dataLen to use the length of the string. +// Encryption has some size overhead, so call this sparingly. +//////////////////////////////////////////////////// +GHTTPBool ghiEncryptDataToBuffer +( + GHIBuffer * buffer, // The buffer to append to. + const char * data, // The data to append. + int dataLen // The number of bytes of data to append, 0 for NUL-terminated string. +); + +// Appends a header to the buffer. +// Both the name and value must be NUL-terminated. +// The header will be added to the buffer as: +// : \n +////////////////////////////////////////////////// +GHTTPBool ghiAppendHeaderToBuffer +( + GHIBuffer * buffer, // The buffer to append to. + const char * name, // The name of the header. + const char * value // The value of the header. +); + +// Appends a single character to the buffer. +//////////////////////////////////////////// +GHTTPBool ghiAppendCharToBuffer +( + GHIBuffer * buffer, // The buffer to append to. + int c // The char to append. +); + +// Read data from a buffer +GHTTPBool ghiReadDataFromBuffer +( + GHIBuffer * bufferIn, // the GHIBuffer to read from + char bufferOut[], // the raw buffer to write to + int * len // max number of bytes to append, becomes actual length written +); + +// Read a fixed number of bytes from a buffer +GHTTPBool ghiReadDataFromBufferFixed +( + GHIBuffer * bufferIn, + char bufferOut[], + int len +); + +// Converts the int to a string and appends it to the buffer. +///////////////////////////////////////////////////////////// +GHTTPBool ghiAppendIntToBuffer +( + GHIBuffer * buffer, // The buffer to append to. + int i // The int to append. +); + +// Resets a buffer. +// Does this by setting both len and pos to 0. +////////////////////////////////////////////// +void ghiResetBuffer +( + GHIBuffer * buffer // The buffer to reset. +); + +// Sends as much buffer data as it can. +// Returns false if there was an error. +/////////////////////////////////////// +GHTTPBool ghiSendBufferedData +( + struct GHIConnection * connection +); + +// Increases the size of a buffer. +// This happens automatically when using the ghiAppend* functions +GHTTPBool ghiResizeBuffer +( + GHIBuffer * buffer, + int sizeIncrement +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/ghttp/ghttpCallbacks.c b/code/gamespy/ghttp/ghttpCallbacks.c new file mode 100644 index 00000000..7fecb000 --- /dev/null +++ b/code/gamespy/ghttp/ghttpCallbacks.c @@ -0,0 +1,114 @@ + /* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "ghttpCallbacks.h" +#include "ghttpPost.h" + +void ghiCallCompletedCallback +( + GHIConnection * connection +) +{ + GHTTPBool freeBuffer; + char * buffer; + GHTTPByteCount bufferLen; + + assert(connection); + +#ifdef GSI_COMMON_DEBUG + if(connection->result != GHTTPSuccess) + { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Socket Error: %d\n", connection->socketError); + } +#endif + + // Check for no callback. + ///////////////////////// + if(!connection->completedCallback) + return; + + // Figure out the buffer/bufferLen parameters. + ////////////////////////////////////////////// + if(connection->type == GHIGET) + { + buffer = connection->getFileBuffer.data; + } + else + { + buffer = NULL; + } + bufferLen = connection->fileBytesReceived; + + // Call the callback. + ///////////////////// + freeBuffer = connection->completedCallback( + connection->request, + connection->result, + buffer, + bufferLen, + connection->callbackParam); + + // Check for gsifree. + ////////////////// + if(buffer && !freeBuffer) + connection->getFileBuffer.dontFree = GHTTPTrue; +} + +void ghiCallProgressCallback +( + GHIConnection * connection, + const char * buffer, + GHTTPByteCount bufferLen +) +{ + assert(connection); + + // Check for no callback. + ///////////////////////// + if(!connection->progressCallback) + return; + + // Call the callback. + ///////////////////// + connection->progressCallback( + connection->request, + connection->state, + buffer, + bufferLen, + connection->fileBytesReceived, + connection->totalSize, + connection->callbackParam + ); +} + +void ghiCallPostCallback +( + GHIConnection * connection +) +{ + assert(connection); + + // Check for no callback. + ///////////////////////// + if(!connection->postingState.callback) + return; + + // Call the callback. + ///////////////////// + connection->postingState.callback( + connection->request, + connection->postingState.bytesPosted, + connection->postingState.totalBytes, + connection->postingState.index, + ArrayLength(connection->postingState.states), + connection->callbackParam + ); +} diff --git a/code/gamespy/ghttp/ghttpCallbacks.h b/code/gamespy/ghttp/ghttpCallbacks.h new file mode 100644 index 00000000..d53835aa --- /dev/null +++ b/code/gamespy/ghttp/ghttpCallbacks.h @@ -0,0 +1,48 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GHTTPCALLBACKS_H_ +#define _GHTTPCALLBACKS_H_ + +#include "ghttpMain.h" +#include "ghttpConnection.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Call the completed callback for this connection. +/////////////////////////////////////////////////// +void ghiCallCompletedCallback +( + GHIConnection * connection +); + +// Call the progress callback for this connection. +////////////////////////////////////////////////// +void ghiCallProgressCallback +( + GHIConnection * connection, + const char * buffer, + GHTTPByteCount bufferLen +); + +// Call the post callback for this connection. +////////////////////////////////////////////// +void ghiCallPostCallback +( + GHIConnection * connection +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/ghttp/ghttpCommon.c b/code/gamespy/ghttp/ghttpCommon.c new file mode 100644 index 00000000..ee402b85 --- /dev/null +++ b/code/gamespy/ghttp/ghttpCommon.c @@ -0,0 +1,597 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "ghttpCommon.h" + +// Disable compiler warnings for issues that are unavoidable. +///////////////////////////////////////////////////////////// +#if defined(_MSC_VER) // DevStudio +// Level4, "conditional expression is constant". +// Occurs with use of the MS provided macro FD_SET +#pragma warning ( disable: 4127 ) +#endif // _MSC_VER + +#ifdef WIN32 +// A lock. +////////// +typedef void * GLock; + +// The lock used by ghttp. +////////////////////////// +static GLock ghiGlobalLock; +#endif + +// Proxy server. +//////////////// +char * ghiProxyAddress; +unsigned short ghiProxyPort; + +// Throttle settings. +///////////////////// +int ghiThrottleBufferSize = 125; +gsi_time ghiThrottleTimeDelay = 250; + +// Number of connections +///////////////////// +extern int ghiNumConnections; + + +#ifdef WIN32 +// Creates a lock. +////////////////// +static GLock GNewLock(void) +{ + CRITICAL_SECTION * criticalSection; + + criticalSection = (CRITICAL_SECTION *)gsimalloc(sizeof(CRITICAL_SECTION)); + if(!criticalSection) + return NULL; + + InitializeCriticalSection(criticalSection); + + return (GLock)criticalSection; +} + +// Frees a lock. +//////////////// +static void GFreeLock(GLock lock) +{ + CRITICAL_SECTION * criticalSection = (CRITICAL_SECTION *)lock; + + if(!lock) + return; + + DeleteCriticalSection(criticalSection); + + gsifree(criticalSection); +} + +// Locks a lock. +//////////////// +static void GLockLock(GLock lock) +{ + CRITICAL_SECTION * criticalSection = (CRITICAL_SECTION *)lock; + + if(!lock) + return; + + EnterCriticalSection(criticalSection); +} + +// Unlocks a lock. +////////////////// +static void GUnlockLock(GLock lock) +{ + CRITICAL_SECTION * criticalSection = (CRITICAL_SECTION *)lock; + + if(!lock) + return; + + LeaveCriticalSection(criticalSection); +} +#endif + +// Creates the ghttp lock. +////////////////////////// +void ghiCreateLock(void) +{ +#ifdef WIN32 + // We shouldn't already have a lock. + //////////////////////////////////// + assert(!ghiGlobalLock); + + // Create the lock. + /////////////////// + ghiGlobalLock = GNewLock(); +#endif +} + +// Frees the ghttp lock. +//////////////////////// +void ghiFreeLock(void) +{ +#ifdef WIN32 + if(!ghiGlobalLock) + return; + + GFreeLock(ghiGlobalLock); + ghiGlobalLock = NULL; +#endif +} + +// Locks the ghttp lock. +//////////////////////// +void ghiLock +( + void +) +{ +#ifdef WIN32 + if(!ghiGlobalLock) + return; + + GLockLock(ghiGlobalLock); +#endif +} + +// Unlocks the ghttp lock. +////////////////////////// +void ghiUnlock +( + void +) +{ +#ifdef WIN32 + if(!ghiGlobalLock) + return; + + GUnlockLock(ghiGlobalLock); +#endif +} + +// Logs traffic. +//////////////// +#ifdef HTTP_LOG +void ghiLogToFile(const char * buffer, int len, const char* fileName) +{ +#ifdef _NITRO + int i; + + if(!buffer || !len) + return; + + for(i = 0 ; i < len ; i++) + OS_PutChar(buffer[i]); +#else + FILE * file; + + if(!buffer || !len) + return; + + file = fopen(fileName, "ab"); + if(file) + { + fwrite(buffer, 1, len, file); + fclose(file); + } +#endif +} +#endif + +// Reads encrypted data from decodeBuffer +// Appends decrypted data to recvBuffer +// Returns GHTTPFalse if there was a fatal error +//////////////////////////////////////////////// +GHTTPBool ghiDecryptReceivedData(struct GHIConnection * connection) +{ + // Decrypt data from decodeBuffer to recvBuffer + GHIEncryptionResult aResult = GHIEncryptionResult_None; + + // data to be decrypted + char* aReadPos = NULL; + char* aWritePos = NULL; + int aReadLen = 0; + int aWriteLen = 0; + + do + { + // Call the decryption func + do + { + aReadPos = connection->decodeBuffer.data + connection->decodeBuffer.pos; + aReadLen = connection->decodeBuffer.len - connection->decodeBuffer.pos; + aWritePos = connection->recvBuffer.data + connection->recvBuffer.len; + aWriteLen = connection->recvBuffer.size - connection->recvBuffer.len; // the amount of room in recvbuffer + + aResult = (connection->encryptor.mDecryptFunc)(connection, &connection->encryptor, + aReadPos, &aReadLen, aWritePos, &aWriteLen); + if (aResult == GHIEncryptionResult_BufferTooSmall) + { + // Make some more room + if (GHTTPFalse == ghiResizeBuffer(&connection->recvBuffer, connection->recvBuffer.sizeIncrement)) + return GHTTPFalse; // error + } + else if(aResult == GHIEncryptionResult_Error) + { + return GHTTPFalse; + } + } while (aResult == GHIEncryptionResult_BufferTooSmall && aWriteLen == 0); + + // Adjust GHIBuffer sizes so they account for transfered data + if(aReadLen > connection->decodeBuffer.len) + { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "ghiDecryptReceivedData read past the end of connection->decodeBuffer! (%d\\%d bytes)\r\n", + aReadLen, connection->decodeBuffer.len); + return GHTTPFalse; + } + + connection->decodeBuffer.pos += aReadLen; + connection->recvBuffer.len += aWriteLen; + + } while(aWriteLen > 0); + + // Discard data from the decodedBuffer in chunks + if (connection->decodeBuffer.pos > 0xFF) + { + int bytesToKeep = connection->decodeBuffer.len - connection->decodeBuffer.pos; + if (bytesToKeep == 0) + ghiResetBuffer(&connection->decodeBuffer); + else + { + memmove(connection->decodeBuffer.data, + connection->decodeBuffer.data + connection->decodeBuffer.pos, + (size_t)bytesToKeep); + connection->decodeBuffer.pos = 0; + connection->decodeBuffer.len = bytesToKeep; + } + } + + return GHTTPTrue; +} + +// Receive some data. +///////////////////// +GHIRecvResult ghiDoReceive +( + GHIConnection * connection, + char buffer[], + int * bufferLen +) +{ + int rcode; + int socketError; + int len; + + // How much to try and receive. + /////////////////////////////// + len = (*bufferLen - 1); + + // Are we throttled? + //////////////////// + if(connection->throttle) + { + unsigned long now; + + // Don't receive too often. + /////////////////////////// + now = current_time(); + if(now < (connection->lastThrottleRecv + ghiThrottleTimeDelay)) + return GHINoData; + + // Update the receive time. + /////////////////////////// + connection->lastThrottleRecv = (unsigned int)now; + + // Don't receive too much. + ////////////////////////// + len = min(len, ghiThrottleBufferSize); + } + + // Receive some data. + ///////////////////// + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None && + connection->encryptor.mSessionEstablished == GHTTPTrue && + connection->encryptor.mEncryptOnSend == GHTTPTrue ) + { + GHIEncryptionResult result; + int recvLength = len; + + result = ghiEncryptorSslDecryptRecv(connection, &connection->encryptor, buffer, &recvLength); + if (result == GHIEncryptionResult_Success) + rcode = recvLength; + else + rcode = -1; // signal termination of connection + } + else + { + rcode = recv(connection->socket, buffer, len, 0); + } + + + // There was an error. + ////////////////////// + if(gsiSocketIsError(rcode)) + { + // Get the error code. + ////////////////////// + socketError = GOAGetLastError(connection->socket); + + // Check for a closed connection. + ///////////////////////////////// + if(socketError == WSAENOTCONN) + { + connection->connectionClosed = GHTTPTrue; + return GHIConnClosed; + } + + // Check for nothing waiting. + ///////////////////////////// + if((socketError == WSAEWOULDBLOCK) || (socketError == WSAEINPROGRESS) || (socketError == WSAETIMEDOUT)) + return GHINoData; + + // There was a real error. + ////////////////////////// + connection->completed = GHTTPTrue; + connection->result = GHTTPSocketFailed; + connection->socketError = socketError; + connection->connectionClosed = GHTTPTrue; + + return GHIError; + } + + // The connection was closed. + ///////////////////////////// + if(rcode == 0) + { + connection->connectionClosed = GHTTPTrue; + return GHIConnClosed; + } + + // Cap the buffer. + ////////////////// + buffer[rcode] = '\0'; + *bufferLen = rcode; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_RawDump, "Received %d bytes\n", rcode); + + // Notify app. + ////////////// + return GHIRecvData; +} + +int ghiDoSend +( + struct GHIConnection * connection, + const char * buffer, + int len +) +{ + int rcode; + + if (buffer == NULL || len == 0) + return 0; + + // Do the send. + /////////////// + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None && + connection->encryptor.mSessionEstablished == GHTTPTrue && + connection->encryptor.mEncryptOnSend == GHTTPTrue) + { + int bytesSent = 0; + GHIEncryptionResult result; + + // send through encryption engine + result = ghiEncryptorSslEncryptSend(connection, &connection->encryptor, buffer, len, &bytesSent); + + // Check for an error. + ////////////////////// + if(result != GHIEncryptionResult_Success) + rcode = -1; // signal termination of connection + else + rcode = bytesSent; + } + else + { + // send directly to socket + rcode = send(connection->socket, buffer, len, 0); + } + + // Check for an error. + ////////////////////// + if(gsiSocketIsError(rcode)) + { + int error; + + // Would block just means 0 bytes sent. + /////////////////////////////////////// + error = GOAGetLastError(connection->socket); + if((error == WSAEWOULDBLOCK) || (error == WSAEINPROGRESS) || (error == WSAETIMEDOUT)) + return 0; + + connection->completed = GHTTPTrue; + connection->result = GHTTPSocketFailed; + connection->socketError = error; + + return -1; + } + + //do not add CRLF as part of bytes posted - make sure waitPostContinue is false + if(connection->state == GHTTPPosting && connection->postingState.waitPostContinue == GHTTPFalse) + { + connection->postingState.bytesPosted += rcode; + ghiLogRequest(buffer, rcode); + } + + return rcode; +} + +GHITrySendResult ghiTrySendThenBuffer +( + GHIConnection * connection, + const char * buffer, + int len +) +{ + int rcode = 0; + + // **SSL pattern 1: buffer everything into an SSL record** + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None && + connection->encryptor.mSessionEstablished == GHTTPTrue && + connection->encryptor.mEncryptOnBuffer == GHTTPTrue) + { + if (!ghiEncryptDataToBuffer(&connection->sendBuffer, buffer + rcode, len - rcode)) + return GHITrySendError; + + // Try to send immediately + if (ghiSendBufferedData(connection) == GHTTPFalse) + return GHITrySendError; + if (connection->sendBuffer.pos >= connection->sendBuffer.len) + { + ghiResetBuffer(&connection->sendBuffer); + return GHITrySendSent; // everything sent + } + return GHITrySendBuffered; + } + + // **Plain text or SSL encrypt on send** + + // If we already have something buffered, don't send. + ///////////////////////////////////////////////////// + if(connection->sendBuffer.pos >= connection->sendBuffer.len) + { + // Try and send. + //////////////// + rcode = ghiDoSend(connection, buffer, len); + if(gsiSocketIsError(rcode)) + return GHITrySendError; + + // Was it all sent? + /////////////////// + if(rcode == len) + return GHITrySendSent; + } + + // Buffer whatever wasn't sent. + /////////////////////////////// + if(!ghiAppendDataToBuffer(&connection->sendBuffer, buffer + rcode, len - rcode)) + return GHITrySendError; + return GHITrySendBuffered; +} + +static GHTTPBool ghiParseProxyServer +( + const char * server, + char ** proxyAddress, // [out] the proxy address + unsigned short * proxyPort // [out] the proxy port +) +{ + char * strPort; + + // Make sure each pointer is valid as well as what it points to + assert(server && *server); + assert(proxyAddress && !*proxyAddress); + assert(proxyPort); + + // Copy off the server address. + /////////////////////////////// + *proxyAddress = goastrdup(server); + if(!*proxyAddress) + return GHTTPFalse; + + // Check for a port. + //////////////////// + if((strPort = strchr(*proxyAddress, ':')) != NULL) + { + *strPort++ = '\0'; + + // Try getting the port. + //////////////////////// + *proxyPort = (unsigned short)atoi(strPort); + if(!*proxyPort) + { + gsifree(*proxyAddress); + *proxyAddress = NULL; + return GHTTPFalse; + } + } + else + { + *proxyPort = GHI_DEFAULT_PORT; + } + return GHTTPTrue; +} + +GHTTPBool ghiSetProxy +( + const char * server +) +{ + // Free any existing proxy address. + /////////////////////////////////// + if(ghiProxyAddress) + { + gsifree(ghiProxyAddress); + ghiProxyAddress = NULL; + } + ghiProxyPort = 0; + + // If a server was supplied, try to parse it + if(server && *server) + return ghiParseProxyServer(server, &ghiProxyAddress, &ghiProxyPort); + + // No server supplied results in proxy being cleared + return GHTTPTrue; +} + +GHTTPBool ghiSetRequestProxy +( + GHTTPRequest request, + const char * server +) +{ + // Obtain the connection for this request + GHIConnection* connection = ghiRequestToConnection(request); + if (connection == NULL) + return GHTTPFalse; + + // Free any existing proxy address. + /////////////////////////////////// + if(connection->proxyOverrideServer) + { + gsifree(connection->proxyOverrideServer); + connection->proxyOverrideServer = NULL; + connection->proxyOverridePort = GHI_DEFAULT_PORT; + } + + // If a server was supplied, try to parse it + if(server && *server) + return ghiParseProxyServer(server, &connection->proxyOverrideServer, &connection->proxyOverridePort); + + // No server supplied results in proxy being cleared + return GHTTPTrue; +} + +void ghiThrottleSettings +( + int bufferSize, + gsi_time timeDelay +) +{ + ghiThrottleBufferSize = bufferSize; + ghiThrottleTimeDelay = timeDelay; +} + +// Re-enable previously disabled compiler warnings +/////////////////////////////////////////////////// +#if defined(_MSC_VER) +#pragma warning ( default: 4127 ) +#endif // _MSC_VER + diff --git a/code/gamespy/ghttp/ghttpCommon.h b/code/gamespy/ghttp/ghttpCommon.h new file mode 100644 index 00000000..2034e80f --- /dev/null +++ b/code/gamespy/ghttp/ghttpCommon.h @@ -0,0 +1,154 @@ + /* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GHTTPCOMMON_H_ +#define _GHTTPCOMMON_H_ + +#include "ghttp.h" +#include "ghttpConnection.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// HTTP Line-terminator. +//////////////////////// +#define CRLF "\xD\xA" + +// HTTP URL Encoding +//////////////////////// +#define GHI_LEGAL_URLENCODED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_@-.*" +#define GHI_DIGITS "0123456789ABCDEF" + +// Default HTTP port. +///////////////////// +#define GHI_DEFAULT_PORT 80 +#define GHI_DEFAULT_SECURE_PORT 443 +#define GHI_DEFAULT_THROTTLE_BUFFER_SIZE 125 +#define GHI_DEFAULT_THROTTLE_TIME_DELAY 250 + +// Proxy server. +//////////////// +extern char * ghiProxyAddress; +extern unsigned short ghiProxyPort; + +// Throttle settings. +///////////////////// +extern int ghiThrottleBufferSize; +extern gsi_time ghiThrottleTimeDelay; + +// Our thread lock. +/////////////////// +void ghiCreateLock(void); +void ghiFreeLock(void); +void ghiLock(void); +void ghiUnlock(void); + +// Do logging. +////////////// +#ifdef HTTP_LOG +void ghiLogToFile +( + const char * buffer, + int len, + const char * fileName +); +#define ghiLogRequest(b,c) ghiLogToFile(b,c,"request.log"); +#define ghiLogResponse(b,c) ghiLogToFile(b,c,"response.log"); +#define ghiLogPost(b,c) ghiLogToFile(b,c,"post.log"); +#else +#define ghiLogRequest(b,c) +#define ghiLogResponse(b,c) +#define ghiLogPost(b,c) +#endif + + +// Possible results from ghiDoReceive. +////////////////////////////////////// +typedef enum +{ + GHIRecvData, // Data was received. + GHINoData, // No data was available. + GHIConnClosed, // The connection was closed. + GHIError // There was a socket error. +} GHIRecvResult; + +// Receive some data. +///////////////////// +GHIRecvResult ghiDoReceive +( + GHIConnection * connection, + char buffer[], + int * bufferLen +); + +// Do a send on the connection's socket. +// Returns number of bytes sent (0 or more). +// If error, returns (-1). +//////////////////////////////////////////// +int ghiDoSend +( + GHIConnection * connection, + const char * buffer, + int len +); + +// Results for ghtTrySendThenBuffer. +//////////////////////////////////// +typedef enum +{ + GHITrySendError, // There was an error sending. + GHITrySendSent, // Everything was sent. + GHITrySendBuffered // Some or all of the data was buffered. +} GHITrySendResult; + +// Sends whatever it can on the socket. +// Buffers whatever can't be sent in the sendBuffer. +//////////////////////////////////////////////////// +GHITrySendResult ghiTrySendThenBuffer +( + GHIConnection * connection, + const char * buffer, + int len +); + +// Set the proxy server +//////////////////////// +GHTTPBool ghiSetProxy +( + const char * server +); + +// Set the proxy server for a specific request +//////////////////////// +GHTTPBool ghiSetRequestProxy +( + GHTTPRequest request, + const char * server +); + +// Set the throttle settings. +///////////////////////////// +void ghiThrottleSettings +( + int bufferSize, + gsi_time timeDelay +); + +// Decrypt data from the decode buffer into the receive buffer. +/////////////////////////////////////////////////////////////// +GHTTPBool ghiDecryptReceivedData(struct GHIConnection * connection); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/ghttp/ghttpConnection.c b/code/gamespy/ghttp/ghttpConnection.c new file mode 100644 index 00000000..a0882b07 --- /dev/null +++ b/code/gamespy/ghttp/ghttpConnection.c @@ -0,0 +1,424 @@ + /* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "ghttpConnection.h" +#include "ghttpCommon.h" + +// Initial size and increment amount for the connections array. +/////////////////////////////////////////////////////////////// +#define CONNECTIONS_CHUNK_LEN 4 + +// An array of pointers to GHIConnection objects. +// A GHTTPRequest is an index into this array. +///////////////////////////////////////////////// +static GHIConnection ** ghiConnections; +static int ghiConnectionsLen; +static int ghiNumConnections; +static int ghiNextUniqueID; + +// Finds a gsifree slot in the ghiConnections array. +// If there are no gsifree slots, the array size will be increased. +//////////////////////////////////////////////////////////////// +static int ghiFindFreeSlot +( + void +) +{ + int i; + GHIConnection ** tempPtr; + int oldLen; + int newLen; + + // Look for an open slot. + ///////////////////////// + for(i = 0 ; i < ghiConnectionsLen ; i++) + { + if(!ghiConnections[i]->inUse) + return i; + } + + assert(ghiNumConnections == ghiConnectionsLen); + + // Nothing found, resize the array. + /////////////////////////////////// + oldLen = ghiConnectionsLen; + newLen = (ghiConnectionsLen + CONNECTIONS_CHUNK_LEN); + tempPtr = (GHIConnection **)gsirealloc(ghiConnections, sizeof(GHIConnection *) * newLen); + if(!tempPtr) + return -1; + ghiConnections = tempPtr; + + // Create the new connection objects. + ///////////////////////////////////// + for(i = oldLen ; i < newLen ; i++) + { + ghiConnections[i] = (GHIConnection *)gsimalloc(sizeof(GHIConnection)); + if(!ghiConnections[i]) + { + for(i-- ; i >= oldLen ; i--) + gsifree(ghiConnections[i]); + return -1; + } + ghiConnections[i]->inUse = GHTTPFalse; + } + + // Update the length. + ///////////////////// + ghiConnectionsLen = newLen; + + return oldLen; +} + +GHIConnection * ghiNewConnection +( + void +) +{ + int slot; + GHIConnection * connection; + GHTTPBool bResult; + + ghiLock(); + + // Find a gsifree slot. + //////////////////// + slot = ghiFindFreeSlot(); + if(slot == -1) + { + ghiUnlock(); + return NULL; + } + + // Get a pointer to the object. + /////////////////////////////// + connection = ghiConnections[slot]; + + // Init the object. + /////////////////// + memset(connection, 0, sizeof(GHIConnection)); + connection->inUse = GHTTPTrue; + connection->request = (GHTTPRequest)slot; + connection->uniqueID = ghiNextUniqueID++; + connection->type = GHIGET; + connection->state = GHTTPSocketInit; + connection->URL = NULL; + connection->serverAddress = NULL; + connection->serverIP = INADDR_ANY; + connection->serverPort = (unsigned short)0; + connection->requestPath = NULL; + connection->sendHeaders = NULL; + connection->saveFile = NULL; + connection->blocking = GHTTPFalse; + connection->persistConnection = GHTTPFalse; + connection->result = GHTTPSuccess; + connection->progressCallback = NULL; + connection->completedCallback = NULL; + connection->callbackParam = NULL; + connection->socket = INVALID_SOCKET; + connection->socketError = 0; + connection->userBufferSupplied = GHTTPFalse; + connection->statusMajorVersion = 0; + connection->statusMinorVersion = 0; + connection->statusCode = 0; + connection->statusStringIndex = 0; + connection->headerStringIndex = 0; + connection->completed = GHTTPFalse; + connection->fileBytesReceived = 0; + connection->totalSize = -1; + connection->redirectURL = NULL; + connection->redirectCount = 0; + connection->chunkedTransfer = GHTTPFalse; + connection->processing = GHTTPFalse; + connection->throttle = GHTTPFalse; + connection->lastThrottleRecv = 0; + connection->post = NULL; + connection->maxRecvTime = 500; // Prevent blocking in async mode with systems that never generate WSAEWOULDBLOCK + connection->proxyOverridePort = GHI_DEFAULT_PORT; + connection->proxyOverrideServer = NULL; + connection->encryptor.mInterface = NULL; + +//handle used for asynch DNS lookups +#if !defined(GSI_NO_THREADS) + connection->handle = NULL; +#endif + + bResult = ghiInitBuffer(connection, &connection->sendBuffer, SEND_BUFFER_INITIAL_SIZE, SEND_BUFFER_INCREMENT_SIZE); + if(bResult) + bResult = ghiInitBuffer(connection, &connection->encodeBuffer, ENCODE_BUFFER_INITIAL_SIZE, ENCODE_BUFFER_INCREMENT_SIZE); + if(bResult) + bResult = ghiInitBuffer(connection, &connection->recvBuffer, RECV_BUFFER_INITIAL_SIZE, RECV_BUFFER_INCREMENT_SIZE); + if (bResult) + bResult = ghiInitBuffer(connection, &connection->decodeBuffer, DECODE_BUFFER_INITIAL_SIZE, DECODE_BUFFER_INCREMENT_SIZE); + + if(!bResult) + { + ghiFreeConnection(connection); + ghiUnlock(); + return NULL; + } + + // One more connection. + /////////////////////// + ghiNumConnections++; + + ghiUnlock(); + + return connection; +} + +GHTTPBool ghiFreeConnection +( + GHIConnection * connection +) +{ + assert(connection); + assert(connection->request >= 0); + assert(connection->request < ghiConnectionsLen); + assert(connection->inUse); + + // Check args. + ////////////// + if(!connection) + return GHTTPFalse; + if(!connection->inUse) + return GHTTPFalse; + if(connection->request < 0) + return GHTTPFalse; + if(connection->request >= ghiConnectionsLen) + return GHTTPFalse; + + ghiLock(); + + // Free data. + ///////////// + gsifree(connection->URL); + gsifree(connection->serverAddress); + gsifree(connection->requestPath); + gsifree(connection->sendHeaders); + gsifree(connection->redirectURL); + gsifree(connection->proxyOverrideServer); +#ifndef NOFILE + if(connection->saveFile) + fclose(connection->saveFile); +#endif + if(connection->socket != INVALID_SOCKET) + { + shutdown(connection->socket, 2); + closesocket(connection->socket); + } + ghiFreeBuffer(&connection->sendBuffer); + ghiFreeBuffer(&connection->encodeBuffer); + ghiFreeBuffer(&connection->recvBuffer); + ghiFreeBuffer(&connection->decodeBuffer); + ghiFreeBuffer(&connection->getFileBuffer); + if(connection->postingState.states) + ghiPostCleanupState(connection); + +#if !defined(GSI_NO_THREADS) + // Cancel and free asychronous lookup if it has not already been done + ///////////////////////////////////////////////////////////////////// + if (connection->handle) + { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "Cancelling Thread and freeing memory\n"); + gsiCancelResolvingHostname(connection->handle); + } +#endif + + // Check for an auto-free post. + /////////////////////////////// + if(connection->post && ghiIsPostAutoFree(connection->post)) + { + ghiFreePost(connection->post); + connection->post = NULL; + } + + // Check for an encryptor + if (connection->encryptor.mInitialized != GHTTPFalse) + { + if (connection->encryptor.mCleanupFunc) + (connection->encryptor.mCleanupFunc)(connection, &connection->encryptor); + connection->encryptor.mInitialized = GHTTPFalse; + } + + // Free the slot. + ///////////////// + connection->inUse = GHTTPFalse; + + // One less connection. + /////////////////////// + ghiNumConnections--; + + ghiUnlock(); + + return GHTTPTrue; +} + +GHIConnection * ghiRequestToConnection +( + GHTTPRequest request +) +{ + GHIConnection * connection; + + assert(request >= 0); + assert(request < ghiConnectionsLen); + + ghiLock(); + + // Check args. + ////////////// + if((request < 0) || (request >= ghiConnectionsLen)) + { + ghiUnlock(); + return NULL; + } + + connection = ghiConnections[request]; + + // Check for not in use. + //////////////////////// + if(!connection->inUse) + connection = NULL; + + ghiUnlock(); + + return connection; +} + +void ghiEnumConnections +( + GHTTPBool (* callback)(GHIConnection *) +) +{ + int i; + + // Check for no connections. + //////////////////////////// + if(ghiNumConnections <= 0) + return; + + ghiLock(); + for(i = 0 ; i < ghiConnectionsLen ; i++) + if(ghiConnections[i]->inUse) + callback(ghiConnections[i]); + ghiUnlock(); +} + +void ghiRedirectConnection +( + GHIConnection * connection +) +{ + assert(connection); + assert(connection->redirectURL); + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Redirecting Connection\n"); + + // Reset state. + /////////////// + connection->state = GHTTPSocketInit; + +#if !defined(GSI_NO_THREADS) + // Cancel and free asychronous lookup if it has not already been done + ///////////////////////////////////////////////////////////////////// + if (connection->handle) + { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "Cancelling Thread and freeing memory\n"); + gsiCancelResolvingHostname(connection->handle); + } +#endif + + // New URL. + /////////// + gsifree(connection->URL); + connection->URL = connection->redirectURL; + connection->redirectURL = NULL; + + // Reset stuff parsed from the URL. + /////////////////////////////////// + gsifree(connection->serverAddress); + connection->serverAddress = NULL; + connection->serverIP = 0; + connection->serverPort = 0; + gsifree(connection->requestPath); + connection->requestPath = NULL; + + // Close the socket. + //////////////////// + shutdown(connection->socket, 2); + closesocket(connection->socket); + connection->socket = INVALID_SOCKET; + + // Reset buffers. + ///////////////// + ghiResetBuffer(&connection->sendBuffer); + ghiResetBuffer(&connection->encodeBuffer); + ghiResetBuffer(&connection->recvBuffer); + ghiResetBuffer(&connection->decodeBuffer); + + // Reset status. + //////////////// + connection->statusMajorVersion = 0; + connection->statusMinorVersion = 0; + connection->statusCode = 0; + connection->statusStringIndex = 0; + + connection->headerStringIndex = 0; + + // The connection isn't closed. + /////////////////////////////// + connection->connectionClosed = GHTTPFalse; + + // Check for an encryptor + if (connection->encryptor.mInitialized != GHTTPFalse) + { + // cleanup the encryptor + if (connection->encryptor.mCleanupFunc) + (connection->encryptor.mCleanupFunc)(connection, &connection->encryptor); + connection->encryptor.mInitialized = GHTTPFalse; + + // if the redirect isn't secure, clear it + if(strncmp("https://", connection->URL, 8) != 0) + { + connection->encryptor.mEngine = GHTTPEncryptionEngine_None; + connection->encryptor.mInterface = NULL; + } + } + + // One more redirect. + ///////////////////// + connection->redirectCount++; +} + +void ghiCleanupConnections +( + void +) +{ + int i; + + if(!ghiConnections) + return; + + // Cleanup all running connections. + /////////////////////////////////// + ghiEnumConnections(ghiFreeConnection); + + // Cleanup the connection states. + ///////////////////////////////// + for(i = 0 ; i < ghiConnectionsLen ; i++) + gsifree(ghiConnections[i]); + gsifree(ghiConnections); + ghiConnections = NULL; + ghiConnectionsLen = 0; + ghiNumConnections = 0; +} diff --git a/code/gamespy/ghttp/ghttpConnection.h b/code/gamespy/ghttp/ghttpConnection.h new file mode 100644 index 00000000..b80b3bbf --- /dev/null +++ b/code/gamespy/ghttp/ghttpConnection.h @@ -0,0 +1,217 @@ + /* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GHTTPCONNECTION_H_ +#define _GHTTPCONNECTION_H_ + +#include "ghttpMain.h" +#include "ghttpEncryption.h" +#include "ghttpBuffer.h" +#include "ghttpPost.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Initial size and increment amount for the send buffer. +///////////////////////////////////////////////////////// +#define SEND_BUFFER_INITIAL_SIZE (2 * 1024) +#define SEND_BUFFER_INCREMENT_SIZE (4 * 1024) + +// Initial size and increment amount for the recv buffer. +///////////////////////////////////////////////////////////////// +#define RECV_BUFFER_INITIAL_SIZE (2 * 1024) +#define RECV_BUFFER_INCREMENT_SIZE (2 * 1024) + +// Initial size and increment amount for the get file buffer. +///////////////////////////////////////////////////////////// +#define GET_FILE_BUFFER_INITIAL_SIZE (2 * 1024) +#define GET_FILE_BUFFER_INCREMENT_SIZE (2 * 1024) + +// Initial size and increment amount for the ssl encoding buffer. +///////////////////////////////////////////////////////////////// +#define ENCODE_BUFFER_INITIAL_SIZE (2 * 1024) +#define ENCODE_BUFFER_INCREMENT_SIZE (1 * 1024) + +// Initial size and increment amount for the ssl decoding buffer. +///////////////////////////////////////////////////////////////// +#define DECODE_BUFFER_INITIAL_SIZE (2 * 1024) +#define DECODE_BUFFER_INCREMENT_SIZE (1 * 1024) + +// The size of the buffer for chunk headers (NOT including the NUL). +//////////////////////////////////////////////////////////////////// +#define CHUNK_HEADER_SIZE 10 + +// The type of request made. +//////////////////////////// +typedef enum +{ + GHIGET, // Buffer the file. + GHISAVE, // Save the file to disk. + GHISTREAM, // Stream the file. + GHIHEAD, // Get just the headers for a request. + GHIPOST // Only posting data (all types can post). +} GHIRequestType; + +// Chunk-reading states. +//////////////////////// +typedef enum +{ + CRHeader, // Reading a chunk header. + CRChunk, // Reading chunk (actual content). + CRCRLF, // Reading the CRLF at the end of a chunk. + CRFooter // Reading the footer at the end of the file (chunk with size of 0). +} CRState; + +// Protocol. +//////////// +typedef enum +{ + GHIHttp, + GHIHttps +} GHIProtocol; + +// This is the data for a single http connection. +///////////////////////////////////////////////// +typedef struct GHIConnection +{ + GHTTPBool inUse; // If true, this connection object is being used. + GHTTPRequest request; // This object's request index. + int uniqueID; // Every connection object has a unqiue ID. + + GHIRequestType type; // The type of request this connection is for. + + GHTTPState state; // The state of the request. + + char * URL; // The URL for the file. + char * serverAddress; // The address of the server as contained in the URL. + unsigned int serverIP; // The server's IP. + unsigned short serverPort; // The server's port. + char * requestPath; // The path as contained in the URL. + + GHIProtocol protocol; // Protocol used for this connection. + + char * sendHeaders; // Optional headers to pass with the request. + + FILE * saveFile; // If saving to disk, the file being saved to. + + GHTTPBool blocking; // Blocking flag. + + GHTTPBool persistConnection; // If TRUE, Connection: close will not be sent in the headers and the connection will be left open + + GHTTPResult result; // The result of the request. + ghttpProgressCallback progressCallback; // Called periodically with progress updates. + ghttpCompletedCallback completedCallback; // Called when the file has been received. + void * callbackParam; // User-data to be passed to the callbacks. + + SOCKET socket; // The socket for this connection. + int socketError; // If there was a socket error, the last error code is stored here. + + GHIBuffer sendBuffer; // The buffer for outgoing data. + GHIBuffer encodeBuffer; // The buffer for outgoing data. (will be encrypted; only used with https) + GHIBuffer recvBuffer; // The buffer for incoming data. (plain text) + GHIBuffer decodeBuffer; // The buffer for incoming data. (encrypted)(only used with https) + + GHIBuffer getFileBuffer; // ghttpGetFile[Ex] uses this buffer (which may be user-supplied). + GHTTPBool userBufferSupplied; // True if a user buffer was supplied. + + int statusMajorVersion; // The major-version number from the server's response. + int statusMinorVersion; // The minor-version number from the server's response. + int statusCode; // The status-code from the server's response. + int statusStringIndex; // Index in the recvBuffer where the status string starts. + + int headerStringIndex; // Index in the recvBuffer where the headers begin + + GHTTPBool completed; // This connection is completed - call the callback and kill it. + + GHTTPByteCount fileBytesReceived; // Number of file bytes received. + GHTTPByteCount totalSize; // Total size of the file, -1 if unknown. + + char * redirectURL; // If this is not NULL, we need to redirect the download to this URL. + int redirectCount; // Number of redirections done for this request. + + GHTTPBool chunkedTransfer; // The body of the response is chunky ("Transfer-Encoding: chunked"). + char chunkHeader[CHUNK_HEADER_SIZE + 1]; // Partial chunk headers are stored in here. + int chunkHeaderLen; // The number of bytes in chunkHeader. + int chunkBytesLeft; // Number of bytes left in the chunk (only valid for CRChunk). + CRState chunkReadingState; // Determines if a chunk header or chunk data is being read. + + GHTTPBool processing; // If true, being processed. Used to prevent recursive processing. + GHTTPBool connectionClosed; // If true, the connection has been closed (orderly or abortive) + + GHTTPBool throttle; // If true, throttle this connection. + gsi_time lastThrottleRecv; // The last time we received on a throttled connection. + + GHTTPPost post; // If not NULL, a reference to a post object to upload with the request. + GHIPostingState postingState; // If posting, the state of the upload. + + gsi_time maxRecvTime; // Max time spent receiving per call to "Think" - Prevents blocking on ultrafast connections + char * proxyOverrideServer; // Allows use of a different proxy than the global proxy + unsigned short proxyOverridePort; + + struct GHIEncryptor encryptor; + +#if !defined(GSI_NO_THREADS) + GSIResolveHostnameHandle handle; //handle used for asychronous DNS lookups +#endif + +} GHIConnection; + +// Create a new connection object. +// Returns NULL on failure. +////////////////////////////////// +GHIConnection * ghiNewConnection +( + void +); + +// Frees the connection object. +/////////////////////////////// +GHTTPBool ghiFreeConnection +( + GHIConnection * connection +); + +// Returns the connection object for a request index. +// Returns NULL if the index is bad. +///////////////////////////////////////////////////// +GHIConnection * ghiRequestToConnection +( + GHTTPRequest request +); + +// Calls the callback on each connection. +///////////////////////////////////////// +void ghiEnumConnections +( + GHTTPBool (* callback)(GHIConnection *) +); + +// Redirects the given connection. +// Resets the connection to get the +// file at connection->redirectURL. +/////////////////////////////////// +void ghiRedirectConnection +( + GHIConnection * connection +); + +// Kills all connections and frees up all memory. +///////////////////////////////////////////////// +void ghiCleanupConnections +( + void +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/ghttp/ghttpEncryption.c b/code/gamespy/ghttp/ghttpEncryption.c new file mode 100644 index 00000000..ccc579cd --- /dev/null +++ b/code/gamespy/ghttp/ghttpEncryption.c @@ -0,0 +1,1786 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "ghttpCommon.h" +#if defined(MATRIXSSL) +#include "../matrixssl/matrixssl.h" +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +GHTTPBool ghttpSetRequestEncryptionEngine(GHTTPRequest request, GHTTPEncryptionEngine engine) +{ + GHIConnection * connection = ghiRequestToConnection(request); + if(!connection) + return GHTTPFalse; + + // Translate default into the actual engine name + // We don't want to set the engine value to "default" because + // we'd lose the ability to determine the engine name in other places + if (engine == GHTTPEncryptionEngine_Default) + { + #if defined(MATRIXSSL) + engine = GHTTPEncryptionEngine_MatrixSsl; + #elif defined(REVOEXSSL) + engine = GHTTPEncryptionEngine_RevoEx; + #else + engine = GHTTPEncryptionEngine_GameSpy; + #endif + } + + // If the same engine has previously been set then we're done + if (connection->encryptor.mEngine == engine) + return GHTTPTrue; + + // If a different engine has previously been set then we're screwed + if (connection->encryptor.mInterface != NULL && + connection->encryptor.mEngine != engine) + { + return GHTTPFalse; + } + + // If the URL is HTTPS but the engine is specific as NONE then we can't connect + if((engine == GHTTPEncryptionEngine_None) && (strncmp(connection->URL, "https://", 8) == 0)) + return GHTTPFalse; + + // Initialize the engine + connection->encryptor.mEngine = engine; + + if (engine == GHTTPEncryptionEngine_None) + { + connection->encryptor.mInterface = NULL; + return GHTTPTrue; // this is the default, just return + } + else + { + // 02OCT07 BED: Design was changed to only allow one engine at a time + // Assert that the specified engine is the one supported + if (engine != GHTTPEncryptionEngine_Default) + { + #if defined(MATRIXSSL) + GS_ASSERT(engine==GHTTPEncryptionEngine_MatrixSsl); + #elif defined(REVOEXSSL) + GS_ASSERT(engine==GHTTPEncryptionEngine_RevoEx); + #else + GS_ASSERT(engine==GHTTPEncryptionEngine_GameSpy); + #endif + } + + connection->encryptor.mInterface = NULL; + connection->encryptor.mInitFunc = ghiEncryptorSslInitFunc; + connection->encryptor.mStartFunc = ghiEncryptorSslStartFunc; + connection->encryptor.mCleanupFunc = ghiEncryptorSslCleanupFunc; + connection->encryptor.mEncryptFunc = ghiEncryptorSslEncryptFunc; + connection->encryptor.mDecryptFunc = ghiEncryptorSslDecryptFunc; + connection->encryptor.mInitialized = GHTTPFalse; + connection->encryptor.mSessionStarted = GHTTPFalse; + connection->encryptor.mSessionEstablished = GHTTPFalse; + connection->encryptor.mEncryptOnBuffer = GHTTPTrue; + connection->encryptor.mEncryptOnSend = GHTTPFalse; + connection->encryptor.mLibSendsHandshakeMessages = GHTTPFalse; + return GHTTPTrue; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// ********************* MATRIXSSL ENCRYPTION ENGINE ********************* // +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MATRIXSSL + +// SSL requires a certificate validator +static int ghiSslCertValidator(struct sslCertInfo* theCertInfo, void* theUserData) +{ + // Taken from matrisSslExample + sslCertInfo_t *next; +/* + Make sure we are checking the last cert in the chain +*/ + next = theCertInfo; + while (next->next != NULL) { + next = next->next; + } + return next->verified; +} + +// Init the engine +GHIEncryptionResult ghiEncryptorSslInitFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor) +{ + sslKeys_t *keys = NULL; + sslSessionId_t *id = NULL; + + int ecodeResult; + + if (matrixSslOpen() < 0) + return GHIEncryptionResult_Error; + + if (matrixSslReadKeys(&keys, NULL, NULL, NULL, NULL) < 0) + return GHIEncryptionResult_Error; + + if (matrixSslNewSession((ssl_t**)&theEncryptor->mInterface, keys, id, 0) < 0) + return GHIEncryptionResult_Error; + + matrixSslSetCertValidator((ssl_t*)theEncryptor->mInterface, ghiSslCertValidator, NULL); + + theEncryptor->mInitialized = GHTTPTrue; + return GHIEncryptionResult_Success; +} + +// Start the handshake process +GHIEncryptionResult ghiEncryptorSslInitFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor) +{ + sslBuf_t helloWrapper; + + // Prepare the hello message + helloWrapper.buf = connection->sendBuffer.data; + helloWrapper.size = connection->sendBuffer.size; + helloWrapper.start = connection->sendBuffer.data + connection->sendBuffer.pos; + helloWrapper.end = helloWrapper.start; // start writing here + + ecodeResult = matrixSslEncodeClientHello((ssl_t*)theEncryptor->mInterface, &helloWrapper, 0); // 0 = cipher + if (ecodeResult != 0) + return GHIEncryptionResult_Error; // error! + + // Adjust the sendBuffer to account for the new data + connection->sendBuffer.len += (int)(helloWrapper.end - helloWrapper.start); + connection->sendBuffer.encrypted = GHTTPTrue; + theEncryptor->mSessionStarted = GHTTPTrue; +} + +// Destroy the engine +GHIEncryptionResult ghiEncryptorSslCleanupFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor) +{ + matrixSslClose(); + return GHIEncryptionResult_Success; +} + +// Encrypt some data +// - theEncryptedLength is reduced by the length of data written to theEncryptedBuffer +GHIEncryptionResult ghiEncryptorSslEncryptFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor, + const char * thePlainTextBuffer, + int thePlainTextLength, + char * theEncryptedBuffer, + int * theEncryptedLength) +{ + int encodeResult = 0; + + // SSL buffer wrapper + // Append to theDecryptedBuffer + sslBuf_t encryptedBuf; + encryptedBuf.buf = theEncryptedBuffer; // buf starts here + encryptedBuf.start = theEncryptedBuffer; // readpos, set to start + encryptedBuf.end = theEncryptedBuffer; // writepos, set to start + encryptedBuf.size = *theEncryptedLength; // total size of buf + + // perform the encryption + encodeResult = matrixSslEncode(connection->encryptor.mInterface, + (unsigned char*)thePlainTextBuffer, *thePlainTextLength, &encryptedBuf); + + if (encodeResult == SSL_ERROR) + return GHIEncryptionResult_Error; + else if (encodeResult == SSL_FULL) + return GHIEncryptionResult_BufferTooSmall; + else + { + //*thePlainTextLength = *thePlainTextLength; // we always use the entire buffer + *theEncryptedLength -= (int)(encryptedBuf.end - encryptedBuf.start); + return GHIEncryptionResult_Success; + } +} + +// Decrypt some data +// - During the handshaking process, this may result in data being appended to the send buffer +// - Data may be left in the encrypted buffer +// - theEncryptedLength becomes the length of data read from theEncryptedBuffer +// - theDecryptedLength becomes the length of data written to theDecryptedBuffer +GHIEncryptionResult ghiEncryptorSslDecryptFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor, + const char * theEncryptedBuffer, + int * theEncryptedLength, + char * theDecryptedBuffer, + int * theDecryptedLength) +{ + GHTTPBool decryptMore = GHTTPTrue; + int decodeResult = 0; + + // SSL buffer wrappers + sslBuf_t inBuf; + sslBuf_t decryptedBuf; + int encryptedStartSize = *theEncryptedLength; + + // Read from theEncryptedBuffer - Have to cast away the "const" + inBuf.buf = (unsigned char*)theEncryptedBuffer; + inBuf.start = (unsigned char*)theEncryptedBuffer; + inBuf.end = (unsigned char*)theEncryptedBuffer + *theEncryptedLength; + inBuf.size = *theEncryptedLength; + + // Append to theDecryptedBuffer + decryptedBuf.buf = theDecryptedBuffer; // buf starts here + decryptedBuf.start = theDecryptedBuffer; // readpos, set to start + decryptedBuf.end = theDecryptedBuffer; // writepos, set to start + decryptedBuf.size = *theDecryptedLength; // total size of buf + + // Perform the decode operation + // - may require multiple tries + while(decryptMore != GHTTPFalse && ((inBuf.end-inBuf.start) > 0)) + { + unsigned char error = 0; + unsigned char alertlevel = 0; + unsigned char alertdescription = 0; + + // perform the decode, this will decode a single SSL message at a time + decodeResult = matrixSslDecode(theEncryptor->mInterface, &inBuf, &decryptedBuf, + &error, &alertlevel, &alertdescription); + switch(decodeResult) + { + case SSL_SUCCESS: + // a message was handled internally by matrixssl + // No data is appeneded to the decrypted buffer + if (matrixSslHandshakeIsComplete(theEncryptor->mInterface)) + theEncryptor->mSessionEstablished = GHTTPTrue; + break; + + case SSL_PROCESS_DATA: + // We've received app data, continue on. + // App data was appended to the decrypted buffer + break; + + case SSL_SEND_RESPONSE: + { + // we must send an SSL response which has been written to decryptedBuf + // transfer this response to the connection's sendBuffer + int responseSize = decryptedBuf.end - decryptedBuf.start; + + // force disable-encryption + // this may seem like a hack, but it's the best way to avoid + // unnecessary data copies without modifying matrixSSL + theEncryptor->mSessionEstablished = GHTTPFalse; + ghiTrySendThenBuffer(connection, decryptedBuf.start, responseSize); + theEncryptor->mSessionEstablished = GHTTPTrue; + + // Remove the bytes from the decrypted buffer (we don't want to return them to the app) + decryptedBuf.end = decryptedBuf.start; // bug? + break; + } + + case SSL_ERROR: + // error decoding the data + decryptMore = GHTTPFalse; + break; + + case SSL_ALERT: + // server sent an alert + if (alertdescription == SSL_ALERT_CLOSE_NOTIFY) + decryptMore = GHTTPFalse; + break; + + case SSL_PARTIAL: + // need to read more data from the socket(inbuf incomplete) + decryptMore = GHTTPFalse; + break; + + case SSL_FULL: + { + // decodeBuffer is too small, need to increase size and try again + decryptMore = GHTTPFalse; + break; + } + }; + } + + // Store off the lengths + *theEncryptedLength = encryptedStartSize - (inBuf.end - inBuf.start); + *theDecryptedLength = decryptedBuf.end - decryptedBuf.start; + + // Return status to app + if (decodeResult == SSL_FULL) + return GHIEncryptionResult_BufferTooSmall; + else if (decodeResult == SSL_ERROR || decodeResult == SSL_ALERT) + return GHIEncryptionResult_Error; + + //if ((int)(decryptedBuf.end - decryptedBuf.start) > 0) + // printf ("Decrypted: %d bytes\r\n", *theDecryptedLength); + return GHIEncryptionResult_Success; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// ********************* REVOEX SSL ENCRYPTION ENGINE ******************** // +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#elif defined(REVOEXSSL) +#include + +typedef struct gsRevoExInterface +{ + SSLId mId; + SSLClientCertId mClientCertId; + SSLRootCAId mRootCAId; + GHTTPBool mConnected; // means "connected to socket", not "connected to remote machine" +} gsRevoExInterface; + +// Init the engine +GHIEncryptionResult ghiEncryptorSslInitFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor) +{ + int i=0; + gsRevoExInterface* sslInterface = NULL; + + // There is only one place where this function should be called, + // and it should check if the engine has been initialized + GS_ASSERT(theEncryptor->mInitialized == GHTTPFalse); + GS_ASSERT(theEncryptor->mInterface == NULL); + + // allocate the interface (need one per connection) + theEncryptor->mInterface = gsimalloc(sizeof(gsRevoExInterface)); + if (theEncryptor->mInterface == NULL) + { + // memory allocation failed + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Memory, GSIDebugLevel_WarmError, + "Failed to allocate SSL interface (out of memory: %d bytes)\r\n", sizeof(gsRevoExInterface)); + return GHIEncryptionResult_Error; + } + memset(theEncryptor->mInterface, 0, sizeof(gsRevoExInterface)); + sslInterface = (gsRevoExInterface*)theEncryptor->mInterface; + + { + int verifyOption = 0; + int rcode = 0; + + verifyOption = SSL_VERIFY_COMMON_NAME + | SSL_VERIFY_ROOT_CA + | SSL_VERIFY_DATE + | SSL_VERIFY_CHAIN + | SSL_VERIFY_SUBJECT_ALT_NAME; + + // todo serverAddress, is this used for certificate name? + sslInterface->mId = SSLNew(verifyOption, connection->serverAddress); + + rcode = SSLSetBuiltinRootCA(sslInterface->mId, SSL_ROOTCA_NINTENDO_1); + if(rcode != SSL_ENONE){ + SSLShutdown(sslInterface->mId); + return GHIEncryptionResult_Error; + } + + rcode = SSLSetBuiltinClientCert(sslInterface->mId, SSL_CLIENTCERT_NINTENDO_0); + if(rcode != SSL_ENONE){ + SSLShutdown(sslInterface->mId); + return GHIEncryptionResult_Error; + } + } + + theEncryptor->mInitialized = GHTTPTrue; + theEncryptor->mSessionStarted = GHTTPFalse; + theEncryptor->mSessionEstablished = GHTTPFalse; + //theEncryptor->mUseSSLConnect = GHTTPTrue; + theEncryptor->mEncryptOnBuffer = GHTTPFalse; + theEncryptor->mEncryptOnSend = GHTTPTrue; + theEncryptor->mLibSendsHandshakeMessages = GHTTPTrue; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx engine) initialized\r\n"); + + return GHIEncryptionResult_Success; +} + + +// Destroy the engine +GHIEncryptionResult ghiEncryptorSslCleanupFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor) +{ + if (theEncryptor != NULL) + { + gsRevoExInterface* sslInterface = (gsRevoExInterface*)theEncryptor->mInterface; + if (sslInterface != NULL) + { + SSLShutdown(sslInterface->mId); + gsifree(sslInterface); + theEncryptor->mInterface = NULL; + } + theEncryptor->mInitialized = GHTTPFalse; + theEncryptor->mSessionStarted = GHTTPFalse; + theEncryptor->mSessionEstablished = GHTTPFalse; + } + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) engine cleanup called\r\n"); + + GSI_UNUSED(connection); + + return GHIEncryptionResult_Success; +} + + +GHIEncryptionResult ghiEncryptorSslStartFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor) +{ + gsRevoExInterface* sslInterface = (gsRevoExInterface*)theEncryptor->mInterface; + int result = 0; + + GS_ASSERT(theEncryptor->mSessionStarted == GHTTPFalse); + + // Call this only AFTER the socket has been connected to the remote server + if (!sslInterface->mConnected) + { + result = SSLConnect(sslInterface->mId, connection->socket); + if (result != SSL_ENONE) + { + switch(result) + { + case SSL_EFAILED: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLConnect failed (SSL_EFAILED)\r\n"); + break; + case SSL_ESSLID: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLConnect failed (SSL_ESSLID)\r\n"); + break; + default: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLConnect failed (Unhandled Error)\r\n"); + break; + } + return GHIEncryptionResult_Error; + } + sslInterface->mConnected = GHTTPTrue; + } + + GS_ASSERT(sslInterface->mConnected == GHTTPTrue); + + // begin securing the session + result = SSLDoHandshake(sslInterface->mId); + if (result != SSL_ENONE) + { + // Check for EWOULDBLOCK conditions + if (result == SSL_EWANT_READ || result == SSL_EWANT_WRITE) + return GHIEncryptionResult_Success; + + switch(result) + { + case SSL_EFAILED: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EFAILED)\r\n"); + break; + case SSL_ESSLID: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_ESSLID)\r\n"); + break; + case SSL_ESYSCALL: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_ESYSCALL)\r\n"); + break; + case SSL_EZERO_RETURN: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EZERO_RETURN)\r\n"); + break; + case SSL_EWANT_CONNECT: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EWANT_CONNECT)\r\n"); + break; + case SSL_EVERIFY_COMMON_NAME: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EVERIFY_COMMON_NAME)\r\n"); + break; + case SSL_EVERIFY_CHAIN: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EVERIFY_CHAIN)\r\n"); + break; + case SSL_EVERIFY_ROOT_CA: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EVERIFY_ROOT_CA)\r\n"); + break; + case SSL_EVERIFY_DATE: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EVERIFY_DATE)\r\n"); + break; + default: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (Unhandled Error)\r\n"); + break; + } + return GHIEncryptionResult_Error; + } + + // Success + theEncryptor->mSessionStarted = GHTTPTrue; + theEncryptor->mSessionEstablished = GHTTPTrue; + return GHIEncryptionResult_Success; +} + +// Encrypt and send some data +GHIEncryptionResult ghiEncryptorSslEncryptSend(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor, + const char * thePlainTextBuffer, + int thePlainTextLength, + int * theBytesSentOut) +{ + gsRevoExInterface* sslInterface = (gsRevoExInterface*)theEncryptor->mInterface; + int result = 0; + + result = SSLWrite(sslInterface->mId, thePlainTextBuffer, thePlainTextLength); + if (result == SSL_EZERO_RETURN) + { + // send 0 is not fatal + *theBytesSentOut = 0; + } + else if (result == SSL_EWANT_WRITE) + { + // signal socket error, GetLastError will return EWOULDBLOCK or EINPROGRESS + *theBytesSentOut = -1; + } + else if (result < 0) + { + switch(result) + { + case SSL_EFAILED: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLWrite failed (SSL_EFAILED)\r\n"); + break; + case SSL_ESSLID: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLWrite failed (SSL_ESSLID)\r\n"); + break; + case SSL_EWANT_READ: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLWrite failed (SSL_EWANT_READ)\r\n"); + break; + case SSL_ESYSCALL: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLWrite failed (SSL_ESYSCALL)\r\n"); + break; + case SSL_EWANT_CONNECT: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLWrite failed (SSL_EWANT_CONNECT)\r\n"); + break; + default: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLWrite failed (Unhandled Error)\r\n"); + break; + } + return GHIEncryptionResult_Error; + } + else + { + GS_ASSERT(result > 0); + *theBytesSentOut = result; + } + GSI_UNUSED(connection); + return GHIEncryptionResult_Success; +} + +// Receive and decrypt some data +GHIEncryptionResult ghiEncryptorSslDecryptRecv(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor, + char * theDecryptedBuffer, + int * theDecryptedLength) +{ + gsRevoExInterface* sslInterface = (gsRevoExInterface*)theEncryptor->mInterface; + int result = 0; + + result = SSLRead(sslInterface->mId, theDecryptedBuffer, *theDecryptedLength); + if (result == SSL_EZERO_RETURN) + { + // receive 0 is not fatal + *theDecryptedLength = 0; + } + else if (result == SSL_EWANT_READ) + { + // signal socket error, GetLastError will return EWOULDBLOCK or EINPROGRESS + *theDecryptedLength = -1; + } + else if (result < 0) + { + // Fatal errors + switch(result) + { + case SSL_EFAILED: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLRead failed (SSL_EFAILED)\r\n"); + break; + case SSL_ESSLID: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLRead failed (SSL_ESSLID)\r\n"); + break; + case SSL_EWANT_WRITE: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLRead failed (SSL_EWANT_WRITE)\r\n"); + break; + case SSL_ESYSCALL: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLRead failed (SSL_ESYSCALL)\r\n"); + break; + case SSL_EWANT_CONNECT: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLRead failed (SSL_EWANT_CONNECT)\r\n"); + break; + default: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLRead failed (Unhandled Error)\r\n"); + break; + } + return GHIEncryptionResult_Error; + } + else + { + GS_ASSERT(result > 0); + *theDecryptedLength = result; + } + GSI_UNUSED(connection); + return GHIEncryptionResult_Success; +} + +GHIEncryptionResult ghiEncryptorSslEncryptFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor, + const char * thePlainTextBuffer, + int thePlainTextLength, + char * theEncryptedBuffer, + int * theEncryptedLength) +{ + GS_FAIL(); // Should never call this for RevoEx SSL! It uses encrypt on send + + GSI_UNUSED(connection); + GSI_UNUSED(theEncryptor); + GSI_UNUSED(thePlainTextBuffer); + GSI_UNUSED(thePlainTextLength); + GSI_UNUSED(theEncryptedBuffer); + GSI_UNUSED(theEncryptedLength); + + return GHIEncryptionResult_Error; +} + +GHIEncryptionResult ghiEncryptorSslDecryptFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor, + const char * theEncryptedBuffer, + int * theEncryptedLength, + char * theDecryptedBuffer, + int * theDecryptedLength) +{ + GS_FAIL(); // Should never call this for RevoEx SSL! It uses decrypt on recv + + GSI_UNUSED(connection); + GSI_UNUSED(theEncryptor); + GSI_UNUSED(theEncryptedBuffer); + GSI_UNUSED(theEncryptedLength); + GSI_UNUSED(theDecryptedBuffer); + GSI_UNUSED(theDecryptedLength); + + return GHIEncryptionResult_Error; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// *********************** GS SSL ENCRYPTION ENGINE ********************** // +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#else + +#include "../common/gsSSL.h" +#include "../common/gsSHA1.h" +#include "../common/gsRC4.h" +#include "../md5.h" + + +// Processor for SSL state messages (transparent to application) +static GHIEncryptionResult ghiEncryptorProcessSSLHandshake(struct GHIConnection * connection, + struct GHIEncryptor * encryptor, + GHIBuffer * data); + +// SSL-ASN.1 lengths are variable length NBO integers +// we use this utility to make data packing easier +// example: 61 little-endian(intel) = 61 00 00 00 +// big-endian(network) = 00 00 00 61 +static void ghiEncryptorWriteNBOLength(unsigned char* buf, int value, int size) +{ + int NBO = (int)htonl(value); + unsigned char* NBOData = (unsigned char*)&NBO; + + assert(size <= sizeof(NBO)); + if (size > sizeof(NBO)) + return; // can't write more than 4 bytes! + + // this won't work if NBO ever changes from big-endian + memcpy(buf, NBOData+(sizeof(int)-size), (size_t)size); +} +static GHTTPBool ghiEncryptorReadNBOLength(GHIBuffer * data, int* value, int size) +{ + assert(size <= sizeof(*value)); + if (size > sizeof(*value)) + return GHTTPFalse; + if (GHTTPFalse == ghiReadDataFromBufferFixed(data, ((char*)value)+(sizeof(int)-size), size)) + return GHTTPFalse; + + *value = (int)htonl(*value); + return GHTTPTrue; +} + +static GHTTPBool ghiEncryptorParseASN1Sequence(GHIBuffer * data, int* lenOut) +{ + char tempChar = '\0'; + + if (GHTTPFalse == ghiReadDataFromBufferFixed(data, &tempChar, 1)) + return GHTTPFalse; + if (tempChar != 0x30) // sequence start byte + return GHTTPFalse; + + if (GHTTPFalse == ghiReadDataFromBufferFixed(data, &tempChar, 1)) + return GHTTPFalse; + if ((tempChar & 0x80) == 0x80) + { + int tempInt = 0; + + // length is stored in next (tempChar^0x80) bytes + tempChar ^= 0x80; + if (GHTTPFalse == ghiEncryptorReadNBOLength(data, &tempInt, tempChar)) + return GHTTPFalse; + if (tempInt > (data->len - data->pos)) + return GHTTPFalse; + + *lenOut = tempInt; + return GHTTPTrue; + } + else + { + if ((int)tempChar > (data->len - data->pos)) + return GHTTPFalse; + + *lenOut = tempChar; + return GHTTPTrue; + } +} + +static void ghiEncryptorGenerateEncryptionKeys(gsSSL* sslInterface) +{ + // Use the server random, client random and pre master secret + // to compute the encryption key. + + // SSLv3 style + // master_secret = { + // MD5(pre_master_secret + SHA1("A"+pre_master_secret+client_random+server_random)) + + // MD5(pre_master_secret + SHA1("BB"+pre_master_secret+client_random+server_random)) + + // MD5(pre_master_secret + SHA1("CCC"+pre_master_secret+client_random+server_random)) + // } + // key_block = { + // MD5(master_secret + SHA1("A"+master_secret+server_random+client_random)) + + // MD5(master_secret + SHA1("BB"+master_secret+server_random+client_random)) + + // MD5(master_secret + SHA1("CCC"+master_secret+server_random+client_random)) + + SHA1Context sha1; + MD5_CTX md5; + unsigned char temp[SHA1HashSize]; + + unsigned int randomSize = 32; + unsigned char keyblock[64]; // todo: support different key sizes + + // master_secret "A" + SHA1Reset(&sha1); + SHA1Input(&sha1, (const unsigned char*)"A", 1); + SHA1Input(&sha1, (const unsigned char*)sslInterface->premastersecret, GS_SSL_MASTERSECRET_LEN); + SHA1Input(&sha1, (const unsigned char*)sslInterface->clientRandom, randomSize); + SHA1Input(&sha1, (const unsigned char*)sslInterface->serverRandom, randomSize); + SHA1Result(&sha1, temp); + MD5Init(&md5); + MD5Update(&md5, (unsigned char*)sslInterface->premastersecret, GS_SSL_MASTERSECRET_LEN); + MD5Update(&md5, temp, GS_CRYPT_SHA1_HASHSIZE); + MD5Final((unsigned char*)&sslInterface->mastersecret[0*GS_CRYPT_MD5_HASHSIZE], &md5); + + // master_secret "BB" + SHA1Reset(&sha1); + SHA1Input(&sha1, (const unsigned char*)"BB", 2); + SHA1Input(&sha1, (const unsigned char*)sslInterface->premastersecret, GS_SSL_MASTERSECRET_LEN); + SHA1Input(&sha1, (const unsigned char*)sslInterface->clientRandom, randomSize); + SHA1Input(&sha1, (const unsigned char*)sslInterface->serverRandom, randomSize); + SHA1Result(&sha1, temp); + MD5Init(&md5); + MD5Update(&md5, (unsigned char*)sslInterface->premastersecret, GS_SSL_MASTERSECRET_LEN); + MD5Update(&md5, temp, GS_CRYPT_SHA1_HASHSIZE); + MD5Final((unsigned char*)&sslInterface->mastersecret[1*GS_CRYPT_MD5_HASHSIZE], &md5); + + // master_secret "CCC" + SHA1Reset(&sha1); + SHA1Input(&sha1, (const unsigned char*)"CCC", 3); + SHA1Input(&sha1, (const unsigned char*)sslInterface->premastersecret, GS_SSL_MASTERSECRET_LEN); + SHA1Input(&sha1, (const unsigned char*)sslInterface->clientRandom, randomSize); + SHA1Input(&sha1, (const unsigned char*)sslInterface->serverRandom, randomSize); + SHA1Result(&sha1, temp); + MD5Init(&md5); + MD5Update(&md5, (unsigned char*)sslInterface->premastersecret, GS_SSL_MASTERSECRET_LEN); + MD5Update(&md5, temp, GS_CRYPT_SHA1_HASHSIZE); + MD5Final((unsigned char*)&sslInterface->mastersecret[2*GS_CRYPT_MD5_HASHSIZE], &md5); + + // key_block "A" + SHA1Reset(&sha1); + SHA1Input(&sha1, (const unsigned char*)"A", 1); + SHA1Input(&sha1, (const unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN); + SHA1Input(&sha1, (const unsigned char*)sslInterface->serverRandom, randomSize); + SHA1Input(&sha1, (const unsigned char*)sslInterface->clientRandom, randomSize); + SHA1Result(&sha1, temp); + MD5Init(&md5); + MD5Update(&md5, (unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN); + MD5Update(&md5, temp, GS_CRYPT_SHA1_HASHSIZE); + MD5Final(&keyblock[0*GS_CRYPT_MD5_HASHSIZE], &md5); + + // key_block "BB" + SHA1Reset(&sha1); + SHA1Input(&sha1, (const unsigned char*)"BB", 2); + SHA1Input(&sha1, (const unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN); + SHA1Input(&sha1, (const unsigned char*)sslInterface->serverRandom, randomSize); + SHA1Input(&sha1, (const unsigned char*)sslInterface->clientRandom, randomSize); + SHA1Result(&sha1, temp); + MD5Init(&md5); + MD5Update(&md5, (unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN); + MD5Update(&md5, temp, GS_CRYPT_SHA1_HASHSIZE); + MD5Final(&keyblock[1*GS_CRYPT_MD5_HASHSIZE], &md5); + + // key_block "CCC" + SHA1Reset(&sha1); + SHA1Input(&sha1, (const unsigned char*)"CCC", 3); + SHA1Input(&sha1, (const unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN); + SHA1Input(&sha1, (const unsigned char*)sslInterface->serverRandom, randomSize); + SHA1Input(&sha1, (const unsigned char*)sslInterface->clientRandom, randomSize); + SHA1Result(&sha1, temp); + MD5Init(&md5); + MD5Update(&md5, (unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN); + MD5Update(&md5, temp, GS_CRYPT_SHA1_HASHSIZE); + MD5Final(&keyblock[2*GS_CRYPT_MD5_HASHSIZE], &md5); + + // key_block "DDDD" + SHA1Reset(&sha1); + SHA1Input(&sha1, (const unsigned char*)"DDDD", 4); + SHA1Input(&sha1, (const unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN); + SHA1Input(&sha1, (const unsigned char*)sslInterface->serverRandom, randomSize); + SHA1Input(&sha1, (const unsigned char*)sslInterface->clientRandom, randomSize); + SHA1Result(&sha1, temp); + MD5Init(&md5); + MD5Update(&md5, (unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN); + MD5Update(&md5, temp, GS_CRYPT_SHA1_HASHSIZE); + MD5Final(&keyblock[3*GS_CRYPT_MD5_HASHSIZE], &md5); + + // key_block "EEEEE" + // key_block "FFFFFF" + // ... continue if more key material is needed + + // todo: support different key sizes + // KEYBLOCK + // writemac[16], readmac[16], writekey[16], readkey[16], writeIV[0], readIV[0] + memcpy(sslInterface->clientWriteMACSecret, &keyblock[0], 16); + memcpy(sslInterface->clientReadMACSecret, &keyblock[16], 16); + memcpy(sslInterface->clientWriteKey, &keyblock[32], 16); + memcpy(sslInterface->clientReadKey, &keyblock[48], 16); + + sslInterface->clientWriteMACLen = 16; + sslInterface->clientReadMACLen = 16; + sslInterface->clientWriteKeyLen = 16; + sslInterface->clientReadKeyLen = 16; + + // Init the stream cipher + RC4Init(&sslInterface->sendRC4, (const unsigned char*)sslInterface->clientWriteKey, sslInterface->clientWriteKeyLen); + RC4Init(&sslInterface->recvRC4, (const unsigned char*)sslInterface->clientReadKey, sslInterface->clientReadKeyLen); + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "Generated SSL session keys\r\n"); +} + + +// Init the engine +GHIEncryptionResult ghiEncryptorSslInitFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor) +{ + gsSSL* sslInterface = NULL; + + // There is only one place where this function should be called, + // and it should check if the engine has been initialized + assert(theEncryptor->mInitialized == GHTTPFalse); + assert(theEncryptor->mInterface == NULL); + + // Make sure the send buffer is large enough for the SSL handshake (handshake is <1k) + if (connection->sendBuffer.size - connection->sendBuffer.len < sizeof(gsSSLClientHelloMsg)) + return GHIEncryptionResult_BufferTooSmall; + + // allocate the interface (need one per connection) + theEncryptor->mInterface = gsimalloc(sizeof(gsSSL)); + if (theEncryptor->mInterface == NULL) + { + // memory allocation failed + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Memory, GSIDebugLevel_WarmError, + "Failed to allocate SSL interface (out of memory: %d bytes)\r\n", sizeof(gsSSL)); + return GHIEncryptionResult_Error; + } + memset(theEncryptor->mInterface, 0, sizeof(gsSSL)); + sslInterface = (gsSSL*)theEncryptor->mInterface; + + theEncryptor->mInitialized = GHTTPTrue; + theEncryptor->mSessionStarted = GHTTPFalse; + theEncryptor->mSessionEstablished = GHTTPFalse; + theEncryptor->mEncryptOnBuffer = GHTTPTrue; + theEncryptor->mEncryptOnSend = GHTTPFalse; + theEncryptor->mLibSendsHandshakeMessages = GHTTPFalse; + MD5Init(&sslInterface->finishHashMD5); + SHA1Reset(&sslInterface->finishHashSHA1); + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL engine initialized\r\n"); + + return GHIEncryptionResult_Success; +} + +// Destroy the engine +GHIEncryptionResult ghiEncryptorSslCleanupFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor) +{ + if (theEncryptor != NULL) + { + gsSSL* sslInterface = (gsSSL*)theEncryptor->mInterface; + if (sslInterface != NULL) + { + gsifree(sslInterface); + theEncryptor->mInterface = NULL; + } + theEncryptor->mInitialized = GHTTPFalse; + theEncryptor->mSessionStarted = GHTTPFalse; + theEncryptor->mSessionEstablished = GHTTPFalse; + } + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL engine cleanup called\r\n"); + + GSI_UNUSED(connection); + + return GHIEncryptionResult_Success; +} + +// Init the engine +GHIEncryptionResult ghiEncryptorSslStartFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor) +{ + gsSSL* sslInterface = (gsSSL*)theEncryptor->mInterface; + gsSSLClientHelloMsg helloMsg; + int i=0; + + // prepare the client hello + // 1) + helloMsg.header.contentType = GS_SSL_CONTENT_HANDSHAKE; + helloMsg.header.versionMajor = GS_SSL_VERSION_MAJOR; + helloMsg.header.versionMinor = GS_SSL_VERSION_MINOR; + + // Set the length of the client hello message (2-byte NBO int, not including record header) + ghiEncryptorWriteNBOLength(helloMsg.header.lengthNBO, sizeof(gsSSLClientHelloMsg)-sizeof(gsSSLRecordHeaderMsg), 2); + + helloMsg.handshakeType = GS_SSL_HANDSHAKE_CLIENTHELLO; + helloMsg.versionMajor = GS_SSL_VERSION_MAJOR; + helloMsg.versionMinor = GS_SSL_VERSION_MINOR; + + // Set the length of the client hello data (3 byte NBO int) + // This is the total message length MINUS the SSL record header MINUS four additional header bytes + ghiEncryptorWriteNBOLength(helloMsg.lengthNBO, sizeof(gsSSLClientHelloMsg)-sizeof(gsSSLRecordHeaderMsg)-4, 3); + //ghttpEncryptorSetNBOBytesFromHBOInt(helloMsg.time, (gsi_u32)current_time(), 4); // 4 byte time value (for randomness) + ghiEncryptorWriteNBOLength(helloMsg.time, 0, 4); // test code: no randomness + + // fill in the [rest of the] random + // Security Note: If a hacker is able to discern the current_time() they may be able to + // recreate the random bytes and recover the session key. + Util_RandSeed(current_time()); + for (i=0; i<28; i++) + { + #if defined(GS_CRYPT_NO_RANDOM) + #pragma message ("!!!WARNING: SSL Random disable for debug purposes. SSL not secured!!!") + helloMsg.random[i] = 0; // test code: no randomness + #else + helloMsg.random[i] = (unsigned char)Util_RandInt(0, 0xff); + #endif + } + + // store a copy of the random (used later for key generation) + memcpy(&sslInterface->clientRandom[0], helloMsg.time, 4); + memcpy(&sslInterface->clientRandom[4], helloMsg.random, 28); + + // todo: session resumption + helloMsg.sessionIdLen = 0; + + // fill in cipher suite IDs + helloMsg.cipherSuitesLength = htons(sizeof(gsi_u16)*GS_SSL_NUM_CIPHER_SUITES); + for (i=0; i < GS_SSL_NUM_CIPHER_SUITES; i++) + helloMsg.cipherSuites[i] = htons((unsigned short)gsSSLCipherSuites[i].mSuiteID); + + // there are no standard SSL compression methods + helloMsg.compressionMethodLen = 1; + helloMsg.compressionMethodList = 0; + + // We need to compute a hash of all the handshake messages + // Add this message to the hash (both MD5 hash and SHA1 hash) + MD5Update(&sslInterface->finishHashMD5, (unsigned char*)&helloMsg+sizeof(gsSSLRecordHeaderMsg), sizeof(gsSSLClientHelloMsg)-sizeof(gsSSLRecordHeaderMsg)); + SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)&helloMsg+sizeof(gsSSLRecordHeaderMsg), sizeof(gsSSLClientHelloMsg)-sizeof(gsSSLRecordHeaderMsg)); + + // Now send it (we already verified the length, so this should not fail) + if (GHTTPFalse == ghiAppendDataToBuffer(&connection->sendBuffer, (const char*)&helloMsg, sizeof(gsSSLClientHelloMsg))) + { + // assert or just return? + return GHIEncryptionResult_BufferTooSmall; + } + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL engine handshake started\r\n"); + + theEncryptor->mSessionStarted = GHTTPTrue; + return GHIEncryptionResult_Success; + +} + +// Encrypt some data +// - theEncryptedLength is reduced by the length of data written to theEncryptedBuffer +// So if the encrypted buffer is 255 bytes long and we write 50 additional bytes, we'll return 205. +GHIEncryptionResult ghiEncryptorSslEncryptFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor, + const char * thePlainTextBuffer, + int thePlainTextLength, + char * theEncryptedBuffer, + int * theEncryptedLength) +{ + if (theEncryptor != NULL) + { + gsSSL* sslInterface = (gsSSL*)theEncryptor->mInterface; + if (sslInterface == NULL || theEncryptor->mSessionEstablished == GHTTPFalse) + { + // not secured yet, send as plain text + if (thePlainTextLength > *theEncryptedLength) + return GHIEncryptionResult_BufferTooSmall; + memcpy(theEncryptedBuffer, thePlainTextBuffer, (size_t)thePlainTextLength); + *theEncryptedLength += thePlainTextLength; // number of bytes written + } + else + { + // Create an SSL encrypted record + // The order of operations below is very important. + // The MAC must be computed before ciphering the plain text because + // theEncryptedBuffer may be the same memory location as thePlainTextBuffer + + gsSSL* sslInterface = (gsSSL*)theEncryptor->mInterface; + gsSSLRecordHeaderMsg* header = NULL; + MD5_CTX md5; + int pos = 0; + unsigned short lengthNBO = htons((unsigned short)thePlainTextLength); + unsigned char MAC[GS_CRYPT_MD5_HASHSIZE]; + + // The SSL record adds a little overhead + if (*theEncryptedLength < (thePlainTextLength+(int)sizeof(gsSSLRecordHeaderMsg))) + return GHIEncryptionResult_BufferTooSmall; + + // write the SSL header + header = (gsSSLRecordHeaderMsg*)theEncryptedBuffer; + header->contentType = GS_SSL_CONTENT_APPLICATIONDATA; + header->versionMajor = GS_SSL_VERSION_MAJOR; + header->versionMinor = GS_SSL_VERSION_MINOR; + pos += sizeof(gsSSLRecordHeaderMsg); + + // calculate the MAC + MD5Init(&md5); + MD5Update(&md5, sslInterface->clientWriteMACSecret, (unsigned int)sslInterface->clientWriteMACLen); + MD5Update(&md5, (unsigned char*)GS_SSL_PAD_ONE, GS_SSL_MD5_PAD_LEN); + MD5Update(&md5, sslInterface->sendSeqNBO, sizeof(sslInterface->sendSeqNBO)); + MD5Update(&md5, (unsigned char*)"\x17", 1); // content type application data + MD5Update(&md5,(unsigned char*)&lengthNBO, sizeof(lengthNBO)); + MD5Update(&md5, (unsigned char*)thePlainTextBuffer, (unsigned int)thePlainTextLength); // **cast-away const** + MD5Final(MAC, &md5); // first half of MAC + + MD5Init(&md5); + MD5Update(&md5, sslInterface->clientWriteMACSecret, (unsigned int)sslInterface->clientWriteMACLen); + MD5Update(&md5, (unsigned char*)GS_SSL_PAD_TWO, GS_SSL_MD5_PAD_LEN); + MD5Update(&md5, MAC, GS_CRYPT_MD5_HASHSIZE); + MD5Final(MAC, &md5); // complete MAC + + // apply stream cipher to data + MAC + RC4Encrypt(&sslInterface->sendRC4, (const unsigned char*)thePlainTextBuffer, (unsigned char*)&theEncryptedBuffer[pos], thePlainTextLength); + pos += thePlainTextLength; + RC4Encrypt(&sslInterface->sendRC4, MAC, (unsigned char*)&theEncryptedBuffer[pos], GS_CRYPT_MD5_HASHSIZE); + pos += GS_CRYPT_MD5_HASHSIZE; + + // Now that we know the final length (data+mac+pad), write it into the header + ghiEncryptorWriteNBOLength(header->lengthNBO, (int)(pos - sizeof(gsSSLRecordHeaderMsg)), 2); + + // adjust encrypted length + *theEncryptedLength -= pos; + + // Update the sequence number for the next message (8-byte, NBO) + pos = 7; // **changing the semantic of variable "pos" + do + { + //int carry = 0; + if (sslInterface->sendSeqNBO[pos] == 0xFF) // wraparound means carry + { + //carry = 1; + sslInterface->sendSeqNBO[pos] = 0; + pos -= 1; + } + else + { + sslInterface->sendSeqNBO[pos] += 1; + pos = 0; // end addition + } + } while(pos >= 0); + } + } + + GSI_UNUSED(connection); + return GHIEncryptionResult_Success; +} + +// Decrypt some data +// - During the handshaking process, this may result in data being appended to the send buffer +// - Data may be left in the encrypted buffer +// - theEncryptedLength becomes the length of data read from theEncryptedBuffer +// - theDecryptedLength becomes the length of data written to theDecryptedBuffer +GHIEncryptionResult ghiEncryptorSslDecryptFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor, + const char * theEncryptedBuffer, + int * theEncryptedLength, + char * theDecryptedBuffer, + int * theDecryptedLength) +{ + gsSSL* sslInterface = NULL; + int readPos = 0; + int writePos = 0; + + // Make sure we have a valid encryptor + assert(theEncryptor != NULL); + assert(theEncryptor->mInterface != NULL); + if (theEncryptor == NULL || theEncryptor->mInterface == NULL) + { + // no encryption set? copy as plain text + memcpy(theDecryptedBuffer, theEncryptedBuffer, (size_t)(*theEncryptedLength)); + *theDecryptedLength = *theEncryptedLength; + *theEncryptedLength = 0; // no bytes remaining + return GHIEncryptionResult_Success; + } + + sslInterface = (gsSSL*)theEncryptor->mInterface; + if (sslInterface == NULL) + return GHIEncryptionResult_Error; + + // Read each SSL message from the stream (leave partial messages) + while(readPos < *theEncryptedLength) + { + gsSSLRecordHeaderMsg* header = NULL; + unsigned short length = 0; + GHIEncryptionResult result; + + // make sure we have the complete record header + if ((*theEncryptedLength-readPos) < sizeof(gsSSLRecordHeaderMsg)) + break; + header = (gsSSLRecordHeaderMsg*)&theEncryptedBuffer[readPos]; + + // make sure we have the complete record data + // Warning: Convert the length in two steps to avoid issues with byte order + // BAD!! -> length = ntohs((header->lengthNBO[0] | (header->lengthNBO[1] << 8))); + //length = (unsigned short)(header->lengthNBO[0] | (header->lengthNBO[1] << 8)); + memcpy(&length, &header->lengthNBO[0], sizeof(length)); + length = ntohs(length); + if ( *theEncryptedLength < (readPos + length +(int)sizeof(gsSSLRecordHeaderMsg))) + break; // wait for more data + + // if we have to decrypt, make sure there is room in the decrypt buffer + if (connection->encryptor.mSessionEstablished) + { + if ((*theDecryptedLength-writePos) < length) + { + *theEncryptedLength = readPos; // bytes read *NOT* bytes remaining + *theDecryptedLength = writePos; // bytes written + + if (*theDecryptedLength>0) + return GHIEncryptionResult_Success; + else + return GHIEncryptionResult_BufferTooSmall; + } + } + + //readPos += sizeof(gsSSLRecordHeaderMsg); + + // process the record data + switch(header->contentType) + { + case GS_SSL_CONTENT_HANDSHAKE: + { + GHIBuffer data; + + // Apply stream cipher if the session has been established + readPos += sizeof(gsSSLRecordHeaderMsg); + if (connection->encryptor.mSessionEstablished) + RC4Encrypt(&sslInterface->recvRC4, (const unsigned char*)&theEncryptedBuffer[readPos], (unsigned char*)&theEncryptedBuffer[readPos], length); + + ghiInitReadOnlyBuffer(connection, &data, &theEncryptedBuffer[readPos], length); + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "SSL handshake message received\r\n"); + + result = ghiEncryptorProcessSSLHandshake(connection, theEncryptor, &data); + if (result != GHIEncryptionResult_Success) + return result; // error! + + break; + } + case GS_SSL_CONTENT_APPLICATIONDATA: + { + // make sure there is enough room to receive this data + if ( (*theDecryptedLength-writePos) < length) + { + } + + // Apply stream cipher if the session has been established + readPos += sizeof(gsSSLRecordHeaderMsg); + if (connection->encryptor.mSessionEstablished) + RC4Encrypt(&sslInterface->recvRC4, (const unsigned char*)&theEncryptedBuffer[readPos], (unsigned char*)&theEncryptedBuffer[readPos], length); + + // verify MAC and pad + // verifyMAC(); + + // copy to decrypted buffer so HTTP layer can process + memcpy(theDecryptedBuffer+writePos, &theEncryptedBuffer[readPos], (size_t)(length - GS_CRYPT_MD5_HASHSIZE)); + writePos += length - GS_CRYPT_MD5_HASHSIZE; + break; + } + + case GS_SSL_CONTENT_CHANGECIPHERSPEC: + readPos += sizeof(gsSSLRecordHeaderMsg); + //if(readPos > *theEncryptedLength) + // _asm int 3; + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "SSL change cipher spec message received\r\n"); + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_Notice, + "SSL: Incoming traffic now encrypted\r\n"); + connection->encryptor.mSessionEstablished = GHTTPTrue; + break; + + case GS_SSL_CONTENT_ALERT: + readPos += sizeof(gsSSLRecordHeaderMsg); + //if(readPos > *theEncryptedLength) + // _asm int 3; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_WarmError, + "SSL received unhandled ALERT\r\n"); + // server alert + break; + + default: + readPos += sizeof(gsSSLRecordHeaderMsg); + return GHIEncryptionResult_Error; // unhandled content type + }; + + readPos += length; + }; + + // remove read bytes from the stream + *theEncryptedLength = readPos; // bytes read *NOT* bytes remaining + *theDecryptedLength = writePos; // bytes written + + if (*theEncryptedLength < 0) + return GHIEncryptionResult_Error; + else + return GHIEncryptionResult_Success; +} + +static GHTTPBool ghiCertificateChainIsValid(gsSSL* sslInterface) +{ + GSI_UNUSED(sslInterface); + + return GHTTPTrue; +} + +#define CHECK(a) { if (GHTTPFalse == a) return GHIEncryptionResult_Error; } + + +// Programmer note: +// The structure of these SSL handshake messages may seem a bit cryptic, +// due to their variable length data items. Refer to the ASN1/DER encoding guide +// for tag specifics. +GHIEncryptionResult ghiEncryptorProcessSSLHandshake(struct GHIConnection * connection, + struct GHIEncryptor * encryptor, + GHIBuffer * data) +{ + // There may be multiple messages within the handshake message + // length must be completely used, otherwise it's a protocol error + gsSSL* sslInterface = (gsSSL*)encryptor->mInterface; + + while(data->pos < data->len) + { + // Parse each SSL handshake message (there may be multiple) + int messageStart = data->pos; + char messageType = 0; + CHECK(ghiReadDataFromBufferFixed(data, &messageType, 1)); + + if (messageType == GS_SSL_HANDSHAKE_SERVERHELLO) + { + int totalMsgLen = 0; // length of header + data + int msgDataLen = 0; // length of data + int tempInt = 0; + char tempChar = '\0'; + + // make sure we don't have a session already (e.g. dupe hello message) + if (sslInterface->sessionLen != 0) + return GHIEncryptionResult_Error; // abort connection + + CHECK(ghiEncryptorReadNBOLength(data, &msgDataLen, 3)); + + // check reported size against the actual bytes remaining + if (msgDataLen > (data->len - data->pos)) + return GHIEncryptionResult_Error; // abort connection + + // skip SSL version + // (length check not required because we did that above) + data->pos += 2; + + // store server random (used for key generation) + CHECK(ghiReadDataFromBufferFixed(data, (char*)&sslInterface->serverRandom[0], 32)); + + // store session information (length followed by data) + CHECK(ghiReadDataFromBufferFixed(data, &tempChar, 1)); + CHECK(ghiReadDataFromBufferFixed(data, (char*)sslInterface->sessionData, tempChar)); + sslInterface->sessionLen = (int)tempChar; + + // store cipher suite + CHECK(ghiEncryptorReadNBOLength(data, &tempInt, 2)); + sslInterface->cipherSuite = (unsigned short)tempInt; + + // skip compression algorithms (should always be 0x00 since we don't support any!) + CHECK(ghiReadDataFromBufferFixed(data, &tempChar, 1)); + if (tempChar != 0x00) + return GHIEncryptionResult_Error; + + // add it to the running handshake hash + totalMsgLen = data->pos - messageStart; + MD5Update(&sslInterface->finishHashMD5, (unsigned char*)&data->data[messageStart], (unsigned int)totalMsgLen); + SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)&data->data[messageStart], (unsigned int)totalMsgLen); + } + else if (messageType == GS_SSL_HANDSHAKE_CERTIFICATE) + { + int msgLength = 0; // combined length of the message (size in SSL message header) + int certListLen = 0; // combined length of all certificates + int totalMsgLen = 0; // our calculated msg length (for handshake hashing) + + int certCount = 0; + int certListEndPos = 0; + + CHECK(ghiEncryptorReadNBOLength(data, &msgLength, 3)); + CHECK(ghiEncryptorReadNBOLength(data, &certListLen, 3)); + if (msgLength != certListLen + 3) + return GHIEncryptionResult_Error; + + // make sure we don't have the certificates already (e.g. dupe message) + //if (sslInterface->certificateArray != NULL) + // return GHIEncryptionResult_Error; // abort connection + + // make sure we have enough data to cover the certificate list + certListEndPos = data->pos + certListLen; + if (certListLen > (data->len - data->pos)) + return GHIEncryptionResult_Error; + + // read the certificates + while(data->pos < certListEndPos) + { + int certLength = 0; + int certStartPos = 0; + + int temp = 0; + int version = 0; + + // Must start with a 3 byte length + CHECK(ghiEncryptorReadNBOLength(data, &certLength, 3)); + + // Make sure we have enough data to cover this certificate + if (certLength > (data->len - data->pos)) + return GHIEncryptionResult_Error; // certificate too big + + // 0xFFFF is max message size in SSL v3.0, we don't currently support + // split messages + if (certLength > 0xFFFF) + return GHIEncryptionResult_Error; + + certStartPos = data->pos; // remember this for a shortcut later + certCount++; + + // make a copy of the certificate data + //certCopy = gsimalloc(certLength); + //if (certCopy == NULL) + //{ + // gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Memory, GSIDebugLevel_WarmError, + // "SSL failed to allocate certificate #%d (out of memory)\r\n", certCount); + // return GHIEncryptionResult_Error; + //} + //memcpy(certCopy, &data[browsePos], certLength); + //ArrayAppend(sslInterface->certificateArray, &certCopy); + + // The first certificate holds the server's public key + if (certCount == 1) + { + // X.509 format is rather convoluted. Since we only support + // one variation anyways, I'm hardcoding the specific values + // we require. Anything else is a protocol error. + // 0x30 marks the start of a sequence. next byte is a length field size + // 0x82 is a length tag, meaning the next two bytes contain the length + // 0x81 is the same thing, only the next one byte contains the length + // The other values usually denote required types + + // Certificate SEQUENCE + int seqLen = 0; + CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen)); + // todo: verify reported length of this sequence + + // TBSCertificate SEQUENCE + CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen)); + // todo: verify reported length of this sequence + + // EXPLICIT Version (must be one of: 0x03,0x02,0x01) + if (5 > (data->len - data->pos)) return GHIEncryptionResult_Error; + if ((unsigned char)data->data[data->pos++] != 0xa0) return GHIEncryptionResult_Error; + if ((unsigned char)data->data[data->pos++] != 0x03) return GHIEncryptionResult_Error; + if ((unsigned char)data->data[data->pos++] != 0x02) return GHIEncryptionResult_Error; + if ((unsigned char)data->data[data->pos++] != 0x01) return GHIEncryptionResult_Error; + version = (unsigned char)data->data[data->pos++]; + + // Serial Number (variable length, with 2-byte length field) + if ((unsigned char)data->data[data->pos++] != 0x02) return GHIEncryptionResult_Error; + temp = (unsigned char)data->data[data->pos++]; // len of serial number + if (data->pos + temp > certListEndPos) return GHIEncryptionResult_Error; + data->pos += temp; // skip the serial number + + // Signature algorithm identifier SEQUENCE + CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen)); + data->pos += seqLen; // skip algorithm ID (todo: verify signatures) + + // Issuer Name + CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen)); + data->pos += seqLen; // skip the issuer name sequence + + // Validity + CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen)); + data->pos += seqLen; // skip the validity sequence + + // Subject Name + CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen)); + data->pos += seqLen; // skip the subject name sequence + + // Subject Public Key Info + CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen)); + // AlgorithmIdentifier + CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen)); + if (seqLen != 0x0d) return GHIEncryptionResult_Error; + if ((unsigned char)data->data[data->pos++] != 0x06) return GHIEncryptionResult_Error; + if ((unsigned char)data->data[data->pos++] != 0x09) return GHIEncryptionResult_Error; + if (0 != memcmp(&data->data[data->pos], gsSslRsaOid, sizeof(gsSslRsaOid))) + return GHIEncryptionResult_Error; // only RSA certs are supported + data->pos += sizeof(gsSslRsaOid); + if ((unsigned char)data->data[data->pos++] != 0x05) return GHIEncryptionResult_Error; + if ((unsigned char)data->data[data->pos++] != 0x00) return GHIEncryptionResult_Error; + + // Bitstring (subject public key) + if (2 > (certListEndPos - data->pos)) return GHIEncryptionResult_Error; + if ((unsigned char)data->data[data->pos++] != 0x03) return GHIEncryptionResult_Error; // bitstring + if ((unsigned char)data->data[data->pos++] != 0x81) return GHIEncryptionResult_Error; // 1 byte len field + if (temp > (certListEndPos - data->pos)) return GHIEncryptionResult_Error; + temp = (unsigned char)data->data[data->pos++]; // remaining data size (check or ignore) + + if ((unsigned char)data->data[data->pos++] != 0x00) return GHIEncryptionResult_Error; + + // Start of the public key modulus + CHECK(ghiEncryptorParseASN1Sequence(data, &seqLen)); + + // Read out the public key modulus + if (data->data[data->pos++] != 0x02) return GHIEncryptionResult_Error; // integer tag + if ((data->data[data->pos]&0x80)==0x80) // ASN1 variable length field + { + int lensize = data->data[data->pos++]&0x7f; + if (lensize > 4) + return GHIEncryptionResult_Error; + temp = 0; + while(lensize-- > 0) + temp = (temp << 8) | (unsigned char)data->data[data->pos++]; + } + else + { + temp = (unsigned char)data->data[data->pos++]; + } + if (data->pos + temp > certListEndPos) return GHIEncryptionResult_Error; + if (data->data[data->pos++] != 0x00) return GHIEncryptionResult_Error; // ignore bits must be 0x00 + if (temp-1 > GS_LARGEINT_BINARY_SIZE/sizeof(char)) return GHIEncryptionResult_Error; + sslInterface->serverpub.modulus.mLength = (unsigned int)((temp-1)/GS_LARGEINT_DIGIT_SIZE_BYTES); //-1 to subtract the previous 0x00 byte + gsLargeIntSetFromMemoryStream(&sslInterface->serverpub.modulus, (const gsi_u8*)&data->data[data->pos], (gsi_u32)temp-1); + data->pos += temp-1; + + // Read out the public key exponent + if (data->data[data->pos++] != 0x02) return GHIEncryptionResult_Error; // integer + if ((data->data[data->pos]&0x80)==0x80) + { + int lensize = data->data[data->pos++]&0x7f; + if (lensize > 4) + return GHIEncryptionResult_Error; + temp = 0; + while(lensize-- > 0) + temp = (temp << 8) | (unsigned char)data->data[data->pos++]; + } + else + { + temp = (unsigned char)data->data[data->pos++]; + } + if (data->pos + temp > certListEndPos) return GHIEncryptionResult_Error; + if (temp == 0) return GHIEncryptionResult_Error; // no exponent? + if (temp > GS_LARGEINT_BINARY_SIZE/sizeof(char)) return GHIEncryptionResult_Error; + sslInterface->serverpub.exponent.mLength = (unsigned int)(((temp-1)/GS_LARGEINT_DIGIT_SIZE_BYTES)+1); // ceiling of temp/4 + gsLargeIntSetFromMemoryStream(&sslInterface->serverpub.exponent, (const gsi_u8*)&data->data[data->pos], (gsi_u32)temp); + data->pos += temp; + } + + // update the position + data->pos = certStartPos + certLength; + + GSI_UNUSED(version); + } + if (data->pos != certListEndPos) + return GHIEncryptionResult_Error; // bytes hanging off the end! + + // todo: verify certificate chain + // first certificate is the server's, the rest likely belong to CA + if (GHTTPFalse == ghiCertificateChainIsValid(sslInterface)) + return GHIEncryptionResult_Error; + + // add it to the running handshake hash + totalMsgLen = data->pos - messageStart; + MD5Update(&sslInterface->finishHashMD5, (unsigned char*)&data->data[messageStart], (unsigned int)totalMsgLen); + SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)&data->data[messageStart], (unsigned int)totalMsgLen); + } + else if (messageType == GS_SSL_HANDSHAKE_SERVERHELLODONE) + { + // Process the hello done + // Respond with 3 messages + // ClientKeyExchange + // ChangeCipherSpec + // Finished (final handshake) + int i=0; + + gsSSLClientKeyExchangeMsg* clientKeyExchange = NULL; + gsSSLRecordHeaderMsg* changeCipherSpec = NULL; + gsSSLRecordHeaderMsg* finalHandshake = NULL; + + unsigned char temp[7]; + unsigned char hashTempMD5[GS_CRYPT_MD5_HASHSIZE]; + unsigned char hashTempSHA1[GS_CRYPT_SHA1_HASHSIZE]; + int tempInt = 0; + + // ServerHelloDone has a zero length data field + CHECK(ghiEncryptorReadNBOLength(data, &tempInt, 3)); + if (tempInt != 0x00) return GHIEncryptionResult_Error; + + // add it to the running handshake hash + MD5Update(&sslInterface->finishHashMD5, (unsigned char*)&data->data[messageStart], (unsigned int)(data->pos - messageStart)); + SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)&data->data[messageStart], (unsigned int)(data->pos - messageStart)); + + // Make sure there is room in the send buffer for the response messages + tempInt = (int)(sizeof(gsSSLClientKeyExchangeMsg) + sslInterface->serverpub.modulus.mLength*GS_LARGEINT_DIGIT_SIZE_BYTES); + while (connection->sendBuffer.size - connection->sendBuffer.len < tempInt) + { + // not enough room in send buffer, try to grow it + if (GHTTPFalse == ghiResizeBuffer(&connection->sendBuffer, connection->sendBuffer.sizeIncrement)) + return GHIEncryptionResult_Error; + } + + // 1) Client key exchange, + // create the pre-master-secret + sslInterface->premastersecret[0] = GS_SSL_VERSION_MAJOR; + sslInterface->premastersecret[1] = GS_SSL_VERSION_MINOR; + for (i=2; ipremastersecret[i] = 0; // rand() + #else + Util_RandSeed(current_time()); + sslInterface->premastersecret[i] = (unsigned char)(Util_RandInt(0, 0x0100)); // range = [0...FF] + #endif + } + + clientKeyExchange = (gsSSLClientKeyExchangeMsg*)&connection->sendBuffer.data[connection->sendBuffer.len]; + connection->sendBuffer.len += sizeof(gsSSLClientKeyExchangeMsg); + clientKeyExchange->header.contentType = GS_SSL_CONTENT_HANDSHAKE; + clientKeyExchange->header.versionMajor = GS_SSL_VERSION_MAJOR; + clientKeyExchange->header.versionMinor = GS_SSL_VERSION_MINOR; + ghiEncryptorWriteNBOLength(clientKeyExchange->header.lengthNBO, (int)(sslInterface->serverpub.modulus.mLength*GS_LARGEINT_DIGIT_SIZE_BYTES+4), 2); + clientKeyExchange->handshakeType = GS_SSL_HANDSHAKE_CLIENTKEYEXCHANGE; + ghiEncryptorWriteNBOLength(clientKeyExchange->lengthNBO, (int)(sslInterface->serverpub.modulus.mLength*GS_LARGEINT_DIGIT_SIZE_BYTES), 3); + // encrypt the preMasterSecret using the server's public key (store result in sendbuffer) + gsCryptRSAEncryptBuffer(&sslInterface->serverpub, sslInterface->premastersecret, + GS_SSL_MASTERSECRET_LEN, (unsigned char*)&connection->sendBuffer.data[connection->sendBuffer.len]); + connection->sendBuffer.len += sslInterface->serverpub.modulus.mLength*GS_LARGEINT_DIGIT_SIZE_BYTES; + + // add it to the running handshake hash + MD5Update(&sslInterface->finishHashMD5, (unsigned char*)clientKeyExchange+sizeof(gsSSLRecordHeaderMsg), + sizeof(gsSSLClientKeyExchangeMsg) - sizeof(gsSSLRecordHeaderMsg) + + sslInterface->serverpub.modulus.mLength*GS_LARGEINT_DIGIT_SIZE_BYTES); + SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)clientKeyExchange+sizeof(gsSSLRecordHeaderMsg), + sizeof(gsSSLClientKeyExchangeMsg) - sizeof(gsSSLRecordHeaderMsg) + + sslInterface->serverpub.modulus.mLength*GS_LARGEINT_DIGIT_SIZE_BYTES); + + + // 2) change cipher spec + changeCipherSpec = (gsSSLRecordHeaderMsg*)&connection->sendBuffer.data[connection->sendBuffer.len]; + changeCipherSpec->contentType = GS_SSL_CONTENT_CHANGECIPHERSPEC; + changeCipherSpec->versionMajor = GS_SSL_VERSION_MAJOR; + changeCipherSpec->versionMinor = GS_SSL_VERSION_MINOR; + changeCipherSpec->lengthNBO[0] = 0; + changeCipherSpec->lengthNBO[1] = 1; // always one byte length + connection->sendBuffer.len += sizeof(gsSSLRecordHeaderMsg); + connection->sendBuffer.data[connection->sendBuffer.len++] = 0x01; // always set to 0x01 + // DO NOT add it to the running handshake hash (its content is not GS_SSL_CONTENT_HANDSHAKE) + + // Calculate the encryption keys + ghiEncryptorGenerateEncryptionKeys(sslInterface); + + // 3) final handshake message (encrypted) + finalHandshake = (gsSSLRecordHeaderMsg*)&connection->sendBuffer.data[connection->sendBuffer.len]; + finalHandshake->contentType = GS_SSL_CONTENT_HANDSHAKE; + finalHandshake->versionMajor = GS_SSL_VERSION_MAJOR; + finalHandshake->versionMinor = GS_SSL_VERSION_MINOR; + finalHandshake->lengthNBO[0] = 0; + finalHandshake->lengthNBO[1] = 56; // handshake type(1)+handshake lenNBO(3)+SHA1(20)+MD5(16)+MAC(16) + connection->sendBuffer.len += sizeof(gsSSLRecordHeaderMsg); + connection->sendBuffer.data[connection->sendBuffer.len++] = GS_SSL_HANDSHAKE_FINISHED; + ghiEncryptorWriteNBOLength((unsigned char*)&connection->sendBuffer.data[connection->sendBuffer.len], 36, 3); + connection->sendBuffer.len += 3; + + + // MD5(master_secret + pad2 + MD5(handshake_messages+"CLNT"+master_secret+pad1)) + // SHA1(master_secret + pad2 + SHA1(handshake_messages+"CLNT"+master_secret+pad1)) + // prepare the final hashes (inner hashes) + MD5Update(&sslInterface->finishHashMD5, (unsigned char*)GS_SSL_CLIENT_FINISH_VALUE, 4); + MD5Update(&sslInterface->finishHashMD5, (unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN); + MD5Update(&sslInterface->finishHashMD5, (unsigned char*)GS_SSL_PAD_ONE, GS_SSL_MD5_PAD_LEN); + MD5Final(hashTempMD5, &sslInterface->finishHashMD5); + + SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)GS_SSL_CLIENT_FINISH_VALUE, 4); + SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN); + SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)GS_SSL_PAD_ONE, GS_SSL_SHA1_PAD_LEN); + SHA1Result(&sslInterface->finishHashSHA1, hashTempSHA1); + + // prepare the final hashes (outer hashes) + MD5Init(&sslInterface->finishHashMD5); + MD5Update(&sslInterface->finishHashMD5, (unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN); + MD5Update(&sslInterface->finishHashMD5, (unsigned char*)GS_SSL_PAD_TWO, GS_SSL_MD5_PAD_LEN); + MD5Update(&sslInterface->finishHashMD5, hashTempMD5, GS_CRYPT_MD5_HASHSIZE); + MD5Final(hashTempMD5, &sslInterface->finishHashMD5); + + SHA1Reset(&sslInterface->finishHashSHA1); + SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)sslInterface->mastersecret, GS_SSL_MASTERSECRET_LEN); + SHA1Input(&sslInterface->finishHashSHA1, (unsigned char*)GS_SSL_PAD_TWO, GS_SSL_SHA1_PAD_LEN); + SHA1Input(&sslInterface->finishHashSHA1, hashTempSHA1, GS_CRYPT_SHA1_HASHSIZE); + SHA1Result(&sslInterface->finishHashSHA1, hashTempSHA1); + + // copy results into the sendbuffer + memcpy(&connection->sendBuffer.data[connection->sendBuffer.len], hashTempMD5, GS_CRYPT_MD5_HASHSIZE); + connection->sendBuffer.len += GS_CRYPT_MD5_HASHSIZE; + memcpy(&connection->sendBuffer.data[connection->sendBuffer.len], hashTempSHA1, GS_CRYPT_SHA1_HASHSIZE); + connection->sendBuffer.len += GS_CRYPT_SHA1_HASHSIZE; + + // output the message MAC (hash(MAC_write_secret+pad_2+ hash(MAC_write_secret+pad_1+seq_num+length+content))); + // Re-using the finishHashMD5 since it has already been allocated + MD5Init(&sslInterface->finishHashMD5); + MD5Update(&sslInterface->finishHashMD5, (unsigned char*)sslInterface->clientWriteMACSecret, GS_CRYPT_MD5_HASHSIZE); + MD5Update(&sslInterface->finishHashMD5, (unsigned char*)GS_SSL_PAD_ONE, GS_SSL_MD5_PAD_LEN); + MD5Update(&sslInterface->finishHashMD5, (unsigned char*)sslInterface->sendSeqNBO, 8); + temp[0] = 0x16; + temp[1] = (unsigned char)((GS_CRYPT_MD5_HASHSIZE+GS_CRYPT_SHA1_HASHSIZE+4)>>8); + temp[2] = (unsigned char)((GS_CRYPT_MD5_HASHSIZE+GS_CRYPT_SHA1_HASHSIZE+4)); + //temp[1] = (unsigned char)(htons(GS_CRYPT_MD5_HASHSIZE+GS_CRYPT_SHA1_HASHSIZE)); + //temp[2] = (unsigned char)(htons(GS_CRYPT_MD5_HASHSIZE+GS_CRYPT_SHA1_HASHSIZE+4)>>8); + temp[3] = 0x14; // 20-bytes of data (MD5+SHA1) + temp[4] = 0x00; // 3-byte length NBO + temp[5] = 0x00; // .. + temp[6] = 0x24; // .. + MD5Update(&sslInterface->finishHashMD5, (unsigned char*)&temp, 7); + MD5Update(&sslInterface->finishHashMD5, hashTempMD5, GS_CRYPT_MD5_HASHSIZE); // content part 1 + MD5Update(&sslInterface->finishHashMD5, hashTempSHA1, GS_CRYPT_SHA1_HASHSIZE); // content part 2 + MD5Final(hashTempMD5, &sslInterface->finishHashMD5); + MD5Init(&sslInterface->finishHashMD5); // reset for outer hash + MD5Update(&sslInterface->finishHashMD5, (unsigned char*)sslInterface->clientWriteMACSecret, GS_CRYPT_MD5_HASHSIZE); + MD5Update(&sslInterface->finishHashMD5, (unsigned char*)GS_SSL_PAD_TWO, GS_SSL_MD5_PAD_LEN); + MD5Update(&sslInterface->finishHashMD5, hashTempMD5, GS_CRYPT_MD5_HASHSIZE); + MD5Final(hashTempMD5, &sslInterface->finishHashMD5); + + memcpy(&connection->sendBuffer.data[connection->sendBuffer.len], hashTempMD5, GS_CRYPT_MD5_HASHSIZE); + connection->sendBuffer.len += GS_CRYPT_MD5_HASHSIZE; + + // increment sequence each time we send a message + // ...assume NBO is bigendian for simplicity + memset(sslInterface->sendSeqNBO, 0, sizeof(sslInterface->sendSeqNBO)); + ghiEncryptorWriteNBOLength(&sslInterface->sendSeqNBO[4], 1, 4); + + // now encrypt the message (not including record header) + RC4Encrypt(&sslInterface->sendRC4, + ((unsigned char*)finalHandshake)+sizeof(gsSSLRecordHeaderMsg), + ((unsigned char*)finalHandshake)+sizeof(gsSSLRecordHeaderMsg), + 56); + } + else if (messageType == GS_SSL_HANDSHAKE_FINISHED) + { + // process server finished and verify hashes + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_Notice, + "SSL: todo - verify server finished hash\r\n"); + data->pos = data->len; + } + else + { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_WarmError, + "SSL received unexpected handshake message type: %d\r\n", messageType); + return GHIEncryptionResult_Error; // abort connection + } + } + + GSI_UNUSED(connection); + + if (data->pos == data->len) + return GHIEncryptionResult_Success; + else + return GHIEncryptionResult_Error; // too many or too few bytes, protocol error! +} + +GHIEncryptionResult ghiEncryptorSslEncryptSend(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor, + const char * thePlainTextBuffer, + int thePlainTextLength, + int * theBytesSentOut) +{ + GS_FAIL(); // Should never call this for GameSpy SSL! It uses encrypt on buffer + + GSI_UNUSED(connection); + GSI_UNUSED(theEncryptor); + GSI_UNUSED(thePlainTextBuffer); + GSI_UNUSED(thePlainTextLength); + GSI_UNUSED(theBytesSentOut); + + return GHIEncryptionResult_Error; +} + +GHIEncryptionResult ghiEncryptorSslDecryptRecv(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor, + char * theDecryptedBuffer, + int * theDecryptedLength) +{ + GS_FAIL(); // Should never call this for GameSpy SSL! It uses decrypt on buffer + + GSI_UNUSED(connection); + GSI_UNUSED(theEncryptor); + GSI_UNUSED(theDecryptedBuffer); + GSI_UNUSED(theDecryptedLength); + + return GHIEncryptionResult_Error; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // encryption engine switch + + diff --git a/code/gamespy/ghttp/ghttpEncryption.h b/code/gamespy/ghttp/ghttpEncryption.h new file mode 100644 index 00000000..f0c0ea00 --- /dev/null +++ b/code/gamespy/ghttp/ghttpEncryption.h @@ -0,0 +1,137 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __GHTTPENCRYPTION_H__ +#define __GHTTPENCRYPTION_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//#include "ghttpCommon.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Encryption method +typedef enum +{ + GHIEncryptionMethod_None, + GHIEncryptionMethod_Encrypt, // encrypt raw data written to buffer + GHIEncryptionMethod_Decrypt // decrypt raw data written to buffer +} GHIEncryptionMethod; + +// Encryption results +typedef enum +{ + GHIEncryptionResult_None, + GHIEncryptionResult_Success, // successfully encrypted/decrypted + GHIEncryptionResult_BufferTooSmall, // buffer was too small to hold converted data + GHIEncryptionResult_Error // some other kind of error +} GHIEncryptionResult; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +struct GHIEncryptor; // forward declare for callbacks +struct GHIConnection; + +// Called to init the encryption engine +typedef GHIEncryptionResult (*GHTTPEncryptorInitFunc) (struct GHIConnection * theConnection, + struct GHIEncryptor * theEncryptor); + +// Called to connect the socket (some engines do this internally) +typedef GHIEncryptionResult (*GHTTPEncryptorConnectFunc)(struct GHIConnection * theConnection, + struct GHIEncryptor * theEncryptor); + +// Called to start the handshake process engine +typedef GHIEncryptionResult (*GHTTPEncryptorStartFunc)(struct GHIConnection * theConnection, + struct GHIEncryptor * theEncryptor); + +// Called to destroy the encryption engine +typedef GHIEncryptionResult (*GHTTPEncryptorCleanupFunc)(struct GHIConnection * theConnection, + struct GHIEncryptor * theEncryptor); + +// Called when data needs to be encrypted +// - entire plain text buffer will be encrypted +typedef GHIEncryptionResult (*GHTTPEncryptorEncryptFunc)(struct GHIConnection * theConnection, + struct GHIEncryptor * theEncryptor, + const char * thePlainTextBuffer, + int thePlainTextLength, // [in] + char * theEncryptedBuffer, + int * theEncryptedLength); // [in/out] + +// Called when data needs to be decrypted +// - encrypted data may be left in the buffer +// - decrypted buffer is appended to, not overwritten +typedef GHIEncryptionResult (*GHTTPEncryptorDecryptFunc)(struct GHIConnection * theConnection, + struct GHIEncryptor* theEncryptor, + const char * theEncryptedBuffer, + int * theEncryptedLength, // [in/out] + char * theDecryptedBuffer, + int * theDecryptedLength);// [in/out] + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct GHIEncryptor +{ + void* mInterface; // only SSL is currently supported + GHTTPEncryptionEngine mEngine; + GHTTPBool mInitialized; + GHTTPBool mSessionStarted; // handshake started? + GHTTPBool mSessionEstablished; // handshake completed? + + // (As coded, these two are exclusive!) + // pattern 1 = manually encrypt the buffer, then send using normal socket functions + // pattern 2 = send plain text through the encryption engine, it will send + GHTTPBool mEncryptOnBuffer; // engine encrypts when writing to a buffer? (pattern 1) + GHTTPBool mEncryptOnSend; // engine encrypts when sending over socket? (pattern 2) + + // If GHTTPTrue, the SSL library handles sending/receiving handshake messages + GHTTPBool mLibSendsHandshakeMessages; + + // Functions for engine use + GHTTPEncryptorInitFunc mInitFunc; + GHTTPEncryptorCleanupFunc mCleanupFunc; + GHTTPEncryptorStartFunc mStartFunc; // start the handshake process + GHTTPEncryptorEncryptFunc mEncryptFunc; + GHTTPEncryptorDecryptFunc mDecryptFunc; +} GHIEncryptor; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// ssl encryption +GHIEncryptionResult ghiEncryptorSslInitFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor); +GHIEncryptionResult ghiEncryptorSslCleanupFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor); + +GHIEncryptionResult ghiEncryptorSslStartFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor); + +GHIEncryptionResult ghiEncryptorSslEncryptFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor, + const char * thePlainTextBuffer, + int thePlainTextLength, + char * theEncryptedBuffer, + int * theEncryptedLength); +GHIEncryptionResult ghiEncryptorSslDecryptFunc(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor, + const char * theEncryptedBuffer, + int * theEncryptedLength, + char * theDecryptedBuffer, + int * theDecryptedLength); +GHIEncryptionResult ghiEncryptorSslEncryptSend(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor, + const char * thePlainTextBuffer, + int thePlainTextLength, + int * theBytesSent); +GHIEncryptionResult ghiEncryptorSslDecryptRecv(struct GHIConnection * connection, + struct GHIEncryptor * theEncryptor, + char * theDecryptedBuffer, + int * theDecryptedLength); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __GHTTPENCRYPTION_H__ diff --git a/code/gamespy/ghttp/ghttpMain.c b/code/gamespy/ghttp/ghttpMain.c new file mode 100644 index 00000000..ddbc3bda --- /dev/null +++ b/code/gamespy/ghttp/ghttpMain.c @@ -0,0 +1,1485 @@ + /* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2001 GameSpy Industries, Inc + +18002 Skypark Circle +Irvine, California 92614 +949.798.4200 (Tel) +949.798.4299 (Fax) +devsupport@gamespy.com +*/ + +#include "ghttpMain.h" +#include "ghttpASCII.h" +#include "ghttpConnection.h" +#include "ghttpCallbacks.h" +#include "ghttpProcess.h" +#include "ghttpPost.h" +#include "ghttpCommon.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Ascii versions which must be available even in the unicode build +GHTTPRequest ghttpGetExA(const char * URL, const char * headers, char * buffer, int bufferSize, GHTTPPost post, GHTTPBool throttle, GHTTPBool blocking, ghttpProgressCallback progressCallback, ghttpCompletedCallback completedCallback, void * param); +GHTTPRequest ghttpSaveExA(const char * URL, const char * filename, const char * headers, GHTTPPost post, GHTTPBool throttle, GHTTPBool blocking, ghttpProgressCallback progressCallback, ghttpCompletedCallback completedCallback, void * param); +GHTTPRequest ghttpStreamExA(const char * URL, const char * headers, GHTTPPost post, GHTTPBool throttle, GHTTPBool blocking, ghttpProgressCallback progressCallback, ghttpCompletedCallback completedCallback, void * param); +GHTTPRequest ghttpHeadExA(const char * URL, const char * headers, GHTTPBool throttle, GHTTPBool blocking, ghttpProgressCallback progressCallback, ghttpCompletedCallback completedCallback, void * param); +GHTTPRequest ghttpPostExA(const char * URL, const char * headers, GHTTPPost post, GHTTPBool throttle, GHTTPBool blocking, ghttpProgressCallback progressCallback, ghttpCompletedCallback completedCallback, void * param); + + + +// Reference count. +/////////////////// +static int ghiReferenceCount; + +// Called right before callback is called. +// Sets result based on response status code. +///////////////////////////////////////////// +static void ghiHandleStatus +( + GHIConnection * connection +) +{ + // Check the status code. + ///////////////////////// + switch(connection->statusCode / 100) + { + case 1: // Informational. + return; + case 2: // Successful. + return; + case 3: // Redirection. + return; + case 4: // Client Error. + switch(connection->statusCode) + { + case 401: + connection->result = GHTTPUnauthorized; + break; + case 403: + connection->result = GHTTPForbidden; + break; + case 404: + case 410: + connection->result = GHTTPFileNotFound; + break; + default: + connection->result = GHTTPRequestRejected; + break; + } + return; + case 5: // Internal Server Error. + connection->result = GHTTPServerError; + return; + } +} + +// Processes a single connection based on its state. +// Returns true if the connection is finished. +//////////////////////////////////////////////////// +static GHTTPBool ghiProcessConnection +( + GHIConnection * connection +) +{ + GHTTPBool completed; + + assert(connection); + assert(ghiRequestToConnection(connection->request) == connection); + + // Don't process if already processing this connection. + // Happens if, for example, ghttpThink is called from a callback. + ///////////////////////////////////////////////////////////////// + if(connection->processing) + return GHTTPFalse; + + // We're now processing. + //////////////////////// + connection->processing = GHTTPTrue; + + // Process based on state. + // else-if is not used so that if one ghiDo*() + // finishes the one after it can start. + ////////////////////////////////////////////// + + if(connection->state == GHTTPSocketInit) + ghiDoSocketInit(connection); + if(connection->state == GHTTPHostLookup) + ghiDoHostLookup(connection); + if(connection->state == GHTTPLookupPending) + ghiDoLookupPending(connection); + if(connection->state == GHTTPConnecting) + ghiDoConnecting(connection); + if(connection->state == GHTTPSecuringSession) + ghiDoSecuringSession(connection); + if(connection->state == GHTTPSendingRequest) + ghiDoSendingRequest(connection); + if(connection->state == GHTTPPosting) + ghiDoPosting(connection); + if(connection->state == GHTTPWaiting) + ghiDoWaiting(connection); + if(connection->state == GHTTPReceivingStatus) + ghiDoReceivingStatus(connection); + if(connection->state == GHTTPReceivingHeaders) + ghiDoReceivingHeaders(connection); + if(connection->state == GHTTPReceivingFile) + ghiDoReceivingFile(connection); + + // Check for a redirect. + //////////////////////// + if(connection->redirectURL) + ghiRedirectConnection(connection); + + // Grab completed before we possibly free it. + ///////////////////////////////////////////// + completed = connection->completed; + + // Graceful shutdown support. + // Close connection when there is no more data + if (connection->result == GHTTPRequestCancelled && !connection->completed && !CanReceiveOnSocket(connection->socket)) + { + connection->completed = GHTTPTrue; + } + + // Is it finished? + ////////////////// + if(connection->completed) + { + // Set result based on status code. + /////////////////////////////////// + ghiHandleStatus(connection); + + // If we're saving to file, close it before the callback. + ///////////////////////////////////////////////////////// +#ifndef NOFILE + if(connection->saveFile) + { + fclose(connection->saveFile); + connection->saveFile = NULL; + } +#endif + // Log buffer data + ghiLogResponse(connection->getFileBuffer.data, connection->getFileBuffer.len); + + // Call the callback. + ///////////////////// + ghiCallCompletedCallback(connection); + + // Free it. + /////////// + ghiFreeConnection(connection); + } + else + { + // Done processing. This is in the else, + // because we don't want to set it if the + // connection has already been freed. + ///////////////////////////////////////// + connection->processing = GHTTPFalse; + } + + return completed; +} + +void ghttpStartup +( + void +) +{ + // This will just return if we haven't created the lock yet. + //////////////////////////////////////////////////////////// + ghiLock(); + + // One more startup. + //////////////////// + ghiReferenceCount++; + + // Check if we are the first. + ///////////////////////////// + if(ghiReferenceCount == 1) + { + // Create the lock. + /////////////////// + ghiCreateLock(); + + // Set some defaults. + ///////////////////// + ghiThrottleBufferSize = GHI_DEFAULT_THROTTLE_BUFFER_SIZE; + ghiThrottleTimeDelay = GHI_DEFAULT_THROTTLE_TIME_DELAY; + } + else + { + // Unlock the lock. + /////////////////// + ghiUnlock(); + } +} + +void ghttpCleanup +( + void +) +{ + // Lockdown for cleanup. + //////////////////////// + ghiLock(); + + // One less. + //////////// + ghiReferenceCount--; + + // Should we cleanup? + ///////////////////// + if(!ghiReferenceCount) + { + // Cleanup the connections. + /////////////////////////// + ghiCleanupConnections(); + + // Cleanup proxy. + ///////////////// + if(ghiProxyAddress) + { + gsifree(ghiProxyAddress); + ghiProxyAddress = NULL; + } + + // Unlock the lock before freeing it. + ///////////////////////////////////// + ghiUnlock(); + + // Free the lock. + ///////////////// + ghiFreeLock(); + } + else + { + // Unlock our lock. + /////////////////// + ghiUnlock(); + } +} + +GHTTPRequest ghttpGetA +( + const char * URL, + GHTTPBool blocking, + ghttpCompletedCallback completedCallback, + void * param +) +{ + return ghttpGetExA(URL, NULL, NULL, 0, NULL, GHTTPFalse, blocking, NULL, completedCallback, param); +} +#ifdef GSI_UNICODE +GHTTPRequest ghttpGetW +( + const unsigned short * URL, + GHTTPBool blocking, + ghttpCompletedCallback completedCallback, + void * param +) +{ + char URL_A[1024]; + + assert(URL != NULL); + UCS2ToAsciiString(URL, (char*)URL_A); + return ghttpGetA(URL_A, blocking, completedCallback, param); +} +#endif + +GHTTPRequest ghttpGetExA +( + const char * URL, + const char * headers, + char * buffer, + int bufferSize, + GHTTPPost post, + GHTTPBool throttle, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void * param +) +{ + GHTTPBool bResult; + GHIConnection * connection; + + assert(URL && URL[0]); + assert(bufferSize >= 0); + assert(!buffer || bufferSize); + + // Check args. + ////////////// + if(!URL || !URL[0]) + return GHTTPInvalidURL; + if(bufferSize < 0) + return GHTTPInvalidBufferSize; + if(buffer && !bufferSize) + return GHTTPInvalidBufferSize; + + // Startup if it hasn't been done. + ////////////////////////////////// + if(!ghiReferenceCount) + ghttpStartup(); + + // Get a new connection object. + /////////////////////////////// + connection = ghiNewConnection(); + if(!connection) + return GHTTPInsufficientMemory; + + // Fill in the necessary info. + ////////////////////////////// + connection->type = GHIGET; + connection->URL = goastrdup(URL); + if(!connection->URL) + { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + if(headers && *headers) + { + connection->sendHeaders = goastrdup(headers); + if(!connection->sendHeaders) + { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + } + connection->post = post; + connection->blocking = blocking; + connection->progressCallback = progressCallback; + connection->completedCallback = completedCallback; + connection->callbackParam = param; + connection->throttle = throttle; + connection->userBufferSupplied = (buffer != NULL)?GHTTPTrue:GHTTPFalse; + if(connection->userBufferSupplied) + bResult = ghiInitFixedBuffer(connection, &connection->getFileBuffer, buffer, bufferSize); + else + bResult = ghiInitBuffer(connection, &connection->getFileBuffer, GET_FILE_BUFFER_INITIAL_SIZE, GET_FILE_BUFFER_INCREMENT_SIZE); + if(!bResult) + { + ghiFreeConnection(connection); + return GHTTPUnspecifiedError; + } + + // Setup the post state if needed. + ////////////////////////////////// + if(post && !ghiPostInitState(connection)) + { + ghiFreeConnection(connection); + return GHTTPInvalidPost; + } + + // Check blocking. + ////////////////// + if(blocking) + { + // Loop until completed. + //////////////////////// + while(!ghiProcessConnection(connection)) + msleep(10); + + // Done. + //////// + return 0; + } + + return connection->request; +} +#ifdef GSI_UNICODE +GHTTPRequest ghttpGetExW +( + const unsigned short * URL, + const unsigned short * headers, + char * buffer, + int bufferSize, + GHTTPPost post, + GHTTPBool throttle, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void * param +) +{ + char URL_A[1024]; + char headers_A[1024] = { '\0' }; + + assert(URL != NULL); + UCS2ToAsciiString(URL, (char*)URL_A); + if (headers != NULL) + UCS2ToAsciiString(headers, headers_A); + return ghttpGetExA((char*)URL_A, (char*)headers_A, buffer, bufferSize, post, throttle, blocking, progressCallback, completedCallback, param); +} +#endif + +GHTTPRequest ghttpSaveA +( + const char * URL, + const char * filename, + GHTTPBool blocking, + ghttpCompletedCallback completedCallback, + void * param +) +{ + return ghttpSaveExA(URL, filename, NULL, NULL, GHTTPFalse, blocking, NULL, completedCallback, param); +} +#ifdef GSI_UNICODE +GHTTPRequest ghttpSaveW +( + const unsigned short * URL, + const unsigned short * filename, + GHTTPBool blocking, + ghttpCompletedCallback completedCallback, + void * param +) +{ + char URL_A[1024] = { '\0' }; + char filename_A[1024] = { '\0' }; + + assert(URL != NULL); + UCS2ToAsciiString(URL, URL_A); + UCS2ToAsciiString(filename, filename_A); + return ghttpSaveA(URL_A, filename_A, blocking, completedCallback, param); +} +#endif + +static GHTTPRequest _ghttpSaveEx +( + const char * URL, + const gsi_char * filename, + const char * headers, + GHTTPPost post, + GHTTPBool throttle, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void * param +) +{ + GHIConnection * connection; + + assert(URL && URL[0]); + assert(filename && filename[0]); + + // Check args. + ////////////// + if(!URL || !URL[0]) + return GHTTPInvalidURL; + if(!filename || !filename[0]) + return GHTTPInvalidFileName; + + // Startup if it hasn't been done. + ////////////////////////////////// + if(!ghiReferenceCount) + ghttpStartup(); + + // Get a new connection object. + /////////////////////////////// + connection = ghiNewConnection(); + if(!connection) + return GHTTPInsufficientMemory; + + // Fill in the necessary info. + ////////////////////////////// + connection->type = GHISAVE; + connection->URL = goastrdup(URL); + if(!connection->URL) + { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + if(headers && *headers) + { + connection->sendHeaders = goastrdup(headers); + if(!connection->sendHeaders) + { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + } + connection->post = post; + connection->blocking = blocking; + connection->progressCallback = progressCallback; + connection->completedCallback = completedCallback; + connection->callbackParam = param; + connection->throttle = throttle; + + // Setup the post state if needed. + ////////////////////////////////// + if(post && !ghiPostInitState(connection)) + { + ghiFreeConnection(connection); + return GHTTPInvalidPost; + } + + // Open the file we're saving to. + ///////////////////////////////// +#ifdef NOFILE + connection->saveFile = NULL; +#else + connection->saveFile = _tfopen(filename, _T("wb")); +#endif + if(!connection->saveFile) + { + ghiFreeConnection(connection); + return GHTTPFailedToOpenFile; + } + + // Check blocking. + ////////////////// + if(blocking) + { + // Loop until completed. + //////////////////////// + while(!ghiProcessConnection(connection)) + msleep(10); + + // Done. + //////// + return 0; + } + + return connection->request; +} + +GHTTPRequest ghttpSaveExA +( + const char * URL, + const char * filename, + const char * headers, + GHTTPPost post, + GHTTPBool throttle, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void * param +) +{ + #ifdef GSI_UNICODE + unsigned short filename_W[1024]; + AsciiToUCS2String(filename, filename_W); + return _ghttpSaveEx(URL, filename_W, headers, post, throttle, blocking, progressCallback, completedCallback, param); + #else + return _ghttpSaveEx(URL, filename, headers, post, throttle, blocking, progressCallback, completedCallback, param); + #endif +} + +#ifdef GSI_UNICODE +GHTTPRequest ghttpSaveExW +( + const unsigned short * URL, + const unsigned short * filename, + const unsigned short * headers, + GHTTPPost post, + GHTTPBool throttle, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void * param +) +{ + char URL_A[1024]; + //char filename_A[1024] = { '\0' }; + char headers_A[1024] = { '\0' }; + + assert(URL_A != NULL); + UCS2ToAsciiString(URL, URL_A); + //if (filename != NULL) + // UCS2ToAsciiString(filename, filename_A); + if (headers != NULL) + UCS2ToAsciiString(headers, headers_A); + + return _ghttpSaveEx(URL_A, filename, headers_A, post, throttle, blocking, progressCallback, completedCallback, param); +} +#endif + +GHTTPRequest ghttpStreamA +( + const char * URL, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void * param +) +{ + return ghttpStreamExA(URL, NULL, NULL, GHTTPFalse, blocking, progressCallback, completedCallback, param); +} +#ifdef GSI_UNICODE +GHTTPRequest ghttpStreamW +( + const unsigned short * URL, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void * param +) +{ + char* URL_A = { '\0' }; + UCS2ToAsciiString(URL, URL_A); + return ghttpStreamA(URL_A, blocking, progressCallback, completedCallback, param); +} +#endif + +GHTTPRequest ghttpStreamExA +( + const char * URL, + const char * headers, + GHTTPPost post, + GHTTPBool throttle, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void * param +) +{ + GHIConnection * connection; + + assert(URL && URL[0]); + + // Check args. + ////////////// + if(!URL || !URL[0]) + return GHTTPInvalidURL; + + // Startup if it hasn't been done. + ////////////////////////////////// + if(!ghiReferenceCount) + ghttpStartup(); + + // Get a new connection object. + /////////////////////////////// + connection = ghiNewConnection(); + if(!connection) + return GHTTPInsufficientMemory; + + // Fill in the necessary info. + ////////////////////////////// + connection->type = GHISTREAM; + connection->URL = goastrdup(URL); + if(!connection->URL) + { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + if(headers && *headers) + { + connection->sendHeaders = goastrdup(headers); + if(!connection->sendHeaders) + { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + } + connection->post = post; + connection->blocking = blocking; + connection->progressCallback = progressCallback; + connection->completedCallback = completedCallback; + connection->callbackParam = param; + connection->throttle = throttle; + + // Setup the post state if needed. + ////////////////////////////////// + if(post && !ghiPostInitState(connection)) + { + ghiFreeConnection(connection); + return GHTTPInvalidPost; + } + + // Check blocking. + ////////////////// + if(blocking) + { + // Loop until completed. + //////////////////////// + while(!ghiProcessConnection(connection)) + msleep(10); + + // Done. + //////// + return 0; + } + + return connection->request; +} +#ifdef GSI_UNICODE +GHTTPRequest ghttpStreamExW +( + const unsigned short * URL, + const unsigned short * headers, + GHTTPPost post, + GHTTPBool throttle, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void * param +) +{ + char URL_A[1024] = {'\0'}; + char headers_A[1024] = {'\0'}; + UCS2ToAsciiString(URL, URL_A); + if(headers != NULL) + UCS2ToAsciiString(headers, headers_A); + return ghttpStreamExA(URL_A, headers_A, post, throttle, blocking, progressCallback, completedCallback, param); +} +#endif + +GHTTPRequest ghttpHeadA +( + const char * URL, + GHTTPBool blocking, + ghttpCompletedCallback completedCallback, + void * param +) +{ + return ghttpHeadExA(URL, NULL, GHTTPFalse, blocking, NULL, completedCallback, param); +} +#ifdef GSI_UNICODE +GHTTPRequest ghttpHeadW +( + const unsigned short * URL, + GHTTPBool blocking, + ghttpCompletedCallback completedCallback, + void * param +) +{ + char URL_A[1024] = {'\0'}; + UCS2ToAsciiString(URL, URL_A); + return ghttpHeadA(URL_A, blocking, completedCallback, param); +} +#endif + +GHTTPRequest ghttpHeadExA +( + const char * URL, + const char * headers, + GHTTPBool throttle, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void * param +) +{ + GHIConnection * connection; + + assert(URL && URL[0]); + + // Check args. + ////////////// + if(!URL || !URL[0]) + return GHTTPInvalidURL; + + // Startup if it hasn't been done. + ////////////////////////////////// + if(!ghiReferenceCount) + ghttpStartup(); + + // Get a new connection object. + /////////////////////////////// + connection = ghiNewConnection(); + if(!connection) + return GHTTPInsufficientMemory; + + // Fill in the necessary info. + ////////////////////////////// + connection->type = GHIHEAD; + connection->URL = goastrdup(URL); + if(!connection->URL) + { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + if(headers && *headers) + { + connection->sendHeaders = goastrdup(headers); + if(!connection->sendHeaders) + { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + } + connection->blocking = blocking; + connection->progressCallback = progressCallback; + connection->completedCallback = completedCallback; + connection->callbackParam = param; + connection->throttle = throttle; + + // Check blocking. + ////////////////// + if(blocking) + { + // Loop until completed. + //////////////////////// + while(!ghiProcessConnection(connection)) + msleep(10); + + // Done. + //////// + return 0; + } + + return connection->request; +} +#ifdef GSI_UNICODE +GHTTPRequest ghttpHeadExW +( + const unsigned short * URL, + const unsigned short * headers, + GHTTPBool throttle, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void * param +) +{ + char URL_A[1024] = {'\0'}; + char headers_A[1024] = {'\0'}; + if (URL != NULL) + UCS2ToAsciiString(URL, URL_A); + if (headers != NULL) + UCS2ToAsciiString(headers, headers_A); + return ghttpHeadExA(URL_A, headers_A, throttle, blocking, progressCallback, completedCallback, param); +} +#endif + +GHTTPRequest ghttpPostA +( + const char * URL, + GHTTPPost post, + GHTTPBool blocking, + ghttpCompletedCallback completedCallback, + void * param +) +{ + return ghttpPostExA(URL, NULL, post, GHTTPFalse, blocking, NULL, completedCallback, param); +} +#ifdef GSI_UNICODE +GHTTPRequest ghttpPostW +( + const unsigned short * URL, + GHTTPPost post, + GHTTPBool blocking, + ghttpCompletedCallback completedCallback, + void * param +) +{ + char URL_A[1024] = {'\0'}; + UCS2ToAsciiString(URL, URL_A); + return ghttpPostA(URL_A, post, blocking, completedCallback, param); +} +#endif + +GHTTPRequest ghttpPostExA +( + const char * URL, + const char * headers, + GHTTPPost post, + GHTTPBool throttle, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void * param +) +{ + GHIConnection * connection; + + assert(URL && URL[0]); + assert(post); + + // Check args. + ////////////// + if(!URL || !URL[0]) + return GHTTPInvalidURL; + if(!post) + return GHTTPInvalidPost; + + // Startup if it hasn't been done. + ////////////////////////////////// + if(!ghiReferenceCount) + ghttpStartup(); + + // Get a new connection object. + /////////////////////////////// + connection = ghiNewConnection(); + if(!connection) + return GHTTPInsufficientMemory; + + // Fill in the necessary info. + ////////////////////////////// + connection->type = GHIPOST; + connection->URL = goastrdup(URL); + if(!connection->URL) + { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + if(headers && *headers) + { + connection->sendHeaders = goastrdup(headers); + if(!connection->sendHeaders) + { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + } + connection->post = post; + connection->blocking = blocking; + connection->progressCallback = progressCallback; + connection->completedCallback = completedCallback; + connection->callbackParam = param; + connection->throttle = throttle; + + // Setup the post state if needed. + ////////////////////////////////// + if(post && !ghiPostInitState(connection)) + { + ghiFreeConnection(connection); + return GHTTPInvalidPost; + } + + // Check blocking. + ////////////////// + if(blocking) + { + // Loop until completed. + //////////////////////// + while(!ghiProcessConnection(connection)) + msleep(10); + + // Done. + //////// + return 0; + } + + return connection->request; +} +#ifdef GSI_UNICODE +GHTTPRequest ghttpPostExW +( + const unsigned short * URL, + const unsigned short * headers, + GHTTPPost post, + GHTTPBool throttle, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void * param +) +{ + char URL_A[1024] = {'\0'}; + char headers_A[1024] = {'\0'}; + UCS2ToAsciiString(URL, URL_A); + if (headers != NULL) + UCS2ToAsciiString(headers, headers_A); + return ghttpPostExA(URL_A, headers_A, post, throttle, blocking, progressCallback, completedCallback, param); +} +#endif + +void ghttpThink +( + void +) +{ + // Process all the connections. + /////////////////////////////// + ghiEnumConnections(ghiProcessConnection); +} + +GHTTPBool ghttpRequestThink +( + GHTTPRequest request +) +{ + GHIConnection * connection; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if(!connection) + return GHTTPFalse; + + // Think. + ///////// + ghiProcessConnection(connection); + return GHTTPTrue; +} + +void ghttpCancelRequest +( + GHTTPRequest request +) +{ + GHIConnection * connection; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if(!connection) + return; + + // Free it. + /////////// + ghiFreeConnection(connection); +} + +#if !defined(INSOCK) +// INSOCK does not support partial shutdown +void ghttpCloseRequest +( + GHTTPRequest request +) +{ + GHIConnection * connection; + + connection = ghiRequestToConnection(request); + if (!connection) + return; + + if (connection->socket) + { + // Gracefully close the connection + // SDK will dispatch a "request cancelled" callback when all data + // has been received + shutdown(connection->socket, 1); + connection->result = GHTTPRequestCancelled; + } +} +#endif + +GHTTPState ghttpGetState +( + GHTTPRequest request +) +{ + GHIConnection * connection; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if(!connection) + return (GHTTPState)0; + + return connection->state; +} + +const char * ghttpGetResponseStatus +( + GHTTPRequest request, + int * statusCode +) +{ + GHIConnection * connection; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if(!connection) + return NULL; + + // Check if we don't have the status yet. + ///////////////////////////////////////// + if(connection->state <= GHTTPReceivingStatus) + return NULL; + + // Set the status code. + /////////////////////// + if(statusCode) + *statusCode = connection->statusCode; + + return (connection->recvBuffer.data + connection->statusStringIndex); +} + +const char * ghttpGetHeaders +( + GHTTPRequest request +) +{ + GHIConnection * connection; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if(!connection) + return NULL; + + // Check if we don't have the headers yet. + ////////////////////////////////////////// + if(connection->state < GHTTPReceivingHeaders) + return NULL; + + // Verify we have headers. + ////////////////////////// + if(connection->headerStringIndex >= connection->recvBuffer.len) + return NULL; + + return (connection->recvBuffer.data + connection->headerStringIndex); +} + +const char * ghttpGetURL +( + GHTTPRequest request +) +{ + GHIConnection * connection; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if(!connection) + return NULL; + + return connection->URL; +} + +GHTTPBool ghttpSetProxy +( + const char * server +) +{ + return ghiSetProxy(server); +} + +GHTTPBool ghttpSetRequestProxy +( + GHTTPRequest request, + const char * server +) +{ + return ghiSetRequestProxy(request, server); +} + + +void ghttpSetThrottle +( + GHTTPRequest request, + GHTTPBool throttle +) +{ + GHIConnection * connection; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if(!connection) + return; + + connection->throttle = throttle; + + // Set the buffer size based on the throttle setting. + ///////////////////////////////////////////////////// + if(connection->socket != INVALID_SOCKET) + SetReceiveBufferSize(connection->socket, throttle?ghiThrottleBufferSize:(8 * 1024)); +} + +void ghttpThrottleSettings +( + int bufferSize, + gsi_time timeDelay +) +{ + ghiThrottleSettings(bufferSize, timeDelay); +} + +void ghttpSetMaxRecvTime +( + GHTTPRequest request, + gsi_time maxRecvTime +) +{ + GHIConnection* connection = ghiRequestToConnection(request); + if (connection == NULL) + return; + + connection->maxRecvTime = maxRecvTime; +} + +// Internal prototypes for persistent HTTP connections +// Prevents warnings from strict compilers +////////////////////////////////////////////////////// +SOCKET ghttpGetSocket(GHTTPRequest request); +GHTTPBool ghttpReuseSocket(GHTTPRequest request, SOCKET socket); + +// For use in persistent HTTP connections +// Call this in the completed callback to obtain the socket, which can be used with +// ghttpReuseSocket to make a second request to the same host +/////////////////////////////////////////////////////////////////// +SOCKET ghttpGetSocket +( + GHTTPRequest request +) +{ + GHIConnection * connection; + SOCKET ret; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if(!connection) + return INVALID_SOCKET; + + // Only allow them to grab the socket during the competion callback (not while a request is in process) + ///////////////////////////////////////////////////////// + if (!connection->completed) + return INVALID_SOCKET; + + ret = connection->socket; + // Mark the connection as invalid so that it doesn't get closed + connection->socket = INVALID_SOCKET; + + return ret; +} + +// For use in persistent HTTP connections +// Call this after creating a request, but before calling think the first time in order to reuse +// an existing connection to the same server +// If the socket passed is INVALID_SOCKET, a new connection will be created and marked as persistent +/////////////////////////////////////////////////////////////////// +GHTTPBool ghttpReuseSocket +( + GHTTPRequest request, + SOCKET socket +) +{ + GHIConnection * connection; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if(!connection) + return GHTTPFalse; + if (connection->state != GHTTPSocketInit) + return GHTTPFalse; + if (connection->socket != INVALID_SOCKET) + return GHTTPFalse; + + connection->persistConnection = GHTTPTrue; + connection->socket = socket; + + + /* + if (socket == INVALID_SOCKET) + { + // The connection is marked as persistent, but we still need to lookup and connect, so don't advance the state + ///////////////////////////////////////////////////////////////////// + return GHTTPTrue; + } + + // Skip the host lookup & connect - send data once the socket is writable + ////////////////////////////////////////////// + connection->state = GHTTPConnecting; + */ + return GHTTPTrue; +} + + +GHTTPPost ghttpNewPost +( + void +) +{ + return ghiNewPost(); +} + +void ghttpPostSetAutoFree +( + GHTTPPost post, + GHTTPBool autoFree +) +{ + assert(post); + if(!post) + return; + + ghiPostSetAutoFree(post, autoFree); +} + +void ghttpFreePost +( + GHTTPPost post +) +{ + assert(post); + if(!post) + return; + + ghiFreePost(post); +} + +GHTTPBool ghttpPostAddStringA +( + GHTTPPost post, + const char * name, + const char * string +) +{ + assert(post); + assert(name && name[0]); + + if(!post) + return GHTTPFalse; + if(!name || !name[0]) + return GHTTPFalse; + if(!string) + string = ""; + + return ghiPostAddString(post, name, string); +} +#ifdef GSI_UNICODE +GHTTPBool ghttpPostAddStringW +( + GHTTPPost post, + const unsigned short * name, + const unsigned short * string +) +{ + char name_A[1024] = {'\0'}; + char string_A[1024] = {'\0'}; + if (name != NULL) + UCS2ToAsciiString(name, name_A); + if (string != NULL) + UCS2ToAsciiString(string, string_A); + return ghttpPostAddStringA(post, name_A, string_A); +} +#endif + +GHTTPBool ghttpPostAddFileFromDiskA +( + GHTTPPost post, + const char * name, + const char * filename, + const char * reportFilename, + const char * contentType +) +{ + assert(post); + assert(name && name[0]); + assert(filename && filename[0]); + + if(!post) + return GHTTPFalse; + if(!name || !name[0]) + return GHTTPFalse; + if(!filename || !filename[0]) + return GHTTPFalse; + if(!reportFilename || !reportFilename[0]) + reportFilename = filename; + if(!contentType) + contentType = "application/octet-stream"; + + return ghiPostAddFileFromDisk(post, name, filename, reportFilename, contentType); +} +#ifdef GSI_UNICODE +GHTTPBool ghttpPostAddFileFromDiskW +( + GHTTPPost post, + const unsigned short * name, + const unsigned short * filename, + const unsigned short * reportFilename, + const unsigned short * contentType +) +{ + char name_A[1024] = {'\0'}; + char filename_A[1024] = {'\0'}; + char reportFilename_A[1024] = {'\0'}; + char contentType_A[1024] = {'\0'}; + if (name != NULL) UCS2ToAsciiString(name, name_A); + if (filename != NULL) UCS2ToAsciiString(filename, filename_A); + if (reportFilename != NULL) UCS2ToAsciiString(reportFilename, reportFilename_A); + if (contentType != NULL) UCS2ToAsciiString(contentType, contentType_A); + return ghttpPostAddFileFromDiskA(post, name_A, filename_A, reportFilename_A, contentType_A); +} +#endif + +GHTTPBool ghttpPostAddFileFromMemoryA +( + GHTTPPost post, + const char * name, + const char * buffer, + int bufferLen, + const char * reportFilename, + const char * contentType +) +{ + assert(post); + assert(name && name[0]); + assert(bufferLen >= 0); +#ifdef _DEBUG + if(bufferLen > 0) + assert(buffer); +#endif + assert(reportFilename && reportFilename[0]); + + if(!post) + return GHTTPFalse; + if(!name || !name[0]) + return GHTTPFalse; + if(bufferLen < 0) + return GHTTPFalse; + if(!bufferLen && !buffer) + return GHTTPFalse; + if(!contentType) + contentType = "application/octet-stream"; + + return ghiPostAddFileFromMemory(post, name, buffer, bufferLen, reportFilename, contentType); +} +#ifdef GSI_UNICODE +GHTTPBool ghttpPostAddFileFromMemoryW +( + GHTTPPost post, + const unsigned short * name, + const char * buffer, + int bufferLen, + const unsigned short * reportFilename, + const unsigned short * contentType +) +{ + char name_A[1024] = { '\0' }; + char reportFilename_A[1024] = { '\0' }; + char contentType_A[1024] = { '\0' }; + if (name != NULL) + UCS2ToAsciiString(name, name_A); + if (reportFilename != NULL) + UCS2ToAsciiString(reportFilename, reportFilename_A); + if (contentType != NULL) + UCS2ToAsciiString(contentType, contentType_A); + + + return ghttpPostAddFileFromMemoryA(post, name_A, buffer, bufferLen, reportFilename_A, contentType_A); + + GSI_UNUSED(reportFilename); + GSI_UNUSED(contentType); +} +#endif + +GHTTPBool ghttpPostAddXml +( + GHTTPPost post, + GSXmlStreamWriter soap +) +{ + GS_ASSERT(post != NULL); + GS_ASSERT(soap != NULL); + + return ghiPostAddXml(post, soap); +} + +void ghttpPostSetCallback +( + GHTTPPost post, + ghttpPostCallback callback, + void * param +) +{ + assert(post); + + if(!post) + return; + + ghiPostSetCallback(post, callback, param); +} + + diff --git a/code/gamespy/ghttp/ghttpMain.h b/code/gamespy/ghttp/ghttpMain.h new file mode 100644 index 00000000..0b19a7cd --- /dev/null +++ b/code/gamespy/ghttp/ghttpMain.h @@ -0,0 +1,19 @@ + /* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GHTTPMAIN_H_ +#define _GHTTPMAIN_H_ + +#include +#include "ghttp.h" +#include "../common/gsCommon.h" +#include "../common/gsStringUtil.h" + +#endif diff --git a/code/gamespy/ghttp/ghttpPost.c b/code/gamespy/ghttp/ghttpPost.c new file mode 100644 index 00000000..de1c6678 --- /dev/null +++ b/code/gamespy/ghttp/ghttpPost.c @@ -0,0 +1,1643 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "ghttpPost.h" +#include "ghttpMain.h" +#include "ghttpConnection.h" +#include "ghttpCommon.h" + +#include "../common/gsCrypt.h" +#include "../common/gsSSL.h" +#include "../common/gsXML.h" + + +// The border between parts in a file send. +/////////////////////////////////////////// +#define GHI_MULTIPART_BOUNDARY "Qr4G823s23d---<<><><<<>--7d118e0536" +#define GHI_MULTIPART_BOUNDARY_BASE "--" GHI_MULTIPART_BOUNDARY +#define GHI_MULTIPART_BOUNDARY_FIRST GHI_MULTIPART_BOUNDARY_BASE CRLF +#define GHI_MULTIPART_BOUNDARY_NORMAL CRLF GHI_MULTIPART_BOUNDARY_BASE CRLF +#define GHI_MULTIPART_BOUNDARY_END CRLF GHI_MULTIPART_BOUNDARY_BASE "--" CRLF + +#define GHI_LEGAL_URLENCODED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_@-.*" +#define GHI_DIGITS "0123456789ABCDEF" + +// DIME header settings + // first byte is a combination of VERSION + first/last/chunked +#define GHI_DIME_VERSION (0x1<<3) // 5th bit (from the left) +#define GHI_DIMEFLAG_FIRSTRECORD (1<<2) +#define GHI_DIMEFLAG_LASTRECORD (1<<1) +#define GHI_DIMEFLAG_CHUNKED (1<<0) + // second byte is combination of TYPE_T and reserved (4bits = 0) +#define GHI_DIMETYPE_T_UNCHANGED (0x0 << 4) +#define GHI_DIMETYPE_T_MEDIA (0x1 << 4) +#define GHI_DIMETYPE_T_URI (0x2 << 4) +#define GHI_DIMETYPE_T_UNKNOWN (0x3 << 4) +#define GHI_DIMETYPE_T_EMPTY (0x4 << 4) // lengths must be set to 0 + +//#define GHI_DIME_SOAPID "gsi:soap" +#define GHI_DIME_SOAPID "cid:id0" +#define GHI_DIME_SOAPTYPE "http://schemas.xmlsoap.org/soap/envelope/" + +typedef struct GSIDimeHeader +{ + gsi_u8 mVersionAndFlags; + gsi_u8 mTypeT; + gsi_u16 mOptionsLength; + gsi_u16 mIdLength; + gsi_u16 mTypeLength; + gsi_u32 mDataLength; + // gsi_u8 mOptions[mOptionsLength]; + // gsi_u8 mId[mIdLength]; + // gsi_u8 mType[mTypeLength]; + // gsi_u8 mData[mDataLength]; +} GHIDimeHeader; + +// POST TYPES. +////////////// +typedef enum +{ + GHIString, // A regular string. + GHIFileDisk, // A file from disk. + GHIFileMemory, // A file from memory. + GHIXmlData // XML Soap. (long string) +} GHIPostDataType; + +// POST OBJECT. +/////////////// +typedef struct GHIPost +{ + DArray data; + ghttpPostCallback callback; + void * param; + GHTTPBool hasFiles; + GHTTPBool hasSoap; + GHTTPBool useDIME; + GHTTPBool autoFree; +} GHIPost; + +// POST DATA. +///////////// +typedef struct GHIPostStringData +{ + char * string; + int len; + GHTTPBool invalidChars; + int extendedChars; +} GHIPostStringData; + +typedef struct GHIPostFileDiskData +{ + char * filename; + char * reportFilename; + char * contentType; +} GHIPostFileDiskData; + +typedef struct GHIPostFileMemoryData +{ + const char * buffer; + int len; + char * reportFilename; + char * contentType; +} GHIPostFileMemoryData; + +typedef struct GHIPostXmlData +{ + GSXmlStreamWriter xml; +} GHIPostXmlData; + +typedef struct GHIPostData +{ + GHIPostDataType type; + char * name; + union + { + GHIPostStringData string; + GHIPostFileDiskData fileDisk; + GHIPostFileMemoryData fileMemory; + GHIPostXmlData xml; + } data; +} GHIPostData; + +// POST STATE. +////////////// +//typedef struct GHIPostStringState +//{ +//} GHIPostStringState; + +typedef struct GHIPostFileDiskState +{ + FILE * file; + long len; +} GHIPostFileDiskState; + +//typedef struct GHIPostFileMemoryState +//{ +//} GHIPostFileMemoryState; + + +//typedef struct GHIPostSoapState +//{ +//} GHIPostSoapState; + +typedef struct GHIPostState +{ + GHIPostData * data; + int pos; + union + { + //GHIPostStringState string; + GHIPostFileDiskState fileDisk; + //GHIPostFileMemoryState fileMemory; + //GHIPostSoapState soap; + } state; +} GHIPostState; + +// FUNCTIONS. +///////////// +static void ghiPostDataFree +( + void * elem +) +{ + GHIPostData * data = (GHIPostData *)elem; + + // Free the name. + ///////////////// + if (data->type != GHIXmlData) + gsifree(data->name); + + // Free based on type. + ////////////////////// + if(data->type == GHIString) + { + // Free the string string. + ///////////////////////// + gsifree(data->data.string.string); + } + else if(data->type == GHIFileDisk) + { + // Free the strings. + //////////////////// + gsifree(data->data.fileDisk.filename); + gsifree(data->data.fileDisk.reportFilename); + gsifree(data->data.fileDisk.contentType); + } + else if(data->type == GHIFileMemory) + { + // Free the strings. + //////////////////// + gsifree(data->data.fileMemory.reportFilename); + gsifree(data->data.fileMemory.contentType); + } + else if(data->type == GHIXmlData) + { + gsXmlFreeWriter(data->data.xml.xml); + } + else + { + // The type didn't match any known types. + ///////////////////////////////////////// + assert(0); + } +} + +GHTTPPost ghiNewPost +( + void +) +{ + GHIPost * post; + + // Allocate the post object. + //////////////////////////// + post = (GHIPost *)gsimalloc(sizeof(GHIPost)); + if(!post) + return NULL; + + // Initialize it. + ///////////////// + memset(post, 0, sizeof(GHIPost)); + post->autoFree = GHTTPTrue; + + // Create the array of data objects. + //////////////////////////////////// + post->data = ArrayNew(sizeof(GHIPostData), 0, ghiPostDataFree); + if(!post->data) + { + gsifree(post); + return NULL; + } + + return (GHTTPPost)post; +} + +void ghiPostSetAutoFree +( + GHTTPPost _post, + GHTTPBool autoFree +) +{ + GHIPost * post = (GHIPost *)_post; + + post->autoFree = autoFree; +} + +GHTTPBool ghiIsPostAutoFree +( + GHTTPPost _post +) +{ + GHIPost * post = (GHIPost *)_post; + + return post->autoFree; +} + +void ghiFreePost +( + GHTTPPost _post +) +{ + GHIPost * post = (GHIPost *)_post; + + // Free the array of data objects. + ////////////////////////////////// + ArrayFree(post->data); + + // Free the post object. + //////////////////////// + gsifree(post); +} + +GHTTPBool ghiPostAddString +( + GHTTPPost _post, + const char * name, + const char * string +) +{ + GHIPost * post = (GHIPost *)_post; + GHIPostData data; + int len; + int rcode; + + // Copy the strings. + //////////////////// + name = goastrdup(name); + string = goastrdup(string); + if(!name || !string) + { + gsifree((char *)name); + gsifree((char *)string); + return GHTTPFalse; + } + + // Set the data. + //////////////// + memset(&data, 0, sizeof(GHIPostData)); + data.type = GHIString; + data.name = (char *)name; + data.data.string.string = (char *)string; + len = (int)strlen(string); + data.data.string.len = len; + data.data.string.invalidChars = GHTTPFalse; + + // Are there any invalid characters? + //////////////////////////////////// + rcode = (int)strspn(string, GHI_LEGAL_URLENCODED_CHARS); + if(rcode != len) + { + int i; + int count = 0; + + data.data.string.invalidChars = GHTTPTrue; + + // Count the number, not including spaces. + ////////////////////////////////////////// + for(i = 0 ; string[i] ; i++) + if(!strchr(GHI_LEGAL_URLENCODED_CHARS, string[i]) && (string[i] != ' ')) + count++; + + data.data.string.extendedChars = count; + } + + // Add it. + ////////// + ArrayAppend(post->data, &data); + + return GHTTPTrue; +} + +GHTTPBool ghiPostAddFileFromDisk +( + GHTTPPost _post, + const char * name, + const char * filename, + const char * reportFilename, + const char * contentType +) +{ + GHIPost * post = (GHIPost *)_post; + GHIPostData data; + + // Copy the strings. + //////////////////// + name = goastrdup(name); + filename = goastrdup(filename); + reportFilename = goastrdup(reportFilename); + contentType = goastrdup(contentType); + if(!name || !filename || !reportFilename || !contentType) + { + gsifree((char *)name); + gsifree((char *)filename); + gsifree((char *)reportFilename); + gsifree((char *)contentType); + return GHTTPFalse; + } + + // Set the data. + //////////////// + memset(&data, 0, sizeof(GHIPostData)); + data.type = GHIFileDisk; + data.name = (char *)name; + data.data.fileDisk.filename = (char *)filename; + data.data.fileDisk.reportFilename = (char *)reportFilename; + data.data.fileDisk.contentType = (char *)contentType; + + // Add it. + ////////// + ArrayAppend(post->data, &data); + + // We have files. + ///////////////// + post->hasFiles = GHTTPTrue; + + // if we have both soap and a file we MUST use DIME + if (post->hasSoap == GHTTPTrue) + post->useDIME = GHTTPTrue; + + return GHTTPTrue; +} + +GHTTPBool ghiPostAddFileFromMemory +( + GHTTPPost _post, + const char * name, + const char * buffer, + int bufferLen, + const char * reportFilename, + const char * contentType +) +{ + GHIPost * post = (GHIPost *)_post; + GHIPostData data; + + // Copy the strings. + //////////////////// + name = goastrdup(name); + reportFilename = goastrdup(reportFilename); + contentType = goastrdup(contentType); + if(!name || !reportFilename || !contentType) + { + gsifree((char *)name); + gsifree((char *)reportFilename); + gsifree((char *)contentType); + return GHTTPFalse; + } + + // Set it. + ////////// + memset(&data, 0, sizeof(GHIPostData)); + data.type = GHIFileMemory; + data.name = (char *)name; + data.data.fileMemory.buffer = (char *)buffer; + data.data.fileMemory.len = bufferLen; + data.data.fileMemory.reportFilename = (char *)reportFilename; + data.data.fileMemory.contentType = (char *)contentType; + + // Add it. + ////////// + ArrayAppend(post->data, &data); + + // We have a file. + ////////////////// + post->hasFiles = GHTTPTrue; + + // if we have both soap and a file we MUST use DIME + if (post->hasSoap == GHTTPTrue) + post->useDIME = GHTTPTrue; + + return GHTTPTrue; +} + +GHTTPBool ghiPostAddXml +( + GHTTPPost _post, + GSXmlStreamWriter xml +) +{ + GHIPostData data; + //unsigned int rcode = 0; + + GHIPost * post = (GHIPost *)_post; + + data.type = GHIXmlData; + data.data.xml.xml = xml; + ArrayAppend(post->data, &data); + post->hasSoap = GHTTPTrue; + + // if we have both soap and a file we MUST use DIME + if (post->hasFiles == GHTTPTrue) + post->useDIME = GHTTPTrue; + + return GHTTPTrue; +} + +void ghiPostSetCallback +( + GHTTPPost _post, + ghttpPostCallback callback, + void * param +) +{ + GHIPost * post = (GHIPost *)_post; + + // Set the callback and param. + ////////////////////////////// + post->callback = callback; + post->param = param; +} + +const char * ghiPostGetContentType +( + struct GHIConnection * connection +) +{ + GHIPost * post = connection->post; + + assert(post); + if(!post) + return ""; + + // Report content-type based on if we are sending files or not. + /////////////////////////////////////////////////////////////// + if(post->useDIME) + return ("application/dime"); + else if (post->hasFiles) + { + GS_ASSERT(!post->hasSoap); + return ("multipart/form-data; boundary=" GHI_MULTIPART_BOUNDARY); + } + else if (post->hasSoap) + { + GS_ASSERT(!post->hasFiles); + return ("text/xml"); + } + else + { + GS_ASSERT(!post->hasSoap); + GS_ASSERT(!post->hasFiles); + return "application/x-www-form-urlencoded"; + } +} + +static int ghiPostGetNoFilesContentLength +( + struct GHIConnection * connection +) +{ + GHIPost * post = connection->post; + GHIPostData * data; + int i; + int num; + int total = 0; + int foundSoapAlready = 0; + + num = ArrayLength(post->data); + if(!num) + return 0; + + for(i = 0 ; i < num ; i++) + { + data = (GHIPostData *)ArrayNth(post->data, i); + + GS_ASSERT(data->type == GHIString || data->type == GHIXmlData); + + if (data->type == GHIString) + { + total += (int)strlen(data->name); + total += data->data.string.len; + total += (data->data.string.extendedChars * 2); + total++; // '=' + } + else if (data->type == GHIXmlData) + { + GS_ASSERT(foundSoapAlready == 0); // only support one soap object per request + foundSoapAlready = 1; + total += gsXmlWriterGetDataLength(data->data.xml.xml); + } + } + + total += (num - 1); // '&' + + GSI_UNUSED(foundSoapAlready); + return total; +} + +static int ghiPostGetHasFilesContentLength +( + struct GHIConnection * connection +) +{ + GHIPost * post = connection->post; + GHIPostData * data; + int i; + int num; + int total = 0; + int foundSoapAlready = 0; + static int boundaryLen; + static int stringBaseLen; + static int fileBaseLen; + static int endLen; + static int xmlBaseLen; + + if(!boundaryLen) + { + if (post->useDIME) + { + GS_ASSERT(post->hasSoap); + GS_ASSERT(post->hasFiles); + boundaryLen = sizeof(GHIDimeHeader); + stringBaseLen = boundaryLen; + fileBaseLen = boundaryLen; + xmlBaseLen = boundaryLen; + endLen = 0; + } + else + { + GS_ASSERT(!post->hasSoap); + boundaryLen = (int)strlen(GHI_MULTIPART_BOUNDARY_BASE); + stringBaseLen = (boundaryLen + 47); // + name + string + fileBaseLen = (boundaryLen + 76); // + name + filename + content-type + file + xmlBaseLen = 0; // no boundaries for text/xml type soap + endLen = (boundaryLen + 4); + } + } + + num = ArrayLength(post->data); + + for(i = 0 ; i < num ; i++) + { + data = (GHIPostData *)ArrayNth(post->data, i); + + if(data->type == GHIString) + { + total += stringBaseLen; + total += (int)strlen(data->name); + total += data->data.string.len; + } + else if(data->type == GHIFileDisk) + { + GHIPostState * state; + + total += fileBaseLen; + total += (int)strlen(data->name); + total += (int)strlen(data->data.fileDisk.contentType); + state = (GHIPostState *)ArrayNth(connection->postingState.states, i); + assert(state); + total += (int)state->state.fileDisk.len; + + if (!post->useDIME) + total += (int)strlen(data->data.fileDisk.reportFilename); + + if (post->useDIME) + { + // have to include padding bytes! + int padBytes = 0; + + padBytes = 4-(int)strlen(data->name)%4; + if (padBytes != 4) + total += padBytes; + padBytes = 4-(int)strlen(data->data.fileDisk.contentType)%4; + if (padBytes != 4) + total += padBytes; + padBytes = 4-(int)state->state.fileDisk.len%4; + if (padBytes != 4) + total += padBytes; + } + } + else if(data->type == GHIFileMemory) + { + total += fileBaseLen; + total += (int)strlen(data->name); + total += (int)strlen(data->data.fileMemory.contentType); + total += data->data.fileMemory.len; + + if (!post->useDIME) + total += (int)strlen(data->data.fileMemory.reportFilename); + + if (post->useDIME) + { + // have to include padding bytes! + int padBytes = 0; + + padBytes = 4-(int)strlen(data->name)%4; + if (padBytes != 4) + total += padBytes; + padBytes = 4-(int)strlen(data->data.fileMemory.contentType)%4; + if (padBytes != 4) + total += padBytes; + padBytes = 4-(int)data->data.fileMemory.len%4; + if (padBytes != 4) + total += padBytes; + } + } + else if(data->type == GHIXmlData) + { + int padBytes = 0; + + GS_ASSERT(foundSoapAlready == 0); // only one soap envelope per request + GS_ASSERT(post->useDIME); // soap+file = use DIME + foundSoapAlready = 1; + total += xmlBaseLen; + total += gsXmlWriterGetDataLength(data->data.xml.xml); + + // have to include padding bytes! + padBytes = 4-(int)gsXmlWriterGetDataLength(data->data.xml.xml)%4; + if (padBytes != 4) + total += padBytes; + total += (int)strlen(GHI_DIME_SOAPID); + padBytes = 4-(int)strlen(GHI_DIME_SOAPID)%4; + if (padBytes != 4) + total += padBytes; + total += (int)strlen(GHI_DIME_SOAPTYPE); + padBytes = 4-(int)strlen(GHI_DIME_SOAPTYPE)%4; + if (padBytes != 4) + total += padBytes; + } + else + { + assert(0); + return 0; + } + } + + // Add the end. + /////////////// + total += endLen; + + GSI_UNUSED(foundSoapAlready); + return total; +} + +static int ghiPostGetContentLength +( + struct GHIConnection * connection +) +{ + GHIPost * post = connection->post; + + assert(post); + if(!post) + return 0; + + if(post->hasFiles) + return ghiPostGetHasFilesContentLength(connection); + + return ghiPostGetNoFilesContentLength(connection); +} + +static GHTTPBool ghiPostStateInit +( + GHIPostState * state +) +{ + GHIPostDataType type; + + // The type. + //////////// + type = state->data->type; + + // Set the position to sending header. + ////////////////////////////////////// + state->pos = -1; + + // Init based on type. + ////////////////////// + if(type == GHIString) + { + } + else if(type == GHIFileDisk) + { + // Open the file. + ///////////////// +#ifndef NOFILE + state->state.fileDisk.file = fopen(state->data->data.fileDisk.filename, "rb"); +#endif + if(!state->state.fileDisk.file) + return GHTTPFalse; + + // Get the file length. + /////////////////////// + if(fseek(state->state.fileDisk.file, 0, SEEK_END) != 0) + return GHTTPFalse; + state->state.fileDisk.len = ftell(state->state.fileDisk.file); + if(state->state.fileDisk.len == EOF) + return GHTTPFalse; + rewind(state->state.fileDisk.file); + } + else if(type == GHIFileMemory) + { + } + else if(type == GHIXmlData) + { + } + else + { + // The type didn't match any known types. + ///////////////////////////////////////// + assert(0); + + return GHTTPFalse; + } + + return GHTTPTrue; +} + +static void ghiPostStateCleanup +( + GHIPostState * state +) +{ + GHIPostDataType type; + + // The type. + //////////// + type = state->data->type; + + // Init based on type. + ////////////////////// + if(type == GHIString) + { + } + else if(type == GHIFileDisk) + { + if(state->state.fileDisk.file) + fclose(state->state.fileDisk.file); + state->state.fileDisk.file = NULL; + } + else if(type == GHIFileMemory) + { + } + else if(type == GHIXmlData) + { + } + else + { + // The type didn't match any known types. + ///////////////////////////////////////// + assert(0); + } +} + +GHTTPBool ghiPostInitState +( + struct GHIConnection * connection +) +{ + int i; + int len; + GHIPostData * data; + GHIPostState state; + GHIPostState * pState; + + assert(connection->post); + if(!connection->post) + return GHTTPFalse; + + // Create an array for the states. + ////////////////////////////////// + connection->postingState.index = 0; + connection->postingState.bytesPosted = 0; + connection->postingState.totalBytes = 0; + connection->postingState.completed = GHTTPFalse; + connection->postingState.callback = connection->post->callback; + connection->postingState.param = connection->post->param; + len = ArrayLength(connection->post->data); + connection->postingState.states = ArrayNew(sizeof(GHIPostState), len, NULL); + if(!connection->postingState.states) + return GHTTPFalse; + + // Setup all the states. + //////////////////////// + for(i = 0 ; i < len ; i++) + { + // Get the data object for this index. + ////////////////////////////////////// + data = (GHIPostData *)ArrayNth(connection->post->data, i); + + // Initialize the state's members. + ////////////////////////////////// + memset(&state, 0, sizeof(GHIPostState)); + state.data = data; + + // Call the init function. + ////////////////////////// + if(!ghiPostStateInit(&state)) + { + // We need to cleanup everything we just initialized. + ///////////////////////////////////////////////////// + for(i-- ; i >= 0 ; i--) + { + pState = (GHIPostState *)ArrayNth(connection->postingState.states, i); + ghiPostStateCleanup(pState); + } + + // Free the array. + ////////////////// + ArrayFree(connection->postingState.states); + connection->postingState.states = NULL; + + return GHTTPFalse; + } + + // Add it to the array. + /////////////////////// + ArrayAppend(connection->postingState.states, &state); + } + + // If this asserts, there aren't the same number of state objects as data objects. + // There should be a 1-to-1 mapping between data and states. + ////////////////////////////////////////////////////////////////////////////////// + assert(ArrayLength(connection->post->data) == ArrayLength(connection->postingState.states)); + + // Get the total number of bytes. + ///////////////////////////////// + connection->postingState.totalBytes = ghiPostGetContentLength(connection); + + // Wait for continue before posting. + // -- Enabled for Soap messages only + // -- Disabled for all other content because many web servers do not support it + ////////////////////////////////////////////////////// + if (connection->post->hasSoap == GHTTPTrue) + connection->postingState.waitPostContinue = GHTTPTrue; + else + connection->postingState.waitPostContinue = GHTTPFalse; + + return GHTTPTrue; +} + +void ghiPostCleanupState +( + struct GHIConnection * connection +) +{ + int i; + int len; + GHIPostState * state; + + // Loop through and call the cleanup function. + ////////////////////////////////////////////// + if(connection->postingState.states) + { + len = ArrayLength(connection->postingState.states); + for(i = 0 ; i < len ; i++) + { + state = (GHIPostState *)ArrayNth(connection->postingState.states, i); + ghiPostStateCleanup(state); + } + + // Free the array. + ////////////////// + ArrayFree(connection->postingState.states); + connection->postingState.states = NULL; + } + + // Free the post. + ///////////////// + if(connection->post && connection->post->autoFree) + { + ghiFreePost(connection->post); + connection->post = NULL; + } +} + +static GHIPostingResult ghiPostStringStateDoPosting +( + GHIPostState * state, + GHIConnection * connection +) +{ + //GHTTPBool result; + + assert(state->pos >= 0); + + // Is this an empty string? + /////////////////////////// + if(state->data->data.string.len == 0) + return GHIPostingDone; + + assert(state->pos < state->data->data.string.len); + + // If we're doing a simple post, we need to fix invalid characters. + // - only applies to simple posts + /////////////////////////////////////////////////////////////////// + if(!connection->post->hasFiles && !connection->post->hasSoap && state->data->data.string.invalidChars) + { + int i; + int c; + const char * string = state->data->data.string.string; + char hex[4] = "%00"; + GHIBuffer *writeBuffer; + + // When encrypting, we need space for two copies + if (connection->encryptor.mEngine == GHTTPEncryptionEngine_None) + writeBuffer = &connection->sendBuffer; + else + writeBuffer = &connection->encodeBuffer; + + // This could probably be done a lot better. + //////////////////////////////////////////// + for(i = 0 ; (c = string[i]) != 0 ; i++) + { + if(strchr(GHI_LEGAL_URLENCODED_CHARS, c)) + { + // Legal. + ///////// + //result = ghiAppendCharToBuffer(writeBuffer, c); + ghiAppendCharToBuffer(writeBuffer, c); + } + else if(c == ' ') + { + // Space. + ///////// + //result = ghiAppendCharToBuffer(writeBuffer, '+'); + ghiAppendCharToBuffer(writeBuffer, '+'); + } + else + { + // To hex. + ////////// + assert((c / 16) < 16); + hex[1] = GHI_DIGITS[c / 16]; + hex[2] = GHI_DIGITS[c % 16]; + //result = ghiAppendDataToBuffer(writeBuffer, hex, 3); + ghiAppendDataToBuffer(writeBuffer, hex, 3); + } + } + } + else + { + // copy the string as-is, encrypting if necessary + GHITrySendResult result = ghiTrySendThenBuffer(connection, + state->data->data.string.string, state->data->data.string.len); + if (result == GHITrySendError) + return GHIPostingError; + else + return GHIPostingDone; + } + + // Send the URL fixed string + //////////////////////////// + if (connection->encryptor.mEngine == GHTTPEncryptionEngine_None) + { + // The URL fixed string was written to the send buffer, so send it! + if (!ghiSendBufferedData(connection)) + return GHIPostingError; + + if (connection->sendBuffer.pos == connection->sendBuffer.len) + ghiResetBuffer(&connection->sendBuffer); + return GHIPostingDone; + } + else + { + // SSL data is in the "to be encrypted" buffer, so wait until + // we have the full MIME form before encrypting (for efficiency) + return GHIPostingDone; + } +} + +static GHIPostingResult ghiPostXmlStateDoPosting +( + GHIPostState * state, + GHIConnection * connection +) +{ + GSXmlStreamWriter xml = state->data->data.xml.xml; + char pad[3] = { '\0', '\0', '\0' }; + int padlen = 0; + + // make sure state is valid + GS_ASSERT(state->pos >= 0); + GS_ASSERT(connection->post != NULL); + + // when using a DIME, we have to pad to multiple of 4 + if (connection->post->useDIME) + { + padlen = 4-(gsXmlWriterGetDataLength(xml)%4); + if (padlen == 4) + padlen = 0; + } + + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None && + connection->encryptor.mEncryptOnBuffer == GHTTPTrue) + { + // Copy to encode buffer before encrypting + GS_ASSERT(connection->encodeBuffer.len >= 0); // there must be a header for this soap data! + if (!ghiAppendDataToBuffer(&connection->encodeBuffer, gsXmlWriterGetData(xml), gsXmlWriterGetDataLength(xml)) || + !ghiAppendDataToBuffer(&connection->encodeBuffer, pad, padlen) || + !ghiEncryptDataToBuffer(&connection->sendBuffer, connection->encodeBuffer.data, connection->encodeBuffer.len) + ) + { + return GHIPostingError; + } + + // Clear out our temporary buffer + ghiResetBuffer(&connection->encodeBuffer); + + // Send what we can now + if (GHTTPFalse == ghiSendBufferedData(connection)) + return GHIPostingError; + + // is there more to send? + if (connection->sendBuffer.pos == connection->sendBuffer.len) + ghiResetBuffer(&connection->sendBuffer); + + return GHIPostingDone; + } + else + { + GHITrySendResult result; + + // plain text - send immediately + result = ghiTrySendThenBuffer(connection, gsXmlWriterGetData(xml), gsXmlWriterGetDataLength(xml)); + if (result == GHITrySendError) + return GHIPostingError; + result = ghiTrySendThenBuffer(connection, pad, padlen); + if (result == GHITrySendError) + return GHIPostingError; + return GHIPostingDone; + } +} + +static GHIPostingResult ghiPostFileDiskStateDoPosting +( + GHIPostState * state, + GHIConnection * connection +) +{ + char buffer[4096]; + int len; + GHITrySendResult result; + + assert(state->pos >= 0); + assert(state->pos < state->state.fileDisk.len); + assert(state->pos == (int)ftell(state->state.fileDisk.file)); + + // Loop while data is being sent. + ///////////////////////////////// + do + { + // Read some data from the file. + //////////////////////////////// + len = (int)fread(buffer, 1, sizeof(buffer), state->state.fileDisk.file); + if(len <= 0) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPFileReadFailed; + return GHIPostingError; + } + + // Update our position. + /////////////////////// + state->pos += len; + + // Check for too much. + ////////////////////// + if(state->pos > state->state.fileDisk.len) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPFileReadFailed; + return GHIPostingError; + } + + // Send. + //////// + result = ghiTrySendThenBuffer(connection, buffer, len); + if(result == GHITrySendError) + return GHIPostingError; + + // Check if we've handled everything. + ///////////////////////////////////// + if(state->pos == state->state.fileDisk.len) + { + // when using a DIME, we have to pad to multiple of 4 + if (connection->post->useDIME) + { + char pad[3] = { '\0', '\0', '\0' }; + int padlen = 4-state->state.fileDisk.len%4; + if (padlen != 4 && padlen > 0) + { + if (GHITrySendError == ghiTrySendThenBuffer(connection, pad, padlen)) + return GHIPostingError; + } + } + return GHIPostingDone; + } + } + while(result == GHITrySendSent); + + return GHIPostingPosting; +} + +static GHIPostingResult ghiPostFileMemoryStateDoPosting +( + GHIPostState * state, + GHIConnection * connection +) +{ + int rcode; + int len; + + assert(state->pos >= 0); + + // Is this an empty file? + ///////////////////////// + if(state->data->data.fileMemory.len == 0) + return GHIPostingDone; + + assert(state->pos < state->data->data.fileMemory.len); + + // Send what we can. + //////////////////// + if (connection->encryptor.mEngine == GHTTPEncryptionEngine_None) + { + // Plain text: Send directly from memory + do + { + len = (state->data->data.fileMemory.len - state->pos); + rcode = ghiDoSend(connection, state->data->data.fileMemory.buffer + state->pos, len); + if(gsiSocketIsError(rcode)) + return GHIPostingError; + + // Update the pos. + ////////////////// + state->pos += rcode; + + // Did we send it all? + ////////////////////// + if(state->data->data.fileMemory.len == state->pos) + { + // when using a DIME, we have to pad to multiple of 4 + if (connection->post->useDIME) + { + char pad[3] = { '\0', '\0', '\0' }; + int padlen = 4-state->data->data.fileMemory.len%4; + if (padlen != 4 && padlen > 0) + { + if (GHITrySendError == ghiTrySendThenBuffer(connection, pad, padlen)) + return GHIPostingError; + } + } + return GHIPostingDone; + } + } + while(rcode); + return GHIPostingPosting; // (rcode == 0) ? + } + else + { + // Encrypted: can't avoid the copy due to encryption+MAC + GHITrySendResult result; + do + { + len = (state->data->data.fileMemory.len - state->pos); + len = min(len, GS_SSL_MAX_CONTENTLENGTH); + result = ghiTrySendThenBuffer(connection, state->data->data.fileMemory.buffer + state->pos, len); + if (result == GHITrySendError) + return GHIPostingError; + + // Update the pos. + ////////////////// + state->pos += len; + + // Did we send it all? + ////////////////////// + if(state->data->data.fileMemory.len == state->pos) + { + // when using a DIME, we have to pad to multiple of 4 + if (connection->post->useDIME) + { + char pad[3] = { '\0', '\0', '\0' }; + int padlen = 4-state->data->data.fileMemory.len%4; + if (padlen != 4 && padlen > 0) + { + if (GHITrySendError == ghiTrySendThenBuffer(connection, pad, padlen)) + return GHIPostingError; + } + } + return GHIPostingDone; + } + } + while(result == GHITrySendSent); + return GHIPostingPosting; + } +} + +static GHIPostingResult ghiPostStateDoPosting +( + GHIPostState * state, + GHIConnection * connection, + GHTTPBool first, + GHTTPBool last +) +{ + int len = 0; + GHITrySendResult result; + + // Check for sending the header. + //////////////////////////////// + if(state->pos == -1) + { + char buffer[2048]; + + // Bump up the position so we only send the header once. + //////////////////////////////////////////////////////// + state->pos = 0; + + // Check if this is a simple post. + ////////////////////////////////// + if(!connection->post->hasFiles && !connection->post->hasSoap) + { + // Simple post only supports strings. + ///////////////////////////////////// + assert(state->data->type == GHIString); + + // Format the header. + ///////////////////// + if(first) + sprintf(buffer, "%s=", state->data->name); + else + sprintf(buffer, "&%s=", state->data->name); + } + else + { + // Format the header based on string or file. + ///////////////////////////////////////////// + if(state->data->type == GHIString) + { + sprintf(buffer, + "%s" + "Content-Disposition: form-data; " + "name=\"%s\"" CRLF + CRLF, + first?GHI_MULTIPART_BOUNDARY_FIRST:GHI_MULTIPART_BOUNDARY_NORMAL, + state->data->name); + } + else if(state->data->type == GHIXmlData) + { + if (connection->post->useDIME) + { + // use DIME header + // Copy from a temp struct to circumvent alignment issues + int writePos = 0; + int padBytes = 0; + GHIDimeHeader header; + + header.mVersionAndFlags = GHI_DIME_VERSION; + if (first) + header.mVersionAndFlags |= GHI_DIMEFLAG_FIRSTRECORD; + if (last) + header.mVersionAndFlags |= GHI_DIMEFLAG_LASTRECORD; + header.mTypeT = GHI_DIMETYPE_T_URI; + header.mOptionsLength = 0; + header.mIdLength = htons((short)strlen(GHI_DIME_SOAPID)); + header.mTypeLength = htons((short)strlen(GHI_DIME_SOAPTYPE)); + header.mDataLength = htonl(gsXmlWriterGetDataLength(state->data->data.xml.xml)); + + memcpy(&buffer[writePos], &header, sizeof(GHIDimeHeader)); + writePos += sizeof(GHIDimeHeader); + + // id + strcpy(&buffer[writePos], GHI_DIME_SOAPID); + writePos += strlen(GHI_DIME_SOAPID); + padBytes = (int)(4-strlen(GHI_DIME_SOAPID)%4); + if (padBytes != 4) + { + while(padBytes-- > 0) + buffer[writePos++] = '\0'; + } + + // type + strcpy(&buffer[writePos], GHI_DIME_SOAPTYPE); + writePos += strlen(GHI_DIME_SOAPTYPE); + padBytes = (int)(4-strlen(GHI_DIME_SOAPTYPE)%4); + if (padBytes != 4) + { + while(padBytes-- > 0) + buffer[writePos++] = '\0'; + } + + len = writePos; + } + else + buffer[0] = '\0'; + } + else if((state->data->type == GHIFileDisk) || (state->data->type == GHIFileMemory)) + { + const char * filename; + const char * contentType; + int filelen; + + if(state->data->type == GHIFileDisk) + { + filelen = state->state.fileDisk.len; + filename = state->data->data.fileDisk.reportFilename; + contentType = state->data->data.fileDisk.contentType; + } + else + { + filelen = state->data->data.fileMemory.len; + filename = state->data->data.fileMemory.reportFilename; + contentType = state->data->data.fileMemory.contentType; + } + + if (connection->post->useDIME) + { + // use DIME header + // Copy from a temp struct to circumvent alignment issues + int writePos = 0; + int padBytes = 0; + GHIDimeHeader header; + + header.mVersionAndFlags = GHI_DIME_VERSION; + if (first) + header.mVersionAndFlags |= GHI_DIMEFLAG_FIRSTRECORD; + if (last) + header.mVersionAndFlags |= GHI_DIMEFLAG_LASTRECORD; + header.mTypeT = GHI_DIMETYPE_T_MEDIA; + header.mOptionsLength = 0; + header.mIdLength = htons((short)strlen(state->data->name)); + header.mTypeLength = htons((short)strlen(contentType)); + header.mDataLength = htonl(filelen); + + memcpy(&buffer[writePos], &header, sizeof(GHIDimeHeader)); + writePos += sizeof(GHIDimeHeader); + + // id + strcpy(&buffer[writePos], state->data->name); + writePos += strlen(state->data->name); + padBytes = (int)(4-strlen(state->data->name)%4); + if (padBytes != 4) + { + while(padBytes-- > 0) + buffer[writePos++] = '\0'; + } + + // type + strcpy(&buffer[writePos], contentType); + writePos += strlen(contentType); + padBytes = (int)(4-strlen(contentType)%4); + if (padBytes != 4) + { + while(padBytes-- > 0) + buffer[writePos++] = '\0'; + } + + len = writePos; + } + else + { + // use MIME header + sprintf(buffer, + "%s" + "Content-Disposition: form-data; " + "name=\"%s\"; " + "filename=\"%s\"" CRLF + "Content-Type: %s" CRLF CRLF, + first?GHI_MULTIPART_BOUNDARY_FIRST:GHI_MULTIPART_BOUNDARY_NORMAL, + state->data->name, + filename, + contentType); + } + } + else + { + assert(0); + } + } + + // SSL: encrypt and send + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None && + connection->encryptor.mEncryptOnBuffer == GHTTPTrue) + { + if (len == 0) + len = (int)strlen(buffer); + if (GHTTPFalse == ghiEncryptDataToBuffer(&connection->sendBuffer, buffer, len)) + return GHIPostingError; + if (GHTTPFalse == ghiSendBufferedData(connection)) + return GHIPostingError; + + // any data remaining? + if (connection->sendBuffer.pos < connection->sendBuffer.len) + return GHIPostingPosting; + + // We sent everything, reset the send buffer to conserve space + ghiResetBuffer(&connection->sendBuffer); + } + // If sending plain text, send right away + else + { + // Try sending. (the one-time header) + ///////////////////////////////////// + if (len == 0) + len = (int)strlen(buffer); + result = ghiTrySendThenBuffer(connection, buffer, len); + if(result == GHITrySendError) + return GHIPostingError; + + // If it was buffered, don't try anymore. + ///////////////////////////////////////// + if(result == GHITrySendBuffered) + return GHIPostingPosting; + + // We sent everything, reset the send buffer to conserve space + ghiResetBuffer(&connection->sendBuffer); + } + } + + // Post based on type. + ////////////////////// + if(state->data->type == GHIString) + return ghiPostStringStateDoPosting(state, connection); + + if(state->data->type == GHIXmlData) + return ghiPostXmlStateDoPosting(state, connection); + + if(state->data->type == GHIFileDisk) + return ghiPostFileDiskStateDoPosting(state, connection); + + assert(state->data->type == GHIFileMemory); + return ghiPostFileMemoryStateDoPosting(state, connection); +} + +GHIPostingResult ghiPostDoPosting +( + struct GHIConnection * connection +) +{ + GHIPostingResult postingResult; + GHITrySendResult trySendResult; + GHIPostingState * postingState; + GHIPostState * postState; + int len; + + assert(connection); + assert(connection->post); + assert(connection->postingState.states); + assert(ArrayLength(connection->post->data) == ArrayLength(connection->postingState.states)); + assert(connection->postingState.index >= 0); + assert(connection->postingState.index <= ArrayLength(connection->postingState.states)); + + // Cache some stuff. + //////////////////// + postingState = &connection->postingState; + len = ArrayLength(postingState->states); + + // Check for buffered data. + /////////////////////////// + if(connection->sendBuffer.pos < connection->sendBuffer.len) + { + // Send the buffered data. + ////////////////////////// + if(!ghiSendBufferedData(connection)) + return GHIPostingError; + + // Check if we couldn't send it all. + //////////////////////////////////// + if(connection->sendBuffer.pos < connection->sendBuffer.len) + return GHIPostingPosting; + + // We sent it all, so reset the buffer. + /////////////////////////////////////// + ghiResetBuffer(&connection->sendBuffer); + + // If uploading a DIME attachment, wait for HTTP continue. + ////////////////////////////////////////////////////////// + if (connection->postingState.waitPostContinue) + return GHIPostingWaitForContinue; + + // Was that all that's left? + //////////////////////////// + if(connection->postingState.index == len) + return GHIPostingDone; + } + + // When posting soap and DIME attachments, we should terminate the + // header and wait for a response. This will either be a continue or + // a server error. + if (connection->postingState.waitPostContinue) + { + if (connection->post->hasFiles || connection->post->hasSoap) + { + // terminate the header and wait for a response + GS_ASSERT(connection->encodeBuffer.len == 0); + trySendResult = ghiTrySendThenBuffer(connection, CRLF, (int)strlen(CRLF)); + if(trySendResult == GHITrySendError) + return GHIPostingError; + else if (trySendResult == GHITrySendBuffered) + return GHIPostingPosting; + else + { + if (connection->postingState.waitPostContinue == GHTTPTrue) + return GHIPostingWaitForContinue; + //else + // fall through + } + } + else + { + // simple posts don't have to wait + connection->postingState.waitPostContinue = GHTTPFalse; + // fall through + } + } + + // Loop while there's data to upload. + ///////////////////////////////////// + while(postingState->index < len) + { + // Get the current data state. + ////////////////////////////// + postState = (GHIPostState *)ArrayNth(postingState->states, postingState->index); + assert(postState); + + // Upload the current data. + /////////////////////////// + postingResult = ghiPostStateDoPosting(postState, connection, + (postingState->index == 0)?GHTTPTrue:GHTTPFalse, + (postingState->index == (ArrayLength(postingState->states)-1))?GHTTPTrue:GHTTPFalse); + + // Check for error. + /////////////////// + if(postingResult == GHIPostingError) + { + // Make sure we already set the error stuff. + //////////////////////////////////////////// + assert(connection->completed && connection->result); + + return GHIPostingError; + } + + // Check for still posting. + /////////////////////////// + if(postingResult == GHIPostingPosting) + return GHIPostingPosting; + + // One more done. + ///////////////// + postingState->index++; + } + + // Encrypt and send anything left in the encode buffer + // -- for example, when posting string data we don't encrypt until we have the entire string (for efficiency only) + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None) + { + if (connection->encodeBuffer.len > 0) + { + GS_ASSERT(connection->encodeBuffer.pos == 0); // if you hit this, it means you forgot the clear the buffer + if (GHTTPFalse == ghiEncryptDataToBuffer(&connection->sendBuffer, + connection->encodeBuffer.data, connection->encodeBuffer.len)) + { + return GHIPostingError; + } + ghiResetBuffer(&connection->encodeBuffer); + } + } + + // Send or buffer the end marker. + ///////////////////////////////// + if(connection->post->hasFiles && !connection->post->useDIME) + { + GS_ASSERT(!connection->post->hasSoap); + + // send MIME boundary end + trySendResult = ghiTrySendThenBuffer(connection, GHI_MULTIPART_BOUNDARY_END, (int)strlen(GHI_MULTIPART_BOUNDARY_END)); + if(trySendResult == GHITrySendError) + return GHIPostingError; + } + + // We're not done if there's stuff in the buffer. + ///////////////////////////////////////////////// + if(connection->sendBuffer.pos < connection->sendBuffer.len) + return GHIPostingPosting; + + return GHIPostingDone; +} diff --git a/code/gamespy/ghttp/ghttpPost.h b/code/gamespy/ghttp/ghttpPost.h new file mode 100644 index 00000000..1fab2a83 --- /dev/null +++ b/code/gamespy/ghttp/ghttpPost.h @@ -0,0 +1,126 @@ + /* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GHTTPPOST_H_ +#define _GHTTPPOST_H_ + +#include "ghttp.h" +#include "ghttpBuffer.h" +#include "../darray.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum +{ + GHIPostingError, + GHIPostingDone, + GHIPostingPosting, + GHIPostingWaitForContinue +} GHIPostingResult; + +typedef struct GHIPostingState +{ + DArray states; + int index; + int bytesPosted; + int totalBytes; + ghttpPostCallback callback; + void * param; + GHTTPBool waitPostContinue; // does DIME need to wait for continue? + GHTTPBool completed; // prevent re-post in the event of a redirect. +} GHIPostingState; + +GHTTPPost ghiNewPost +( + void +); + +void ghiPostSetAutoFree +( + GHTTPPost post, + GHTTPBool autoFree +); + +GHTTPBool ghiIsPostAutoFree +( + GHTTPPost post +); + +void ghiFreePost +( + GHTTPPost post +); + +GHTTPBool ghiPostAddString +( + GHTTPPost post, + const char * name, + const char * string +); + +GHTTPBool ghiPostAddFileFromDisk +( + GHTTPPost post, + const char * name, + const char * filename, + const char * reportFilename, + const char * contentType +); + +GHTTPBool ghiPostAddFileFromMemory +( + GHTTPPost post, + const char * name, + const char * buffer, + int bufferLen, + const char * reportFilename, + const char * contentType +); + +GHTTPBool ghiPostAddXml +( + GHTTPPost post, + GSXmlStreamWriter xmlSoap +); + +void ghiPostSetCallback +( + GHTTPPost post, + ghttpPostCallback callback, + void * param +); + +const char * ghiPostGetContentType +( + struct GHIConnection * connection +); + +GHTTPBool ghiPostInitState +( + struct GHIConnection * connection +); + +void ghiPostCleanupState +( + struct GHIConnection * connection +); + +GHIPostingResult ghiPostDoPosting +( + struct GHIConnection * connection +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/ghttp/ghttpProcess.c b/code/gamespy/ghttp/ghttpProcess.c new file mode 100644 index 00000000..2be268a3 --- /dev/null +++ b/code/gamespy/ghttp/ghttpProcess.c @@ -0,0 +1,1740 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "ghttpProcess.h" +#include "ghttpCallbacks.h" +#include "ghttpPost.h" +#include "ghttpMain.h" +#include "ghttpCommon.h" + +// Parse the URL into: +// server address (and IP) +// server port +// request path. +///////////////////////////// +static GHTTPBool ghiParseURL +( + GHIConnection * connection +) +{ + char * URL; + int nIndex; + char tempChar; + char * str; + + assert(connection); + if(!connection) + return GHTTPFalse; + + // 2002.Apr.18.JED - Make sure we have an URL + ///////////////////////////////////////////// + assert(connection->URL); + if(!connection->URL) + return GHTTPFalse; + + URL = connection->URL; + + // Check for "http://". + ////////////////////// + if(strncmp(URL, "http://", 7) == 0) + { + connection->protocol = GHIHttp; + URL += 7; + } + else if (strncmp(URL, "https://", 8) == 0) + { + connection->protocol = GHIHttps; + URL += 8; + } + else + { + return GHTTPFalse; + } + + // Read the address. + //////////////////// + nIndex = (int)strcspn(URL, ":/"); + tempChar = URL[nIndex]; + URL[nIndex] = '\0'; + connection->serverAddress = goastrdup(URL); + if(!connection->serverAddress) + return GHTTPFalse; + URL[nIndex] = tempChar; + URL += nIndex; + + // Read the port. + ///////////////// + if(*URL == ':') + { + URL++; + connection->serverPort = (unsigned short)atoi(URL); + if(!connection->serverPort) + return GHTTPFalse; + do + { + URL++; + }while(*URL && (*URL != '/')); + } + else + { + if (connection->protocol == GHIHttps) + connection->serverPort = GHI_DEFAULT_SECURE_PORT; + else + connection->serverPort = GHI_DEFAULT_PORT; + } + + // Read the path. + ///////////////// + if(!*URL) + URL = "/"; + connection->requestPath = goastrdup(URL); + while((str = strchr(connection->requestPath, ' ')) != NULL) + *str = '+'; + if(!connection->requestPath) + return GHTTPFalse; + + return GHTTPTrue; +} + +/**************** +** SOCKET INIT ** +****************/ +void ghiDoSocketInit +( + GHIConnection * connection +) +{ + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Socket Initialization\n"); + + // Progress. + //////////// + ghiCallProgressCallback(connection, NULL, 0); + + // Init sockets. + //////////////// + SocketStartUp(); + + // Parse the URL. + ///////////////// + if(!ghiParseURL(connection)) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPParseURLFailed; + return; + } + + // Check if an encryption type was set. + /////////////////////////////////////// + if((connection->protocol == GHIHttps) && (connection->encryptor.mEngine == GHTTPEncryptionEngine_None)) + { + // default to gamespy engine + //ghttpSetRequestEncryptionEngine(connection->request, GHTTPEncryptionEngine_GameSpy); + + // 02OCT07 BED: Design changed so that only one engine can be active at a time + // Use the active engine rather than GameSpy + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Encryption engine not set for HTTPS. Using default engine\r\n"); + ghttpSetRequestEncryptionEngine(connection->request, GHTTPEncryptionEngine_Default); + } + else if ((connection->protocol != GHIHttps) && (connection->encryptor.mEngine != GHTTPEncryptionEngine_None)) + { + // URL is not secured + ghttpSetRequestEncryptionEngine(connection->request, GHTTPEncryptionEngine_None); + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Encryption engine set for unsecured URL. Removing encryption.\r\n"); + } + + // Init the encryption engine. + ////////////////////////////// + if ((connection->protocol == GHIHttps) && connection->encryptor.mInitialized == GHTTPFalse) + { + GHIEncryptionResult aResult; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Debug, "Initializing SSL engine\n"); + aResult = (connection->encryptor.mInitFunc)(connection, &connection->encryptor); + if (aResult == GHIEncryptionResult_Error) + { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_WarmError, "Failed to initialize SSL engine\n"); + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + return; + } + } + + // Progress. + //////////// + connection->state = GHTTPHostLookup; + ghiCallProgressCallback(connection, NULL, 0); +} + +/**************** +** HOST LOOKUP ** +****************/ +void ghiDoHostLookup +( + GHIConnection * connection +) +{ + HOSTENT * host = NULL; + const char * server = NULL; + +#if !defined(GSI_NO_THREADS) + // Check to see if asynch lookup is taking place + //////////////////////////////////////////////// + if (connection->handle) + { + GSI_UNUSED(host); + GSI_UNUSED(server); + + // Lookup incomplete - set to lookupPending state + ///////////////////////////////////////////////// + connection->state = GHTTPLookupPending; + ghiCallProgressCallback(connection, NULL, 0); + return; + } +#endif + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Host Lookup\n"); + + + // Check for using a proxy. + /////////////////////////// + if (connection->proxyOverrideServer) // request specific proxy + server = connection->proxyOverrideServer; + else if(ghiProxyAddress) + server = ghiProxyAddress; + else + server = connection->serverAddress; + + // Try resolving the address as an IP a.b.c.d number. + ///////////////////////////////////////////////////// + connection->serverIP = inet_addr(server); + if(connection->serverIP == INADDR_NONE) + { + // Try resolving with DNS - asynchronously if possible + ////////////////////////// + +#if defined(GSI_NO_THREADS) + //blocking version - no threads + host = gethostbyname(server); + + if(host == NULL) + { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_HotError, + "Host Lookup failed\n"); + connection->completed = GHTTPTrue; + connection->result = GHTTPHostLookupFailed; + return; + } + + // Get the IP. + ////////////// + connection->serverIP = *(unsigned int *)host->h_addr_list[0]; +#else + + //threaded version + if (gsiStartResolvingHostname(server, &(connection->handle)) == -1) + { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_HotError, + "Thread Creation Failed\n"); + + //make sure to set it back to NULL + connection->handle = NULL; + + //exit with Host Lookup Failed error message + connection->completed = GHTTPTrue; + connection->result = GHTTPHostLookupFailed; + return; + } + else + { + //thread created properly - continue into lookupPending state + GSI_UNUSED(host); + } +#endif + } + + // Progress. + //////////// + + //check to see if lookup is complete + if (connection->serverIP == INADDR_NONE) + { + //lookup incomplete - set to lookupPending state + connection->state = GHTTPLookupPending; + ghiCallProgressCallback(connection, NULL, 0); + } + else + { + //lookup complete - proceed with connection stage + connection->state = GHTTPConnecting; + ghiCallProgressCallback(connection, NULL, 0); + } +} + +/****************** +** LOOKUP PENDING** +******************/ +void ghiDoLookupPending +( + GHIConnection * connection +) +{ +#if !defined(GSI_NO_THREADS) + //check if lookup is complete + connection->serverIP = gsiGetResolvedIP(connection->handle); + + //make sure there were no problems with the IP + if (connection->serverIP == GSI_ERROR_RESOLVING_HOSTNAME) + { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_HotError, + "Error resolving hostname\n"); + + //set to NULL + connection->handle = NULL; + + //notify that the lookup failed + connection->completed = GHTTPTrue; + connection->result = GHTTPHostLookupFailed; + return; + } + + if (connection->serverIP == GSI_STILL_RESOLVING_HOSTNAME) + { + //lookup incomplete - keep calling this function + connection->state = GHTTPLookupPending; + ghiCallProgressCallback(connection, NULL, 0); + } + else + { + //set to NULL + connection->handle = NULL; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "DNS lookup complete\n"); + //looks like we got ourselves a server! proceed with connection phase + connection->state = GHTTPConnecting; + ghiCallProgressCallback(connection, NULL, 0); + } +#endif +} + +/*************** +** CONNECTING ** +***************/ +void ghiDoConnecting +( + GHIConnection * connection +) +{ + int rcode; + SOCKADDR_IN address; + int writeFlag; + int exceptFlag; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Connecting\n"); + + // If we don't have a socket yet, set it up. + //////////////////////////////////////////// + if(connection->socket == INVALID_SOCKET) + { + // Create the socket. + ///////////////////// + connection->socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(connection->socket == INVALID_SOCKET) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPSocketFailed; + connection->socketError = GOAGetLastError(connection->socket); + return; + } + + // Set the socket to non-blocking. + ////////////////////////////////// + if(!SetSockBlocking(connection->socket, 0)) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPSocketFailed; + connection->socketError = GOAGetLastError(connection->socket); + return; + } + + // If throttling, use a small receive buffer. + ///////////////////////////////////////////// + if(connection->throttle) + SetReceiveBufferSize(connection->socket, ghiThrottleBufferSize); + + // Setup the server address. + //////////////////////////// + memset(&address, 0, sizeof(SOCKADDR_IN)); + address.sin_family = AF_INET; + if (connection->proxyOverrideServer) + address.sin_port = htons(connection->proxyOverridePort); + else if(ghiProxyAddress) + address.sin_port = htons(ghiProxyPort); + else + address.sin_port = htons(connection->serverPort); + address.sin_addr.s_addr = connection->serverIP; + + // Start the connect. + ///////////////////// + rcode = connect(connection->socket, (SOCKADDR *)&address, sizeof(address)); + if(gsiSocketIsError(rcode)) + { + int socketError = GOAGetLastError(connection->socket); + if((socketError != WSAEWOULDBLOCK) && (socketError != WSAEINPROGRESS) && (socketError != WSAETIMEDOUT)) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPConnectFailed; + connection->socketError = socketError; + return; + } + } + } + + // Check if the connect has completed. + ////////////////////////////////////// + rcode = GSISocketSelect(connection->socket, NULL, &writeFlag, &exceptFlag); + if((gsiSocketIsError(rcode)) || ((rcode == 1) && exceptFlag)) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPConnectFailed; + if(gsiSocketIsError(rcode)) + connection->socketError = GOAGetLastError(connection->socket); + else + connection->socketError = 0; + return; + } + + // Check if we're connected. + //////////////////////////// + if((rcode == 1) && writeFlag) + { + // Progress. + //////////// + if (connection->encryptor.mEngine == GHTTPEncryptionEngine_None) + connection->state = GHTTPSendingRequest; + else + connection->state = GHTTPSecuringSession; + ghiCallProgressCallback(connection, NULL, 0); + } +} + +/****************** +** SSL HANDSHAKE ** +*******************/ +void ghiDoSecuringSession +( + GHIConnection * connection +) +{ + // Client sends hello + // Server sends hello, [certificate], [certificate request], [server key exchange] + // Client sends client , , [certificate], [certificate verify] + // Server sends finished + + // skip the ghiDoSecuringSession step... + // - when not using encryption or + // - if the connection is already secure + + GHIRecvResult result; + + // This buffer must be large enough to receive any handshake messages. + char buffer[1025]; + int bufferLen; + + // Start the handshake process + if (connection->encryptor.mSessionStarted == GHTTPFalse) + { + GHIEncryptionResult aResult; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Securing Session\n"); + + GS_ASSERT(connection->encryptor.mStartFunc != NULL); + if (connection->encryptor.mStartFunc != NULL) + { + aResult = (connection->encryptor.mStartFunc)(connection, &connection->encryptor); + if (aResult == GHIEncryptionResult_Error) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + return; + } + } + + // Check for session established + if (connection->encryptor.mSessionEstablished) + { + connection->state = GHTTPSendingRequest; + ghiCallProgressCallback(connection, NULL, 0); + return; + } + } + + // if the SSL lib controls the handshake, just keep calling + // start until the session has been established + GS_ASSERT(connection->encryptor.mSessionEstablished == GHTTPFalse); + if (connection->encryptor.mLibSendsHandshakeMessages) + { + GS_ASSERT(connection->encryptor.mStartFunc != NULL); + if (connection->encryptor.mStartFunc != NULL) + { + GHIEncryptionResult aResult = (connection->encryptor.mStartFunc)(connection, &connection->encryptor); + if (aResult == GHIEncryptionResult_Error) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + return; + } + } + + // Check for session established + if (connection->encryptor.mSessionEstablished) + { + connection->state = GHTTPSendingRequest; + ghiCallProgressCallback(connection, NULL, 0); + } + } + else + { + // Continue to send and receive handshake messages until the session has been secured + // Send any session messages + if (connection->sendBuffer.pos < connection->sendBuffer.len) + { + if (!ghiSendBufferedData(connection)) + return; // Todo: handle error? + + // Check for data still buffered. + ///////////////////////////////// + if(connection->sendBuffer.pos < connection->sendBuffer.len) + return; + + ghiResetBuffer(&connection->sendBuffer); + } + + // Get data + bufferLen = sizeof(buffer); + result = ghiDoReceive(connection, buffer, &bufferLen); + + // Handle error or conn closed. + /////////////////////////////// + if((result == GHIError) || (result == GHIConnClosed)) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + return; + } + + // check for received data + if(result == GHIRecvData) + { + // Append new encrypted data to anything we've held over + // We have to do this because we can't decrypt partial SSL messages + if (!ghiAppendDataToBuffer(&connection->decodeBuffer, buffer, bufferLen)) + return; + + // Decrypt as much as we can + if (!ghiDecryptReceivedData(connection)) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + return; + } + + // Check for session established (handshake complete) + if (connection->encryptor.mSessionEstablished) + { + connection->state = GHTTPSendingRequest; + ghiCallProgressCallback(connection, NULL, 0); + return; + } + } + } +} + + +/******************** +** SENDING REQUEST ** +********************/ +void ghiDoSendingRequest +( + GHIConnection * connection +) +{ + char * requestType; + int oldPos; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Sending Request\n"); + + // If we haven't filled the send buffer yet, do that first. + /////////////////////////////////////////////////////////// + if(!connection->sendBuffer.len) + { + // Using a pointer so we can pipe output to a different destination + // (e.g. for efficiency and testing purposes we may want to encrypt in larger blocks) + GHIBuffer* writeBuffer = NULL; + if (connection->encryptor.mEngine == GHTTPEncryptionEngine_None || + connection->encryptor.mEncryptOnBuffer == GHTTPFalse) + { + // write directly to send buffer + writeBuffer = &connection->sendBuffer; + } + else + { + // write to temp buffer so it can be encrypted before sending + writeBuffer = &connection->encodeBuffer; + } + + // Fill in the request line. + //////////////////////////// + if(connection->post && !connection->postingState.completed) + requestType = "POST "; + else if(connection->type == GHIHEAD) + requestType = "HEAD "; + else + requestType = "GET "; + ghiAppendDataToBuffer(writeBuffer, requestType, 0); + if (connection->proxyOverrideServer || ghiProxyAddress) + ghiAppendDataToBuffer(writeBuffer, connection->URL, 0); + else + ghiAppendDataToBuffer(writeBuffer, connection->requestPath, 0); + ghiAppendDataToBuffer(writeBuffer, " HTTP/1.1" CRLF, 0); + + // Add the host header. + /////////////////////// + if(connection->serverPort == GHI_DEFAULT_PORT) + { + ghiAppendHeaderToBuffer(writeBuffer, "Host", connection->serverAddress); + } + else + { + ghiAppendDataToBuffer(writeBuffer, "Host: ", 0); + ghiAppendDataToBuffer(writeBuffer, connection->serverAddress, 0); + ghiAppendCharToBuffer(writeBuffer, ':'); + ghiAppendIntToBuffer(writeBuffer, connection->serverPort); + ghiAppendDataToBuffer(writeBuffer, CRLF, 2); + } + + // Add the user-agent header. + ///////////////////////////// + if (connection->sendHeaders == NULL || strstr(connection->sendHeaders, "User-Agent")==NULL) + ghiAppendHeaderToBuffer(writeBuffer, "User-Agent", "GameSpyHTTP/1.0"); + + // Check for persistant connections. + ////////////////////////////////////// + if (connection->persistConnection) + ghiAppendHeaderToBuffer(writeBuffer, "Connection", "Keep-Alive"); + else + ghiAppendHeaderToBuffer(writeBuffer, "Connection", "close"); + + // Post needs extra headers. + //////////////////////////// + if(connection->post && !connection->postingState.completed) + { + char buf[16]; + + // Add the content-length header. + ///////////////////////////////// + sprintf(buf, "%d", connection->postingState.totalBytes); + ghiAppendHeaderToBuffer(writeBuffer, "Content-Length", buf); + + // Add the content-type header. + /////////////////////////////// + ghiAppendHeaderToBuffer(writeBuffer, "Content-Type", ghiPostGetContentType(connection)); + } + + // Not supported by all servers + //ghiAppendHeaderToBuffer(writeBuffer, "Expect", "100-continue"); + + // Add user-headers. + //////////////////// + if(connection->sendHeaders) + ghiAppendDataToBuffer(writeBuffer, connection->sendHeaders, 0); + + // Add the blank line to finish it off. + /////////////////////////////////////// + ghiAppendDataToBuffer(writeBuffer, CRLF, 2); + + // Encrypt it, if necessary. This copy is unfortunate since matrixSsl can't encrypt in place + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None && + connection->encryptor.mEncryptOnBuffer == GHTTPTrue) + { + GS_ASSERT(writeBuffer == &connection->encodeBuffer); + if (!ghiEncryptDataToBuffer(&connection->sendBuffer, writeBuffer->data, writeBuffer->len)) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + return; + } + ghiResetBuffer(writeBuffer); + } + } + + // Store the old position. + ////////////////////////// + oldPos = connection->sendBuffer.pos; + + // Send what we can. + //////////////////// + if(!ghiSendBufferedData(connection)) + return; + + // Log anything we sent. + //////////////////////// + #ifdef HTTP_LOG + if(connection->sendBuffer.pos != oldPos) + ghiLogRequest(connection->sendBuffer.data + oldPos, connection->sendBuffer.pos - oldPos); + #endif + + // Check for data still buffered. + ///////////////////////////////// + if(connection->sendBuffer.pos < connection->sendBuffer.len) + return; + + // Clear the send buffer. + ///////////////////////// + ghiResetBuffer(&connection->sendBuffer); + + // Finished sending. + //////////////////// + if(connection->post && !connection->postingState.completed) + connection->state = GHTTPPosting; + else + connection->state = GHTTPWaiting; + ghiCallProgressCallback(connection, NULL, 0); + + GSI_UNUSED(oldPos); +} + +/************ +** POSTING ** +************/ +void ghiDoPosting +( + GHIConnection * connection +) +{ + GHIPostingResult result; + int oldBytesPosted; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Posting\n"); + + // Store the old bytes posted. + ////////////////////////////// + oldBytesPosted = connection->postingState.bytesPosted; + + // Do some posting. + /////////////////// + result = ghiPostDoPosting(connection); + + // Check for an error. + ////////////////////// + if(result == GHIPostingError) + { + int rcode = 0; + int readFlag = 0; + + // Make sure we already set the error stuff. + //////////////////////////////////////////// + assert(connection->completed && connection->result); + + // Cleanup the posting state. + ///////////////////////////// + ghiPostCleanupState(connection); + + // Is there a server response? + rcode = GSISocketSelect(connection->socket, &readFlag, NULL, NULL); + if((rcode == 1) && readFlag) + { + // Ready to receive. + //////////////////// + connection->state = GHTTPReceivingStatus; + ghiCallProgressCallback(connection, NULL, 0); + } + return; + } + + // When sending DIME wait for initial + // continue before uploading + ///////////////////////////////////////// + if (result == GHIPostingWaitForContinue) + { + // Disable by skipping the wait + connection->postingState.waitPostContinue = GHTTPFalse; + return; + + //connection->state = GHTTPWaiting; + //return; + } + + // Call the callback if we sent anything. + ///////////////////////////////////////// + if(oldBytesPosted != connection->postingState.bytesPosted) + ghiCallPostCallback(connection); + + // Check for done. + ////////////////// + if(result == GHIPostingDone) + { + // Cleanup the posting state. + ///////////////////////////// + ghiPostCleanupState(connection); + connection->postingState.completed = GHTTPTrue; + + // Set the new connection state. + //////////////////////////////// + connection->state = GHTTPWaiting; + ghiCallProgressCallback(connection, NULL, 0); + + return; + } +} + +/************ +** WAITING ** +************/ +void ghiDoWaiting +( + GHIConnection * connection +) +{ + int readFlag; + int exceptFlag; + int rcode; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Waiting\n"); + + // We're waiting to receive something. + ////////////////////////////////////// + rcode = GSISocketSelect(connection->socket, &readFlag, NULL, &exceptFlag); + if((gsiSocketIsError(rcode)) || ((rcode == 1) && exceptFlag)) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPSocketFailed; + if(gsiSocketIsError(rcode)) + connection->socketError = GOAGetLastError(connection->socket); + else + connection->socketError = 0; + return; + } + + // Check for waiting data. + ////////////////////////// + if((rcode == 1) && readFlag) + { + // Ready to receive. + //////////////////// + connection->state = GHTTPReceivingStatus; + ghiCallProgressCallback(connection, NULL, 0); + } +} + +// Parse the status line. +///////////////////////// +static GHTTPBool ghiParseStatus +( + GHIConnection * connection +) +{ + int majorVersion; + int minorVersion; + int statusCode; + int statusStringIndex; + int rcode; + char c; + + GS_ASSERT(connection); + GS_ASSERT(connection->recvBuffer.len > 0); + +#if defined(_X360) + //Xbox 360 needs "%n" to be manually enabled + { + int oldPrintCountValue = _set_printf_count_output(1); +#endif + + // Parse the string. + //////////////////// + rcode = sscanf(connection->recvBuffer.data, "HTTP/%d.%d %d%n", + &majorVersion, + &minorVersion, + &statusCode, + &statusStringIndex); + +#if defined(_X360) + _set_printf_count_output(oldPrintCountValue); + } +#endif + + // Check what we got. + ///////////////////// + if((rcode != 3) || // Not all fields read. + //!*statusString || // No status string. PANTS|9.16.02 - apparently some servers don't return a status string + (majorVersion < 1) || // Major version is less than 1. + (statusCode < 100) || // 1xx is lowest status code. + (statusCode >= 600)) // 5xx is highest status code. + { + connection->completed = GHTTPTrue; + connection->result = GHTTPBadResponse; + return GHTTPFalse; + } + + // Figure out where the status string starts. + ///////////////////////////////////////////// + while((c = connection->recvBuffer.data[statusStringIndex]) != '\0' && isspace(c)) + statusStringIndex++; + + // Set connection members. + ////////////////////////// + connection->statusMajorVersion = majorVersion; + connection->statusMinorVersion = minorVersion; + connection->statusCode = statusCode; + connection->statusStringIndex = statusStringIndex; + + return GHTTPTrue; +} + +/********************* +** RECEIVING STATUS ** +*********************/ +void ghiDoReceivingStatus +( + GHIConnection * connection +) +{ + char buffer[1024]; + int bufferLen; + GHIRecvResult result; + char * endOfStatus; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Receiving Status\n"); + + // Get data. + //////////// + bufferLen = sizeof(buffer); + result = ghiDoReceive(connection, buffer, &bufferLen); + + // Handle error or no data. + /////////////////////////// + if(result == GHIError) + return; + if(result == GHINoData) + return; + + // Only append data if we got data. + /////////////////////////////////// + if(result == GHIRecvData) + { + // Check for encryption. + //////////////////////// + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None && + connection->encryptor.mEncryptOnBuffer == GHTTPTrue) + { + // Append new encrypted data to anything we've held over + // We have to do this because we can't decrypt partial SSL messages + if (!ghiAppendDataToBuffer(&connection->decodeBuffer, buffer, bufferLen)) + return; + + // Decrypt as much as we can + if (!ghiDecryptReceivedData(connection)) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + return; + } + } + else + { + // Add the data directly to the buffer. + /////////////////////////////////////// + if(!ghiAppendDataToBuffer(&connection->recvBuffer, buffer, bufferLen)) + return; + } + } + + // Check if the status is finished. + ///////////////////////////////////// + endOfStatus = strstr(connection->recvBuffer.data, CRLF); + if(endOfStatus) + { + int statusLength; + + // Cap the status. + ////////////////// + *endOfStatus = '\0'; + + // Get the status length. + ///////////////////////// + statusLength = (endOfStatus - connection->recvBuffer.data); + + // Log it. + ////////// + ghiLogResponse(connection->recvBuffer.data, statusLength); + ghiLogResponse("\n", 1); + + // Parse the status line. + ///////////////////////// + if(!ghiParseStatus(connection)) + return; + + // Store the position of the start of the headers. + ////////////////////////////////////////////////// + connection->headerStringIndex = (statusLength + 2); + + + if (connection->statusCode == 100 && connection->postingState.waitPostContinue) + { + // DIME uploads must wait for initial continue before posting + connection->postingState.waitPostContinue = GHTTPFalse; + ghiResetBuffer(&connection->recvBuffer); // clear the continue + connection->state = GHTTPPosting; + ghiCallProgressCallback(connection, NULL, 0); + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_Comment, + "Got HTTP continue\r\n"); + } + else + { + // We're receiving headers now. + /////////////////////////////// + connection->state = GHTTPReceivingHeaders; + ghiCallProgressCallback(connection, NULL, 0); + } + } + else if(result == GHIConnClosed) + { + // Connection closed. + ///////////////////// + connection->completed = GHTTPTrue; + connection->result = GHTTPBadResponse; + connection->socketError = GOAGetLastError(connection->socket); + return; + } +} + +// Delivers incoming file data to the appropriate place, +// then calls the progress callback. +// For GetFile, adds to buffer. +// For SaveFile, writes to disk. +// For StreamFile, does nothing. +// Returns false on error. +//////////////////////////////////////////////////////// +static GHTTPBool ghiDeliverIncomingFileData +( + GHIConnection * connection, + char * data, + int len +) +{ + char * buffer = NULL; + int bufferLen = 0; + + // Add this to the total. + ///////////////////////// + connection->fileBytesReceived += len; + + // Do we have the whole thing? + ////////////////////////////// + if(connection->fileBytesReceived == connection->totalSize || connection->connectionClosed) + connection->completed = GHTTPTrue; + + // Handle based on type. + //////////////////////// + if(connection->type == GHIGET) + { + // Put this in the buffer. + ////////////////////////// + if(!ghiAppendDataToBuffer(&connection->getFileBuffer, data, len)) + return GHTTPFalse; + + // Set the callback parameters. + /////////////////////////////// + buffer = connection->getFileBuffer.data; + bufferLen = connection->getFileBuffer.len; + } + else if(connection->type == GHISAVE) + { + int bytesWritten = 0; +#ifndef NOFILE + bytesWritten = fwrite(data, 1, len, connection->saveFile); +#endif + if(bytesWritten != len) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPFileWriteFailed; + return GHTTPFalse; + } + + // Set the callback parameters. + /////////////////////////////// + buffer = data; + bufferLen = len; + } + else if(connection->type == GHISTREAM) + { + // Set the callback parameters. + /////////////////////////////// + buffer = data; + bufferLen = len; + } + + // Call the callback. + ///////////////////// + ghiCallProgressCallback(connection, buffer, bufferLen); + + return GHTTPTrue; +} + +// Gets the size of a chunk from a chunk header. +// Returns -1 on error. +//////////////////////////////////////////////// +static int ghiParseChunkSize +( + GHIConnection * connection +) +{ + char * header; + int len; + int num; + int rcode; + + header = connection->chunkHeader; + len = connection->chunkHeaderLen; + + assert(len); + GSI_UNUSED(len); + + rcode = sscanf(header, "%x", &num); + if(rcode != 1) + return -1; + + return num; +} + +// Appends the data to the chunk header buffer. +/////////////////////////////////////////////// +static void ghiAppendToChunkHeaderBuffer +( + GHIConnection * connection, + char * data, + int len +) +{ + assert(connection); + assert(data); + assert(len >= 0); + + // This can happen at the end of a header. + ////////////////////////////////////////// + if(len == 0) + return; + + // Is there room in the buffer? If not, just + // skip, we most likely already have the chunk size. + //////////////////////////////////////////////////// + if(connection->chunkHeaderLen < CHUNK_HEADER_SIZE) + { + int numBytes; + + // How many bytes are we copying? + ///////////////////////////////// + numBytes = min(CHUNK_HEADER_SIZE - connection->chunkHeaderLen, len); + + // Move the (possibly partial) header into the buffer. + ////////////////////////////////////////////////////// + memcpy(connection->chunkHeader + connection->chunkHeaderLen, data, (unsigned int)numBytes); + + // Cap off the buffer. + ////////////////////// + connection->chunkHeaderLen += numBytes; + connection->chunkHeader[connection->chunkHeaderLen] = '\0'; + } +} + +// Does any neccessary processing to incoming file data +// before it gets delivered. This includes un-chunking. +// Returns false on error. +//////////////////////////////////////////////////////// +static GHTTPBool ghiProcessIncomingFileData +( + GHIConnection * connection, + char * data, + int len +) +{ + assert(connection); + assert(data); + assert(len > 0); + + // Is this a chunked transfer? + ////////////////////////////// + if(connection->chunkedTransfer) + { + // Loop while there's stuff to process. + /////////////////////////////////////// + while(len > 0) + { + // Reading a header? + //////////////////// + if(connection->chunkReadingState == CRHeader) + { + char * endOfHeader; + + // Have we hit the LF (as in the CRLF ending the header)? + ///////////////////////////////////////////////////////// + endOfHeader = strchr(data, 0xA); + if(endOfHeader) + { + // Append what we have to the buffer. + ///////////////////////////////////// + ghiAppendToChunkHeaderBuffer(connection, data, endOfHeader - data); + + // Adjust data and len. + /////////////////////// + endOfHeader++; + len -= (endOfHeader - data); + data = endOfHeader; + + // Read the chunk size. + /////////////////////// + connection->chunkBytesLeft = ghiParseChunkSize(connection); + if(connection->chunkBytesLeft == -1) + { + // There was an error reading the chunk size. + ///////////////////////////////////////////// + connection->completed = GHTTPTrue; + connection->result = GHTTPBadResponse; + return GHTTPFalse; + } + + // Set the chunk reading state. + /////////////////////////////// + if(connection->chunkBytesLeft == 0) + { + connection->chunkReadingState = CRFooter; + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_RawDump, + "Reading footer\n"); + } + else + { + connection->chunkReadingState = CRChunk; + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_RawDump, + "Reading %d byte chunk\n", connection->chunkBytesLeft); + } + } + else + { + // Move it all into the buffer. + /////////////////////////////// + ghiAppendToChunkHeaderBuffer(connection, data, len); + + // Nothing else we can do now. + ////////////////////////////// + return GHTTPTrue; + } + } + // Reading a chunk? + /////////////////// + else if(connection->chunkReadingState == CRChunk) + { + int numBytes; + + // How many bytes of data are we dealing with? + ////////////////////////////////////////////// + numBytes = min(connection->chunkBytesLeft, len); + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_RawDump, + "Read %d bytes of chunk\n", numBytes); + + // Deliver the bytes. + ///////////////////// + if(!ghiDeliverIncomingFileData(connection, data, numBytes)) + return GHTTPFalse; + + // Adjust data and len. + /////////////////////// + data += numBytes; + len -= numBytes; + + // Figure out how many bytes left in chunk. + /////////////////////////////////////////// + connection->chunkBytesLeft -= numBytes; + + // Did we finish the chunk? + /////////////////////////// + if(connection->chunkBytesLeft == 0) + connection->chunkReadingState = CRCRLF; + } + // Reading a chunk footer (CRLF)? + ///////////////////////////////// + else if(connection->chunkReadingState == CRCRLF) + { + char * endOfFooter; + + // Did we get an LF? + //////////////////// + endOfFooter = strchr(data, 0xA); + + // The footer hasn't ended yet. + /////////////////////////////// + if(!endOfFooter) + return GHTTPTrue; + + // Adjust data and len. + /////////////////////// + endOfFooter++; + len -= (endOfFooter - data); + data = endOfFooter; + + // Set up for reading the next header. + ////////////////////////////////////// + connection->chunkHeader[0] = '\0'; + connection->chunkHeaderLen = 0; + connection->chunkBytesLeft = 0; + connection->chunkReadingState = CRHeader; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_RawDump, + "Read chunk footer\n"); + } + // Reading the footer? + ////////////////////// + else if(connection->chunkReadingState == CRFooter) + { + // We're done. + ////////////// + connection->completed = GHTTPTrue; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_RawDump, + "Finished reading chunks\n"); + + return GHTTPTrue; + } + // Bad state! + ///////////// + else + { + assert(0); + return GHTTPFalse; + } + } + + return GHTTPTrue; + } + + // Regular transfer, just deliver it. + ///////////////////////////////////// + return ghiDeliverIncomingFileData(connection, data, len); +} + +/********************** +** RECEIVING HEADERS ** +**********************/ +void ghiDoReceivingHeaders +( + GHIConnection * connection +) +{ + char buffer[4096]; + int bufferLen; + GHIRecvResult result; + GHTTPBool hasHeaders = GHTTPTrue; + char * headers; + char * endOfHeaders = NULL; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Receiving Headers\n"); + + // Get data. + //////////// + bufferLen = sizeof(buffer); + result = ghiDoReceive(connection, buffer, &bufferLen); + + // Handle error, no data, conn closed. + ////////////////////////////////////// + if(result == GHIError) + return; + if(result == GHINoData) + return; + + // Only append data if we got data. + /////////////////////////////////// + if(result == GHIRecvData) + { + // Check for encryption. + //////////////////////// + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None && + connection->encryptor.mEncryptOnBuffer == GHTTPTrue) + { + // Append new encrypted data to anything we've held over + // We have to do this because we can't decrypt partial SSL messages + if (!ghiAppendDataToBuffer(&connection->decodeBuffer, buffer, bufferLen)) + return; + + // Decrypt as much as we can + if (!ghiDecryptReceivedData(connection)) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + return; + } + } + else + { + // Add the data directly to the buffer. + /////////////////////////////////////// + if(!ghiAppendDataToBuffer(&connection->recvBuffer, buffer, bufferLen)) + return; + } + } + + // Cache a pointer to the front of the headers. + /////////////////////////////////////////////// + headers = (connection->recvBuffer.data + connection->headerStringIndex); + + // Check if the headers are finished. + ///////////////////////////////////// + if( ((connection->statusCode / 100) == 1) && + (strncmp(headers, "\r\n", 2) == 0 || strncmp(headers, "\xA\xA", 2) == 0) + ) + { + // If a continue doesn't have a header (immediate CRLF) move on to next status + endOfHeaders = headers; + hasHeaders = GHTTPFalse; + } + else + { + endOfHeaders = strstr(headers, CRLF CRLF); + } + if(!endOfHeaders) + { + endOfHeaders = strstr(headers, "\xA\xA"); // some servers seem to use LFs only?! Seen in 302 redirect. (28may01/bgw) + } + if(endOfHeaders) + { + char * fileStart; + int fileLength; + //int headersLength; + char * contentLength; + +#ifdef HTTP_LOG + int headersLength; +#endif + + // Clear off the empty line. + //////////////////////////// + if (GHTTPTrue == hasHeaders) + endOfHeaders += 2; + *endOfHeaders = '\0'; + + // Figure out where the file starts, and how many bytes. + //////////////////////////////////////////////////////// +#ifdef HTTP_LOG + headersLength = (endOfHeaders - headers); +#endif + + fileStart = (endOfHeaders + 2); + fileLength = (connection->recvBuffer.len - (fileStart - connection->recvBuffer.data)); + + // Set the headers buffer's new length. + /////////////////////////////////////// + connection->recvBuffer.len = (endOfHeaders - connection->recvBuffer.data + 1); + connection->recvBuffer.pos = connection->recvBuffer.len; + + // Log it. + ////////// +#ifdef HTTP_LOG + ghiLogResponse(headers, headersLength); + ghiLogResponse("\n", 1); +#endif + + // Check for continue. + ////////////////////// + if((connection->statusCode / 100) == 1) + { + if(fileLength) + { + // Move any data to the front of the buffer. + //////////////////////////////////////////// + memmove(connection->recvBuffer.data, fileStart, (unsigned int)fileLength + 1); + connection->recvBuffer.len = fileLength; + } + else + { + // Reset the buffer. + ///////////////////////// + ghiResetBuffer(&connection->recvBuffer); + } + + // Some posts must wait for continue before uploading + // Check if we should return to posting + if (connection->postingState.waitPostContinue) + { + connection->postingState.waitPostContinue = GHTTPFalse; + connection->state = GHTTPPosting; + ghiCallProgressCallback(connection, NULL, 0); + } + + // We're back to receiving status. + ////////////////////////////////// + connection->state = GHTTPReceivingStatus; + ghiCallProgressCallback(connection, NULL, 0); + + return; + } + + // Check for redirection. + ///////////////////////// + if((connection->statusCode / 100) == 3) + { + char * location; + + // Are we over our redirection count? + ///////////////////////////////////// + if(connection->redirectCount > 10) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPFileNotFound; + return; + } + + // Find the new location. + ///////////////////////// + location = strstr(headers, "Location:"); + if(location) + { + char * end; + + // Find the start of the URL. + ///////////////////////////// + location += 9; + while(isspace(*location)) + location++; + + // Find the end. + //////////////// + for(end = location; *end && !isspace(*end) ; end++) { }; + *end = '\0'; + + // Check if this is not a full URL. + /////////////////////////////////// + if(*location == '/') + { + int len; + + // Recompose the URL ourselves. + /////////////////////////////// + len = (int)(strlen(connection->serverAddress) + 13 + strlen(location) + 1); + connection->redirectURL = (char *)gsimalloc((unsigned int)len); + if(!connection->redirectURL) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPOutOfMemory; + } + sprintf(connection->redirectURL, "http://%s:%d%s", connection->serverAddress, connection->serverPort, location); + } + else + { + // Set the redirect URL. + //////////////////////// + connection->redirectURL = goastrdup(location); + if(!connection->redirectURL) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPOutOfMemory; + } + } + + return; + } + } + + // If we know the file-length, set it. + ////////////////////////////////////// + contentLength = strstr(headers, "Content-Length:"); + if(contentLength) + { + // Verify that the download size is something we can handle + /////////////////////////////////////////////////////////// +#if (GSI_MAX_INTEGRAL_BITS >= 64) + char szMaxSize[] = "9223372036854775807"; // == GSI_MAX_I64 +#else + char szMaxSize[] = "2147483647"; // == GSI_MAX_I32 +#endif + char* pStart = contentLength+16; + char* pEnd = pStart; + int nMaxLen = (int)strlen(szMaxSize); + + // Skip to the end of the line + while( pEnd && *pEnd != '\0' && *pEnd != '\n' && *pEnd != '\r' && *pEnd != ' ' ) + pEnd++; + + if( pEnd-pStart > nMaxLen ) + { + // Wow, that IS a big number + connection->completed = GHTTPTrue; + connection->result = GHTTPFileToBig; + return; + } + else + if( pEnd-pStart == nMaxLen ) + { + // Same length, maybe a bigger number + if( strncmp(pStart,szMaxSize,(unsigned int)(pEnd-pStart)) >= 0 ) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPFileToBig; + return; + } + } + + // Record the full size of the expected download + //////////////////////////////////////////////// +#if (GSI_MAX_INTEGRAL_BITS >= 64) + connection->totalSize = _atoi64(pStart); +#else + connection->totalSize = atoi(pStart); +#endif + } + + // Check the chunky. + //////////////////// + connection->chunkedTransfer = (strstr(headers, "Transfer-Encoding: chunked") != NULL)?GHTTPTrue:GHTTPFalse; + if(connection->chunkedTransfer) + { + connection->chunkHeader[0] = '\0'; + connection->chunkHeaderLen = 0; + connection->chunkBytesLeft = 0; + connection->chunkReadingState = CRHeader; + } + + // If we're just getting headers, or only posting data, we're done. + /////////////////////////////////////////////////////////////////// + if((connection->type == GHIHEAD) || (connection->type == GHIPOST)) + { + connection->completed = GHTTPTrue; + return; + } + + // We're receiving file data now. + ///////////////////////////////// + connection->state = GHTTPReceivingFile; + + // Is this an empty file? + ///////////////////////// + if(contentLength && !connection->totalSize) + { + connection->completed = GHTTPTrue; + return; + } + + // If any of the body has arrived, handle it. + ///////////////////////////////////////////// + if(fileLength > 0) + ghiProcessIncomingFileData(connection, fileStart, fileLength); + + // Don't reset the buffer -- we store status and header info + //ghiResetBuffer(&connection->recvBuffer); + } + else if(result == GHIConnClosed) + { + // The conn was closed, and we didn't finish the headers - bad. + /////////////////////////////////////////////////////////////// + connection->completed = GHTTPTrue; + connection->result = GHTTPBadResponse; + connection->socketError = GOAGetLastError(connection->socket); + } +} + +/******************* +** RECEIVING FILE ** +*******************/ +void ghiDoReceivingFile +( + GHIConnection * connection +) +{ + char buffer[8192]; + int bufferLen; + GHIRecvResult result; + gsi_time start_time = current_time(); + gsi_time running_time = 0; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, "Receiving File\n"); + + while(!connection->completed && (running_time < connection->maxRecvTime)) + { + // Get data. + //////////// + bufferLen = sizeof(buffer); + result = ghiDoReceive(connection, buffer, &bufferLen); + + // Handle error, no data, conn closed. + ////////////////////////////////////// + if(result == GHIError) + return; + if(result == GHINoData) + return; + if(result == GHIConnClosed) + { + // The file is done (hopefully). + //////////////////////////////// + connection->completed = GHTTPTrue; + + if (connection->totalSize > 0 && connection->fileBytesReceived < connection->totalSize) + connection->result = GHTTPFileIncomplete; + return; + } + + // Check for encryption. + //////////////////////// + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None && + connection->encryptor.mEncryptOnBuffer == GHTTPTrue) + { + char * decryptedData; + int decryptedLen; + + // Append new encrypted data to anything we've held over + // We have to do this because we can't decrypt partial SSL messages + if (!ghiAppendDataToBuffer(&connection->decodeBuffer, buffer, bufferLen)) + return; + + // Previously decrypted parts of the file have already been handled. + connection->recvBuffer.len = connection->recvBuffer.pos; + + // Decrypt as much as we can + if (!ghiDecryptReceivedData(connection)) + { + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + return; + } + + // Check for decrypted data. + //////////////////////////// + decryptedLen = (connection->recvBuffer.len - connection->recvBuffer.pos); + if(decryptedLen) + { + // Process the data. + //////////////////// + decryptedData = (connection->recvBuffer.data + connection->recvBuffer.pos); + if(!ghiProcessIncomingFileData(connection, decryptedData, decryptedLen)) + return; + } + } + else + { + // Process the data. + //////////////////// + if(!ghiProcessIncomingFileData(connection, buffer, bufferLen)) + return; + } + + running_time = current_time() - start_time; + } +} diff --git a/code/gamespy/ghttp/ghttpProcess.h b/code/gamespy/ghttp/ghttpProcess.h new file mode 100644 index 00000000..6710b797 --- /dev/null +++ b/code/gamespy/ghttp/ghttpProcess.h @@ -0,0 +1,37 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GHTTPPROCESS_H_ +#define _GHTTPPROCESS_H_ + +#include "ghttpMain.h" +#include "ghttpConnection.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void ghiDoSocketInit (GHIConnection * connection); +void ghiDoHostLookup (GHIConnection * connection); +void ghiDoLookupPending (GHIConnection * connection); +void ghiDoConnecting (GHIConnection * connection); +void ghiDoSecuringSession (GHIConnection * connection); +void ghiDoSendingRequest (GHIConnection * connection); +void ghiDoPosting (GHIConnection * connection); +void ghiDoWaiting (GHIConnection * connection); +void ghiDoReceivingStatus (GHIConnection * connection); +void ghiDoReceivingHeaders(GHIConnection * connection); +void ghiDoReceivingFile (GHIConnection * connection); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/ghttp/ghttpc/ghttpc.c b/code/gamespy/ghttp/ghttpc/ghttpc.c new file mode 100644 index 00000000..68696b04 --- /dev/null +++ b/code/gamespy/ghttp/ghttpc/ghttpc.c @@ -0,0 +1,313 @@ + /* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "../../common/gsCommon.h" +#include "../ghttp.h" + +#ifdef UNDER_CE + void RetailOutputA(CHAR *tszErr, ...); + #define printf RetailOutputA +#elif defined(_NITRO) + #include "../../common/nitro/screen.h" + #define printf Printf + #define vprintf VPrintf +#endif + +#define MAX_REQUESTS 20 + +typedef struct Result +{ + GHTTPBool started; + gsi_time startTime; + gsi_time stopTime; + GHTTPResult result; +} Result; + +static int pendingRequests; +static Result results[MAX_REQUESTS]; + +static gsi_char * stateStrings[] = +{ + _T("Socket Init"), + _T("Host Lookup"), + _T("Lookup Pending"), + _T("Connecting"), + _T("Securing Session"), + _T("Sending Request"), + _T("Posting"), + _T("Waiting"), + _T("Receiving Status"), + _T("Receiving Headers"), + _T("Receiving File") +}; + +static gsi_char * resultStrings[] = +{ + _T("GHTTPSuccess"), + _T("GHTTPOutOfMemory"), + _T("GHTTPBufferOverflow"), + _T("GHTTPParseURLFailed"), + _T("GHTTPHostLookupFailed"), + _T("GHTTPSocketFailed"), + _T("GHTTPConnectFailed"), + _T("GHTTPBadResponse"), + _T("GHTTPRequestRejected"), + _T("GHTTPUnauthorized"), + _T("GHTTPForbidden"), + _T("GHTTPFileNotFound"), + _T("GHTTPServerError"), + _T("GHTTPFileWriteFailed"), + _T("GHTTPFileReadFailed"), + _T("GHTTPFileIncomplete"), + _T("GHTTPFileToBig"), + _T("GHTTPEncryptionError"), + _T("GHTTPRequestCancelled") +}; + + +#ifdef __MWERKS__ // CodeWarrior will warn if function not prototyped +int test_main(int argc, char **argv); +#endif + +#ifdef GSI_COMMON_DEBUG + static void DebugCallback(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel, const char * theTokenStr, + va_list theParamList) + { + GSI_UNUSED(theLevel); + + printf("[%s][%s] ", + gGSIDebugCatStrings[theCat], + gGSIDebugTypeStrings[theType]); + vprintf(theTokenStr, theParamList); + +#if defined(_WIN32) && !defined(_XBOX) + { + static char buffer[4098]; + sprintf(buffer, "[%s][%s] ", + gGSIDebugCatStrings[theCat], + gGSIDebugTypeStrings[theType]); + OutputDebugString(buffer); + vsprintf(buffer, theTokenStr, theParamList); + OutputDebugString(buffer); + } +#endif + } +#endif + +static GHTTPBool CompletedCallback +( + GHTTPRequest request, + GHTTPResult result, + char * buffer, + GHTTPByteCount bufferLen, + void * param +) +{ + int index = (int)param; + + pendingRequests--; + + // Save off the result. + /////////////////////// + results[index].result = result; + + // Save the time. + ///////////////// + results[index].stopTime = current_time(); + + // Check the result and print out some info. + //////////////////////////////////////////// + if(result == GHTTPSuccess) + _tprintf(_T("%d finished\n"), index); + else + _tprintf(_T("%d failed: %s\n"), index, resultStrings[result]); + + // Don't free this buffer, its from the stack. + ////////////////////////////////////////////// + if(index == 1) + return GHTTPFalse; + + // Free the buffer (if there is one). + ///////////////////////////////////// + GSI_UNUSED(request); + GSI_UNUSED(buffer); + GSI_UNUSED(bufferLen); + + return GHTTPTrue; +} + +static void ProgressCallback +( + GHTTPRequest request, + GHTTPState state, + const char * buffer, + GHTTPByteCount bufferLen, + GHTTPByteCount bytesReceived, + GHTTPByteCount totalSize, + void * param +) +{ + int index = (int)param; + + // Show the current state. + ////////////////////////// + _tprintf(_T("%d state: %s"), index, stateStrings[state]); + + // If we're receiving the file, show the progress. + ////////////////////////////////////////////////// + if(state == GHTTPReceivingFile) + { + // Display based on if we know the total size. + ////////////////////////////////////////////// + if(totalSize != -1) + _tprintf(_T(" (%d / %d bytes)\n"), bytesReceived, totalSize); + else + _tprintf(_T(" (%d bytes)\n"), bytesReceived); + } + else + _tprintf(_T("\n")); + + GSI_UNUSED(request); + GSI_UNUSED(buffer); + GSI_UNUSED(bufferLen); +} + +static void CheckRequest(GHTTPRequest request, int index) +{ + assert(index < MAX_REQUESTS); + results[index].started = (request < 0)?GHTTPFalse:GHTTPTrue; + if(results[index].started) + results[index].startTime = current_time(); +} + +int test_main(int argc, char **argv) +{ + int i; + static char buffer[10000] = ""; + GHTTPRequest request; + int numRequests; + +#ifdef GSI_COMMON_DEBUG + // Define GSI_COMMON_DEBUG if you want to view the SDK debug output + // Set the SDK debug log file, or set your own handler using gsSetDebugCallback + //gsSetDebugFile(stdout); // output to console + gsSetDebugCallback(DebugCallback); + + // Set some debug levels + gsSetDebugLevel(GSIDebugCat_All, GSIDebugType_All, GSIDebugLevel_Hardcore); + //gsSetDebugLevel(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Verbose); // Show Detailed data on network traffic + //gsSetDebugLevel(GSIDebugCat_App, GSIDebugType_All, GSIDebugLevel_Hardcore); // Show All app comment +#endif + + // get a file + request = ghttpGet( + _T("http://www.gamespy.net/images/dev_serv_main.jpg"), + GHTTPFalse, + CompletedCallback, + (void *)pendingRequests); + CheckRequest(request, pendingRequests); + pendingRequests++; + + // put a file into our own buffer, with progress updates + // also gets redirected + request = ghttpGetEx( + _T("http://google.com/intl/en_ALL/images/logo.gif"), + NULL, + buffer, sizeof(buffer), + NULL, + GHTTPFalse, + GHTTPFalse, + ProgressCallback, + CompletedCallback, + (void *)pendingRequests); + CheckRequest(request, pendingRequests); + pendingRequests++; + + // download a file +#if !defined(NOFILE) + request = ghttpSave( + _T("http://www.gamespy.net/images/dev_serv_logo.jpg"), + _T("logo.jpg"), + GHTTPFalse, + CompletedCallback, + (void *)pendingRequests); + CheckRequest(request, pendingRequests); + pendingRequests++; +#endif + + // stream a page + request = ghttpStreamEx( + _T("http://www.gamespy.net"), + NULL, + NULL, + GHTTPFalse, + GHTTPFalse, + ProgressCallback, + CompletedCallback, + (void *)pendingRequests); + CheckRequest(request, pendingRequests); + pendingRequests++; + + // get a header + request = ghttpHead( + _T("http://sdkdev.gamespy.com/games/st_ladder/web/index.html"), + GHTTPFalse, + CompletedCallback, + (void *)pendingRequests); + CheckRequest(request, pendingRequests); + pendingRequests++; + + // stream a secure page + request = ghttpStreamEx( +#if defined(_REVOLUTION) + _T("https://mariokartwii.race.gs.nintendowifi.net/RaceService/test.txt"), +#else + _T("https://www.gamespyid.com/"), +#endif + NULL, + NULL, + GHTTPFalse, + GHTTPFalse, + ProgressCallback, + CompletedCallback, + (void *)pendingRequests); + + //if(!IS_GHTTP_ERROR(request)) + // ghttpSetRequestEncryptionEngine(request, GHTTPEncryptionEngine_GameSpy); + CheckRequest(request, pendingRequests); + pendingRequests++; + + // store the number of requests + numRequests = pendingRequests; + + do + { + ghttpThink(); + msleep(20); + } + while(pendingRequests); + + ghttpCleanup(); + + _tprintf(_T("Results:\n")); + for(i = 0 ; i < numRequests ; i++) + { + if(results[i].started == GHTTPFalse) + _tprintf(_T("%d: Failed to start\n"), i); + else + _tprintf(_T("%d: %s [%dms]\n"), i, resultStrings[results[i].result], results[i].stopTime - results[i].startTime); + } + + GSI_UNUSED(argc); + GSI_UNUSED(argv); + + return 0; +} diff --git a/code/gamespy/ghttp/ghttpc/ghttpnitrocw/Nitro.lcf b/code/gamespy/ghttp/ghttpc/ghttpnitrocw/Nitro.lcf new file mode 100644 index 00000000..998f6b00 --- /dev/null +++ b/code/gamespy/ghttp/ghttpc/ghttpnitrocw/Nitro.lcf @@ -0,0 +1,493 @@ +#--------------------------------------------------------------------------- +# Project: NitroSDK - tools - makelcf +# File: ARM9-TS.lcf.template +# +# Copyright 2003-2006 Nintendo. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# proprietary information of Nintendo of America Inc. and/or Nintendo +# Company Ltd., and are protected by Federal copyright law. They may +# not be disclosed to third parties or copied or duplicated in any form, +# in whole or in part, without the prior written consent of Nintendo. +# +# $Log: ARM9-TS.lcf.template,v $ +# Revision 1.34 04/06/2006 09:02:36 kitase_hirotake +# support for .itcm.bss and .dtcm.bss +# +# Revision 1.33 03/30/2006 23:59:22 AM yasu +# changed creation year +# +# Revision 1.32 03/29/2006 13:14:22 AM yasu +# support for overlays in CWVER 2.x +# +# Revision 1.31 11/24/2005 01:16:47 yada +# change start address of mainEX arena from 0x2400000 to 0x23e0000 +# +# Revision 1.30 09/02/2005 04:14:22 AM yasu +# Old symbols were redefined so they can be used even under SDK2.2 +# +# Revision 1.29 08/31/2005 09:34:57 AM yasu +# Corrected a problem where code would not function normally when using section names such as section_BSS +# +# Revision 1.28 08/26/2005 11:22:16 AM yasu +# overlay support for ITCM/DTCM +# +# Revision 1.27 06/20/2005 12:29:20 AM yasu +# Changed Surffix to Suffix +# +# Revision 1.26 06/14/2005 09:03:42 yada +# fix around minus value of SDK_STACKSIZE +# +# Revision 1.25 04/13/2005 12:51:00 terui +# Change SDK_AUTOLOAD.DTCM.START 0x027c0000 -> 0x027e0000 +# +# Revision 1.24 03/30/2005 00:02:14 yosizaki +# fix copyright header. +# +# Revision 1.23 03/25/2005 12:54:59 AM yasu +# Include .version section +# +# Revision 1.22 10/03/2004 02:00:56 AM yasu +# Output component file list for compstatic tool +# +# Revision 1.21 09/27/2004 05:28:21 AM yasu +# Support .sinit +# +# Revision 1.20 09/09/2004 11:49:20 AM yasu +# Support compstatic in default +# +# Revision 1.19 09/06/2004 06:40:00 AM yasu +# Add labels for digest +# +# Revision 1.18 08/20/2004 06:19:59 AM yasu +# DTCM moves to 0x027c0000 at default +# +# Revision 1.17 08/02/2004 10:38:53 AM yasu +# Add autoload-done callback address in overlaydefs +# +# Revision 1.16 07/26/2004 02:22:32 AM yasu +# Change DTCM address to 0x023c0000 +# +# Revision 1.15 07/26/2004 00:08:27 AM yasu +# Fix label of exception table +# +# Revision 1.14 07/24/2004 05:42:25 AM yasu +# Set default values for SDK_AUTOGEN_xTCM_START +# +# Revision 1.13 07/23/2004 11:32:14 AM yasu +# Define labels for __exception_table_start__ and _end__ +# +# Revision 1.12 07/12/2004 12:21:08 AM yasu +# Check size of ITCM/DTCM +# +# Revision 1.11 07/10/2004 04:10:26 AM yasu +# Support command 'Library' +# +# Revision 1.10 07/02/2004 08:13:02 AM yasu +# Support OBJECT( ) +# +# Revision 1.9 07/01/2004 12:54:38 yasu +# support ITCM/DTCM/WRAM autoload +# +# Revision 1.8 07/01/2004 10:41:46 yasu +# support autoload +# +# Revision 1.7 06/02/2004 07:35:37 yasu +# Set libsyscall.a in FORCE_ACTIVE +# Put NitroMain at the top of ROM image +# +# Revision 1.6 06/02/2004 04:56:28 yasu +# Change to fit to new ROM map of TS +# +# Revision 1.5 2004/06/01 06:12:00 miya +# add padding at top of ROM image. +# +# Revision 1.4 04/26/2004 12:16:48 yasu +# add KEEP_SECTIONS +# +# Revision 1.3 04/20/2004 07:41:32 yasu +# Set STATICINIT instead of .ctor temporarily +# +# Revision 1.2 04/14/2004 07:16:42 yasu +# add ALIGN(32) for convenience to handle cache line +# +# Revision 1.1 04/06/2004 01:59:54 yasu +# newly added +# +# $NoKeywords: $ +#--------------------------------------------------------------------------- +MEMORY +{ + main (RWX) : ORIGIN = 0x02000000, LENGTH = 0x0 > main.sbin + ITCM (RWX) : ORIGIN = 0x01ff8000, LENGTH = 0x0 >> main.sbin + DTCM (RWX) : ORIGIN = 0x027e0000, LENGTH = 0x0 >> main.sbin + binary.AUTOLOAD_INFO (RWX) : ORIGIN = 0, LENGTH = 0x0 >> main.sbin + binary.STATIC_FOOTER (RWX) : ORIGIN = 0, LENGTH = 0x0 >> main.sbin + + main_defs (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 > main_defs.sbin + main_table (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 > main_table.sbin + dummy.MAIN_EX (RW) : ORIGIN = 0x023e0000, LENGTH = 0x0 + arena.MAIN (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 + arena.MAIN_EX (RW) : ORIGIN = AFTER(dummy.MAIN_EX), LENGTH = 0x0 + arena.ITCM (RW) : ORIGIN = AFTER(ITCM), LENGTH = 0x0 + arena.DTCM (RW) : ORIGIN = AFTER(DTCM), LENGTH = 0x0 + binary.MODULE_FILES (RW) : ORIGIN = 0x0, LENGTH = 0x0 > component.files + check.ITCM (RWX) : ORIGIN = 0x0, LENGTH = 0x08000 > itcm.check + check.DTCM (RW) : ORIGIN = 0x0, LENGTH = 0x04000 > dtcm.check +} + +FORCE_ACTIVE +{ + SVC_SoftReset +} + +KEEP_SECTION +{ + .sinit +} + +SECTIONS +{ + ############################ STATIC ################################# + .main: + { + ALIGNALL(4); . = ALIGN(32); # Fit to cache line + + # + # TEXT BLOCK: READ ONLY + # + SDK_STATIC_START =.; + SDK_STATIC_TEXT_START =.; + #:::::::::: text/rodata + libsyscall.a (.text) + crt0.o (.text) + crt0.o (.rodata) + * (.version) + OBJECT(NitroMain,*) + GROUP(ROOT) (.text) + . = ALIGN(4); + * (.exception) + . = ALIGN(4); + SDK_STATIC_ETABLE_START =.; + EXCEPTION + SDK_STATIC_ETABLE_END =.; + . = ALIGN(4); + GROUP(ROOT) (.init) + . = ALIGN(4); + GROUP(ROOT) (.rodata) + . = ALIGN(4); + + SDK_STATIC_SINIT_START =.; + #:::::::::: ctor + GROUP(ROOT) (.ctor) + GROUP(ROOT) (.sinit) + WRITEW 0; + #:::::::::: ctor + SDK_STATIC_SINIT_END =.; + + #:::::::::: text/rodata + . = ALIGN(32); + SDK_STATIC_TEXT_END =.; + + # + # DATA BLOCK: READ WRITE + # + SDK_STATIC_DATA_START =.; + #:::::::::: data + GROUP(ROOT) (.sdata) + . = ALIGN(4); + GROUP(ROOT) (.data) + . = ALIGN(4); + SDK_OVERLAY_DIGEST =.; + # NO DIGEST + SDK_OVERLAY_DIGEST_END =.; + #:::::::::: data + . = ALIGN(32); + SDK_STATIC_DATA_END =.; + SDK_STATIC_END =.; + + SDK_STATIC_TEXT_SIZE = SDK_STATIC_TEXT_END - SDK_STATIC_TEXT_START; + SDK_STATIC_DATA_SIZE = SDK_STATIC_DATA_END - SDK_STATIC_DATA_START; + SDK_STATIC_SIZE = SDK_STATIC_END - SDK_STATIC_START; + __sinit__ = SDK_STATIC_SINIT_START; # for static initializer + __exception_table_start__ = SDK_STATIC_ETABLE_START; # for exception table + __exception_table_end__ = SDK_STATIC_ETABLE_END; # for exception table + } > main + + .main.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_STATIC_BSS_START =.; + #:::::::::: bss + GROUP(ROOT) (.sbss) + . = ALIGN(4); + GROUP(ROOT) (.bss) + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_STATIC_BSS_END = .; + SDK_STATIC_BSS_SIZE = SDK_STATIC_BSS_END - SDK_STATIC_BSS_START; + + } >> main + + + ############################ AUTOLOADS ############################## + SDK_AUTOLOAD.ITCM.START = 0x01ff8000; + SDK_AUTOLOAD.ITCM.END = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD.ITCM.BSS_END = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD.ITCM.SIZE = 0; + SDK_AUTOLOAD.ITCM.BSS_SIZE = 0; + SDK_AUTOLOAD.DTCM.START = 0x027e0000; + SDK_AUTOLOAD.DTCM.END = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD.DTCM.BSS_END = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD.DTCM.SIZE = 0; + SDK_AUTOLOAD.DTCM.BSS_SIZE = 0; + SDK_AUTOLOAD_START = SDK_STATIC_END; + SDK_AUTOLOAD_SIZE = 0; + SDK_AUTOLOAD_NUMBER = 2; + + .ITCM: + { + ALIGNALL(4); . = ALIGN(32); + + # + # TEXT BLOCK: READ ONLY + # + SDK_AUTOLOAD_ITCM_ID =0; + SDK_AUTOLOAD.ITCM.ID =0; + SDK_AUTOLOAD.ITCM.START =.; + SDK_AUTOLOAD.ITCM.TEXT_START =.; + #:::::::::: text/rodata + . = ALIGN(4); + * (.itcm) + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: text/rodata + SDK_AUTOLOAD.ITCM.TEXT_END =.; + + # + # DATA BLOCK: READ WRITE BLOCK + # + SDK_AUTOLOAD.ITCM.DATA_START =.; + #:::::::::: data + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: data + . = ALIGN(32); + SDK_AUTOLOAD.ITCM.DATA_END =.; + SDK_AUTOLOAD.ITCM.END =.; + + SDK_AUTOLOAD.ITCM.TEXT_SIZE = SDK_AUTOLOAD.ITCM.TEXT_END - SDK_AUTOLOAD.ITCM.TEXT_START; + SDK_AUTOLOAD.ITCM.DATA_SIZE = SDK_AUTOLOAD.ITCM.DATA_END - SDK_AUTOLOAD.ITCM.DATA_START; + SDK_AUTOLOAD.ITCM.SIZE = SDK_AUTOLOAD.ITCM.END - SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SDK_AUTOLOAD.ITCM.SIZE; + + } > ITCM + + .ITCM.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_AUTOLOAD.ITCM.BSS_START = .; + #:::::::::: bss + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + * (.itcm.bss) + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_AUTOLOAD.ITCM.BSS_END = .; + + SDK_AUTOLOAD.ITCM.BSS_SIZE = SDK_AUTOLOAD.ITCM.BSS_END - SDK_AUTOLOAD.ITCM.BSS_START; + + } >> ITCM + + .DTCM: + { + ALIGNALL(4); . = ALIGN(32); + + # + # TEXT BLOCK: READ ONLY + # + SDK_AUTOLOAD_DTCM_ID =1; + SDK_AUTOLOAD.DTCM.ID =1; + SDK_AUTOLOAD.DTCM.START =.; + SDK_AUTOLOAD.DTCM.TEXT_START =.; + #:::::::::: text/rodata + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: text/rodata + SDK_AUTOLOAD.DTCM.TEXT_END =.; + + # + # DATA BLOCK: READ WRITE BLOCK + # + SDK_AUTOLOAD.DTCM.DATA_START =.; + #:::::::::: data + . = ALIGN(4); + . = ALIGN(4); + * (.dtcm) + . = ALIGN(4); + #:::::::::: data + . = ALIGN(32); + SDK_AUTOLOAD.DTCM.DATA_END =.; + SDK_AUTOLOAD.DTCM.END =.; + + SDK_AUTOLOAD.DTCM.TEXT_SIZE = SDK_AUTOLOAD.DTCM.TEXT_END - SDK_AUTOLOAD.DTCM.TEXT_START; + SDK_AUTOLOAD.DTCM.DATA_SIZE = SDK_AUTOLOAD.DTCM.DATA_END - SDK_AUTOLOAD.DTCM.DATA_START; + SDK_AUTOLOAD.DTCM.SIZE = SDK_AUTOLOAD.DTCM.END - SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SDK_AUTOLOAD.DTCM.SIZE; + + } > DTCM + + .DTCM.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_AUTOLOAD.DTCM.BSS_START = .; + #:::::::::: bss + . = ALIGN(4); + . = ALIGN(4); + * (.dtcm.bss) + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_AUTOLOAD.DTCM.BSS_END = .; + + SDK_AUTOLOAD.DTCM.BSS_SIZE = SDK_AUTOLOAD.DTCM.BSS_END - SDK_AUTOLOAD.DTCM.BSS_START; + + } >> DTCM + + + SDK_AUTOLOAD_ITCM_START = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD_ITCM_END = SDK_AUTOLOAD.ITCM.END; + SDK_AUTOLOAD_ITCM_BSS_END = SDK_AUTOLOAD.ITCM.BSS_END; + SDK_AUTOLOAD_ITCM_SIZE = SDK_AUTOLOAD.ITCM.SIZE; + SDK_AUTOLOAD_ITCM_BSS_SIZE = SDK_AUTOLOAD.ITCM.BSS_SIZE; + SDK_AUTOLOAD_DTCM_START = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD_DTCM_END = SDK_AUTOLOAD.DTCM.END; + SDK_AUTOLOAD_DTCM_BSS_END = SDK_AUTOLOAD.DTCM.BSS_END; + SDK_AUTOLOAD_DTCM_SIZE = SDK_AUTOLOAD.DTCM.SIZE; + SDK_AUTOLOAD_DTCM_BSS_SIZE = SDK_AUTOLOAD.DTCM.BSS_SIZE; + + ############################ AUTOLOAD_INFO ########################## + .binary.AUTOLOAD_INFO: + { + WRITEW ADDR(.ITCM); + WRITEW SDK_AUTOLOAD.ITCM.SIZE; + WRITEW SDK_AUTOLOAD.ITCM.BSS_SIZE; + WRITEW ADDR(.DTCM); + WRITEW SDK_AUTOLOAD.DTCM.SIZE; + WRITEW SDK_AUTOLOAD.DTCM.BSS_SIZE; + } > binary.AUTOLOAD_INFO + + SDK_AUTOLOAD_LIST = SDK_AUTOLOAD_START + SDK_AUTOLOAD_SIZE; + SDK_AUTOLOAD_LIST_END = SDK_AUTOLOAD_START + SDK_AUTOLOAD_SIZE + SIZEOF(.binary.AUTOLOAD_INFO); + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SIZEOF(.binary.AUTOLOAD_INFO); + + ############################ STATIC_FOOTER ########################## + .binary.STATIC_FOOTER: + { + WRITEW 0xdec00621; # LE(0x2106C0DE) = NITRO CODE + WRITEW _start_ModuleParams - ADDR(.main); + WRITEW 0; # NO DIGEST + } > binary.STATIC_FOOTER + + ############################ OVERLAYS ############################### + SDK_OVERLAY_NUMBER = 0; + + + ############################ MAIN EX ################################## + # MAIN EX Area + .dummy.MAIN_EX: + { + . = ALIGN(32); + } > dummy.MAIN_EX + + ############################ ARENA ################################## + .arena.MAIN: + { + . = ALIGN(32); + SDK_SECTION_ARENA_START =.; + } > arena.MAIN + + .arena.MAIN_EX: + { + . = ALIGN(32); + SDK_SECTION_ARENA_EX_START =.; + } > arena.MAIN_EX + + .arena.ITCM: + { + . = ALIGN(32); + SDK_SECTION_ARENA_ITCM_START =.; + } > arena.ITCM + + .arena.DTCM: + { + . = ALIGN(32); + SDK_SECTION_ARENA_DTCM_START =.; + } > arena.DTCM + + ############################ OVERLAYDEFS ############################ + .main_defs: + { + ### main module information + WRITEW ADDR(.main); # load address + WRITEW _start; # entry address + WRITEW SDK_STATIC_SIZE + SDK_AUTOLOAD_SIZE; # size of module + WRITEW _start_AutoloadDoneCallback; # callback autoload done + + ### overlay filename + + } > main_defs + + + ############################ OVERLAYTABLE ########################### + .main_table: + { + + } > main_table + + + ############################ OTHERS ################################# + SDK_MAIN_ARENA_LO = SDK_SECTION_ARENA_START; + SDK_IRQ_STACKSIZE = 4096; # allocated in DTCM + SDK_SYS_STACKSIZE = 0; # when 0 means all remains of DTCM + + # Module filelist + .binary.MODULE_FILES: + { + WRITES ("main.sbin"); + WRITES ("main_defs.sbin"); + WRITES ("main_table.sbin"); + } > binary.MODULE_FILES + + # ITCM/DTCM size checker => check AUTOLOAD_ITCM/DTCM + .check.ITCM: + { + . = . + SDK_AUTOLOAD_ITCM_SIZE + SDK_AUTOLOAD_ITCM_BSS_SIZE; + } > check.ITCM + + SDK_SYS_STACKSIZE_SIGN = (SDK_SYS_STACKSIZE < 0x80000000) * 2 - 1; + .check.DTCM: + { + . = . + SDK_AUTOLOAD_DTCM_SIZE + SDK_AUTOLOAD_DTCM_BSS_SIZE; + . = . + SDK_IRQ_STACKSIZE + SDK_SYS_STACKSIZE * SDK_SYS_STACKSIZE_SIGN; + } > check.DTCM + +} diff --git a/code/gamespy/ghttp/ghttpc/ghttpnitrocw/ROM-TS.rsf b/code/gamespy/ghttp/ghttpc/ghttpnitrocw/ROM-TS.rsf new file mode 100644 index 00000000..cec9e1db --- /dev/null +++ b/code/gamespy/ghttp/ghttpc/ghttpnitrocw/ROM-TS.rsf @@ -0,0 +1,116 @@ +#---------------------------------------------------------------------------- +# Project: NitroSDK - include +# File: ROM-TS.lsf +# +# Copyright 2003-2005 Nintendo. All rights reserved. +# +# These coded insructions, statements, and computer programs contain +# proprietary information of Nintendo of America Inc. and/or Nintendo +# Company Ltd., and are protected by Federal copyright law. They may +# not be disclosed to third parties or copied or duplicated in any form, +# in whole or in part, without the prior written consent of Nintendo. +# +# $Log: ROM-TS.rsf,v $ +# Revision 1.6 2005/04/05 23:52:58 yosizaki +# fix copyright date. +# +# Revision 1.5 2005/04/05 12:16:10 yosizaki +# support RomSpeedType parameter. +# +# Revision 1.4 2004/09/21 02:18:49 yasu +# Add default banner +# +# Revision 1.3 2004/09/09 11:39:09 yasu +# Unified ROM-TS and ROM-TS-C, also ROM-TEG and ROM-TEG-C +# +# Revision 1.2 2004/05/26 12:03:38 yasu +# add :r option to get basename for supporting IDE with makerom +# +# Revision 1.1 2004/04/06 01:59:59 yasu +# newly added +# +# $NoKeywords: $ +#---------------------------------------------------------------------------- +# +# Nitro ROM SPEC FILE +# + +Arm9 +{ + Static "$(MAKEROM_ARM9:r).sbin$(COMPSUFFIX9)" + OverlayDefs "$(MAKEROM_ARM9:r)_defs.sbin$(COMPSUFFIX9)" + OverlayTable "$(MAKEROM_ARM9:r)_table.sbin$(COMPSUFFIX9)" + Elf "$(MAKEROM_ARM9:r).nef" +} + +Arm7 +{ + Static "$(MAKEROM_ARM7:r).sbin$(COMPSUFFIX7)" + OverlayDefs "$(MAKEROM_ARM7:r)_defs.sbin$(COMPSUFFIX7)" + OverlayTable "$(MAKEROM_ARM7:r)_table.sbin$(COMPSUFFIX7)" + Elf "$(MAKEROM_ARM7:r).nef" +} + +Property +{ + ### + ### Settings for FinalROM + ### + #### BEGIN + # + # TITLE NAME: Your product name within 12bytes + # + #TitleName "YourAppName" + + # + # MAKER CODE: Your company ID# in 2 ascii words + # issued by NINTENDO + # + #MakerCode "00" + + # + # REMASTER VERSION: Mastering version + # + #RomVersion 0 + + # + # ROM SPEED TYPE: [MROM/1TROM/UNDEFINED] + # + RomSpeedType $(MAKEROM_ROMSPEED) + + # + # ROM SIZE: in bit [64M/128M/256M/512M/1G/2G] + # + #RomSize 128M + #RomSize 256M + + # + # ROM PADDING: TRUE if finalrom + # + #RomFootPadding TRUE + + # + # ROM HEADER TEMPLATE: Provided to every product by NINTENDO + # + #RomHeaderTemplate ./etc/rom_header.template.sbin + + # + # BANNER FILE: generated from Banner Spec File + # + #BannerFile ./etc/myGameBanner.bnr + BannerFile $(NITROSDK_ROOT)/include/nitro/specfiles/default.bnr + + ### + ### + ### + #### END +} + +RomSpec +{ + Offset 0x00000000 + Segment ALL + HostRoot $(MAKEROM_ROMROOT) + Root / + File $(MAKEROM_ROMFILES) +} diff --git a/code/gamespy/ghttp/ghttpc/ghttpps2prodg/ghttpps2prodg.dsp b/code/gamespy/ghttp/ghttpc/ghttpps2prodg/ghttpps2prodg.dsp new file mode 100644 index 00000000..d72929bc --- /dev/null +++ b/code/gamespy/ghttp/ghttpc/ghttpps2prodg/ghttpps2prodg.dsp @@ -0,0 +1,444 @@ +# Microsoft Developer Studio Project File - Name="ghttpps2prodg" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=ghttpps2prodg - Win32 PS2 EE Debug Insock +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "ghttpps2prodg.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "ghttpps2prodg.mak" CFG="ghttpps2prodg - Win32 PS2 EE Debug Insock" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "ghttpps2prodg - Win32 PS2 EE Debug EENet" (based on "Win32 (x86) Console Application") +!MESSAGE "ghttpps2prodg - Win32 PS2 EE Debug Insock" (based on "Win32 (x86) Console Application") +!MESSAGE "ghttpps2prodg - Win32 PS2 EE Debug SNSystems" (based on "Win32 (x86) Console Application") +!MESSAGE "ghttpps2prodg - Win32 PS2 EE Release EENet" (based on "Win32 (x86) Console Application") +!MESSAGE "ghttpps2prodg - Win32 PS2 EE Release Insock" (based on "Win32 (x86) Console Application") +!MESSAGE "ghttpps2prodg - Win32 PS2 EE Release SNSystems" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/Gamespy/GOA/ghttp/ghttpc/ghttpps2prodg", JSEDAAAA" +# PROP Scc_LocalPath "." +CPP=snCl.exe +RSC=rc.exe + +!IF "$(CFG)" == "ghttpps2prodg - Win32 PS2 EE Debug EENet" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "PS2 EE Debug EENet" +# PROP BASE Intermediate_Dir "PS2 EE Debug EENet" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_EENet" +# PROP Intermediate_Dir "Debug_EENet" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Debug/" /FD /debug /c +# ADD CPP /nologo /W4 /Od /I "c:\usr\local\sce\ee\include\libeenet" /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "EENET" /D "SN_TARGET_PS2" /D "_DEBUG" /FD /debug /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"PS2_EE_Debug\ghttpps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 ent_smap.a ent_eth.a ent_ppp.a eenetctl.a libeenet.a libscf.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"Debug_EENet\ghttpps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "ghttpps2prodg - Win32 PS2 EE Debug Insock" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "ghttpps2prodg___Win32_PS2_EE_Debug_Insock" +# PROP BASE Intermediate_Dir "ghttpps2prodg___Win32_PS2_EE_Debug_Insock" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_Insock" +# PROP Intermediate_Dir "Debug_Insock" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "c:\usr\local\sce\ee\include\libeenet" /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "EENET" /D "SN_TARGET_PS2" /D "_DEBUG" /FD /debug /c +# ADD CPP /nologo /W4 /Od /I "c:\usr\local\sce\ee\include\libeenet" /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "INSOCK" /D "SN_TARGET_PS2" /D "_DEBUG" /FD /debug /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 ent_smap.a ent_eth.a ent_ppp.a eenetctl.a libeenet.a libscf.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"Debug_EENet\ghttpps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 libinsck.a libnet.a libmrpc.a libkernl.a libcdvd.a libnetif.a netcnfif.a /nologo /pdb:none /debug /machine:IX86 /out:"Debug_Insock\ghttpps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "ghttpps2prodg - Win32 PS2 EE Debug SNSystems" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "PS2 EE Debug SNSystems" +# PROP BASE Intermediate_Dir "PS2 EE Debug SNSystems" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_SNSystems" +# PROP Intermediate_Dir "Debug_SNSystems" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Debug/" /FD /debug /c +# ADD CPP /nologo /W4 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_SYSTEMS" /D "SN_TARGET_PS2" /D "_DEBUG" /FD /debug /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"PS2_EE_Debug\ghttpps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 libcdvd.a sneetcp.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"Debug_SNSystems\ghttpps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "ghttpps2prodg - Win32 PS2 EE Release EENet" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "PS2 EE Release EENet" +# PROP BASE Intermediate_Dir "PS2 EE Release EENet" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Release_EENet" +# PROP Intermediate_Dir "Release_EENet" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /O2 /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Release/" /FD /c +# ADD CPP /nologo /W4 /WX /O2 /I "c:\usr\local\sce\ee\include\libeenet" /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /D "EENET" /FD /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"PS2_EE_Release\ghttpps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 ent_smap.a ent_eth.a ent_ppp.a eenetctl.a libeenet.a libscf.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"Release_EENet\ghttpps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "ghttpps2prodg - Win32 PS2 EE Release Insock" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "ghttpps2prodg___Win32_PS2_EE_Release_Insock" +# PROP BASE Intermediate_Dir "ghttpps2prodg___Win32_PS2_EE_Release_Insock" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Release_Insock" +# PROP Intermediate_Dir "Release_Insock" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /O2 /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /D "SN_SYSTEMS" /FD /c +# ADD CPP /nologo /W4 /WX /O2 /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /D "INSOCK" /FD /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libcdvd.a sneetcp.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"Release_SNSystems\ghttpps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 libinsck.a libnet.a libmrpc.a libkernl.a libcdvd.a libnetif.a netcnfif.a /nologo /pdb:none /machine:IX86 /out:"Release_Insock\ghttpps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "ghttpps2prodg - Win32 PS2 EE Release SNSystems" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "PS2 EE Release SNSystems" +# PROP BASE Intermediate_Dir "PS2 EE Release SNSystems" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Release_SNSystems" +# PROP Intermediate_Dir "Release_SNSystems" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /O2 /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Release/" /FD /c +# ADD CPP /nologo /W4 /WX /O2 /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /D "SN_SYSTEMS" /FD /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"PS2_EE_Release\ghttpps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 libcdvd.a sneetcp.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"Release_SNSystems\ghttpps2prodg.elf" /D:SN_TARGET_PS2 + +!ENDIF + +# Begin Target + +# Name "ghttpps2prodg - Win32 PS2 EE Debug EENet" +# Name "ghttpps2prodg - Win32 PS2 EE Debug Insock" +# Name "ghttpps2prodg - Win32 PS2 EE Debug SNSystems" +# Name "ghttpps2prodg - Win32 PS2 EE Release EENet" +# Name "ghttpps2prodg - Win32 PS2 EE Release Insock" +# Name "ghttpps2prodg - Win32 PS2 EE Release SNSystems" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\ghttpc.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\..\common\ps2\prodg\PS2_in_VC.h +# End Source File +# End Group +# Begin Group "HttpSDK" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\ghttp.h +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpASCII.h +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpBuffer.c +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpCallbacks.c +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpCallbacks.h +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpCommon.c +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpCommon.h +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpConnection.c +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpConnection.h +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpEncryption.c +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpEncryption.h +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpMain.c +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpMain.h +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpPost.c +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpPost.h +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpProcess.c +# End Source File +# Begin Source File + +SOURCE=..\..\ghttpProcess.h +# End Source File +# End Group +# Begin Group "GsCommon" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\darray.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\darray.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAssert.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAssert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAvailable.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAvailable.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsCommon.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsCrypt.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsCrypt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsDebug.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsDebug.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsLargeInt.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsLargeInt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsMemory.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsMemory.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatform.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatform.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformSocket.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformSocket.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformThread.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformThread.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformUtil.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformUtil.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsRC4.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsRC4.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsSHA1.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsSHA1.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsSSL.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsSSL.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsStringUtil.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsStringUtil.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsXML.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsXML.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\md5.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\md5c.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\ps2\ps2common.c +# End Source File +# End Group +# Begin Source File + +SOURCE=c:\usr\local\sce\ee\lib\app.cmd +# End Source File +# Begin Source File + +SOURCE=c:\usr\local\sce\ee\lib\crt0.s +# End Source File +# Begin Source File + +SOURCE=..\..\..\ps2common\prodg\ps2.lk +# End Source File +# End Target +# End Project diff --git a/code/gamespy/ghttp/ghttpc/ghttpps2prodg/ghttpps2prodg.dsw b/code/gamespy/ghttp/ghttpc/ghttpps2prodg/ghttpps2prodg.dsw new file mode 100644 index 00000000..01bdfc75 --- /dev/null +++ b/code/gamespy/ghttp/ghttpc/ghttpps2prodg/ghttpps2prodg.dsw @@ -0,0 +1,37 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "ghttpps2prodg"=.\ghttpps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/ghttp/ghttpc/ghttpps2prodg", JSEDAAAA + . + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/ghttp/ghttpc/ghttpps2prodg", JSEDAAAA + . + end source code control +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/code/gamespy/ghttp/ghttpmfc/ReadMe.txt b/code/gamespy/ghttp/ghttpmfc/ReadMe.txt new file mode 100644 index 00000000..68dd40ec --- /dev/null +++ b/code/gamespy/ghttp/ghttpmfc/ReadMe.txt @@ -0,0 +1,88 @@ +======================================================================== + MICROSOFT FOUNDATION CLASS LIBRARY : ghttpmfc +======================================================================== + + +AppWizard has created this ghttpmfc application for you. This application +not only demonstrates the basics of using the Microsoft Foundation classes +but is also a starting point for writing your application. + +This file contains a summary of what you will find in each of the files that +make up your ghttpmfc application. + +ghttpmfc.dsp + This file (the project file) contains information at the project level and + is used to build a single project or subproject. Other users can share the + project (.dsp) file, but they should export the makefiles locally. + +ghttpmfc.h + This is the main header file for the application. It includes other + project specific headers (including Resource.h) and declares the + CGhttpmfcApp application class. + +ghttpmfc.cpp + This is the main application source file that contains the application + class CGhttpmfcApp. + +ghttpmfc.rc + This is a listing of all of the Microsoft Windows resources that the + program uses. It includes the icons, bitmaps, and cursors that are stored + in the RES subdirectory. This file can be directly edited in Microsoft + Visual C++. + +ghttpmfc.clw + This file contains information used by ClassWizard to edit existing + classes or add new classes. ClassWizard also uses this file to store + information needed to create and edit message maps and dialog data + maps and to create prototype member functions. + +res\ghttpmfc.ico + This is an icon file, which is used as the application's icon. This + icon is included by the main resource file ghttpmfc.rc. + +res\ghttpmfc.rc2 + This file contains resources that are not edited by Microsoft + Visual C++. You should place all resources not editable by + the resource editor in this file. + + + + +///////////////////////////////////////////////////////////////////////////// + +AppWizard creates one dialog class: + +ghttpmfcDlg.h, ghttpmfcDlg.cpp - the dialog + These files contain your CGhttpmfcDlg class. This class defines + the behavior of your application's main dialog. The dialog's + template is in ghttpmfc.rc, which can be edited in Microsoft + Visual C++. + + +///////////////////////////////////////////////////////////////////////////// +Other standard files: + +StdAfx.h, StdAfx.cpp + These files are used to build a precompiled header (PCH) file + named ghttpmfc.pch and a precompiled types file named StdAfx.obj. + +Resource.h + This is the standard header file, which defines new resource IDs. + Microsoft Visual C++ reads and updates this file. + +///////////////////////////////////////////////////////////////////////////// +Other notes: + +AppWizard uses "TODO:" to indicate parts of the source code you +should add to or customize. + +If your application uses MFC in a shared DLL, and your application is +in a language other than the operating system's current language, you +will need to copy the corresponding localized resources MFC42XXX.DLL +from the Microsoft Visual C++ CD-ROM onto the system or system32 directory, +and rename it to be MFCLOC.DLL. ("XXX" stands for the language abbreviation. +For example, MFC42DEU.DLL contains resources translated to German.) If you +don't do this, some of the UI elements of your application will remain in the +language of the operating system. + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/ghttp/ghttpmfc/StdAfx.cpp b/code/gamespy/ghttp/ghttpmfc/StdAfx.cpp new file mode 100644 index 00000000..652cfa41 --- /dev/null +++ b/code/gamespy/ghttp/ghttpmfc/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// ghttpmfc.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/code/gamespy/ghttp/ghttpmfc/StdAfx.h b/code/gamespy/ghttp/ghttpmfc/StdAfx.h new file mode 100644 index 00000000..44588896 --- /dev/null +++ b/code/gamespy/ghttp/ghttpmfc/StdAfx.h @@ -0,0 +1,27 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__5ED40F92_A7FA_40E2_B911_E0DA573FB72A__INCLUDED_) +#define AFX_STDAFX_H__5ED40F92_A7FA_40E2_B911_E0DA573FB72A__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +#include "../ghttp.h" + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__5ED40F92_A7FA_40E2_B911_E0DA573FB72A__INCLUDED_) diff --git a/code/gamespy/ghttp/ghttpmfc/ghttpmfc.cpp b/code/gamespy/ghttp/ghttpmfc/ghttpmfc.cpp new file mode 100644 index 00000000..bd40f5fb --- /dev/null +++ b/code/gamespy/ghttp/ghttpmfc/ghttpmfc.cpp @@ -0,0 +1,72 @@ +// ghttpmfc.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "ghttpmfc.h" +#include "ghttpmfcDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CGhttpmfcApp + +BEGIN_MESSAGE_MAP(CGhttpmfcApp, CWinApp) + //{{AFX_MSG_MAP(CGhttpmfcApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CGhttpmfcApp construction + +CGhttpmfcApp::CGhttpmfcApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CGhttpmfcApp object + +CGhttpmfcApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CGhttpmfcApp initialization + +BOOL CGhttpmfcApp::InitInstance() +{ + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. +/* +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif +*/ + CGhttpmfcDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + // TODO: Place code here to handle when the dialog is + // dismissed with OK + } + else if (nResponse == IDCANCEL) + { + // TODO: Place code here to handle when the dialog is + // dismissed with Cancel + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/code/gamespy/ghttp/ghttpmfc/ghttpmfc.h b/code/gamespy/ghttp/ghttpmfc/ghttpmfc.h new file mode 100644 index 00000000..f2ebcbae --- /dev/null +++ b/code/gamespy/ghttp/ghttpmfc/ghttpmfc.h @@ -0,0 +1,49 @@ +// ghttpmfc.h : main header file for the GHTTPMFC application +// + +#if !defined(AFX_GHTTPMFC_H__30518F92_6CEC_4512_AC74_EF31A5DE2D1C__INCLUDED_) +#define AFX_GHTTPMFC_H__30518F92_6CEC_4512_AC74_EF31A5DE2D1C__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CGhttpmfcApp: +// See ghttpmfc.cpp for the implementation of this class +// + +class CGhttpmfcApp : public CWinApp +{ +public: + CGhttpmfcApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CGhttpmfcApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CGhttpmfcApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_GHTTPMFC_H__30518F92_6CEC_4512_AC74_EF31A5DE2D1C__INCLUDED_) diff --git a/code/gamespy/ghttp/ghttpmfc/ghttpmfc.rc b/code/gamespy/ghttp/ghttpmfc/ghttpmfc.rc new file mode 100644 index 00000000..e636e807 --- /dev/null +++ b/code/gamespy/ghttp/ghttpmfc/ghttpmfc.rc @@ -0,0 +1,240 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\ghttpmfc.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\ghttpmfc.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_GHTTPMFC_DIALOG DIALOGEX 0, 0, 320, 262 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "ghttp mfc test" +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "Get File",IDC_GET_FILE,"Button",BS_AUTORADIOBUTTON | + WS_GROUP,7,78,40,10 + CONTROL "Save File",IDC_SAVE_FILE,"Button",BS_AUTORADIOBUTTON,7, + 92,45,10 + CONTROL "Stream File",IDC_STREAM_FILE,"Button", + BS_AUTORADIOBUTTON,7,106,51,10 + CONTROL "Head File",IDC_HEAD_FILE,"Button",BS_AUTORADIOBUTTON,7, + 120,46,10 + CONTROL "Post",IDC_POST,"Button",BS_AUTORADIOBUTTON,7,134,30,10 + CONTROL "Progress1",IDC_PROGRESS,"msctls_progress32",PBS_SMOOTH | + WS_BORDER | WS_GROUP,8,154,230,8 + CONTROL "Host Lookup",IDC_HOST_LOOKUP,"Button", + BS_AUTORADIOBUTTON | WS_GROUP,240,154,57,10 + CONTROL "Connecting",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON,240, + 167,52,10 + CONTROL "Sending Request",IDC_RADIO4,"Button",BS_AUTORADIOBUTTON, + 240,180,71,10 + CONTROL "Posting",IDC_RADIO6,"Button",BS_AUTORADIOBUTTON,240,193, + 39,10 + CONTROL "Waiting",IDC_RADIO5,"Button",BS_AUTORADIOBUTTON,240,206, + 40,10 + CONTROL "Receiving Status",IDC_RADIO7,"Button", + BS_AUTORADIOBUTTON,240,219,70,10 + CONTROL "Receiving Headers",IDC_RADIO8,"Button", + BS_AUTORADIOBUTTON,240,232,77,10 + CONTROL "Receiving File",IDC_RADIO9,"Button",BS_AUTORADIOBUTTON, + 240,245,61,10 + DEFPUSHBUTTON "Quit",IDOK,260,7,50,14,WS_GROUP + EDITTEXT IDC_URL,32,7,214,12,ES_AUTOHSCROLL + EDITTEXT IDC_HEADERS,44,22,202,22,ES_MULTILINE | ES_AUTOHSCROLL + CONTROL "Blocking",IDC_BLOCKING,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,8,64,43,10 + CONTROL "Progress Callback",IDC_PROGRESS_CALLBACK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,53,64,73,10 + CONTROL "Completed Callback",IDC_COMPLETED_CALLBACK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,128,64,79,10 + CONTROL "Throttle",IDC_THROTTLE,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,209,64,40,10 + CONTROL "User Buffer",IDC_USER_BUFFER,"Button",BS_AUTOCHECKBOX | + WS_GROUP | WS_TABSTOP,68,78,51,10 + EDITTEXT IDC_BUFFER_SIZE,151,77,94,12,ES_AUTOHSCROLL + EDITTEXT IDC_SAVE_AS,104,92,94,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "Start",IDC_START,260,26,50,14 + DEFPUSHBUTTON "Cancel",IDC_CANCEL,260,45,50,14 + EDITTEXT IDC_FILE,8,218,230,36,ES_MULTILINE | ES_AUTOHSCROLL | + ES_READONLY | WS_VSCROLL + LTEXT "URL:",IDC_STATIC,9,8,18,8 + LTEXT "Headers:",IDC_STATIC,9,23,30,8 + LTEXT "Size:",IDC_STATIC,129,79,16,8 + LTEXT "Save as:",IDC_STATIC,70,93,29,8 + LTEXT "X/X",IDC_SO_FAR,8,144,225,8 + EDITTEXT IDC_HEADERS_RECV,8,178,230,37,ES_MULTILINE | + ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL + EDITTEXT IDC_STATUS,8,163,230,12,ES_MULTILINE | ES_AUTOHSCROLL | + ES_READONLY + DEFPUSHBUTTON "Think",IDC_THINK,258,108,50,14 + CONTROL "Step Think",IDC_STEP_THINK,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,258,93,51,10 + CONTROL "Post File",IDC_POST_FILE,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,68,134,43,10 + LTEXT "Objects:",IDC_STATIC,118,135,27,8 + LTEXT "X/X",IDC_POST_OBJECTS,149,135,24,8 + LTEXT "Bytes:",IDC_STATIC,182,135,20,8 + LTEXT "X/X",IDC_POST_BYTES,209,135,99,8 + EDITTEXT IDC_PROXY,30,48,112,12,ES_AUTOHSCROLL + LTEXT "Proxy:",IDC_STATIC,7,49,20,8 + DEFPUSHBUTTON "IE Settings",IDC_IE_SETTINGS,196,48,50,12 + DEFPUSHBUTTON "Set Proxy",IDC_SET_PROXY,144,48,50,12 +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "ghttpmfc MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "ghttpmfc\0" + VALUE "LegalCopyright", "Copyright (C) 2000\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "ghttpmfc.EXE\0" + VALUE "ProductName", "ghttpmfc Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_GHTTPMFC_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 313 + TOPMARGIN, 7 + BOTTOMMARGIN, 255 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\ghttpmfc.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/gamespy/ghttp/ghttpmfc/ghttpmfcDlg.cpp b/code/gamespy/ghttp/ghttpmfc/ghttpmfcDlg.cpp new file mode 100644 index 00000000..ce899092 --- /dev/null +++ b/code/gamespy/ghttp/ghttpmfc/ghttpmfcDlg.cpp @@ -0,0 +1,577 @@ +// ghttpmfcDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "ghttpmfc.h" +#include "ghttpmfcDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#include "InetReg.h" + +///////////////////////////////////////////////////////////////////////////// +// CGhttpmfcDlg dialog + +CGhttpmfcDlg::CGhttpmfcDlg(CWnd* pParent /*=NULL*/) + : CDialog(CGhttpmfcDlg::IDD, pParent) +{ + char buffer[512]; + int rcode; + FILE * fp; + fp = fopen("url.cache", "rt"); + if(fp) + { + rcode = fread(buffer, 1, 511, fp); + buffer[rcode] = '\0'; + m_url = buffer; + fclose(fp); + } + else + m_url = _T("http://planetquake.com/excessive"); + + fp = fopen("saveas.cache", "rt"); + if(fp) + { + rcode = fread(buffer, 1, 511, fp); + buffer[rcode] = '\0'; + m_saveAs = buffer; + fclose(fp); + } + else + m_saveAs = _T("file.html"); + + //{{AFX_DATA_INIT(CGhttpmfcDlg) + m_blocking = FALSE; + m_completedCallback = TRUE; + m_headers = _T(""); + m_progressCallback = TRUE; + m_bufferSize = 0; + m_userBuffer = FALSE; + m_type = 0; + m_file = _T(""); + m_soFar = _T(""); + m_state = -1; + m_throttle = FALSE; + m_headersRecv = _T(""); + m_status = _T(""); + m_stepThink = FALSE; + m_postFile = FALSE; + m_postObjects = _T(""); + m_postBytes = _T(""); + m_proxy = _T(""); + //}}AFX_DATA_INIT + // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); +} + +void CGhttpmfcDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CGhttpmfcDlg) + DDX_Control(pDX, IDC_PROGRESS, m_progress); + DDX_Check(pDX, IDC_BLOCKING, m_blocking); + DDX_Check(pDX, IDC_COMPLETED_CALLBACK, m_completedCallback); + DDX_Text(pDX, IDC_HEADERS, m_headers); + DDX_Check(pDX, IDC_PROGRESS_CALLBACK, m_progressCallback); + DDX_Text(pDX, IDC_URL, m_url); + DDX_Text(pDX, IDC_BUFFER_SIZE, m_bufferSize); + DDX_Text(pDX, IDC_SAVE_AS, m_saveAs); + DDX_Check(pDX, IDC_USER_BUFFER, m_userBuffer); + DDX_Radio(pDX, IDC_GET_FILE, m_type); + DDX_Text(pDX, IDC_FILE, m_file); + DDX_Text(pDX, IDC_SO_FAR, m_soFar); + DDX_Radio(pDX, IDC_HOST_LOOKUP, m_state); + DDX_Check(pDX, IDC_THROTTLE, m_throttle); + DDX_Text(pDX, IDC_HEADERS_RECV, m_headersRecv); + DDX_Text(pDX, IDC_STATUS, m_status); + DDX_Check(pDX, IDC_STEP_THINK, m_stepThink); + DDX_Check(pDX, IDC_POST_FILE, m_postFile); + DDX_Text(pDX, IDC_POST_OBJECTS, m_postObjects); + DDX_Text(pDX, IDC_POST_BYTES, m_postBytes); + DDX_Text(pDX, IDC_PROXY, m_proxy); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CGhttpmfcDlg, CDialog) + //{{AFX_MSG_MAP(CGhttpmfcDlg) + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_BN_CLICKED(IDC_START, OnStart) + ON_BN_CLICKED(IDC_CANCEL, OnCancel_) + ON_WM_TIMER() + ON_WM_DESTROY() + ON_BN_CLICKED(IDC_THROTTLE, OnThrottle) + ON_BN_CLICKED(IDC_THINK, OnThink) + ON_BN_CLICKED(IDC_SET_PROXY, OnSetProxy) + ON_BN_CLICKED(IDC_IE_SETTINGS, OnIeSettings) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CGhttpmfcDlg message handlers + +BOOL CGhttpmfcDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Set the icon for this dialog. The framework does this automatically + // when the application's main window is not a dialog + SetIcon(m_hIcon, TRUE); // Set big icon + SetIcon(m_hIcon, FALSE); // Set small icon + + m_request = -1; + m_memFile = NULL; + SetTimer(50, 50, NULL); + + return TRUE; // return TRUE unless you set the focus to a control +} + +// If you add a minimize button to your dialog, you will need the code below +// to draw the icon. For MFC applications using the document/view model, +// this is automatically done for you by the framework. + +void CGhttpmfcDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CDialog::OnPaint(); + } +} + +// The system calls this to obtain the cursor to display while the user drags +// the minimized window. +HCURSOR CGhttpmfcDlg::OnQueryDragIcon() +{ + return (HCURSOR) m_hIcon; +} + +static GHTTPBool CompletedCallback +( + GHTTPRequest request, + GHTTPResult result, + char * buffer, + GHTTPByteCount bufferLen, + void * param +) +{ + CGhttpmfcDlg * dlg = (CGhttpmfcDlg *)param; + + static char * resultStrings[] = + { + "GHTTPSuccess", + "GHTTPOutOfMemory", + "GHTTPBufferOverflow", + "GHTTPParseURLFailed", + "GHTTPHostLookupFailed", + "GHTTPSocketFailed", + "GHTTPConnectFailed", + "GHTTPBadResponse", + "GHTTPRequestRejected", + "GHTTPUnauthorized", + "GHTTPForbidden", + "GHTTPFileNotFound", + "GHTTPServerError", + "GHTTPFileWriteFailed", + "GHTTPFileReadFailed", + "GHTTPFileIncomplete", + "GHTTPFileToBig", + "GHTTPEncryptionError", + "GHTTPRequestCancelled" + }; + + CString msg; + + if( GHTTPSuccess == result ) + { + msg = "File received successfully"; + } + else + { + msg.Format("Error: (%d) ", result); + if( result < 0 || result >= sizeof(resultStrings)/sizeof(resultStrings[0]) ) + msg += "Unknown - local error table may be out of date"; + else + msg += resultStrings[(int)result]; + } + + dlg->MessageBox(msg); + + dlg->m_request = -1; + + return GHTTPTrue; + + GSI_UNUSED(bufferLen); + GSI_UNUSED(buffer); + GSI_UNUSED(request); +} + +void ProgressCallback +( + GHTTPRequest request, + GHTTPState state, + const char * buffer, + GHTTPByteCount bufferLen, + GHTTPByteCount bytesReceived, + GHTTPByteCount totalSize, + void * param +) +{ + CGhttpmfcDlg * dlg = (CGhttpmfcDlg *)param; + + dlg->UpdateData(); + + dlg->m_state = state; + + //added new states for asynch DNS lookup - set all to HOST_LOOKUP for radio buttons + if (dlg->m_state == GHTTPSocketInit || dlg->m_state == GHTTPHostLookup || dlg->m_state == GHTTPLookupPending) + dlg->m_state = 0; + + if(state == GHTTPReceivingFile) + { + if(totalSize == -1) + dlg->m_soFar.Format("%d bytes", bytesReceived); + else + { + int percent = (int)((bytesReceived * 100) / totalSize); + dlg->m_soFar.Format("%d%% (%d / %d bytes)", percent, bytesReceived, totalSize); + dlg->m_progress.SetPos(percent); + } + if((dlg->m_type == 0) || (dlg->m_type == 4)) + dlg->m_file = buffer; + } + + dlg->UpdateData(FALSE); + + GSI_UNUSED(bufferLen); + GSI_UNUSED(request); +} + +void PostCallback +( + GHTTPRequest request, + int bytesPosted, + int totalBytes, + int objectsPosted, + int totalObjects, + void * param +) +{ + CGhttpmfcDlg * dlg = (CGhttpmfcDlg *)param; + + dlg->UpdateData(); + + int percent = ((bytesPosted * 100) / totalBytes); + dlg->m_postObjects.Format("%d / %d", objectsPosted, totalObjects); + dlg->m_postBytes.Format("%d%% (%d / %d)", percent, bytesPosted, totalBytes); + + dlg->UpdateData(FALSE); + + GSI_UNUSED(request); +} + +void CGhttpmfcDlg::OnStart() +{ + UpdateData(); + + if(m_type == 0) + { + m_request = ghttpGetEx( + m_url, + m_headers, + NULL, + 0, + NULL, + (GHTTPBool)m_throttle, + (GHTTPBool)m_blocking, + ProgressCallback, + CompletedCallback, + this); + } + else if(m_type == 1) + { + m_request = ghttpSaveEx( + m_url, + m_saveAs, + m_headers, + NULL, + (GHTTPBool)m_throttle, + (GHTTPBool)m_blocking, + ProgressCallback, + CompletedCallback, + this); + } + else if(m_type == 2) + { + m_request = ghttpStreamEx( + m_url, + m_headers, + NULL, + (GHTTPBool)m_throttle, + (GHTTPBool)m_blocking, + ProgressCallback, + CompletedCallback, + this); + } + else if(m_type == 3) + { + m_request = ghttpHeadEx( + m_url, + m_headers, + (GHTTPBool)m_throttle, + (GHTTPBool)m_blocking, + ProgressCallback, + CompletedCallback, + this); + } + else if(m_type == 4) + { + GHTTPPost post; + post = ghttpNewPost(); + ghttpPostSetCallback(post, PostCallback, this); + ghttpPostAddString(post, "test1", "bag"); + ghttpPostAddString(post, "test2", "test%test!@#) $(%^(*&test"); + if(m_postFile) + { + static int memFileSize = 100000; + if(!m_memFile) + m_memFile = (char *)malloc(memFileSize); + memset(m_memFile, 0, memFileSize); + sprintf(m_memFile, "steve"); + ghttpPostAddFileFromMemory(post, "memfile", m_memFile, memFileSize, "steve.txt", NULL); + ghttpPostAddFileFromDisk(post, "diskfile", "../ghttpMain.c", "main.c", "text/html"); + } + +#if 1 + m_request = ghttpGetEx( + m_url, + m_headers, + NULL, + 0, + post, + (GHTTPBool)m_throttle, + (GHTTPBool)m_blocking, + ProgressCallback, + CompletedCallback, + this); +#else + m_request = ghttpPostEx( + m_url, + m_headers, + post, + (GHTTPBool)m_throttle, + (GHTTPBool)m_blocking, + ProgressCallback, + CompletedCallback, + this); +#endif + } + + if(m_request == -1) + MessageBox("Unable to start request"); + else if(m_url.Left(8).Compare("https://") == 0) + ghttpSetRequestEncryptionEngine(m_request, GHTTPEncryptionEngine_GameSpy); + + m_state = 0; + m_soFar = ""; + m_file = ""; + m_postObjects = ""; + m_postBytes = ""; + + UpdateData(FALSE); +} + +void CGhttpmfcDlg::OnCancel_() +{ + if(m_request >= 0) + ghttpCancelRequest(m_request); +} + +void CGhttpmfcDlg::OnTimer(UINT nIDEvent) +{ + if(nIDEvent == 50) + { + UpdateData(); + + if(!m_stepThink) + ghttpThink(); + + if(m_request >= 0) + { + const char * statusString; + int statusCode; + statusString = ghttpGetResponseStatus(m_request, &statusCode); + if(statusString) + m_status.Format("%d: %s", statusCode, statusString); + else + m_status.Empty(); + + const char * headers; + headers = ghttpGetHeaders(m_request); + if(headers) + m_headersRecv = headers; + else + m_headersRecv.Empty(); + + m_state = (int)ghttpGetState(m_request); + //added new states for asynch DNS lookup - set all to HOST_LOOKUP for radio buttons + if (m_state == GHTTPSocketInit || m_state == GHTTPHostLookup || m_state == GHTTPLookupPending) + m_state = 0; + UpdateData(FALSE); + } + } + + CDialog::OnTimer(nIDEvent); +} + +void CGhttpmfcDlg::OnDestroy() +{ + CDialog::OnDestroy(); + + FILE * fp; + fp = fopen("url.cache", "wt"); + if(fp) + { + fprintf(fp, "%s", m_url); + fclose(fp); + } + + fp = fopen("saveas.cache", "wt"); + if(fp) + { + fprintf(fp, "%s", m_saveAs); + fclose(fp); + } + + ghttpCleanup(); + + if(m_memFile) + free(m_memFile); + m_memFile = NULL; +} + +void CGhttpmfcDlg::OnThrottle() +{ + UpdateData(); + + if(m_request >= 0) + ghttpSetThrottle(m_request, (GHTTPBool)m_throttle); +} + +void CGhttpmfcDlg::OnThink() +{ + if(m_request >= 0) + ghttpThink(); +} + +void CGhttpmfcDlg::OnSetProxy() +{ + UpdateData(); + + ghttpSetProxy(m_proxy); +} + +// Copied from JED's ProxyInfo. Edited down for this app's purposes. +void CGhttpmfcDlg::OnIeSettings() +{ + HKEY key; + LONG result; + DWORD type; + DWORD data; + DWORD len; + CString str; + int nStart; + int nEnd; + + UpdateData(); + + // Open the IE settings in the registry. + //////////////////////////////////////// + result = RegOpenKeyEx(HKEY_CURRENT_USER, REGSTR_PATH_INTERNETSETTINGS, 0, KEY_READ, &key); + if(SUCCEEDED(result)) + { + // Is the proxy enabled? + //////////////////////// + len = sizeof(DWORD); + data = 0; + result = RegQueryValueEx(key, REGSTR_VAL_PROXYENABLE, 0, &type, (LPBYTE)&data, &len); + if(SUCCEEDED(result) && data) + { + //---------------------------------------------------------------------- + // + // The list of proxy servers to use + // + len = 0; + result = RegQueryValueEx(key, REGSTR_VAL_PROXYSERVER, 0, &type, NULL, &len); + result = RegQueryValueEx(key, REGSTR_VAL_PROXYSERVER, 0, &type, (LPBYTE)str.GetBuffer(len), &len); + str.ReleaseBuffer(); + if(SUCCEEDED(result) && (len > 0)) + { + // Find the http proxy. + // + // Use the same proxy for all protocols: "single_proxy_address:13" + // read as: server:port + // individualized protocols: "ftp=ftp_address;gopher=gopher_address:4;http=http_address:1;https=secure_address:2;socks=socks_address:5" + // read as: protocol=server:port;protocol=server:port + // If only protocol is to use proxy: "socks=socks_address:5" + // Apparently, ports are optional - I would presume that you should revert to the default port when it is missing + // + // First search for "http=[:port]". + /////////////////////////////////////////// + nStart = str.Find("http="); + if(nStart != -1) + { + nStart += 5; + nEnd = str.Find(';', nStart); + if(nEnd == -1) + nEnd = str.GetLength(); + } + else if(str.Find('=') == -1) + { + nStart = 0; + nEnd = str.GetLength(); + } + else + { + nStart = -1; + nEnd = 0; //won't be used; need to initialize to prevent compiler warning + } + + if((nStart != -1) && (nStart != nEnd)) + str = str.Mid(nStart, nEnd - nStart); + else + str = ""; + } + } + + // Cleanup. + /////////// + RegCloseKey(key); + } + + m_proxy = str; + + UpdateData(FALSE); + + OnSetProxy(); +} \ No newline at end of file diff --git a/code/gamespy/ghttp/ghttpmfc/ghttpmfcDlg.h b/code/gamespy/ghttp/ghttpmfc/ghttpmfcDlg.h new file mode 100644 index 00000000..11681d5d --- /dev/null +++ b/code/gamespy/ghttp/ghttpmfc/ghttpmfcDlg.h @@ -0,0 +1,79 @@ +// ghttpmfcDlg.h : header file +// + +#if !defined(AFX_GHTTPMFCDLG_H__14B35CAF_3960_4669_972D_59B741AA032C__INCLUDED_) +#define AFX_GHTTPMFCDLG_H__14B35CAF_3960_4669_972D_59B741AA032C__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +///////////////////////////////////////////////////////////////////////////// +// CGhttpmfcDlg dialog + +class CGhttpmfcDlg : public CDialog +{ +// Construction +public: + CGhttpmfcDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CGhttpmfcDlg) + enum { IDD = IDD_GHTTPMFC_DIALOG }; + CProgressCtrl m_progress; + BOOL m_blocking; + BOOL m_completedCallback; + CString m_headers; + BOOL m_progressCallback; + CString m_url; + int m_bufferSize; + CString m_saveAs; + BOOL m_userBuffer; + int m_type; + CString m_file; + CString m_soFar; + int m_state; + BOOL m_throttle; + CString m_headersRecv; + CString m_status; + BOOL m_stepThink; + BOOL m_postFile; + CString m_postObjects; + CString m_postBytes; + CString m_proxy; + //}}AFX_DATA + + GHTTPRequest m_request; + char * m_memFile; + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CGhttpmfcDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + HICON m_hIcon; + + // Generated message map functions + //{{AFX_MSG(CGhttpmfcDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + afx_msg void OnStart(); + afx_msg void OnCancel_(); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnDestroy(); + afx_msg void OnThrottle(); + afx_msg void OnThink(); + afx_msg void OnSetProxy(); + afx_msg void OnIeSettings(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_GHTTPMFCDLG_H__14B35CAF_3960_4669_972D_59B741AA032C__INCLUDED_) diff --git a/code/gamespy/ghttp/ghttpmfc/res/ghttpmfc.ico b/code/gamespy/ghttp/ghttpmfc/res/ghttpmfc.ico new file mode 100644 index 0000000000000000000000000000000000000000..7eef0bcbe6580a6f464d688906172c2d9de44262 GIT binary patch literal 1078 zcmc&zF>b>!3}jLb9s)T}@Kod(893@u8ajANzT`op9^o+)S?=nU(FD@%0s)Sg^oyC8{H z9myetc;MEP)59v(LMa~xK8Yu^jIR*H22uCFiq5%C{s7(PJi>o15i^bmX4(vPxWAio z9ryY#AU_jfnd047-@`)XzL?%iS$gQyFP{44kS9X)fN{{QoL~hO-&=q&20Zr*cxFAt PkaNE{wR~2C$NfnjhSXWT literal 0 HcmV?d00001 diff --git a/code/gamespy/ghttp/ghttpmfc/res/ghttpmfc.rc2 b/code/gamespy/ghttp/ghttpmfc/res/ghttpmfc.rc2 new file mode 100644 index 00000000..b5533ab2 --- /dev/null +++ b/code/gamespy/ghttp/ghttpmfc/res/ghttpmfc.rc2 @@ -0,0 +1,13 @@ +// +// GHTTPMFC.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/ghttp/ghttpmfc/resource.h b/code/gamespy/ghttp/ghttpmfc/resource.h new file mode 100644 index 00000000..bc4776ba --- /dev/null +++ b/code/gamespy/ghttp/ghttpmfc/resource.h @@ -0,0 +1,54 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by ghttpmfc.rc +// +#define IDD_GHTTPMFC_DIALOG 102 +#define IDR_MAINFRAME 128 +#define IDC_URL 1000 +#define IDC_HEADERS 1001 +#define IDC_BLOCKING 1002 +#define IDC_PROGRESS_CALLBACK 1003 +#define IDC_COMPLETED_CALLBACK 1004 +#define IDC_USER_BUFFER 1005 +#define IDC_GET_FILE 1006 +#define IDC_SAVE_FILE 1007 +#define IDC_STREAM_FILE 1008 +#define IDC_BUFFER_SIZE 1009 +#define IDC_FILE 1010 +#define IDC_SAVE_AS 1011 +#define IDC_SO_FAR 1012 +#define IDC_HEAD_FILE 1013 +#define IDC_HOST_LOOKUP 1014 +#define IDC_RADIO3 1015 +#define IDC_RADIO4 1016 +#define IDC_RADIO5 1017 +#define IDC_RADIO6 1018 +#define IDC_PROGRESS 1019 +#define IDC_THROTTLE 1020 +#define IDC_RADIO7 1021 +#define IDC_RADIO8 1022 +#define IDC_RADIO9 1023 +#define IDC_HEADERS_RECV 1024 +#define IDC_STATUS 1025 +#define IDC_POST 1026 +#define IDC_STEP_THINK 1027 +#define IDC_POST_FILE 1028 +#define IDC_POST_OBJECTS 1029 +#define IDC_POST_BYTES 1030 +#define IDC_PROXY 1031 +#define IDC_START 1050 +#define IDC_CANCEL 1051 +#define IDC_THINK 1052 +#define IDC_IE_SETTINGS 1053 +#define IDC_SET_PROXY 1054 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1029 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/gamespy/gstats/changelog.txt b/code/gamespy/gstats/changelog.txt new file mode 100644 index 00000000..f39098e6 --- /dev/null +++ b/code/gamespy/gstats/changelog.txt @@ -0,0 +1,102 @@ +Changelog for: GameSpy Stats & Tracking / Persistent Storage SDK +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 1.19.00 RMV RELEASE Released to Developer Site +12-10-2007 1.18.02 RMV OTHER Created VS2005 projects for persisttest and gstats +12-07-2007 1.18.01 SAH OTHER Fixed projects that were missing dependencies +08-06-2007 1.18.00 RMV RELEASE Released to Developer Site +12-15-2006 1.17.00 MJW RELEASE Released to Developer Site +10-05-2006 1.16.43 SAH FIX Updated MacOSX Makefile +09-28-2006 1.16.42 SAH FIX Fixed PS3 project to work with PS3 095.00x SDK; changed included libaries in linker input. +08-24-2006 1.16.41 SAH FIX Fixed VC7 project file +08-23-2006 1.16.40 SAH FIX Added a fix for len == -1 in RecvSessionKey function. +08-02-2006 1.16.39 SAH RELEASE Releasing to developer site +07-31-2006 1.16.38 SAH FIX Fixed PS3 project file - added post-build step to create *.SELF for execution +07-25-2006 1.16.37 SAH FIX Fixed NITRO project, include for crt0.o now above others so it add correct file +07-06-2006 1.16.36 SAH FIX Fixed PSP project file to not explicitly include the PSP linker file +06-30-2006 1.16.35 SAH FIX Fixed NITRO projects & linker command files (to work with CW 2.0/NitroSDK 3.1) + SAH FIX Fixed Linux makefiles +05-31-2006 1.16.34 SAH RELEASE Releasing to developer site +05-30-2006 1.16.33 SAH FIX Fixed PS3 projects to work with PS3(084_001 SDK) +05-25-2006 1.16.32 SAH FIX Changed PSP project warning levels +05-19-2006 1.16.31 SAH FIX Added gsTestMain.c to nitro CodeWarrior project +05-15-2006 1.16.30 SAH FIX Added "PS3 Release" configuration(s) to project +05-11-2006 1.16.29 SN FIX Modified the update game message to include the connid so that games aren't discarded when a disconnect occurs +04-25-2006 1.16.28 SAH RELEASE Releasing to developer site +04-24-2006 1.16.28 SAH FIX Fixed Nitro project files to work on build machine +04-20-2006 1.16.27 SAH FIX Removed unused variables, moved GSI_UNUSED above return calls +01-27-2006 1.16.26 SN RELEASE Releasing to developer site +01-27-2006 1.16.26 SN FIX Added PSP to test_main define in statstest + SN OTHER Added psp prodg project and solution to sgv for statstest +01-27-2006 1.16.25 SN FIX Added PSP to test_main define in persisttest + SN OTHER Added psp prodg project and solution to sgv for persisttest +12-19-2005 1.16.24 SN OTHER Cleaned up projects, adding any missing common code. + Replaced gstats project with statstest in its subdirectory +11-17-2005 1.16.23 DES FIX Updated Nitro Makefile. +11-14-2005 1.16.22 DES FIX Updated the OSX Makefiles. + DES FEATURE Added GSI_DOMAIN_NAME support. +09-23-2005 1.16.21 DES FEATURE Updated DS support +07-28-2005 1.16.20 SN RELEASE Releasing to developer site. +07-28-2005 1.16.20 SN FIX fixed PS2 projects to use new common code +06-03-2005 1.16.19 SN RELEASE Releasing to developer site. +05-05-2005 1.16.19 BED FIX Updated project files to use new common folder. +05-03-2005 1.16.18 SN FIX Removed deprecated MFC code for Visual Studio .NET projects + SN OTHER Created Visual Studio .NET projects +04-28-2005 1.16.17 SN RELEASE Releasing to developer site. +04-27-2005 1.16.17 DES RELEASE Limited release to Nintendo DS developers. +04-27-2005 1.16.17 DES FIX Changed the default connect timeout to 20 seconds for the DS. + DES FIX Added extra printfs to statstest. +04-25-2005 1.16.16 DES FIX Check for socket error when receiving the challenge. + DES CLEANUP Removed printf()s from gstats.c +04-04-2005 1.16.15 SN RELEASE Releasing to developer site. +04-03-2005 1.16.15 SN FIX Added a StatsThink function due to socket api buffer clogging +03-25-2005 1.16.14 SN FIX Fixed bug to clean g_statsgame when FreeGame was called with local statsgame_t and both were the same object. +03-16-2005 1.16.13 SN FIX Fixed PS2 bug not being able to print hex chars correctly. +03-14-2005 1.16.13 DES FEATURE Nintendo DS support +12-28-2004 1.16.12 SN FIX Added const qualifiers to unmodified formal function parameters +09-16-2004 1.16.11 SN RELEASE Releasing to developer site. +09-16-2004 1.16.11 SN FIX Renamed a global variable to avoid MacOS X naming confliction +09-09-2004 1.16.10 BED FEATURE Added InitStatsAsync and InitStatsThink. +08-27-2004 1.16.09 DES CLEANUP Removed MacOS style includes + DES CLEANUP Updated Win32 project configurations + DES CLEANUP General Unicode cleanup + DES CLEANUP Fixed warnings under OSX + DES CLEANUP Updated OSX Makefile +08-05-2004 1.16.08 BED RELEASE Releasing to developer site. +08-05-2004 1.16.08 BED FIX Updated samples to use GT2 instead of legacy GT1 SDK +07-20-2004 1.16.07 SN FIX Updated code with explicit casts to remove implicit cast error + when compiling at highest level and warnings treated as errors. +07-20-2004 1.16.06 BED FEATURE Added Remote Auth hooks +06-18-2004 1.16.05 BED RELEASE Releasing to developer site. + FEATURE Added PS2 Insock support +11-10-2003 1.16.04 DES RELEASE Releasing to developer site. +11-07-2003 1.16.04 BED FIX Removed CodeWarrior strictest warnings. +11-07-2003 1.16.03 DES FIX Updated the linux and PS2 makefiles. +11-04-2003 1.16.02 DES FEATURE Added availability check code. +10-30-2003 1.16.01 DES FIX Updated gp_stats to work with the latest versions of GP and stats. +10-21-2003 1.16.00 BED RELEASE Releasing to developer site. (UNIQUE NICK AND UNICODE SUPPORT) +10-01-2003 1.16.00 DDW FEATURE Added modified time checking/reporting to persistent storage + NOTE: Persistent Storage callbacks modified w/ additional parameter + Prepend hostname with gamename for unique DNS names +09-08-2003 1.15.11 BED FEATURE Added UTF-8 wrapper for UNICODE support. +07-24-2003 1.15.10 DES RELEASE Releasing to developer site. +07-18-2003 1.15.10 BED FEATURE Added CodeWarrior (PS2) sample project file. + BED CLEANUP General cleanup to remove CodeWarrior warnings. +07-17-2003 1.15.09 DES CLEANUP Cleaned up the PS2 Makefiles, they now use Makefile.commmon. +07-14-2003 1.15.08 DES FIX ALLOW_DISK is now undef'd whenever NOFILE is defined. + FIX Changed __mips64 check to _PS2 check. + CLEANUP No need to make remove() an empty define on systems with NOFILE defined. + BED FEATURE Added ProDG sample project files to gstats and persistest +07-03-2003 1.15.07 BED FIX Added gtUtility.c to the ladderTrack sample. +05-09-2003 1.15.06 DES CLEANUP Removed Dreamcast support. + FIX Metrowerks for Win32 is no longer falsely identified as MacOS. +03-03-2003 1.15.05 DES CLEANUP General cleanup to remove warnings. +12-19-2002 1.15.04 DES RELEASE Releasing to developer site. +12-19-2002 1.15.04 DES CLEANUP Removed assert.h include. +12-13-2002 1.15.03 DES FEATURE Added PS2 eenet stack support. +11-22-2002 1.15.02 DES RELEASE Releasing to developer site. +11-20-2002 1.15.02 DES CLEANUP Cleaned up to remove PS2 compiler warnings. +11-15-2002 1.15.01 DES CLEANUP Updated persisttest PS2 Makefile +09-25-2002 1.15.00 DDW OTHER Changelog started diff --git a/code/gamespy/gstats/gbucket.c b/code/gamespy/gstats/gbucket.c new file mode 100644 index 00000000..d786fe2c --- /dev/null +++ b/code/gamespy/gstats/gbucket.c @@ -0,0 +1,396 @@ +/****** +gbucket.c +GameSpy Stats/Tracking SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +******/ + +/******** +INCLUDES +********/ +#include "../common/gsCommon.h" +#include "gbucket.h" +#include "../hashtable.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/******** +TYPEDEFS +********/ +struct bucketset_s +{ + HashTable buckets; +}; + + +typedef struct bucket_s +{ + char *name; + BucketType type; + int nvals; //for averaging + union + { + int ival; + double fval; + char *sval; + } vals; +} bucket_t; + +typedef struct dumpdata_s +{ + char *data; + unsigned int maxlen; + unsigned int len; +} dumpdata_t; + +/******** +PROTOTYPES +********/ +static void DumpMap(void *elem, void *clientData); +static void BucketFree(void *elem); +static int BucketCompare(const void *entry1, const void *entry2); +static int BucketHash(const void *elem, int numbuckets); +static void *DoSet(bucket_t *pbucket, void *value); +static void *DoGet(bucket_t *pbucket); +static bucket_t *DoFind(bucketset_t set, char *name); + + +/******** +VARS +********/ +bucketset_t g_buckets; + +/****************************************************************************/ +/* PUBLIC FUNCTIONS */ +/****************************************************************************/ +bucketset_t NewBucketSet(void) +{ + bucketset_t set; + + set = (bucketset_t)gsimalloc(sizeof (struct bucketset_s)); + assert(set); + set->buckets = TableNew(sizeof(bucket_t),32,BucketHash, BucketCompare, BucketFree); + + g_buckets = set; + return set; +} + +void FreeBucketSet(bucketset_t set) +{ + assert(set); + assert(set->buckets); + TableFree(set->buckets); + gsifree(set); +} + +char *DumpBucketSet(bucketset_t set) +{ + dumpdata_t data; + if (set == NULL) + set = g_buckets; + assert(set); + data.data = (char *)gsimalloc(128); //alloc an initial buffer + data.data[0] = 0; + data.len = 0; + data.maxlen = 128; + TableMap(set->buckets,DumpMap, &data); + return data.data; +} + +void *BucketNew(bucketset_t set, char *name, BucketType type, void *initialvalue) +{ + bucket_t bucket; + + if (set == NULL) + set = g_buckets; + assert(set); + bucket.name = goastrdup(name); + bucket.type = type; + bucket.vals.sval = NULL; + bucket.nvals = 1; + DoSet(&bucket, initialvalue); + TableEnter(set->buckets,&bucket); + return DoGet(DoFind(set, name)); +} + +void *BucketSet(bucketset_t set, char *name,void *value) +{ + bucket_t *pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + pbucket->nvals = 0; + return DoSet(pbucket,value); +} + +void *BucketGet(bucketset_t set, char *name) +{ + return DoGet(DoFind(set,name)); +} + +void *BucketAdd(bucketset_t set, char *name, void *value) +{ + bucket_t *pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + if (pbucket->type == bt_int) + return DoSet(pbucket, bint( (*(int *)DoGet(pbucket)) + (*(int *)value))); + if (pbucket->type == bt_float) + return DoSet(pbucket, bfloat( (*(double *)DoGet(pbucket)) + (*(double *)value))); + //else, string -- just concat + return BucketConcat(set, name, value); +} + +void *BucketSub(bucketset_t set, char *name, void *value) +{ + bucket_t *pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + if (pbucket->type == bt_int) + return DoSet(pbucket, bint( (*(int *)DoGet(pbucket)) - (*(int *)value))); + if (pbucket->type == bt_float) + return DoSet(pbucket, bfloat( (*(double *)DoGet(pbucket)) - (*(double *)value))); + //else, string -- just ignore + return DoGet(pbucket); + +} + +void *BucketMult(bucketset_t set, char *name, void *value) +{ + bucket_t *pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + if (pbucket->type == bt_int) + return DoSet(pbucket, bint( (*(int *)DoGet(pbucket)) * (*(int *)value))); + if (pbucket->type == bt_float) + return DoSet(pbucket, bfloat( (*(double *)DoGet(pbucket)) * (*(double *)value))); + //else, string -- just ignore + return DoGet(pbucket); +} + +void *BucketDiv(bucketset_t set, char *name, void *value) +{ + bucket_t *pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + if (pbucket->type == bt_int) + return DoSet(pbucket, bint( (*(int *)DoGet(pbucket)) / (*(int *)value))); + if (pbucket->type == bt_float) + return DoSet(pbucket, bfloat( (*(double *)DoGet(pbucket)) / (*(double *)value))); + //else, string -- just ignore + return DoGet(pbucket); +} + +void *BucketConcat(bucketset_t set, char *name, void *value) +{ + bucket_t *pbucket = DoFind(set, name); + char *temp, *s; + if (!pbucket) + return NULL; + + assert(pbucket->type == bt_string); + s = DoGet(pbucket); + temp = (char *)gsimalloc(strlen(s) + strlen(value) + 1); + strcpy(temp,s); + strcat(temp, value); + + DoSet(pbucket, temp); + gsifree(temp); + + return DoGet(pbucket); +} + +#define AVG(cur, new, num) (((cur * num) + new) / (++num)) +void *BucketAvg(bucketset_t set, char *name, void *value) +{ + bucket_t *pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + if (pbucket->type == bt_int) + return DoSet(pbucket, bint( AVG((*(int *)DoGet(pbucket)), (*(int *)value), pbucket->nvals))); + if (pbucket->type == bt_float) + return DoSet(pbucket, bfloat( AVG((*(double *)DoGet(pbucket)), (*(double *)value), pbucket->nvals))); + //else, string -- just ignore + return DoGet(pbucket); +} + +/* Note: these are NOT thread safe! */ +void *bint(int i) +{ + static int j; + j=i; + return &j; +} + +void *bfloat(double f) +{ + static double g; + g=f; + return &g; +} + + + +/*********** + * UTILITY FUNCTIONS + **********/ +static void DumpMap(void *elem, void *clientData) +{ + bucket_t *bucket = (bucket_t *)elem; + dumpdata_t *data = (dumpdata_t *)clientData; + unsigned int minlen; + + //find out if we need to resize! + minlen = strlen(bucket->name) + 3; + if (bucket->type == bt_int || bucket->type == bt_float) + minlen += data->len + 16; + else if (bucket->type == bt_string) + minlen += data->len + strlen(bucket->vals.sval); + + if (data->maxlen <= minlen) + { + if (data->maxlen == 0) + data->maxlen = minlen * 2; + else + data->maxlen *= 2; + data->data = gsirealloc(data->data, data->maxlen); + + } + + switch (bucket->type) + { + case bt_int: + data->len += (unsigned int)sprintf(data->data + data->len,"\\%s\\%d",bucket->name,bucket->vals.ival); + break; + case bt_float: + data->len += (unsigned int)sprintf(data->data + data->len,"\\%s\\%f",bucket->name,bucket->vals.fval); + break; + case bt_string: + data->len += (unsigned int)sprintf(data->data + data->len,"\\%s\\%s",bucket->name,bucket->vals.sval); + break; + + } +} + + +static char *stripchars(char *s) +{ + char *p = s; + while (*s) + { + if (*s == '\\') + *s = '/'; + s++; + } + return p; +} + +static void *DoSet(bucket_t *pbucket, void *value) +{ + if (pbucket->type == bt_int) + pbucket->vals.ival = *(int*)value; + else if (pbucket->type == bt_float) + pbucket->vals.fval = *(double *)value; + else if (pbucket->type == bt_string) + { + if (pbucket->vals.sval != NULL) + gsifree(pbucket->vals.sval); + pbucket->vals.sval = (value == NULL ? NULL : stripchars(goastrdup((char *)value))); + } + return DoGet(pbucket); +} + +static void *DoGet(bucket_t *pbucket) +{ + if (!pbucket) + return NULL; + if (pbucket->type == bt_string) + return pbucket->vals.sval; + else //since it's a union, we can return any member + return &pbucket->vals.sval; + +} + +static bucket_t *DoFind(bucketset_t set, char *name) +{ + bucket_t tbucket; + + if (set == NULL) + set = g_buckets; + assert(set); + + tbucket.name = name; + return (bucket_t *)TableLookup(set->buckets,&tbucket); +} + + + +/* NonTermHash + * ---------- + * The hash code is computed using a method called "linear congruence." + * This hash function has the additional feature of being case-insensitive, + */ +#define MULTIPLIER -1664117991 +static int BucketHash(const void *elem, int numbuckets) +{ + unsigned int i; + unsigned int len; + unsigned int hashcode = 0; + + char *s = ((bucket_t *)elem)->name; + len = strlen(s); + for (i = 0; i < len ; i++) { + hashcode = (unsigned int)((int)hashcode * MULTIPLIER + tolower(s[i])); + } + return (int)(hashcode % numbuckets); +} + + +/* CaseInsensitiveCompare + * ---------------------- + * Comparison function passed to qsort to sort an array of + * strings in alphabetical order. It uses strcasecmp which is + * identical to strcmp, except that it doesn't consider case of the + * characters when comparing them, thus it sorts case-insensitively. + */ +static int CaseInsensitiveCompare(const void *entry1, const void *entry2) +{ + return strcasecmp(*(char **)entry1,*(char **)entry2); +} + +/* keyval + * Compares two buckets (case insensative) + */ +static int BucketCompare(const void *entry1, const void *entry2) +{ + + return CaseInsensitiveCompare(&((bucket_t *)entry1)->name, + &((bucket_t *)entry2)->name); +} + + +/* KeyValFree + * Frees the memory INSIDE a Bucket structure + */ +static void BucketFree(void *elem) +{ + gsifree(((bucket_t *)elem)->name); + if (((bucket_t *)elem)->type == bt_string && ((bucket_t *)elem)->vals.sval != NULL) + gsifree(((bucket_t *)elem)->vals.sval); + +} + +#ifdef __cplusplus +} +#endif diff --git a/code/gamespy/gstats/gbucket.h b/code/gamespy/gstats/gbucket.h new file mode 100644 index 00000000..add085f6 --- /dev/null +++ b/code/gamespy/gstats/gbucket.h @@ -0,0 +1,52 @@ +/****** +gbucket.h +GameSpy Stats/Tracking SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + +Please see the GameSpy Stats and Tracking SDK for more info +You should not need to use the functions in this file, they +are used to manage the buckets by the gstats SDK. +Use the type-safe bucket functions in the gstats SDK instead. +******/ + + +#ifndef _GBUCKET_H_ +#define _GBUCKET_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct bucketset_s *bucketset_t; +typedef enum {bt_int, bt_float, bt_string} BucketType; + +bucketset_t NewBucketSet(void); +void FreeBucketSet(bucketset_t set); +char *DumpBucketSet(bucketset_t set); + +void *BucketNew(bucketset_t set, char *name, BucketType type, void *initialvalue); +void *BucketSet(bucketset_t set, char *name,void *value); +void *BucketAdd(bucketset_t set, char *name, void *value); +void *BucketSub(bucketset_t set, char *name, void *value); +void *BucketMult(bucketset_t set, char *name, void *value); +void *BucketDiv(bucketset_t set, char *name, void *value); +void *BucketConcat(bucketset_t set, char *name, void *value); +void *BucketAvg(bucketset_t set, char *name, void *value); +void *BucketGet(bucketset_t set, char *name); + +/* Helper functions */ +void *bint(int i); +void *bfloat(double f); +#define bstring(a) ((void *)a) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/gstats/gp_stats/StdAfx.cpp b/code/gamespy/gstats/gp_stats/StdAfx.cpp new file mode 100644 index 00000000..9b9d9667 --- /dev/null +++ b/code/gamespy/gstats/gp_stats/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// gp_stats.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/code/gamespy/gstats/gp_stats/StdAfx.h b/code/gamespy/gstats/gp_stats/StdAfx.h new file mode 100644 index 00000000..840a0e6b --- /dev/null +++ b/code/gamespy/gstats/gp_stats/StdAfx.h @@ -0,0 +1,27 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__14D457F1_07B9_4AB5_97B4_B82E57AFA0D4__INCLUDED_) +#define AFX_STDAFX_H__14D457F1_07B9_4AB5_97B4_B82E57AFA0D4__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__14D457F1_07B9_4AB5_97B4_B82E57AFA0D4__INCLUDED_) diff --git a/code/gamespy/gstats/gp_stats/gp_stats.cpp b/code/gamespy/gstats/gp_stats/gp_stats.cpp new file mode 100644 index 00000000..51822d83 --- /dev/null +++ b/code/gamespy/gstats/gp_stats/gp_stats.cpp @@ -0,0 +1,74 @@ +// gp_stats.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "gp_stats.h" +#include "gp_statsDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CGp_statsApp + +BEGIN_MESSAGE_MAP(CGp_statsApp, CWinApp) + //{{AFX_MSG_MAP(CGp_statsApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CGp_statsApp construction + +CGp_statsApp::CGp_statsApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CGp_statsApp object + +CGp_statsApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CGp_statsApp initialization + +BOOL CGp_statsApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. +/* +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif +*/ + CGp_statsDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + // TODO: Place code here to handle when the dialog is + // dismissed with OK + } + else if (nResponse == IDCANCEL) + { + // TODO: Place code here to handle when the dialog is + // dismissed with Cancel + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/code/gamespy/gstats/gp_stats/gp_stats.h b/code/gamespy/gstats/gp_stats/gp_stats.h new file mode 100644 index 00000000..63b5a7f2 --- /dev/null +++ b/code/gamespy/gstats/gp_stats/gp_stats.h @@ -0,0 +1,49 @@ +// gp_stats.h : main header file for the GP_STATS application +// + +#if !defined(AFX_GP_STATS_H__90B1DDCE_A86E_49A8_B136_0B29EA43F6B4__INCLUDED_) +#define AFX_GP_STATS_H__90B1DDCE_A86E_49A8_B136_0B29EA43F6B4__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CGp_statsApp: +// See gp_stats.cpp for the implementation of this class +// + +class CGp_statsApp : public CWinApp +{ +public: + CGp_statsApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CGp_statsApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CGp_statsApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_GP_STATS_H__90B1DDCE_A86E_49A8_B136_0B29EA43F6B4__INCLUDED_) diff --git a/code/gamespy/gstats/gp_stats/gp_stats.rc b/code/gamespy/gstats/gp_stats/gp_stats.rc new file mode 100644 index 00000000..dee94b35 --- /dev/null +++ b/code/gamespy/gstats/gp_stats/gp_stats.rc @@ -0,0 +1,201 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\gp_stats.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\gp_stats.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_GP_STATS_DIALOG DIALOGEX 0, 0, 197, 266 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "Tracking Stats by Account" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Quit",IDOK,105,85,50,14 + GROUPBOX "Account",IDC_STATIC,10,10,175,95 + LTEXT "E-mail:",IDC_STATIC,20,23,22,8 + LTEXT "Nick:",IDC_STATIC,20,38,18,8 + LTEXT "Password:",IDC_STATIC,20,53,34,8 + LTEXT "Profile ID:",IDC_STATIC,20,68,32,8 + EDITTEXT IDC_EMAIL,65,20,110,12,ES_AUTOHSCROLL + EDITTEXT IDC_NICK,65,35,110,12,ES_AUTOHSCROLL + EDITTEXT IDC_PASSWORD,65,50,110,12,ES_PASSWORD | ES_AUTOHSCROLL + EDITTEXT IDC_PROFILE_ID,65,65,110,12,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "Authenticate",IDC_AUTHENTICATE,35,85,50,14 + GROUPBOX "Persistant Data",IDC_STATIC,10,115,175,140 + CONTROL "Private Read/Write",IDC_PRIVATE_RW,"Button", + BS_AUTORADIOBUTTON | WS_GROUP,20,130,77,10 + CONTROL "Private Read-Only",IDC_PRIVATE_RO,"Button", + BS_AUTORADIOBUTTON,20,145,73,10 + CONTROL "Public Read/Write",IDC_PUBLIC_RW,"Button", + BS_AUTORADIOBUTTON,100,130,75,10 + CONTROL "Public Read-Only",IDC_PUBLIC_RO,"Button", + BS_AUTORADIOBUTTON,100,145,71,10 + PUSHBUTTON "Get",IDC_GET,35,160,50,14,WS_GROUP + PUSHBUTTON "Set",IDC_SET,110,160,50,14 + LTEXT "Key:",IDC_STATIC,20,199,15,8 + LTEXT "Value:",IDC_STATIC,20,181,21,8 + EDITTEXT IDC_VALUE,50,180,125,12,ES_AUTOHSCROLL + LISTBOX IDC_KEYS,50,195,70,50,LBS_SORT | LBS_NOINTEGRALHEIGHT | + WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Add Key",IDC_ADD,125,200,50,14 + EDITTEXT IDC_NEW_KEY,125,215,50,14,ES_AUTOHSCROLL +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "gp_stats MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "gp_stats\0" + VALUE "LegalCopyright", "Copyright (C) 2000\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "gp_stats.EXE\0" + VALUE "ProductName", "gp_stats Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_GP_STATS_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 190 + TOPMARGIN, 7 + BOTTOMMARGIN, 259 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\gp_stats.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/gamespy/gstats/gp_stats/gp_statsDlg.cpp b/code/gamespy/gstats/gp_stats/gp_statsDlg.cpp new file mode 100644 index 00000000..6abd7325 --- /dev/null +++ b/code/gamespy/gstats/gp_stats/gp_statsDlg.cpp @@ -0,0 +1,733 @@ +// gp_statsDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "gp_stats.h" +#include "gp_statsDlg.h" +#include "../gpersist.h" +#include "../../ghttp/ghttp.h" +#include "../../common/gsAvailable.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +// These are controls that are enabled when authenticated, +// and disabled when not authenticated. +////////////////////////////////////////////////////////// +CWnd * ToggleControls[64]; + +///////////////////////////////////////////////////////////////////////////// +// CGp_statsDlg dialog + +CGp_statsDlg::CGp_statsDlg(CWnd* pParent /*=NULL*/) + : CDialog(CGp_statsDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CGp_statsDlg) + m_email = _T("dan@gamespy.com"); + m_nick = _T("mrpants"); + m_password = _T("mrpants"); + m_profileID = 0; + m_type = 0; + m_value = _T(""); + m_newKey = _T(""); + //}}AFX_DATA_INIT + // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); +} + +void CGp_statsDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CGp_statsDlg) + DDX_Control(pDX, IDC_KEYS, m_keys); + DDX_Text(pDX, IDC_EMAIL, m_email); + DDX_Text(pDX, IDC_NICK, m_nick); + DDX_Text(pDX, IDC_PASSWORD, m_password); + DDX_Text(pDX, IDC_PROFILE_ID, m_profileID); + DDX_Radio(pDX, IDC_PRIVATE_RW, m_type); + DDX_Text(pDX, IDC_VALUE, m_value); + DDX_Text(pDX, IDC_NEW_KEY, m_newKey); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CGp_statsDlg, CDialog) + //{{AFX_MSG_MAP(CGp_statsDlg) + ON_BN_CLICKED(IDC_AUTHENTICATE, OnAuthenticate) + ON_WM_DESTROY() + ON_BN_CLICKED(IDC_GET, OnGet) + ON_BN_CLICKED(IDC_SET, OnSet) + ON_BN_CLICKED(IDC_ADD, OnAdd) + ON_LBN_SELCHANGE(IDC_KEYS, OnSelchangeKeys) + ON_EN_CHANGE(IDC_VALUE, OnChangeValue) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CGp_statsDlg message handlers + +BOOL CGp_statsDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Set the icon for this dialog. The framework does this automatically + // when the application's main window is not a dialog + SetIcon(m_hIcon, TRUE); // Set big icon + SetIcon(m_hIcon, FALSE); // Set small icon + + // Build the list of toggle controls. + ///////////////////////////////////// + int num = 0; +#define ADD_TOGGLE(id) ToggleControls[num++] = this->GetDlgItem(id) + ADD_TOGGLE(IDC_PRIVATE_RW); + ADD_TOGGLE(IDC_PRIVATE_RO); + ADD_TOGGLE(IDC_PUBLIC_RW); + ADD_TOGGLE(IDC_PUBLIC_RO); + ADD_TOGGLE(IDC_GET); + ADD_TOGGLE(IDC_SET); + ADD_TOGGLE(IDC_VALUE); + ADD_TOGGLE(IDC_KEYS); + ADD_TOGGLE(IDC_ADD); + ADD_TOGGLE(IDC_NEW_KEY); + ToggleControls[num++] = NULL; + + // Init data. + ///////////// + m_authenticated = FALSE; + m_gp = NULL; + + // Put ourselves in the unauthenticated stats. + ////////////////////////////////////////////// + UnAuthenticate(); + + // Init the connection to the stats manager. + //////////////////////////////////////////// + CheckStatsConnection(); + + // Init GP. + /////////// + if(gpInitialize(&m_gp, 0, 0, GP_PARTNERID_GAMESPY) != GP_NO_ERROR) + MessageBox("FAILED TO INITIALIZE GP!!!"); + + return TRUE; // return TRUE unless you set the focus to a control +} + +void CGp_statsDlg::OnDestroy() +{ + CDialog::OnDestroy(); + + ClearKeys(); + + // Close the stats connection. + ////////////////////////////// + CloseStatsConnection(); + + // Shutdown GP. + /////////////// + gpDestroy(&m_gp); + + // Shutdown GHTTP. + ////////////////// + ghttpCleanup(); +} + +void CGp_statsDlg::Authenticated() +{ + m_authenticated = TRUE; + + // Enable controls. + /////////////////// + int i; + for(i = 0 ; ToggleControls[i] ; i++) + ToggleControls[i]->EnableWindow(); +} + +void CGp_statsDlg::UnAuthenticate() +{ + m_authenticated = FALSE; + m_profileID = 0; + + // Disable controls. + //////////////////// + int i; + for(i = 0 ; ToggleControls[i] ; i++) + ToggleControls[i]->EnableWindow(FALSE); +} + +BOOL CGp_statsDlg::CheckStatsConnection() +{ + // Are we connected? + //////////////////// + if(IsStatsConnected()) + return TRUE; + + // Set the gamename and secret key. + /////////////////////////////////// +#if 1 + strcpy(gcd_gamename, "gmtest"); + gcd_secret_key[0] = 'H'; + gcd_secret_key[1] = 'A'; + gcd_secret_key[2] = '6'; + gcd_secret_key[3] = 'z'; + gcd_secret_key[4] = 'k'; + gcd_secret_key[5] = 'S'; + gcd_secret_key[7] = '\0'; +#else + strcpy(gcd_gamename, "excessive"); + gcd_secret_key[0] = 'G'; + gcd_secret_key[1] = 'n'; + gcd_secret_key[2] = '3'; + gcd_secret_key[3] = 'a'; + gcd_secret_key[4] = 'Y'; + gcd_secret_key[5] = '9'; + gcd_secret_key[7] = '\0'; +#endif + + // check that the game's backend is available + GSIACResult ac_result; + GSIStartAvailableCheck(gcd_gamename); + while((ac_result = GSIAvailableCheckThink()) == GSIACWaiting) + msleep(5); + if(ac_result != GSIACAvailable) + { + MessageBox("The backend is not available\n"); + return TRUE; + } + + // Try to connect. + ////////////////// + int result = InitStatsConnection(0); + if(result == GE_NOERROR) + return TRUE; + + // Error! + ///////// + CString message; + if(result == GE_NOSOCKET) + message = "Unable to create a socket."; + else if(result == GE_NODNS) + message = "Unable to resolve a DNS name."; + else if(result == GE_NOCONNECT) + message = "Unable to connect to stats server, or connection lost."; + else if(result == GE_DATAERROR) + message = "Bad data from the stats server."; + else + message = "Error."; + MessageBox(message, "Error connecting to the stats server"); + + return FALSE; +} + +void CGp_statsDlg::SendPassword() +{ + // Form the URL for the request. + //////////////////////////////// + CString URL = "http://gamespyarcade.com/software/reqemail.asp?email="; + URL += m_email; + + // Set the wait cursor. + /////////////////////// + HCURSOR hPrevCursor = GetCursor(); + SetCursor(LoadCursor(NULL, IDC_WAIT)); + + // Do the request. + ////////////////// + ghttpGetFile(URL, GHTTPTrue, NULL, NULL); + + // Reset the previous cursor. + ///////////////////////////// + SetCursor(hPrevCursor); +} + +void CreateAccountCallback(GPConnection * connection, void * arg_, void * param) +{ + GPNewUserResponseArg * arg = (GPNewUserResponseArg *)arg_; + int * pid = (int *)param; + + // Store the result. + //////////////////// + if(arg->result == GP_NO_ERROR) + *pid = arg->profile; + else + { + // If the nick was already in use, just pretend we created it. + ////////////////////////////////////////////////////////////// + GPErrorCode errorCode; + gpGetErrorCode(connection, &errorCode); + if(errorCode == GP_NEWUSER_BAD_NICK) + *pid = arg->profile; + else + *pid = 0; + } +} + +BOOL CGp_statsDlg::CreateAccount() +{ + int pid; + int rcode; + + // Create the account. + ////////////////////// + GPResult result = gpNewUser( + &m_gp, + m_nick, + NULL, + m_email, + m_password, + NULL, + GP_BLOCKING, + CreateAccountCallback, + &pid); + if(result != GP_NO_ERROR) + { + MessageBox("There was an error creating the account."); + return FALSE; + } + + // Check for success. + ///////////////////// + if(pid) + { + m_profileID = pid; + return TRUE; + } + + // Get the error code. + ////////////////////// + GPErrorCode errorCode; + gpGetErrorCode(&m_gp, &errorCode); + + // Handle the error code. + ///////////////////////// + if(errorCode == GP_NEWUSER_BAD_PASSWORD) + { + rcode = MessageBox( + "You have entered an incorrect password for this e-mail address\n" + "Would you like the password sent to the e-mail address?", + NULL, + MB_YESNO); + + // If no, we're done. + ///////////////////// + if(rcode == IDNO) + return FALSE; + + // Send the password. + ///////////////////// + SendPassword(); + + return FALSE; + } + + // An unknown error code. + ///////////////////////// + MessageBox("There was an error creating the account."); + return FALSE; +} + +void CheckAccountCallback(GPConnection * connection, void * arg_, void * param) +{ + GPCheckResponseArg * arg = (GPCheckResponseArg *)arg_; + int * pid = (int *)param; + + // Store the result. + //////////////////// + if(arg->result == GP_NO_ERROR) + *pid = arg->profile; + else + *pid = 0; +} + +BOOL CGp_statsDlg::CheckAccount() +{ + int pid; + int rcode; + + // Check for the account. + ///////////////////////// + GPResult result = gpCheckUser( + &m_gp, + m_nick, + m_email, + m_password, + GP_BLOCKING, + CheckAccountCallback, + &pid); + if(result != GP_NO_ERROR) + { + MessageBox("There was an error authenticating the account."); + return FALSE; + } + + // Check for success. + ///////////////////// + if(pid) + { + m_profileID = pid; + return TRUE; + } + + // Get the error code. + ////////////////////// + GPErrorCode errorCode; + gpGetErrorCode(&m_gp, &errorCode); + + // Handle the error code. + ///////////////////////// + if(errorCode == GP_CHECK_BAD_EMAIL) + { + // Ask if they want to create the account. + ////////////////////////////////////////// + rcode = MessageBox( + "This account does not exist.\n" + "Would you like to create it?", + NULL, + MB_YESNO); + + // If no, we're done. + ///////////////////// + if(rcode == IDNO) + return FALSE; + + // Create the account. + ////////////////////// + return CreateAccount(); + } + else if(errorCode == GP_CHECK_BAD_NICK) + { + rcode = MessageBox( + "There are no profiles under this account with the nick you have entered\n" + "Would you like to create one?", + NULL, + MB_YESNO); + + // If no, we're done. + ///////////////////// + if(rcode == IDNO) + return FALSE; + + // Create the account. + ////////////////////// + return CreateAccount(); + } + else if(errorCode == GP_CHECK_BAD_PASSWORD) + { + rcode = MessageBox( + "You have entered an incorrect password for this e-mail address\n" + "Would you like the password sent to the e-mail address?", + NULL, + MB_YESNO); + + // If no, we're done. + ///////////////////// + if(rcode == IDNO) + return FALSE; + + // Send the password. + ///////////////////// + SendPassword(); + + return FALSE; + } + + // An unknown error code. + ///////////////////////// + MessageBox("There was an error authenticating the account."); + return FALSE; +} + +BOOL statsAuthFinished; +void StatsAuthenticationCallback(int localid, int profileid, int authenticated, char *errmsg, void *instance) +{ + int * result = (int *)instance; + + *result = authenticated; + + if(authenticated != 1) + MessageBox(NULL, errmsg, "Error authenticating with the stats backend", MB_OK); + + statsAuthFinished = TRUE; +} + +BOOL CGp_statsDlg::StatsAuthentication() +{ + char response[33]; + int result; + + // The auth call. + ///////////////// + statsAuthFinished = FALSE; + PreAuthenticatePlayerPM( + 0, + m_profileID, + GenerateAuth(GetChallenge(NULL), (char *)(LPCSTR)m_password, response), + StatsAuthenticationCallback, + &result); + + // Wait for it to finish. + ///////////////////////// + while(!statsAuthFinished) + if(!PersistThink()) + CheckStatsConnection(); + + return (result == 1); +} + +void CGp_statsDlg::OnAuthenticate() +{ + // Update dialog members. + ///////////////////////// + UpdateData(); + + // Sanity check args. + ///////////////////// + if(!m_email.GetLength() || !m_nick.GetLength() || !m_password.GetLength()) + { + MessageBox("E-mail, nick, and password must not be blank."); + return; + } + + // If we're authenticated, unauthenticate. + ////////////////////////////////////////// + if(m_authenticated) + UnAuthenticate(); + + // Check the account. + ///////////////////// + if(!CheckAccount()) + return; + + // Do stats authentication. + /////////////////////////// + if(!StatsAuthentication()) + return; + + // We're authenticated. + /////////////////////// + Authenticated(); + + // Update dialog display. + ///////////////////////// + UpdateData(FALSE); +} + +persisttype_t TypeConversion(int type) +{ + if(type == 0) + return pd_private_rw; + if(type == 1) + return pd_private_ro; + if(type == 2) + return pd_public_rw; + return pd_public_ro; +} + +BOOL GetKeyValue(LPCSTR src, CString & key, CString & value) +{ + const char * str; + const char * keyStart; + const char * valueStart; + int keyLen; + int valueLen; + + // Check for no input. + ////////////////////// + if(!src) + return FALSE; + + // Check the starting \. + //////////////////////// + if(src[0] != '\\') + return FALSE; + + // Clear the key and value. + /////////////////////////// + key.Empty(); + value.Empty(); + + // Find the key and value, plus lengths. + //////////////////////////////////////// + keyStart = (src + 1); + valueStart = strchr(keyStart, '\\'); + if(!valueStart || (valueStart == keyStart)) + return FALSE; + keyLen = (valueStart - keyStart); + valueStart++; + str = strchr(valueStart, '\\'); + if(str) + valueLen = (str - valueStart); + else + valueLen = strlen(valueStart); + + // Copy off the key. + //////////////////// + char * keyStr = key.GetBuffer(keyLen); + memcpy(keyStr, keyStart, keyLen); + key.ReleaseBuffer(keyLen); + + // Copy off the value. + //////////////////// + char * valueStr = value.GetBuffer(valueLen); + memcpy(valueStr, valueStart, valueLen); + value.ReleaseBuffer(valueLen); + + return TRUE; +} + +void CGp_statsDlg::ClearKeys() +{ + int count = m_keys.GetCount(); + int i; + for(i = 0 ; i < count ; i++) + delete (CString *)m_keys.GetItemDataPtr(i); + m_keys.ResetContent(); +} + +void CGp_statsDlg::GotData(LPCSTR data) +{ + CString key; + CString value; + int nIndex; + + // Go through the keys/values. + ////////////////////////////// + while(GetKeyValue(data, key, value)) + { + // Adjust the data based on the key and value lengths. + ////////////////////////////////////////////////////// + data += (key.GetLength() + value.GetLength() + 2); + + // Add the new key/value. + ///////////////////////// + nIndex = m_keys.AddString(key); + if(nIndex != -1) + m_keys.SetItemDataPtr(nIndex, new CString(value)); + } +} + +BOOL getDataFinished; +void GetDataCallback(int localid, int profileid, persisttype_t type, int index, int success, time_t modified, char *data, int len, void *instance) +{ + getDataFinished = TRUE; + + if(success) + { + CGp_statsDlg * dlg = (CGp_statsDlg *)instance; + dlg->GotData(data); + } +} + +void CGp_statsDlg::OnGet() +{ + // Update dialog members. + ///////////////////////// + UpdateData(); + + // Clear the keys and value. + //////////////////////////// + ClearKeys(); + m_value.Empty(); + + // Make sure we're connected to the stats server. + ///////////////////////////////////////////////// + if(!CheckStatsConnection()) + return; + + // Get the data. + //////////////// + getDataFinished = FALSE; + GetPersistDataValues(0, m_profileID, TypeConversion(m_type), 0, "", GetDataCallback, this); + while(!getDataFinished) + if(!PersistThink()) + CheckStatsConnection(); + + // Update dialog display. + ///////////////////////// + UpdateData(FALSE); +} + +BOOL setDataFinished; +void SetDataCallback(int localid, int profileid, persisttype_t type, int index, int success, time_t modified, void *instance) +{ + setDataFinished = TRUE; +} + +void CGp_statsDlg::OnSet() +{ + // Update dialog members. + ///////////////////////// + UpdateData(); + + // Make sure we're connected to the stats server. + ///////////////////////////////////////////////// + if(!CheckStatsConnection()) + return; + + // Build the data string. + ///////////////////////// + int count = m_keys.GetCount(); + int i; + CString data; + CString key; + CString * value; + for(i = 0 ; i < count ; i++) + { + m_keys.GetText(i, key); + value = (CString *)m_keys.GetItemDataPtr(i); + + data += '\\'; + data += key; + data += '\\'; + data += *value; + } + + // Set some data. + ///////////////// + setDataFinished = FALSE; + SetPersistDataValues(0, m_profileID, TypeConversion(m_type), 0, (char *)(LPCSTR)data, SetDataCallback, this); + while(!setDataFinished) + if(!PersistThink()) + CheckStatsConnection(); +} + +void CGp_statsDlg::OnSelchangeKeys() +{ + UpdateData(); + + // Get the new key/value. + ///////////////////////// + int nIndex = m_keys.GetCurSel(); + if(nIndex != -1) + m_value = *(CString *)m_keys.GetItemDataPtr(nIndex); + + UpdateData(FALSE); +} + +void CGp_statsDlg::OnAdd() +{ + UpdateData(); + + if(m_newKey.IsEmpty()) + return; + + int nIndex = m_keys.AddString(m_newKey); + if(nIndex != -1) + m_keys.SetItemDataPtr(nIndex, new CString); + + m_newKey.Empty(); + + UpdateData(FALSE); +} + +void CGp_statsDlg::OnChangeValue() +{ + UpdateData(); + int nIndex = m_keys.GetCurSel(); + if(nIndex != -1) + { + CString * string = (CString *)m_keys.GetItemDataPtr(nIndex); + *string = m_value; + } +} diff --git a/code/gamespy/gstats/gp_stats/gp_statsDlg.h b/code/gamespy/gstats/gp_stats/gp_statsDlg.h new file mode 100644 index 00000000..b5646418 --- /dev/null +++ b/code/gamespy/gstats/gp_stats/gp_statsDlg.h @@ -0,0 +1,77 @@ +// gp_statsDlg.h : header file +// + +#if !defined(AFX_GP_STATSDLG_H__B7B25B2A_61E9_4BFE_BBD7_D7C4D7242B7D__INCLUDED_) +#define AFX_GP_STATSDLG_H__B7B25B2A_61E9_4BFE_BBD7_D7C4D7242B7D__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "../../gp/gp.h" + +///////////////////////////////////////////////////////////////////////////// +// CGp_statsDlg dialog + +class CGp_statsDlg : public CDialog +{ +// Construction +public: + CGp_statsDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CGp_statsDlg) + enum { IDD = IDD_GP_STATS_DIALOG }; + CListBox m_keys; + CString m_email; + CString m_nick; + CString m_password; + int m_profileID; + int m_type; + CString m_value; + CString m_newKey; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CGp_statsDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +public: + void GotData(LPCSTR data); + +// Implementation +protected: + HICON m_hIcon; + + BOOL m_authenticated; + GPConnection m_gp; + + void Authenticated(); + void UnAuthenticate(); + BOOL CheckStatsConnection(); + BOOL CheckAccount(); + BOOL CreateAccount(); + void SendPassword(); + BOOL StatsAuthentication(); + void ClearKeys(); + + // Generated message map functions + //{{AFX_MSG(CGp_statsDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnAuthenticate(); + afx_msg void OnDestroy(); + afx_msg void OnGet(); + afx_msg void OnSet(); + afx_msg void OnAdd(); + afx_msg void OnSelchangeKeys(); + afx_msg void OnChangeValue(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_GP_STATSDLG_H__B7B25B2A_61E9_4BFE_BBD7_D7C4D7242B7D__INCLUDED_) diff --git a/code/gamespy/gstats/gp_stats/res/gp_stats.ico b/code/gamespy/gstats/gp_stats/res/gp_stats.ico new file mode 100644 index 0000000000000000000000000000000000000000..7eef0bcbe6580a6f464d688906172c2d9de44262 GIT binary patch literal 1078 zcmc&zF>b>!3}jLb9s)T}@Kod(893@u8ajANzT`op9^o+)S?=nU(FD@%0s)Sg^oyC8{H z9myetc;MEP)59v(LMa~xK8Yu^jIR*H22uCFiq5%C{s7(PJi>o15i^bmX4(vPxWAio z9ryY#AU_jfnd047-@`)XzL?%iS$gQyFP{44kS9X)fN{{QoL~hO-&=q&20Zr*cxFAt PkaNE{wR~2C$NfnjhSXWT literal 0 HcmV?d00001 diff --git a/code/gamespy/gstats/gp_stats/res/gp_stats.rc2 b/code/gamespy/gstats/gp_stats/res/gp_stats.rc2 new file mode 100644 index 00000000..0cd2a66d --- /dev/null +++ b/code/gamespy/gstats/gp_stats/res/gp_stats.rc2 @@ -0,0 +1,13 @@ +// +// GP_STATS.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/gstats/gp_stats/resource.h b/code/gamespy/gstats/gp_stats/resource.h new file mode 100644 index 00000000..85b0316f --- /dev/null +++ b/code/gamespy/gstats/gp_stats/resource.h @@ -0,0 +1,33 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by gp_stats.rc +// +#define IDD_GP_STATS_DIALOG 102 +#define IDR_MAINFRAME 128 +#define IDC_EMAIL 1000 +#define IDC_NICK 1001 +#define IDC_PASSWORD 1002 +#define IDC_PROFILE_ID 1003 +#define IDC_AUTHENTICATE 1004 +#define IDC_PRIVATE_RW 1005 +#define IDC_PRIVATE_RO 1006 +#define IDC_PUBLIC_RW 1007 +#define IDC_PUBLIC_RO 1008 +#define IDC_GET 1009 +#define IDC_SET 1010 +#define IDC_VALUE 1012 +#define IDC_KEYS 1014 +#define IDC_ADD 1015 +#define IDC_NEW_KEY 1016 +#define IDC_EDIT1 1016 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1017 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/gamespy/gstats/gpersist.h b/code/gamespy/gstats/gpersist.h new file mode 100644 index 00000000..cb3b28ed --- /dev/null +++ b/code/gamespy/gstats/gpersist.h @@ -0,0 +1,415 @@ +/****** +gpersist.h +GameSpy Persistent Storage SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +****** + +Please see the GameSpy Persistent Storage SDK for more info + +*****/ +//todo: get/set @ offset / length + +#ifndef _GPERSIST_H_ +#define _GPERSIST_H_ + +#include "gstats.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/************************* +The following defines and prototypes are included +inside the "gstats.h" file, but listed here as well for easy reference +since they are also used by the Persistent Storage SDK. +The comments for them have also been changed to reflect their +specific use inside the Persistent Storage SDK. +**************************/ +#if 0 + /* Error codes */ + #define GE_NOERROR 0 + #define GE_NOSOCKET 1 /* Unable to create a socket */ + #define GE_NODNS 2 /* Unable to resolve a DNS name */ + #define GE_NOCONNECT 3 /* Unable to connect to stats server, or connection lost */ + #define GE_BUSY 4 /* Not used */ + #define GE_DATAERROR 5 /* Bad data from the stats server */ + + /* You need to fill these in with your game-specific info */ + extern char gcd_secret_key[256]; + extern char gcd_gamename[256]; + + /******** + InitStatsConnection + + DESCRIPTION + Opens a connection to the stats / persistent storage server. Should be done before calling + any of the other persistent storage functions. May block for 1-2 secs + while the connection is established so you will want to do this before + gameplay starts or in another thread. + + PARAMETERS + gameport: integer port associated with your server (may be the same as + your developer spec query port). If not appropriate for your game, pass in 0. + + RETURNS + GE_NODNS: Unable to resolve stats server DNS + GE_NOSOCKET: Unable to create data socket + GE_NOCONNECT: Unable to connect to stats server + GE_DATAERROR: Unable to receive challenge from stats server, or bad challenge + GE_NOERROR: Connected to stats server and ready to send data + + Note: If the connection fails, all Persistent Storage functions will fail. + *********/ + int InitStatsConnection(int gameport); + + /******** + IsStatsConnected + + DESCRIPTION + Returns whether or not you are currently connected to the stats server. Even + if your initial connection was successful, you may lose connection later and + want to try to reconnnect. + If a callback returns unsuccessfully, check this function to see if it was + because of a disconnection. + + RETURNS + 1 if connected, 0 otherwise + *********/ + int IsStatsConnected(); + + /******** + CloseStatsConnection + + DESCRIPTION + Closes the connection to the stats server. You should do this when done + with the connection. + *********/ + void CloseStatsConnection(void); + + /******** + GetChallenge + + DESCRIPTION + Returns the string to pass as the challenge to the GenerateAuth function. + + PARAMETERS + game: Pass in NULL (or your current game, if you are also using the Stats SDK) + + RETURNS + A string to pass to GenerateAuth to create the authentication hash + *********/ + char *GetChallenge(statsgame_t game); + + /******** + GenerateAuth + + DESCRIPTION + Used to generate on the "challengeresponse" parameter for the PreAuthenticatePlayer + functions. + + PARAMETERS + challenge: The challenge string generated by GetChallange. + password: The CD Key (un-hashed) or profile password + response: The output authentication string + + RETURNS + A pointer to response + *********/ + char *GenerateAuth(char *challenge, gsi_char *password,/*[out]*/char response[33]); +#endif //#ifdef 0 section from gstats.h + +/************************* +The rest of the prototypes in this file are specific to +the Persistent Storage SDK +**************************/ + +#ifndef GSI_UNICODE +#define GenerateAuth GenerateAuthA +#define PreAuthenticatePlayerCD PreAuthenticatePlayerCDA +#define GetProfileIDFromCD GetProfileIDFromCDA +#define GetPersistDataValues GetPersistDataValuesA +#define GetPersistDataValuesModified GetPersistDataValuesModifiedA +#define SetPersistDataValues SetPersistDataValuesA +#else +#define GenerateAuth GenerateAuthW +#define PreAuthenticatePlayerCD PreAuthenticatePlayerCDW +#define GetProfileIDFromCD GetProfileIDFromCDW +#define GetPersistDataValues GetPersistDataValuesW +#define GetPersistDataValuesModified GetPersistDataValuesModifiedW +#define SetPersistDataValues SetPersistDataValuesW +#endif + +/******** +persisttype_t +There are 4 types of persistent data stored for each player: +pd_private_ro: Readable only by the authenticated client it belongs to, can only by set on the server +pd_private_rw: Readable only by the authenticated client it belongs to, set by the authenticated client it belongs to +pd_public_ro: Readable by any client, can only be set on the server +pd_public_rw: Readable by any client, set by the authenicated client is belongs to +*********/ +typedef enum {pd_private_ro, pd_private_rw, pd_public_ro, pd_public_rw} persisttype_t; + +/***************** +CALLBACK FUNCTIONS +*****************/ + +/**************** +PersAuthCallbackFn + +DESCRIPTION +This type of function is passed to the two PreAuthentication functions. +It returns the result of the Authentication request. + +PARAMETERS +localid: The localid number passed into the PreAuthenticate function +profileid: If authentication was successful, the profileid for this user +authenticated: 1 if the player was authenticated < 1 otherwise +errmsg: Error returned by the server to indicate why the player was not authenticated +instance: Opaque value passed into the PreAuthenticate function (for your use) +*****************/ +typedef void (*PersAuthCallbackFn)(int localid, int profileid, int authenticated, gsi_char *errmsg, void *instance); + + +/**************** +PersDataCallbackFn + +DESCRIPTION +This type of function is passed to the two GetPersistData functions. +It returns the result of the data request. +localid + +PARAMETERS +localid: The localid number passed into the GetPersistData function +profileid: The profileid of the user who the data was requested for +type: The type of persistent data being returned +index: The persistent data index +success: 1 if the data was retrieved successfully + 2 if the data had not been modified since the time requested + < 1 if there was an error +modified: The last time the data for this index was modified (any persist type) + Only returned if success is 1 +data: Pointer to the data being returned. Note: you must use or copy + off the data before returning from the callback, as it may be freed or overwritten + once the callback is complete. +len: Length of the data being returned. 0 indicates that no data was stored on the server + (if success was 1) +instance: Opaque value passed into the GetPersistData function (for your use) +*****************/ +typedef void (*PersDataCallbackFn)(int localid, int profileid, persisttype_t type, int index, int success, time_t modified, char *data, int len, void *instance); + +/**************** +PersDataSaveCallbackFn + +DESCRIPTION +This type of function is passed to the two SetPersistData functions. +It returns the result of the set data request. + +PARAMETERS +localid: The localid number passed into the SetPersistData function +profileid: The profileid of the user who the data is being saved for +success: 1 if the data was saved successfully, < 1 otherwise +modified: The time recorded on the backend for last modification +instance: Opaque value passed into the SetPersistData function (for your use) +*****************/ +typedef void (*PersDataSaveCallbackFn)(int localid, int profileid, persisttype_t type, int index, int success, time_t modified, void *instance); + +/**************** +ProfileCallbackFn + +DESCRIPTION +This type of function is passed to the GetProfileIDFromCD function. +It returns the result of the lookup request. + +PARAMETERS +localid: The localid number passed into the GetProfileIDFromCD function +profileid: The profileid of the requested user, if the lookup was successful +success: 1 if the lookup was successful, < 1 otherwise +instance: Opaque value passed into the GetProfileIDFromCD function (for your use) +*****************/ +typedef void (*ProfileCallbackFn)(int localid, int profileid, int success, void *instance); + + +/*************************** +PERSISTENT STORAGE FUNCTIONS +****************************/ + +/**************** +PreAuthenticatePlayerPM +PreAuthenticatePlayerCD + +DESCRIPTION +These two functions are used to authenticate a player on the Stats server. +A player MUST be authenticated before getting private persistent data, or +setting public or private data. +If the StatsServer connection is ever lost and reconnected (using InitStatsConnection) +the player must be reauthenticated before reading / writing their data. +PreAuthenticatePlayerPM authenticates players using the Presence & Messaging SDK account info +PreAuthenticatePlayerCD authenticates players using the CDKey SDK CD Key. +Typically you will only use one of these in your game (depending on whether you use +the Presence & Messaging SDK, or the CD Key SDK), however they can both be used in the +same game if needed. + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the callback + to allow you to identify which player it is referring to. +profileid: (PreAuthenticatePlayerPM) The profileid of the player being authenicated. This can be obtained in the + Presence & Messaging SDK through gpIDFromProfile() +nick: (PreAuthenticatePlayerCD) Nickname of the player to authenticate. Note that the nickname is not actually + authenticated, it is simply used to determine which profile under the authenticated CD Key to use. + Each CD Key can have mutiple profiles, each using a different nick. +keyhash: (PreAuthenticatePlayerCD) Hash of the player's CD Key. If used on the server, this can be obtained from gcd_getkeyhash + On the client, you can easily get the hash by calling GenerateAuth() with challenge as an empty string ("") + and the CD Key has the password parameter. +challengeresponse: Result of the GenerateAuth() call, after passing in the challenge and the client's + password or CD Key +PersAuthCallbackFn: Callback to be called after the authentication is complete +instance: Pointer that will be passed to the callback function (for your use) + Typically used for passing an object or structure pointer into the callback. +*****************/ +void PreAuthenticatePlayerPartner(int localid, const char* authtoken, const char *challengeresponse, PersAuthCallbackFn callback, void *instance); +void PreAuthenticatePlayerPM(int localid, int profileid, const char *challengeresponse, PersAuthCallbackFn callback, void *instance); +void PreAuthenticatePlayerCD(int localid, const gsi_char *nick, const char *keyhash, const char *challengeresponse, PersAuthCallbackFn callback, void *instance); + +/**************** +GetProfileIDFromCD + +DESCRIPTION +Given a nickname and CD Key hash, this will lookup the profileid for the user. +If the user has never authenticated (and has no persistent data associated with them), +the callback will indicate a failure. No persistent data can be retreived for the user, +since they don't have any stored. Persistent data can be stored, but only after authenticating +with PreAuthenticatePlayerCD. + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the callback + to allow you to identify which player it is referring to. +nick: The nick of the user whose profileid you are looking up +keyhash: The CD Key Hash of the user whose profileid you are looking up +ProfileCallbackFn: Callback to be called when the lookup is completed +instance: Pointer that will be passed to the callback function (for your use) +*****************/ +void GetProfileIDFromCD(int localid, const gsi_char *nick, const char *keyhash, ProfileCallbackFn callback, void *instance); + +/**************** +GetPersistData[Modified] + +DESCRIPTION +Gets the entire block of persistent data for a user. +The data and length are returned in the callback function. +Note that only an authenticated player can get their private data. Any +player can get any other player's public data. + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the callback + to allow you to identify which player it is referring to. +profileid: The profileid of the player whose data you are looking up. + Returned by gpIDFromProfile() in the Presence & Messaging SDK, or using GetProfileIDFromCD +type: The type of persistent data you are looking up +index: Each profile can have multiple persistent data records associated with them. Usually you + just want to use index 0. +modifiedsince: A time value to limit the request for data. Data will only be returned if it has been + modified since the time provided. If data has not been modified since that time, the callback will be + called with a success value that indicates it is unmodified. + Note: modification time is tracked for the given profileid/index, not on a per persisttype basis +PersDataCallbackFn: Callback that will be called with the data when it is returned +ProfileCallbackFn: Callback to be called when the lookup is completed +instance: Pointer that will be passed to the callback function (for your use) +*****************/ +void GetPersistData(int localid, int profileid, persisttype_t type, int index, PersDataCallbackFn callback, void *instance); +void GetPersistDataModified(int localid, int profileid, persisttype_t type, int index, time_t modifiedsince, PersDataCallbackFn callback, void *instance); + +/**************** +GetPersistDataValues[Modified] + +DESCRIPTION +If you store your data in key\value delimited pairs, GetPersistDataValues will +allow you to easily retrieve a subset of the stored data. To retrieve the entire +data set, use GetPersistData. The data will be returned as a null-terminated string, +unless no data is available (in which case len will be 0 in the callback). + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the callback + to allow you to identify which player it is referring to. +profileid: The profileid of the player whose data you are looking up. + Returned by gpIDFromProfile() in the Presence & Messaging SDK, or using GetProfileIDFromCD +type: The type of persistent data you are looking up +index: Each profile can have multiple persistent data records associated with them. Usually you + just want to use index 0. +modifiedsince: A time value to limit the request for data. Data will only be returned if it has been + modified since the time provided. If data has not been modified since that time, the callback will be + called with a success value that indicates it is unmodified. + Note: modification time is tracked for the given profileid/index, not on a per-persisttype or per-key basis +keys: A "\" delimited list of the keys you want returned (for example: "\clan\color\homepage\birthday") +PersDataCallbackFn: Callback that will be called with the data when it is returned +instance: Pointer that will be passed to the callback function (for your use) +*****************/ +void GetPersistDataValues(int localid, int profileid, persisttype_t type, int index, gsi_char *keys, PersDataCallbackFn callback, void *instance); +void GetPersistDataValuesModified(int localid, int profileid, persisttype_t type, int index, time_t modifiedsince, gsi_char *keys, PersDataCallbackFn callback, void *instance); + +/**************** +SetPersistData + +DESCRIPTION +Sets the entire block of persistent data for a user. +The profileid for whom the data is being set MUST have been authenticated already. + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the callback + to allow you to identify which player it is referring to. +profileid: The profileid of the player whose data you are setting. + The player must have already been authenticated with one of the PreAuthenticatePlayer functions. +type: The type of persistent data you are setting. Only rw data is setable. +index: Each profile can have multiple persistent data records associated with them. Usually you + just want to use index 0. +data: The persistent data to be saved +len: The length of the data. If you are setting key\value delimited data, make + sure the "len" parameter includes length of the null terminator +PersDataSaveCallbackFn: Callback that will be called with the data save is complete +instance: Pointer that will be passed to the callback function (for your use) +*****************/ +void SetPersistData(int localid, int profileid, persisttype_t type, int index, const char *data, int len, PersDataSaveCallbackFn callback, void *instance); + +/**************** +SetPersistDataValues + +DESCRIPTION +If you are saving data in key\value delimited format, you can use this function +to only set SOME of the key\value pairs. Only the key value pairs you include in +they keyvalues parameter will be updated, the other pairs will stay the same. + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the callback + to allow you to identify which player it is referring to. +profileid: The profileid of the player whose data you are setting. + The player must have already been authenticated with one of the PreAuthenticatePlayer functions. +type: The type of persistent data you are setting. Only rw data is setable. +index: Each profile can have multiple persistent data records associated with them. Usually you + just want to use index 0. +keyvalues: The key\value pairs to be updated (for example: \clan\The A-Team\homepage\http://www.myclan.net\age\15) +PersDataSaveCallbackFn: Callback that will be called with the data save is complete +instance: Pointer that will be passed to the callback function (for your use) +*****************/ +void SetPersistDataValues(int localid, int profileid, persisttype_t type, int index, const gsi_char *keyvalues, PersDataSaveCallbackFn callback, void *instance); + +/**************** +PersistThink + +DESCRIPTION +This function needs to be called any time a asynchronous operation is in progress. It will +check for the incoming replies and call the callbacks associated with them as needed. +It's recommened that you call this in your main loop at all times while you are connected +to the stats server, so that if the stats server disconnects it can be detected immediately. + +RETURNS +0 if the connection to the stats server is lost, 1 otherwise +*****************/ +int PersistThink(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/gstats/gstats.c b/code/gamespy/gstats/gstats.c new file mode 100644 index 00000000..2edb3412 --- /dev/null +++ b/code/gamespy/gstats/gstats.c @@ -0,0 +1,1681 @@ +/****** +gstats.c +GameSpy Stats/Tracking SDK +GameSpy Persistent Storage SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +****** + +Please see the GameSpy Stats and Tracking SDK documentation for more info + +******/ + +/******** +INCLUDES +********/ +#include "gstats.h" +#include "gpersist.h" +#include "../common/gsAvailable.h" +#include "../darray.h" +#include "../md5.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/******** +TYPEDEFS +********/ +struct statsgame_s +{ + int connid; + int sesskey; + int usebuckets; + bucketset_t buckets; + char challenge[9]; + DArray playernums; //for player number translation + DArray teamnums; //for team number translation + int totalplayers, totalteams; + gsi_time sttime; + +}; + +typedef enum {rt_authcb, rt_datacb, rt_savecb, rt_profilecb} reqtype_t; +typedef struct +{ + reqtype_t reqtype; + int localid; + int profileid; + persisttype_t pdtype; + int pdindex; + void *instance; + void *callback; + +} serverreq_t; + + +/******** +PROTOTYPES +********/ +static int ServerOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index); +static double ServerOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index); +static char *ServerOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index); + +static int TeamOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index); +static double TeamOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index); +static char *TeamOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index); + +static int PlayerOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index); +static double PlayerOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index); +static char *PlayerOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index); + +static char *CreateBucketSnapShot(bucketset_t buckets); + +#ifdef ALLOW_DISK +static void CheckDiskFile(); +static void DiskWrite(char *line, int len); +#endif +static void InternalInit(); +static int SendChallengeResponse(const char *indata, int gameport); +static int RecvSessionKey(); +static int DoSend(char *data, int len); +static void xcode_buf(char *buf, int len); +static int g_crc32(char *s, int len); +static void create_challenge(int challenge, char chstr[9]); +static char *value_for_key(const char *s, const char *key); +static char *value_for_key_safe(const char *s, const char *key); +static int get_sockaddrin(const char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent); +/************** +PERSISTENT STORAGE PROTOTYPES +**************/ +static void AddRequestCallback(reqtype_t reqtype, int localid, int profileid, persisttype_t pdtype, int pdindex, void *callback, void *instance); +static void SendPlayerAuthRequest(char *data, int len, int localid, PersAuthCallbackFn callback, void *instance); +static void SendPlayerAuthRequest(char *data, int len, int localid, PersAuthCallbackFn callback, void *instance); +static int SocketReadable(SOCKET s); +static char *FindFinal(char *buff, int len); +static int FindRequest(reqtype_t reqtype, int localid, int profileid); +static void ProcessPlayerAuth(const char *buf, int len); +static void ProcessGetPid(const char *buf, int len); +static void ProcessGetData(const char *buf, int len); +static void ProcessSetData(const char *buf, int len); +static void ProcessStatement(char *buff, int len); +static int ProcessInBuffer(char *buff, int len); +static void CallReqCallback(int reqindex, int success, time_t modified, char *data, int length); +static void ClosePendingCallbacks(); +static void SetPersistDataHelper(int localid, int profileid, persisttype_t type, int index, const char *data, int len, PersDataSaveCallbackFn callback, void *instance, int kvset); +void GetPersistDataValuesA(int localid, int profileid, persisttype_t type, int index, char *keys, PersDataCallbackFn callback, void *instance); +void GetPersistDataValuesModifiedA(int localid, int profileid, persisttype_t type, int index, time_t modifiedsince, char *keys, PersDataCallbackFn callback, void *instance); + +/******** +DEFINES +********/ +//#define SSHOST "207.199.80.230" +#define SSHOST "gamestats." GSI_DOMAIN_NAME +#define SSPORT 29920 + +#define FIXGAME(g,r) if (g == NULL) g = g_statsgame; if (g == NULL) return r; +#define DoFunc(f,g, n, v, t, r) \ + if (g == NULL) g = g_statsgame; \ + if (!g) r = v; \ + else { \ + r = f(g->buckets, n, v); \ + if (!r) \ + r = BucketNew(g->buckets, n, t, v); } +#define DOXCODE(b, l, e) enc = e; xcode_buf(b,l); + + +/******** +VARS +********/ +char gcd_gamename[256] = ""; +char gcd_secret_key[256] = ""; +static statsgame_t g_statsgame = NULL; +static int connid = 0; +static int sesskey = 0; +static SOCKET sock = INVALID_SOCKET; +/* #define enc1 "GameSpy 3D" + #define enc2 "Industries" + #define enc3 "ProjectAphex" + #define STATSFILE "gstats.dat" */ +/* A couple vars to help avoid the string table */ +static char enc1[16] = {'\0','a','m','e','S','p','y','3','D','\0'}; +static char enc3[16] = {'\0','r','o','j','e','c','t','A','p','h','e','x','\0'}; + +#ifdef ALLOW_DISK +static char statsfile[16] = {'\0','s','t','a','t','s','.','d','a','t','\0'}; +static char enc2[16]= {'\0','n','d','u','s','t','r','i','e','s','\0'}; +#endif + +static char finalstr[10] = {'\0','f','i','n','a','l','\\','\0'}; +static char *enc = enc1; +static int internal_init = 0; +static char *rcvbuffer = NULL; +static int rcvmax = 0; +static int rcvlen = 0; +// Changed By Saad Nader, 09-16-2004 +// Due to confliction with MacOS X +/////////////////////////////////////////// +static int stats_initstate = init_none; +static int gameport = 0; + +static gsi_time initstart = 0; +static gsi_time inittimeout = 20000; // 20 seconds + +char StatsServerHostname[64] = SSHOST; + +static DArray serverreqs = NULL; //for pre-authentication requests + + +BucketFunc bucketfuncs[NUMOPS] = +{BucketSet, BucketAdd, BucketSub, BucketMult, BucketDiv, BucketConcat, BucketAvg}; + +void * bopfuncs[][3] = +{ + {ServerOpInt, ServerOpFloat, ServerOpString}, + {TeamOpInt, TeamOpFloat, TeamOpString}, + {PlayerOpInt, PlayerOpFloat, PlayerOpString}, +}; + +/****************************************************************************/ +/* PUBLIC FUNCTIONS */ +/****************************************************************************/ +#define RAWSIZE 128 +char *GenerateAuthA(const char *challenge, const char *password, char response[33]) +{ + char rawout[RAWSIZE]; + + /* check to make sure we weren't passed a huge pass/challenge */ + if (strlen(password) + strlen(challenge) + 20>= RAWSIZE) + { + strcpy(response,"CD Key or challenge too long"); + return response; + } + + /* response = MD5(pass + challenge) */ + sprintf(rawout, "%s%s",password, challenge ); + + /* do the response md5 */ + MD5Digest((unsigned char *)rawout, strlen(rawout), response); + return response; +} +#ifdef GSI_UNICODE +char *GenerateAuthW(const char* challenge, const unsigned short *password, char response[33]) +{ + char* password_A = UCS2ToUTF8StringAlloc(password); + GenerateAuthA(challenge, password_A, response); + gsifree(password_A); + return response; +} +#endif + +/****************************************************************************/ +int InitStatsAsync(int theGamePort, gsi_time theInitTimeout) +{ + struct sockaddr_in saddr; + char tempHostname[128]; + int ret; + + gameport = theGamePort; + + if (theInitTimeout != 0) + inittimeout = theInitTimeout; + + /* check if the backend is available */ + if(__GSIACResult != GSIACAvailable) + return GE_NOSOCKET; + + /* Init our hidden strings if needed */ + if (!internal_init) + InternalInit(); + + SocketStartUp(); + sesskey = (int)current_time(); + + /* Get connected */ + if (sock != INVALID_SOCKET) + CloseStatsConnection(); + + rcvlen = 0; //make sure ther receive buffer is cleared + + if (inet_addr(StatsServerHostname) == INADDR_NONE) + { + strcpy(tempHostname, gcd_gamename); + strcat(tempHostname,"."); + strcat(tempHostname,StatsServerHostname); + } else + strcpy(tempHostname, StatsServerHostname); //it's already been resolved + + if (get_sockaddrin(tempHostname,SSPORT,&saddr,NULL) == 0) + return GE_NODNS; + +#ifdef INSOCK + sock = socket ( AF_INET, SOCK_STREAM, 0 ); +#else + sock = socket ( AF_INET, SOCK_STREAM, IPPROTO_TCP ); +#endif + if (sock == INVALID_SOCKET) + return GE_NOSOCKET; + + SetSockBlocking(sock, 0); + + ret = connect(sock, (struct sockaddr*)&saddr, sizeof(saddr)); + if (gsiSocketIsError(ret)) + { + int anError = GOAGetLastError(sock); + if ((anError != WSAEWOULDBLOCK) && (anError != WSAETIMEDOUT) && (anError != WSAEINPROGRESS)) + { + stats_initstate = init_failed; + closesocket(sock); + return GE_NOCONNECT; + } + } + + // allocate the recv buffer + rcvbuffer = gsimalloc(64); + if (rcvbuffer == NULL) + return GE_NOCONNECT; // add a new error code for out of mem? + + rcvmax = 64; + rcvlen = 0; + + initstart = current_time(); + stats_initstate = init_connecting; + return GE_CONNECTING; +} + +/****************************************************************************/ +int InitStatsThink() +{ + switch(stats_initstate) + { + case init_failed: return GE_NOCONNECT; + case init_connecting: + { + // Check if socket is writeable yet + int aWriteFlag = 0; + int aExceptFlag = 0; + int aResult = GSISocketSelect(sock, NULL, &aWriteFlag, &aExceptFlag); + if ((gsiSocketIsError(aResult)) || // socket error + (aResult == 1 && aExceptFlag == 1)) // exception + { + stats_initstate = init_failed; + CloseStatsConnection(); + return GE_NOCONNECT; + } + else if (aResult == 0) // no progress yet + { + // Should we continue to wait? + if (current_time() - initstart > inittimeout) + { + stats_initstate = init_failed; + CloseStatsConnection(); + return GE_TIMEDOUT; + } + else + return GE_CONNECTING; + } + + // Otherwise connected + assert(aResult == 1 && aWriteFlag == 1); + stats_initstate = init_awaitchallenge; + // fall through + } + case init_awaitchallenge: + { + int ret = 0; + + // Try to receive data + if (!CanReceiveOnSocket(sock)) + { + // should we continue to wait? + if (current_time() - initstart > inittimeout) + { + stats_initstate = init_failed; + CloseStatsConnection(); + return GE_TIMEDOUT; + } + return GE_CONNECTING; + } + + // Receive the 38 byte challenge + ret = recv(sock, rcvbuffer+rcvlen, rcvmax-rcvlen, 0); + if (gsiSocketIsError(ret)) + { + stats_initstate = init_failed; + CloseStatsConnection(); + return GE_NOCONNECT; + } + rcvlen += ret; + rcvmax -= ret; + + // need at least 38 bytes + if (rcvlen < 38) + return GE_CONNECTING; + + // Process challenge + rcvbuffer[rcvlen] = '\0'; + stats_initstate = init_awaitsessionkey; + + /* Decode it */ + DOXCODE(rcvbuffer, rcvlen, enc1); + /* Send a response */ + ret = SendChallengeResponse(rcvbuffer, gameport); + if (ret != GE_NOERROR) + { + stats_initstate = init_failed; + CloseStatsConnection(); + return ret; + } + + stats_initstate = init_awaitsessionkey; + + // clear receive buffer for next stage + rcvmax += rcvlen; // reclaim the used bytes as free space + rcvlen = 0; + memset(rcvbuffer, 0, (unsigned int)rcvmax); + + // fall through + } + case init_awaitsessionkey: + { + int ret = 0; + + // Try to receive data + if (!CanReceiveOnSocket(sock)) + { + // should we continue to wait? + if (current_time() - initstart > inittimeout) + { + stats_initstate = init_failed; + CloseStatsConnection(); + return GE_TIMEDOUT; + } + return GE_CONNECTING; + } + + ret = RecvSessionKey(); + if (ret != GE_NOERROR) + { + stats_initstate = init_failed; + CloseStatsConnection(); + return ret; + } + + // Init complete + // Clear the receive buffer + rcvmax += rcvlen; + rcvlen = 0; + memset(rcvbuffer, 0, (unsigned int)rcvmax); + + #ifdef ALLOW_DISK + /* Check for old data */ + CheckDiskFile(); + #endif + + stats_initstate = init_complete; + + // fall through + } + case init_complete: + return GE_NOERROR; + + default: + return GE_NOCONNECT; + }; +} + + +/****************************************************************************/ +// Blocking version of InitStatsAsync, for backwards compatability +int InitStatsConnection(int gameport) +{ + int aResult = InitStatsAsync(gameport, 0); + while (aResult == GE_CONNECTING) + { + aResult = InitStatsThink(); + msleep(5); + } + return aResult; +} + +/****************************************************************************/ +void CloseStatsConnection() +{ + if (sock != INVALID_SOCKET) + { + shutdown(sock,2); + closesocket(sock); + } + sock = INVALID_SOCKET; + //call any pending callbacks with the data as lost + ClosePendingCallbacks(); + if (rcvbuffer != NULL) + { + gsifree(rcvbuffer); + rcvbuffer = NULL; + rcvmax = 0; + rcvlen = 0; + } + +} + +/****************************************************************************/ +int IsStatsConnected() +{ + return (sock != INVALID_SOCKET); +} + +/****************************************************************************/ +#define CHALLENGEXOR 0x38F371E6 +char *GetChallenge(statsgame_t game) +{ + static char challenge[9]; + if (game == NULL) + game = g_statsgame; + if (game == NULL) + { + create_challenge(connid ^ CHALLENGEXOR,challenge); + return challenge; + } + return game->challenge; +} + +/****************************************************************************/ +statsgame_t NewGame(int usebuckets) +{ + statsgame_t game = (statsgame_t)gsimalloc(sizeof (struct statsgame_s)); + char data[256]; + int len; + + if (!internal_init) + InternalInit(); + game->connid = connid; + game->sesskey = sesskey++; + game->buckets = NULL; + game->playernums = NULL; + game->teamnums = NULL; + game->usebuckets = usebuckets; + /* If connected, try to send */ + if (sock != INVALID_SOCKET) + { + char respformat[] = "\xC\x1C\xA\x1D\x2\x2\x19\x24\x2C\x34\x6\x17\x3E\x1C\x6\xE\x39\x46\x10\x1D\x3\xD\x16\xB\x3B\x17\x16\x36\x40\x7"; + //"\newgame\\connid\%d\sesskey\%d" + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(data,respformat,game->connid, game->sesskey); + len = DoSend(data, len); + if (len <= 0) + { + CloseStatsConnection(); + } + create_challenge(game->connid ^ CHALLENGEXOR,game->challenge); + } + /* If send failed then write to disk */ + if (sock == INVALID_SOCKET) + { +#ifdef ALLOW_DISK + char respformat[] = "\xC\x1C\xA\x1D\x2\x2\x19\x24\x2C\x34\x16\x1D\x23\x1\x4\xF\x1C\x3F\x51\x25\x2C\xB\xD\x19\x3C\x1E\xA\x4\x2\x6\x28\x64\x14"; + // "\newgame\\sesskey\%d\challenge\%d"; + + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(data,respformat,game->sesskey, game->sesskey ^ CHALLENGEXOR); + DiskWrite(data, len); + game->connid = 0; + create_challenge(game->sesskey ^ CHALLENGEXOR,game->challenge); + +#else + gsifree(game); + game = NULL; +#endif + + } + + if (game && game->usebuckets) + { + game->buckets = NewBucketSet(); + game->playernums = ArrayNew(sizeof(int),32,NULL); + game->teamnums = ArrayNew(sizeof(int),2,NULL); + game->totalplayers = game->totalteams = 0; + } + if (game) + game->sttime = current_time(); + g_statsgame = game; + return game; + +} + + +/****************************************************************************/ +void FreeGame(statsgame_t game) +{ + if (!game || game == g_statsgame) + { + game = g_statsgame; + g_statsgame = NULL; + } + if (!game) + return; + if (game->usebuckets) + { + if (game->buckets != NULL) + FreeBucketSet(game->buckets); + if (game->playernums != NULL) + ArrayFree(game->playernums); + if (game->teamnums != NULL) + ArrayFree(game->teamnums); + } + gsifree(game); +} + +/****************************************************************************/ +int SendGameSnapShotA(statsgame_t game, const char *snapshot, int final) +{ + int snaplen; + int len; + int ret = GE_NOERROR; + char *snapcopy; + char *data; + FIXGAME(game, GE_DATAERROR); + + /* If using buckets, get the data out of the buckets */ + if (game->usebuckets) + snapcopy = CreateBucketSnapShot(game->buckets); + else + snapcopy = goastrdup(snapshot); + snaplen = (int)strlen(snapcopy); + + data = (char *)gsimalloc((unsigned int)snaplen + 256); + + /* Escape the data */ + while (snaplen--) + if (snapcopy[snaplen] == '\\') + snapcopy[snaplen] = '\x1'; + + /* If connected, try to send it */ + if (sock != INVALID_SOCKET) + { + // Updated response format to contain connid + //char respformat[] = "\xC\x7\x1F\xE\x2\x2\x19\x24\x2C\x34\x16\x1D\x23\x1\x4\xF\x1C\x3F\x51\x25\x2C\xC\xA\x16\x35\x2E\x4A\xE\x39\x4\x15\x2C\x15\xC\x4\xC\x31\x2E\x4A\x19"; + char respformat[] = "\xC\x7\x1F\xE\x2\x2\x19\x24\x2C\x34\x16\x1D\x23\x1\x4\xF\x1C\x3F\x51\x25\x2C\xB\xA\x16\x3E\x1B\xB\x36\x40\x7\x28\x25\x1F\x6\x00\x24\x75\x16\x33\xD\x4\xE\x11\x25\x11\x1C\x4\x24\x75\x1"; + // "\updgame\\sesskey\%d\done\%d\gamedata\%s" + // The above string is now: + // "\updgame\\sesskey\%d\connid\%d\done\%d\gamedata\%s" + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(data, respformat, game->sesskey, game->connid, final, snapcopy); + snaplen = DoSend(data, len); + /* If the send failed, close the socket */ + if (snaplen <= 0) + { + CloseStatsConnection(); + } + } + /* If not connected, or send failed, return error or log to disk */ + if (sock == INVALID_SOCKET) + { +#ifdef ALLOW_DISK + char respformat[] = "\xC\x7\x1F\xE\x2\x2\x19\x24\x2C\x34\x16\x1D\x23\x1\x4\xF\x1C\x3F\x51\x25\x2C\xB\xA\x16\x3E\x1B\xB\x36\x40\x7\x28\x25\x1F\x6\x0\x24\x75\x16\x33\xD\x4\xE\x11\x25\x11\x1C\x4\x24\x75\x1\x33\xE\x9\x3F\x45"; + //"\updgame\\sesskey\%d\connid\%d\done\%d\gamedata\%s\dl\1" + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(data, respformat, game->sesskey, game->connid, final, snapcopy); + DiskWrite(data, len); +#else + ret = GE_NOCONNECT; +#endif + } + gsifree(snapcopy); + gsifree(data); + return ret; +} +#ifdef GSI_UNICODE +int SendGameSnapShotW(statsgame_t game, const unsigned short*snapshot, int final) +{ + char* snapshot_A = UCS2ToUTF8StringAlloc(snapshot); + int result = SendGameSnapShotA(game, snapshot_A, final); + gsifree(snapshot_A); + return result; +} +#endif + +/****************************************************************************/ +void NewPlayerA(statsgame_t game, int pnum, char *name) +{ + int i = -1; + FIXGAME(game, ;) + while (pnum >= ArrayLength(game->playernums)) + ArrayAppend(game->playernums, &i); + i = game->totalplayers++; + /* update the pnum array */ + ArrayReplaceAt(game->playernums,&i, pnum); + BucketIntOp(game, "ctime",bo_set,(int)(current_time() - game->sttime) / 1000,bl_player,pnum); + BucketStringOp(game,"player",bo_set,name, bl_player,pnum); + +} +#ifdef GSI_UNICODE +void NewPlayerW(statsgame_t game, int pnum, unsigned short *name) +{ + char* name_A = UCS2ToUTF8StringAlloc(name); + NewPlayerA(game, pnum, name_A); + gsifree(name_A); +} +#endif + +/****************************************************************************/ +void RemovePlayer(statsgame_t game,int pnum) +{ + FIXGAME(game, ;); + BucketIntOp(game,"dtime",bo_set,(int)(current_time() - game->sttime) / 1000, bl_player, pnum); +} + +/****************************************************************************/ +void NewTeamA(statsgame_t game,int tnum, char *name) +{ + int i = -1; + FIXGAME(game, ;) + while (tnum >= ArrayLength(game->teamnums)) + ArrayAppend(game->teamnums, &i); + i = game->totalteams++; + /* update the tnum array */ + ArrayReplaceAt(game->teamnums,&i, tnum); + BucketIntOp(game, "ctime",bo_set,(int)(current_time() - game->sttime) / 1000,bl_team,tnum); + BucketStringOp(game,"team",bo_set,name, bl_team,tnum); +} +#ifdef GSI_UNICODE +void NewTeamW(statsgame_t game,int tnum, unsigned short *name) +{ + char* name_A = UCS2ToUTF8StringAlloc(name); + NewTeamA(game, tnum, name_A); + gsifree(name_A); +} +#endif + +/****************************************************************************/ +void RemoveTeam(statsgame_t game, int tnum) +{ + FIXGAME(game, ;); + BucketIntOp(game,"dtime",bo_set,(int)(current_time() - game->sttime) / 1000, bl_team, tnum); +} + +/**************************************************************************** + * PERSISTENT STORAGE FUNCTIONS + ****************************************************************************/ + +/****************************************************************************/ +void PreAuthenticatePlayerPartner(int localid, const char * authtoken, const char *challengeresponse, PersAuthCallbackFn callback, void *instance) +{ + char respformat[] = "\xC\x13\x1A\x1E\xD\x13\x28\x1D\x11\x1D\x11\x10\x24\x1D\x04\x0F\x0B\x3F\x51\x32\x2C\x1A\x00\x0B\x20\x2E\x4A\x19\x39\x0F\x1D\x25\x2C\x4D\x01"; + //\authp\\authtoken\%s\resp\%s\lid\%d"; + int len; + char data[256]; + + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(data, respformat, authtoken, challengeresponse, localid); + + SendPlayerAuthRequest(data, len, localid, callback, instance); +} + + +/****************************************************************************/ +void PreAuthenticatePlayerPM(int localid, int profileid, const char *challengeresponse, PersAuthCallbackFn callback, void *instance) +{ + char respformat[] = "\xC\x13\x1A\x1E\xD\x13\x28\x1D\x0\x1\x1\x24\x75\x16\x33\x18\x0\x10\x4\x1D\x55\x1B\x39\x14\x39\x16\x33\x4F\x1"; + //\authp\\pid\%d\resp\%s\lid\%d + int len; + char data[256]; + + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(data, respformat, profileid, challengeresponse,localid); + + SendPlayerAuthRequest(data, len, localid, callback, instance); + +} + +/****************************************************************************/ +void PreAuthenticatePlayerCDA(int localid, const char *nick, const char *keyhash, const char *challengeresponse, PersAuthCallbackFn callback, void *instance) +{ + char respformat[] = "\xC\x13\x1A\x1E\xD\x13\x28\x1D\x1E\x1\x6\x13\xC\x57\x1C\x36\xE\x6\xD\x29\x11\x1B\xD\x24\x75\x1\x33\x18\x0\x10\x4\x1D\x55\x1B\x39\x14\x39\x16\x33\x4F\x1"; + //\authp\\nick\%s\keyhash\%s\resp\%s\lid\%d + int len; + char data[256]; + + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(data, respformat, nick, keyhash, challengeresponse,localid); + + SendPlayerAuthRequest(data, len, localid, callback, instance); + +} +#ifdef GSI_UNICODE +void PreAuthenticatePlayerCDW(int localid, const unsigned short *nick, const char *keyhash, const char *challengeresponse, PersAuthCallbackFn callback, void *instance) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + PreAuthenticatePlayerCDA(localid, nick_A, keyhash, challengeresponse, callback, instance); + gsifree(nick_A); +} +#endif + +/****************************************************************************/ +void GetProfileIDFromCDA(int localid, const char *nick, const char *keyhash, ProfileCallbackFn callback, void *instance) +{ + char respformat[] = "\xC\x15\xA\x1E\x15\xA\x10\x1D\x2C\x6\xC\x1B\x3B\x2E\x4A\x19\x39\x8\x11\x38\x18\x9\x16\x10\xC\x57\x1C\x36\x9\xA\x10\x1D\x55\xC"; + //\getpid\\nick\%s\keyhash\%s\lid\%d + int len; + char data[512]; + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(data, respformat, nick, keyhash,localid); + + if (sock != INVALID_SOCKET) + len = DoSend(data, len); + + /* If the send failed, close the socket */ + if (len <= 0) + { + CloseStatsConnection(); + if (callback) + callback(0,-1,0,instance); + } else + { /* set up the callback */ + AddRequestCallback(rt_profilecb, localid, 0,(persisttype_t)0,0,callback, instance); + } +} +#ifdef GSI_UNICODE +void GetProfileIDFromCDW(int localid, const unsigned short *nick, const char *keyhash, ProfileCallbackFn callback, void *instance) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + GetProfileIDFromCDA(localid, nick_A, keyhash, callback, instance); + gsifree(nick_A); +} +#endif + +/****************************************************************************/ +void GetPersistData(int localid, int profileid, persisttype_t type, int index, PersDataCallbackFn callback, void *instance) +{ + GetPersistDataValuesModifiedA(localid,profileid, type,index,0,"",callback, instance); +} + +void GetPersistDataModified(int localid, int profileid, persisttype_t type, int index, time_t modifiedsince, PersDataCallbackFn callback, void *instance) +{ + GetPersistDataValuesModifiedA(localid,profileid, type,index, modifiedsince, "",callback, instance); +} + +/****************************************************************************/ +void SetPersistData(int localid, int profileid, persisttype_t type, int index, const char *data, int len, PersDataSaveCallbackFn callback, void *instance) +{ + SetPersistDataHelper(localid, profileid, type, index, data, len, callback, instance, 0); +} + +/****************************************************************************/ +void GetPersistDataValuesModifiedA(int localid, int profileid, persisttype_t type, int index, time_t modifiedsince, char *keys, PersDataCallbackFn callback, void *instance) +{ + char respformat[] = "\xC\x15\xA\x1E\x15\x7\x28\x1D\x0\x1\x1\x24\x75\x16\x33\x1A\x11\x1A\x4\x24\x2C\x4D\x1\x24\x34\x1B\x1\xE\x0\x1B\x28\x64\x14\x34\xE\x1D\x29\x1\x33\x4F\x16\x3F\x18\x28\x14\x34\x40\x1C"; + char modformat[] = {'\\','m','o','d','\\','%','d','\0'}; //\\mod\\%d + //\getpd\\pid\%d\ptype\%d\dindex\%d\keys\%s\lid\%d + int len; + char data[512]; + char tempkeys[256]; + char *p; + + DOXCODE(respformat, sizeof(respformat)-1, enc3); + strcpy(tempkeys, keys); + //replace the \ chars with #1 + for (p = tempkeys; *p != 0; p++) + if (*p == '\\') + *p = '\x1'; + + len = sprintf(data, respformat, profileid, type, index, tempkeys, localid); + if (modifiedsince != 0) //append it + { + len += sprintf(data + len, modformat, modifiedsince); + } + + if (sock != INVALID_SOCKET) + len = DoSend(data, len); + + /* If the send failed, close the socket */ + if (len <= 0) + { + CloseStatsConnection(); + if (callback) + callback(localid, profileid,type, index, 0,0,"",0,instance); + } else + { /* set up the callback */ + AddRequestCallback(rt_datacb, localid, profileid,type, index, callback, instance); + } +} + +void GetPersistDataValuesA(int localid, int profileid, persisttype_t type, int index, char *keys, PersDataCallbackFn callback, void *instance) +{ + GetPersistDataValuesModifiedA(localid,profileid, type,index, 0, keys,callback, instance); +} + +#ifdef GSI_UNICODE +void GetPersistDataValuesModifiedW(int localid, int profileid, persisttype_t type, int index, time_t modifiedsince, unsigned short*keys, PersDataCallbackFn callback, void *instance) +{ + char* keys_A = UCS2ToUTF8StringAlloc(keys); + GetPersistDataValuesModifiedA(localid, profileid, type, index, modifiedsince, keys_A, callback, instance); + gsifree(keys_A); +} +#endif + +#ifdef GSI_UNICODE +void GetPersistDataValuesW(int localid, int profileid, persisttype_t type, int index, unsigned short*keys, PersDataCallbackFn callback, void *instance) +{ + GetPersistDataValuesModifiedW(localid,profileid, type,index, 0, keys,callback, instance); +} +#endif + +/****************************************************************************/ +void SetPersistDataValuesA(int localid, int profileid, persisttype_t type, int index, const char *keyvalues, PersDataSaveCallbackFn callback, void *instance) +{ + SetPersistDataHelper(localid, profileid, type, index, keyvalues, (int)strlen(keyvalues) + 1, callback, instance, 1); +} +#ifdef GSI_UNICODE +void SetPersistDataValuesW(int localid, int profileid, persisttype_t type, int index, const unsigned short *keyvalues, PersDataSaveCallbackFn callback, void *instance) +{ + char* keyvalues_A = UCS2ToUTF8StringAlloc(keyvalues); + SetPersistDataValuesA(localid, profileid, type, index, keyvalues_A, callback, instance); + gsifree(keyvalues_A); +} +#endif + +/****************************************************************************/ +int PersistThink() +{ + int len; + int processed; + + if (sock == INVALID_SOCKET) + return 0; + + if (stats_initstate != init_complete) + return 0; + + while (SocketReadable(sock)) + { + if (rcvmax - rcvlen < 128) //make sure there are at least 128 bytes gsifree in the buffer + { + if (rcvmax < 256) + rcvmax = 256; + else + rcvmax *= 2; + rcvbuffer = gsirealloc(rcvbuffer, (unsigned int)(rcvmax+1)); + if (rcvbuffer == NULL) + return 0; //errcon + } + len = recv(sock, rcvbuffer + rcvlen, rcvmax - rcvlen, 0); + if (len <= 0) //lost the connection + { + CloseStatsConnection(); + return 0; + } + rcvlen += len; + rcvbuffer[rcvlen] = 0; + + processed = ProcessInBuffer(rcvbuffer, rcvlen); + if (processed == rcvlen) //then we can just zero it + rcvlen = 0; + else + { + //shift the remaining data down + memmove(rcvbuffer,rcvbuffer + processed, (unsigned int)(rcvlen - processed)); + rcvlen -= processed; + } + + + } + if (sock == INVALID_SOCKET) + return 0; + else + return 1; + +} + +/****************************************************************************/ +int StatsThink() +{ + return PersistThink(); +} + +/**************************************************************************** + * UTILITY FUNCTIONS + ****************************************************************************/ + +void InternalInit() +{ + internal_init = 1; + enc1[0] = 'G'; + enc3[0] = 'P'; + finalstr[0] = '\\'; + +#ifdef ALLOW_DISK + statsfile[0] = 'g'; + enc2[0] = 'I'; +#endif +} + + +static int SendChallengeResponse(const char *indata, int gameport) +{ + static char challengestr[] = {'\0','h','a','l','l','e','n','g','e','\0'}; + char *challenge; + char resp[128]; + char md5val[33]; + + /* make this harder to find in the string table */ + char respformat[] = "\xC\x13\x1A\x1E\xD\x3F\x28\x26\x11\x5\x0\x16\x31\x1F\xA\x36\x40\x10\x28\x33\x15\x1B\x15\x17\x3E\x1\xA\x36\x40\x10\x28\x31\x1F\x1A\x11\x24\x75\x16\x33\x3\x1\x3F\x45"; + /* \auth\\gamename\%s\response\%s\port\%d\id\1 */ + int len; + + challengestr[0] = 'c'; + challenge = value_for_key(indata,challengestr ); + if (challenge == NULL) + { + closesocket(sock); + return GE_DATAERROR; + } + + len = sprintf(resp, "%d%s",g_crc32(challenge,(int)strlen(challenge)), gcd_secret_key); + + MD5Digest((unsigned char *)resp, (unsigned int)len, md5val); + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(resp,respformat,gcd_gamename, md5val, gameport); + + if ( DoSend(resp, len) <= 0 ) + { + closesocket(sock); + return GE_NOCONNECT; + } + + return GE_NOERROR; +} + + +// 09-13-2004 BED Unused parameter removed +static int RecvSessionKey() +{ + /* get the response */ + static char sesskeystr[] = {'\0','e','s','s','k','e','y','\0'}; + char resp[128]; + char *stext; + int len = (int)recv(sock, resp,128,0); + if (gsiSocketIsError(len)) + { + int anError = GOAGetLastError(sock); + closesocket(sock); + + if ((anError != WSAEWOULDBLOCK) && (anError != WSAETIMEDOUT) && (anError != WSAEINPROGRESS)) + return GE_NOCONNECT; + else + return GE_DATAERROR; //temp fix in case len == -1, SOCKET_ERROR + } + + resp[len] = 0; + DOXCODE(resp, len, enc1); + sesskeystr[0] = 's'; + stext = value_for_key(resp, sesskeystr); + if (stext == NULL) + { + closesocket(sock); + return GE_DATAERROR; + } else + connid = atoi(stext); + + return GE_NOERROR; +} + + + +static int DoSend(char *data, int len) +{ + int sent = 0; + + DOXCODE(data,len, enc1); + strcpy(data+len,finalstr); + + // Loop to make sure async send goes through! + while(sent < (len+7)) + { + // Send remaining data + int ret = send(sock, (data+sent), (len+7-sent), 0); + if (gsiSocketIsError(ret)) + { + int anError = GOAGetLastError(sock); + if ((anError != WSAEWOULDBLOCK) && (anError != WSAETIMEDOUT) && (anError != WSAEINPROGRESS)) + return anError; + } + else if (ret == 0) + { + // socket was closed + return -1; + } + else + sent += ret; + }; + + return sent; +} + + + +#ifdef ALLOW_DISK +/* Note: lots of this is byte order and type size specific, but it shouldn't +matter since the data is read/written always on the same machine */ +#define DISKLENXOR 0x70F33A5F +static void CheckDiskFile() +{ + FILE *f; + char *line; + char *alldata; + int len, check, alllen, fsize; + char filemode[3]; + /* hide our file access from the string table */ + filemode[0] = 'r'; filemode[1] = 'b'; filemode[2] = 0; + f = fopen(statsfile,filemode); + if (!f) + return; + /* get the size */ + fseek(f, 0, SEEK_END); + fsize = ftell(f); + fseek(f, 0, SEEK_SET); + /* make room for the whole thing */ + alldata = (char *)gsimalloc(fsize + 2); + alldata[0] = 0; + alllen = 0; + while (!feof(f) && !ferror(f)) + { + /* read the check and line values */ + if (fread(&check,sizeof(check),1,f) == 0 || + fread(&len,sizeof(len),1,f) == 0) + break; + len ^= DISKLENXOR; + line = (char *)gsimalloc(len + 1); + /* read the data */ + if (fread(line, 1, len, f) != (size_t)len) + break; + line[len] = 0; + /* decode for checking */ + DOXCODE(line, len, enc2); + /* double "check" */ + if (check != g_crc32(line, len)) + { + gsifree(line); + break; + } + /* encode for xmission */ + DOXCODE(line, len, enc1); + memcpy(alldata + alllen, line, len); + alllen += len; + memcpy(alldata + alllen, finalstr, 7); + alllen += 7; + gsifree(line); + } + fclose(f); + /* try to send */ + len = send(sock, alldata, alllen, 0); + if (len <= 0) + { + closesocket(sock); + sock = INVALID_SOCKET; + } else + remove(statsfile); +} + +static void DiskWrite(char *line, int len) +{ + FILE *f; + int check; + int temp; + char filemode[3]; + /* hide our file access from the string table */ + filemode[0] = 'a'; filemode[1] = 'b'; filemode[2] = 0; + f = fopen(statsfile,filemode); + if (!f) + return; + check = g_crc32(line, len); + fwrite(&check, sizeof(check),1,f); + temp = len ^ DISKLENXOR; + fwrite(&temp, sizeof(temp), 1, f); + DOXCODE(line, len, enc2); + fwrite(line, 1, len, f); + fclose(f); +} + +#endif /* ALLOW_DISK */ + +/* simple xor encoding */ +static void xcode_buf(char *buf, int len) +{ + int i; + char *pos = enc; + + for (i = 0 ; i < len ; i++) + { + buf[i] ^= *pos++; + if (*pos == 0) + pos = enc; + } +} + +#define MULTIPLIER -1664117991 +static int g_crc32(char *s, int len) +{ + int i; + int hashcode = 0; + + for (i = 0; i < len; i++) + hashcode = hashcode * MULTIPLIER + s[i]; + return hashcode; +} + +static void create_challenge(int challenge, char chstr[9]) +{ + char *p = chstr; + sprintf(chstr, "%08x",challenge); + + while (*p != 0) + { + *p = (char)((*p) + ('A' - '0') + (p-chstr)); + p++; + } +} + +/* value_for_key: this returns a value for a certain key in s, where s is a string +containing key\value pairs. If the key does not exist, it returns NULL. +Note: the value is stored in a common buffer. If you want to keep it, make a copy! */ +static char *value_for_key(const char *s, const char *key) +{ + static int valueindex; + char *pos,*pos2; + char keyspec[256]="\\"; + static char value[2][256]; + + valueindex ^= 1; + strcat(keyspec,key); + strcat(keyspec,"\\"); + pos = strstr(s,keyspec); + if (!pos) + return NULL; + pos += strlen(keyspec); + pos2 = value[valueindex]; + while (*pos && *pos != '\\') + *pos2++ = *pos++; + *pos2 = '\0'; + return value[valueindex]; +} + +/* like value_for_key, but returns an empty string instead of NULL in the not-found case */ +static char *value_for_key_safe(const char *s, const char *key) +{ + char *temp; + + temp = value_for_key(s, key); + if (!temp) + return ""; + else + return temp; +} + +/* Return a sockaddrin for the given host (numeric or DNS) and port) +Returns the hostent in savehent if it is not NULL */ +static int get_sockaddrin(const char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent) +{ + struct hostent *hent = NULL; + + memset(saddr,0,sizeof(struct sockaddr_in)); + saddr->sin_family = AF_INET; + saddr->sin_port = htons((unsigned short)port); + if (host == NULL) + saddr->sin_addr.s_addr = INADDR_ANY; + else + saddr->sin_addr.s_addr = inet_addr(host); + + if (saddr->sin_addr.s_addr == INADDR_NONE) + { + hent = gethostbyname(host); + if (!hent) + return 0; + saddr->sin_addr.s_addr = *(unsigned int *)hent->h_addr_list[0]; + } + if (savehent != NULL) + *savehent = hent; + return 1; +} + +/* adds a request callback to the list */ +static void AddRequestCallback(reqtype_t reqtype, int localid, int profileid, persisttype_t pdtype, int pdindex, void *callback, void *instance) +{ + serverreq_t req; + + req.callback = callback; + req.instance = instance; + req.localid = localid; + req.reqtype = reqtype; + req.profileid = profileid; + req.pdtype = pdtype; + req.pdindex = pdindex; + if (serverreqs == NULL) //create the callback array + { + serverreqs = ArrayNew(sizeof(serverreq_t),2,NULL); + } + ArrayAppend(serverreqs,&req); +} + +/* sends the player authentication request (GP or CD) */ +static void SendPlayerAuthRequest(char *data, int len, int localid, PersAuthCallbackFn callback, void *instance) +{ + int sentlen = 0; + char connerror[] = "\x13\x1D\x1\x4\x0\x0\x0\x28\x1F\x6\x45\x34\x3F\x1\x1B"; + //"Connection Lost" + + if (sock != INVALID_SOCKET) + sentlen = DoSend(data, len); + + /* If the send failed, close the socket */ + if (sentlen <= 0) + { + CloseStatsConnection(); + DOXCODE(connerror, sizeof(connerror)-1, enc3); + if (callback) + { +#ifndef GSI_UNICODE + callback(localid, 0,0, connerror ,instance); +#else + unsigned short connerror_W[] = L"\x13\x1D\x1\x4\x0\x0\x0\x28\x1F\x6\x45\x34\x3F\x1\x1B"; + callback(localid, 0, 0, connerror_W, instance); +#endif + } + } else + { /* set up the callback */ + AddRequestCallback(rt_authcb, localid, 0,(persisttype_t)0,0,callback, instance); + } +} + +/* send a set request, if kvset, then only those keys/values will bet updated */ +static void SetPersistDataHelper(int localid, int profileid, persisttype_t type, int index, const char *data, int len, PersDataSaveCallbackFn callback, void *instance, int kvset) +{ + char respformat[] = "\xC\x1\xA\x1E\x15\x7\x28\x1D\x0\x1\x1\x24\x75\x16\x33\x1A\x11\x1A\x4\x24\x2C\x4D\x1\x24\x34\x1B\x1\xE\x0\x1B\x28\x64\x14\x34\xE\xE\xC\x57\xB\x36\x9\xA\x10\x1D\x55\xC\x39\x14\x35\x1C\x8\x1E\xD\x3F\x51\x25\x2C\xC\x4\xC\x31\x2E"; + //\setpd\\pid\%d\ptype\%d\dindex\%d\kv\%d\lid\%d\length\%d\data\ -- + int tlen; + char tdata[512]; + char *senddata; + + DOXCODE(respformat, sizeof(respformat)-1, enc3); + + if (type == pd_private_ro || type == pd_public_ro) + { //can't set read-only types, check that client side + if (callback) + callback(localid, profileid, type, index, 0, 0, instance); + return; + } + + tlen = sprintf(tdata, respformat, profileid, type, index, kvset, localid, len); + if (tlen + len < 480) //we have enough room to put it in the data block + { + memcpy(tdata + tlen, data, (unsigned int)len); + senddata = tdata; + + } else //need to alloc a temp buffer + { + senddata = (char *)gsimalloc((unsigned int)(len + tlen + 256)); + memcpy(senddata, tdata, (unsigned int)tlen); + memcpy(senddata + tlen, data, (unsigned int)len); + } + + if (sock != INVALID_SOCKET) + tlen = DoSend(senddata, tlen + len); + + /* If the send failed, close the socket */ + if (tlen <= 0) + { + CloseStatsConnection(); + if (callback) + callback(localid, profileid, type, index, 0, 0, instance); + } else + { /* set up the callback */ + AddRequestCallback(rt_savecb, localid, profileid,type, index, callback, instance); + } + if (senddata != tdata) //if we alloc'd before sending + gsifree(senddata); +} + +/* returns 1 if the socket is readable, 0 otherwise */ +static int SocketReadable(SOCKET s) +{ + return CanReceiveOnSocket(s); +/* + fd_set set; + struct timeval tv = {0,0}; + int n; + + if (s == INVALID_SOCKET) + return 0; + + FD_ZERO(&set); + FD_SET(s, &set); + + n = select(FD_SETSIZE, &set, NULL, NULL, &tv); + + return n; +*/ +} + +/* find the \final\ string */ +static char *FindFinal(char *buff, int len) +{ + char *pos = buff; + + while (pos - buff < len - 6) + { + if (pos[0] == '\\' && + pos[1] == 'f' && + pos[2] == 'i' && + pos[3] == 'n' && + pos[4] == 'a' && + pos[5] == 'l' && + pos[6] == '\\') + { + return pos; + } else + pos++; + } + return NULL; +} + +/* find the request in the callback list */ +static int FindRequest(reqtype_t reqtype, int localid, int profileid) +{ + int i; + serverreq_t *req; + + if (serverreqs == NULL) + return -1; + for (i = 0 ; i < ArrayLength(serverreqs); i++) + { + req = (serverreq_t *)ArrayNth(serverreqs, i); + if (req->reqtype == reqtype && req->localid == localid && req->profileid == profileid) + return i; + } + return -1; + +} + +/* process the playerauth result */ +static void ProcessPlayerAuth(const char *buf, int len) +{ + // \\pauthr\\100000\\lid\\1 + int reqindex; + char *errmsg; + int pid; + int lid; + pid = atoi(value_for_key_safe(buf,"pauthr")); + lid = atoi(value_for_key_safe(buf,"lid")); + errmsg = value_for_key_safe(buf,"errmsg"); + reqindex = FindRequest(rt_authcb,lid,0); + if (reqindex == -1) + return; + ((serverreq_t *)ArrayNth(serverreqs, reqindex))->profileid = pid; + CallReqCallback(reqindex,(pid > 0),0, errmsg,0); + + GSI_UNUSED(len); +} + +/* process the get profileid result */ +static void ProcessGetPid(const char *buf, int len) +{ + // \\getpidr\\100000\\lid\\1 + int reqindex; + int pid; + int lid; + pid = atoi(value_for_key_safe(buf,"getpidr")); + lid = atoi(value_for_key_safe(buf,"lid")); + reqindex = FindRequest(rt_profilecb,lid,0); + if (reqindex == -1) + return; + ((serverreq_t *)ArrayNth(serverreqs, reqindex))->profileid = pid; + CallReqCallback(reqindex,(pid > 0),0,NULL,0); + + GSI_UNUSED(len); +} + +/* process the get data result */ +static void ProcessGetData(const char *buf, int len) +{ + // \\getpdr\\1\\lid\\1\\mod\\1234\\length\\5\\data\\mydata\\final + int reqindex; + int pid; + int lid; + int success; + int length; + time_t modified; + char *data; + success = atoi(value_for_key_safe(buf,"getpdr")); + lid = atoi(value_for_key_safe(buf,"lid")); + pid = atoi(value_for_key_safe(buf,"pid")); + modified = atoi(value_for_key_safe(buf,"mod")); + reqindex = FindRequest(rt_datacb,lid,pid); + if (reqindex == -1) + return; + length = atoi(value_for_key_safe(buf,"length")); + data = strstr(buf,"\\data\\"); + if (!data) + { + length = 0; + data = ""; + } else + data += 6; //skip the key + CallReqCallback(reqindex,success,modified, data,length); + + GSI_UNUSED(len); +} + +/* process the set data result */ +static void ProcessSetData(const char *buf, int len) +{ + // \\setpdr\\1\\lid\\2\\pid\\100000\\mod\\12345 + int reqindex; + int pid; + int lid; + int success; + int modified; + success = atoi(value_for_key_safe(buf,"setpdr")); + pid = atoi(value_for_key_safe(buf,"pid")); + lid = atoi(value_for_key_safe(buf,"lid")); + modified = atoi(value_for_key_safe(buf,"mod")); + reqindex = FindRequest(rt_savecb,lid,pid); + if (reqindex == -1) + return; + CallReqCallback(reqindex,success,modified,NULL,0); + + GSI_UNUSED(len); +} + +/* process a single statement */ +static void ProcessStatement(char *buff, int len) +{ + //determine the type + + buff[len] = 0; + // printf("GOT: %s\n",buff); + if (strncmp(buff,"\\pauthr\\",8) == 0) + { + ProcessPlayerAuth(buff, len); + } else if (strncmp(buff,"\\getpidr\\",9) == 0) + { + ProcessGetPid(buff, len); + } else if (strncmp(buff,"\\getpidr\\",9) == 0) + { + ProcessGetPid(buff, len); + } else if (strncmp(buff,"\\getpdr\\",8) == 0) + { + ProcessGetData(buff, len); + } else if (strncmp(buff,"\\setpdr\\",8) == 0) + { + ProcessSetData(buff, len); + } + +} + + +/* processes statements in the buffer and returns amount processed */ +// 09-13-2004 BED Modified loop to silence compiler warning +static int ProcessInBuffer(char *buff, int len) +{ + char *pos; + int oldlen = len; + + pos = FindFinal(buff, len); + //while (len > 0 && (pos = FindFinal(buff, len))) + while ((len > 0) && (pos != NULL)) + { + DOXCODE(buff,pos - buff, enc1); + ProcessStatement(buff, pos - buff); + len -= (pos - buff) + 7; + buff = pos + 7; //skip the final + if (len>0) + pos = FindFinal(buff, len); + } + return oldlen - len; //amount processed +} + +/* call a single callback function */ +static void CallReqCallback(int reqindex, int success, time_t modified, char *data, int length) +{ + serverreq_t *req; + if (reqindex < 0 || reqindex >= ArrayLength(serverreqs)) + return; + req = (serverreq_t *)ArrayNth(serverreqs, reqindex); + if (req->callback) + switch (req->reqtype) + { + case rt_authcb: +#ifndef GSI_UNICODE + ((PersAuthCallbackFn )req->callback)(req->localid, req->profileid, success, data, req->instance); +#else + { + unsigned short* data_W = UTF8ToUCS2StringAlloc(data); + ((PersAuthCallbackFn )req->callback)(req->localid, req->profileid, success, data_W, req->instance); + gsifree(data_W); + } +#endif + break; + case rt_datacb: + ((PersDataCallbackFn )req->callback)(req->localid,req->profileid,req->pdtype, req->pdindex, success, modified, data, length, req->instance); + break; + case rt_savecb: + ((PersDataSaveCallbackFn )req->callback)(req->localid, req->profileid, req->pdtype, req->pdindex,success, modified, req->instance); + break; + case rt_profilecb: + ((ProfileCallbackFn )req->callback)(req->localid, req->profileid, success, req->instance); + break; + } + ArrayDeleteAt(serverreqs,reqindex); +} + +/* if we get disconnected while callbacks are still pending, make sure we +call all of them, with a success of 0 */ +static void ClosePendingCallbacks() +{ + int i; + + if (serverreqs == NULL) + return; + for (i = ArrayLength(serverreqs) - 1 ; i >= 0 ; i--) + { + char connerror[] = "\x13\x1D\x1\x4\x0\x0\x0\x28\x1F\x6\x45\x34\x3F\x1\x1B"; + //"Connection Lost" + DOXCODE(connerror, sizeof(connerror)-1, enc3); + + CallReqCallback(i,0,0,connerror,0); + + } + ArrayFree(serverreqs); + serverreqs = NULL; +} + + + +/****************************************************************************/ +/* BUCKET FUNCTIONS */ +/****************************************************************************/ +int GetTeamIndex(statsgame_t game, int tnum) +{ + FIXGAME(game, tnum); + return *(int *)ArrayNth(game->teamnums,tnum); +} +int GetPlayerIndex(statsgame_t game, int pnum) +{ + FIXGAME(game, pnum); + return *(int *)ArrayNth(game->playernums,pnum); +} + +static int ServerOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index) +{ + int *ret; + DoFunc(func, game, name, &value, bt_int, ret); + + GSI_UNUSED(index); + return *(int *)ret; +} +static double ServerOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index) +{ + double *ret; + DoFunc(func, game, name, &value, bt_float, ret); + + GSI_UNUSED(index); + return *(double *)ret; +} +static char *ServerOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index) +{ + char *ret; + DoFunc(func, game, name, value, bt_string, ret); + + GSI_UNUSED(index); + return ret; +} + +static int TeamOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index) +{ + char fullname[64]; + sprintf(fullname, "%s_t%d",name, GetTeamIndex(game, index)); + return ServerOpInt(game, fullname, func, value, index); +} +static double TeamOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index) +{ + char fullname[64]; + sprintf(fullname, "%s_t%d",name, GetTeamIndex(game, index)); + return ServerOpFloat(game, fullname, func, value, index); +} +static char *TeamOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index) +{ + char fullname[64]; + sprintf(fullname, "%s_t%d",name, GetTeamIndex(game, index)); + return ServerOpString(game, fullname, func, value, index); +} + +static int PlayerOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index) +{ + char fullname[64]; + sprintf(fullname, "%s_%d",name, GetPlayerIndex(game, index)); + return ServerOpInt(game, fullname, func, value, index); +} +static double PlayerOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index) +{ + char fullname[64]; + sprintf(fullname, "%s_%d",name, GetPlayerIndex(game, index)); + return ServerOpFloat(game, fullname, func, value, index); +} +static char *PlayerOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index) +{ + char fullname[64]; + sprintf(fullname, "%s_%d",name, GetPlayerIndex(game, index)); + return ServerOpString(game, fullname, func, value, index); +} + +static char *CreateBucketSnapShot(bucketset_t buckets) +{ + return DumpBucketSet(buckets); +} + +#ifdef __cplusplus +} +#endif diff --git a/code/gamespy/gstats/gstats.h b/code/gamespy/gstats/gstats.h new file mode 100644 index 00000000..b1f69bfb --- /dev/null +++ b/code/gamespy/gstats/gstats.h @@ -0,0 +1,389 @@ +/****** +gstats.h +GameSpy Stats/Tracking SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +****** + +Please see the GameSpy Stats and Tracking SDK documentation for more info + +08-23-00 - DDW +Fixed a problem that prevented opening/closing/re-opening of the connection within +a single session. + +*****/ + +#ifndef _GSTATS_H_ +#define _GSTATS_H_ + + +/******** +INCLUDES +********/ +#include "../common/gsCommon.h" +#include "gbucket.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_WIN32) + // Warnings are generated because we store function ptrs into a void* array +#pragma warning(disable: 4152) // function to data ptr +#pragma warning(disable: 4055) // data to function ptr +#endif + + +/******** +TYPEDEFS +********/ + + +/* The abstracted "game" structure */ +typedef struct statsgame_s *statsgame_t; + +/* All of the operations you can do on a bucket */ +typedef enum {bo_set, bo_add, bo_sub, bo_mult, bo_div, bo_concat, bo_avg} bucketop_t; +#define NUMOPS 7 + +/* The types of buckets (server info, team info, or player info) */ +typedef enum {bl_server, bl_team, bl_player} bucketlevel_t; + +/* Init states for async initialization */ +typedef enum {init_none, init_failed, init_connecting, init_awaitchallenge, init_awaitsessionkey, init_complete} initstate_t; + +/* Used by the bucket operation macros */ +typedef void *(*BucketFunc)(bucketset_t set, char *name,void *value); +typedef int (*SetIntFunc)(statsgame_t game,char *name, BucketFunc func, int value, int index); +typedef double (*SetFloatFunc)(statsgame_t game,char *name, BucketFunc func, double value, int index); +typedef char *(*SetStringFunc)(statsgame_t game,char *name, BucketFunc func, char *value, int index); +extern BucketFunc bucketfuncs[NUMOPS]; +extern void * bopfuncs[][3]; + +/******** +DEFINES +********/ +/* Error codes */ +#define GE_NOERROR 0 +#define GE_NOSOCKET 1 /* Unable to create a socket */ +#define GE_NODNS 2 /* Unable to resolve a DNS name */ +#define GE_NOCONNECT 3 /* Unable to connect to stats server, or connection lost */ +#define GE_BUSY 4 /* Not used */ +#define GE_DATAERROR 5 /* Bad data from the stats server */ +#define GE_CONNECTING 6 /* Connect did no immediately complete. Call InitStatsThink() */ +#define GE_TIMEDOUT 7 /* Connect attempt timed out */ + +/* Types of snapshots, update (any snapshot that is not final) or final */ +#define SNAP_UPDATE 0 +#define SNAP_FINAL 1 + +/* If you want to allow disk logging in case the stats server isn't available. +This has SERIOUS security repercussions, so please read the docs before turning this on */ +#define ALLOW_DISK + +#if defined(NOFILE) + #undef ALLOW_DISK +#endif /* make sure it's never defined on platforms with no disk! */ + +/******** +VARS +********/ + +/* You need to fill these in with your game-specific info */ +extern char gcd_secret_key[256]; +extern char gcd_gamename[256]; + +/* The hostname of the stats server. +If the app resolves the hostname, an +IP can be stored here before calling +InitStatsConnection */ +extern char StatsServerHostname[64]; + + +/******** +PROTOTYPES +********/ +#ifndef GSI_UNICODE +#define GenerateAuth GenerateAuthA +#define SendGameSnapShot SendGameSnapShotA +#define NewPlayer NewPlayerA +#define NewTeam NewTeamA +#else +#define GenerateAuth GenerateAuthW +#define SendGameSnapShot SendGameSnapShotW +#define NewPlayer NewPlayerW +#define NewTeam NewTeamW +#endif + +/******** +InitStatsConnection + +DESCRIPTION +Opens a connection to the stats server. Should be done before calling +NewGame or any of the bucket/snapshot functions. May block for 1-2 secs +while the connection is established so you will want to do this before +gameplay starts or in another thread. + +PARAMETERS +gameport: integer port associated with your server (may be the same as + your developer spec query port). Used only to help players differentiate + between servers on the same machine (no queries are done on it). If not + appropriate for your game, pass in 0. + +RETURNS +GE_NODNS: Unable to resolve stats server DNS +GE_NOSOCKET: Unable to create data socket +GE_NOCONNECT: Unable to connect to stats server +GE_DATAERROR: Unable to receive challenge from stats server, or bad challenge +GE_NOERROR: Connected to stats server and ready to send data + +Note: You can still call ANY of the other Stats SDK functions, even if the +connection fails. If you have disk logging enabled, these calls will be logged +for future sending, otherwise they will be discarded. +*********/ +int InitStatsConnection(int gameport); +int InitStatsAsync(int gameport, gsi_time theInitTimeout); +int InitStatsThink(); + + +/******** +StatsThink + +DESCRIPTION +Eats up any incoming keep-alive messages that are sent by the stats server. +Returns any errors occur because of a socket problem or if the SDK was +not completely initialized. + +RETURNS +1 if no errors occured during read, 0 on all other errors +********/ +int StatsThink(); + +/******** +IsStatsConnected + +DESCRIPTION +Returns whether or not you are currently connected to the stats server. Even +if your initial connection was successful, you may lose connection later and +want to try to reconnnect + +RETURNS +1 if connected, 0 otherwise +*********/ +int IsStatsConnected(); + +/******** +CloseStatsConnection + +DESCRIPTION +Closes the connection to the stats server. You should do this when done +with the connection. +*********/ +void CloseStatsConnection(void); + +/******** +GetChallenge + +DESCRIPTION +Returns a string that should be sent to clients for authentication +(using GenerateAuth). You do not have to free the string when done. +This string will be constant for the entire length of the game and is +generated during the call to NewGame. + +PARAMETERS +game: Game to return the challenge string for. If game is NULL, the last + game created with NewGame will be used. + +RETURNS +A string to send to clients so they can authorize. If you game is NULL and +you haven't created a game with NewGame, it returns "NULLGAME". +*********/ +char *GetChallenge(statsgame_t game); + +/******** +GenerateAuth + +DESCRIPTION +Should be used on the CLIENT SIDE to generate an authentication reply +(auth_N) for a given challenge and password (CD Key or Profile password) + +PARAMETERS +challenge: The challenge string sent by the server. On the server this + should be generated with GetChallenge +password: The CD Key (un-hashed) or profile password +response: The output authentication string + +RETURNS +A pointer to response +*********/ +char *GenerateAuth(const char *challenge, const gsi_char *password,/*[out]*/char response[33]); + +/******** +NewGame + +DESCRIPTION +Creates a new game for logging and registers it with the stats server. +Creates all the game structures, including buckets if needed. + +PARAMETERS +usebuckets: Set to 1 for bucket based logging, 0 if you are going to create + the snapshots yourself. See the SDK for more info. + +RETURNS +A pointer to the new game. If you are not connected, and disk logging is +disabled, this will be NULL. You can still pass NULL to any function without +causing any errors. +Note: The last game created by NewGame is stored internally. +If you only create / use one game at a time, you can simply discard +the return value and pass NULL for game into all of the bucket and snapshot functions. +*********/ +statsgame_t NewGame(int usebuckets); + +/******** +FreeGame + +DESCRIPTION +Frees a game and its associated structures (including buckets). You should +send a final snapshot for the game (using SendGameSnapShot with SNAP_FINAL) +before freeing the game. + +PARAMETERS +game: The game you want to free. If set to NULL, it will free the last + game created with NewGame. +*********/ +void FreeGame(statsgame_t game); + +/******** +SendGameSnapShot + +DESCRIPTION +Sends a snapshot of information about the current game. If bucket based +logging is enabled the snapshot will be generated from the buckets, otherwise +you should provide it in "snapshot". + +PARAMETERS +game: The game to send a snapshot for. If set to NULL, the last game + created with NewGame will be used. +snapshot: The snapshot to send. If you are using buckets, this will not be + used, so you can pass in NULL +final: If this is SNAP_UPDATE, the game is marked as in progress, if it + is SNAP_FINAL, the game is marked as complete. + +RETURNS +GE_DATAERROR: If game is NULL and the last game created by NewGame failed + (because the connection was lost and disk logging is disabled) +GE_NOCONNECT: If the connection is lost and disk logging is disabled +GE_NOERROR: The update was sent, or disk logging is enabled and the game was logged +*********/ +int SendGameSnapShot(statsgame_t game, const gsi_char *snapshot, int final); + +/****************************** +BUCKET FUNCTION PROTOTYPES +These functions are only used for bucket-based logging +*******************************/ + +/******** +Bucket_____Op + +DESCRIPTION +Performs an operation on a bucket for a game. If the bucket doesn't exist already, +the call will set the bucket to whatever "value" is. +You can always create each bucket explicitly by using bo_set with whatever initial +value you want the bucket to have. +Valid operations include set, add, subtract, multiply, divide, concat, and average. +Each bucket type (int, float, or string) has its own operation function, always call +the same one for each bucket (i.e. don't create a bucket with BucketIntOp then try to +add a float with BucketFloatOp). + +PARAMETERS +game: The game to send containing the bucket you want to operate on. + If set to NULL, the last game created with NewGame will be used. +name: The name of the bucket to update. Note that for player or team buckets, this name + does NOT include the "_" or "_t" (e.g. "score" for player score, not "score_N"). The underscore + and number will be added automatically. +operation: One of the bucketop_t enums defined above +value: Argument for the operation (bucket OP= value, e.g. bucket += value, bucket *= value) +bucketlevel: One of the bucketlevel_t enums defined above. Determines whether you are + referring to a server, player, or team bucket. Note that you can have seperate buckets of + each type with the same name (e.g. "score" player bucket for each player and "score" team + bucket for each team) +index: For player or team buckets, the game index of the player or team (as passed to NewPlayer or + NewTeam). This will be translated to the actual index internally. + Not used for server buckets (bl_server). +*********/ +#define BucketIntOp(game, name, operation, value, bucketlevel, index) (((SetIntFunc)bopfuncs[bucketlevel][bt_int])(game,name,bucketfuncs[operation],value,index) ) +#define BucketFloatOp(game, name, operation, value, bucketlevel, index) (((SetFloatFunc)bopfuncs[bucketlevel][bt_float])(game,name,bucketfuncs[operation],value,index) ) +#define BucketStringOp(game, name, operation, value, bucketlevel, index) (((SetStringFunc)bopfuncs[bucketlevel][bt_string])(game,name,bucketfuncs[operation],value,index) ) + +/******** +NewPlayer + +DESCRIPTION +Adds a "player" to the game and assigns them an internal player number. Sets +their connect time to the number of seconds since NewGame was called. + +PARAMETERS +game: The game to add the player to. If set to NULL, the last game created + with NewGame will be used. +pnum: Your internal reference for this player, use this value in any calls + to the Bucket___Op functions. +name: The name for this player. If you don't have one yet, set it to empty ("") + then call: BucketStringOp(game,"player",bo_set,realplayername, bl_player, pnum) + when you get a realplayername. +**********/ +void NewPlayer(statsgame_t game,int pnum, gsi_char *name); + +/******** +RemovePlayer + +DESCRIPTION +Removes a "player" from the game and sets their disconnect time to the +number of seconds since NewGame was called. + +PARAMETERS +game: The game to remove the player from. If set to NULL, the last game created + with NewGame will be used. +pnum: Your internal reference for this player, use this value in any calls + to the Bucket___Op functions. +**********/ +void RemovePlayer(statsgame_t game,int pnum); + +/********* +NewTeam +RemoveTeam + +DESCRIPTION +See the player functions above. These function the same, except for teams +**********/ +void NewTeam(statsgame_t game,int tnum, gsi_char *name); +void RemoveTeam(statsgame_t game,int tnum); + +/********* +GetPlayerIndex +GetTeamIndex + +DESCRIPTION +Gets the gstats reference number for that player or team. For +example, if you start the game and players 0, 1, and 2 join, then player 1 +leaves, and another player 1 joins, the new player 1 will be referenced +by gstats as 3. If player 3 joins, it will be referenced as player 4, and so on. +Normally this doesn't matter to you, but if you want to do a key name or key value +that references a player or team number (for example, setting a player's team number), +you need to use the translated values. + +PARAMETERS +game: The game to retrieve the translated value for. If set to NULL,the last game created + with NewGame will be used. +pnum/tnum: Your internal player or team number (as sent to NewTeam/NewPlayer) +**********/ +int GetPlayerIndex(statsgame_t game, int pnum); +int GetTeamIndex(statsgame_t game, int tnum); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/gstats/ladderTrack/HostOrJoinDlg.cpp b/code/gamespy/gstats/ladderTrack/HostOrJoinDlg.cpp new file mode 100644 index 00000000..85de2c74 --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/HostOrJoinDlg.cpp @@ -0,0 +1,69 @@ +// HostOrJoinDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "ladderTrack.h" +#include "HostOrJoinDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CHostOrJoinDlg dialog + + +CHostOrJoinDlg::CHostOrJoinDlg(CWnd* pParent /*=NULL*/) + : CDialog(CHostOrJoinDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CHostOrJoinDlg) + m_joinAddress = _T(""); + m_hostOrJoin = 0; + //}}AFX_DATA_INIT +} + + +void CHostOrJoinDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CHostOrJoinDlg) + DDX_Text(pDX, IDC_JOIN_ADDRESS, m_joinAddress); + DDX_Radio(pDX, IDC_HOST, m_hostOrJoin); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CHostOrJoinDlg, CDialog) + //{{AFX_MSG_MAP(CHostOrJoinDlg) + ON_EN_SETFOCUS(IDC_JOIN_ADDRESS, OnSetfocusJoinAddress) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CHostOrJoinDlg message handlers + +void CHostOrJoinDlg::OnOK() +{ + UpdateData(); + + // Check for no join address. + ///////////////////////////// + if((m_hostOrJoin == HOSTORJOIN_JOIN) && m_joinAddress.IsEmpty()) + { + MessageBox("Please enter the address of the host to connect to"); + return; + } + + CDialog::OnOK(); +} + +void CHostOrJoinDlg::OnSetfocusJoinAddress() +{ + // Make sure its on join. + ///////////////////////// + UpdateData(); + m_hostOrJoin = HOSTORJOIN_JOIN; + UpdateData(FALSE); +} diff --git a/code/gamespy/gstats/ladderTrack/HostOrJoinDlg.h b/code/gamespy/gstats/ladderTrack/HostOrJoinDlg.h new file mode 100644 index 00000000..98fbc34e --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/HostOrJoinDlg.h @@ -0,0 +1,51 @@ +#if !defined(AFX_HOSTORJOINDLG_H__7A7BDB42_5AA8_45CD_9DD1_0EA7FC3A723F__INCLUDED_) +#define AFX_HOSTORJOINDLG_H__7A7BDB42_5AA8_45CD_9DD1_0EA7FC3A723F__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// HostOrJoinDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CHostOrJoinDlg dialog + +#define HOSTORJOIN_HOST 0 +#define HOSTORJOIN_JOIN 1 + +class CHostOrJoinDlg : public CDialog +{ +// Construction +public: + CHostOrJoinDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CHostOrJoinDlg) + enum { IDD = IDD_HOST_OR_JOIN }; + CString m_joinAddress; + int m_hostOrJoin; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CHostOrJoinDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CHostOrJoinDlg) + virtual void OnOK(); + afx_msg void OnSetfocusJoinAddress(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_HOSTORJOINDLG_H__7A7BDB42_5AA8_45CD_9DD1_0EA7FC3A723F__INCLUDED_) diff --git a/code/gamespy/gstats/ladderTrack/LoginDlg.cpp b/code/gamespy/gstats/ladderTrack/LoginDlg.cpp new file mode 100644 index 00000000..d8c7ef74 --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/LoginDlg.cpp @@ -0,0 +1,188 @@ +// LoginDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "ladderTrack.h" +#include "LoginDlg.h" +#include "../../common/gsAvailable.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + + +CLoginDlg::CLoginDlg(CWnd* pParent /*=NULL*/) + : CDialog(CLoginDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CLoginDlg) + m_email = _T(""); + m_nick = _T(""); + m_password = _T(""); + //}}AFX_DATA_INIT +} + + +void CLoginDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CLoginDlg) + DDX_Text(pDX, IDC_EMAIL, m_email); + DDX_Text(pDX, IDC_NICK, m_nick); + DDX_Text(pDX, IDC_PASSWORD, m_password); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CLoginDlg, CDialog) + //{{AFX_MSG_MAP(CLoginDlg) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg message handlers + +GPResult checkResult; +int checkProfile; + +void CheckUserCallback(GPConnection * connection, void * _arg, void * param) +{ + GPCheckResponseArg * arg = (GPCheckResponseArg *)_arg; + + checkResult = arg->result; + checkProfile = arg->profile; +} + +void CLoginDlg::OnOK() +{ + GPConnection connection; + HCURSOR hourglass; + HCURSOR lastCursor; + GPResult result; + + UpdateData(); + + // CHECK FOR NO ACCOUNT INFO + //////////////////////////// + if(m_email.IsEmpty() || m_nick.IsEmpty() || m_password.IsEmpty()) + { + MessageBox("Please fill in all the account information."); + return; + } + + // Make sure the backend is available + GSIACResult aResult = GSIACWaiting; + GSIStartAvailableCheck(gcd_gamename); + while(aResult == GSIACWaiting) + { + aResult = GSIAvailableCheckThink(); + msleep(5); + } + + if (aResult == GSIACUnavailable) + { + MessageBox("GameSpy backend services are not available."); + return; + } + + // INITIALIZE GP + //////////////// + if(gpInitialize(&connection, 535, 0, GP_PARTNERID_GAMESPY) != GP_NO_ERROR) + { + MessageBox("Error initializing the login system."); + return; + } + + // wait cursor on + ///////////////// + hourglass = LoadCursor(NULL, IDC_WAIT); + if(hourglass) + lastCursor = SetCursor(hourglass); + + // CHECK FOR THE ACCOUNT SPECIFIED + ////////////////////////////////// + result = gpCheckUser(&connection, m_nick, m_email, m_password, GP_BLOCKING, CheckUserCallback, NULL); + + // wait cursor off + ////////////////// + if(hourglass) + SetCursor(lastCursor); + + // DESTROY THE GP OBJECT + //////////////////////// + gpDestroy(&connection); + + // CHECK FOR AN ERROR + ///////////////////// + if(result != GP_NO_ERROR) + { + MessageBox("Error verifying the account."); + return; + } + + // CHECK THE RESULT + /////////////////// + if(checkResult != GP_NO_ERROR) + { + if(checkResult == GP_CHECK_BAD_EMAIL) + MessageBox("Invalid e-mail."); + else if(checkResult == GP_CHECK_BAD_NICK) + MessageBox("Invalid nick."); + else if(checkResult == GP_CHECK_BAD_PASSWORD) + MessageBox("Invalid password."); + else + MessageBox("Error verifying the account."); + return; + } + + // save the login info for next time + //////////////////////////////////// + FILE * file; + file = fopen("login.txt", "wt"); + if(file) + { + fprintf(file, "%s\n%s\n%s", m_email, m_nick, m_password); + fclose(file); + } + + // STORE THE PROFILE ID + /////////////////////// + m_profile = checkProfile; + + CDialog::OnOK(); +} + +BOOL CLoginDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // load login info + ////////////////// + FILE * file; + file = fopen("login.txt", "rt"); + if(file) + { + char buffer[512]; + + if(fgets(buffer, sizeof(buffer), file)) + m_email = buffer; + if(fgets(buffer, sizeof(buffer), file)) + m_nick = buffer; + if(fgets(buffer, sizeof(buffer), file)) + m_password = buffer; + + fclose(file); + + m_email.Remove('\n'); + m_nick.Remove('\n'); + m_password.Remove('\n'); + } + + UpdateData(FALSE); + + return TRUE; +} \ No newline at end of file diff --git a/code/gamespy/gstats/ladderTrack/LoginDlg.h b/code/gamespy/gstats/ladderTrack/LoginDlg.h new file mode 100644 index 00000000..26d4d1f1 --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/LoginDlg.h @@ -0,0 +1,50 @@ +#if !defined(AFX_LOGINDLG_H__58A8C6B5_2AD3_4C62_882E_DC1F8D104CC5__INCLUDED_) +#define AFX_LOGINDLG_H__58A8C6B5_2AD3_4C62_882E_DC1F8D104CC5__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// LoginDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + +class CLoginDlg : public CDialog +{ +// Construction +public: + int m_profile; + CLoginDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CLoginDlg) + enum { IDD = IDD_LOGIN }; + CString m_email; + CString m_nick; + CString m_password; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CLoginDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CLoginDlg) + virtual void OnOK(); + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_LOGINDLG_H__58A8C6B5_2AD3_4C62_882E_DC1F8D104CC5__INCLUDED_) diff --git a/code/gamespy/gstats/ladderTrack/StdAfx.cpp b/code/gamespy/gstats/ladderTrack/StdAfx.cpp new file mode 100644 index 00000000..96d55a4b --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// ladderTrack.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/code/gamespy/gstats/ladderTrack/StdAfx.h b/code/gamespy/gstats/ladderTrack/StdAfx.h new file mode 100644 index 00000000..83d5a02b --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/StdAfx.h @@ -0,0 +1,32 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__207466EE_5E75_4F57_8FC6_639F9C33B505__INCLUDED_) +#define AFX_STDAFX_H__207466EE_5E75_4F57_8FC6_639F9C33B505__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +#include "../gstats.h" +#include "../../gp/gp.h" +#include "../../ghttp/ghttp.h" +//#include "../../gt/gt.h" +//#include "../../gt/gtEncode.h" + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__207466EE_5E75_4F57_8FC6_639F9C33B505__INCLUDED_) diff --git a/code/gamespy/gstats/ladderTrack/WaitingDlg.cpp b/code/gamespy/gstats/ladderTrack/WaitingDlg.cpp new file mode 100644 index 00000000..60eeffc1 --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/WaitingDlg.cpp @@ -0,0 +1,43 @@ +// WaitingDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "ladderTrack.h" +#include "WaitingDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CWaitingDlg dialog + + +CWaitingDlg::CWaitingDlg(CWnd* pParent /*=NULL*/) + : CDialog(CWaitingDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CWaitingDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void CWaitingDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CWaitingDlg) + // NOTE: the ClassWizard will add DDX and DDV calls here + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CWaitingDlg, CDialog) + //{{AFX_MSG_MAP(CWaitingDlg) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CWaitingDlg message handlers diff --git a/code/gamespy/gstats/ladderTrack/WaitingDlg.h b/code/gamespy/gstats/ladderTrack/WaitingDlg.h new file mode 100644 index 00000000..e5d262bd --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/WaitingDlg.h @@ -0,0 +1,46 @@ +#if !defined(AFX_WAITINGDLG_H__874B6CC6_8058_4BDF_B714_8FE3610A12B4__INCLUDED_) +#define AFX_WAITINGDLG_H__874B6CC6_8058_4BDF_B714_8FE3610A12B4__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// WaitingDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CWaitingDlg dialog + +class CWaitingDlg : public CDialog +{ +// Construction +public: + CWaitingDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CWaitingDlg) + enum { IDD = IDD_WAITING }; + // NOTE: the ClassWizard will add data members here + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CWaitingDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CWaitingDlg) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_WAITINGDLG_H__874B6CC6_8058_4BDF_B714_8FE3610A12B4__INCLUDED_) diff --git a/code/gamespy/gstats/ladderTrack/ladderTrack.cpp b/code/gamespy/gstats/ladderTrack/ladderTrack.cpp new file mode 100644 index 00000000..a2542dd0 --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/ladderTrack.cpp @@ -0,0 +1,63 @@ +// ladderTrack.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "ladderTrack.h" +#include "ladderTrackDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackApp + +BEGIN_MESSAGE_MAP(CLadderTrackApp, CWinApp) + //{{AFX_MSG_MAP(CLadderTrackApp) + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackApp construction + +CLadderTrackApp::CLadderTrackApp() +{ +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CLadderTrackApp object + +CLadderTrackApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackApp initialization + +BOOL CLadderTrackApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization +/* +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif +*/ + CLadderTrackDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + } + else if (nResponse == IDCANCEL) + { + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/code/gamespy/gstats/ladderTrack/ladderTrack.h b/code/gamespy/gstats/ladderTrack/ladderTrack.h new file mode 100644 index 00000000..04348d51 --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/ladderTrack.h @@ -0,0 +1,47 @@ +// ladderTrack.h : main header file for the LADDERTRACK application +// + +#if !defined(AFX_LADDERTRACK_H__E9856F44_580A_48C0_ABFF_6FFA9BA944A3__INCLUDED_) +#define AFX_LADDERTRACK_H__E9856F44_580A_48C0_ABFF_6FFA9BA944A3__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackApp: +// See ladderTrack.cpp for the implementation of this class +// + +class CLadderTrackApp : public CWinApp +{ +public: + CLadderTrackApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CLadderTrackApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CLadderTrackApp) + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_LADDERTRACK_H__E9856F44_580A_48C0_ABFF_6FFA9BA944A3__INCLUDED_) diff --git a/code/gamespy/gstats/ladderTrack/ladderTrack.rc b/code/gamespy/gstats/ladderTrack/ladderTrack.rc new file mode 100644 index 00000000..6592521e --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/ladderTrack.rc @@ -0,0 +1,241 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\ladderTrack.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\ladderTrack.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_LADDERTRACK_DIALOG DIALOGEX 0, 0, 212, 100 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "ladderTrack" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Exit",IDOK,155,5,50,14 + PUSHBUTTON "Logout",IDC_LOGOUT,155,21,50,14 + PUSHBUTTON "Start Race",IDC_START_RACE,10,15,50,14 + LTEXT "Info",IDC_INFO,70,15,80,8 + CONTROL "Progress1",IDC_LOCAL_PROGRESS,"msctls_progress32", + PBS_SMOOTH | WS_BORDER,7,48,80,14 + CONTROL "Progress1",IDC_REMOTE_PROGRESS,"msctls_progress32", + PBS_SMOOTH | WS_BORDER,113,48,80,14 + LTEXT "You:",IDC_STATIC,7,38,16,8 + LTEXT "Opponent:",IDC_STATIC,113,38,34,8 + LTEXT "Use Z && X to race",IDC_STATIC,70,25,57,8 + LTEXT "Ladder Position:",IDC_STATIC,7,68,52,8 + LTEXT "Ladder Position:",IDC_STATIC,113,68,52,8 + LTEXT "123",IDC_LOCAL_POSITION,63,68,25,8 + LTEXT "123",IDC_REMOTE_POSITION,167,68,35,8 + PUSHBUTTON "Update Positions",IDC_UPDATE_POSITIONS,73,79,65,14 +END + +IDD_HOST_OR_JOIN DIALOG DISCARDABLE 0, 0, 165, 50 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Host or Join?" +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "Host",IDC_HOST,"Button",BS_AUTORADIOBUTTON | WS_GROUP,7, + 7,31,10 + CONTROL "Join",IDC_JOIN,"Button",BS_AUTORADIOBUTTON,7,31,29,10 + EDITTEXT IDC_JOIN_ADDRESS,52,28,106,14,ES_AUTOHSCROLL | WS_GROUP + DEFPUSHBUTTON "OK",IDOK,52,7,50,14,WS_GROUP + PUSHBUTTON "Cancel",IDCANCEL,108,7,50,14 +END + +IDD_WAITING DIALOG DISCARDABLE 0, 0, 64, 45 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "ladderTrack" +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "Cancel",IDCANCEL,7,24,50,14 + LTEXT "Please Wait...",IDC_STATIC,9,7,45,8 +END + +IDD_LOGIN DIALOG DISCARDABLE 0, 0, 147, 76 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "GameSpy Login" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_EMAIL,51,8,84,12,ES_AUTOHSCROLL + EDITTEXT IDC_NICK,51,23,84,12,ES_AUTOHSCROLL + EDITTEXT IDC_PASSWORD,51,38,84,12,ES_PASSWORD | ES_AUTOHSCROLL + DEFPUSHBUTTON "Login",IDOK,15,55,50,14 + PUSHBUTTON "Cancel",IDCANCEL,80,55,50,14 + LTEXT "email:",IDC_STATIC,10,10,19,8 + LTEXT "nick:",IDC_STATIC,10,25,16,8 + LTEXT "password:",IDC_STATIC,10,40,33,8 +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "ladderTrack MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "ladderTrack\0" + VALUE "LegalCopyright", "Copyright (C) 2001\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "ladderTrack.EXE\0" + VALUE "ProductName", "ladderTrack Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_LADDERTRACK_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 205 + TOPMARGIN, 7 + BOTTOMMARGIN, 93 + END + + IDD_HOST_OR_JOIN, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 158 + TOPMARGIN, 7 + BOTTOMMARGIN, 43 + END + + IDD_WAITING, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 57 + TOPMARGIN, 7 + BOTTOMMARGIN, 38 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\ladderTrack.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/gamespy/gstats/ladderTrack/ladderTrackDlg.cpp b/code/gamespy/gstats/ladderTrack/ladderTrackDlg.cpp new file mode 100644 index 00000000..1ce4f1e1 --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/ladderTrackDlg.cpp @@ -0,0 +1,989 @@ +// ladderTrackDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "ladderTrack.h" +#include "ladderTrackDlg.h" +#include "waitingDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackDlg dialog + +CLadderTrackDlg::CLadderTrackDlg(CWnd* pParent /*=NULL*/) + : CDialog(CLadderTrackDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CLadderTrackDlg) + m_localPosition = _T(""); + m_remotePosition = _T(""); + m_info = _T("Ready"); + //}}AFX_DATA_INIT + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); +} + +void CLadderTrackDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CLadderTrackDlg) + DDX_Control(pDX, IDC_START_RACE, m_startRace); + DDX_Control(pDX, IDC_REMOTE_PROGRESS, m_remoteProgress); + DDX_Control(pDX, IDC_LOCAL_PROGRESS, m_localProgress); + DDX_Text(pDX, IDC_LOCAL_POSITION, m_localPosition); + DDX_Text(pDX, IDC_REMOTE_POSITION, m_remotePosition); + DDX_Text(pDX, IDC_INFO, m_info); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CLadderTrackDlg, CDialog) + //{{AFX_MSG_MAP(CLadderTrackDlg) + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_BN_CLICKED(IDC_START_RACE, OnStart) + ON_WM_DESTROY() + ON_WM_TIMER() + ON_BN_CLICKED(IDC_LOGOUT, OnLogout) + ON_BN_CLICKED(IDC_UPDATE_POSITIONS, OnUpdatePositions) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackDlg message handlers + +#define MSG_CONNECT "is" // client-pid, nick +#define MSG_CHALLENGE "is" // host-pid, challenge +#define MSG_CHALLENGE_TYPE 1 +#define MSG_RESPONSE "s" // response +#define MSG_RESPONSE_TYPE 2 +#define MSG_COUNTDOWN "i" // count +#define MSG_COUNTDOWN_TYPE 3 +#define MSG_START_RACE "" +#define MSG_START_RACE_TYPE 5 +#define MSG_PROGRESS "i" // progress +#define MSG_PROGRESS_TYPE 6 +#define MSG_END_RACE "i" // time +#define MSG_END_RACE_TYPE 7 +#define MSG_CHAT "s" // message +#define MSG_CHAT_TYPE 8 + +//#define HOST_PORT 38466 +#define HOST_PORT_STRING ":38466" +#define CLIENT_PORT_STRING ":38467" // so you can run both +#define COUNTDOWN_START 5 + +#define TIMER_THINK 100 +#define TIMER_COUNTDOWN 101 + +CLadderTrackDlg * Dlg; + +void ConnectedCallback +( + GT2Connection connection, + GT2Result result, + GT2Byte * message, + int len +) +{ + if(result == GT2Success) + Dlg->m_state = JOIN_WAITING; + else + { + Dlg->m_state = JOIN_ERROR; + Dlg->m_GT2Connection = NULL; + } +} + +void ReceivedCallback +( + GT2Connection connection, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + if(!message || !len) + return; + + GTMessageType type; + + type = gtEncodedMessageType((char*)message); + + if(type == MSG_CHALLENGE_TYPE) + { + ASSERT(!Dlg->m_hosting && (Dlg->m_state == JOIN_WAITING)); + + int pid; + char challenge[64]; + char response[33]; + if(gtDecode(MSG_CHALLENGE, (char*)message, len, &pid, challenge) == -1) + { + Dlg->m_state = JOIN_ERROR; + return; + } + + GenerateAuth(challenge, (char *)(LPCSTR)Dlg->m_loginDlg.m_password, response); + + // Send the response. + ///////////////////// + char message[64]; + int rcode; + rcode = gtEncode(MSG_RESPONSE_TYPE, MSG_RESPONSE, message, sizeof(message), response); + ASSERT(rcode != -1); + gt2Send(connection, (const GT2Byte *)message, rcode, GT2True); + + Dlg->m_remoteProfile = pid; + Dlg->m_state = JOIN_CONNECTED; + } + else if(type == MSG_RESPONSE_TYPE) + { + ASSERT(Dlg->m_hosting && (Dlg->m_state == HOST_CHALLENGING)); + + char response[33]; + if(gtDecode(MSG_RESPONSE, (char*)message, len, response) == -1) + { + Dlg->m_state = HOST_ERROR; + return; + } + Dlg->m_remoteResponse = response; + Dlg->m_state = HOST_CONNECTED; + } + else if(type == MSG_COUNTDOWN_TYPE) + { + ASSERT(!Dlg->m_hosting); + + if(gtDecode(MSG_COUNTDOWN, (char*)message, len, &Dlg->m_countdown) == -1) + { + Dlg->m_state = JOIN_ERROR; + return; + } + + Dlg->Countdown(); + } + else if(type == MSG_START_RACE_TYPE) + { + ASSERT(!Dlg->m_hosting); + + Dlg->StartRace(); + } + else if(type == MSG_PROGRESS_TYPE) + { + if(Dlg->m_racing) + { + int progress; + if(gtDecode(MSG_PROGRESS, (char*)message, len, &progress) != -1) + Dlg->m_remoteProgress.SetPos(progress); + } + } + else if(type == MSG_END_RACE_TYPE) + { + if(Dlg->m_racing) + { + gtDecode(MSG_END_RACE, (char*)message, len, &Dlg->m_remoteTime); + } + } +} + +void ClosedCallback +( + GT2Connection connection, + GT2CloseReason reason +) +{ + // Logout triggers this function when it's a local close + // so we need to make sure we don't loop + if (reason != GT2LocalClose) + { + Dlg->MessageBox("Connection closed"); + Dlg->Logout(); + } + else + Dlg->m_GT2Connection = NULL; +} + +void ConnectAttemptCallback +( + GT2Socket listener, + GT2Connection connection, + unsigned int ip, + unsigned short port, + int latency, + GT2Byte * message, + int len +) +{ + int pid = 0; + char nick[128]; + + // Only allow one connection. + ///////////////////////////// + if(Dlg->m_GT2Connection) + { + gt2Reject(connection, NULL, 0); + return; + } + + // Decode the pid. + ////////////////// + if(message && len) + { + if(gtDecodeNoType(MSG_CONNECT, (char*)message, len, &pid, nick) == -1) + pid = 0; + } + + // If we didn't/couldn't get the pid, reject the attempt. + ///////////////////////////////////////////////////////// + if(!pid) + { + gt2Reject(connection, NULL, 0); + return; + } + + // Accept the connection. + ///////////////////////// + GT2ConnectionCallbacks callbacks; + memset(&callbacks, 0, sizeof(GT2ConnectionCallbacks)); + callbacks.received = ReceivedCallback; + callbacks.closed = ClosedCallback; + if(!gt2Accept(connection, &callbacks)) + return; + + // Set some states. + /////////////////// + Dlg->m_remoteProfile = pid; + Dlg->m_GT2Connection = connection; + Dlg->m_state = HOST_CHALLENGING; + Dlg->m_challenged = FALSE; + Dlg->m_remoteNick = nick; +} + +/*void StoppedCallback +( + GT2Socket listener, + GTStopReason reason +) +{ + if(reason != GTStopped) + { + Dlg->MessageBox("Listener stopped"); + Dlg->Logout(); + } +}*/ + +void SocketErrorCallback +( + GT2Socket socket +) +{ + Dlg->MessageBox("Socket Error!"); + Dlg->Logout(); +} + + +BOOL CLadderTrackDlg::SetupHosting() +{ + int rcode; + CString str; + + if(!IsStatsConnected()) + { + // Use the development system. + ////////////////////////////// + strcpy(StatsServerHostname, "sdkdev.gamespy.com"); + + // Set the gamename and secret key. + /////////////////////////////////// + strcpy(gcd_gamename, "st_ladder"); + gcd_secret_key[0] = 'K'; + gcd_secret_key[1] = 'w'; + gcd_secret_key[2] = 'F'; + gcd_secret_key[3] = 'J'; + gcd_secret_key[4] = '2'; + gcd_secret_key[5] = 'X'; + + // Init the connection to the backend. + ////////////////////////////////////// + rcode = InitStatsConnection(0); + if(rcode != GE_NOERROR) + { + str.Format("Failed to connect to the stats server (%d).", rcode); + MessageBox(str); + PostQuitMessage(1); + return TRUE; + } + + // Get the challenge. + ///////////////////// + m_challenge = GetChallenge(NULL); + + //FakeStats(); + } + + /* + // Setup the listener's callbacks. + ////////////////////////////////// + GTListenerCallbacks callbacks; + memset(&callbacks, 0, sizeof(GTListenerCallbacks)); + callbacks.connectAttempt = ConnectAttemptCallback; + callbacks.stopped = StoppedCallback; + + // Create the listener. + /////////////////////// + + m_listener = gtListen(gtAddressToString(0, HOST_PORT, NULL), &callbacks); + if(!m_listener) + return FALSE;*/ + + GT2ConnectionCallbacks connectionCallbacks; + memset(&connectionCallbacks, 0, sizeof(GT2ConnectionCallbacks)); + connectionCallbacks.received = ReceivedCallback; + connectionCallbacks.closed = ClosedCallback; + + GT2Result aResult = gt2CreateSocket(&m_GT2Socket, HOST_PORT_STRING, 0, 0, SocketErrorCallback); + if (GT2Success != aResult) + return FALSE; + + gt2Listen(m_GT2Socket, ConnectAttemptCallback); + m_state = HOST_LISTENING; + + // Bring up the "waiting" dialog. + ///////////////////////////////// + rcode = m_waitingDlg.DoModal(); + + // If it was cancelled, try again. + ////////////////////////////////// + if(rcode != IDOK) + Logout(); + + return TRUE; +} + +BOOL CLadderTrackDlg::SetupJoining() +{ + int rcode; + + // Setup the address to connect to. + /////////////////////////////////// + CString remoteAddress; + remoteAddress.Format("%s%s", m_hostOrJoinDlg.m_joinAddress, HOST_PORT_STRING); + + // Encode the profile id. + ///////////////////////// + char buffer[64]; + rcode = gtEncodeNoType(MSG_CONNECT, buffer, sizeof(buffer), m_loginDlg.m_profile, m_loginDlg.m_nick); + ASSERT(rcode != -1); + + // Setup the callbacks. + /////////////////////// + GT2ConnectionCallbacks callbacks; + memset(&callbacks, 0, sizeof(GT2ConnectionCallbacks)); + callbacks.connected = ConnectedCallback; + callbacks.received = ReceivedCallback; + callbacks.closed = ClosedCallback; + + // Create the socket + GT2Result aResult = gt2CreateSocket(&m_GT2Socket, CLIENT_PORT_STRING, 0, 0, SocketErrorCallback); + if (aResult != GT2Success) + { + MessageBox("Failed to create socket!"); + return FALSE; + } + + // Connect. + /////////// + m_state = JOIN_CONNECTING; + GT2Result result; + result = gt2Connect(m_GT2Socket, &m_GT2Connection, remoteAddress, (const GT2Byte *)buffer, sizeof(buffer), -1, &callbacks, GT2False); + if(!m_GT2Connection) + return FALSE; + + // Bring up the "waiting" dialog. + ///////////////////////////////// + rcode = m_waitingDlg.DoModal(); + + // If it was cancelled, try again. + ////////////////////////////////// + if(rcode != IDOK) + Logout(); + + return TRUE; +} + +GHTTPBool PlayerPositionPageCompleted +( + GHTTPRequest request, + GHTTPResult result, + char * buffer, + __int64 bufferLen, + void * param +) +{ + if(result == GHTTPSuccess) + { + BOOL localPlayer; + int position; + + Dlg->UpdateData(); + + localPlayer = (BOOL)param; + position = atoi(buffer); + + if(localPlayer) + Dlg->m_localPosition.Format("%d", position); + else + Dlg->m_remotePosition.Format("%d", position); + + Dlg->UpdateData(FALSE); + } + + return GHTTPTrue; +} + +void CLadderTrackDlg::UpdatePlayerPositions() +{ + CString url; + url.Format("http://sdkdev.gamespy.com/games/st_ladder/web/playerposition.asp?pid=%d", m_loginDlg.m_profile); + ghttpGet(url, GHTTPFalse, PlayerPositionPageCompleted, (void *)TRUE); + url.Format("http://sdkdev.gamespy.com/games/st_ladder/web/playerposition.asp?pid=%d", m_remoteProfile); + ghttpGet(url, GHTTPFalse, PlayerPositionPageCompleted, (void *)FALSE); +} + +BOOL CLadderTrackDlg::SetupMatch() +{ + int rcode; + BOOL result; + + m_state = SETTING_UP; + + ASSERT(!m_GT2Connection); + ASSERT(!m_GT2Socket); + + m_state = 0; + m_remoteResponse.Empty(); + m_remoteProfile = 0; + m_GT2Connection = NULL; + m_startRace.EnableWindow(FALSE); + m_start = 0; + m_countdown = 0; + m_racing = FALSE; + m_numSteps = 0; + m_step = NONE; + + do + { + // Login the user (actually just verifying the login). + ////////////////////////////////////////////////////// + rcode = m_loginDlg.DoModal(); + if(rcode != IDOK) + { + PostQuitMessage(1); + return FALSE; + } + + // See if they want to host or join. + //////////////////////////////////// + rcode = m_hostOrJoinDlg.DoModal(); + } + while(rcode != IDOK); + + m_GT2Connection = NULL; + m_hosting = (m_hostOrJoinDlg.m_hostOrJoin == HOSTORJOIN_HOST); + + CString str; + str.Format("ladderTrack%s", m_hosting?" (hosting)":""); + SetWindowText(str); + + if(m_hosting) + result = SetupHosting(); + else + result = SetupJoining(); + + if(result && m_hosting) + { + m_startRace.EnableWindow(); + } + + UpdatePlayerPositions(); + + return result; +} + +BOOL CLadderTrackDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + SetIcon(m_hIcon, TRUE); + SetIcon(m_hIcon, FALSE); + + // Basic initialization. + //////////////////////// + Dlg = this; + m_GT2Connection = NULL; + m_GT2Socket = NULL; + m_state = LOGGED_OUT; + m_countdown = 0; + m_racing = FALSE; + + // Init gt. + /////////// + //gtStartup(); + + // Set a think timer. + ///////////////////// + SetTimer(TIMER_THINK, 50, NULL); + + return TRUE; +} + +void CLadderTrackDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CDialog::OnPaint(); + } +} + +HCURSOR CLadderTrackDlg::OnQueryDragIcon() +{ + return (HCURSOR) m_hIcon; +} + +void CLadderTrackDlg::OnLogout() +{ + // Clean stuff up. + ////////////////// + if(m_GT2Connection) + { + gt2CloseConnection(m_GT2Connection); + m_GT2Connection = NULL; + } + if(m_GT2Socket) + { + gt2CloseSocket(m_GT2Socket); + m_GT2Socket = NULL; + } + if(m_waitingDlg.m_hWnd && m_waitingDlg.IsWindowEnabled()) + { + m_waitingDlg.EndDialog(IDCANCEL); + } + + m_state = LOGGED_OUT; +} + +void CLadderTrackDlg::OnDestroy() +{ + CDialog::OnDestroy(); + + if(IsStatsConnected()) + CloseStatsConnection(); + + ghttpCleanup(); + + //gt2Cleanup(GTFalse); + if (m_GT2Connection) + { + gt2CloseConnection(m_GT2Connection); + m_GT2Connection = NULL; + } + if (m_GT2Socket) + { + gt2CloseSocket(m_GT2Socket); + m_GT2Socket = NULL; + } +} + +void CLadderTrackDlg::OnTimer(UINT nIDEvent) +{ + char buffer[64]; + int rcode; + + if(nIDEvent == TIMER_THINK) + { + static BOOL thinking; + + if(!thinking) + { + thinking = TRUE; + + if (m_GT2Socket) + gt2Think(m_GT2Socket); + ghttpThink(); + + if(m_state == HOST_CHALLENGING) + { + if(!m_challenged) + { + // Send the challenge string and our profile. + ///////////////////////////////////////////// + rcode = gtEncode(MSG_CHALLENGE_TYPE, MSG_CHALLENGE, buffer, sizeof(buffer), m_loginDlg.m_profile, (LPCSTR)m_challenge); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)buffer, rcode, GT2True); + m_challenged = TRUE; + } + } + else if(m_state == HOST_CONNECTED) + { + m_waitingDlg.EndDialog(IDOK); + m_state = RACING; + } + else if(m_state == HOST_ERROR) + { + MessageBox("Error setting up hosting"); + m_waitingDlg.EndDialog(IDCANCEL); + } + else if(m_state == JOIN_CONNECTED) + { + if(m_waitingDlg.m_hWnd && m_waitingDlg.IsWindowEnabled()) + m_waitingDlg.EndDialog(IDOK); + } + else if(m_state == JOIN_ERROR) + { + MessageBox("Error joining a game"); + m_waitingDlg.EndDialog(IDCANCEL); + } + + thinking = FALSE; + } + + if(m_state == LOGGED_OUT) + { + if(!Dlg->SetupMatch()) + { + MessageBox("Error setting up the match"); + Logout(); + } + } + + // Are we racing? + ///////////////// + if(m_racing) + { + // Did we finish? + ///////////////// + if(m_localTime) + { + // Did we both finish? + ////////////////////// + if(m_remoteTime) + { + // Done racing. + /////////////// + m_racing = FALSE; + + m_info = "Race Complete"; + UpdateData(FALSE); + + // Report the stats. + //////////////////// + if(m_hosting) + ReportStats(); + + // Show the times. + ////////////////// + CString message; + if(m_localTime < m_remoteTime) + message.Format("You won!\n%0.3fs to %0.3fs", m_localTime / 1000.0, m_remoteTime / 1000.0); + else if(m_remoteTime < m_localTime) + message.Format("You lost!\n%0.3fs to %0.3fs", m_localTime / 1000.0, m_remoteTime / 1000.0); + else + message.Format("You tied!\n%0.3fs", m_localTime / 1000.0); + MessageBox(message); + + m_localProgress.SetPos(0); + m_remoteProgress.SetPos(0); + } + } + else + { + // Let our opponent know how far we are. + //////////////////////////////////////// + rcode = gtEncode(MSG_PROGRESS_TYPE, MSG_PROGRESS, buffer, sizeof(buffer), m_numSteps); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)buffer, rcode, GT2False); + } + } + } + else if(nIDEvent == TIMER_COUNTDOWN) + { + m_countdown--; + if(m_countdown <= 0) + KillTimer(TIMER_COUNTDOWN); + + if(m_countdown < 0) + return; + + Countdown(); + if(!m_countdown) + StartRace(); + } + + CDialog::OnTimer(nIDEvent); +} + +void CLadderTrackDlg::Logout() +{ + OnLogout(); +} + +void CLadderTrackDlg::Countdown() +{ + if(m_hosting) + { + int rcode; + char message[32]; + rcode = gtEncode(MSG_COUNTDOWN_TYPE, MSG_COUNTDOWN, message, sizeof(message), m_countdown); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)message, rcode, GT2True); + } + + if(m_countdown) + { + UpdateData(); + + m_info.Format("Race starts in %ds", m_countdown); + + UpdateData(FALSE); + } +} + +void CLadderTrackDlg::OnStart() +{ + // Start the countdown. + /////////////////////// + m_countdown = COUNTDOWN_START; + SetTimer(TIMER_COUNTDOWN, 1000, NULL); + Countdown(); +} + +BOOL CLadderTrackDlg::PreTranslateMessage(MSG* pMsg) +{ + if(pMsg->message == WM_KEYDOWN) + { + int nChar = pMsg->wParam; + if((nChar == 'Z') || (nChar == 'X')) + { + if((pMsg->lParam & 0xFFFF) == 1) + { + CString str; + BOOL stepped = FALSE; + + if((nChar == 'Z') && (m_step != LEFT)) + { + m_step = LEFT; + m_numSteps++; + stepped = TRUE; + } + else if ((nChar == 'X') && (m_step != RIGHT)) + { + m_step = RIGHT; + m_numSteps++; + stepped = TRUE; + } + + if(stepped && m_racing) + { + m_localProgress.SetPos(m_numSteps); + if(m_numSteps == m_totalSteps) + { + m_localTime = (GetTickCount() - m_start); + str.Format("%0.3fs\n", m_localTime / 1000.0); + OutputDebugString(str); + //MessageBox(str); + + if(!m_remoteTime) + { + UpdateData(); + m_info = "Waiting for opponent"; + UpdateData(FALSE); + } + + // Let them know we finished. + ///////////////////////////// + char buffer[32]; + int rcode; + rcode = gtEncode(MSG_END_RACE_TYPE, MSG_END_RACE, buffer, sizeof(buffer), m_localTime); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)buffer, rcode, GT2True); + } + } + } + + return TRUE; + } + } + + return CDialog::PreTranslateMessage(pMsg); +} + +void CLadderTrackDlg::StartRace() +{ + if(m_hosting) + { + int rcode; + char buffer[32]; + rcode = gtEncode(MSG_START_RACE_TYPE, MSG_START_RACE, buffer, sizeof(buffer)); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)buffer, rcode, GT2True); + } + + m_localTime = 0; + m_remoteTime = 0; + m_racing = TRUE; + m_numSteps = 0; + m_step = NONE; + m_racing = TRUE; + m_start = GetTickCount(); + m_totalSteps = RACE_STEPS; + m_localProgress.SetRange(0, m_totalSteps); + m_localProgress.SetPos(0); + m_remoteProgress.SetRange(0, m_totalSteps); + m_remoteProgress.SetPos(0); + + UpdateData(); + m_info.Format("GO!"); + UpdateData(FALSE); +} + +void CLadderTrackDlg::ReportStats() +{ + char response[33]; + + // Game. + //////// + NewGame(1); + BucketStringOp(NULL, "hostname", bo_set, (char *)(LPCSTR)m_loginDlg.m_nick, bl_server, 0); + + // Local player (host). + /////////////////////// + NewPlayer(NULL, 0, (char *)(LPCSTR)m_loginDlg.m_nick); + BucketIntOp(NULL, "time", bo_set, m_localTime, bl_player, 0); + BucketIntOp(NULL, "pid", bo_set, m_loginDlg.m_profile, bl_player, 0); + GenerateAuth((char *)(LPCSTR)m_challenge, (char *)(LPCSTR)m_loginDlg.m_password, response); + BucketStringOp(NULL, "auth", bo_set, response, bl_player, 0); + + // Remote player (joined). + ////////////////////////// + NewPlayer(NULL, 1, (char *)(LPCSTR)m_remoteNick); + BucketIntOp(NULL, "time", bo_set, m_remoteTime, bl_player, 1); + BucketIntOp(NULL, "pid", bo_set, m_remoteProfile, bl_player, 1); + BucketStringOp(NULL, "auth", bo_set, (char *)(LPCSTR)m_remoteResponse, bl_player, 1); + + SendGameSnapShot(NULL, NULL, SNAP_FINAL); + FreeGame(NULL); +} + +struct FakeProfile +{ + int pid; + char password[32]; + char nick[32]; + DWORD minTime; + DWORD maxTime; +}; + +FakeProfile FakeProfiles[] = +{ + { 4933555, "mrpants", "ABCDEF", 4500, 6000}, + { 4903509, "mrpants", "BigFatty", 5500, 7000}, + { 4933502, "mrpants", "Colonel Op", 5500, 7000}, + { 4933634, "mrpants", "GHIJKL", 4500, 7000}, + { 3017545, "mrpants", "test1", 4500, 8000}, + { 3017574, "mrpants", "test2", 4500, 6000}, + { 3360407, "mrpants", "test3", 6500, 7000}, + { 4933752, "mrpants", "tester", 5500, 7000}, + { 2840651, "mrpants", "a,b,c", 6500, 8000}, + { 3410517, "mrpants", "abcabc", 4500, 6000}, + { 3652959, "mrpants", "bane", 6500, 8000}, + { 3654054, "mrpants", "cccccc", 6500, 7000}, + { 8696884, "mrpants", "Jimmy Page", 5500, 6000}, + { 100001, "mrpants", "Mr. Pants", 5500, 8000}, + { 2881317, "mrpants", "mrpants", 4500, 7000}, + { 3651362, "mrpants", "mrpantsz", 6500, 7000}, + { 3671851, "mrpants", "mrwasabi", 5500, 6000}, + { 3652930, "mrpants", "pants", 6500, 8000}, + { 2833041, "mrpants", "pants3", 6500, 7000}, + { 2833042, "mrpants", "pants4", 5500, 7000}, + { 2833043, "mrpants", "pants5", 6500, 7000}, + { 2833045, "mrpants", "pants6", 4500, 7000}, + { 2833048, "mrpants", "pants7", 6500, 7000}, + { 2833049, "mrpants", "pants8", 4500, 6000}, + { 2833050, "mrpants", "pants9", 6500, 8000}, + { 2833074, "mrpants", "pants16", 7000, 8000}, + { 2833075, "mrpants", "pants17", 4500, 8000}, + { 2833076, "mrpants", "pants18", 6500, 7000}, + { 2833077, "mrpants", "pants19", 5500, 7000}, + { 2833079, "mrpants", "pants20", 6500, 7000}, + { 2833080, "mrpants", "pants21", 6500, 8000}, + { 2833081, "mrpants", "pants22", 4500, 6000}, + { 3654074, "mrpants", "sdfrrr", 4500, 6000}, + { 3733688, "mrpants", "Skeletor", 4500, 7000}, + { 2977286, "mrpants", "testtesttest", 6500, 8000}, + { 3654019, "mrpants", "tttttest", 4500, 6000} +}; + +void CLadderTrackDlg::FakeStats() +{ + int num = (sizeof(FakeProfiles) / sizeof(FakeProfile)); + int total = 1000; + int p1; + int p2; + int i; + char response[33]; + CString msg; + + srand(time(NULL)); + + for(i = 0 ; i < total ; i++) + { + p1 = (rand() % num); + p2 = (rand() % num); + + if(p1 == p2) + continue; + + m_loginDlg.m_nick = FakeProfiles[p1].nick; + m_loginDlg.m_profile = FakeProfiles[p1].pid; + m_loginDlg.m_password = FakeProfiles[p1].password; + m_localTime = (DWORD)(FakeProfiles[p1].minTime + ((rand() / (float)RAND_MAX) * (FakeProfiles[p1].maxTime - FakeProfiles[p1].minTime))); + + m_remoteNick = FakeProfiles[p2].nick; + m_remoteProfile = FakeProfiles[p2].pid; + GenerateAuth((char *)(LPCSTR)m_challenge, FakeProfiles[p2].password, response); + m_remoteResponse = response; + m_remoteTime = (DWORD)(FakeProfiles[p2].minTime + ((rand() / (float)RAND_MAX) * (FakeProfiles[p2].maxTime - FakeProfiles[p2].minTime))); + + ReportStats(); + + msg.Format("game %d reported\n", i); + OutputDebugString(msg); + + Sleep(1200); + } + + exit(1); +} + +void CLadderTrackDlg::OnUpdatePositions() +{ + UpdatePlayerPositions(); +} diff --git a/code/gamespy/gstats/ladderTrack/ladderTrackDlg.h b/code/gamespy/gstats/ladderTrack/ladderTrackDlg.h new file mode 100644 index 00000000..0929139e --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/ladderTrackDlg.h @@ -0,0 +1,117 @@ +// ladderTrackDlg.h : header file +// + +#if !defined(AFX_LADDERTRACKDLG_H__82121F55_1FDB_427A_B616_B59EB16C5E6D__INCLUDED_) +#define AFX_LADDERTRACKDLG_H__82121F55_1FDB_427A_B616_B59EB16C5E6D__INCLUDED_ + +#include "LoginDlg.h" +#include "HostOrJoinDlg.h" +#include "..\..\GT2\gt2.h" // Added by ClassView +#include "..\..\GT2\gt2Encode.h" +#include "WaitingDlg.h" // Added by ClassView + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackDlg dialog + +#define LOGGED_OUT 1 +#define SETTING_UP 2 +#define RACING 3 + +#define HOST_LISTENING 11 +#define HOST_CHALLENGING 12 +#define HOST_CONNECTED 13 +#define HOST_ERROR 14 + +#define JOIN_CONNECTING 21 +#define JOIN_WAITING 22 +#define JOIN_CONNECTED 23 +#define JOIN_ERROR 24 + +#define NONE -1 +#define LEFT 0 +#define RIGHT 1 + +#define RACE_STEPS 60 + +class CLadderTrackDlg : public CDialog +{ +// Construction +public: + void FakeStats(); + CString m_remoteNick; + DWORD m_remoteTime; + DWORD m_localTime; + int m_totalSteps; + int m_step; + int m_numSteps; + DWORD m_start; + BOOL m_racing; + int m_countdown; + BOOL m_challenged; + CString m_remoteResponse; + int m_state; + BOOL m_hosting; + CString m_challenge; + int m_remoteProfile; + CLoginDlg m_loginDlg; + CHostOrJoinDlg m_hostOrJoinDlg; + CWaitingDlg m_waitingDlg; + + GT2Socket m_GT2Socket; // raw socket + GT2Connection m_GT2Connection; // established connection + + BOOL SetupHosting(); + BOOL SetupJoining(); + BOOL SetupMatch(); + void UpdatePlayerPositions(); + void Countdown(); + void Logout(); + void StartRace(); + void ReportStats(); + CLadderTrackDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CLadderTrackDlg) + enum { IDD = IDD_LADDERTRACK_DIALOG }; + CButton m_startRace; + CProgressCtrl m_remoteProgress; + CProgressCtrl m_localProgress; + CString m_localPosition; + CString m_remotePosition; + CString m_info; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CLadderTrackDlg) + public: + virtual BOOL PreTranslateMessage(MSG* pMsg); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + HICON m_hIcon; + + // Generated message map functions + //{{AFX_MSG(CLadderTrackDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + afx_msg void OnStart(); + afx_msg void OnDestroy(); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnLogout(); + afx_msg void OnUpdatePositions(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_LADDERTRACKDLG_H__82121F55_1FDB_427A_B616_B59EB16C5E6D__INCLUDED_) diff --git a/code/gamespy/gstats/ladderTrack/res/ladderTrack.ico b/code/gamespy/gstats/ladderTrack/res/ladderTrack.ico new file mode 100644 index 0000000000000000000000000000000000000000..7eef0bcbe6580a6f464d688906172c2d9de44262 GIT binary patch literal 1078 zcmc&zF>b>!3}jLb9s)T}@Kod(893@u8ajANzT`op9^o+)S?=nU(FD@%0s)Sg^oyC8{H z9myetc;MEP)59v(LMa~xK8Yu^jIR*H22uCFiq5%C{s7(PJi>o15i^bmX4(vPxWAio z9ryY#AU_jfnd047-@`)XzL?%iS$gQyFP{44kS9X)fN{{QoL~hO-&=q&20Zr*cxFAt PkaNE{wR~2C$NfnjhSXWT literal 0 HcmV?d00001 diff --git a/code/gamespy/gstats/ladderTrack/res/ladderTrack.rc2 b/code/gamespy/gstats/ladderTrack/res/ladderTrack.rc2 new file mode 100644 index 00000000..c70ab2ee --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/res/ladderTrack.rc2 @@ -0,0 +1,13 @@ +// +// LADDERTRACK.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/gstats/ladderTrack/resource.h b/code/gamespy/gstats/ladderTrack/resource.h new file mode 100644 index 00000000..8cc94c9f --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/resource.h @@ -0,0 +1,34 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by ladderTrack.rc +// +#define IDD_LADDERTRACK_DIALOG 102 +#define IDC_EMAIL 103 +#define IDC_NICK 104 +#define IDC_PASSWORD 105 +#define IDR_MAINFRAME 128 +#define IDD_LOGIN 129 +#define IDD_HOST_OR_JOIN 130 +#define IDD_WAITING 132 +#define IDC_HOST 1000 +#define IDC_LOCAL_POSITION 1000 +#define IDC_JOIN 1001 +#define IDC_REMOTE_POSITION 1001 +#define IDC_JOIN_ADDRESS 1002 +#define IDC_LOGOUT 1004 +#define IDC_START_RACE 1005 +#define IDC_INFO 1006 +#define IDC_LOCAL_PROGRESS 1007 +#define IDC_REMOTE_PROGRESS 1008 +#define IDC_UPDATE_POSITIONS 1009 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/gamespy/gstats/multiTrack/HostOrJoinDlg.cpp b/code/gamespy/gstats/multiTrack/HostOrJoinDlg.cpp new file mode 100644 index 00000000..2988012a --- /dev/null +++ b/code/gamespy/gstats/multiTrack/HostOrJoinDlg.cpp @@ -0,0 +1,70 @@ +// HostOrJoinDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "multiTrack.h" +#include "HostOrJoinDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CHostOrJoinDlg dialog + + +CHostOrJoinDlg::CHostOrJoinDlg(CWnd* pParent /*=NULL*/) + : CDialog(CHostOrJoinDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CHostOrJoinDlg) + m_hostOrJoin = 0; + m_joinAddress = _T(""); + //}}AFX_DATA_INIT +} + + +void CHostOrJoinDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CHostOrJoinDlg) + DDX_Radio(pDX, IDC_HOST, m_hostOrJoin); + DDX_Text(pDX, IDC_JOIN_ADDRESS, m_joinAddress); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CHostOrJoinDlg, CDialog) + //{{AFX_MSG_MAP(CHostOrJoinDlg) + ON_EN_SETFOCUS(IDC_JOIN_ADDRESS, OnSetfocusJoinAddress) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CHostOrJoinDlg message handlers + + +void CHostOrJoinDlg::OnOK() +{ + UpdateData(); + + // Check for no join address. + ///////////////////////////// + if((m_hostOrJoin == HOSTORJOIN_JOIN) && m_joinAddress.IsEmpty()) + { + MessageBox("Please enter the address of the host to connect to"); + return; + } + + CDialog::OnOK(); +} + +void CHostOrJoinDlg::OnSetfocusJoinAddress() +{ + // Make sure its on join. + ///////////////////////// + UpdateData(); + m_hostOrJoin = HOSTORJOIN_JOIN; + UpdateData(FALSE); +} diff --git a/code/gamespy/gstats/multiTrack/HostOrJoinDlg.h b/code/gamespy/gstats/multiTrack/HostOrJoinDlg.h new file mode 100644 index 00000000..1041829b --- /dev/null +++ b/code/gamespy/gstats/multiTrack/HostOrJoinDlg.h @@ -0,0 +1,51 @@ +#if !defined(AFX_HOSTORJOINDLG_H__7D7ACBE1_D82A_4CB0_B070_368D4263332E__INCLUDED_) +#define AFX_HOSTORJOINDLG_H__7D7ACBE1_D82A_4CB0_B070_368D4263332E__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// HostOrJoinDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CHostOrJoinDlg dialog + +#define HOSTORJOIN_HOST 0 +#define HOSTORJOIN_JOIN 1 + +class CHostOrJoinDlg : public CDialog +{ +// Construction +public: + CHostOrJoinDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CHostOrJoinDlg) + enum { IDD = IDD_HOST_OR_JOIN }; + int m_hostOrJoin; + CString m_joinAddress; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CHostOrJoinDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CHostOrJoinDlg) + virtual void OnOK(); + afx_msg void OnSetfocusJoinAddress(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_HOSTORJOINDLG_H__7D7ACBE1_D82A_4CB0_B070_368D4263332E__INCLUDED_) diff --git a/code/gamespy/gstats/multiTrack/LoginDlg.cpp b/code/gamespy/gstats/multiTrack/LoginDlg.cpp new file mode 100644 index 00000000..13ecf2d1 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/LoginDlg.cpp @@ -0,0 +1,186 @@ +// LoginDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "multiTrack.h" +#include "LoginDlg.h" +#include "../../common/gsAvailable.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + + +CLoginDlg::CLoginDlg(CWnd* pParent /*=NULL*/) + : CDialog(CLoginDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CLoginDlg) + m_email = _T(""); + m_nick = _T(""); + m_password = _T(""); + //}}AFX_DATA_INIT +} + + +void CLoginDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CLoginDlg) + DDX_Text(pDX, IDC_EMAIL, m_email); + DDX_Text(pDX, IDC_NICK, m_nick); + DDX_Text(pDX, IDC_PASSWORD, m_password); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CLoginDlg, CDialog) + //{{AFX_MSG_MAP(CLoginDlg) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg message handlers + +GPResult checkResult; +int checkProfile; + +void CheckUserCallback(GPConnection * connection, void * _arg, void * param) +{ + GPCheckResponseArg * arg = (GPCheckResponseArg *)_arg; + + checkResult = arg->result; + checkProfile = arg->profile; +} + +void CLoginDlg::OnOK() +{ + GPConnection connection; + HCURSOR hourglass; + HCURSOR lastCursor; + GPResult result; + const char* aGameName = "gmtest"; + + UpdateData(); + + // CHECK FOR NO ACCOUNT INFO + //////////////////////////// + if(m_email.IsEmpty() || m_nick.IsEmpty() || m_password.IsEmpty()) + { + MessageBox("Please fill in all the account information."); + return; + } + + // AVAILABILITY CHECK + GSIStartAvailableCheck(aGameName); + GSIACResult ac_result = GSIACWaiting;; + while((ac_result = GSIAvailableCheckThink()) == GSIACWaiting) + msleep(5); + if(ac_result != GSIACAvailable) + { + printf("The backend is not available\n"); + return ;; + } + + + // INITIALIZE GP + //////////////// + if(gpInitialize(&connection, 502, 0, GP_PARTNERID_GAMESPY) != GP_NO_ERROR) + { + MessageBox("Error initializing the login system."); + return; + } + + // wait cursor on + ///////////////// + hourglass = LoadCursor(NULL, IDC_WAIT); + if(hourglass) + lastCursor = SetCursor(hourglass); + + // CHECK FOR THE ACCOUNT SPECIFIED + ////////////////////////////////// + result = gpCheckUser(&connection, m_nick, m_email, m_password, GP_BLOCKING, CheckUserCallback, NULL); + + // wait cursor off + ////////////////// + if(hourglass) + SetCursor(lastCursor); + + // DESTROY THE GP OBJECT + //////////////////////// + gpDestroy(&connection); + + // CHECK FOR AN ERROR + ///////////////////// + if(result != GP_NO_ERROR) + { + MessageBox("Error verifying the account."); + return; + } + + // CHECK THE RESULT + /////////////////// + if(checkResult != GP_NO_ERROR) + { + if(checkResult == GP_CHECK_BAD_EMAIL) + MessageBox("Invalid e-mail."); + else if(checkResult == GP_CHECK_BAD_NICK) + MessageBox("Invalid nick."); + else if(checkResult == GP_CHECK_BAD_PASSWORD) + MessageBox("Invalid password."); + else + MessageBox("Error verifying the account."); + return; + } + + // save the login info for next time + //////////////////////////////////// + FILE * file; + file = fopen("login.txt", "wt"); + if(file) + { + fprintf(file, "%s\n%s\n%s", m_email, m_nick, m_password); + fclose(file); + } + + // STORE THE PROFILE ID + /////////////////////// + m_profile = checkProfile; + + CDialog::OnOK(); +} + +BOOL CLoginDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // load login info + ////////////////// + FILE * file; + file = fopen("login.txt", "rt"); + if(file) + { + char buffer[512]; + + if(fgets(buffer, sizeof(buffer), file)) + m_email = buffer; + if(fgets(buffer, sizeof(buffer), file)) + m_nick = buffer; + if(fgets(buffer, sizeof(buffer), file)) + m_password = buffer; + + fclose(file); + + m_email.Remove('\n'); + m_nick.Remove('\n'); + m_password.Remove('\n'); + } + + UpdateData(FALSE); + + return TRUE; +} \ No newline at end of file diff --git a/code/gamespy/gstats/multiTrack/LoginDlg.h b/code/gamespy/gstats/multiTrack/LoginDlg.h new file mode 100644 index 00000000..11a5e2a7 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/LoginDlg.h @@ -0,0 +1,50 @@ +#if !defined(AFX_LOGINDLG_H__E77DE9BB_5AB3_4731_B26A_403169643353__INCLUDED_) +#define AFX_LOGINDLG_H__E77DE9BB_5AB3_4731_B26A_403169643353__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// LoginDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + +class CLoginDlg : public CDialog +{ +// Construction +public: + int m_profile; + CLoginDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CLoginDlg) + enum { IDD = IDD_LOGIN }; + CString m_email; + CString m_nick; + CString m_password; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CLoginDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CLoginDlg) + virtual BOOL OnInitDialog(); + virtual void OnOK(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_LOGINDLG_H__E77DE9BB_5AB3_4731_B26A_403169643353__INCLUDED_) diff --git a/code/gamespy/gstats/multiTrack/StdAfx.cpp b/code/gamespy/gstats/multiTrack/StdAfx.cpp new file mode 100644 index 00000000..aacf2ef2 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// multiTrack.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/code/gamespy/gstats/multiTrack/StdAfx.h b/code/gamespy/gstats/multiTrack/StdAfx.h new file mode 100644 index 00000000..4c8d2878 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/StdAfx.h @@ -0,0 +1,31 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__535325A8_F784_4AFE_827C_AF35A881DD77__INCLUDED_) +#define AFX_STDAFX_H__535325A8_F784_4AFE_827C_AF35A881DD77__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +#include "../gstats.h" +#include "../../gp/gp.h" +#include "../../ghttp/ghttp.h" +#include "../../gt2/gt2.h" +#include "../../gt2/gt2Encode.h" + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__535325A8_F784_4AFE_827C_AF35A881DD77__INCLUDED_) diff --git a/code/gamespy/gstats/multiTrack/WaitingDlg.cpp b/code/gamespy/gstats/multiTrack/WaitingDlg.cpp new file mode 100644 index 00000000..5b6b322c --- /dev/null +++ b/code/gamespy/gstats/multiTrack/WaitingDlg.cpp @@ -0,0 +1,43 @@ +// WaitingDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "multiTrack.h" +#include "WaitingDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CWaitingDlg dialog + + +CWaitingDlg::CWaitingDlg(CWnd* pParent /*=NULL*/) + : CDialog(CWaitingDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CWaitingDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void CWaitingDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CWaitingDlg) + // NOTE: the ClassWizard will add DDX and DDV calls here + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CWaitingDlg, CDialog) + //{{AFX_MSG_MAP(CWaitingDlg) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CWaitingDlg message handlers + diff --git a/code/gamespy/gstats/multiTrack/WaitingDlg.h b/code/gamespy/gstats/multiTrack/WaitingDlg.h new file mode 100644 index 00000000..594f33f7 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/WaitingDlg.h @@ -0,0 +1,45 @@ +#if !defined(AFX_WAITINGDLG_H__E9C283EE_68D1_40E9_83C6_AAB81D13ED25__INCLUDED_) +#define AFX_WAITINGDLG_H__E9C283EE_68D1_40E9_83C6_AAB81D13ED25__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// WaitingDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CWaitingDlg dialog + +class CWaitingDlg : public CDialog +{ +// Construction +public: + CWaitingDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CWaitingDlg) + enum { IDD = IDD_WAITING }; + // NOTE: the ClassWizard will add data members here + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CWaitingDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CWaitingDlg) + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_WAITINGDLG_H__E9C283EE_68D1_40E9_83C6_AAB81D13ED25__INCLUDED_) diff --git a/code/gamespy/gstats/multiTrack/multiTrack.cpp b/code/gamespy/gstats/multiTrack/multiTrack.cpp new file mode 100644 index 00000000..5300e019 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/multiTrack.cpp @@ -0,0 +1,61 @@ +// multiTrack.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "multiTrack.h" +#include "multiTrackDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CMultiTrackApp + +BEGIN_MESSAGE_MAP(CMultiTrackApp, CWinApp) + //{{AFX_MSG_MAP(CMultiTrackApp) + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CMultiTrackApp construction + +CMultiTrackApp::CMultiTrackApp() +{ +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CMultiTrackApp object + +CMultiTrackApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CMultiTrackApp initialization + +BOOL CMultiTrackApp::InitInstance() +{ + // Standard initialization +/* +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif +*/ + CMultiTrackDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + } + else if (nResponse == IDCANCEL) + { + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/code/gamespy/gstats/multiTrack/multiTrack.h b/code/gamespy/gstats/multiTrack/multiTrack.h new file mode 100644 index 00000000..3c41b229 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/multiTrack.h @@ -0,0 +1,47 @@ +// multiTrack.h : main header file for the MULTITRACK application +// + +#if !defined(AFX_MULTITRACK_H__6EF40CA1_83C8_448A_883A_63AF15457C5E__INCLUDED_) +#define AFX_MULTITRACK_H__6EF40CA1_83C8_448A_883A_63AF15457C5E__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CMultiTrackApp: +// See multiTrack.cpp for the implementation of this class +// + +class CMultiTrackApp : public CWinApp +{ +public: + CMultiTrackApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CMultiTrackApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CMultiTrackApp) + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_MULTITRACK_H__6EF40CA1_83C8_448A_883A_63AF15457C5E__INCLUDED_) diff --git a/code/gamespy/gstats/multiTrack/multiTrack.rc b/code/gamespy/gstats/multiTrack/multiTrack.rc new file mode 100644 index 00000000..735ea833 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/multiTrack.rc @@ -0,0 +1,253 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\multiTrack.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\multiTrack.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_MULTITRACK_DIALOG DIALOGEX 0, 0, 252, 135 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "multiTrack" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Exit",IDOK,195,5,50,14 + PUSHBUTTON "Logout",IDC_LOGOUT,195,21,50,14 + PUSHBUTTON "Start 50m",IDC_START_50,10,15,50,14 + LTEXT "Info",IDC_INFO,10,35,80,8 + CONTROL "Progress1",IDC_LOCAL_PROGRESS,"msctls_progress32", + PBS_SMOOTH | WS_BORDER,10,70,80,14 + CONTROL "Progress1",IDC_REMOTE_PROGRESS,"msctls_progress32", + PBS_SMOOTH | WS_BORDER,10,105,80,14 + PUSHBUTTON "Start 100m",IDC_START_100,70,15,50,14 + PUSHBUTTON "Start 200m",IDC_START_200,130,15,50,14 + CTEXT "8888888888",IDC_LOCAL_INFO_OVERALL,145,70,41,8 + CTEXT "8888888888",IDC_REMOTE_INFO_OVERALL,195,70,41,8 + GROUPBOX "Rating",IDC_STATIC,105,45,140,85 + LTEXT "Opponent",IDC_STATIC,201,55,32,8 + LTEXT "Overall",IDC_STATIC,111,70,23,8 + LTEXT "50m",IDC_STATIC,111,85,14,8 + LTEXT "100m",IDC_STATIC,111,100,18,8 + LTEXT "200m",IDC_STATIC,111,115,18,8 + LTEXT "You",IDC_STATIC,161,55,14,8 + CTEXT "8888888888",IDC_LOCAL_INFO_50,145,85,41,8 + CTEXT "8888888888",IDC_LOCAL_INFO_100,145,100,41,8 + CTEXT "8888888888",IDC_LOCAL_INFO_200,145,115,41,8 + CTEXT "8888888888",IDC_REMOTE_INFO_100,195,100,41,8 + CTEXT "8888888888",IDC_REMOTE_INFO_50,195,85,41,8 + CTEXT "8888888888",IDC_REMOTE_INFO_200,195,115,41,8 + LTEXT "You:",IDC_STATIC,10,60,16,8 + LTEXT "Opponent:",IDC_STATIC,10,95,34,8 + LTEXT "Use Z && X to race",IDC_STATIC,10,45,57,8 +END + +IDD_LOGIN DIALOG DISCARDABLE 0, 0, 147, 76 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "GameSpy Login" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_EMAIL,51,8,84,12,ES_AUTOHSCROLL + EDITTEXT IDC_NICK,51,23,84,12,ES_AUTOHSCROLL + EDITTEXT IDC_PASSWORD,51,38,84,12,ES_PASSWORD | ES_AUTOHSCROLL + DEFPUSHBUTTON "Login",IDOK,15,55,50,14 + PUSHBUTTON "Cancel",IDCANCEL,80,55,50,14 + LTEXT "email:",IDC_STATIC,10,10,19,8 + LTEXT "nick:",IDC_STATIC,10,25,16,8 + LTEXT "password:",IDC_STATIC,10,40,33,8 +END + +IDD_HOST_OR_JOIN DIALOG DISCARDABLE 0, 0, 165, 50 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Host or Join?" +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "Host",IDC_HOST,"Button",BS_AUTORADIOBUTTON | WS_GROUP,7, + 7,31,10 + CONTROL "Join",IDC_JOIN,"Button",BS_AUTORADIOBUTTON,7,31,29,10 + EDITTEXT IDC_JOIN_ADDRESS,52,28,106,14,ES_AUTOHSCROLL | WS_GROUP + DEFPUSHBUTTON "OK",IDOK,52,7,50,14,WS_GROUP + PUSHBUTTON "Cancel",IDCANCEL,108,7,50,14 +END + +IDD_WAITING DIALOG DISCARDABLE 0, 0, 64, 45 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "multiTrack" +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "Cancel",IDCANCEL,7,24,50,14 + LTEXT "Please Wait...",IDC_STATIC,9,7,45,8 +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "multiTrack MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "multiTrack\0" + VALUE "LegalCopyright", "Copyright (C) 2001\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "multiTrack.EXE\0" + VALUE "ProductName", "multiTrack Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_MULTITRACK_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 245 + TOPMARGIN, 7 + BOTTOMMARGIN, 128 + END + + IDD_HOST_OR_JOIN, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 158 + TOPMARGIN, 7 + BOTTOMMARGIN, 43 + END + + IDD_WAITING, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 57 + TOPMARGIN, 7 + BOTTOMMARGIN, 38 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\multiTrack.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/gamespy/gstats/multiTrack/multiTrackDlg.cpp b/code/gamespy/gstats/multiTrack/multiTrackDlg.cpp new file mode 100644 index 00000000..ed0dcdf3 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/multiTrackDlg.cpp @@ -0,0 +1,1028 @@ +// multiTrackDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "multiTrack.h" +#include "multiTrackDlg.h" +#include "waitingDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CMultiTrackDlg dialog + +CMultiTrackDlg::CMultiTrackDlg(CWnd* pParent /*=NULL*/) + : CDialog(CMultiTrackDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CMultiTrackDlg) + m_info = _T("Ready"); + m_localInfo100 = _T(""); + m_localInfo200 = _T(""); + m_localInfo50 = _T(""); + m_localInfoOverall = _T(""); + m_remoteInfo100 = _T(""); + m_remoteInfo200 = _T(""); + m_remoteInfo50 = _T(""); + m_remoteInfoOverall = _T(""); + //}}AFX_DATA_INIT + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); +} + +void CMultiTrackDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CMultiTrackDlg) + DDX_Control(pDX, IDC_START_100, m_start100); + DDX_Control(pDX, IDC_START_200, m_start200); + DDX_Control(pDX, IDC_REMOTE_PROGRESS, m_remoteProgress); + DDX_Control(pDX, IDC_LOCAL_PROGRESS, m_localProgress); + DDX_Control(pDX, IDC_START_50, m_start50); + DDX_Text(pDX, IDC_INFO, m_info); + DDX_Text(pDX, IDC_LOCAL_INFO_100, m_localInfo100); + DDX_Text(pDX, IDC_LOCAL_INFO_200, m_localInfo200); + DDX_Text(pDX, IDC_LOCAL_INFO_50, m_localInfo50); + DDX_Text(pDX, IDC_LOCAL_INFO_OVERALL, m_localInfoOverall); + DDX_Text(pDX, IDC_REMOTE_INFO_100, m_remoteInfo100); + DDX_Text(pDX, IDC_REMOTE_INFO_200, m_remoteInfo200); + DDX_Text(pDX, IDC_REMOTE_INFO_50, m_remoteInfo50); + DDX_Text(pDX, IDC_REMOTE_INFO_OVERALL, m_remoteInfoOverall); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CMultiTrackDlg, CDialog) + //{{AFX_MSG_MAP(CMultiTrackDlg) + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_BN_CLICKED(IDC_LOGOUT, OnLogout) + ON_WM_DESTROY() + ON_WM_TIMER() + ON_BN_CLICKED(IDC_START_50, OnStart50) + ON_BN_CLICKED(IDC_START_100, OnStart100) + ON_BN_CLICKED(IDC_START_200, OnStart200) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CMultiTrackDlg message handlers + +#define MSG_CONNECT "is" // client-pid, nick +#define MSG_CHALLENGE "is" // host-pid, challenge +#define MSG_CHALLENGE_TYPE 1 +#define MSG_RESPONSE "s" // response +#define MSG_RESPONSE_TYPE 2 +#define MSG_COUNTDOWN "ii" // event, count +#define MSG_COUNTDOWN_TYPE 3 +#define MSG_START_RACE "" +#define MSG_START_RACE_TYPE 5 +#define MSG_PROGRESS "i" // progress +#define MSG_PROGRESS_TYPE 6 +#define MSG_END_RACE "i" // time +#define MSG_END_RACE_TYPE 7 +#define MSG_CHAT "s" // message +#define MSG_CHAT_TYPE 8 + +#define HOST_PORT 38465 +#define HOST_PORT_STRING ":38465" +#define CLIENT_PORT_STRING ":38446" +#define COUNTDOWN_START 5 + +#define TIMER_THINK 100 +#define TIMER_COUNTDOWN 101 + +#define DEFRATING 1400 +#define POINTSWIN 16 +#define POINTSLOSS -16 + +CMultiTrackDlg * Dlg; + +void ConnectedCallback +( + GT2Connection connection, + GT2Result result, + GT2Byte * message, + int len +) +{ + if(result == GT2Success) + Dlg->m_state = JOIN_WAITING; + else + { + Dlg->m_state = JOIN_ERROR; + Dlg->m_connection = NULL; + } +} + +void ReceivedCallback +( + GT2Connection connection, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + if(!message || !len) + return; + + GTMessageType type; + + type = gtEncodedMessageType((char*)message); + + if(type == MSG_CHALLENGE_TYPE) + { + ASSERT(!Dlg->m_hosting && (Dlg->m_state == JOIN_WAITING)); + + int pid; + char challenge[64]; + char response[33]; + if(gtDecode(MSG_CHALLENGE, (char*)message, len, &pid, challenge) == -1) + { + Dlg->m_state = JOIN_ERROR; + return; + } + + GenerateAuth(challenge, (char *)(LPCSTR)Dlg->m_loginDlg.m_password, response); + + // Send the response. + ///////////////////// + char message[64]; + int rcode; + rcode = gtEncode(MSG_RESPONSE_TYPE, MSG_RESPONSE, message, sizeof(message), response); + ASSERT(rcode != -1); + gt2Send(connection, (const GT2Byte*)message, rcode, GT2True); + + Dlg->m_remoteProfile = pid; + Dlg->m_state = JOIN_CONNECTED; + } + else if(type == MSG_RESPONSE_TYPE) + { + ASSERT(Dlg->m_hosting && (Dlg->m_state == HOST_CHALLENGING)); + + char response[33]; + if(gtDecode(MSG_RESPONSE, (char*)message, len, response) == -1) + { + Dlg->m_state = HOST_ERROR; + return; + } + Dlg->m_remoteResponse = response; + Dlg->m_state = HOST_CONNECTED; + } + else if(type == MSG_COUNTDOWN_TYPE) + { + ASSERT(!Dlg->m_hosting); + + if(gtDecode(MSG_COUNTDOWN, (char*)message, len, &Dlg->m_event, &Dlg->m_countdown) == -1) + { + Dlg->m_state = JOIN_ERROR; + return; + } + + Dlg->Countdown(); + } + else if(type == MSG_START_RACE_TYPE) + { + ASSERT(!Dlg->m_hosting); + + Dlg->StartRace(); + } + else if(type == MSG_PROGRESS_TYPE) + { + if(Dlg->m_racing) + { + int progress; + if(gtDecode(MSG_PROGRESS, (char*)message, len, &progress) != -1) + Dlg->m_remoteProgress.SetPos(progress); + } + } + else if(type == MSG_END_RACE_TYPE) + { + if(Dlg->m_racing) + { + gtDecode(MSG_END_RACE, (char*)message, len, &Dlg->m_remoteTime); + } + } +} + +void ClosedCallback +( + GT2Connection connection, + GT2CloseReason reason +) +{ + if(reason != GT2LocalClose) + { + Dlg->MessageBox("Connection closed"); + Dlg->Logout(); + } +} + +void ConnectAttemptCallback +( + GT2Socket listener, + GT2Connection connection, + unsigned int ip, + unsigned short port, + int latency, + GT2Byte * message, + int len +) +{ + int pid = 0; + char nick[128]; + + // Only allow one connection. + ///////////////////////////// + if(Dlg->m_connection) + { + gt2Reject(connection, NULL, 0); + return; + } + + // Decode the pid. + ////////////////// + if(message && len) + { + if(gtDecodeNoType(MSG_CONNECT, (char*)message, len, &pid, nick) == -1) + pid = 0; + } + + // If we didn't/couldn't get the pid, reject the attempt. + ///////////////////////////////////////////////////////// + if(!pid) + { + gt2Reject(connection, NULL, 0); + return; + } + + // Accept the connection. + ///////////////////////// + GT2ConnectionCallbacks callbacks; + memset(&callbacks, 0, sizeof(GT2ConnectionCallbacks)); + callbacks.received = ReceivedCallback; + callbacks.closed = ClosedCallback; + if(!gt2Accept(connection, &callbacks)) + return; + + // Set some states. + /////////////////// + Dlg->m_remoteProfile = pid; + Dlg->m_connection = connection; + Dlg->m_state = HOST_CHALLENGING; + Dlg->m_challenged = FALSE; + Dlg->m_remoteNick = nick; +} + +/*void StoppedCallback +( + GTListener listener, + GTStopReason reason +) +{ + if(reason != GTStopped) + { + Dlg->MessageBox("Listener stopped"); + Dlg->Logout(); + } +}*/ + +void SocketErrorCallback +( + GT2Socket socket +) +{ + Dlg->MessageBox("Socket Error!"); + Dlg->Logout(); +} + +BOOL CMultiTrackDlg::SetupHosting() +{ + int rcode; + CString str; + + if(!IsStatsConnected()) + { + // Use the development system. + ////////////////////////////// + strcpy(StatsServerHostname, "sdkdev.gamespy.com"); + + // Set the gamename and secret key. + /////////////////////////////////// + strcpy(gcd_gamename, "st_rank"); + gcd_secret_key[0] = '5'; + gcd_secret_key[1] = '3'; + gcd_secret_key[2] = 'J'; + gcd_secret_key[3] = 'x'; + gcd_secret_key[4] = '7'; + gcd_secret_key[5] = 'W'; + + // Init the connection to the backend. + ////////////////////////////////////// + rcode = InitStatsConnection(0); + if(rcode != GE_NOERROR) + { + str.Format("Failed to connect to the stats server (%d).", rcode); + MessageBox(str); + PostQuitMessage(1); + return TRUE; + } + + // Get the challenge. + ///////////////////// + m_challenge = GetChallenge(NULL); + } + + /* + // Setup the listener's callbacks. + ////////////////////////////////// + GTListenerCallbacks callbacks; + memset(&callbacks, 0, sizeof(GTListenerCallbacks)); + callbacks.connectAttempt = ConnectAttemptCallback; + callbacks.stopped = StoppedCallback; + + // Create the listener. + /////////////////////// + m_listener = gtListen(gtAddressToString(0, HOST_PORT, NULL), &callbacks); + if(!m_listener) + return FALSE; + */ + + GT2ConnectionCallbacks connectionCallbacks; + memset(&connectionCallbacks, 0, sizeof(GT2ConnectionCallbacks)); + connectionCallbacks.received = ReceivedCallback; + connectionCallbacks.closed = ClosedCallback; + + GT2Result aResult = gt2CreateSocket(&m_socket, HOST_PORT_STRING, 0, 0, SocketErrorCallback); + if (GT2Success != aResult) + return FALSE; + + gt2Listen(m_socket, ConnectAttemptCallback); + + m_state = HOST_LISTENING; + + // Bring up the "waiting" dialog. + ///////////////////////////////// + rcode = m_waitingDlg.DoModal(); + + // If it was cancelled, try again. + ////////////////////////////////// + if(rcode != IDOK) + Logout(); + + return TRUE; +} + +BOOL CMultiTrackDlg::SetupJoining() +{ + int rcode; + + // Setup the address to connect to. + /////////////////////////////////// + CString remoteAddress; + remoteAddress.Format("%s:%d", m_hostOrJoinDlg.m_joinAddress, HOST_PORT); + + // Encode the profile id. + ///////////////////////// + char buffer[64]; + rcode = gtEncodeNoType(MSG_CONNECT, buffer, sizeof(buffer), m_loginDlg.m_profile, m_loginDlg.m_nick); + ASSERT(rcode != -1); + + // Setup the callbacks. + /////////////////////// + GT2ConnectionCallbacks callbacks; + memset(&callbacks, 0, sizeof(GT2ConnectionCallbacks)); + callbacks.connected = ConnectedCallback; + callbacks.received = ReceivedCallback; + callbacks.closed = ClosedCallback; + + // Create the socket + GT2Result aResult = gt2CreateSocket(&m_socket, CLIENT_PORT_STRING, 0, 0, SocketErrorCallback); + if (aResult != GT2Success) + { + MessageBox("Failed to create socket!"); + return FALSE; + } + + // Connect. + /////////// + m_state = JOIN_CONNECTING; + aResult = gt2Connect(m_socket, &m_connection, remoteAddress, (const GT2Byte*)buffer, sizeof(buffer), -1, &callbacks, GT2False); + if(!m_connection) + return FALSE; + + // Bring up the "waiting" dialog. + ///////////////////////////////// + rcode = m_waitingDlg.DoModal(); + + // If it was cancelled, try again. + ////////////////////////////////// + if(rcode != IDOK) + Logout(); + + return TRUE; +} + +void CMultiTrackDlg::UpdateRatingsDisplay() +{ + Dlg->UpdateData(); + + m_localInfoOverall.Format("%d", m_localRatings[0]); + m_localInfo50.Format("%d", m_localRatings[1]); + m_localInfo100.Format("%d", m_localRatings[2]); + m_localInfo200.Format("%d", m_localRatings[3]); + + m_remoteInfoOverall.Format("%d", m_remoteRatings[0]); + m_remoteInfo50.Format("%d", m_remoteRatings[1]); + m_remoteInfo100.Format("%d", m_remoteRatings[2]); + m_remoteInfo200.Format("%d", m_remoteRatings[3]); + + Dlg->UpdateData(FALSE); +} + +GHTTPBool PlayerRatingsPageCompleted +( + GHTTPRequest request, + GHTTPResult result, + char * buffer, + __int64 bufferLen, + void * param +) +{ + if(result == GHTTPSuccess) + { + BOOL localPlayer = (BOOL)param; + int ratings[4]; + int rcode; + + // Scan out ratings. + //////////////////// + rcode = sscanf(buffer, "%d %d %d %d", &ratings[0], &ratings[1], &ratings[2], &ratings[3]); + if(rcode == 4) + { + if(localPlayer) + memcpy(Dlg->m_localRatings, ratings, sizeof(int) * 4); + else + memcpy(Dlg->m_remoteRatings, ratings, sizeof(int) * 4); + + Dlg->UpdateRatingsDisplay(); + } + } + + return GHTTPTrue; +} + +BOOL CMultiTrackDlg::SetupMatch() +{ + int rcode; + BOOL result; + + m_state = SETTING_UP; + + ASSERT(!m_connection); + ASSERT(!m_socket); + + m_state = 0; + m_remoteResponse.Empty(); + m_remoteProfile = 0; + m_connection = NULL; + m_start50.EnableWindow(FALSE); + m_start100.EnableWindow(FALSE); + m_start200.EnableWindow(FALSE); + m_start = 0; + m_countdown = 0; + m_racing = FALSE; + m_event = EVENT_NONE; + m_numSteps = 0; + m_step = NONE; + + do + { + // Login the user (actually just verifying the login). + ////////////////////////////////////////////////////// + rcode = m_loginDlg.DoModal(); + if(rcode != IDOK) + { + PostQuitMessage(1); + return FALSE; + } + + // See if they want to host or join. + //////////////////////////////////// + rcode = m_hostOrJoinDlg.DoModal(); + } + while(rcode != IDOK); + + m_connection = NULL; + m_hosting = (m_hostOrJoinDlg.m_hostOrJoin == HOSTORJOIN_HOST); + + CString str; + str.Format("multiTrack%s", m_hosting?" (hosting)":""); + SetWindowText(str); + + if(m_hosting) + result = SetupHosting(); + else + result = SetupJoining(); + + if(result && m_hosting) + { + m_start50.EnableWindow(); + m_start100.EnableWindow(); + m_start200.EnableWindow(); + } + + CString url; + url.Format("http://sdkdev.gamespy.com/games/st_rank/web/playerratings.asp?pid=%d", m_loginDlg.m_profile); + ghttpGet(url, GHTTPFalse, PlayerRatingsPageCompleted, (void *)TRUE); + url.Format("http://sdkdev.gamespy.com/games/st_rank/web/playerratings.asp?pid=%d", m_remoteProfile); + ghttpGet(url, GHTTPFalse, PlayerRatingsPageCompleted, (void *)FALSE); + + return result; +} + +BOOL CMultiTrackDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + SetIcon(m_hIcon, TRUE); + SetIcon(m_hIcon, FALSE); + + // Basic initialization. + //////////////////////// + Dlg = this; + m_connection = NULL; + m_socket = NULL; + m_state = LOGGED_OUT; + m_countdown = 0; + m_racing = FALSE; + + // Init gt. + /////////// + //gt2Startup(); + + // Set a think timer. + ///////////////////// + SetTimer(TIMER_THINK, 50, NULL); + + return TRUE; +} + +void CMultiTrackDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CDialog::OnPaint(); + } +} + +HCURSOR CMultiTrackDlg::OnQueryDragIcon() +{ + return (HCURSOR) m_hIcon; +} + +void CMultiTrackDlg::OnLogout() +{ + m_state = LOGGED_OUT; + + // Clean stuff up. + ////////////////// + if(m_connection) + { + gt2CloseConnection(m_connection); + m_connection = NULL; + } + if(m_socket) + { + gt2CloseSocket(m_socket); + m_socket = NULL; + } + if(m_waitingDlg.m_hWnd && m_waitingDlg.IsWindowEnabled()) + { + m_waitingDlg.EndDialog(IDCANCEL); + } +} + +void CMultiTrackDlg::OnDestroy() +{ + CDialog::OnDestroy(); + + if(IsStatsConnected()) + CloseStatsConnection(); + + ghttpCleanup(); + + //gt2Cleanup(GT2False); + if (m_connection) + { + gt2CloseConnection(m_connection); + m_connection = NULL; + } + if (m_socket) + { + gt2CloseSocket(m_socket); + m_socket = NULL; + } + +} + +int CalculateHandicap(int rating1, int rating2) +{ + static int htable[] = { 12, 34, 56, 78, 101, 126, 151, 177, 206, 239, 273, 315, 366, 446, 471, 715 }; + int diff; + int i; + + diff = abs(rating1 - rating2); + + for(i = 0 ; i < 16 ; i++) + { + if(diff < htable[i]) + return i; + } + + return 16; +} + +void CMultiTrackDlg::UpdateStats() +{ + int handicap; + + handicap = CalculateHandicap(m_localRatings[0], m_remoteRatings[0]); + if(handicap) + { + if(m_localRatings[0] > m_remoteRatings[0]) + { + m_localRatings[0] -= handicap; + m_remoteRatings[0] += handicap; + } + else + { + m_localRatings[0] += handicap; + m_remoteRatings[0] -= handicap; + } + } + + handicap = CalculateHandicap(m_localRatings[m_event], m_remoteRatings[m_event]); + if(handicap) + { + if(m_localRatings[m_event] > m_remoteRatings[m_event]) + { + m_localRatings[m_event] -= handicap; + m_remoteRatings[m_event] += handicap; + } + else + { + m_localRatings[m_event] += handicap; + m_remoteRatings[m_event] -= handicap; + } + } + + if(m_localTime < m_remoteTime) + { + m_localRatings[0] += POINTSWIN; + m_localRatings[m_event] += POINTSWIN; + m_remoteRatings[0] += POINTSLOSS; + m_remoteRatings[m_event] += POINTSLOSS; + } + else if(m_remoteTime < m_localTime) + { + m_remoteRatings[0] += POINTSWIN; + m_remoteRatings[m_event] += POINTSWIN; + m_localRatings[0] += POINTSLOSS; + m_localRatings[m_event] += POINTSLOSS; + } + + UpdateRatingsDisplay(); +} + +void CMultiTrackDlg::OnTimer(UINT nIDEvent) +{ + char buffer[64]; + int rcode; + + if(nIDEvent == TIMER_THINK) + { + static BOOL thinking; + + if(!thinking) + { + thinking = TRUE; + + if (m_socket) + gt2Think(m_socket); + ghttpThink(); + + if(m_state == HOST_CHALLENGING) + { + if(!m_challenged) + { + // Send the challenge string and our profile. + ///////////////////////////////////////////// + rcode = gtEncode(MSG_CHALLENGE_TYPE, MSG_CHALLENGE, buffer, sizeof(buffer), m_loginDlg.m_profile, (LPCSTR)m_challenge); + ASSERT(rcode != -1); + gt2Send(m_connection, (const unsigned char*)buffer, rcode, GT2True); + m_challenged = TRUE; + } + } + else if(m_state == HOST_CONNECTED) + { + m_waitingDlg.EndDialog(IDOK); + m_state = RACING; + } + else if(m_state == HOST_ERROR) + { + MessageBox("Error setting up hosting"); + m_waitingDlg.EndDialog(IDCANCEL); + } + else if(m_state == JOIN_CONNECTED) + { + if(m_waitingDlg.m_hWnd && m_waitingDlg.IsWindowEnabled()) + m_waitingDlg.EndDialog(IDOK); + } + else if(m_state == JOIN_ERROR) + { + MessageBox("Error joining a game"); + m_waitingDlg.EndDialog(IDCANCEL); + } + + thinking = FALSE; + } + + if(m_state == LOGGED_OUT) + { + if(!Dlg->SetupMatch()) + { + MessageBox("Error setting up the match"); + Logout(); + } + } + + // Are we racing? + ///////////////// + if(m_racing) + { + // Did we finish? + ///////////////// + if(m_localTime) + { + // Did we both finish? + ////////////////////// + if(m_remoteTime) + { + // Done racing. + /////////////// + m_racing = FALSE; + + // Show the times. + ////////////////// + CString message; + if(m_localTime < m_remoteTime) + message.Format("You won!\n%0.3fs to %0.3fs", m_localTime / 1000.0, m_remoteTime / 1000.0); + else if(m_remoteTime < m_localTime) + message.Format("You lost!\n%0.3fs to %0.3fs", m_localTime / 1000.0, m_remoteTime / 1000.0); + else + message.Format("You tied!\n%0.3fs", m_localTime / 1000.0); + MessageBox(message); + + m_localProgress.SetPos(0); + m_remoteProgress.SetPos(0); + + // Report the stats. + //////////////////// + if(m_hosting) + ReportStats(); + + m_event = EVENT_NONE; + + UpdateStats(); + } + } + else + { + // Let our opponent know how far we are. + //////////////////////////////////////// + rcode = gtEncode(MSG_PROGRESS_TYPE, MSG_PROGRESS, buffer, sizeof(buffer), m_numSteps); + ASSERT(rcode != -1); + gt2Send(m_connection, (const GT2Byte*)buffer, rcode, GT2False); + } + } + } + else if(nIDEvent == TIMER_COUNTDOWN) + { + m_countdown--; + if(m_countdown <= 0) + KillTimer(TIMER_COUNTDOWN); + + if(m_countdown < 0) + return; + + Countdown(); + if(!m_countdown) + StartRace(); + } + + CDialog::OnTimer(nIDEvent); +} + +void CMultiTrackDlg::Logout() +{ + OnLogout(); +} + +void CMultiTrackDlg::Countdown() +{ + if(m_hosting) + { + int rcode; + char message[32]; + rcode = gtEncode(MSG_COUNTDOWN_TYPE, MSG_COUNTDOWN, message, sizeof(message), m_event, m_countdown); + ASSERT(rcode != -1); + gt2Send(m_connection, (const GT2Byte*)message, rcode, GT2True); + } + + if(m_countdown) + { + UpdateData(); + + CString strEvent; + if(m_event == EVENT_50) + strEvent = "50m"; + else if(m_event == EVENT_100) + strEvent = "100m"; + else if(m_event == EVENT_200) + strEvent = "200m"; + m_info.Format("%s starts in %ds", strEvent, m_countdown); + + UpdateData(FALSE); + } +} + +void CMultiTrackDlg::OnStart50() +{ + // Start the countdown. + /////////////////////// + m_countdown = COUNTDOWN_START; + SetTimer(TIMER_COUNTDOWN, 1000, NULL); + m_event = EVENT_50; + Countdown(); +} + +void CMultiTrackDlg::OnStart100() +{ + // Start the countdown. + /////////////////////// + m_countdown = COUNTDOWN_START; + SetTimer(TIMER_COUNTDOWN, 1000, NULL); + m_event = EVENT_100; + Countdown(); +} + +void CMultiTrackDlg::OnStart200() +{ + // Start the countdown. + /////////////////////// + m_countdown = COUNTDOWN_START; + SetTimer(TIMER_COUNTDOWN, 1000, NULL); + m_event = EVENT_200; + Countdown(); +} + +BOOL CMultiTrackDlg::PreTranslateMessage(MSG* pMsg) +{ + if(pMsg->message == WM_KEYDOWN) + { + int nChar = pMsg->wParam; + if((nChar == 'Z') || (nChar == 'X')) + { + if((pMsg->lParam & 0xFFFF) == 1) + { + CString str; + BOOL stepped = FALSE; + + if((nChar == 'Z') && (m_step != LEFT)) + { + m_step = LEFT; + m_numSteps++; + stepped = TRUE; + } + else if ((nChar == 'X') && (m_step != RIGHT)) + { + m_step = RIGHT; + m_numSteps++; + stepped = TRUE; + } + + if(stepped && m_racing) + { + m_localProgress.SetPos(m_numSteps); + if(m_numSteps == m_totalSteps) + { + m_localTime = (GetTickCount() - m_start); + str.Format("%0.3fs\n", m_localTime / 1000.0); + OutputDebugString(str); + //MessageBox(str); + + UpdateData(); + + m_info = "Race Complete"; + + // Let them know we finished. + ///////////////////////////// + char buffer[32]; + int rcode; + rcode = gtEncode(MSG_END_RACE_TYPE, MSG_END_RACE, buffer, sizeof(buffer), m_localTime); + ASSERT(rcode != -1); + gt2Send(m_connection, (const GT2Byte*)buffer, rcode, GT2True); + + UpdateData(FALSE); + } + } + } + + return TRUE; + } + } + + return CDialog::PreTranslateMessage(pMsg); +} + +void CMultiTrackDlg::StartRace() +{ + if(m_hosting) + { + int rcode; + char buffer[32]; + rcode = gtEncode(MSG_START_RACE_TYPE, MSG_START_RACE, buffer, sizeof(buffer)); + ASSERT(rcode != -1); + gt2Send(m_connection, (const GT2Byte*)buffer, rcode, GT2True); + } + + m_localTime = 0; + m_remoteTime = 0; + m_racing = TRUE; + m_numSteps = 0; + m_step = NONE; + m_racing = TRUE; + m_start = GetTickCount(); + if(m_event == EVENT_50) + m_totalSteps = RACE_STEPS_50; + else if(m_event == EVENT_100) + m_totalSteps = RACE_STEPS_100; + else if(m_event == EVENT_200) + m_totalSteps = RACE_STEPS_200; + m_localProgress.SetRange(0, m_totalSteps); + m_localProgress.SetPos(0); + m_remoteProgress.SetRange(0, m_totalSteps); + m_remoteProgress.SetPos(0); + + UpdateData(); + m_info.Format("GO!"); + UpdateData(FALSE); +} + +void CMultiTrackDlg::ReportStats() +{ + char response[33]; + + // Game. + //////// + NewGame(1); + BucketStringOp(NULL, "hostname", bo_set, (char *)(LPCSTR)m_loginDlg.m_nick, bl_server, 0); + BucketIntOp(NULL, "event", bo_set, m_event, bl_server, 0); + + // Local player (host). + /////////////////////// + NewPlayer(NULL, 0, (char *)(LPCSTR)m_loginDlg.m_nick); + BucketIntOp(NULL, "time", bo_set, m_localTime, bl_player, 0); + BucketIntOp(NULL, "pid", bo_set, m_loginDlg.m_profile, bl_player, 0); + GenerateAuth((char *)(LPCSTR)m_challenge, (char *)(LPCSTR)m_loginDlg.m_password, response); + BucketStringOp(NULL, "auth", bo_set, response, bl_player, 0); + + // Remote player (joined). + ////////////////////////// + NewPlayer(NULL, 1, (char *)(LPCSTR)m_remoteNick); + BucketIntOp(NULL, "time", bo_set, m_remoteTime, bl_player, 1); + BucketIntOp(NULL, "pid", bo_set, m_remoteProfile, bl_player, 1); + BucketStringOp(NULL, "auth", bo_set, (char *)(LPCSTR)m_remoteResponse, bl_player, 1); + + SendGameSnapShot(NULL, NULL, SNAP_FINAL); + FreeGame(NULL); +} diff --git a/code/gamespy/gstats/multiTrack/multiTrackDlg.h b/code/gamespy/gstats/multiTrack/multiTrackDlg.h new file mode 100644 index 00000000..af84f899 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/multiTrackDlg.h @@ -0,0 +1,135 @@ +// multiTrackDlg.h : header file +// + +#if !defined(AFX_MULTITRACKDLG_H__4B977C2B_DD72_4B3A_B1F9_E95D26F0C328__INCLUDED_) +#define AFX_MULTITRACKDLG_H__4B977C2B_DD72_4B3A_B1F9_E95D26F0C328__INCLUDED_ + +#include "LoginDlg.h" +#include "HostOrJoinDlg.h" +#include "..\..\GT2\gt2.h" // Added by ClassView +#include "WaitingDlg.h" // Added by ClassView + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +///////////////////////////////////////////////////////////////////////////// +// CMultiTrackDlg dialog + +#define LOGGED_OUT 1 +#define SETTING_UP 2 +#define RACING 3 + +#define HOST_LISTENING 11 +#define HOST_CHALLENGING 12 +#define HOST_CONNECTED 13 +#define HOST_ERROR 14 + +#define JOIN_CONNECTING 21 +#define JOIN_WAITING 22 +#define JOIN_CONNECTED 23 +#define JOIN_ERROR 24 + +#define NONE -1 +#define LEFT 0 +#define RIGHT 1 + +#define RACE_STEPS_50 60 +#define RACE_STEPS_100 120 +#define RACE_STEPS_200 240 + +#define EVENT_NONE 0 +#define EVENT_50 1 +#define EVENT_100 2 +#define EVENT_200 3 + +class CMultiTrackDlg : public CDialog +{ +// Construction +public: + void UpdateRatingsDisplay(); + int m_localRatings[4]; + int m_remoteRatings[4]; + void UpdateStats(); + CString m_remoteNick; + void ReportStats(); + DWORD m_remoteTime; + DWORD m_localTime; + void StartRace(); + int m_totalSteps; + int m_event; + int m_step; + int m_numSteps; + DWORD m_start; + BOOL m_racing; + void Countdown(); + int m_countdown; + BOOL m_challenged; + void Logout(); + CWaitingDlg m_waitingDlg; + CString m_remoteResponse; + int m_state; + CLoginDlg m_loginDlg; + CHostOrJoinDlg m_hostOrJoinDlg; + BOOL m_hosting; + CString m_challenge; + int m_remoteProfile; + + GT2Connection m_connection; + GT2Socket m_socket; + + BOOL SetupJoining(); + BOOL SetupHosting(); + BOOL SetupMatch(); + CMultiTrackDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CMultiTrackDlg) + enum { IDD = IDD_MULTITRACK_DIALOG }; + CButton m_start100; + CButton m_start200; + CProgressCtrl m_remoteProgress; + CProgressCtrl m_localProgress; + CButton m_start50; + CString m_info; + CString m_localInfo100; + CString m_localInfo200; + CString m_localInfo50; + CString m_localInfoOverall; + CString m_remoteInfo100; + CString m_remoteInfo200; + CString m_remoteInfo50; + CString m_remoteInfoOverall; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CMultiTrackDlg) + public: + virtual BOOL PreTranslateMessage(MSG* pMsg); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + HICON m_hIcon; + + // Generated message map functions + //{{AFX_MSG(CMultiTrackDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + afx_msg void OnLogout(); + afx_msg void OnDestroy(); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnStart50(); + afx_msg void OnStart100(); + afx_msg void OnStart200(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_MULTITRACKDLG_H__4B977C2B_DD72_4B3A_B1F9_E95D26F0C328__INCLUDED_) diff --git a/code/gamespy/gstats/multiTrack/res/multiTrack.ico b/code/gamespy/gstats/multiTrack/res/multiTrack.ico new file mode 100644 index 0000000000000000000000000000000000000000..7eef0bcbe6580a6f464d688906172c2d9de44262 GIT binary patch literal 1078 zcmc&zF>b>!3}jLb9s)T}@Kod(893@u8ajANzT`op9^o+)S?=nU(FD@%0s)Sg^oyC8{H z9myetc;MEP)59v(LMa~xK8Yu^jIR*H22uCFiq5%C{s7(PJi>o15i^bmX4(vPxWAio z9ryY#AU_jfnd047-@`)XzL?%iS$gQyFP{44kS9X)fN{{QoL~hO-&=q&20Zr*cxFAt PkaNE{wR~2C$NfnjhSXWT literal 0 HcmV?d00001 diff --git a/code/gamespy/gstats/multiTrack/res/multiTrack.rc2 b/code/gamespy/gstats/multiTrack/res/multiTrack.rc2 new file mode 100644 index 00000000..94875273 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/res/multiTrack.rc2 @@ -0,0 +1,13 @@ +// +// MULTITRACK.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/gstats/multiTrack/resource.h b/code/gamespy/gstats/multiTrack/resource.h new file mode 100644 index 00000000..96e71e41 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/resource.h @@ -0,0 +1,41 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by multiTrack.rc +// +#define IDD_MULTITRACK_DIALOG 102 +#define IDC_EMAIL 103 +#define IDC_NICK 104 +#define IDC_PASSWORD 105 +#define IDR_MAINFRAME 128 +#define IDD_LOGIN 129 +#define IDD_HOST_OR_JOIN 130 +#define IDD_WAITING 132 +#define IDC_HOST 1000 +#define IDC_JOIN 1001 +#define IDC_JOIN_ADDRESS 1002 +#define IDC_LOGOUT 1004 +#define IDC_START_50 1005 +#define IDC_INFO 1006 +#define IDC_LOCAL_PROGRESS 1007 +#define IDC_REMOTE_PROGRESS 1008 +#define IDC_START_100 1009 +#define IDC_START_200 1010 +#define IDC_LOCAL_INFO_OVERALL 1011 +#define IDC_LOCAL_INFO_50 1012 +#define IDC_LOCAL_INFO_100 1013 +#define IDC_LOCAL_INFO_200 1014 +#define IDC_REMOTE_INFO_100 1015 +#define IDC_REMOTE_INFO_50 1016 +#define IDC_REMOTE_INFO_200 1017 +#define IDC_REMOTE_INFO_OVERALL 1018 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 133 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1019 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/gamespy/gstats/track/LoginDlg.cpp b/code/gamespy/gstats/track/LoginDlg.cpp new file mode 100644 index 00000000..c465d962 --- /dev/null +++ b/code/gamespy/gstats/track/LoginDlg.cpp @@ -0,0 +1,174 @@ +// LoginDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "track.h" +#include "LoginDlg.h" + +#include "../../gp/gp.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + + +CLoginDlg::CLoginDlg(CWnd* pParent /*=NULL*/) + : CDialog(CLoginDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CLoginDlg) + m_email = _T(""); + m_nick = _T(""); + m_password = _T(""); + //}}AFX_DATA_INIT +} + + +void CLoginDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CLoginDlg) + DDX_Text(pDX, IDC_EMAIL, m_email); + DDX_Text(pDX, IDC_NICK, m_nick); + DDX_Text(pDX, IDC_PASSWORD, m_password); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CLoginDlg, CDialog) + //{{AFX_MSG_MAP(CLoginDlg) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg message handlers + +GPResult checkResult; +int checkProfile; + +void CheckUserCallback(GPConnection * connection, void * _arg, void * param) +{ + GPCheckResponseArg * arg = (GPCheckResponseArg *)_arg; + + checkResult = arg->result; + checkProfile = arg->profile; +} + +void CLoginDlg::OnOK() +{ + GPConnection connection; + HCURSOR hourglass; + HCURSOR lastCursor; + GPResult result; + + UpdateData(); + + // CHECK FOR NO ACCOUNT INFO + //////////////////////////// + if(m_email.IsEmpty() || m_nick.IsEmpty() || m_password.IsEmpty()) + { + MessageBox("Please fill in all the account information."); + return; + } + + // INITIALIZE GP + //////////////// + if(gpInitialize(&connection, 488, 0, GP_PARTNERID_GAMESPY) != GP_NO_ERROR) + { + MessageBox("Error initializing the login system."); + return; + } + + // wait cursor on + ///////////////// + hourglass = LoadCursor(NULL, IDC_WAIT); + if(hourglass) + lastCursor = SetCursor(hourglass); + + // CHECK FOR THE ACCOUNT SPECIFIED + ////////////////////////////////// + result = gpCheckUser(&connection, m_nick, m_email, m_password, GP_BLOCKING, CheckUserCallback, NULL); + + // wait cursor off + ////////////////// + if(hourglass) + SetCursor(lastCursor); + + // DESTROY THE GP OBJECT + //////////////////////// + gpDestroy(&connection); + + // CHECK FOR AN ERROR + ///////////////////// + if(result != GP_NO_ERROR) + { + MessageBox("Error verifying the account."); + return; + } + + // CHECK THE RESULT + /////////////////// + if(checkResult != GP_NO_ERROR) + { + if(checkResult == GP_CHECK_BAD_EMAIL) + MessageBox("Invalid e-mail."); + else if(checkResult == GP_CHECK_BAD_NICK) + MessageBox("Invalid nick."); + else if(checkResult == GP_CHECK_BAD_PASSWORD) + MessageBox("Invalid password."); + else + MessageBox("Error verifying the account."); + return; + } + + // save the login info for next time + //////////////////////////////////// + FILE * file; + file = fopen("login.txt", "wt"); + if(file) + { + fprintf(file, "%s\n%s\n%s", m_email, m_nick, m_password); + fclose(file); + } + + // STORE THE PROFILE ID + /////////////////////// + m_profile = checkProfile; + + CDialog::OnOK(); +} + +BOOL CLoginDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // load login info + ////////////////// + FILE * file; + file = fopen("login.txt", "rt"); + if(file) + { + char buffer[512]; + + if(fgets(buffer, sizeof(buffer), file)) + m_email = buffer; + if(fgets(buffer, sizeof(buffer), file)) + m_nick = buffer; + if(fgets(buffer, sizeof(buffer), file)) + m_password = buffer; + + fclose(file); + + m_email.Remove('\n'); + m_nick.Remove('\n'); + m_password.Remove('\n'); + } + + UpdateData(FALSE); + + return TRUE; +} diff --git a/code/gamespy/gstats/track/LoginDlg.h b/code/gamespy/gstats/track/LoginDlg.h new file mode 100644 index 00000000..8d98d134 --- /dev/null +++ b/code/gamespy/gstats/track/LoginDlg.h @@ -0,0 +1,50 @@ +#if !defined(AFX_LOGINDLG_H__6C88CC46_47E9_420F_BC33_BA7F28711BAD__INCLUDED_) +#define AFX_LOGINDLG_H__6C88CC46_47E9_420F_BC33_BA7F28711BAD__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// LoginDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + +class CLoginDlg : public CDialog +{ +// Construction +public: + int m_profile; + CLoginDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CLoginDlg) + enum { IDD = IDD_LOGIN }; + CString m_email; + CString m_nick; + CString m_password; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CLoginDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CLoginDlg) + virtual void OnOK(); + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_LOGINDLG_H__6C88CC46_47E9_420F_BC33_BA7F28711BAD__INCLUDED_) diff --git a/code/gamespy/gstats/track/StdAfx.cpp b/code/gamespy/gstats/track/StdAfx.cpp new file mode 100644 index 00000000..5a50dd72 --- /dev/null +++ b/code/gamespy/gstats/track/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// track.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/code/gamespy/gstats/track/StdAfx.h b/code/gamespy/gstats/track/StdAfx.h new file mode 100644 index 00000000..7b0d7191 --- /dev/null +++ b/code/gamespy/gstats/track/StdAfx.h @@ -0,0 +1,26 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__1CD5EB62_03A4_40FF_8CFA_8AF283DA824A__INCLUDED_) +#define AFX_STDAFX_H__1CD5EB62_03A4_40FF_8CFA_8AF283DA824A__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__1CD5EB62_03A4_40FF_8CFA_8AF283DA824A__INCLUDED_) diff --git a/code/gamespy/gstats/track/res/track.ico b/code/gamespy/gstats/track/res/track.ico new file mode 100644 index 0000000000000000000000000000000000000000..7eef0bcbe6580a6f464d688906172c2d9de44262 GIT binary patch literal 1078 zcmc&zF>b>!3}jLb9s)T}@Kod(893@u8ajANzT`op9^o+)S?=nU(FD@%0s)Sg^oyC8{H z9myetc;MEP)59v(LMa~xK8Yu^jIR*H22uCFiq5%C{s7(PJi>o15i^bmX4(vPxWAio z9ryY#AU_jfnd047-@`)XzL?%iS$gQyFP{44kS9X)fN{{QoL~hO-&=q&20Zr*cxFAt PkaNE{wR~2C$NfnjhSXWT literal 0 HcmV?d00001 diff --git a/code/gamespy/gstats/track/res/track.rc2 b/code/gamespy/gstats/track/res/track.rc2 new file mode 100644 index 00000000..e962277e --- /dev/null +++ b/code/gamespy/gstats/track/res/track.rc2 @@ -0,0 +1,13 @@ +// +// TRACK.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/gstats/track/resource.h b/code/gamespy/gstats/track/resource.h new file mode 100644 index 00000000..936c3154 --- /dev/null +++ b/code/gamespy/gstats/track/resource.h @@ -0,0 +1,34 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by track.rc +// +#define IDC_EMAIL 101 +#define IDD_TRACK_DIALOG 102 +#define IDC_NICK 102 +#define IDC_PASSWORD 103 +#define IDR_MAINFRAME 128 +#define IDD_LOGIN 129 +#define IDC_LOGOUT 1003 +#define IDC_PROGRESS 1005 +#define IDC_INFO 1006 +#define IDC_START_100 1007 +#define IDC_START_50 1008 +#define IDC_START_200 1009 +#define IDC_RANDOM_STATS 1010 +#define IDC_BEST_50 1011 +#define IDC_BEST_100 1012 +#define IDC_BEST_200 1013 +#define IDC_TOP_50 1014 +#define IDC_TOP_100 1015 +#define IDC_TOP_200 1016 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 130 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1012 +#define _APS_NEXT_SYMED_VALUE 104 +#endif +#endif diff --git a/code/gamespy/gstats/track/track.cpp b/code/gamespy/gstats/track/track.cpp new file mode 100644 index 00000000..71d228d2 --- /dev/null +++ b/code/gamespy/gstats/track/track.cpp @@ -0,0 +1,74 @@ +// track.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "track.h" +#include "trackDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CTrackApp + +BEGIN_MESSAGE_MAP(CTrackApp, CWinApp) + //{{AFX_MSG_MAP(CTrackApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CTrackApp construction + +CTrackApp::CTrackApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CTrackApp object + +CTrackApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CTrackApp initialization + +BOOL CTrackApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. +/* +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif +*/ + CTrackDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + // TODO: Place code here to handle when the dialog is + // dismissed with OK + } + else if (nResponse == IDCANCEL) + { + // TODO: Place code here to handle when the dialog is + // dismissed with Cancel + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/code/gamespy/gstats/track/track.h b/code/gamespy/gstats/track/track.h new file mode 100644 index 00000000..721cf157 --- /dev/null +++ b/code/gamespy/gstats/track/track.h @@ -0,0 +1,49 @@ +// track.h : main header file for the TRACK application +// + +#if !defined(AFX_TRACK_H__0DF04935_1FD6_4741_AEC5_5A40455C0C7F__INCLUDED_) +#define AFX_TRACK_H__0DF04935_1FD6_4741_AEC5_5A40455C0C7F__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CTrackApp: +// See track.cpp for the implementation of this class +// + +class CTrackApp : public CWinApp +{ +public: + CTrackApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CTrackApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CTrackApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_TRACK_H__0DF04935_1FD6_4741_AEC5_5A40455C0C7F__INCLUDED_) diff --git a/code/gamespy/gstats/track/track.rc b/code/gamespy/gstats/track/track.rc new file mode 100644 index 00000000..e89ea5da --- /dev/null +++ b/code/gamespy/gstats/track/track.rc @@ -0,0 +1,214 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\track.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\track.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_TRACK_DIALOG DIALOGEX 0, 0, 312, 76 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "track" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Exit",IDOK,260,7,50,14 + PUSHBUTTON "Logout",IDC_LOGOUT,260,23,50,14 + CONTROL "Progress1",IDC_PROGRESS,"msctls_progress32",PBS_SMOOTH | + WS_BORDER,15,50,80,14 + LTEXT "Info",IDC_INFO,100,55,60,8 + PUSHBUTTON "Start 100m",IDC_START_100,90,30,65,14 + PUSHBUTTON "Start 50m",IDC_START_50,15,30,65,14 + PUSHBUTTON "Start 200m",IDC_START_200,165,30,65,14 + LTEXT "88.888",IDC_BEST_50,55,10,23,8 + LTEXT "88.888",IDC_BEST_100,130,10,23,8 + LTEXT "88.888",IDC_BEST_200,205,10,23,8 + LTEXT "88.888",IDC_TOP_50,55,20,23,8 + LTEXT "88.888",IDC_TOP_100,130,20,23,8 + LTEXT "88.888",IDC_TOP_200,205,20,23,8 + LTEXT "Best Time:",IDC_STATIC,15,20,34,8 + LTEXT "Your Time:",IDC_STATIC,15,10,35,8 + LTEXT "Best Time:",IDC_STATIC,90,20,34,8 + LTEXT "Your Time:",IDC_STATIC,90,10,35,8 + LTEXT "Best Time:",IDC_STATIC,165,20,34,8 + LTEXT "Your Time:",IDC_STATIC,165,10,35,8 + LTEXT "Use Z && X to race",IDC_STATIC,165,55,75,8 +END + +IDD_LOGIN DIALOG DISCARDABLE 0, 0, 147, 76 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "GameSpy Login" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_EMAIL,51,8,84,12,ES_AUTOHSCROLL + EDITTEXT IDC_NICK,51,23,84,12,ES_AUTOHSCROLL + EDITTEXT IDC_PASSWORD,51,38,84,12,ES_PASSWORD | ES_AUTOHSCROLL + DEFPUSHBUTTON "Login",IDOK,15,55,50,14 + PUSHBUTTON "Cancel",IDCANCEL,80,55,50,14 + LTEXT "email:",IDC_STATIC,10,10,19,8 + LTEXT "nick:",IDC_STATIC,10,25,16,8 + LTEXT "password:",IDC_STATIC,10,40,33,8 +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "track MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "track\0" + VALUE "LegalCopyright", "Copyright (C) 2001\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "track.EXE\0" + VALUE "ProductName", "track Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_TRACK_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 305 + TOPMARGIN, 7 + BOTTOMMARGIN, 69 + END + + IDD_LOGIN, DIALOG + BEGIN + LEFTMARGIN, 7 + TOPMARGIN, 7 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\track.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/gamespy/gstats/track/trackDlg.cpp b/code/gamespy/gstats/track/trackDlg.cpp new file mode 100644 index 00000000..2fc44c42 --- /dev/null +++ b/code/gamespy/gstats/track/trackDlg.cpp @@ -0,0 +1,441 @@ +// trackDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "track.h" +#include "trackDlg.h" + +#include "../gstats.h" +#include "../../common/gsAvailable.h" +#include "../../ghttp/ghttp.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define TIMER_ONE_SECOND 100 +#define TIMER_THINK 101 + +///////////////////////////////////////////////////////////////////////////// +// CTrackDlg dialog + +CTrackDlg::CTrackDlg(CWnd* pParent /*=NULL*/) + : CDialog(CTrackDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CTrackDlg) + m_info = _T("Ready"); + m_best100 = _T(""); + m_best200 = _T(""); + m_best50 = _T(""); + m_top100 = _T(""); + m_top200 = _T(""); + m_top50 = _T(""); + //}}AFX_DATA_INIT + // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); +} + +void CTrackDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CTrackDlg) + DDX_Control(pDX, IDC_PROGRESS, m_progress); + DDX_Text(pDX, IDC_INFO, m_info); + DDX_Text(pDX, IDC_BEST_100, m_best100); + DDX_Text(pDX, IDC_BEST_200, m_best200); + DDX_Text(pDX, IDC_BEST_50, m_best50); + DDX_Text(pDX, IDC_TOP_100, m_top100); + DDX_Text(pDX, IDC_TOP_200, m_top200); + DDX_Text(pDX, IDC_TOP_50, m_top50); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CTrackDlg, CDialog) + //{{AFX_MSG_MAP(CTrackDlg) + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_BN_CLICKED(IDC_LOGOUT, OnLogout) + ON_WM_TIMER() + ON_BN_CLICKED(IDC_START_50, OnStart50) + ON_BN_CLICKED(IDC_START_100, OnStart100) + ON_BN_CLICKED(IDC_START_200, OnStart200) + ON_WM_DESTROY() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CTrackDlg message handlers + +CTrackDlg * Dlg; + +GHTTPBool PlayerBestTimesPageCompleted +( + GHTTPRequest request, + GHTTPResult result, + char * buffer, + GHTTPByteCount bufferLen, + void * param +) +{ + if(result == GHTTPSuccess) + { + int bestTimes[3]; + int rcode; + + // Scan out the best times. + /////////////////////////// + rcode = sscanf(buffer, "%d %d %d", &bestTimes[0], &bestTimes[1], &bestTimes[2]); + if(rcode == 3) + { + Dlg->UpdateData(); + Dlg->m_best50.Format("%0.3f", bestTimes[0] / 1000.0); + Dlg->m_best100.Format("%0.3f", bestTimes[1] / 1000.0); + Dlg->m_best200.Format("%0.3f", bestTimes[2] / 1000.0); + Dlg->UpdateData(FALSE); + } + } + + return GHTTPTrue; +} + +GHTTPBool TopTimePageCompleted +( + GHTTPRequest request, + GHTTPResult result, + char * buffer, + GHTTPByteCount bufferLen, + void * param +) +{ + if(result == GHTTPSuccess) + { + int time = atoi(buffer); + if(time) + { + CString str; + str.Format("%0.3f", time / 1000.0); + + int event = (int)param; + Dlg->UpdateData(); + if(event == 1) + Dlg->m_top50 = str; + else if(event == 2) + Dlg->m_top100 = str; + else if(event == 3) + Dlg->m_top200 = str; + Dlg->UpdateData(FALSE); + } + } + + return GHTTPTrue; +} + +void CTrackDlg::SetupUser() +{ + CString title; + title.Format("track - best time (%s - %s - %d)", m_loginDlg.m_email, m_loginDlg.m_nick, m_loginDlg.m_profile); + SetWindowText(title); + + CString url; + int event; + url.Format("http://sdkdev.gamespy.com/games/st_highscore/web/playertimes.asp?pid=%d", m_loginDlg.m_profile); + ghttpGet(url, GHTTPFalse, PlayerBestTimesPageCompleted, NULL); + for(event = 1 ; event <= 3 ; event++) + { + url.Format("http://sdkdev.gamespy.com/games/st_highscore/web/top_%d.txt", event); + ghttpGet(url, GHTTPFalse, TopTimePageCompleted, (void *)event); + } + + m_start = 0; + m_count = -1; + m_racing = FALSE; + m_event = EVENT_NONE; + m_numSteps = 0; + m_step = NONE; +} + +BOOL CTrackDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + int rcode; + CString str; + + SetIcon(m_hIcon, TRUE); + SetIcon(m_hIcon, FALSE); + + Dlg = this; + + // Use the development system. + ////////////////////////////// + strcpy(StatsServerHostname, "sdkdev.gamespy.com"); + + // Set the gamename and secret key. + /////////////////////////////////// + strcpy(gcd_gamename, "st_highscore"); + gcd_secret_key[0] = 'K'; + gcd_secret_key[1] = 'S'; + gcd_secret_key[2] = '3'; + gcd_secret_key[3] = 'p'; + gcd_secret_key[4] = '2'; + gcd_secret_key[5] = 'Q'; + + // Perform the availability check + ////////////////////////////////////// + GSIACResult aResult = GSIACWaiting; + GSIStartAvailableCheck(gcd_gamename); + while(aResult == GSIACWaiting) + { + aResult = GSIAvailableCheckThink(); + Sleep(50); + } + + // Init the connection to the backend. + ////////////////////////////////////// + rcode = InitStatsConnection(0); + if(rcode != GE_NOERROR) + { + str.Format("Failed to connect to the stats server (%d).", rcode); + MessageBox(str); + PostQuitMessage(1); + return TRUE; + } + + // Login the user (actually just verifying the login). + ////////////////////////////////////////////////////// + if(m_loginDlg.DoModal() != IDOK) + { + PostQuitMessage(1); + return TRUE; + } + + SetTimer(TIMER_ONE_SECOND, 1000, NULL); + SetTimer(TIMER_THINK, 50, NULL); + + SetupUser(); + + return TRUE; +} + +void CTrackDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); + + SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CDialog::OnPaint(); + } +} + +HCURSOR CTrackDlg::OnQueryDragIcon() +{ + return (HCURSOR) m_hIcon; +} + +void CTrackDlg::OnDestroy() +{ + CDialog::OnDestroy(); + + if(IsStatsConnected()) + CloseStatsConnection(); + + ghttpCleanup(); +} + +void CTrackDlg::OnLogout() +{ + ShowWindow(SW_HIDE); + if(m_loginDlg.DoModal() != IDOK) + PostQuitMessage(1); + SetupUser(); + ShowWindow(SW_SHOW); +} + +void CTrackDlg::OnTimer(UINT nIDEvent) +{ + if(nIDEvent == TIMER_ONE_SECOND) + { + if(m_count) + { + UpdateData(TRUE); + + if(m_count == 3) + m_info = "READY"; + else if(m_count == 2) + m_info = "SET"; + else if(m_count == 1) + m_info = "GO"; + + UpdateData(FALSE); + + if(m_count == 1) + { + m_numSteps = 0; + m_step = NONE; + m_racing = TRUE; + m_start = GetTickCount(); + } + + m_count--; + } + } + else if(nIDEvent == TIMER_THINK) + { + ghttpThink(); + } + + CDialog::OnTimer(nIDEvent); +} + +void CTrackDlg::ReportStats(DWORD time) +{ + char response[33]; + + NewGame(1); + BucketStringOp(NULL, "hostname", bo_set, (char *)(LPCSTR)m_loginDlg.m_nick, bl_server, 0); + BucketIntOp(NULL, "event", bo_set, m_event, bl_server, 0); + + NewPlayer(NULL, 0, (char *)(LPCSTR)m_loginDlg.m_nick); + BucketIntOp(NULL, "time", bo_set, time, bl_player, 0); + BucketIntOp(NULL, "pid", bo_set, m_loginDlg.m_profile, bl_player, 0); + GenerateAuth(GetChallenge(NULL), (char *)(LPCSTR)m_loginDlg.m_password, response); + BucketStringOp(NULL, "auth", bo_set, response, bl_player, 0); + + SendGameSnapShot(NULL, NULL, SNAP_FINAL); + FreeGame(NULL); +} + +BOOL CTrackDlg::PreTranslateMessage(MSG* pMsg) +{ + if(pMsg->message == WM_KEYDOWN) + { + int nChar = pMsg->wParam; + if((nChar == 'Z') || (nChar == 'X')) + { + if((pMsg->lParam & 0xFFFF) == 1) + { + CString str; + BOOL stepped = FALSE; + + if((nChar == 'Z') && (m_step != LEFT)) + { + m_step = LEFT; + m_numSteps++; + stepped = TRUE; + } + else if ((nChar == 'X') && (m_step != RIGHT)) + { + m_step = RIGHT; + m_numSteps++; + stepped = TRUE; + } + + if(stepped && m_racing) + { + m_progress.SetPos(m_numSteps); + if(m_numSteps == m_totalSteps) + { + DWORD diff; + + m_racing = FALSE; + diff = (GetTickCount() - m_start); + str.Format("%0.3fs\n", diff / 1000.0); + OutputDebugString(str); + MessageBox(str); + + UpdateData(); + + m_info = "DONE"; + + // Report the stats. + //////////////////// + ReportStats(diff); + + // Update best time(s) if needed. + ///////////////////////////////// + CString * topStr; + CString * bestStr; + DWORD topTime; + DWORD bestTime; + if(m_event == EVENT_50) + { + topStr = &m_top50; + bestStr = &m_best50; + } + else if(m_event == EVENT_100) + { + topStr = &m_top100; + bestStr = &m_best100; + } + else + { + topStr = &m_top200; + bestStr = &m_best200; + } + + topTime = (DWORD)(atof(*topStr) * 1000); + bestTime = (DWORD)(atof(*bestStr) * 1000); + if(!bestTime || (diff < bestTime)) + { + bestStr->Format("%0.3f", diff / 1000.0); + if(diff < topTime) + *topStr = *bestStr; + } + + UpdateData(FALSE); + + m_event = EVENT_NONE; + } + } + } + + return TRUE; + } + } + + return CDialog::PreTranslateMessage(pMsg); +} + +void CTrackDlg::OnStart50() +{ + m_count = 3; + m_totalSteps = RACE_STEPS_50; + m_progress.SetRange(0, m_totalSteps); + m_progress.SetPos(0); + m_event = EVENT_50; +} + +void CTrackDlg::OnStart100() +{ + m_count = 3; + m_totalSteps = RACE_STEPS_100; + m_progress.SetRange(0, m_totalSteps); + m_progress.SetPos(0); + m_event = EVENT_100; +} + +void CTrackDlg::OnStart200() +{ + m_count = 3; + m_totalSteps = RACE_STEPS_200; + m_progress.SetRange(0, m_totalSteps); + m_progress.SetPos(0); + m_event = EVENT_200; +} diff --git a/code/gamespy/gstats/track/trackDlg.h b/code/gamespy/gstats/track/trackDlg.h new file mode 100644 index 00000000..946821dc --- /dev/null +++ b/code/gamespy/gstats/track/trackDlg.h @@ -0,0 +1,90 @@ +// trackDlg.h : header file +// + +#if !defined(AFX_TRACKDLG_H__E16D2398_5E86_47A2_80DA_8F843E24C4A6__INCLUDED_) +#define AFX_TRACKDLG_H__E16D2398_5E86_47A2_80DA_8F843E24C4A6__INCLUDED_ + +#include "LoginDlg.h" + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +///////////////////////////////////////////////////////////////////////////// +// CTrackDlg dialog + +#define NONE -1 +#define LEFT 0 +#define RIGHT 1 + +#define RACE_STEPS_50 60 +#define RACE_STEPS_100 120 +#define RACE_STEPS_200 240 + +#define EVENT_NONE 0 +#define EVENT_50 1 +#define EVENT_100 2 +#define EVENT_200 3 + +class CTrackDlg : public CDialog +{ +// Construction +public: + void SetupUser(); + DWORD m_start; + int m_count; + int m_event; + BOOL m_racing; + int m_numSteps; + int m_step; + int m_totalSteps; + void ReportStats(DWORD time); + + CTrackDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CTrackDlg) + enum { IDD = IDD_TRACK_DIALOG }; + CProgressCtrl m_progress; + CString m_info; + CString m_best100; + CString m_best200; + CString m_best50; + CString m_top100; + CString m_top200; + CString m_top50; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CTrackDlg) + public: + virtual BOOL PreTranslateMessage(MSG* pMsg); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + + CLoginDlg m_loginDlg; + +// Implementation +protected: + HICON m_hIcon; + + // Generated message map functions + //{{AFX_MSG(CTrackDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + afx_msg void OnLogout(); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnStart50(); + afx_msg void OnStart100(); + afx_msg void OnStart200(); + afx_msg void OnDestroy(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_TRACKDLG_H__E16D2398_5E86_47A2_80DA_8F843E24C4A6__INCLUDED_) diff --git a/code/gamespy/gt2/changelog.txt b/code/gamespy/gt2/changelog.txt new file mode 100644 index 00000000..8d6dd70d --- /dev/null +++ b/code/gamespy/gt2/changelog.txt @@ -0,0 +1,144 @@ +Changelog for: GameSpy Transport SDK 2 +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 2.09.00 RMV RELEASE Released to Developer Site +12-07-2007 2.08.03 SAH OTHER Fixed VC6 projects missing dependencies +12-07-2007 2.08.02 SN FIX Added check to sample app for PS3 maximum send size +11-27-2007 2.08.01 SAH CLEANUP Moved extern "c" block below includes to prevent linker errors +08-06-2007 2.08.00 RMV RELEASE Released to Developer Site +07-11-2007 2.07.03 RMV FIX Fixed gt2testc Project files to get rid of Unicode warnings and fixed other compiler warnings +06-07-2007 2.07.02 SN RELEASE Released to Developer Site +04-16-2007 2.07.02 SN CLEANUP Put back a removed calling convention + 2.07.02 SN CLEANUP Added a check to the message handler to pass messages to unrecognized message callback + if a GT2Connection has not been complete. +01-16-2007 2.07.01 DES FEATURE Added X360 support +12-15-2006 2.07.00 MJW RELEASE Released to Developer Site +12-04-2006 2.06.78 SN FIX Added some checks to message size in test application for revolution +12-04-2006 2.06.77 SN FIX Changed the gti2MacToIP and gti2IpToMac functions to be public functions +11-10-2006 2.06.76 JR RELEASE Limited release +10-23-2006 2.06.76 DES RELEASE Limited release +10-05-2006 2.06.76 SAH FIX Updated MacOSX Makefile +09-28-2006 2.06.75 SAH FIX Fixed PS3 project to work with PS3 095.00x SDK; changed included libaries in linker input. +09-07-2006 2.06.74 DES FEATURE Added support for string arrays to gt2Encode + DES FIX Fixed potential to read past the end of a truncated buffer when decoding strings +08-24-2006 2.06.73 SAH FIX Fixed VC7 Project file +08-15-2006 2.06.72 SAH FIX Fixed VDP assertion in gt2main.c (call to gt2Send) +08-04-2006 2.06.71 SAH FIX Changed call in gt2proxy.c from ctime to gsiSecondsToString +08-02-2006 2.06.70 SAH RELEASE Releasing to developer site +07-31-2006 2.06.70 SAH FIX Fixed PS3 project file - added post-build step to create *.SELF for execution +07-25-2006 2.06.69 SAH FIX Fixed NITRO project, include for crt0.o now above others so it add correct file +07-24-2006 2.06.68 SAH FIX Removed #ifdef _PS3 for socket calls (changed platformSocket.h to typecast calls) +07-06-2006 2.06.67 SAH FIX Fixed PSP project file to not explicitly include the PSP linker file +06-30-2006 2.06.66 SAH FIX Fixed NITRO project & linker command file (to work with CW 2.0/NitroSDK 3.1) + SAH FIX Fixed Linux makefile +06-06-2006 2.06.65 MJ FEATURE Added Adhoc Support for PSP. +05-31-2006 2.06.64 SAH RELEASE Releasing to developer site + SAH FIX Fixed Linux makefile +05-30-2006 2.06.63 SAH FIX Fixed PS3 projects to work with PS3(084_001 SDK) +05-25-2006 2.06.62 SAH FIX Changed PSP project warning levels +05-24-2006 2.06.61 SAH FIX Added (socklen_t) typecast for PS3 warnings to gt2socket.c +05-19-2006 2.06.60 SAH FIX Added gsTestMain.c to nitro CodeWarrior project +05-19-2006 2.06.59 SAH FIX Added GS_STATIC_CALLBACK in front of 3 compare functions for __fastcall support +05-15-2006 2.06.58 SAH FIX Added "PS3 Release" configuration to project +05-05-2006 2.06.57 SAH FIX Fixed the gt2action.dsp project - needed to include a few encryption files for http +04-27-2006 2.06.56 MJ FEATURE Added gtEncodedMessageTypeSet +04-25-2006 2.06.55 SAH RELEASE Releasing to developer site +04-24-2006 2.06.55 SAH FIX Added function prototypes, fixed/added typecasts and Nitro project files +04-20-2006 2.06.54 SAH FIX Commented out unused variables, break; statements + FIX Commented out unncessary checks (if unsigned int > 0) +04-20-2006 2.06.53 SAH FIX Added _PS3 wrapper typecast for socket calls +04-14-2006 2.06.52 SAH FIX few more typecasts in gt2memcpy calls (forgot to do PC ones) +04-14-2006 2.06.51 SAH FIX Added some typecasts in gt2memcpy calls to get rid of warnings +01-27-2006 2.05.50 SN RELEASE Releasing to developer site +01-27-2006 2.05.50 SN FIX Updated test_main define for PSP + SN FIX PSP and PS2 only use messages smaller than 8K + SN OTHER Added psp prodg project and solution to sgv +01-26-2006 2.05.49 SN FIX Force gt2IPToHostInfo to return NULL for PSP since PSP doesn't have a gethostbyaddr +01-11-2006 2.05.49 SN FIX Added platform checks to socket error handlers + SN FIX Added visual studio .net project and solution +01-11-2006 2.05.48 SN FIX updated callling convention to only be WIN32 +12-20-2005 2.05.47 SN OTHER Cleaned up and added missing common code to projects if needed +12-20-2005 2.05.46 SN FIX Fixed VDP specific changes that caused messages to be truncated + Removed VDP checks in parts of code that previously had them +12-19-2005 2.05.45 SN FIX Added preprocessor directive around code creating a VDP socket + Changed buffer offset to use a constant +12-07-2005 2.05.44 DDW FEATURE Added Win64 build support +12-06-2005 2.05.43 SN FEATURE Added Xbox VDP support +11-17-2005 2.05.42 DES FIX Updated Nitro Makefile. +11-14-2005 2.05.41 DES FIX Updated the OSX Makefile. +10-11-2005 2.05.40 SN FIX Updated gt2test dsp to have new common code files gsassert and gsmemory +09-21-2005 2.05.39 DES FEATURE Updated DS support + DES FIX Updated to handle additional UDP sending errors + DES FIX Updated OSX Makefile +09-12-2005 2.05.38 SN FIX Fixed unhandled socket error code +07-28-2005 2.05.37 SN RELEASE Releasing to developer site. +07-28-2005 2.05.37 SN FIX Changed IP address to use local host for gt2testc +06-03-2005 2.05.36 SN RELEASE Releasing to developer site. +05-05-2005 2.05.36 BED FIX Updated project files to use new common folder. +05-04-2005 2.05.35 SN FIX Changes for XBox platform +05-03-2005 2.05.34 SN FIX Removed deprecated MFC code for VS .NET project + And fixed implicit cast performed in a callback + SN OTHER Created Visual Studio .NET projects +04-28-2005 2.05.33 SN RELEASE Releasing to developer site. +04-27-2005 2.05.33 DES RELEASE Limited release to Nintendo DS developers. +04-25-2005 2.05.33 DES FIX Limit the Nitro to a 1500 byte message buffer. + DES CLEANUP Disable Win32 linker warning. +04-19-2005 2.05.32 DES FEATURE Added the ability to send raw UDP datagrams through a GT2Socket. + This feature can also be used to send broadcast datagrams. +04-15-2005 2.05.31 DES FIX XBox fix. +04-08-2005 2.05.30 DES FEATURE Changes for XBox support. +04-04-2005 2.05.29 SN RELEASE Release to developer site. +03-14-2005 2.05.29 DES FIX Fixed buffering multiple copies of incoming reliable messages + FIX Fixed detection of incoming reliable message buffer overflow +03-14-2005 2.05.28 DES FEATURE Nintendo DS support +01-27-2003 2.05.27 DES FIX Fixed custom SN sendto and moved it to nonport +01-03-2005 2.05.26 SN FIX Added const qualifiers to unmodified formal function parameters +09-23-2004 2.05.25 DES FIX Changed IPs that were using unsigned long to unsigned int. + DES FIX Changed times that were using unsigned long to gsi_time. + DES FIX Sockets now correctly bind to a provided local IP when created. +09-17-2004 2.05.24 DES FEATURE Added functions for confirming receipt of reliable messages. +09-16-2004 2.05.23 SN RELEASE Releasing to developer site. +08-27-2004 2.05.23 DES CLEANUP General Unicode cleanup + DES CLEANUP Removed MacOS style includes + DES CLEANUP Updated Win32 project configurations + DES CLEANUP Fixed warnings under OSX + DES FEATURE Added OSX Makefile +08-25-2004 2.05.22 DES FEATURE Added OSX makefile +08-16-2004 2.05.22 SN FEATURE Updated gt2hostmig and gt2nat to use QR2 instead of legacy Q & R +08-05-2004 2.05.21 SN RELEASE Releasing to developer site. +07-20-2004 2.05.21 SN FIX Updated code with explicit casts to remove implicit cast error + when compiling at highest level and warnings treated as errors. +06-18-2004 2.05.20 BED RELEASE Releasing to developer site. +06-18-2004 2.05.20 DES FIX Checking for trying to connect to an invalid IP range. +10-21-2003 2.05.19 BED RELEASE Releasing to developer site. (UNIQUE NICK AND UNICODE SUPPORT) +10-21-2003 2.05.19 BED FIX Added #ifdef because SN_SYSTEMS doesn't support EHOSTDOWN +10-09-2003 2.05.18 BED FIX Switched to gsi_time type instead of unsinged long for PS2 compatibility +07-25-2003 2.05.17 DES FIX Ignoring EHOSTDOWN sendto error. +07-24-2003 2.05.16 DES RELEASE Releasing to developer site. +07-18-2003 2.05.16 BED FEATURE Added CodeWarrior (PS2) sample project file. + BED CLEANUP General cleanup to remove CodeWarrior warnings. +07-17-2003 2.05.15 DES CLEANUP Cleaned up the PS2 Makefile, it now uses Makefile.commmon. +07-16-2003 2.05.14 DES FIX Changed a few __mips64 checks to _PS2 checks. + BED FEATURE Added ProDG sample project files. +07-11-2003 2.05.13 DES RELEASE Releasing to developer site. +05-12-2003 2.05.13 DES FIX Fixed a crashing bug that could occur on socket errors. +05-09-2003 2.05.12 DES CLEANUP Removed Dreamcast support. +03-24-2003 2.05.11 DES FIX Fixed assumption that messages passed to callbacks were always NUL terminated. +03-03-2003 2.05.10 DES CLEANUP General cleanup to remove warnings. +02-05-2003 2.05.09 DES RELEASE Releasing to developer site. +02-05-2003 2.05.09 DES CLEANUP Switched to use the common code CanReceiveOnSocket and CanSendOnSocket. +12-19-2002 2.05.08 DES RELEASE Releasing to developer site. +12-19-2002 2.05.08 DES CLEANUP Removed assert.h includes. +12-16-2002 2.05.07 DES OTHER Moved handling of SN Systems lack of support for gethostbyaddr() to nonport. + CLEANUP Removed call to GOAClearSocketError. +12-13-2002 2.05.06 DES FEATURE Added PS2 eenet stack support. +11-22-2002 2.05.05 DES RELEASE Releasing to developer site. +11-20-2002 2.05.05 DES CLEANUP Cleaned up code to remove PS2 compiler warnings. +11-18-2002 2.05.04 DES RELEASE Release to developer site. +11-18-2002 2.05.04 DES FIX Fixed alignment bug in gtEncodedMessageType. +11-14-2002 2.05.03 DES OTHER Removed BLOCKING_SOCKETS define from PS2 makefile, leftover from GT +11-14-2002 2.05.02 DES FIX Fixed gt2testc to stop sending messages at the correct time +11-13-2002 2.05.01 DES FIX Resends an ack if a duplicate message is received +09-25-2002 2.05.00 DDW OTHER Changelog started diff --git a/code/gamespy/gt2/gt2.h b/code/gamespy/gt2/gt2.h new file mode 100644 index 00000000..5ef96914 --- /dev/null +++ b/code/gamespy/gt2/gt2.h @@ -0,0 +1,664 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/**************************** +** GameSpy Transport SDK 2 ** +****************************/ + +/* +** see "configurable defines" in gt2Main.h for certain performance settings that can be changed +*/ + +#ifndef _GT2_H_ +#define _GT2_H_ + +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/********** +** TYPES ** +**********/ + +// boolean +typedef int GT2Bool; +#define GT2False 0 +#define GT2True 1 + +// a byte +typedef unsigned char GT2Byte; + +// a handle to a socket object (can be used to accept connections and initiate connections) +typedef struct GTI2Socket * GT2Socket; + +// a handle to an object representing a connection to a specific IP and port +// the local endpoint is a GT2Socket +typedef struct GTI2Connection * GT2Connection; + +// the id of a reliably sent message +// unreliable messages don't have ids +typedef unsigned short GT2MessageID; + +// the result of a GT2 operation +// check individual function definitions to see possible results +// TODO: list possible results wherever this is used +typedef enum +{ + GT2Success, // success + // errors: + GT2OutOfMemory, // ran out of memory + GT2Rejected, // attempt rejected + GT2NetworkError, // networking error (could be local or remote) + GT2AddressError, // invalid or unreachable address + GT2DuplicateAddress, // a connection was attempted to an address that already has a connection on the socket + GT2TimedOut, // time out reached + GT2NegotiationError, // there was an error negotiating with the remote side + GT2InvalidConnection, // the connection didn't exist + GT2InvalidMessage, // used for vdp reliable messages containing voice data, no voice data in reliable messages + GT2SendFailed // the send failed, +} GT2Result; + +// possible states for any GT2Connection +typedef enum +{ + GT2Connecting, // negotiating the connection + GT2Connected, // the connection is active + GT2Closing, // the connection is being closed + GT2Closed // the connection has been closed and can no longer be used +} GT2ConnectionState; + +// The cause of the connection being closed. +typedef enum +{ + GT2LocalClose, // The connection was closed with gt2CloseConnection. + GT2RemoteClose, // The connection was closed remotely. + // errors: + GT2CommunicationError, // An invalid message was received (it was either unexpected or wrongly formatted). + GT2SocketError, // An error with the socket forced the connection to close. + GT2NotEnoughMemory // There wasn't enough memory to store an incoming or outgoing message. +} GT2CloseReason; + +/************ +** GLOBALS ** +************/ + +// The challenge key is a 32 character string +// that is used in the authentication process. +// The key can be set before GT2 is used so +// that the key will be application-specific. +extern char GT2ChallengeKey[33]; + +/********************* +** SOCKET CALLBACKS ** +*********************/ + +// this callback gets called when there was is an error that forces a socket to close +// all connections that use this socket are terminated, and their gt2CloseCallback callbacks +// will be called before this callback is called (with the reason set to GT2SocketError). +// the socket cannot be used again after this callback returns +typedef void (* gt2SocketErrorCallback) +( + GT2Socket socket +); + +/********************* +** SOCKET FUNCTIONS ** +*********************/ + +// creates a local socket +// if the IP of the local address is 0, then any/all ips will be bound. +// if the port of the local address is 0, then a port will be assigned. +// if either buffer sizes is set to 0, a default value will be used (currently 64K for PC, 4k for Xbox). +// the buffer needs to be able to hold all messages waiting for confirmation of delivery, +// and it needs to hold any messages that arrive out of order. if either buffer runs out +// of space the connection will be dropped. +GT2Result gt2CreateSocket +( + GT2Socket * socket, // if the result is GT2Success, the socket object handle will be stored at this address + const char * localAddress, // the local address to bind to + int outgoingBufferSize, // size of per-connection buffer where sent messages waiting to be confirmed are held, use 0 for default + int incomingBufferSize, // size of per-connection buffer where out-of-order received messages are held, use 0 for default + gt2SocketErrorCallback callback // a callback that is called if there is an error with the socket +); + +// AdHoc Sockets use MAC address instead of IP address. +GT2Result gt2CreateAdHocSocket +( + GT2Socket * socket, // if the result is GT2Success, the socket object handle will be stored at this address + const char * localAddress, // the local address to bind to + int outgoingBufferSize, // size of per-connection buffer where sent messages waiting to be confirmed are held, use 0 for default + int incomingBufferSize, // size of per-connection buffer where out-of-order received messages are held, use 0 for default + gt2SocketErrorCallback callback // a callback that is called if there is an error with the socket +); + +#ifdef _XBOX +// creates a local VDP socket on the Xbox platform +// if the IP of the local address is 0, then any/all ips will be bound. +// if the port of the local address is 0, then a port will be assigned. +// if either buffer sizes is set to 0, a default value will be used (currently 4K). +// the buffer needs to be able to hold all messages waiting for confirmation of delivery, +// and it needs to hold any messages that arrive out of order. if either buffer runs out +// of space the connection will be dropped. +GT2Result gt2CreateVDPSocket +( + GT2Socket * socket, // if the result is GT2Success, the socket object handle will be stored at this address + const char * localAddress, // the local address to bind to + int outgoingBufferSize, // size of per-connection buffer where sent messages waiting to be confirmed are held, use 0 for default + int incomingBufferSize, // size of per-connection buffer where out-of-order received messages are held, use 0 for default + gt2SocketErrorCallback callback // a callback that is called if there is an error with the socket +); +#endif + +// closes a local socket. +// all existing connections will be hard closed, as if gt2CloseAllConnectionsHard was +// called for this socket. all connections send a close message to the remote side, +// and any closed callbacks will be called from within this function +void gt2CloseSocket(GT2Socket socket); + +// processes a socket (and all associated connections) +void gt2Think(GT2Socket socket); + +// sends a raw UDP datagram through the socket +// this function bypasses the normal connection logic +// note that all messages sent this way will be unreliable +// to broadcast a datagram, omit the IP from the remoteAddress (e.g., ":12345") +GT2Result gt2SendRawUDP +( + GT2Socket socket, // the socket through which to send the raw UDP datagram + const char * remoteAddress, // the address to which to send the datagram + const GT2Byte * message, // the message to send, or NULL for an empty datagram + int len // the len of the message (0 for an empty message, ignored if message==NULL) +); + +/************************* +** CONNECTION CALLBACKS ** +*************************/ + +// Called when the connect has completed. +// If the result is GT2Rejected, +// then message is the message that the +// listener passed to gt2Reject. If the +// result is anything else, then message +// is NULL and len is 0. +typedef void (* gt2ConnectedCallback) +( + GT2Connection connection, // The connection object. + GT2Result result, // Result from connect attempt. + GT2Byte * message, // If result==GT2Rejected, the reason. Otherwise, NULL. + int len // If result==GT2Rejected, the length of the reason. Otherwise, 0. +); + +// Called when a message is received. +typedef void (* gt2ReceivedCallback) +( + GT2Connection connection, // The connection the message was received on. + GT2Byte * message, // The message that was received. Will be NULL if an empty message. + int len, // The length of the message in bytes. Will be 0 if an empty message. + GT2Bool reliable // True if this is was sent reliably. +); + +// Called when the connection is closed (remotely or locally). +// The connection can no longer be used after this callback returns. +typedef void (* gt2ClosedCallback) +( + GT2Connection connection, // The connection that was closed. + GT2CloseReason reason // The reason the connection was closed. +); + +// When a reply is received for a ping that was sent, this callback is called. +// The latency reported here is the amount of time between when the ping +// was first sent with gt2Ping and when the pong was received. +typedef void (* gt2PingCallback) +( + GT2Connection connection, // the connection the ping was sent on + int latency // the round-trip time for the ping, in milliseconds +); + +// Callbacks set for each connection. +// The connected callback is ignored +// when this is passed to gt2Accept. +typedef struct +{ + gt2ConnectedCallback connected; // Called when gt2Connect is complete. + gt2ReceivedCallback received; // Called when a message is received. + gt2ClosedCallback closed; // Called when the connection is closed (remotely or locally). + gt2PingCallback ping; // Called when a ping reply is received. +} GT2ConnectionCallbacks; + +/************************* +** CONNECTION FUNCTIONS ** +*************************/ + +// initiates a connection between a local socket and a remote socket +// if blocking is true, the return value signals the connection result: +// GT2Success means the connect attempt succeeded +// anything else means it failed +// if blocking is false, the return value signals the current status of the attempt +// GT2Success means the connection is being attempted +// anything else means there was an error and the connection attempt has been aborted +GT2Result gt2Connect +( + GT2Socket socket, // the local socket to use for the connection + GT2Connection * connection, // if the result is GT2Success, and blocking is false, the connection object handle is stored here + const char * remoteAddress, // the address to connect to + const GT2Byte * message, // an optional initial message (may be NULL) + int len, // length of the initial message (may be 0, or -1 for strlen) + int timeout, // timeout in milliseconds (may be 0 for infinite retries) + GT2ConnectionCallbacks * callbacks, // callbacks for connection related stuff + GT2Bool blocking // if true, don't return until complete (successfuly or unsuccessfuly) +); + +// sends data reliably or unreliably +// reliable messages are guaranteed to arrive, arrive in order, and arrive only once. +// unreliable messages are not guaranteed to arrive, arrive in order, or arrive only once. +// because messages may be held in the outgoing buffer (even unreliable messages may need +// to be put in the buffer), the message size cannot exceed +GT2Result gt2Send +( + GT2Connection connection, // the connection to send the message on + const GT2Byte * message, // the message to send, or NULL for an empty message0 + int len, // the len of the message (0 for an empty message, ignored if message==NULL) + GT2Bool reliable // if true, send the message reliably +); + +// sends a ping on a connection in an attempt to determine latency +// the ping is unreliable, and either it or the pong sent in reply +// could be dropped (resulting in the callback never being called), +// or it could even arrive multiple times (resulting in multiple +// calls to the callback). +void gt2Ping(GT2Connection connection); + +// starts an attempt to close the connection +// when the close is completed, the connection's closed callback will be called +void gt2CloseConnection(GT2Connection connection); + +// same as gt2CloseConnection, but doesn't wait for confirmation from the remote side of the connection +// the closed callback will be called from within this function +void gt2CloseConnectionHard(GT2Connection connection); + +// closes all of a socket's connections (essentially calls gt2CloseConnection on each of them). +void gt2CloseAllConnections(GT2Socket socket); + +// same as gt2CloseAllConnections, but does a hard close +// any closed callbacks will be called from within this function +void gt2CloseAllConnectionsHard(GT2Socket socket); + +/********************* +** LISTEN CALLBACKS ** +*********************/ + +// callback gets called when someone attempts to connect to a socket that is listening for new connections. +// in response to this callback the application should call gt2Accept or gt2Reject. they do not need +// to be called from inside the callback, however they should be called in a timely manner so that the +// remote side does not need to sit around indefinitely waiting for a response. +// the latency is an estimate of the round trip time between connections. +typedef void (* gt2ConnectAttemptCallback) +( + GT2Socket socket, // the socket the attempt came in on + GT2Connection connection, // a connection object for the incoming connection attempt + unsigned int ip, // the IP being used remotely for the connection attempt + unsigned short port, // the port being used remotely for the connection attempt + int latency, // the approximate latency on the connection + GT2Byte * message, // an optional message sent with the attempt. Will be NULL if an empty message. + int len // the length of the message, in characters. Will be 0 if an empty message. +); + +/********************* +** LISTEN FUNCTIONS ** +*********************/ + +// tells a socket to start listening for incoming connections +// any connections attempts will cause the callback to be called +// if the socket is already listening, this callback will replace the exsiting callback being used +// if the callback is NULL, this will cause the connection to stop listening +void gt2Listen(GT2Socket socket, gt2ConnectAttemptCallback callback); + +// after a socket's gt2ConnectAttemptCallback has been called, this function can be used to accept +// the incoming connection attempt. it can be called from either within the callback or some later time. +// as soon as it is called the connection is active, and messages can be sent and received. the remote side +// of the connection will have it's connected callback called with the result set to GT2Success. the callbacks +// that are passed in to this function are the same callbacks that get passed to gt2Connect, with the exception +// that the connected callback can be ignored, as the connection is already established. +// if this function returns GT2True, then the connection has been successfully accepted. if it returns +// GT2False, then the remote side has already closed the connection attempt. in that case, the connection +// is considered closed, and it cannot be referenced again. +GT2Bool gt2Accept(GT2Connection connection, GT2ConnectionCallbacks * callbacks); + +// after a socket's gt2ConnectAttemptCallback has been called, this function can be used to reject +// the incoming connection attempt. it can be called from either within the callback or some later time. +// once the function is called the connection is considered closed and cannot be referenced again. the remote +// side attempting the connection will have its connected callback called with the result set to GT2Rejected. +// if the message is not NULL and the len is not 0, the message will be sent with the rejection, and passed +// into the remote side's connected callback. +void gt2Reject(GT2Connection connection, const GT2Byte * message, int len); + +/************************* +** MESSAGE CONFIRMATION ** +*************************/ +// gets the message id for the last reliably sent message. unreliable messages do not have ids. +// this should be called immediately after gt2Send. waiting until after a call to gt2Think can result in +// an invalid message id being returned. +// note that the use of filters that can either drop or delay messages can complicate the process, because +// in those cases a call to gt2Send does not guarantee that a message will actually be sent. in those cases, +// gt2GetLastSentMessageID should be called after gt2FilteredSend, because the actual message will be sent +// from within that function. +GT2MessageID gt2GetLastSentMessageID(GT2Connection connection); + +// returns true if confirmation was received locally that the reliable message represented by the message id +// was received by the remote end of the connection. returns false if confirmation was not yet received. +// this should only be called on message ids that were returned by gt2GetLastSendMessageID, and should be +// used relatively soon after the message was sent, due to message ids wrapping around after a period of time. +GT2Bool gt2WasMessageIDConfirmed(GT2Connection connection, GT2MessageID messageID); + +/********************* +** FILTER CALLBACKS ** +*********************/ + +// Callback for filtering outgoing data. +// Call gt2FilteredSend with the filtered data, either from within the callback or later. +// the message points to the same memory location as the message passed to gt2Send (or gt2FilteredSend). +// so if the call to gt2FilteredSend is delayed, it is the filter's responsibility to make sure the +// data is still around when and if it is needed. +typedef void (* gt2SendFilterCallback) +( + GT2Connection connection, // The connection on which the message is being sent. + int filterID, // Pass this ID to gt2FilteredSend. + const GT2Byte * message, // The message being sent. Will be NULL if an empty message. + int len, // The length of the message being sent, in bytes. Will be 0 if an empty message. + GT2Bool reliable // If the message is being sent reliably. +); + +// Callback for filtering incoming data. +// Call gt2FilteredRecieve with the filtered data, +// either from within the callback or later. +// the message may point to a memory location supplied to gt2FilteredReceive by a previous filter. +// so if this filter's call to gt2FilteredReceive is delayed, it is the filter's responsibility +// to make sure the data is still around when and if it is needed. +typedef void (* gt2ReceiveFilterCallback) +( + GT2Connection connection, // The connection the message was received on. + int filterID, // Pass this ID to gtFilteredReceive. + GT2Byte * message, // The message that was received. Will be NULL if an empty message. + int len, // The length of the message in bytes. Will be 0 if an empty message. + GT2Bool reliable // True if this is a reliable message. +); + +/********************* +** FILTER FUNCTIONS ** +*********************/ + +// Adds a filter to the connection's outgoing data. +// Returns GT2False if there was an error adding the filter (due to no free memory) +GT2Bool gt2AddSendFilter +( + GT2Connection connection, // The connection on which to add the filter. + gt2SendFilterCallback callback // The callback the outgoing data is filtered through. +); + +// Removes a filter from the connection's outgoing data. +// if callback is NULL, all send filters are removed +void gt2RemoveSendFilter +( + GT2Connection connection, // The connection on which to remove the filter. + gt2SendFilterCallback callback // The callback to remove. +); + +// Called in response to a gt2SendFilterCallback being called. +// It can be called from within the callback, or at any later time. +void gt2FilteredSend +( + GT2Connection connection, // The connection on which the message is being sent. + int filterID, // The ID passed to the gt2SendFilterCallback. + const GT2Byte * message, // The message being sent. May be NULL. + int len, // The lengt2h of the message being sent, in bytes. May be 0 or -1. + GT2Bool reliable // If the message should be sent reliably. +); + +// Adds a filter to the connection's incoming data. +// Returns GT2False if there was an error adding the filter (due to no free memory) +GT2Bool gt2AddReceiveFilter +( + GT2Connection connection, // The connection on which to add the filter. + gt2ReceiveFilterCallback callback // The callback the incoming data is filtered through. +); + +// Removes a filter from the connection's incoming data. +// if callback is NULL, all receive filters are removed +void gt2RemoveReceiveFilter +( + GT2Connection connection, // The connection on which to remove the filter. + gt2ReceiveFilterCallback callback // The callback to remove. +); + +// Called in response to a gt2ReceiveFilterCallback being called. +// It can be called from within the callback, or at any later time. +void gt2FilteredReceive +( + GT2Connection connection, // The connection the message was received on. + int filterID, // The ID passed to the gt2ReceiveFilterCallback. + GT2Byte * message, // The message that was received. May be NULL. + int len, // The lengt2h of the message in bytes. May be 0. + GT2Bool reliable // True if this is a reliable message. +); + +/***************************** +** SOCKET SHARING CALLBACKS ** +*****************************/ + +// this callback gets called when the sock receives a message that it cannot match to an existing +// connection. if the callback recognizes the message and handles it, it should return GT2True, which +// will tell the socket to ignore the message. if the callback does not recognize the message, it +// should return GT2False, which tells the socket to let the other side know there is no connection. +typedef GT2Bool (* gt2UnrecognizedMessageCallback) +( + GT2Socket socket, // the socket the message was received on + unsigned int ip, // the ip of the remote machine the message came from (in network byte order) + unsigned short port, // the port on the remote machine (in host byte order) + GT2Byte * message, // the message (may be NULL for an empty message) + int len // the length of the message (may be 0) +); + +/***************************** +** SOCKET SHARING FUNCTIONS ** +*****************************/ + +// this function returns the actual underlying socket for a GT2Socket. +// this can be used for socket sharing purposes, along with the gt2UnrecognizedMessageCallback. +SOCKET gt2GetSocketSOCKET(GT2Socket socket); + +// sets a callback that all unrecognized messages are passed to. an unrecognized message is one +// that can't be matched up to a specific connection. if the callback handles the message, it +// returns true, and the GT2Socket ignores the message. if the callback does not recognize the message, +// it returns false, and the socket handles the message (by sending a message back indicating the connection +// is closed). if the callback is NULL, it removes any previously set callback. +void gt2SetUnrecognizedMessageCallback(GT2Socket socket, gt2UnrecognizedMessageCallback callback); + +/******************* +** INFO FUNCTIONS ** +*******************/ + +// gets the socket this connection exists on +GT2Socket gt2GetConnectionSocket(GT2Connection connection); + +// gets the connection's connection state +// GT2Connecting - the connection is still being negotiated +// GT2Connected - the connection is active (has successfully connected, and not yet closed) +// GT2Closing - the connection is in the process of closing (i.e., sent a close message and waiting for confirmation). +// GT2Closed - the connection has already been closed and will soon be freed +GT2ConnectionState gt2GetConnectionState(GT2Connection connection); + +// gets a connection's remote IP (in network byte order) +unsigned int gt2GetRemoteIP(GT2Connection connection); + +// gets a connection's remote port (in host byte order) +unsigned short gt2GetRemotePort(GT2Connection connection); + +// gets a socket's local IP (in network byte order) +unsigned int gt2GetLocalIP(GT2Socket socket); + +// gets a socket's local port (in host byte order) +unsigned short gt2GetLocalPort(GT2Socket socket); + +// gets the total size of the connection's incoming buffer. +int gt2GetIncomingBufferSize(GT2Connection connection); + +// gets the amount of available space in the connection's incoming buffer. +int gt2GetIncomingBufferFreeSpace(GT2Connection connection); + +// gets the total size of the connection's outgoing buffer. +int gt2GetOutgoingBufferSize(GT2Connection connection); + +// gets the amount of available space in the connection's outgoing buffer. +int gt2GetOutgoingBufferFreeSpace(GT2Connection connection); + +/************************ +** USER DATA FUNCTIONS ** +************************/ + +void gt2SetSocketData(GT2Socket socket, void * data); +void * gt2GetSocketData(GT2Socket socket); +void gt2SetConnectionData(GT2Connection connection, void * data); +void * gt2GetConnectionData(GT2Connection connection); + +/************************* +** BYTE ORDER FUNCTIONS ** +*************************/ + +unsigned int gt2NetworkToHostInt(unsigned int i); +unsigned int gt2HostToNetworkInt(unsigned int i); +unsigned short gt2HostToNetworkShort(unsigned short s); +unsigned short gt2NetworkToHostShort(unsigned short s); + +/********************** +** ADDRESS FUNCTIONS ** +**********************/ + +// Converts an IP and a port into a text string. The IP must be in network byte order, and the port +// in host byte order. The string must be able to hold at least 22 characters (including the NUL). +// "XXX.XXX.XXX.XXX:XXXXX" +// If both the IP and port are non-zero, the string will be of the form "1.2.3.4:5" (":"). +// If the port is zero, and the IP is non-zero, the string will be of the form "1.2.3.4" (""). +// If the IP is zero, and the port is non-zero, the string will be of the form ":5" (":"). +// If both the IP and port are zero, the string will be an empty string ("") +// The string is returned. If the string paramater is NULL, then an internal static string will be +// used. There are two internal strings that are alternated between. +const char * gt2AddressToString +( + unsigned int ip, // IP in network byte order. Can be 0. + unsigned short port, // Port in host byte order. Can be 0. + char string[22] // String will be placed in here. Can be NULL. +); + +// Converts a string address into an IP and a port. The IP is stored in network byte order, and the port +// is stored in host byte order. Returns false if there was an error parsing the string, or if a supplied +// hostname can't be resolved. +// Possible string forms: +// NULL => all IPs, any port (localAddress only). +// "" => all IPs, any port (localAddress only). +// "1.2.3.4" => 1.2.3.4 IP, any port (localAddress only). +// "host.com" => host.com's IP, any port (localAddress only). +// ":2786" => all IPs, 2786 port (localAddress only). +// "1.2.3.4:0" => 1.2.3.4 IP, any port (localAddress only). +// "host.com:0" => host.com's IP, any port (localAddress only). +// "0.0.0.0:2786" => all IPs, 2786 port (localAddress only). +// "1.2.3.4:2786" => 1.2.3.4 IP, 2786 port (localAddress or remoteAddress). +// "host.com:2786" => host.com's IP, 2786 port (localAddress or remoteAddress). +// If this function needs to resolve a hostname ("host.com") it may need to contact a DNS server, which can +// cause the function to block for an indefinite period of time. Usually it is < 2 seconds, but on certain +// systems, and under certain circumstances, it can take 30 seconds or longer. +GT2Bool gt2StringToAddress +( + const char * string, // The string to convert. + unsigned int * ip, // The IP is stored here, in network byte order. Can be NULL. + unsigned short * port // The port is stored here, in host byte order. Can be NULL. +); + +// Gets the host information for a machine on the Internet. The first version takes an IP in network byte order, +// and the second version takes a string that is either a dotted ip ("1.2.3.4"), or a hostname ("www.gamespy.com"). +// If the function can successfully lookup the host's info, the host's main hostname will be returned. If it +// cannot find the host's info, it returns NULL. +// For the aliases parameter, pass in a pointer to a variable of type (char **). If this parameter is not NULL, +// and the function succeeds, the variable will point to a NULL-terminated list of alternate names for the host. +// For the ips parameter, pass in a pointer to a variable of type (int **). If this parameter is not NULL, and +// the function succeeds, the variable will point to a NULL-terminated list of altername IPs for the host. Each +// element in the list is actually a pointer to an unsigned int, which is an IP address in network byte order. +// The return value, aliases, and IPs all point to an internal data structure, and none of these values should +// be modified directly. Also, the data is only valid until another call needs to use the same data structure +// (virtually ever internet address function will use this data structure). If the data will be needed in the +// future, it should be copied off. +// If this function needs to resolve a hostname ("host.com") it may need to contact a DNS server, which can +// cause the function to block for an indefinite period of time. Usually it is < 2 seconds, but on certain +// systems, and under certain circumstances, it can take 30 seconds or longer. +const char * gt2IPToHostInfo(unsigned int ip, char *** aliases, unsigned int *** ips); +const char * gt2StringToHostInfo(const char * string, char *** aliases, unsigned int *** ips); + +// The following functions are shortcuts for the above two functions (gt2*ToHostInfo()), and each performs a subset +// of the functionality. They are provided so that code that only needs certain information can be a little simpler. +// Before using these, read the comments for the gt2*ToHostInfo() functions, as the info also applies to these functions. +const char * gt2IPToHostname(unsigned int ip); +const char * gt2StringToHostname(const char * string); +char ** gt2IPToAliases(unsigned int ip); +char ** gt2StringToAliases(const char * string); +unsigned int ** gt2IPToIPs(unsigned int ip); +unsigned int ** gt2StringToIPs(const char * string); + +#ifdef _XBOX +unsigned int gt2XnAddrToIP(XNADDR theAddr, XNKID theKeyId); +GT2Bool gt2IPToXnAddr(int ip, XNADDR *theAddr, XNKID *theKeyId); +#endif + +// these are for getting around adhoc which requires a 48 bit address v.s. a 32 bit inet address +void gt2IpToMac(gsi_u32 ip,char *mac); +// change IP address to mac ethernet +gsi_u32 gt2MacToIp(const char *mac); +// change mac ethernet to IP address + +/******************* +** DUMP CALLBACKS ** +*******************/ + +// called with either sent or received data +// trying to send a message from within the send dump callback, or letting the socket think from within the receive +// dump callback can cause serious problems, and should not be done. +typedef void (* gt2DumpCallback) +( + GT2Socket socket, // the socket the message was on + GT2Connection connection, // the connection the message was on, or NULL if there is no connection for this message + unsigned int ip, // the remote ip, in network byte order + unsigned short port, // the remote port, in host byte order + GT2Bool reset, // if true, the connection has been reset (only used by the receive callback) + const GT2Byte * message, // the message (should not be modified) + int len // the length of the message +); + +/******************* +** DUMP FUNCTIONS ** +*******************/ + +// sets a callback to be called whenever a UDP datagram is sent or received, and when a connection reset is received. +// pass in a callback of NULL to remove the callback. the dumps sit at a lower level than the filters, and allow an +// app to keep an eye on exactly what datagrams are being sent and received, allowing for close monitoring. however +// the dumps cannot be used to modify data, only monitor it. the dumps are useful for debugging purposes, and +// to keep track of data send and receive rates (e.g., the Quake 3 engine's netgraph). +// note that these are the actual UDP datagrams being sent and received - datagrams may be dropped, repeated, or +// out-of-order. control datagrams (those used internally by the protocol) will be passed to the dump callbacks, +// and certain application messages will have a header at the beginning. +void gt2SetSendDump(GT2Socket socket, gt2DumpCallback callback); +void gt2SetReceiveDump(GT2Socket socket, gt2DumpCallback callback); + + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/gt2/gt2Auth.c b/code/gamespy/gt2/gt2Auth.c new file mode 100644 index 00000000..6594e042 --- /dev/null +++ b/code/gamespy/gt2/gt2Auth.c @@ -0,0 +1,102 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Main.h" +#include "gt2Auth.h" +#include + +#define CALCULATEODDMODE(buffer, i, oddmode) ((buffer[i-1] & 1) ^ (i & 1) ^ oddmode ^ (buffer[0] & 1) ^ ((buffer[0] < 79) ? 1 : 0) ^ ((buffer[i-1] < buffer[0]) ? 1 : 0)); + +char GT2ChallengeKey[33] = "3b8dd8995f7c40a9a5c5b7dd5b481341"; + +static int gti2VerifyChallenge(const GT2Byte *buffer) +{ + int oddmode = 0; + int i; + for (i = 1; i < GTI2_CHALLENGE_LEN ; i++) + { + oddmode = CALCULATEODDMODE(buffer,i, oddmode); + if ((oddmode && (buffer[i] & 1) == 0) || (!oddmode && ((buffer[i] & 1) == 1))) + return 0; //failed!! + } + return 1; +} + +GT2Byte * gti2GetChallenge +( + GT2Byte * buffer +) +{ + int i; + int oddmode; + assert(buffer); + + srand((unsigned int)current_time()); + buffer[0] = (GT2Byte)(33 + rand() % 93); //use chars in the range 33 - 125 + oddmode = 0; + for (i = 1; i < GTI2_CHALLENGE_LEN ; i++) + { + oddmode = CALCULATEODDMODE(buffer,i, oddmode); + buffer[i] = (GT2Byte)(33 + rand() % 93); //use chars in the range 33 - 125 + //if oddmode make sure the char is odd, otherwise make sure it's even + if ((oddmode && (buffer[i] & 1) == 0) || (!oddmode && ((buffer[i] & 1) == 1))) + buffer[i]++; + + } + return buffer; +} + +GT2Byte * gti2GetResponse +( + GT2Byte * buffer, + const GT2Byte * challenge +) +{ + int i; + int valid; + char cchar; + int keylen = (int)strlen(GT2ChallengeKey); + int chalrand; + valid = gti2VerifyChallenge(challenge); //it's an invalid challenge, give them a bogus response + //assert(GTI2_RESPONSE_LEN <= GTI2_CHALLENGE_LEN); + for (i = 0 ; i < GTI2_RESPONSE_LEN ; i++) + { + //use random vals for spots 0 and 13 + if (!valid || i == 0 || i == 13) + buffer[i] = (GT2Byte)(33 + rand() % 93); //use chars in the range 33 - 125 + else + { //set the character to look back at, never use the random ones! + if (i == 1 || i == 14) + cchar = (char)challenge[i]; + else + cchar = (char)challenge[i-1]; + chalrand = abs((challenge[((i * challenge[i]) + GT2ChallengeKey[(i + challenge[i]) % keylen]) % GTI2_CHALLENGE_LEN] ^ GT2ChallengeKey[(i * 17991 * cchar) % keylen])); + buffer[i] = (GT2Byte)(33 + chalrand % 93); + } + } + return buffer; +} + + +GT2Bool gti2CheckResponse +( + const GT2Byte * response1, + const GT2Byte * response2 +) +{ + int i; //when comparing ignore the ones that are random + for (i = 0 ; i < GTI2_RESPONSE_LEN ; i++) + { + if (i != 0 && i != 13 && response1[i] != response2[i]) + return GT2False; + } + return GT2True; +} + diff --git a/code/gamespy/gt2/gt2Auth.h b/code/gamespy/gt2/gt2Auth.h new file mode 100644 index 00000000..8d3134d6 --- /dev/null +++ b/code/gamespy/gt2/gt2Auth.h @@ -0,0 +1,42 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_AUTH_H_ +#define _GT2_AUTH_H_ + +#define GTI2_CHALLENGE_LEN 32 +#define GTI2_RESPONSE_LEN 32 + +#ifdef __cplusplus +extern "C" { +#endif + +GT2Byte * gti2GetChallenge +( + GT2Byte * buffer +); + +GT2Byte * gti2GetResponse +( + GT2Byte * buffer, + const GT2Byte * challenge +); + +GT2Bool gti2CheckResponse +( + const GT2Byte * response1, + const GT2Byte * response2 +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/gt2/gt2Buffer.c b/code/gamespy/gt2/gt2Buffer.c new file mode 100644 index 00000000..43821a41 --- /dev/null +++ b/code/gamespy/gt2/gt2Buffer.c @@ -0,0 +1,80 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Buffer.h" +#include + +GT2Bool gti2AllocateBuffer(GTI2Buffer * buffer, int size) +{ + buffer->buffer = (GT2Byte *)gsimalloc((unsigned int)size); + if(!buffer->buffer) + return GT2False; + buffer->size = size; + + return GT2True; +} + +int gti2GetBufferFreeSpace(const GTI2Buffer * buffer) +{ + return (buffer->size - buffer->len); +} + +void gti2BufferWriteByte(GTI2Buffer * buffer, GT2Byte b) +{ + assert(buffer->len < buffer->size); +#if 0 + if(buffer->len >= buffer->size) + return; +#endif + + buffer->buffer[buffer->len++] = b; +} + +void gti2BufferWriteUShort(GTI2Buffer * buffer, unsigned short s) +{ + assert((buffer->len + 2) <= buffer->size); +#if 0 + if((buffer->len + 2) > buffer->size) + return; +#endif + + buffer->buffer[buffer->len++] = (GT2Byte)((s >> 8) & 0xFF); + buffer->buffer[buffer->len++] = (GT2Byte)(s & 0xFF); +} + +void gti2BufferWriteData(GTI2Buffer * buffer, const GT2Byte * data, int len) +{ + if(!data || !len) + return; + + if(len == -1) + len = (int)strlen((const char *)data); + + assert((buffer->len + len) <= buffer->size); +#if 0 + if(buffer->len >= buffer->size) + return; +#endif + + memcpy(buffer->buffer + buffer->len, data, (unsigned int)len); + buffer->len += len; +} + +void gti2BufferShorten(GTI2Buffer * buffer, int start, int shortenBy) +{ + if(start == -1) + start = (buffer->len - shortenBy); + + assert(start <= buffer->len); + assert(shortenBy <= (buffer->len - start)); + + memmove(buffer->buffer + start, buffer->buffer + start + shortenBy, (unsigned int)(buffer->len - start - shortenBy)); + buffer->len -= shortenBy; +} diff --git a/code/gamespy/gt2/gt2Buffer.h b/code/gamespy/gt2/gt2Buffer.h new file mode 100644 index 00000000..d93f3927 --- /dev/null +++ b/code/gamespy/gt2/gt2Buffer.h @@ -0,0 +1,27 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_BUFFER_H_ +#define _GT2_BUFFER_H_ + +#include "gt2Main.h" + +GT2Bool gti2AllocateBuffer(GTI2Buffer * buffer, int size); + +int gti2GetBufferFreeSpace(const GTI2Buffer * buffer); + +void gti2BufferWriteByte(GTI2Buffer * buffer, GT2Byte b); +void gti2BufferWriteUShort(GTI2Buffer * buffer, unsigned short s); +void gti2BufferWriteData(GTI2Buffer * buffer, const GT2Byte * data, int len); + +// shortens the buffer by "shortenBy" (length, not size) +void gti2BufferShorten(GTI2Buffer * buffer, int start, int shortenBy); + +#endif diff --git a/code/gamespy/gt2/gt2Callback.c b/code/gamespy/gt2/gt2Callback.c new file mode 100644 index 00000000..b42e0365 --- /dev/null +++ b/code/gamespy/gt2/gt2Callback.c @@ -0,0 +1,431 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Callback.h" +#include "gt2Socket.h" + +/********************* +** SOCKET CALLBACKS ** +*********************/ + +GT2Bool gti2SocketErrorCallback +( + GT2Socket socket +) +{ + assert(socket); + if(!socket) + return GT2True; + + if(!socket->socketErrorCallback) + return GT2True; + + socket->callbackLevel++; + + socket->socketErrorCallback(socket); + + socket->callbackLevel--; + + // check if the socket should be closed + if(socket->close && !socket->callbackLevel) + { + gti2CloseSocket(socket); + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2ConnectAttemptCallback +( + GT2Socket socket, + GT2Connection connection, + unsigned int ip, + unsigned short port, + int latency, + GT2Byte * message, + int len +) +{ + assert(socket && connection); + if(!socket || !connection) + return GT2True; + + if(!socket->connectAttemptCallback) + return GT2True; + + // check for an empty message + if(!len || !message) + { + message = NULL; + len = 0; + } + + socket->callbackLevel++; + connection->callbackLevel++; + + socket->connectAttemptCallback(socket, connection, ip, port, latency, message, len); + + socket->callbackLevel--; + connection->callbackLevel--; + + // check if the socket should be closed + if(socket->close && !socket->callbackLevel) + { + gti2CloseSocket(socket); + return GT2False; + } + + return GT2True; +} + +/************************* +** CONNECTION CALLBACKS ** +*************************/ + +GT2Bool gti2ConnectedCallback +( + GT2Connection connection, + GT2Result result, + GT2Byte * message, + int len +) +{ + assert(connection); + if(!connection) + return GT2True; + + // store the result + connection->connectionResult = result; + + if(!connection->callbacks.connected) + return GT2True; + + // check for an empty message + if(!len || !message) + { + message = NULL; + len = 0; + } + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + connection->callbacks.connected(connection, result, message, len); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if(connection->socket->close && !connection->socket->callbackLevel) + { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2ReceivedCallback +( + GT2Connection connection, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + assert(connection); + if(!connection) + return GT2True; + + if(!connection->callbacks.received) + return GT2True; + + // check for an empty message + if(!len || !message) + { + message = NULL; + len = 0; + } + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + connection->callbacks.received(connection, message, len, reliable); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if(connection->socket->close && !connection->socket->callbackLevel) + { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2ClosedCallback +( + GT2Connection connection, + GT2CloseReason reason +) +{ + assert(connection); + if(!connection) + return GT2True; + + if(!connection->callbacks.closed) + return GT2True; + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + connection->callbacks.closed(connection, reason); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if(connection->socket->close && !connection->socket->callbackLevel) + { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2PingCallback +( + GT2Connection connection, + int latency +) +{ + assert(connection); + if(!connection) + return GT2True; + + if(!connection->callbacks.ping) + return GT2True; + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + connection->callbacks.ping(connection, latency); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if(connection->socket->close && !connection->socket->callbackLevel) + { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +/********************* +** FILTER CALLBACKS ** +*********************/ + +GT2Bool gti2SendFilterCallback +( + GT2Connection connection, + int filterID, + const GT2Byte * message, + int len, + GT2Bool reliable +) +{ + gt2SendFilterCallback * callback; + + assert(connection); + if(!connection) + return GT2True; + + callback = (gt2SendFilterCallback *)ArrayNth(connection->sendFilters, filterID); + if(!callback) + return GT2True; + + // check for an empty message + if(!len || !message) + { + message = NULL; + len = 0; + } + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + (*callback)(connection, filterID, message, len, reliable); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if(connection->socket->close && !connection->socket->callbackLevel) + { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2ReceiveFilterCallback +( + GT2Connection connection, + int filterID, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + gt2ReceiveFilterCallback * callback; + + assert(connection); + if(!connection) + return GT2True; + + callback = (gt2ReceiveFilterCallback *)ArrayNth(connection->receiveFilters, filterID); + if(!callback) + return GT2True; + + // check for an empty message + if(!len || !message) + { + message = NULL; + len = 0; + } + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + (*callback)(connection, filterID, message, len, reliable); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if(connection->socket->close && !connection->socket->callbackLevel) + { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +/******************* +** DUMP CALLBACKS ** +*******************/ + +GT2Bool gti2DumpCallback +( + GT2Socket socket, + GT2Connection connection, + unsigned int ip, + unsigned short port, + GT2Bool reset, + const GT2Byte * message, + int len, + GT2Bool send +) +{ + gt2DumpCallback callback; + + assert(socket); + if(!socket) + return GT2True; + + if(send) + callback = socket->sendDumpCallback; + else + callback = socket->receiveDumpCallback; + + if(!callback) + return GT2True; + + // check for an empty message + if(!len || !message) + { + message = NULL; + len = 0; + } + + socket->callbackLevel++; + if(connection) + connection->callbackLevel++; + + callback(socket, connection, ip, port, reset, message, len); + + socket->callbackLevel--; + if(connection) + connection->callbackLevel--; + + // check if the socket should be closed + if(socket->close && !socket->callbackLevel) + { + gti2CloseSocket(socket); + return GT2False; + } + + return GT2True; +} + +/***************************** +** SOCKET SHARING CALLBACKS ** +*****************************/ + +GT2Bool gti2UnrecognizedMessageCallback +( + GT2Socket socket, + unsigned int ip, + unsigned short port, + GT2Byte * message, + int len, + GT2Bool * handled +) +{ + *handled = GT2False; + + assert(socket); + if(!socket) + return GT2True; + + if(!socket->unrecognizedMessageCallback) + return GT2True; + + // check for an empty message + if(!len || !message) + { + message = NULL; + len = 0; + } + + socket->callbackLevel++; + + *handled = socket->unrecognizedMessageCallback(socket, ip, port, message, len); + + socket->callbackLevel--; + + // check if the socket should be closed + if(socket->close && !socket->callbackLevel) + { + gti2CloseSocket(socket); + return GT2False; + } + + return GT2True; +} diff --git a/code/gamespy/gt2/gt2Callback.h b/code/gamespy/gt2/gt2Callback.h new file mode 100644 index 00000000..f1a74cc5 --- /dev/null +++ b/code/gamespy/gt2/gt2Callback.h @@ -0,0 +1,120 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_CALLBACK_H_ +#define _GT2_CALLBACK_H_ + +#include "gt2Main.h" + +/********************* +** SOCKET CALLBACKS ** +*********************/ + +GT2Bool gti2SocketErrorCallback +( + GT2Socket socket +); + +GT2Bool gti2ConnectAttemptCallback +( + GT2Socket socket, + GT2Connection connection, + unsigned int ip, + unsigned short port, + int latency, + GT2Byte * message, + int len +); + +/************************* +** CONNECTION CALLBACKS ** +*************************/ + +GT2Bool gti2ConnectedCallback +( + GT2Connection connection, + GT2Result result, + GT2Byte * message, + int len +); + +GT2Bool gti2ReceivedCallback +( + GT2Connection connection, + GT2Byte * message, + int len, + GT2Bool reliable +); + +GT2Bool gti2ClosedCallback +( + GT2Connection connection, + GT2CloseReason reason +); + +GT2Bool gti2PingCallback +( + GT2Connection connection, + int latency +); + +/********************* +** FILTER CALLBACKS ** +*********************/ + +GT2Bool gti2SendFilterCallback +( + GT2Connection connection, + int filterID, + const GT2Byte * message, + int len, + GT2Bool reliable +); + +GT2Bool gti2ReceiveFilterCallback +( + GT2Connection connection, + int filterID, + GT2Byte * message, + int len, + GT2Bool reliable +); + +/******************* +** DUMP CALLBACKS ** +*******************/ + +GT2Bool gti2DumpCallback +( + GT2Socket socket, + GT2Connection connection, + unsigned int ip, + unsigned short port, + GT2Bool reset, + const GT2Byte * message, + int len, + GT2Bool send +); + +/***************************** +** SOCKET SHARING CALLBACKS ** +*****************************/ + +GT2Bool gti2UnrecognizedMessageCallback +( + GT2Socket socket, + unsigned int ip, + unsigned short port, + GT2Byte * message, + int len, + GT2Bool * handled +); + +#endif diff --git a/code/gamespy/gt2/gt2Connection.c b/code/gamespy/gt2/gt2Connection.c new file mode 100644 index 00000000..a17d54bb --- /dev/null +++ b/code/gamespy/gt2/gt2Connection.c @@ -0,0 +1,343 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Connection.h" +#include "gt2Socket.h" +#include "gt2Message.h" +#include "gt2Callback.h" +#include "gt2Utility.h" +#include + +GT2Result gti2NewOutgoingConnection(GT2Socket socket, GT2Connection * connection, unsigned int ip, unsigned short port) +{ + GT2Result result; + + // create the object + result = gti2NewSocketConnection(socket, connection, ip, port); + if(result != GT2Success) + return result; + + // set initial states + (*connection)->state = GTI2AwaitingServerChallenge; + (*connection)->initiated = GT2True; + + return GT2Success; +} + +GT2Result gti2NewIncomingConnection(GT2Socket socket, GT2Connection * connection, unsigned int ip, unsigned short port) +{ + GT2Result result; + + // create the object + result = gti2NewSocketConnection(socket, connection, ip, port); + if(result != GT2Success) + return result; + + // set initial states + (*connection)->state = GTI2AwaitingClientChallenge; + (*connection)->initiated = GT2False; + + return GT2Success; +} + +GT2Result gti2StartConnectionAttempt +( + GT2Connection connection, + const GT2Byte * message, + int len, + GT2ConnectionCallbacks * callbacks +) +{ + char challenge[GTI2_CHALLENGE_LEN]; + + // check the message and len + gti2MessageCheck(&message, &len); + + // copy off the message + if(len > 0) + { + connection->initialMessage = (char *)gsimalloc((unsigned int)len); + if(!connection->initialMessage) + return GT2OutOfMemory; + + memcpy(connection->initialMessage, message, (unsigned int)len); + connection->initialMessageLen = len; + } + + // copy the callbacks + if(callbacks) + connection->callbacks = *callbacks; + + // generate a challenge + gti2GetChallenge((GT2Byte *)challenge); + + // generate and store the expected response + gti2GetResponse((GT2Byte *)connection->response, (GT2Byte *)challenge); + + // send the client challenge + gti2SendClientChallenge(connection, challenge); + + // update our state + connection->state = GTI2AwaitingServerChallenge; + + return GT2Success; +} + +GT2Bool gti2AcceptConnection(GT2Connection connection, GT2ConnectionCallbacks * callbacks) +{ + // was the connection already closed? + if(connection->freeAtAcceptReject) + { + // clear the flag + connection->freeAtAcceptReject = GT2False; + + // let the app know if was already closed + return GT2False; + } + + // make sure this flag gets cleared + connection->freeAtAcceptReject = GT2False; + + // check that we're still awaiting this + if(connection->state != GTI2AwaitingAcceptReject) + return GT2False; + + // let the other side know + gti2SendAccept(connection); + + // update our state + connection->state = GTI2Connected; + + // store the callbacks + if(callbacks) + connection->callbacks = *callbacks; + + return GT2True; +} + +void gti2RejectConnection(GT2Connection connection, const GT2Byte * message, int len) +{ + // make sure this flag gets cleared + connection->freeAtAcceptReject = GT2False; + + // check that we're still awaiting this + if(connection->state != GTI2AwaitingAcceptReject) + return; + + // check the message and len + gti2MessageCheck(&message, &len); + + // let the other side know + gti2SendReject(connection, message, len); + + // update our state + connection->state = GTI2Closing; +} + +GT2Bool gti2ConnectionSendData(GT2Connection connection, const GT2Byte * message, int len) +{ + // send the data on the socket + if(!gti2SocketSend(connection->socket, connection->ip, connection->port, message, len)) + return GT2False; + + // mark the time (used for keep-alives) + connection->lastSend = current_time(); + + return GT2True; +} + +static GT2Bool gti2CheckTimeout(GT2Connection connection, gsi_time now) +{ + // are we still trying to connect? + if(connection->state < GTI2Connected) + { + GT2Bool timedOut = GT2False; + + // is this the initiator + if(connection->initiated) + { + // do we have a timeout? + if(connection->timeout) + { + // check the time taken against the timeout + if((now - connection->startTime) > connection->timeout) + timedOut = GT2True; + } + } + else + { + // don't time them out if they're waiting for us + if(connection->state < GTI2AwaitingAcceptReject) + { + // check the time taken against the timeout + if((now - connection->startTime) > GTI2_SERVER_TIMEOUT) + timedOut = GT2True; + } + } + + // check if we timed out + if(timedOut) + { + // let them know + gti2SendClosed(connection); + + // mark it as closed + gti2ConnectionClosed(connection); + + // call the callback + if(!gti2ConnectedCallback(connection, GT2TimedOut, NULL, 0)) + return GT2False; + } + } + + return GT2True; +} + +static GT2Bool gti2SendRetries(GT2Connection connection, gsi_time now) +{ + int i; + int len; + GTI2OutgoingBufferMessage * message; + + // go through the list of outgoing messages awaiting confirmation + len = ArrayLength(connection->outgoingBufferMessages); + for(i = 0 ; i < len ; i++) + { + // get the message + message = (GTI2OutgoingBufferMessage *)ArrayNth(connection->outgoingBufferMessages, i); + + // check if it's time to resend it + if((now - message->lastSend) > GTI2_RESEND_TIME) + { + if(!gti2ResendMessage(connection, message)) + return GT2False; + } + } + + return GT2True; +} + +static GT2Bool gti2CheckPendingAck(GT2Connection connection, gsi_time now) +{ + // check for nothing pending + if(!connection->pendingAck) + return GT2True; + + // check how long it has been pending + if((now - connection->pendingAckTime) > GTI2_PENDING_ACK_TIME) + { + if(!gti2SendAck(connection)) + return GT2False; + } + + return GT2True; +} + +static GT2Bool gti2CheckKeepAlive(GT2Connection connection, gsi_time now) +{ + if((now - connection->lastSend) > GTI2_KEEP_ALIVE_TIME) + { + if(!gti2SendKeepAlive(connection)) + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2ConnectionThink(GT2Connection connection, gsi_time now) +{ + // check timeout + if(!gti2CheckTimeout(connection, now)) + return GT2False; + + // check keep alives + if(!gti2CheckKeepAlive(connection, now)) + return GT2False; + + // send retries + if(!gti2SendRetries(connection, now)) + return GT2False; + + // check the pending ack + if(!gti2CheckPendingAck(connection, now)) + return GT2False; + + return GT2True; +} + +void gti2CloseConnection(GT2Connection connection, GT2Bool hard) +{ + // check if it should be hard or soft closed + if(hard) + { + // check if it's already closed + if(connection->state >= GTI2Closed) + return; + + // mark it as closed + gti2ConnectionClosed(connection); + + // send a closed message + gti2SendClosed(connection); + + // call the callback + gti2ClosedCallback(connection, GT2LocalClose); + + // try and free it + gti2FreeSocketConnection(connection); + } + else + { + // mark it as closing + connection->state = GTI2Closing; + + // send the close + gti2SendClose(connection); + } +} + +void gti2ConnectionClosed(GT2Connection connection) +{ + // check for already closed + if(connection->state == GTI2Closed) + return; + + // mark the connection as closed + connection->state = GTI2Closed; + + // remove it from the connected list + TableRemove(connection->socket->connections, &connection); + + // add it to the closed list + ArrayAppend(connection->socket->closedConnections, &connection); +} + +void gti2ConnectionCleanup(GT2Connection connection) +{ + if(connection->initialMessage) + gsifree(connection->initialMessage); + + if(connection->incomingBuffer.buffer) + gsifree(connection->incomingBuffer.buffer); + if(connection->outgoingBuffer.buffer) + gsifree(connection->outgoingBuffer.buffer); + + if(connection->incomingBufferMessages) + ArrayFree(connection->incomingBufferMessages); + if(connection->outgoingBufferMessages) + ArrayFree(connection->outgoingBufferMessages); + + if(connection->sendFilters) + ArrayFree(connection->sendFilters); + if(connection->receiveFilters) + ArrayFree(connection->receiveFilters); + + gsifree(connection); +} diff --git a/code/gamespy/gt2/gt2Connection.h b/code/gamespy/gt2/gt2Connection.h new file mode 100644 index 00000000..43423f34 --- /dev/null +++ b/code/gamespy/gt2/gt2Connection.h @@ -0,0 +1,41 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_CONNECTION_H_ +#define _GT2_CONNECTION_H_ + +#include "gt2Main.h" + +GT2Result gti2NewOutgoingConnection(GT2Socket socket, GT2Connection * connection, unsigned int ip, unsigned short port); +GT2Result gti2NewIncomingConnection(GT2Socket socket, GT2Connection * connection, unsigned int ip, unsigned short port); + +GT2Result gti2StartConnectionAttempt +( + GT2Connection connection, + const GT2Byte * message, + int len, + GT2ConnectionCallbacks * callbacks +); + +GT2Bool gti2AcceptConnection(GT2Connection connection, GT2ConnectionCallbacks * callbacks); + +void gti2RejectConnection(GT2Connection connection, const GT2Byte * message, int len); + +GT2Bool gti2ConnectionSendData(GT2Connection connection, const GT2Byte * message, int len); + +GT2Bool gti2ConnectionThink(GT2Connection connection, gsi_time now); + +void gti2CloseConnection(GT2Connection connection, GT2Bool hard); + +void gti2ConnectionClosed(GT2Connection connection); + +void gti2ConnectionCleanup(GT2Connection connection); + +#endif diff --git a/code/gamespy/gt2/gt2Encode.c b/code/gamespy/gt2/gt2Encode.c new file mode 100644 index 00000000..aa2f2cea --- /dev/null +++ b/code/gamespy/gt2/gt2Encode.c @@ -0,0 +1,648 @@ +#include +#include +#include "gt2Encode.h" +#include "gt2Main.h" + + + +// This handles alignment issues and endianess +void gt2MemCopy16(char *out, char const *in) +{ + #ifdef _GT2_ENDIAN_CONVERT + *out = in[1]; + out[1] = *in; + #else + // straight copy + *out = *in; + out[1] = in[1]; + #endif +} + +// This handles alignment issues and endianess +void gt2MemCopy32(char *out, char const *in) +{ + #ifdef _GT2_ENDIAN_CONVERT + out[0] = in[3]; + out[1] = in[2]; + out[2] = in[1]; + out[3] = in[0]; + #else + // straight copy + *out = *in; + out[1] = in[1]; + out[2] = in[2]; + out[3] = in[3]; + #endif +} + +// This handles alignment issues and endianess +void gt2MemCopy64(char *out, char const *in) +{ + #ifdef _GT2_ENDIAN_CONVERT + out[0] = in[7]; + out[1] = in[6]; + out[2] = in[5]; + out[3] = in[4]; + out[4] = in[3]; + out[5] = in[2]; + out[6] = in[1]; + out[7] = in[0]; + #else + // straight copy + memcpy(out, in, 8); + #endif + +} +void gt2MemCopy(char *out, char const *in, int size) +{ + if (size == 2) + { + gt2MemCopy16(out, in); + } + else + if (size == 4) + { + gt2MemCopy32(out, in); + } + else + if (size == 8) + { + gt2MemCopy64(out, in); + } + else + { + // warning... no endianess decode. + memcpy(out,in,(size_t)size); + } +} +#if defined(_PS2) || defined(_UNIX) || defined(_PS3) || defined(_WIN64) || defined(_X360) + +#define GT_ENCODE_ELEM(TYPE,b,l,args) \ +{ \ + TYPE v; \ + if (l < sizeof(TYPE)) \ + return -1; \ + v = (TYPE)va_arg(*args, int); \ + gt2MemCopy(b, (const char *)&v, sizeof(TYPE)); \ + return (int)sizeof(TYPE); \ +} + +#define GT_DECODE_ELEM(TYPE,b,l,args) \ +{ \ + TYPE* v; \ + if (l < sizeof(TYPE)) \ + return -1; \ + v = va_arg(*args, TYPE*); \ + gt2MemCopy((char *)v, b, sizeof(TYPE)); \ + return (int)sizeof(TYPE); \ +} + +#define GT_ENCODE_ELEM_NC(TYPE,b,l,args) \ +{ \ + TYPE v; \ + if (l < sizeof(TYPE)) \ + return -1; \ + v = (TYPE)va_arg(*args, int); \ + memcpy(b, &v, sizeof(TYPE)); \ + return (int)sizeof(TYPE); \ +} + +#define GT_DECODE_ELEM_NC(TYPE,b,l,args) \ +{ \ + TYPE* v; \ + if (l < sizeof(TYPE)) \ + return -1; \ + v = va_arg(*args, TYPE*); \ + memcpy(v, b, sizeof(TYPE)); \ + return (int)sizeof(TYPE); \ +} + +#else + +#define GT_ENCODE_ELEM(TYPE,b,l,args) {if (l < sizeof(TYPE)) return -1; gt2MemCopy(b,(const char *)&va_arg(*args,TYPE),sizeof(TYPE)); return sizeof(TYPE);} +#define GT_DECODE_ELEM(TYPE,b,l,args) {if (l < sizeof(TYPE)) return -1; gt2MemCopy((char *)va_arg(*args,TYPE*),b,sizeof(TYPE)); return sizeof(TYPE);} +// nc = no endian convert +#define GT_ENCODE_ELEM_NC(TYPE,b,l,args) {if (l < sizeof(TYPE)) return -1; memcpy(b,&va_arg(*args,TYPE),sizeof(TYPE)); return sizeof(TYPE);} +#define GT_DECODE_ELEM_NC(TYPE,b,l,args) {if (l < sizeof(TYPE)) return -1; memcpy(va_arg(*args,TYPE*),b,sizeof(TYPE)); return sizeof(TYPE);} + +#endif /* _PS || _UNIX */ + +static int dbstrlen(GT_DBSTR_TYPE dbstr) +{ + int len = 0; +#ifdef ALIGNED_COPY + short achar; + do + { + memcpy(&achar, dbstr, sizeof(achar)); + dbstr++; + len++; + } while (achar != 0); + len--; +#else + while (*dbstr++) + len++; +#endif + return len; +} + +static short *dbstrcpy(GT_DBSTR_TYPE dest, GT_DBSTR_TYPE src) +{ + GT_DBSTR_TYPE hold = dest; + #ifdef ALIGNED_COPY + int len = dbstrlen(src); + memcpy(dest, src, (unsigned int)(len + 1) * 2); + #else + while ((*dest++ = *src++) != 0) ; + #endif + return hold; +} + +static int gtiDecodeBits(int bitcount, char *inBuffer, int inLength, va_list *args) +{ + char bucket; + int i; + + if (inLength < 1) + return -1; + bucket = *inBuffer; + for (i = 0 ; i < bitcount ; i++) + { + *va_arg(*args,char*) = (char)((bucket & (1 << i)) ? 1 : 0); + } + + return 1; +} + +static int gtiEncodeBits(int bitcount, char *outBuffer, int outLength, va_list *args) +{ + char bucket = 0; + int i; + + if (outLength < 1) + return -1; + for (i = 0 ; i < bitcount ; i++) + { + bucket |= (char)((va_arg(*args,int) ? 1 : 0) << i); + //bucket |= ((va_arg(*args,char) ? 1 : 0) << i); + } + *outBuffer = bucket; + return 1; +} + +// length in bytes including NUL, or -1 if error +static int gtiCheckStringLen(char *inBuffer, int inLength) +{ + int len = 0; + do + { + len++; + if(len > inLength) + return -1; + } + while(inBuffer[len - 1] != '\0'); + return len; +} + +// length in bytes (not chars) including NUL, or -1 if error +static int gtiCheckDoubleStringLen(char *inBuffer, int inLength) +{ + int len = 0; + do + { + len += 2; + if(len > inLength) + return -1; + } + while((inBuffer[len - 2] != '\0') || (inBuffer[len - 1] != '\0')); + return len; +} + +// length in bytes including NULs, or -1 if error +static int gtiCheckStringArrayLen(char *inBuffer, int inLength) +{ + int len = 0; + int strLen; + do + { + strLen = gtiCheckStringLen(inBuffer + len, inLength - len); + if(strLen == -1) + return -1; + len += strLen; + } + while(strLen > 1); + return len; +} + +static int gtiDecodeSingle(char elemType, char *inBuffer, int inLength, va_list *args) +{ + switch (elemType) + { + case GT_INT: + GT_DECODE_ELEM(GT_INT_TYPE,inBuffer, inLength, args); + //break; + case GT_UINT: + GT_DECODE_ELEM(GT_UINT_TYPE,inBuffer, inLength, args); + //break; + case GT_SHORT: + GT_DECODE_ELEM(GT_SHORT_TYPE,inBuffer, inLength, args); + //break; + case GT_USHORT: + GT_DECODE_ELEM(GT_USHORT_TYPE,inBuffer, inLength, args); + //break; + case GT_CHAR: + GT_DECODE_ELEM(GT_CHAR_TYPE,inBuffer, inLength, args); + //break; + case GT_UCHAR: + GT_DECODE_ELEM(GT_UCHAR_TYPE,inBuffer, inLength, args); + //break; + case GT_FLOAT: + { + #if(0) + // no endian convert + GT_FLOAT_TYPE* v; + if (inLength < sizeof(GT_FLOAT_TYPE)) + return -1; + v = va_arg(*args, GT_FLOAT_TYPE*); + v[0] = inBuffer[0]; + v[1] = inBuffer[1]; + v[2] = inBuffer[2]; + v[3] = inBuffer[3]; + return (int)sizeof(GT_FLOAT_TYPE); + #else + GT_DECODE_ELEM_NC(GT_FLOAT_TYPE,inBuffer, inLength, args); + #endif + } + case GT_DOUBLE: + #if(0) + // no endian convert + { GT_DOUBLE_TYPE* v; + if (inLength < sizeof(GT_DOUBLE_TYPE)) + return -1; + v = va_arg(*args, GT_DOUBLE_TYPE*); + v[0] = inBuffer[0]; + v[1] = inBuffer[1]; + v[2] = inBuffer[2]; + v[3] = inBuffer[3]; + v[4] = inBuffer[4]; + v[5] = inBuffer[5]; + v[6] = inBuffer[6]; + v[7] = inBuffer[7]; + return (int)sizeof(GT_DOUBLE_TYPE); + } + #else + GT_DECODE_ELEM_NC(GT_DOUBLE_TYPE,inBuffer, inLength, args); + #endif + //break; + case GT_BIT: + GT_DECODE_ELEM(GT_BIT_TYPE,inBuffer, inLength, args); + //break; + case GT_CSTR: + { + int len; + GT_CSTR_TYPE s = va_arg(*args, GT_CSTR_TYPE); + assert(s != NULL); + len = gtiCheckStringLen(inBuffer, inLength); + if(len == -1) + return -1; + memcpy(s, inBuffer, (size_t)len); + return len; + } + //break; + case GT_CSTR_PTR: + *va_arg(*args, GT_CSTR_PTR_TYPE) = (GT_CSTR_TYPE)inBuffer; + return gtiCheckStringLen(inBuffer, inLength); + //break; + case GT_DBSTR: + { + int len; + GT_DBSTR_TYPE s = va_arg(*args, GT_DBSTR_TYPE); + assert(s != NULL); + len = gtiCheckDoubleStringLen(inBuffer, inLength); + if (len == -1) + return -1; + memcpy(s, inBuffer, (size_t)len); + return len; + } + //break; + case GT_DBSTR_PTR: + *va_arg(*args, GT_DBSTR_PTR_TYPE) = (GT_DBSTR_TYPE)inBuffer; + return gtiCheckDoubleStringLen(inBuffer, inLength); + //break; + case GT_CSTR_ARRAY: + { + int len; + GT_CSTR_ARRAY_TYPE s = va_arg(*args, GT_CSTR_ARRAY_TYPE); + assert(s != NULL); + len = gtiCheckStringArrayLen(inBuffer, inLength); + if(len == -1) + return -1; + memcpy(s, inBuffer, (size_t)len); + return len; + } + //break; + case GT_CSTR_ARRAY_PTR: + *va_arg(*args, GT_CSTR_ARRAY_PTR_TYPE) = (GT_CSTR_ARRAY_TYPE)inBuffer; + return gtiCheckStringArrayLen(inBuffer, inLength); + //break; + case GT_RAW: + { + int *len, holdlen; + GT_RAW_TYPE data = va_arg(*args, GT_RAW_TYPE); + len = va_arg(*args, int *); + if (inLength < sizeof(*len)) + return -1; + holdlen = *len; + memcpy(len, inBuffer, sizeof(*len)); + if (*len > holdlen) //there isn't enough room in their dest! + return -1; + if (inLength < (int)sizeof(*len) + *len) + return -1; + memcpy(data, inBuffer + sizeof(*len), (unsigned int)*len); + return *len + (int)sizeof(*len); + } + case GT_RAW_PTR: + { + int *len; + *va_arg(*args, GT_RAW_PTR_TYPE) = (GT_RAW_TYPE)(inBuffer + sizeof(*len)); + len = va_arg(*args, int *); + if (inLength < sizeof(*len)) + return -1; + memcpy(len, inBuffer, sizeof(*len)); + return *len + (int)sizeof(*len); + } + //break; + + } + return -1; //bad type! +} + +static int gtiEncodeSingle(char elemType, char *outBuffer, int outLength, va_list *args) +{ + switch (elemType) + { + case GT_INT: + GT_ENCODE_ELEM(GT_INT_TYPE,outBuffer, outLength, args); + //break; + case GT_UINT: + GT_ENCODE_ELEM(GT_UINT_TYPE,outBuffer, outLength, args); + //break; + case GT_SHORT: + GT_ENCODE_ELEM(GT_SHORT_TYPE,outBuffer, outLength, args); + //break; + case GT_USHORT: + GT_ENCODE_ELEM(GT_USHORT_TYPE,outBuffer, outLength, args); + //break; + case GT_CHAR: + GT_ENCODE_ELEM(GT_CHAR_TYPE,outBuffer, outLength, args); + //break; + case GT_UCHAR: + GT_ENCODE_ELEM(GT_UCHAR_TYPE,outBuffer, outLength, args); + //break; + case GT_FLOAT: //floats are promoted to double in varargs, need to demote + { + double temp; + float f; + double v = va_arg(*args,double); + memcpy(&temp,&v,sizeof(double)); + f = (float)temp; + if (outLength < sizeof(float)) + return -1; + memcpy(outBuffer, &f, sizeof(float)); + return sizeof(float); + } + //break; + case GT_DOUBLE: + { + double v; + if(outLength < sizeof(double)) + return -1; + v = va_arg(*args, double); + memcpy(outBuffer, &v, sizeof(double)); + return sizeof(double); + } + //break; + case GT_BIT: + GT_ENCODE_ELEM(GT_BIT_TYPE,outBuffer, outLength, args); + //break; + case GT_CSTR: + case GT_CSTR_PTR: + { + int len; + GT_CSTR_TYPE s = va_arg(*args, GT_CSTR_TYPE); + assert(s != NULL); + len = (int)strlen(s) + 1; + if (outLength < len ) + return -1; + strcpy(outBuffer, s); + return len; + } + //break; + case GT_DBSTR: + case GT_DBSTR_PTR: + { + int len; + GT_DBSTR_TYPE s = va_arg(*args, GT_DBSTR_TYPE); + assert(s != NULL); + len = dbstrlen(s) + 1; + if (outLength < len * 2) + return -1; + dbstrcpy((short *)outBuffer, s); + return len * 2; + } + //break; + case GT_CSTR_ARRAY: + case GT_CSTR_ARRAY_PTR: + { + int len = 0; + int strLen; + GT_CSTR_ARRAY_TYPE s = va_arg(*args, GT_CSTR_ARRAY_TYPE); + assert(s != NULL); + do + { + strLen = (int)strlen(s + len) + 1; + len += strLen; + if(outLength < len) + return -1; + } + while(strLen != 1); + memcpy(outBuffer, s, (size_t)len); + return len; + } + //break; + case GT_RAW: + case GT_RAW_PTR: + { + int len; + GT_RAW_TYPE data = va_arg(*args, GT_RAW_TYPE); + len = va_arg(*args, int); + if (outLength < len + (int)sizeof(len)) + return -1; + memcpy(outBuffer, &len, sizeof(len)); + memcpy(outBuffer + sizeof(len), data, (unsigned int)len); + return len + (int)sizeof(len); + } + + } + return -1; //bad type! +} + +static int gtInternalEncodeV(int usetype, GTMessageType msgType, const char *fmtString, char *outBuffer, int outLength, va_list *args) +{ + int elemSize; + int totSize = outLength; + const char *bitCounter; + + //set the message type + if (usetype) + { + elemSize = sizeof(msgType); + if (outLength < elemSize) + return -1; + + gt2MemCopy(outBuffer, (const char *)&msgType, elemSize); + outBuffer += elemSize; + outLength -= elemSize; + } + while (*fmtString) + { + if (*fmtString == GT_BIT) //see how many + { + for (bitCounter = fmtString; *bitCounter == GT_BIT && bitCounter - fmtString <= 8; bitCounter++) + {}; + elemSize = gtiEncodeBits((int)(bitCounter - fmtString), outBuffer, outLength, args); + fmtString = bitCounter - 1; + } else + elemSize = gtiEncodeSingle(*fmtString, outBuffer, outLength, args); + if (elemSize < 0) + return -1; //out of space + outBuffer += elemSize; + outLength -= elemSize; + fmtString++; + } + return totSize - outLength; +} + +int gtEncodeNoTypeV(const char *fmtString, char *outBuffer, int outLength, va_list *args) +{ + return gtInternalEncodeV(0,0,fmtString, outBuffer, outLength, args); +} + +int gtEncodeV(GTMessageType msgType, const char *fmtString, char *outBuffer, int outLength, va_list *args) +{ + return gtInternalEncodeV(1,msgType,fmtString, outBuffer, outLength, args); +} + +int gtEncode(GTMessageType msgType, const char *fmtString, char *outBuffer, int outLength, ...) +{ + int rcode; + va_list args; + + //set the values + va_start(args, outLength); + rcode = gtEncodeV(msgType, fmtString, outBuffer, outLength, &args); + va_end(args); + + return rcode; +} + +int gtEncodeNoType(const char *fmtString, char *outBuffer, int outLength, ...) +{ + int rcode; + va_list args; + + //set the values + va_start(args, outLength); + rcode = gtEncodeNoTypeV(fmtString, outBuffer, outLength, &args); + va_end(args); + + return rcode; +} + +static int gtDecodeInternalV(int usetype, const char *fmtString, char *inBuffer, int inLength, va_list *args) +{ + int elemSize; + int totSize = inLength; + const char *bitCounter; + + //skip the message type + if (usetype) + { + inBuffer += sizeof(GTMessageType); + inLength -= sizeof(GTMessageType); + } + + while (*fmtString) + { + if (*fmtString == GT_BIT) //see how many + { + for (bitCounter = fmtString; *bitCounter == GT_BIT && bitCounter - fmtString <= 8; bitCounter++) + {}; + elemSize = gtiDecodeBits((int)(bitCounter - fmtString), inBuffer, inLength, args); + fmtString = bitCounter - 1; + } else + elemSize = gtiDecodeSingle(*fmtString, inBuffer, inLength, args); + if (elemSize < 0) + return -1; //out of space + inBuffer += elemSize; + inLength -= elemSize; + fmtString++; + } + //NOTE: inLength should be 0 here if we "ate" the whole message + //If it's not 0, then the encoding and decoding strings probably did not match + //which would generally indicate a bug + //PANTS - commented out because we could be decoding the rest with a gtDecodeNoType +// assert(inLength == 0); + return totSize - inLength; +} + +int gtDecodeV(const char *fmtString, char *inBuffer, int inLength, va_list *args) +{ + return gtDecodeInternalV(1,fmtString, inBuffer, inLength, args); +} + +int gtDecodeNoTypeV(const char *fmtString, char *inBuffer, int inLength, va_list *args) +{ + return gtDecodeInternalV(0,fmtString, inBuffer, inLength, args); +} + +int gtDecode(const char *fmtString, char *inBuffer, int inLength, ...) +{ + int rcode; + va_list args; + + //set the values + va_start(args, inLength); + rcode = gtDecodeV(fmtString, inBuffer, inLength, &args); + va_end(args); + + return rcode; +} + +int gtDecodeNoType(const char *fmtString, char *inBuffer, int inLength, ...) +{ + int rcode; + va_list args; + + //set the values + va_start(args, inLength); + rcode = gtDecodeNoTypeV(fmtString, inBuffer, inLength, &args); + va_end(args); + + return rcode; +} + +GTMessageType gtEncodedMessageType(char *inBuffer) +{ + GTMessageType type; + //GS_ASSERT(sizeof(GTMessageType) ==2 ) + gt2MemCopy16((char *)&type, inBuffer); + return type; +} + +// change the message type for an encoded message +void gtEncodedMessageTypeSet (char *inBuffer, GTMessageType newtype) +{ + gt2MemCopy16(inBuffer, (char *)&newtype); +} + diff --git a/code/gamespy/gt2/gt2Encode.h b/code/gamespy/gt2/gt2Encode.h new file mode 100644 index 00000000..4e49fba5 --- /dev/null +++ b/code/gamespy/gt2/gt2Encode.h @@ -0,0 +1,180 @@ +//TODO: Address Byte order & Byte alignment issues +#ifndef _GT_ENCODE_H +#define _GT_ENCODE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(UNDER_CE) || defined(__mips64) || defined(_PSP) +#define ALIGNED_COPY +//the aligned copy code all needs to be optimized +#endif + + + + +// Used to identify the type of message so you can look up the correct format string +// and pass the correct parameters +// You should use 1 msgType for each unique format string/parameter combination +//////////////////////////////////////////////////////// +typedef unsigned short GTMessageType; + +// Encode a message into outBuffer +// Returns the length of the encoded message, or -1 to indicate insufficient space +// You must make sure the number of arguments match the fmtString list +//////////////////////////////////////////////////////// +int gtEncode(GTMessageType msgType, const char *fmtString, char *outBuffer, int outLength, ...); +int gtEncodeV(GTMessageType msgType, const char *fmtString, char *outBuffer, int outLength, va_list *args); +int gtEncodeNoType(const char *fmtString, char *outBuffer, int outLength, ...); +int gtEncodeNoTypeV(const char *fmtString, char *outBuffer, int outLength, va_list *args); + +// Decode the message from inBuffer into the vars provided +// Returns -1 if there was a problem with the buffer +// Vars should all be pointers (as if using scanf) +// You must make sure the number of arguments match the fmtString list +//////////////////////////////////////////////////////// +int gtDecode(const char *fmtString, char *inBuffer, int inLength, ...); +int gtDecodeV(const char *fmtString, char *inBuffer, int inLength, va_list *args); +int gtDecodeNoType(const char *fmtString, char *inBuffer, int inLength, ...); +int gtDecodeNoTypeV(const char *fmtString, char *inBuffer, int inLength, va_list *args); + +// Retrieve the message type for an encoded message +//////////////////////////////////////////////////////// +GTMessageType gtEncodedMessageType (char *inBuffer); +// change the message type for an encoded message +void gtEncodedMessageTypeSet (char *inBuffer, GTMessageType newtype); + +// This handles alignment issues and endianess +///////////////////////////////////////////////////////// +void gt2MemCopy16(char *out, char const *in); +void gt2MemCopy32(char *out, char const *in); +void gt2MemCopy64(char *out, char const *in); +void gt2MemCopy(char *out, char const *in, int size); + +/**************************** +Types that can be sent using the encode/decode functions +Most are self-explanatory, but the following require some clarification: + +GT_CSTR: This is a NUL terminated C-string. The length is determined automatically. +The NUL character is restored in decode. You simply pass the char * string as +the arguement for Encode. Note that in the pointer passed to Decode must have enough +memory allocated to it for the max string that will be encoded - otherwise +the destination may get trashed. If you cannot guarantee them max length of the +string, you should use GT_RAW (see below). + +GT_DBSTR: Same as a GT_CSTR, except with 2-byte characters instead of single byte. +String must be terminated with a double NUL character. + +GT_RAW: Use raw to send data blocks, structures, arrays, etc - although you must +make sure they're the same on all platforms! +Requires you to pass both a buffer and a length arguement to Encode and Decode. +You should pass the buffer first, then the length. +For Decode, you must initialize the length pointer to the max length of the buffer. +If the decoded data exceeds this length, the Decode function will return -1, and the +length pointer will be set to the actual length. You can then call Decode again with +a buffer that is at least the required length. +Example: +gtEncode(0, "r", buf, buflen, "somerawdata",10); +char *rawbuffer = malloc(5); +char rawlen = 5; +ret = gtDecode(0, "r", buf, buflen, rawbuffer, &rawlen); +//gtDecode will return -1, and rawlen will be set to 10 +if (ret == -1) +{ + rawbuffer = realloc(rawbuffer, rawlen); + gtDecode(0, "r", buf, buflen, rawbuffer, &rawlen); + //gtDecode will now succeed +} + +GT_CSTR_PTR, GT_DBSTR_PTR, GT_RAW_PTR: For Decode, instead of copying the data from +input buffer into the pointer provided, the pointer is simply set to the +offset of the data in the input buffer. This removes the need to allocate +memory for the pointers before hand, and elimantes an extra memory copy. +You will need to pass in double pointers (e.g. char **) so that the pointer +can be changed. +However, you must make sure you don't try to use the pointers after the +input buffer is freed/changed. +Note that the buffer passed in gtReceivedCallback must be copied off if +you want to continue using it after you return from the callback (or, you +can use the non-PTR versions that copy off into the buffers you provide +automatically) +You can pass the _PTR versions to Encode and they will behave exactly as +the regular versions. + +GT_CSTR_PTR, GT_CSTR_ARRAY_PTR: Same as GT_CSTR and GT_CSTR_PTR, but uses +an array of strings instead of a single string. The array of strings is +terminated by an empty string (a single NUL character). + +GT_BIT: If you pass all your bits together in the format string, they will be +packed together to save space. So, the format string "zzzzzzzz" will only take +1 byte for the data (+2 bytes for the message type). +Note that if you have other types between the bits, the packing will NOT occur, +e.g.: "ziz" will use 6 bytes (2 for the bits, 4 for the int), whereas "zzi" would use +only 5 bytes (1 for the bits, 4 for the int) +The argument type for bits is char for Encode and char * for Decode - +If the char is 0, the bit will not be set, if it's non-zero, the bit will be set. +Note that in Decode, the set bit will always be returned as 1 (not the non-zero value +you set) +*/ + +#define GT_INT 'i' +#define GT_INT_ "i" +#define GT_INT_TYPE int +#define GT_UINT 'u' +#define GT_UINT_ "u" +#define GT_UINT_TYPE unsigned int +#define GT_SHORT 'o' +#define GT_SHORT_ "o" +#define GT_SHORT_TYPE short +#define GT_USHORT 'p' +#define GT_USHORT_ "p" +#define GT_USHORT_TYPE unsigned short +#define GT_CHAR 'c' +#define GT_CHAR_ "c" +#define GT_CHAR_TYPE signed char +#define GT_UCHAR 'b' +#define GT_UCHAR_ "b" +#define GT_UCHAR_TYPE unsigned char +#define GT_FLOAT 'f' +#define GT_FLOAT_ "f" +#define GT_FLOAT_TYPE float +#define GT_DOUBLE 'd' +#define GT_DOUBLE_ "d" +#define GT_DOUBLE_TYPE double +#define GT_CSTR 's' +#define GT_CSTR_ "s" +#define GT_CSTR_TYPE char * +#define GT_CSTR_PTR 'S' +#define GT_CSTR_PTR_ "S" +#define GT_CSTR_PTR_TYPE char ** +#define GT_DBSTR 'w' +#define GT_DBSTR_ "w" +#define GT_DBSTR_TYPE short * +#define GT_DBSTR_PTR 'W' +#define GT_DBSTR_PTR_ "W" +#define GT_DBSTR_PTR_TYPE short ** +#define GT_CSTR_ARRAY 'a' +#define GT_CSTR_ARRAY_ "a" +#define GT_CSTR_ARRAY_TYPE char * +#define GT_CSTR_ARRAY_PTR 'A' +#define GT_CSTR_ARRAY_PTR_ "A" +#define GT_CSTR_ARRAY_PTR_TYPE char ** +#define GT_RAW 'r' //two parameters! (data, then length) +#define GT_RAW_ "r" //two parameters! +#define GT_RAW_TYPE char * +#define GT_RAW_PTR 'R' +#define GT_RAW_PTR_ "R" +#define GT_RAW_PTR_TYPE char ** +#define GT_BIT 'z' +#define GT_BIT_ "z" +#define GT_BIT_TYPE unsigned char + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/gt2/gt2Filter.c b/code/gamespy/gt2/gt2Filter.c new file mode 100644 index 00000000..27195af7 --- /dev/null +++ b/code/gamespy/gt2/gt2Filter.c @@ -0,0 +1,191 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Filter.h" +#include "gt2Callback.h" +#include "gt2Message.h" +#include "gt2Utility.h" + +static int GS_STATIC_CALLBACK gti2SendFiltersCompare +( + const void * elem1, + const void * elem2 +) +{ + gt2SendFilterCallback * callback1 = (gt2SendFilterCallback *)elem1; + gt2SendFilterCallback * callback2 = (gt2SendFilterCallback *)elem2; + + if(*callback1 == *callback2) + return 0; + + return 1; +} + +static int GS_STATIC_CALLBACK gti2ReceiveFiltersCompare +( + const void * elem1, + const void * elem2 +) +{ + gt2ReceiveFilterCallback * callback1 = (gt2ReceiveFilterCallback *)elem1; + gt2ReceiveFilterCallback * callback2 = (gt2ReceiveFilterCallback *)elem2; + + if(*callback1 == *callback2) + return 0; + + return 1; +} + +GT2Bool gti2AddSendFilter(GT2Connection connection, gt2SendFilterCallback callback) +{ + // Check if we have a send filters list. + if(!connection->sendFilters) + return GT2False; + + // Add this callback to the list. + ArrayAppend(connection->sendFilters, &callback); + + // Return GT2True if it was added. + return (ArraySearch(connection->sendFilters, &callback, gti2SendFiltersCompare, 0, 0) != NOT_FOUND); +} + +GT2Bool gti2AddReceiveFilter(GT2Connection connection, gt2ReceiveFilterCallback callback) +{ + // Check if we have a receive filters list. + if(!connection->receiveFilters) + return GT2False; + + // Add this callback to the list. + ArrayAppend(connection->receiveFilters, &callback); + + // Return GT2True if it was added. + return (ArraySearch(connection->receiveFilters, &callback, gti2ReceiveFiltersCompare, 0, 0) != NOT_FOUND); +} + +void gti2RemoveSendFilter(GT2Connection connection, gt2SendFilterCallback callback) +{ + int index; + + // Check for no filters. + if(!connection->sendFilters) + return; + + // check for removing all + if(!callback) + { + // Remove all the filters. + ArrayClear(connection->sendFilters); + return; + } + + // Find it. + index = ArraySearch(connection->sendFilters, &callback, gti2SendFiltersCompare, 0, 0); + if(index == NOT_FOUND) + return; + + // Remove it. + ArrayRemoveAt(connection->sendFilters, index); +} + +void gti2RemoveReceiveFilter(GT2Connection connection, gt2ReceiveFilterCallback callback) +{ + int index; + + // Check for no filters. + if(!connection->receiveFilters) + return; + + // check for removing all + if(!callback) + { + // Remove all the filters. + ArrayClear(connection->receiveFilters); + return; + } + + // Find it. + index = ArraySearch(connection->receiveFilters, &callback, gti2ReceiveFiltersCompare, 0, 0); + if(index == NOT_FOUND) + return; + + // Remove it. + ArrayRemoveAt(connection->receiveFilters, index); +} + +GT2Bool gti2FilteredSend(GT2Connection connection, int filterID, const GT2Byte * message, int len, GT2Bool reliable) +{ + int num; + + // Make sure we're connected. + if(connection->state != GTI2Connected) + return GT2True; + + // check the message and len + gti2MessageCheck(&message, &len); + + // Get the number of filters. + num = ArrayLength(connection->sendFilters); + + // Check if its a valid ID. + if(filterID < 0) + return GT2True; + if(filterID >= num) + return GT2True; + + // Is it the last one? + if(filterID == (num - 1)) + { + // Do the actual send. + if(!gti2Send(connection, message, len, reliable)) + return GT2False; + } + else + { + // Filter it. + if(!gti2SendFilterCallback(connection, ++filterID, message, len, reliable)) + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2FilteredReceive(GT2Connection connection, int filterID, GT2Byte * message, int len, GT2Bool reliable) +{ + int num; + + // Make sure we're connected. + if(connection->state != GTI2Connected) + return GT2True; + + // Get the number of filters. + num = ArrayLength(connection->receiveFilters); + + // Check if its a valid ID. + if(filterID < 0) + return GT2True; + if(filterID >= num) + return GT2True; + + // Is it the last one? + if(filterID == (num - 1)) + { + // call the callback + if(!gti2ReceivedCallback(connection, message, len, reliable)) + return GT2False; + } + else + { + // Filter it. + if(!gti2ReceiveFilterCallback(connection, ++filterID, message, len, reliable)) + return GT2False; + } + + return GT2True; +} diff --git a/code/gamespy/gt2/gt2Filter.h b/code/gamespy/gt2/gt2Filter.h new file mode 100644 index 00000000..a3595d97 --- /dev/null +++ b/code/gamespy/gt2/gt2Filter.h @@ -0,0 +1,24 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_FILTER_H_ +#define _GT2_FILTER_H_ + +#include "gt2Main.h" + +GT2Bool gti2AddSendFilter(GT2Connection connection, gt2SendFilterCallback callback); +void gti2RemoveSendFilter(GT2Connection connection, gt2SendFilterCallback callback); +GT2Bool gti2FilteredSend(GT2Connection connection, int filterID, const GT2Byte * message, int len, GT2Bool reliable); + +GT2Bool gti2AddReceiveFilter(GT2Connection connection, gt2ReceiveFilterCallback callback); +void gti2RemoveReceiveFilter(GT2Connection connection, gt2ReceiveFilterCallback callback); +GT2Bool gti2FilteredReceive(GT2Connection connection, int filterID, GT2Byte * message, int len, GT2Bool reliable); + +#endif diff --git a/code/gamespy/gt2/gt2Main.c b/code/gamespy/gt2/gt2Main.c new file mode 100644 index 00000000..9c688e2e --- /dev/null +++ b/code/gamespy/gt2/gt2Main.c @@ -0,0 +1,477 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Main.h" +#include "gt2Socket.h" +#include "gt2Connection.h" +#include "gt2Message.h" +#include "gt2Callback.h" +#include "gt2Filter.h" +#include "gt2Utility.h" + +#define GTI2_INVALID_IP_MASK 0xE0000000 + +/********************* +** SOCKET FUNCTIONS ** +*********************/ + +// Xbox VDP socket function(s). +#ifdef _XBOX +GT2Result gt2CreateVDPSocket +( + GT2Socket * socket, + const char * localAddress, + int outgoingBufferSize, + int incomingBufferSize, + gt2SocketErrorCallback callback +) +{ + return gti2CreateSocket(socket, localAddress, outgoingBufferSize, incomingBufferSize, callback, GTI2VdpProtocol); +} +#endif + + +GT2Result gt2CreateSocket +( + GT2Socket * socket, + const char * localAddress, + int outgoingBufferSize, + int incomingBufferSize, + gt2SocketErrorCallback callback +) +{ + return gti2CreateSocket(socket, localAddress, outgoingBufferSize, incomingBufferSize, callback, GTI2UdpProtocol); +} + +GT2Result gt2CreateAdHocSocket +( + GT2Socket * socket, + const char * localAddress, + int outgoingBufferSize, + int incomingBufferSize, + gt2SocketErrorCallback callback +) +{ + return gti2CreateSocket(socket, localAddress, outgoingBufferSize, incomingBufferSize, callback, GTI2AdHocProtocol); +} + + +void gt2CloseSocket(GT2Socket socket) +{ + // hard close the connections + gt2CloseAllConnectionsHard(socket); + + // close the socket + gti2CloseSocket(socket); +} + +void gt2Think(GT2Socket socket) +{ + // check for incoming messages + if(!gti2ReceiveMessages(socket)) + return; + + // let the connections think + if(!gti2SocketConnectionsThink(socket)) + return; + + // free closed connections + gti2FreeClosedConnections(socket); +} + +GT2Result gt2SendRawUDP +( + GT2Socket socket, + const char * remoteAddress, + const GT2Byte * message, + int len +) +{ + unsigned int ip; + unsigned short port; + + // get the ip and port + if(!gt2StringToAddress(remoteAddress, &ip, &port) || !port) + return GT2AddressError; + + // check for invalid IP ranges + // class D (224-239.*, multicast) and class E (240-255.*, experimental) + if((ntohl(ip) & GTI2_INVALID_IP_MASK) == GTI2_INVALID_IP_MASK) + return GT2AddressError; + + // check if this is for broadcast + if(!ip) + { + // check if broadcast is enable + if(!socket->broadcastEnabled) + { + if(!SetSockBroadcast(socket->socket)) + return GT2NetworkError; + socket->broadcastEnabled = GT2True; + } + + // set the broadcast ip + ip = gsiGetBroadcastIP(); + } + + // send the datagram + gti2SocketSend(socket, ip, port, message, len); + + return GT2Success; +} + +/********************* +** LISTEN FUNCTIONS ** +*********************/ + +void gt2Listen(GT2Socket socket, gt2ConnectAttemptCallback callback) +{ + gti2Listen(socket, callback); +} + +GT2Bool gt2Accept(GT2Connection connection, GT2ConnectionCallbacks * callbacks) +{ + return gti2AcceptConnection(connection, callbacks); +} + +void gt2Reject(GT2Connection connection, const GT2Byte * message, int len) +{ + gti2RejectConnection(connection, message, len); +} + +/************************* +** CONNECTION FUNCTIONS ** +*************************/ + +GT2Result gt2Connect +( + GT2Socket socket, + GT2Connection * connection, + const char * remoteAddress, + const GT2Byte * message, + int len, + int timeout, + GT2ConnectionCallbacks * callbacks, + GT2Bool blocking +) +{ + GT2Connection connectionTemp; + GT2Result result; + GT2Bool done; + unsigned int ip; + unsigned short port; + + { + // get the ip and port + if(!gt2StringToAddress(remoteAddress, &ip, &port) || !ip || !port) + return GT2AddressError; + } + + // check for invalid IP ranges + // class D (224-239.*, multicast) and class E (240-255.*, experimental) + if((ntohl(ip) & GTI2_INVALID_IP_MASK) == GTI2_INVALID_IP_MASK) + return GT2AddressError; + + // create the connection object + result = gti2NewOutgoingConnection(socket, &connectionTemp, ip, port); + if(result) + return result; + + // save the timeout value + connectionTemp->timeout = (unsigned int)timeout; + + // initiate the connection attempt + result = gti2StartConnectionAttempt(connectionTemp, message, len, callbacks); + if(result) + { + gti2FreeSocketConnection(connectionTemp); + return result; + } + + // if not blocking, return now + if(!blocking) + { + if(connection) + *connection = connectionTemp; + return GT2Success; + } + + // we're not really in a callback, but this will prevent the connection + // from being freed before the loop finishes. + connectionTemp->callbackLevel++; + + // if blocking, loop until the connect attempt is done + do + { + // think + gt2Think(socket); + + // check if we're done + done = (connectionTemp->state >= GTI2Connected); + + // if we're not done, take a rest + if(!done) + msleep(1); + } while(!done); + + // bring the callback level back down + connectionTemp->callbackLevel--; + + // is it success? + if(connectionTemp->state == GTI2Connected) + *connection = connectionTemp; + + return connectionTemp->connectionResult; +} + +GT2Result gt2Send +( + GT2Connection connection, + const GT2Byte * message, + int len, + GT2Bool reliable +) +{ + // used to check for voice data in reliable messages + unsigned short vdpDataLength; + + // can't send a message if not connected + if(connection->state != GTI2Connected) + return GT2InvalidConnection; + + // check the message and len + gti2MessageCheck(&message, &len); + + if (reliable && connection->socket->protocolType == GTI2VdpProtocol) + { + memcpy(&vdpDataLength, message, sizeof(unsigned short)); + assert(vdpDataLength + connection->socket->protocolOffset == len); + if (vdpDataLength + connection->socket->protocolOffset != len) + return GT2InvalidMessage; + } + + // do we need to filter it? + if(ArrayLength(connection->sendFilters)) + { + gti2SendFilterCallback(connection, 0, message, len, reliable); + return GT2Success; + } + + if (gti2Send(connection, message, len, reliable)) + return GT2Success; + + return GT2SendFailed; +} + +void gt2Ping(GT2Connection connection) +{ + gti2SendPing(connection); +} + +void gt2CloseConnection(GT2Connection connection) +{ + gti2CloseConnection(connection, GT2False); +} + +void gt2CloseConnectionHard(GT2Connection connection) +{ + gti2CloseConnection(connection, GT2True); +} + +static void gti2CloseAllConnectionsMap(void * elem, void * clientData) +{ + gt2CloseConnection(*(GT2Connection *)elem); + + GSI_UNUSED(clientData); +} + +void gt2CloseAllConnections(GT2Socket socket) +{ + TableMapSafe(socket->connections, gti2CloseAllConnectionsMap, NULL); +} + +static void gti2CloseAllConnectionsHardMap(void * elem, void * clientData) +{ + gt2CloseConnectionHard(*(GT2Connection *)elem); + + GSI_UNUSED(clientData); +} + +void gt2CloseAllConnectionsHard(GT2Socket socket) +{ + TableMapSafe(socket->connections, gti2CloseAllConnectionsHardMap, NULL); +} + +/************************* +** MESSAGE CONFIRMATION ** +*************************/ +GT2MessageID gt2GetLastSentMessageID(GT2Connection connection) +{ + return (GT2MessageID)(connection->serialNumber - 1); +} + +GT2Bool gt2WasMessageIDConfirmed(GT2Connection connection, GT2MessageID messageID) +{ + return gti2WasMessageIDConfirmed(connection, messageID); +} + +/********************* +** FILTER FUNCTIONS ** +*********************/ + +GT2Bool gt2AddSendFilter(GT2Connection connection, gt2SendFilterCallback callback) +{ + return gti2AddSendFilter(connection, callback); +} + +void gt2RemoveSendFilter(GT2Connection connection, gt2SendFilterCallback callback) +{ + gti2RemoveSendFilter(connection, callback); +} + +void gt2FilteredSend(GT2Connection connection, int filterID, const GT2Byte * message, int len, GT2Bool reliable) +{ + gti2FilteredSend(connection, filterID, message, len, reliable); +} + +GT2Bool gt2AddReceiveFilter(GT2Connection connection, gt2ReceiveFilterCallback callback) +{ + return gti2AddReceiveFilter(connection, callback); +} + +void gt2RemoveReceiveFilter(GT2Connection connection, gt2ReceiveFilterCallback callback) +{ + gti2RemoveReceiveFilter(connection, callback); +} + +void gt2FilteredReceive(GT2Connection connection, int filterID, GT2Byte * message, int len, GT2Bool reliable) +{ + gti2FilteredReceive(connection, filterID, message, len, reliable); +} + +/******************* +** INFO FUNCTIONS ** +*******************/ + +GT2Socket gt2GetConnectionSocket(GT2Connection connection) +{ + return connection->socket; +} + +GT2ConnectionState gt2GetConnectionState(GT2Connection connection) +{ + if(connection->state < GTI2Connected) + return GT2Connecting; + if(connection->state == GTI2Connected) + return GT2Connected; + if(connection->state == GTI2Closing) + return GT2Closing; + return GT2Closed; +} + +unsigned int gt2GetRemoteIP(GT2Connection connection) +{ + return connection->ip; +} + +unsigned short gt2GetRemotePort(GT2Connection connection) +{ + return connection->port; +} + +unsigned int gt2GetLocalIP(GT2Socket socket) +{ + return socket->ip; +} + +unsigned short gt2GetLocalPort(GT2Socket socket) +{ + return socket->port; +} + +int gt2GetIncomingBufferSize(GT2Connection connection) +{ + return connection->incomingBuffer.size; +} + +int gt2GetIncomingBufferFreeSpace(GT2Connection connection) +{ + return (connection->incomingBuffer.size - connection->incomingBuffer.len); +} + +int gt2GetOutgoingBufferSize(GT2Connection connection) +{ + return connection->outgoingBuffer.size; +} + +int gt2GetOutgoingBufferFreeSpace(GT2Connection connection) +{ + return (connection->outgoingBuffer.size - connection->outgoingBuffer.len); +} + +/***************************** +** SOCKET SHARING FUNCTIONS ** +*****************************/ + +SOCKET gt2GetSocketSOCKET(GT2Socket socket) +{ + return socket->socket; +} + +void gt2SetUnrecognizedMessageCallback(GT2Socket socket, gt2UnrecognizedMessageCallback callback) +{ + socket->unrecognizedMessageCallback = callback; +} + +/************************ +** USER DATA FUNCTIONS ** +************************/ + +void gt2SetSocketData(GT2Socket socket, void * data) +{ + assert(socket); + + socket->data = data; +} + +void * gt2GetSocketData(GT2Socket socket) +{ + assert(socket); + + return socket->data; +} + +void gt2SetConnectionData(GT2Connection connection, void * data) +{ + assert(connection); + + connection->data = data; +} + +void * gt2GetConnectionData(GT2Connection connection) +{ + assert(connection); + + return connection->data; +} + +/******************* +** DUMP FUNCTIONS ** +*******************/ + +void gt2SetSendDump(GT2Socket socket, gt2DumpCallback callback) +{ + socket->sendDumpCallback = callback; +} + +void gt2SetReceiveDump(GT2Socket socket, gt2DumpCallback callback) +{ + socket->receiveDumpCallback = callback; +} diff --git a/code/gamespy/gt2/gt2Main.h b/code/gamespy/gt2/gt2Main.h new file mode 100644 index 00000000..0915f45a --- /dev/null +++ b/code/gamespy/gt2/gt2Main.h @@ -0,0 +1,255 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_MAIN_H_ +#define _GT2_MAIN_H_ + +#include "gt2.h" +#include "../darray.h" +#include "../hashtable.h" +#include "gt2Auth.h" + +/************************* +** CONFIGURABLE DEFINES ** +*************************/ + +// these defines are internal to GT2 and are NOT guaranteed to persist from version to version. + + +// If set, this will convert all big endian vars to little endian before sending accross the net +// And on big endian machines, convert little endian to big endian on recv +//#define _GT2_ENDIAN_CONVERT_ENABLE // add this to your compiler pre-processor options + +#if defined GSI_BIG_ENDIAN && defined _GT2_ENDIAN_CONVERT_ENABLE + #define _GT2_ENDIAN_CONVERT +#endif + + + +// any unreliable application message that starts with this magic string will have extra overhead. +// the string can be changed to something that your application will not use, or not use frequently. +// the only impact of this change will be to make your application incomatible with other application's +// using either the original or another different magic string. +// the string can consist of any number of characters, as long as there's at least one character, and the +// length define matches the string's length. +#define GTI2_MAGIC_STRING "\xFE\xFE" +#define GTI2_MAGIC_STRING_LEN 2 + +// the size of the buffer into which GT2 directly receives messages. this buffer is declared on the stack, +// and so can be fairly large on most systems without having any impact. however, on some systems with small +// stacks, this size can overflow the stack, in which case it should be lowered. +// note, this buffer size only needs to be slighty larger than the largest message that will be sent ("slighty +// larger" due to overhead with reliable messages, and unreliable messages starting with the magic string). +#if defined(_PS2) && defined(INSOCK) + #define GTI2_STACK_RECV_BUFFER_SIZE NETBUFSIZE // Max for Insock. Otherwise SOCKET_ERROR +#elif defined(_NITRO) + #define GTI2_STACK_RECV_BUFFER_SIZE 1500 +#elif defined (_XBOX) // Xbox packets are 1304, + #define GTI2_STACK_RECV_BUFFER_SIZE 4096 // when using VDP sockets, 2 bytes are used for data length +#else + #define GTI2_STACK_RECV_BUFFER_SIZE 65535 +#endif + +// a server will disconnect a client that doesn't not successfully connect within this time (in milliseconds). +// if the connectAttemptCallback has been called, and GT2 is awaiting an accept/reject, the attempt will +// not be timed-out (although the client may abort the attempt at any time). +#define GTI2_SERVER_TIMEOUT (1 * 60 * 1000) +// the time (in milliseconds) GT2 waits between resending a message whose delivery has not yet been confirmed. +#define GTI2_RESEND_TIME 1000 +// the time (in milliseconds) GT2 waits after receiving a message it must acknowledge before it actually sends +// the ack. this allows it to combine acks, or include acks as part of other reliable messages it sends. +// if an ack is pending, a new incoming message does not reset this timer. +#define GTI2_PENDING_ACK_TIME 100 +// if GT2 does not send a message for this amount of time (in milliseconds), it sends a keep-alive message. +#define GTI2_KEEP_ALIVE_TIME (30 * 1000) +// if this is defined, it sets the percentage of sent datagrams to drop. this is good for simulating what will +// happen on a high packet loss connection. +//#define GTI2_DROP_SEND_RATE 30 +typedef enum +{ + GTI2UdpProtocol, // UDP socket type for standard sockets + GTI2VdpProtocol = 2, // VDP socket type only used for Xbox VDP sockets + GTI2AdHocProtocol = 3 // socket type only used for PSP Adhoc sockets +} GTI2ProtocolType; + +// The Maximum offset of eiter UDP or VDP +// measured in bytes +// used as a buffer offset +#define MAX_PROTOCOL_OFFSET 2 + +/********** +** TYPES ** +**********/ + +typedef enum +{ + // client-only states + GTI2AwaitingServerChallenge, // sent challenge, waiting for server's challenge + GTI2AwaitingAcceptance, // sent response, waiting for accept/reject from server + + // server-only states + GTI2AwaitingClientChallenge, // receiving challenge from a new client + GTI2AwaitingClientResponse, // sent challenge, waiting for client's response + GTI2AwaitingAcceptReject, // got client's response, waiting for app to accept/reject + + // post-negotiation states + GTI2Connected, // connected + GTI2Closing, // sent a close message (GTI2Close or GTI2Reject), waiting for confirmation + GTI2Closed // connection has been closed, free it as soon as possible +} GTI2ConnectionState; + +// message types +typedef enum +{ + // reliable messages + // all start with + // type is 1 bytes, SN and ESN are 2 bytes each + GTI2MsgAppReliable, // reliable application message + GTI2MsgClientChallenge, // client's challenge to the server (initial connection request) + GTI2MsgServerChallenge, // server's response to the client's challenge, and his challenge to the client + GTI2MsgClientResponse, // client's response to the server's challenge + GTI2MsgAccept, // server accepting client's connection attempt + GTI2MsgReject, // server rejecting client's connection attempt + GTI2MsgClose, // message indicating the connection is closing + GTI2MsgKeepAlive, // keep-alive used to help detect dropped connections + + GTI2NumReliableMessages, + + // unreliable messages + GTI2MsgAck = 100, // acknowledge receipt of reliable message(s) + GTI2MsgNack, // alert sender to missing reliable message(s) + GTI2MsgPing, // used to determine latency + GTI2MsgPong, // a reply to a ping + GTI2MsgClosed // confirmation of connection closure (GTI2MsgClose or GTI2MsgReject) - also sent in response to bad messages from unknown addresses + + // unreliable messages don't really have a message type, just the magic string repeated at the start +} GTI2MessageType; + +/*************** +** STRUCTURES ** +***************/ + +typedef struct GTI2Buffer +{ + GT2Byte * buffer; // The buffer's bytes. + int size; // Number of bytes in buffer. + int len; // Length of actual data in buffer. +} GTI2Buffer; + +typedef struct GTI2IncomingBufferMessage +{ + int start; // the start of the message + int len; // the length of the message + GTI2MessageType type; // the type + unsigned short serialNumber; // the serial number +} GTI2IncomingBufferMessage; + +typedef struct GTI2OutgoingBufferMessage +{ + int start; // the start of the message + int len; // the length of the message + unsigned short serialNumber; // the serial number + gsi_time lastSend; // last time this message was sent +} GTI2OutgoingBufferMessage; + +typedef struct GTI2Socket +{ + SOCKET socket; // the network socket used for all network communication + + unsigned int ip; // the ip this socket is bound to + unsigned short port; // the port this socket is bound to + + HashTable connections; // the connections that are using this socket + DArray closedConnections; // connections that are closed no longer get a spot in the hash table + + GT2Bool close; // if true, a close was attempted inside a callback, and it should be closed as soon as possible + GT2Bool error; // if true, there was a socket error using this socket + + int callbackLevel; // if >0, then we're inside a callback (or recursive callbacks) + gt2ConnectAttemptCallback connectAttemptCallback; // if set, callback used to handle incoming connection attempts + gt2SocketErrorCallback socketErrorCallback; // if set, call this in case of an error + gt2DumpCallback sendDumpCallback; // if set, gets called for every datagram sent + gt2DumpCallback receiveDumpCallback; // if set, gets called for every datagram and connection reset received + gt2UnrecognizedMessageCallback unrecognizedMessageCallback; // if set, gets called for all unrecognized messages + + void * data; // user data + + int outgoingBufferSize; // per-connection buffer sizes + int incomingBufferSize; + + GTI2ProtocolType protocolType; // set to UDP or VDP protocol depending on the call to create socket + // also used as an offset for VDP sockets + int protocolOffset; + GT2Bool broadcastEnabled; // set to true if the socket has already been broadcast enabled +} GTI2Socket; + +typedef struct GTI2Connection +{ + // ip and port uniquely identify this connection on this socket + unsigned int ip; // the ip on the other side of this connection (network byte order) + unsigned short port; // the port on the other side of this connection (host byte order) + + GTI2Socket * socket; // the parent socket + + GTI2ConnectionState state; // connection state + + GT2Bool initiated; // if true, the local side of the connection initiated the connection (client) + + GT2Bool freeAtAcceptReject; // if true, don't free the connection until accept/reject is called + + GT2Result connectionResult; // the result of the connect attempt + + gsi_time startTime; // the time the connection was created + gsi_time timeout; // the timeout value passed into gt2Connect + + int callbackLevel; // if >0, then we're inside a callback (or recursive callbacks) + GT2ConnectionCallbacks callbacks; // connection callbacks + + char * initialMessage; // this is the initial message for the client + int initialMessageLen; // the initial message length + + void * data; // user data + + GTI2Buffer incomingBuffer; // buffer for incoming data + GTI2Buffer outgoingBuffer; // buffer for outgoing data + DArray incomingBufferMessages; // identifies incoming messages stored in the buffer + DArray outgoingBufferMessages; // identifies outgoing messages stored in the buffer + + unsigned short serialNumber; // serial number of the next message to be sent out + unsigned short expectedSerialNumber; // the next serial number we're expecting from the remote side + + char response[GTI2_RESPONSE_LEN]; // after the challenge is sent during negotiation, this is the response we're expecting + + gsi_time lastSend; // the last time something was sent on this connection + gsi_time challengeTime; // the time the challenge was sent + + GT2Bool pendingAck; // if true, there is an ack waiting to go out, either on its own or as part of a reliable message + + gsi_time pendingAckTime; // the time at which the pending ack was first set + + DArray sendFilters; // filters that apply to outgoing data + DArray receiveFilters; // filters that apply to incoming data + +} GTI2Connection; + +// store last 32 ip's in a ring buffer +#define MAC_TABLE_SIZE 32 // must be power of 2 +typedef struct +{ + gsi_u32 ip; + char mac[6]; +} GTI2MacEntry; + +#ifdef GSI_ADHOC +static int lastmactableentry = 0; +static GTI2MacEntry MacTable[MAC_TABLE_SIZE]; +#endif // GSI_ADHOC + +#endif diff --git a/code/gamespy/gt2/gt2Message.c b/code/gamespy/gt2/gt2Message.c new file mode 100644 index 00000000..2ae44d8b --- /dev/null +++ b/code/gamespy/gt2/gt2Message.c @@ -0,0 +1,1802 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Message.h" +#include "gt2Buffer.h" +#include "gt2Connection.h" +#include "gt2Socket.h" +#include "gt2Callback.h" +#include "gt2Utility.h" +#include + +static unsigned short gti2UShortFromBuffer(const GT2Byte * buffer, int pos) +{ + unsigned short s; + s = (unsigned short)((buffer[pos] << 8) & 0xFF00); + pos++; + s |= buffer[pos]; + + return s; +} + + +static void gti2UShortToBuffer(GT2Byte * buffer, int pos, unsigned short s) +{ + buffer[pos++] = (GT2Byte)((s >> 8) & 0xFF); + buffer[pos] = (GT2Byte)(s & 0xFF); +} + +static int gti2SNDiff(unsigned short SN1, unsigned short SN2) +{ + return (short)(SN1 - SN2); +} + +static GT2Bool gti2ConnectionError(GT2Connection connection, GT2Result result, GT2CloseReason reason) +{ + // first check if we're still connecting + if(connection->state < GTI2Connected) + { + // check if the local side is the initiator + if(connection->initiated) + { + // mark it as closed + gti2ConnectionClosed(connection); + + // call the callback + if(!gti2ConnectedCallback(connection, result, NULL, 0)) + return GT2False; + } + else + { + // are we waiting for accept/reject? + if(connection->state == GTI2AwaitingAcceptReject) + connection->freeAtAcceptReject = GT2True; + + // mark it as closed + gti2ConnectionClosed(connection); + } + } + // report the close, as long as we're not already closed + else if(connection->state != GTI2Closed) + { + // mark it as closed + gti2ConnectionClosed(connection); + + // call the callback + if(!gti2ClosedCallback(connection, reason)) + return GT2False; + } + + return GT2True; +} + +static GT2Bool gti2ConnectionCommunicationError(GT2Connection connection) +{ + return gti2ConnectionError(connection, GT2NegotiationError, GT2CommunicationError); +} + +static GT2Bool gti2ConnectionMemoryError(GT2Connection connection) +{ + // let the other side know + if(!gti2SendClosed(connection)) + return GT2False; + + return gti2ConnectionError(connection, GT2OutOfMemory, GT2NotEnoughMemory); +} + + + + + +static GT2Bool gti2HandleESN(GT2Connection connection, unsigned short ESN) +{ + int len; + int i; + GTI2OutgoingBufferMessage * message; + int shortenBy; + + // get the number of messages in the outgoing queue + len = ArrayLength(connection->outgoingBufferMessages); + if(!len) + return GT2True; + + // loop through until we hit one we can't remove + for(i = 0 ; i < len ; i++) + { + // get the message + message = (GTI2OutgoingBufferMessage *)ArrayNth(connection->outgoingBufferMessages, i); + + // don't stop until we get to the ESN + if(gti2SNDiff(message->serialNumber, ESN) >= 0) + break; + } + + // check for not removing any + if(i == 0) + return GT2True; + + // remove the message info structs + while(i--) + ArrayDeleteAt(connection->outgoingBufferMessages, i); + + // check how many messages are left + len = ArrayLength(connection->outgoingBufferMessages); + if(!len) + { + // buffer is empty + connection->outgoingBuffer.len = 0; + return GT2True; + } + + // figure out how much to move everything forward + message = (GTI2OutgoingBufferMessage *)ArrayNth(connection->outgoingBufferMessages, 0); + shortenBy = message->start; + + // do the move on the info structs + for(i = 0 ; i < len ; i++) + { + message = (GTI2OutgoingBufferMessage *)ArrayNth(connection->outgoingBufferMessages, i); + message->start -= shortenBy; + } + + // move the actual data + gti2BufferShorten(&connection->outgoingBuffer, 0, shortenBy); + + return GT2True; +} + +static GT2Bool gti2HandleAppUnreliable(GT2Connection connection, GT2Byte * message, int len) +{ + // check the state + if((connection->state != GTI2Connected) && (connection->state != GTI2Closing)) + return GT2True; + + // do we need to filter it? + if(ArrayLength(connection->receiveFilters)) + { + if(!gti2ReceiveFilterCallback(connection, 0, message, len, GT2False)) + return GT2False; + return GT2True; + } + + // we received data, call the callback + if(!gti2ReceivedCallback(connection, message, len, GT2False)) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2HandleAppReliable(GT2Connection connection, GT2Byte * message, int len) +{ + // check the state + if((connection->state != GTI2Connected) && (connection->state != GTI2Closing)) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + } + else + { + // do we need to filter it? + if(ArrayLength(connection->receiveFilters)) + { + if(!gti2ReceiveFilterCallback(connection, 0, message, len, GT2True)) + return GT2False; + return GT2True; + } + + // we received data, call the callback + if(!gti2ReceivedCallback(connection, message, len, GT2True)) + return GT2False; + } + + return GT2True; +} + +static GT2Bool gti2HandleClientChallenge(GT2Connection connection, GT2Byte * message, int len) +{ + char response[GTI2_RESPONSE_LEN]; + char challenge[GTI2_CHALLENGE_LEN]; + + // check the state + if(connection->state != GTI2AwaitingClientChallenge) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // make sure the message is long enough + if(len < GTI2_CHALLENGE_LEN) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // generate a response to the challenge + gti2GetResponse((GT2Byte *)response, message); + + // generate our own challenge + gti2GetChallenge((GT2Byte *)challenge); + + // store what our response will be + gti2GetResponse((GT2Byte *)connection->response, (GT2Byte *)challenge); + + // send our own challenge + if(!gti2SendServerChallenge(connection, response, challenge)) + return GT2False; + + // new state + connection->state = GTI2AwaitingClientResponse; + + return GT2True; +} + + + +static GT2Bool gti2HandleServerChallenge(GT2Connection connection, GT2Byte * message, int len) +{ + char response[GTI2_RESPONSE_LEN]; + + // check the state + if(connection->state != GTI2AwaitingServerChallenge) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // make sure the message is long enough + if(len < (GTI2_RESPONSE_LEN + GTI2_CHALLENGE_LEN)) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // check the response + if(!gti2CheckResponse(message, (GT2Byte *)connection->response)) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // generate our response to the server's challenge + gti2GetResponse((GT2Byte *)response, message + GTI2_RESPONSE_LEN); + + // send the response, including our intial message + if(!gti2SendClientResponse(connection, response, connection->initialMessage, connection->initialMessageLen)) + return GT2False; + + // free the initial message + if(connection->initialMessage) + { + gsifree(connection->initialMessage); + connection->initialMessage = NULL; + } + + // new state + connection->state = GTI2AwaitingAcceptance; + + return GT2True; +} + +static GT2Bool gti2HandleClientResponse(GT2Connection connection, GT2Byte * message, int len) +{ + int latency; + + // check the state + if(connection->state != GTI2AwaitingClientResponse) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // make sure the message is long enough + if(len < (GTI2_RESPONSE_LEN)) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // check the response + if(!gti2CheckResponse(message, (GT2Byte *)connection->response)) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // need to make sure the connection didn't just stop listening + if(!connection->socket->connectAttemptCallback) + { + // send them a closed + if(!gti2SendClosed(connection)) + return GT2False; + + // mark it as closed + gti2ConnectionClosed(connection); + + return GT2True; + } + + // new state + connection->state = GTI2AwaitingAcceptReject; + + // calculate the approx. latency + latency = (int)(current_time() - connection->challengeTime); + + // it's up to the app now + if(!gti2ConnectAttemptCallback(connection->socket, connection, connection->ip, connection->port, latency, message + GTI2_RESPONSE_LEN, len - GTI2_RESPONSE_LEN)) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2HandleAccept(GT2Connection connection) +{ + // check the state + if(connection->state != GTI2AwaitingAcceptance) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // new state + connection->state = GTI2Connected; + + // call the callback + if(!gti2ConnectedCallback(connection, GT2Success, NULL, 0)) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2HandleReject(GT2Connection connection, GT2Byte * message, int len) +{ + // check the state + if(connection->state != GTI2AwaitingAcceptance) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // mark it as closed + gti2ConnectionClosed(connection); + + // send a closed reply + if(!gti2SendClosed(connection)) + return GT2False; + + // call the callback + if(!gti2ConnectedCallback(connection, GT2Rejected, message, len)) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2HandleClose(GT2Connection connection) +{ + GT2Bool localClose; + + // send a closed reply + if(!gti2SendClosed(connection)) + return GT2False; + + // were we attempting to close this connection? + localClose = (connection->state == GTI2Closing); + + // handle it as an error (so the right callbacks are called) + if(!gti2ConnectionError(connection, GT2Rejected, localClose?GT2LocalClose:GT2RemoteClose)) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2DeliverReliableMessage(GT2Connection connection, GTI2MessageType type, GT2Byte * message, int len) +{ + // bump our ESN + connection->expectedSerialNumber++; + + if(type == GTI2MsgAppReliable) + { + if(!gti2HandleAppReliable(connection, message, len)) + return GT2False; + } + else if(type == GTI2MsgClientChallenge) + { + if(!gti2HandleClientChallenge(connection, message, len)) + return GT2False; + } + else if(type == GTI2MsgServerChallenge) + { + if(!gti2HandleServerChallenge(connection, message, len)) + return GT2False; + } + else if(type == GTI2MsgClientResponse) + { + if(!gti2HandleClientResponse(connection, message, len)) + return GT2False; + } + else if(type == GTI2MsgAccept) + { + if(!gti2HandleAccept(connection)) + return GT2False; + } + else if(type == GTI2MsgReject) + { + if(!gti2HandleReject(connection, message, len)) + return GT2False; + } + else if(type == GTI2MsgClose) + { + if(!gti2HandleClose(connection)) + return GT2False; + } + else if(type == GTI2MsgKeepAlive) + { + // ignore + } + + return GT2True; +} +#ifdef WIN32 +static int __cdecl gti2IncomingBufferMessageCompare(const void * elem1, const void * elem2) +#else +static int gti2IncomingBufferMessageCompare(const void * elem1, const void * elem2) +#endif +{ + const GTI2IncomingBufferMessage * message1 = (GTI2IncomingBufferMessage *)elem1; + const GTI2IncomingBufferMessage * message2 = (GTI2IncomingBufferMessage *)elem2; + + return gti2SNDiff(message1->serialNumber, message2->serialNumber); +} + +static GT2Bool gti2BufferIncomingMessage(GT2Connection connection, GTI2MessageType type, unsigned short SN, GT2Byte * message, int len, GT2Bool * overflow) +{ + GTI2IncomingBufferMessage messageInfo; + GTI2IncomingBufferMessage * bufferedMessage; + int num; + int i; + + // check the number of messages being held + num = ArrayLength(connection->incomingBufferMessages); + + // check if this message is already buffered + for(i = 0 ; i < num ; i++) + { + // get the message + bufferedMessage = (GTI2IncomingBufferMessage *)ArrayNth(connection->incomingBufferMessages, i); + + // check if this is the same message + if(bufferedMessage->serialNumber == SN) + { + *overflow = GT2False; + return GT2True; + } + + // check if we've already past the target SN + if(gti2SNDiff(bufferedMessage->serialNumber, SN) > 0) + break; + } + + // check that there's enough space to store the message + if(gti2GetBufferFreeSpace(&connection->incomingBuffer) < len) + { + *overflow = GT2True; + return GT2True; + } + + // setup the message info + messageInfo.start = connection->incomingBuffer.len; + messageInfo.len = len; + messageInfo.type = type; + messageInfo.serialNumber = SN; + + // add it to the list + ArrayInsertSorted(connection->incomingBufferMessages, &messageInfo, gti2IncomingBufferMessageCompare); + + // make sure the length is one more + if(ArrayLength(connection->incomingBufferMessages) != (num + 1)) + { + *overflow = GT2True; + return GT2True; + } + + // copy the message into the buffer + gti2BufferWriteData(&connection->incomingBuffer, message, len); + + // check for sending a nack + // we want to send one when we think a message or messages were probably dropped + if(num == 0) + { + // if we're the only message in the hold, send a nack + if(!gti2SendNack(connection, connection->expectedSerialNumber, (unsigned short)(SN - 1))) + return GT2False; + } + else + { + GTI2IncomingBufferMessage * msg; + + // are we the highest message? + msg = (GTI2IncomingBufferMessage *)ArrayNth(connection->incomingBufferMessages, num); + if(msg->serialNumber == SN) + { + GTI2IncomingBufferMessage * prev; + unsigned short diff; + + // if we're not right after the second-highest SN, the ones in between were probably dropped + prev = (GTI2IncomingBufferMessage *)ArrayNth(connection->incomingBufferMessages, num - 1); + diff = (unsigned short)gti2SNDiff(SN, prev->serialNumber); + if(diff > 1) + { + if(!gti2SendNack(connection, (unsigned short)(prev->serialNumber + 1), (unsigned short)(SN - 1))) + return GT2False; + } + } + } + + *overflow = GT2False; + return GT2True; +} + +static void gti2RemoveHoldMessage(GT2Connection connection, GTI2IncomingBufferMessage * message, int index) +{ + int moveAfter; + int shortenBy; + int moveEnd = 0; + int num; + int i; + + // save off info about it + moveAfter = message->start; + shortenBy = message->len; + + // delete it + ArrayDeleteAt(connection->incomingBufferMessages, index); + + // loop through and fix up message's stored after this one in the buffer + // also figure out exactly how much data we'll need to move + num = ArrayLength(connection->incomingBufferMessages); + for(i = 0 ; i < num ; i++) + { + // check if this message needs to be moved forward + message = (GTI2IncomingBufferMessage *)ArrayNth(connection->incomingBufferMessages, i); + if(message->start > moveAfter) + { + message->start -= shortenBy; + moveEnd = max(moveEnd, message->start + message->len); + } + } + + // fix up the buffer itself + gti2BufferShorten(&connection->incomingBuffer, moveAfter, shortenBy); +} + +static GT2Bool gti2DeliverHoldMessages(GT2Connection connection) +{ + GTI2IncomingBufferMessage * message; + int num; + int i; + +restart: + + // loop through the buffered messages, checking if there are any that can now be delivered + // loop through backwards to ease removal + num = ArrayLength(connection->incomingBufferMessages); + for(i = (num - 1) ; i >= 0 ; i--) + { + message = (GTI2IncomingBufferMessage *)ArrayNth(connection->incomingBufferMessages, i); + + // we should deliver this if it's what we're expecting + if(message->serialNumber == connection->expectedSerialNumber) + { + // deliver it + if(!gti2DeliverReliableMessage(connection, message->type, connection->incomingBuffer.buffer + message->start, message->len)) + return GT2False; + + // remove it + gti2RemoveHoldMessage(connection, message, i); + + // we need to go through this loop again. + // goto's are evil, but a little evil is good here + goto restart; + } + } + + return GT2True; +} + +static void gti2SetPendingAck(GT2Connection connection) +{ + // if there's not a pending ack, set one + if(!connection->pendingAck) + { + connection->pendingAck = GT2True; + connection->pendingAckTime = current_time(); + } +} + +static GT2Bool gti2HandleReliableMessage(GT2Connection connection, GTI2MessageType type, GT2Byte * message, int len) +{ + unsigned short SN; + unsigned short ESN; + const int headerLength = connection->socket->protocolOffset + GTI2_MAGIC_STRING_LEN + 1 + 2 + 2; + GT2Bool overflow; + + // check if it's long enough + if(len < (connection->socket->protocolOffset + GTI2_MAGIC_STRING_LEN + 1 + 2 + 2)) // magic string + type + SN + ESN + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // get the SN + SN = gti2UShortFromBuffer(message + connection->socket->protocolOffset, GTI2_MAGIC_STRING_LEN + 1); + + // get the ESN + ESN = gti2UShortFromBuffer(message + connection->socket->protocolOffset, GTI2_MAGIC_STRING_LEN + 3); + + // update the message and length to point to the actual message + if (connection->socket->protocolType == GTI2VdpProtocol && type == GTI2MsgAppReliable) + { + message[connection->socket->protocolOffset + GTI2_MAGIC_STRING_LEN + 3] = message[0]; + message[connection->socket->protocolOffset + GTI2_MAGIC_STRING_LEN + 4] = message[1]; + message += headerLength - connection->socket->protocolOffset; + len -= headerLength - connection->socket->protocolOffset; + } + else + { + message += headerLength; + len -= headerLength; + } + + // handle the ESN + if(!gti2HandleESN(connection, ESN)) + return GT2False; + + // check if it's the SN we expected + if(SN == connection->expectedSerialNumber) + { + // make sure we ack this message + // do this before delivering, because we might send an ack as part of a reliable reply + gti2SetPendingAck(connection); + + // deliver the message + if(!gti2DeliverReliableMessage(connection, type, message, len)) + return GT2False; + + // check if there are any messages in the hold that can now be delivered + if(!gti2DeliverHoldMessages(connection)) + return GT2False; + + return GT2True; + } + + // check if the message is a duplicate + if(gti2SNDiff(SN, connection->expectedSerialNumber) < 0) + { + // it's a duplicate, ack it again + gti2SetPendingAck(connection); + + // ignore it + return GT2True; + } + + // we can't deliver this yet, so put it in the hold + if(!gti2BufferIncomingMessage(connection, type, SN, message, len, &overflow)) + return GT2False; + + // check for a buffer overflow + if(overflow) + { + if(!gti2ConnectionMemoryError(connection)) + return GT2False; + } + + return GT2True; +} + +static GT2Bool gti2HandleAck(GT2Connection connection, const GT2Byte * message, int len) +{ + unsigned short ESN; + + // make sure it has enough space for the ESN + if(len != 2) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // get the ESN + ESN = gti2UShortFromBuffer(message, 0); + + // handle it + if(!gti2HandleESN(connection, ESN)) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2HandleNack(GT2Connection connection, const GT2Byte * message, int len) +{ + unsigned short SNMin; + unsigned short SNMax; + int num; + int i; + GTI2OutgoingBufferMessage * messageInfo; + + // read based on length. + SNMin = gti2UShortFromBuffer(message, 0); + if(len == 2) + { + SNMax = SNMin; + } + else if(len == 4) + { + SNMax = gti2UShortFromBuffer(message, 2); + } + else + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // loop through the messages, resending any specified ones + num = ArrayLength(connection->outgoingBufferMessages); + for(i = 0 ; i < num ; i++) + { + messageInfo = (GTI2OutgoingBufferMessage *)ArrayNth(connection->outgoingBufferMessages, i); + if((gti2SNDiff(messageInfo->serialNumber, SNMin) >= 0) && (gti2SNDiff(messageInfo->serialNumber, SNMax) <= 0)) + { + if(!gti2ResendMessage(connection, messageInfo)) + return GT2False; + } + } + + return GT2True; +} + +static GT2Bool gti2HandlePing(GT2Connection connection, GT2Byte * message, int len) +{ + // send it right back + return gti2SendPong(connection, message, len); +} + +static GT2Bool gti2HandlePong(GT2Connection connection, const GT2Byte * message, int len) +{ + gsi_time startTime; + + // do we care about this? + if(!connection->callbacks.ping) + return GT2True; + + // is this a pong we're interested in? + // "time" + ping-sent-time + if(len != (4 + sizeof(gsi_time))) + return GT2True; + if(memcmp(message, "time", 4) != 0) + return GT2True; + + // get the start time + memcpy(&startTime, message + 4, sizeof(gsi_time)); + + // call the callback + if(!gti2PingCallback(connection, (int)(current_time() - startTime))) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2HandleClosed(GT2Connection connection) +{ + GT2Bool localClose; + + // are we already closed? + if(connection->state == GTI2Closed) + return GT2True; + + // were we attempting to close this connection? + localClose = (connection->state == GTI2Closing); + + // handle it as an error (so the right callbacks are called) + if(!gti2ConnectionError(connection, GT2Rejected, localClose?GT2LocalClose:GT2RemoteClose)) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2HandleUnreliableMessage(GT2Connection connection, GTI2MessageType type, GT2Byte * message, int len) +{ + int headerLength; + GT2Byte * dataStart; + int dataLen; + + // most unreliable messages don't need the header + headerLength = (connection->socket->protocolOffset + GTI2_MAGIC_STRING_LEN + 1); + dataStart = (message + headerLength); + dataLen = (len - headerLength); + + // handle unreliable messages based on type + if(type == GTI2MsgAck) + { + if(!gti2HandleAck(connection, dataStart, dataLen)) + return GT2False; + } + else if(type == GTI2MsgNack) + { + if(!gti2HandleNack(connection, dataStart, dataLen)) + return GT2False; + } + else if(type == GTI2MsgPing) + { + if(!gti2HandlePing(connection, message, len)) + return GT2False; + } + else if(type == GTI2MsgPong) + { + if(!gti2HandlePong(connection, dataStart, dataLen)) + return GT2False; + } + else if(type == GTI2MsgClosed) + { + if(!gti2HandleClosed(connection)) + return GT2False; + } + + return GT2True; +} + +// VDP sockets have data length which needs to be stripped off +static GT2Bool gti2HandleMessage(GT2Socket socket, GT2Byte * message, int len, unsigned int ip, unsigned short port) +{ + GT2Connection connection; + GT2Bool magicString; + GT2Result result; + GTI2MessageType type; + GT2Bool handled; + int actualLength = len - socket->protocolOffset; + + // VDP messages have 2 byte header which is removed based on protocol + GT2Byte *actualMessage = message + socket->protocolOffset; + + // find out if we have an existing connection for this address + connection = gti2SocketFindConnection(socket, ip, port); + + // let the dump handle this + if(socket->receiveDumpCallback) + { + if(!gti2DumpCallback(socket, connection, ip, port, GT2False, message, len, GT2False)) + return GT2False; + } + + // check if the message starts with the magic string + // use greater than for the len compare because it also must have a type + magicString = ((actualLength > GTI2_MAGIC_STRING_LEN) && (memcmp(actualMessage, GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN) == 0)); + + // check if we don't have a connection + if(!connection) + { + // if we don't know who this is from, let the unrecognized message callback have first crack at it + if(!gti2UnrecognizedMessageCallback(socket, ip, port, message, len, &handled)) + return GT2False; + + // if they handled it, we don't care about it. + if(handled) + return GT2True; + + // if this isn't a connection request, tell them the connection is closed + if(!magicString || (actualMessage[GTI2_MAGIC_STRING_LEN] != GTI2MsgClientChallenge)) + { + // if this is a closed message, don't send one back (to avoid recursion) + if(!magicString || (actualMessage[GTI2_MAGIC_STRING_LEN] != GTI2MsgClosed)) + { + if(!gti2SendClosedOnSocket(socket, ip, port)) + return GT2False; + } + return GT2True; + } + + // if we're not listening, we just ignore this + if(!socket->connectAttemptCallback) + return GT2True; + + // create a connection + result = gti2NewIncomingConnection(socket, &connection, ip, port); + if(result != GT2Success) + { + // as long as this wasn't a duplicate address error, tell them we're closed + // in the case of duplicates, we don't want to close the existing connection + if(result != GT2DuplicateAddress) + { + if(!gti2SendClosedOnSocket(socket, ip, port)) + return GT2False; + } + return GT2True; + } + } + + // is the connection already closed? + if(connection->state == GTI2Closed) + { + // if this is a closed message, don't send one back (to avoid recursion) + if(!magicString || (actualMessage[GTI2_MAGIC_STRING_LEN] != GTI2MsgClosed)) + { + if(!gti2SendClosed(connection)) + return GT2False; + } + + return GT2True; + } + + // check if this is an unreliable app message with a magic string header + if(magicString && ((actualLength >= (GTI2_MAGIC_STRING_LEN * 2)) && (memcmp(actualMessage + GTI2_MAGIC_STRING_LEN, GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN) == 0))) + { + message[3] = message[1]; + message[2] = message[0]; + message = actualMessage; + actualMessage += socket->protocolOffset; + actualLength -= socket->protocolOffset; + len -= GTI2_MAGIC_STRING_LEN; + magicString = GT2False; + } + + // if it doesn't have a magic string it's an app unreliable + if(!magicString) + { + // First determine if the connection found has gone throught the internal challenge response + if (connection->state < GTI2Connected) + { + // pass any message that doesn't have a magic string to + // the app so that the SDK doesn't drop them + if(!gti2UnrecognizedMessageCallback(socket, ip, port, message, len, &handled)) + return GT2False; + } + else + { + if(!gti2HandleAppUnreliable(connection, message, len)) + return GT2False; + } + + return GT2True; + } + + // get the message type + type = (GTI2MessageType)actualMessage[GTI2_MAGIC_STRING_LEN]; + // check for a bad type + /*if(type < 0) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + }*/ + + + // check if it's reliable + if(type < GTI2NumReliableMessages) + { + // handle it + if(!gti2HandleReliableMessage(connection, type, message, len)) + return GT2False; + return GT2True; + } + + // handle unreliable messages + if(!gti2HandleUnreliableMessage(connection, type, message, len)) + return GT2False; + + return GT2True; +} + + +GT2Bool gti2HandleConnectionReset(GT2Socket socket, unsigned int ip, unsigned short port) +{ + GT2Connection connection; + + // find the connection for the reset + connection = gti2SocketFindConnection(socket, ip, port); + + // let the dump know about this + if(socket->receiveDumpCallback) + { + if(!gti2DumpCallback(socket, connection, ip, port, GT2True, NULL, 0, GT2False)) + return GT2False; + } + + // there's no connection, so ignore it + if(!connection) + return GT2True; + + // are we waiting for a response from the server? + if(connection->state == GTI2AwaitingServerChallenge) + { + // are we still within the timeout time? + if(!connection->timeout || ((current_time() - connection->startTime) < connection->timeout)) + return GT2True; + + // report this as a timeout + if(!gti2ConnectionError(connection, GT2TimedOut, GT2RemoteClose)) + return GT2False; + } + else + { + // report the error + if(!gti2ConnectionError(connection, GT2Rejected, GT2RemoteClose)) + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2HandleHostUnreachable(GT2Socket socket, unsigned int ip, unsigned short port, GT2Bool send) +{ + GT2Connection connection; + + // find the connection for the reset + connection = gti2SocketFindConnection(socket, ip, port); + + // let the dump know about this + if(socket->receiveDumpCallback) + { + if(!gti2DumpCallback(socket, connection, ip, port, GT2True, NULL, 0, send)) + return GT2False; + } + + // there's no connection, so ignore it + if(!connection) + return GT2True; + + + // report the error + if(!gti2ConnectionError(connection, GT2TimedOut, GT2RemoteClose)) + return GT2False; + + return GT2True; +} + + +#ifdef GSI_ADHOC + + + +// return length if successful +// <=0 on error +gsi_bool _NetworkAdHocSocketRecv(int socket_id, + char *buf, + int bufferlen, + int flags, + char *saddr, //struct SceNetEtherAddr = char[6]; + gsi_u16 *sport); + + + + + + + + +// return 0 if no data, -1 if error, >0 if data to read +int _NetworkAdHocCanReceiveOnSocket(int socket_id); + +GT2Bool gti2ReceiveAdHocMessages(GT2Socket socket,char *buffer, int buffersize) +{ + int rcode; + SOCKADDR_IN address; + int addressLen;//, datasize; + + // check for messages + while (1) + { + int datasize = _NetworkAdHocCanReceiveOnSocket(socket->socket); + if (datasize < 0) // error + { + gti2SocketError(socket); + return GT2False; + } + + if (datasize == 0) + break; // no data + { + // We have data to recv + // receive the message + char mac[6]; + gsi_u16 port; + //gsi_u32 ip; + + addressLen = sizeof(address); + + rcode = _NetworkAdHocSocketRecv(socket->socket, buffer,buffersize , 0, mac,&port); + if(rcode < 0) // fatal socket error + { + #if(0) // notes + if(0)//rcode == WSAECONNRESET) + { + // handle the reset + if(!gti2HandleConnectionReset(socket, address.sin_addr.s_addr, ntohs(address.sin_port))) + return GT2False; + } + else + if (rcode == WSAEHOSTUNREACH) + { + if (!gti2HandleHostUnreachable(socket, address.sin_addr.s_addr, ntohs(address.sin_port), GT2False)) + return GT2False; + } + else + #endif + { + gti2SocketError(socket); + return GT2False; + } + } + if(rcode == 0) // no data + { + return GT2False; + } + + // at this point we have valid data + + // change ethernet to IP address + address.sin_addr.s_addr = gt2MacToIp(mac); + address.sin_port = port; + + #ifdef RECV_LOG + // log it + gti2LogMessage(address.sin_addr.s_addr, ntohs(address.sin_port), + socket->ip, socket->port, + buffer, rcode); + #endif + // handle the message + if(!gti2HandleMessage(socket, (GT2Byte *)buffer, rcode, address.sin_addr.s_addr, address.sin_port)) + return GT2False; + } + } + + return GT2True; +} +#endif + +GT2Bool gti2ReceiveMessages(GT2Socket socket) +{ + int rcode; + SOCKADDR_IN address; + int addressLen; + + + // avoid overflowing stack + #if (GTI2_STACK_RECV_BUFFER_SIZE > 1600) + static char buffer[GTI2_STACK_RECV_BUFFER_SIZE]; + #else + char buffer[GTI2_STACK_RECV_BUFFER_SIZE]; + #endif + + + #ifdef GSI_ADHOC + if(socket->protocolType == GTI2AdHocProtocol) + { + return gti2ReceiveAdHocMessages(socket,buffer,GTI2_STACK_RECV_BUFFER_SIZE); + } + #endif + + // check for messages + while (CanReceiveOnSocket(socket->socket)) + { + // mj todo: get this plat specific stuff out of here. Belongs in play specific layer. + // abstract recvfrom + + // receive the message + addressLen = sizeof(address); + + rcode = recvfrom(socket->socket, buffer, sizeof(buffer), 0, (SOCKADDR *)&address, &addressLen); + + if (gsiSocketIsError(rcode)) + { + rcode = GOAGetLastError(socket->socket); + if(rcode == WSAECONNRESET) + { + // handle the reset + if(!gti2HandleConnectionReset(socket, address.sin_addr.s_addr, ntohs(address.sin_port))) + return GT2False; + } + #ifndef SN_SYSTEMS + else if (rcode == WSAEHOSTUNREACH) + { + if (!gti2HandleHostUnreachable(socket, address.sin_addr.s_addr, ntohs(address.sin_port), GT2False)) + return GT2False; + } + #endif + else if(rcode != WSAEMSGSIZE) + { + // fatal socket error + gti2SocketError(socket); + return GT2False; + } + } + else + { + #ifdef RECV_LOG + // log it + gti2LogMessage(address.sin_addr.s_addr, ntohs(address.sin_port), + socket->ip, socket->port, + buffer, rcode); + #endif + // handle the message + if(!gti2HandleMessage(socket, (GT2Byte *)buffer, rcode, address.sin_addr.s_addr, ntohs(address.sin_port))) + return GT2False; + } + } + + return GT2True; +} + +static GT2Bool gti2StoreOutgoingReliableMessageInfo(GT2Connection connection, unsigned short SN, int len) +{ + GTI2OutgoingBufferMessage messageInfo; + int num; + + // setup the message info + memset(&messageInfo, 0, sizeof(messageInfo)); + messageInfo.start = connection->outgoingBuffer.len; + messageInfo.len = len; + messageInfo.serialNumber = SN; + messageInfo.lastSend = current_time(); + + // check the number of messages before we do the add + num = ArrayLength(connection->outgoingBufferMessages); + + // add it to the list + ArrayAppend(connection->outgoingBufferMessages, &messageInfo); + + // make sure the length is one more + if(ArrayLength(connection->outgoingBufferMessages) != (num + 1)) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2BeginReliableMessage(GT2Connection connection, GTI2MessageType type, int len, GT2Bool * overflow) +{ + int freeSpace; + + // VDP data length needed in the front of every packet + unsigned short vdpDataLength = (unsigned short)(len - connection->socket->protocolOffset); + + // check how much free space is in the outgoing buffer + freeSpace = gti2GetBufferFreeSpace(&connection->outgoingBuffer); + + // do we have the space to hold it? + if(freeSpace < len) + { + if(!gti2ConnectionMemoryError(connection)) + return GT2False; + + *overflow = GT2True; + return GT2True; + } + + // store the message's info + if(!gti2StoreOutgoingReliableMessageInfo(connection, connection->serialNumber, len)) + { + if(!gti2ConnectionMemoryError(connection)) + return GT2False; + + *overflow = GT2True; + return GT2True; + } + + // setup the header + if (connection->socket->protocolType == GTI2VdpProtocol) + gti2BufferWriteData(&connection->outgoingBuffer, (const GT2Byte *)&vdpDataLength, connection->socket->protocolOffset); + gti2BufferWriteData(&connection->outgoingBuffer, (const GT2Byte *)GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN); + gti2BufferWriteByte(&connection->outgoingBuffer, (GT2Byte)type); + gti2BufferWriteUShort(&connection->outgoingBuffer, connection->serialNumber++); + gti2BufferWriteUShort(&connection->outgoingBuffer, connection->expectedSerialNumber); + + *overflow = GT2False; + return GT2True; +} + +static GT2Bool gti2EndReliableMessage(GT2Connection connection) +{ + GTI2OutgoingBufferMessage * message; + int len; + + // the message we're sending is the last one + len = ArrayLength(connection->outgoingBufferMessages); + assert(len > 0); + message = (GTI2OutgoingBufferMessage *)ArrayNth(connection->outgoingBufferMessages, len - 1); + + // send it + if(!gti2ConnectionSendData(connection, connection->outgoingBuffer.buffer + message->start, message->len)) + return GT2False; + + // we just did an ack (as part of the message) + connection->pendingAck = GT2False; + + return GT2True; +} + +GT2Bool gti2SendAppReliable(GT2Connection connection, const GT2Byte * message, int len) +{ + int totalLen; + GT2Bool overflow; + + // magic string + type + SN + ESN + message + totalLen = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2 + len); + + // begin the message + if(!gti2BeginReliableMessage(connection, GTI2MsgAppReliable, totalLen, &overflow)) + return GT2False; + if(overflow) + return GT2True; + + // write the message + gti2BufferWriteData(&connection->outgoingBuffer, message, len); + + // end the message + if(!gti2EndReliableMessage(connection)) + return GT2False; + + return GT2True; +} + +GT2Bool gti2SendClientChallenge(GT2Connection connection, const char challenge[GTI2_CHALLENGE_LEN]) +{ + // magic string + type + SN + ESN + challenge + int totalLen = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2 + GTI2_CHALLENGE_LEN); + GT2Bool overflow; + + // begin the message + if(!gti2BeginReliableMessage(connection, GTI2MsgClientChallenge, totalLen + connection->socket->protocolOffset, &overflow)) + return GT2False; + + if(overflow) + return GT2True; + + // write the challenge + gti2BufferWriteData(&connection->outgoingBuffer, (const GT2Byte *)challenge, GTI2_CHALLENGE_LEN); + + // end the message + if(!gti2EndReliableMessage(connection)) + return GT2False; + + return GT2True; +} + +GT2Bool gti2SendServerChallenge(GT2Connection connection, const char response[GTI2_RESPONSE_LEN], const char challenge[GTI2_CHALLENGE_LEN]) +{ + // magic string + type + SN + ESN + response + challenge + int totalLen = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2 + GTI2_RESPONSE_LEN + GTI2_CHALLENGE_LEN); + GT2Bool overflow; + + // begin the message + if(!gti2BeginReliableMessage(connection, GTI2MsgServerChallenge, totalLen + connection->socket->protocolOffset, &overflow)) + return GT2False; + + if(overflow) + return GT2True; + + // write the response + gti2BufferWriteData(&connection->outgoingBuffer, (const GT2Byte *)response, GTI2_RESPONSE_LEN); + + // write the challenge + gti2BufferWriteData(&connection->outgoingBuffer, (const GT2Byte *)challenge, GTI2_CHALLENGE_LEN); + + // end the message + if(!gti2EndReliableMessage(connection)) + return GT2False; + + // save the time + connection->challengeTime = connection->lastSend; + + return GT2True; +} + +GT2Bool gti2SendClientResponse(GT2Connection connection, const char response[GTI2_RESPONSE_LEN], const char * message, int len) +{ + // magic string + type + SN + ESN + response + message + int totalLen = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2 + GTI2_RESPONSE_LEN + len); + GT2Bool overflow; + + // begin the message + if(!gti2BeginReliableMessage(connection, GTI2MsgClientResponse, totalLen + connection->socket->protocolOffset, &overflow)) + return GT2False; + + if(overflow) + return GT2True; + + // write the response + gti2BufferWriteData(&connection->outgoingBuffer, (const GT2Byte *)response, GTI2_RESPONSE_LEN); + + // write the message + gti2BufferWriteData(&connection->outgoingBuffer, (const GT2Byte *)message, len); + + // end the message + if(!gti2EndReliableMessage(connection)) + return GT2False; + + return GT2True; +} + +GT2Bool gti2SendAccept(GT2Connection connection) +{ + // magic string + type + SN + ESN + int totalLen = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2); + GT2Bool overflow; + + // begin the message + if(!gti2BeginReliableMessage(connection, GTI2MsgAccept, totalLen + connection->socket->protocolOffset, &overflow)) + return GT2False; + + if(overflow) + return GT2True; + + // end the message + if(!gti2EndReliableMessage(connection)) + return GT2False; + + return GT2True; +} + +GT2Bool gti2SendReject(GT2Connection connection, const GT2Byte * message, int len) +{ + // magic string + type + SN + ESN + message + int totalLen = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2 + len); + GT2Bool overflow; + + // begin the message + if(!gti2BeginReliableMessage(connection, GTI2MsgReject, totalLen + connection->socket->protocolOffset, &overflow)) + return GT2False; + + if(overflow) + return GT2True; + + // write the message + gti2BufferWriteData(&connection->outgoingBuffer, message, len); + + // end the message + if(!gti2EndReliableMessage(connection)) + return GT2False; + + return GT2True; +} + +GT2Bool gti2SendClose(GT2Connection connection) +{ + // magic string + type + SN + ESN + int totalLen = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2); + GT2Bool overflow; + + // begin the message + if(!gti2BeginReliableMessage(connection, GTI2MsgClose, totalLen + connection->socket->protocolOffset, &overflow)) + return GT2False; + + if(overflow) + return GT2True; + + // end the message + if(!gti2EndReliableMessage(connection)) + return GT2False; + + return GT2True; +} + +GT2Bool gti2SendKeepAlive(GT2Connection connection) +{ + // magic string + type + SN + ESN + int totalLen = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2); + GT2Bool overflow; + + // begin the message + if(!gti2BeginReliableMessage(connection, GTI2MsgKeepAlive, totalLen + connection->socket->protocolOffset, &overflow)) + return GT2False; + + if(overflow) + return GT2True; + + // end the message + if(!gti2EndReliableMessage(connection)) + return GT2False; + + return GT2True; +} + +GT2Bool gti2SendAppUnreliable(GT2Connection connection, const GT2Byte * message, int len) +{ + int freeSpace; + int totalLen; + GT2Byte * start; + + // check if we can send it right away (unreliable that doesn't start with the magic string) + if((len < GTI2_MAGIC_STRING_LEN) || + (memcmp(message + connection->socket->protocolOffset, GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN) != 0)) + { + if(!gti2ConnectionSendData(connection, message, len)) + return GT2False; + + return GT2True; + } + + // magic string + message + totalLen = (GTI2_MAGIC_STRING_LEN + len); + + // check how much free space is in the outgoing buffer + freeSpace = gti2GetBufferFreeSpace(&connection->outgoingBuffer); + + // do we have the space to hold it? + if(freeSpace < totalLen) + { + // just drop it + return GT2True; + } + + // store the start of the actual message in the buffer + start = (connection->outgoingBuffer.buffer + connection->outgoingBuffer.len); + + // Copy the VDP data length if necessary + if (connection->socket->protocolType == GTI2VdpProtocol) + gti2BufferWriteData(&connection->outgoingBuffer, message, 2); + + // copy it in, repeating the magic string at the beginning + gti2BufferWriteData(&connection->outgoingBuffer, (const GT2Byte *)GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN); + + // copy the data at the starting position + offset based on the protocol + gti2BufferWriteData(&connection->outgoingBuffer, message + connection->socket->protocolOffset, + (int)(len - connection->socket->protocolOffset)); + + // do the send + if(!gti2ConnectionSendData(connection, start, totalLen)) + return GT2False; + + // we don't need to save the message + gti2BufferShorten(&connection->outgoingBuffer, -1, totalLen); + + return GT2True; +} + +GT2Bool gti2SendAck(GT2Connection connection) +{ + // always allocate data length + magic string + type + ESN + // part of the buffer may not be used but more efficience to be on stack + char buffer[MAX_PROTOCOL_OFFSET + GTI2_MAGIC_STRING_LEN + 1 + 2]; + int pos = 0; + + // write the VDP data length + if (connection->socket->protocolType == GTI2VdpProtocol) + { + short dataLength = (GTI2_MAGIC_STRING_LEN + 1 + 2); + memcpy(buffer, &dataLength, 2); + pos += 2; + } + + // write the magic string + memcpy(buffer + pos, GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN); + pos += GTI2_MAGIC_STRING_LEN; + + // write the type + buffer[pos++] = GTI2MsgAck; + + // write the ESN + gti2UShortToBuffer((GT2Byte *)buffer, pos, connection->expectedSerialNumber); + pos += 2; + + // send it + if(!gti2ConnectionSendData(connection, (const GT2Byte *)buffer, pos)) + return GT2False; + + // we just did an ack + connection->pendingAck = GT2False; + + return GT2True; +} + + +GT2Bool gti2SendNack(GT2Connection connection, unsigned short SNMin, unsigned short SNMax) +{ + // data length + magic string + type + SNMin [+ SNMax] + // part of the buffer may not be used but more efficience to be on stack + char buffer[MAX_PROTOCOL_OFFSET + GTI2_MAGIC_STRING_LEN + 1 + 2 + 2]; + int pos = 0; + + // write the VDP data length + if (connection->socket->protocolType == GTI2VdpProtocol) + { + short dataLength = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2); + memcpy(buffer, &dataLength, 2); + pos += 2; + } + + // write the magic string + memcpy(buffer + pos, GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN); + pos += GTI2_MAGIC_STRING_LEN; + + // write the type + buffer[pos++] = GTI2MsgNack; + + // write the SNMin + gti2UShortToBuffer((GT2Byte *)buffer, pos, SNMin); + pos += 2; + + // write the SNMax if it's different + if(SNMin != SNMax) + { + gti2UShortToBuffer((GT2Byte *)buffer, pos, SNMax); + pos += 2; + } + + // send it + if(!gti2ConnectionSendData(connection, (const GT2Byte *)buffer, pos)) + return GT2False; + + return GT2True; +} + + +GT2Bool gti2SendPing(GT2Connection connection) +{ + // data length + magic string + type + "time" + current time + // part of the buffer may not be used but more efficience to be on stack + char buffer[MAX_PROTOCOL_OFFSET + GTI2_MAGIC_STRING_LEN + 1 + 4 + sizeof(gsi_time)]; + int pos = 0; + gsi_time now; + + // write the VDP data length + if (connection->socket->protocolType == GTI2VdpProtocol) + { + short dataLength = (GTI2_MAGIC_STRING_LEN + 1 + 4 + sizeof(gsi_time)); + memcpy(buffer, &dataLength, 2); + pos += 2; + } + // write the magic string + memcpy(buffer + pos, GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN); + pos += GTI2_MAGIC_STRING_LEN; + + // write the type + buffer[pos++] = GTI2MsgPing; + + // copy the time id + memcpy(buffer + pos, "time", 4); + pos += 4; + + // write the current time + now = current_time(); + memcpy(buffer + pos, &now, sizeof(gsi_time)); + + pos += (int)sizeof(gsi_time); + // send it + if(!gti2ConnectionSendData(connection, (const GT2Byte *)buffer, pos)) + return GT2False; + + return GT2True; +} + + +GT2Bool gti2SendPong(GT2Connection connection, GT2Byte * message, int len) +{ + // change the ping to a pong + message[GTI2_MAGIC_STRING_LEN] = GTI2MsgPong; + + // send it + return gti2ConnectionSendData(connection, message, len); +} + +GT2Bool gti2SendClosed(GT2Connection connection) +{ + // normal close + return gti2SendClosedOnSocket(connection->socket, connection->ip, connection->port); +} + + +GT2Bool gti2SendClosedOnSocket(GT2Socket socket, unsigned int ip, unsigned short port) +{ + // Vdp data length (not including voice) + magic string + type + // part of the buffer may not be used but more efficience to be on stack + char buffer[MAX_PROTOCOL_OFFSET + GTI2_MAGIC_STRING_LEN + 1]; + int pos = 0; + + // write the data length + if (socket->protocolType == GTI2VdpProtocol) + { + short dataLength = GTI2_MAGIC_STRING_LEN + 1; + memcpy(buffer, &dataLength, 2); + pos += 2; + } + + // write the magic string + memcpy(buffer + pos, GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN); + pos += GTI2_MAGIC_STRING_LEN; + + // write the type + buffer[pos++] = GTI2MsgClosed; + + // send it + if(!gti2SocketSend(socket, ip, port, (const GT2Byte *)buffer, pos)) + return GT2False; + + return GT2True; +} + + +GT2Bool gti2ResendMessage(GT2Connection connection, GTI2OutgoingBufferMessage * message) +{ + GTI2MessageType type; + int pos; + + // replace the ESN (it's after the magic string, the type, and the SN) + pos = (message->start + connection->socket->protocolOffset + GTI2_MAGIC_STRING_LEN + 1 + 2); + + gti2UShortToBuffer(connection->outgoingBuffer.buffer, pos, connection->expectedSerialNumber); + + // send the message + if(!gti2ConnectionSendData(connection, connection->outgoingBuffer.buffer + message->start, message->len)) + return GT2False; + + // update the last time sent + message->lastSend = connection->lastSend; + + // if it was a server challenge, update that time too + type = (GTI2MessageType)connection->outgoingBuffer.buffer[message->start + connection->socket->protocolOffset + GTI2_MAGIC_STRING_LEN]; + + if(type == GTI2MsgServerChallenge) + connection->challengeTime = connection->lastSend; + + return GT2True; +} + +GT2Bool gti2Send(GT2Connection connection, const GT2Byte * message, int len, GT2Bool reliable) +{ + if (reliable) + return gti2SendAppReliable(connection, message, len); + //send unreliable messages + return gti2SendAppUnreliable(connection, message, len); +} + +GT2Bool gti2WasMessageIDConfirmed(const GT2Connection connection, GT2MessageID messageID) +{ + GTI2OutgoingBufferMessage * message; + int len; + + // if there are no reliable messages waiting confirmation, then this has already been confirmed + len = ArrayLength(connection->outgoingBufferMessages); + if(!len) + return GT2True; + + // get the oldest message waiting confirmation + message = (GTI2OutgoingBufferMessage *)ArrayNth(connection->outgoingBufferMessages, 0); + + // if the message id we are looking for is older than the first one waiting confirmation, + // then it has already been confirmed + if(gti2SNDiff(messageID, message->serialNumber) < 0) + return GT2True; + + // the message hasn't been confirmed yet + return GT2False; +} diff --git a/code/gamespy/gt2/gt2Message.h b/code/gamespy/gt2/gt2Message.h new file mode 100644 index 00000000..96336bb1 --- /dev/null +++ b/code/gamespy/gt2/gt2Message.h @@ -0,0 +1,42 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_MESSAGE_H_ +#define _GT2_MESSAGE_H_ + +#include "gt2Main.h" + +GT2Bool gti2ReceiveMessages(GT2Socket socket); + +GT2Bool gti2Send(GT2Connection connection, const GT2Byte * message, int len, GT2Bool reliable); + +GT2Bool gti2SendAppReliable(GT2Connection connection, const GT2Byte * message, int len); +GT2Bool gti2SendAppUnreliable(GT2Connection connection, const GT2Byte * message, int len); +GT2Bool gti2SendClientChallenge(GT2Connection connection, const char challenge[GTI2_CHALLENGE_LEN]); +GT2Bool gti2SendServerChallenge(GT2Connection connection, const char response[GTI2_RESPONSE_LEN], const char challenge[GTI2_CHALLENGE_LEN]); +GT2Bool gti2SendClientResponse(GT2Connection connection, const char response[GTI2_RESPONSE_LEN], const char * message, int len); +GT2Bool gti2SendAccept(GT2Connection connection); +GT2Bool gti2SendReject(GT2Connection connection, const GT2Byte * message, int len); +GT2Bool gti2SendClose(GT2Connection connection); +GT2Bool gti2SendKeepAlive(GT2Connection connection); +GT2Bool gti2SendAck(GT2Connection connection); +GT2Bool gti2SendNack(GT2Connection connection, unsigned short SNMin, unsigned short SNMax); +GT2Bool gti2SendPing(GT2Connection connection); +GT2Bool gti2SendPong(GT2Connection connection, GT2Byte * message, int len); +GT2Bool gti2SendClosed(GT2Connection connection); +GT2Bool gti2SendClosedOnSocket(GT2Socket socket, unsigned int ip, unsigned short port); + +GT2Bool gti2ResendMessage(GT2Connection connection, GTI2OutgoingBufferMessage * message); + +GT2Bool gti2HandleConnectionReset(GT2Socket socket, unsigned int ip, unsigned short port); +GT2Bool gti2HandleHostUnreachable(GT2Socket socket, unsigned int ip, unsigned short port, GT2Bool send); +GT2Bool gti2WasMessageIDConfirmed(const GT2Connection connection, GT2MessageID messageID); + +#endif diff --git a/code/gamespy/gt2/gt2Socket.c b/code/gamespy/gt2/gt2Socket.c new file mode 100644 index 00000000..31ef7d58 --- /dev/null +++ b/code/gamespy/gt2/gt2Socket.c @@ -0,0 +1,528 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Socket.h" +#include "gt2Buffer.h" +#include "gt2Message.h" +#include "gt2Connection.h" +#include "gt2Utility.h" +#include "gt2Callback.h" +#include + +#ifdef GSI_ADHOC +// External functions defined at the platform specific level +// to be moved to nonport or socket.h +extern int _NetworkAdHocSocketCreate(gsi_u16 port); +extern void _NetworkAdHocSocketDestroy ( int adhoc_socket); +extern int _NetworkAdHocSocketSendTo ( int adhoc_socket, + const void *data, + int len, + int flags, + const char *dest_addr, + gsi_u16 dest_port + ); +extern void NetAdhocMacGet(char *mac); +#endif + +#define GTI2_DEFAULT_INCOMING_BUFFER_SIZE (64 * 1024) +#define GTI2_DEFAULT_OUTGOING_BUFFER_SIZE (64 * 1024) + +#define STARTING_SERIAL_NUMBER 0 + +static int gti2ConnectionHash(const void * elem, int numBuckets) +{ + GT2Connection connection = *(GT2Connection *)elem; + + return (int)(((connection->ip * connection->port)) % numBuckets); +} + +static int GS_STATIC_CALLBACK gti2ConnectionCompare(const void * elem1, const void * elem2) +{ + GT2Connection connection1 = *(GT2Connection *)elem1; + GT2Connection connection2 = *(GT2Connection *)elem2; + + if(connection1->ip != connection2->ip) + return (int)(connection1->ip - connection2->ip); + + return (int)(short)(connection1->port - connection2->port); +} + +static void gti2ConnectionFree(void * elem) +{ + gti2ConnectionCleanup(*(GT2Connection *)elem); +} + +GT2Connection gti2SocketFindConnection(GT2Socket socket, unsigned int ip, unsigned short port) +{ + GTI2Connection connection; + GT2Connection connectionPtr; + GT2Connection * connectionPtrPtr; + + connection.ip = ip; + connection.port = port; + + connectionPtr = &connection; + connectionPtrPtr = (GT2Connection *)TableLookup(socket->connections, &connectionPtr); + if(connectionPtrPtr) + return *connectionPtrPtr; + + return NULL; +} + +GT2Result gti2CreateSocket +( + GT2Socket * sock, + const char * localAddress, + int outgoingBufferSize, + int incomingBufferSize, + gt2SocketErrorCallback callback, + GTI2ProtocolType type +) +{ + SOCKADDR_IN address; + GT2Socket socketTemp; + int rcode; + unsigned int ip; + unsigned short port; + int len; + + // startup the sockets engine if needed + SocketStartUp(); + + // check for using defaults + if(!incomingBufferSize) + incomingBufferSize = GTI2_DEFAULT_INCOMING_BUFFER_SIZE; + if(!outgoingBufferSize) + outgoingBufferSize = GTI2_DEFAULT_OUTGOING_BUFFER_SIZE; + + // convert the address to an IP and port + if(!gt2StringToAddress(localAddress, &ip, &port)) + return GT2AddressError; + + // allocate the object + socketTemp = (GT2Socket)gsimalloc(sizeof(GTI2Socket)); + if(!socketTemp) + return GT2OutOfMemory; + + // set initial values + memset(socketTemp, 0, sizeof(GTI2Socket)); + socketTemp->socket = INVALID_SOCKET; + socketTemp->incomingBufferSize = incomingBufferSize; + socketTemp->outgoingBufferSize = outgoingBufferSize; + socketTemp->socketErrorCallback = callback; + + // create the connections table + socketTemp->connections = TableNew2(sizeof(GT2Connection), 32, 2, gti2ConnectionHash, gti2ConnectionCompare, NULL); + if(!socketTemp->connections) + { + gsifree(socketTemp); + return GT2OutOfMemory; + } + + // create the closed connections list + socketTemp->closedConnections = ArrayNew(sizeof(GT2Connection), 4, gti2ConnectionFree); + if(!socketTemp->closedConnections) + { + TableFree(socketTemp->connections); + gsifree(socketTemp); + return GT2OutOfMemory; + } + + // create the socket + +#ifdef _XBOX + if (type == GTI2VdpProtocol) + + socketTemp->socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_VDP); + else +#endif +#ifdef GSI_ADHOC + if (type == GTI2AdHocProtocol) + { + socketTemp->socket = _NetworkAdHocSocketCreate( port); + } + else +#endif + socketTemp->socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + socketTemp->protocolType = type; + + if (type == GTI2AdHocProtocol) + { + socketTemp->protocolOffset = 0; + } + else + socketTemp->protocolOffset = type; + +#ifdef _XBOX + if (type == GTI2UdpProtocol) + { + SetSockBroadcast(socketTemp->socket); + socketTemp->protocolOffset = type; + } +#endif + + if(socketTemp->socket == INVALID_SOCKET) + { + TableFree(socketTemp->connections); + ArrayFree(socketTemp->closedConnections); + gsifree(socketTemp); + return GT2NetworkError; + } + + // bind it + memset(&address, 0, sizeof(SOCKADDR_IN)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = ip; + address.sin_port = htons(port); + if (type != GTI2AdHocProtocol) + { + rcode = bind(socketTemp->socket, (SOCKADDR *)&address, sizeof(SOCKADDR_IN)); + if (gsiSocketIsError(rcode)) + { + closesocket(socketTemp->socket); + TableFree(socketTemp->connections); + ArrayFree(socketTemp->closedConnections); + gsifree(socketTemp); + return GT2NetworkError; + } + } + + // get the ip and port we're bound to + #ifdef GSI_ADHOC + if (type == GTI2AdHocProtocol) + { + char mac[6]; + NetAdhocMacGet(mac); + + socketTemp->ip = gt2MacToIp(mac); + socketTemp->port = ntohs(address.sin_port); + } + else + #endif + { + len = sizeof(SOCKADDR_IN); + getsockname(socketTemp->socket, (SOCKADDR *)&address, &len); + socketTemp->ip = address.sin_addr.s_addr; + socketTemp->port = ntohs(address.sin_port); + } + + *sock = socketTemp; + + return GT2Success; +} + +void gti2CloseSocket(GT2Socket socket) +{ + + if(socket->callbackLevel) + { + socket->close = GT2True; + return; + } + +#ifdef GSI_ADHOC + if (socket->protocolType == GTI2AdHocProtocol) + { + _NetworkAdHocSocketDestroy(socket->socket); + } + else +#endif + { + closesocket(socket->socket); + } + + TableFree(socket->connections); + ArrayFree(socket->closedConnections); + gsifree(socket); + + SocketShutDown(); +} + +void gti2Listen(GT2Socket socket, gt2ConnectAttemptCallback callback) +{ + socket->connectAttemptCallback = callback; +} + +static GT2Connection gti2CreateConnectionObject(void) +{ + // mj todo: give options of allocating this from a static pool for games with known number of connections. + return (GT2Connection)gsimalloc(sizeof(GTI2Connection)); +} + +GT2Result gti2NewSocketConnection(GT2Socket socket, GT2Connection * connection, unsigned int ip, unsigned short port) +{ + GT2Connection connectionPtr = NULL; + + // check if this ip and port already exists + if(gti2SocketFindConnection(socket, ip, port)) + return GT2DuplicateAddress; + + // allocate a connection object + connectionPtr = gti2CreateConnectionObject(); + if(!connectionPtr) + goto out_of_memory; + + // set some basics + memset(connectionPtr, 0, sizeof(GTI2Connection)); + connectionPtr->ip = ip; + connectionPtr->port = port; + connectionPtr->socket = socket; + connectionPtr->startTime = current_time(); + connectionPtr->lastSend = connectionPtr->startTime; + connectionPtr->serialNumber = STARTING_SERIAL_NUMBER; + connectionPtr->expectedSerialNumber = STARTING_SERIAL_NUMBER; + + // allocate the buffers + if(!gti2AllocateBuffer(&connectionPtr->incomingBuffer, socket->incomingBufferSize)) + goto out_of_memory; + if(!gti2AllocateBuffer(&connectionPtr->outgoingBuffer, socket->outgoingBufferSize)) + goto out_of_memory; + + // allocate the message arrays + connectionPtr->incomingBufferMessages = ArrayNew(sizeof(GTI2IncomingBufferMessage), 64, NULL); + if(!connectionPtr->incomingBufferMessages) + goto out_of_memory; + connectionPtr->outgoingBufferMessages = ArrayNew(sizeof(GTI2OutgoingBufferMessage), 64, NULL); + if(!connectionPtr->outgoingBufferMessages) + goto out_of_memory; + + // allocate the filter arrays + connectionPtr->sendFilters = ArrayNew(sizeof(gt2SendFilterCallback), 2, NULL); + if(!connectionPtr->sendFilters) + goto out_of_memory; + connectionPtr->receiveFilters = ArrayNew(sizeof(gt2ReceiveFilterCallback), 2, NULL); + if(!connectionPtr->receiveFilters) + goto out_of_memory; + + // add it to the table + TableEnter(socket->connections, &connectionPtr); + + // check that it's in the table (and get the address) + *connection = gti2SocketFindConnection(socket, ip, port); + if(!*connection) + goto out_of_memory; + + return GT2Success; + +out_of_memory: + + // there wasn't enough memory, free everything and return the error + if(connectionPtr) + { + gsifree(connectionPtr->incomingBuffer.buffer); + gsifree(connectionPtr->outgoingBuffer.buffer); + if(connectionPtr->incomingBufferMessages) + ArrayFree(connectionPtr->incomingBufferMessages); + if(connectionPtr->outgoingBufferMessages) + ArrayFree(connectionPtr->outgoingBufferMessages); + if(connectionPtr->sendFilters) + ArrayFree(connectionPtr->sendFilters); + if(connectionPtr->receiveFilters) + ArrayFree(connectionPtr->receiveFilters); + gsifree(connectionPtr); + } + + return GT2OutOfMemory; +} + +void gti2FreeSocketConnection(GT2Connection connection) +{ + // check if we can actually free it + if(connection->freeAtAcceptReject || connection->callbackLevel) + return; + + // remove it from the correct list depending on the connect state + if(connection->state == GTI2Closed) + { + int len; + int i; + + len = ArrayLength(connection->socket->closedConnections); + for(i = 0 ; i < len ; i++) + { + if(connection == *(GT2Connection *)ArrayNth(connection->socket->closedConnections, i)) + { + ArrayDeleteAt(connection->socket->closedConnections, i); + return; + } + } + } + else + { + TableRemove(connection->socket->connections, &connection); + } +} + +GT2Bool gti2SocketSend(GT2Socket socket, unsigned int ip, unsigned short port, const GT2Byte * message, int len) +{ + SOCKADDR_IN address; + int rcode; + +#ifdef GTI2_DROP_SEND_RATE + // drop some percentage of packets and see what happens + if((rand() % 100) < GTI2_DROP_SEND_RATE) + return GT2True; +#endif + + // check the message and len + gti2MessageCheck(&message, &len); + + if (socket->protocolType != GTI2AdHocProtocol) + { + #ifndef INSOCK // insock never sets write flag for UDP sockets + // check if we can't send + if(!CanSendOnSocket(socket->socket)) + return GT2True; + #endif + } + + // do the send + #ifdef GSI_ADHOC + if (socket->protocolType == GTI2AdHocProtocol) + { + char mac[6]; + // convert to mac + gt2IpToMac(ip,mac); + // change IP address to mac ethernet + rcode = _NetworkAdHocSocketSendTo(socket->socket, (const char *)message, len, 0, mac, port); + if(rcode <0) + { + gti2SocketError(socket); + return gsi_false; + } + + // let the dump handle this + if(socket->sendDumpCallback) + { + if(!gti2DumpCallback(socket, gti2SocketFindConnection(socket, ip, port), ip, port, GT2False, message, len, GT2True)) + return GT2False; + } + return gsi_true; + } + + #endif + + // fill the address structure + memset(&address, 0, sizeof(SOCKADDR_IN)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = ip; + address.sin_port = htons(port); + rcode = sendto(socket->socket, (const char *)message, len, 0, (SOCKADDR *)&address, sizeof(SOCKADDR_IN)); + if (gsiSocketIsError(rcode)) + { + rcode = GOAGetLastError(socket->socket); + if(rcode == WSAECONNRESET) + { + // handle the reset + if(!gti2HandleConnectionReset(socket, ip, port)) + return GT2False; + } +#ifndef SN_SYSTEMS + else if (rcode == WSAEHOSTUNREACH) + { + if (!gti2HandleHostUnreachable(socket, ip, port, GT2True)) + return GT2False; + } +#endif + // some systems might return these errors + else if((rcode == WSAENOBUFS) || (rcode == WSAEWOULDBLOCK)) + { + return GT2True; + } +#if defined(SN_SYSTEMS) || defined(_NITRO) || defined(_REVOLUTION) + // for systems that don't support WSAEHOSTDOWN (EHOSTDOWN) + else if((rcode != WSAEMSGSIZE)) +#else + else if((rcode != WSAEMSGSIZE) && (rcode != WSAEHOSTDOWN)) +#endif + { + // fatal socket error + gti2SocketError(socket); + return GT2False; + } + } + else + { + // let the dump handle this + if(socket->sendDumpCallback) + { + if(!gti2DumpCallback(socket, gti2SocketFindConnection(socket, ip, port), ip, port, GT2False, message, len, GT2True)) + return GT2False; + } + } + + return GT2True; +} + +static int gti2SocketConnectionsThinkMap(void * elem, void * clientData) +{ + GT2Connection connection = *(GT2Connection *)elem; + gsi_time now = *(gsi_time *)clientData; + + // only think if we're not closed + if(connection->state != GTI2Closed) + { + // think + if(!gti2ConnectionThink(connection, now)) + return 0; + } + + // check for a closed connection + if((connection->state == GTI2Closed) && !connection->freeAtAcceptReject && !connection->callbackLevel) + gti2FreeSocketConnection(connection); + + return 1; +} + +GT2Bool gti2SocketConnectionsThink(GT2Socket socket) +{ + gsi_time now; + + // get the current time + now = current_time(); + + // go through the list of connections and let them think + if(TableMapSafe2(socket->connections, gti2SocketConnectionsThinkMap, &now)) + return GT2False; + + return GT2True; +} + +void gti2FreeClosedConnections(GT2Socket socket) +{ + int i; + int len; + + // loop through the closed connections, attempting to free them all + len = ArrayLength(socket->closedConnections); + for(i = (len - 1) ; i >= 0 ; i--) + gti2FreeSocketConnection(*(GT2Connection *)ArrayNth(socket->closedConnections, i)); +} + +void gti2SocketError(GT2Socket socket) +{ + // if there was already an error, don't go through this again + if(socket->error) + return; + + // flag the error + socket->error = GT2True; + + // first close all the socket's connections + gt2CloseAllConnectionsHard(socket); + + // call the error callback + if(!gti2SocketErrorCallback(socket)) + return; + + // close the socket + gti2CloseSocket(socket); +} diff --git a/code/gamespy/gt2/gt2Socket.h b/code/gamespy/gt2/gt2Socket.h new file mode 100644 index 00000000..5975945e --- /dev/null +++ b/code/gamespy/gt2/gt2Socket.h @@ -0,0 +1,45 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_SOCKET_H_ +#define _GT2_SOCKET_H_ + +#include "gt2Main.h" + +GT2Result gti2CreateSocket +( + GT2Socket * socket, + const char * localAddress, + int outgoingBufferSize, + int incomingBufferSize, + gt2SocketErrorCallback callback, + GTI2ProtocolType type +); + +void gti2CloseSocket(GT2Socket socket); + +void gti2Listen(GT2Socket socket, gt2ConnectAttemptCallback callback); + +GT2Result gti2NewSocketConnection(GT2Socket socket, GT2Connection * connection, unsigned int ip, unsigned short port); +void gti2FreeSocketConnection(GT2Connection connection); + +GT2Connection gti2SocketFindConnection(GT2Socket socket, unsigned int ip, unsigned short port); + +// ip is network byte order, port is host byte order +// returns false if there was a fatal error +GT2Bool gti2SocketSend(GT2Socket socket, unsigned int ip, unsigned short port, const GT2Byte * message, int len); + +GT2Bool gti2SocketConnectionsThink(GT2Socket socket); + +void gti2FreeClosedConnections(GT2Socket socket); + +void gti2SocketError(GT2Socket socket); + +#endif diff --git a/code/gamespy/gt2/gt2Utility.c b/code/gamespy/gt2/gt2Utility.c new file mode 100644 index 00000000..5a8f3a10 --- /dev/null +++ b/code/gamespy/gt2/gt2Utility.c @@ -0,0 +1,421 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Main.h" +#include "gt2Utility.h" +#include +#include +#ifdef WIN32 +#include +#include +#endif + +#define GTI2_STACK_HOSTLEN_MAX 256 + + +/************************* +** BYTE ORDER FUNCTIONS ** +*************************/ + +unsigned int gt2NetworkToHostInt(unsigned int i) +{ + return (unsigned int)ntohl(i); +} + +unsigned int gt2HostToNetworkInt(unsigned int i) +{ + return (unsigned int)htonl(i); +} + +unsigned short gt2NetworkToHostShort(unsigned short s) +{ + return ntohs(s); +} + +unsigned short gt2HostToNetworkShort(unsigned short s) +{ + return htons(s); +} + +/******************************* +** INTERNET ADDRESS FUNCTIONS ** +*******************************/ + +const char * gt2AddressToString(unsigned int ip, unsigned short port, char string[22]) +{ + static char strAddressArray[2][22]; + static int nIndex; + char * strAddress; + + if(string) + strAddress = string; + else + { + nIndex ^= 1; + strAddress = strAddressArray[nIndex]; + } + + if(ip) + { + IN_ADDR inAddr; + + inAddr.s_addr = ip; + + if(port) + sprintf(strAddress, "%s:%d", inet_ntoa(inAddr), port); + else + sprintf(strAddress, "%s", inet_ntoa(inAddr)); + } + else if(port) + sprintf(strAddress, ":%d", port); + else + strAddress[0] = '\0'; + + return strAddress; +} + +GT2Bool gt2StringToAddress(const char * string, unsigned int * ip, unsigned short * port) +{ + unsigned int srcIP = 0; // avoid use uninit condition + unsigned short srcPort = 0; + + if(!string || !string[0]) + { + srcIP = 0; + srcPort = 0; + } + else + { + char stackHost[GTI2_STACK_HOSTLEN_MAX + 1]; + const char * colon; + const char * host; + + // Is there a port? + colon = strchr(string, ':'); + if(!colon) + { + // The string is the host. + host = string; + + // No port. + srcPort = 0; + } + else + { + int len; + const char * check; + int temp; + + // Is it just a port? + if(colon == string) + { + host = NULL; + srcIP = 0; + } + else + { + // Copy the host portion into the array on the stack. + len = (colon - string); + assert(len < GTI2_STACK_HOSTLEN_MAX); + memcpy(stackHost, string, (unsigned int)len); + stackHost[len] = '\0'; + host = stackHost; + } + + // Check the port. + for(check = (colon + 1) ; *check ; check++) + if(!isdigit(*check)) + return GT2False; + + // Get the port. + temp = atoi(colon + 1); + if((temp < 0) || (temp > 0xFFFF)) + return GT2False; + srcPort = (unsigned short)temp; + } + + // Is there a host? + if(host) + { + // Try dotted IP. + ///////////////// + srcIP = inet_addr(host); + if(srcIP == INADDR_NONE) + { +#if defined(_XBOX) + return GT2False; +#else + HOSTENT * hostent; + + hostent = gethostbyname(host); + if(hostent == NULL) + return GT2False; + + srcIP = *(unsigned int *)hostent->h_addr_list[0]; +#endif + } + } + } + + if(ip) + *ip = srcIP; + if(port) + *port = srcPort; + + return GT2True; +} + +#ifdef GSI_ADHOC +gsi_u32 gti2MacToIp(const char *mac) +// change mac ethernet to IP address +{ + // Mac (48 bit)<---> IP (32 bit) convertion. + // horrible hack. But the chances of a conflict are less then getting struck by lightning + // but in order to translate back, we will need to keep a table + // stick real value in table. + GTI2MacEntry *entry; + int i; + gsi_u32 ip; + memcpy(&ip,mac,4); // store rest in table + + + // find match in table + for (i=0;i< MAC_TABLE_SIZE;i++) + { + if(MacTable[i].ip == ip) + { + return ip; // already there, don't add to the table. + } + } + + + // if not found match, add it, overwriting oldest entry + entry = &MacTable[lastmactableentry]; + lastmactableentry = (lastmactableentry +1 ) & (MAC_TABLE_SIZE-1); + entry->ip = ip; + memcpy(entry->mac,mac,6); + + return ip; +} + +void gti2IpToMac(gsi_u32 ip,char *mac) +// change IP address to mac ethernet +{ + int i; + // find match in table + for (i=0;i< MAC_TABLE_SIZE;i++) + { + if(MacTable[i].ip == ip) + { + memcpy(mac,MacTable[i].mac,6); + return; + } + } + // error, can not find mac address + memset(mac,0,6); + GS_FAIL(); +} +#endif + +#if defined(_XBOX) + +// XBox doesn't support the address functions +static const char * gti2HandleHostInfo(HOSTENT * host, char *** aliases, unsigned int *** ips) { return NULL; } +const char * gt2IPToHostInfo(unsigned int ip, char *** aliases, unsigned int *** ips) { return NULL; } +const char * gt2StringToHostInfo(const char * string, char *** aliases, unsigned int *** ips) { return NULL; } + +unsigned int gt2XnAddrToIP(XNADDR theAddr, XNKID theKeyId) +{ + // String to fit ip address + : + port + IN_ADDR anAddr; + if (XNetXnAddrToInAddr(&theAddr, &theKeyId, &anAddr)== 0) + return anAddr.s_addr; + return 0; +} + +GT2Bool gt2IPToXnAddr(int ip, XNADDR *theAddr, XNKID *theKeyId) +{ + IN_ADDR anAddr; + anAddr.s_addr = ip; + if (XNetInAddrToXnAddr(anAddr, theAddr, theKeyId) == 0) + return GT2True; + return GT2False; +} + +#else + +static const char * gti2HandleHostInfo(HOSTENT * host, char *** aliases, unsigned int *** ips) +{ + if(!host || (host->h_addrtype != AF_INET) || (host->h_length != 4)) + return NULL; + + if(aliases) + *aliases = host->h_aliases; + if(ips) + *ips = (unsigned int **)host->h_addr_list; + + return host->h_name; +} + + +const char * gt2IPToHostInfo(unsigned int ip, char *** aliases, unsigned int *** ips) +{ +#ifdef _PSP + return NULL; +#else + HOSTENT * host; + + host = gethostbyaddr((const char *)&ip, 4, AF_INET); + + GSI_UNUSED(ip); + return gti2HandleHostInfo(host, aliases, ips); +#endif +} + +const char * gt2StringToHostInfo(const char * string, char *** aliases, unsigned int *** ips) +{ + HOSTENT * host; + unsigned int ip; + + if(!string || !string[0]) + return NULL; + + // Is the string actually a dotted IP? + ip = inet_addr(string); + if(ip != INADDR_NONE) + return gt2IPToHostInfo(ip, aliases, ips); + + host = gethostbyname(string); + + return gti2HandleHostInfo(host, aliases, ips); +} + +#endif + +const char * gt2IPToHostname(unsigned int ip) +{ + return gt2IPToHostInfo(ip, NULL, NULL); +} + +const char * gt2StringToHostname(const char * string) +{ + return gt2StringToHostInfo(string, NULL, NULL); +} + +char ** gt2IPToAliases(unsigned int ip) +{ + char ** aliases; + + if(!gt2IPToHostInfo(ip, &aliases, NULL)) + return NULL; + + return aliases; +} + +char ** gt2StringToAliases(const char * string) +{ + char ** aliases; + + if(!gt2StringToHostInfo(string, &aliases, NULL)) + return NULL; + + return aliases; +} + +unsigned int ** gt2IPToIPs(unsigned int ip) +{ + unsigned int ** ips; + + if(!gt2IPToHostInfo(ip, NULL, &ips)) + return NULL; + + return ips; +} + +unsigned int ** gt2StringToIPs(const char * string) +{ + unsigned int ** ips; + + if(!gt2StringToHostInfo(string, NULL, &ips)) + return NULL; + + return ips; +} + +/*********************** +** INTERNAL FUNCTIONS ** +***********************/ + +#ifdef __MWERKS__ // CodeWarrior will warn if not prototyped +void gti2MessageCheck(const GT2Byte ** message, int * len); +#endif + +// Used from gt2main.c +void gti2MessageCheck(const GT2Byte ** message, int * len) +{ + // check for an empty message + if(!*message) + { + *message = (const GT2Byte *)""; + *len = 0; + } + // check for calculating the message length + else if(*len == -1) + { + *len = (int)(strlen((const char *)*message) + 1); + } +} + +#ifdef RECV_LOG +void gti2LogMessage +( + unsigned int fromIP, unsigned short fromPort, + unsigned int toIP, unsigned short toPort, + const GT2Byte * message, int len +) +{ + FILE * file; + IN_ADDR ip; + int i; +#ifdef WIN32 + struct _timeb utcTime; + struct tm * now; +#endif + + file = fopen("recv.log", "at"); + if(!file) + return; + + // date-time +#ifdef WIN32 + _ftime(&utcTime); + now = localtime(&utcTime.time); + fprintf(file, "%02d.%02d.%02d %02d:%02d:%02d.%03d\n", + now->tm_year - 100, now->tm_mon + 1, now->tm_mday, + now->tm_hour, now->tm_min, now->tm_sec, utcTime.millitm); +#endif + + // from + ip.s_addr = fromIP; + fprintf(file, "%s:%d -> ", inet_ntoa(ip), fromPort); + + // to + ip.s_addr = toIP; + fprintf(file, "%s:%d\n", inet_ntoa(ip), toPort); + + // data + fprintf(file, "%d: ", len); + for(i = 0 ; i < len ; i++) + fprintf(file, "%02X ", message[i]); + fprintf(file, "\n\n"); + + fclose(file); +} +#endif diff --git a/code/gamespy/gt2/gt2Utility.h b/code/gamespy/gt2/gt2Utility.h new file mode 100644 index 00000000..34a5e439 --- /dev/null +++ b/code/gamespy/gt2/gt2Utility.h @@ -0,0 +1,28 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_UTILITY_H_ +#define _GT2_UTILITY_H_ + +#include "gt2Main.h" + +void gti2MessageCheck(const GT2Byte ** message, int * len); + +#ifdef RECV_LOG +void gti2LogMessage +( + unsigned int fromIP, unsigned short fromPort, + unsigned int toIP, unsigned short toPort, + const GT2Byte * message, int len +); +#endif + + +#endif diff --git a/code/gamespy/gt2/gt2action/TGAFile.cpp b/code/gamespy/gt2/gt2action/TGAFile.cpp new file mode 100644 index 00000000..d65382e5 --- /dev/null +++ b/code/gamespy/gt2/gt2action/TGAFile.cpp @@ -0,0 +1,1574 @@ +/* + + Modified by Chris Losinger for Smaller Animals Software's ImgLib/ImgDLL. + Esp. RGB->BGR and row order switches. 9/98 +*/ +#include +#include + +/* + TGADefs.h - Tye and Constant declaration file + This File defines the Types, and the Constant Values used by the + TGAFile Class. + + Created By: Timothy A. Bish + Created On: 08/18/98 +*/ + +#ifndef TGADEFSH +#define TGADEFSH + +#include + +#pragma pack(1) // Align the structure on byte boundries... + +// Possible Image Types +#define TGA_NOIMAGETYPE 0 // No Image Data Included in Image +#define TGA_MAPRGBTYPE 1 // Colormapped Image Data - No Compression +#define TGA_RAWRGBTYPE 2 // Truecolor Image Data - No Compression +#define TGA_RAWMONOTYPE 3 // Monochrome Image Data - No Compression +#define TGA_MAPENCODETYPE 9 // Colormapped Image Data - Compressed RLE +#define TGA_RAWENCODETYPE 10 // Truecolor Image Data - Compressed RLE +#define TGA_MONOENCODETYPE 11 // Monochrome Image Data - Compressed RLE +// Version Macro +#define TGA_VERSIONONE 1 // Version 1 File Format +#define TGA_VERSIONTWO 2 // Version 2 File Format +// File Read Write Modes +const int GREYSC = 0; // Image is Greyscale +const int COLOUR = 1; // Image is Color +const int MAPPED = 2; // Image has a Color Map +const int RLENCO = 4; // Image is RLE Encoded + +// 18 Byte Sturcture representin the basic definitions of +// the image +typedef struct _aTGAHEADER +{ + BYTE IDLength; // 00h Size of ID Field + BYTE ColorMapType; // 01h Color Map Type + BYTE ImageType; // 02h Image Type Code + WORD CMapStart; // 03h Color Map Origin + WORD CMapLength; // 05h Color Map Length + BYTE CMapDepth; // 07h Color Map Depth + WORD XOffset; // 08h X origin of Image + WORD YOffset; // 0Ah Y origin of Image + WORD Width; // 0Ch Width of Image + WORD Height; // 0Eh Height of Image + BYTE PixelDepth; // 10h Image Pixel Size + BYTE ImageDescriptor; // 11h Image Description Byte +} TGAHEADER; + +// The footer is 26 Bytes in length and is always at the end of a +// TGA v2.0 file. +typedef struct _aTGAFOOTER +{ + DWORD ExtensionOffset; // Extension Area Offset + DWORD DeveloperOffset; // Developer Directory Offset + CHAR Signature[18]; // TGA Signature +} TGAFOOTER; + +typedef struct _aTGATAG +{ + WORD TagNumber; // ID Number of the Tag + DWORD DataOffset; // Offset Location of the Tag + DWORD DataSize; // Size of the Tag Data in Bytes +} TGATAG; + +// The extension area is basically the second header in the TGA v2.0 +// file format. +typedef struct _aTGAEXTENSION +{ + WORD Size; // Extension Size + CHAR AuthorName[41]; // Author Name + CHAR AuthorComment[324]; // Author Comment + WORD StampMonth; // Date/Time Stamp: Month + WORD StampDay; // Date/Time Stamp: Day + WORD StampYear; // Date/Time Stamp: Year + WORD StampHour; // Date/Time Stamp: Hour + WORD StampMinute; // Date/Time Stamp: Minute + WORD StampSecond; // Date/Time Stamp: Second + CHAR JobName[41]; // Job Name/ID + WORD JobHour; // Job Time: Hours + WORD JobMinute; // Job Time: Minutes + WORD JobSecond; // Job Time: Seconds + CHAR SoftwareId[41]; // Software ID + WORD VersionNumber; // Version Number of Software + BYTE VersionLetter; // Version Letter of Software + DWORD KeyColor; // Key Color + WORD PixelNumerator; // Pixel Aspect Ratio + WORD PixelDenominator; // Pixel Aspect Ratio + WORD GammaNumerator; // Gamma Value + WORD GammaDenominator; // Gamma Value + DWORD ColorOffset; // Color Correction Offset + DWORD StampOffset; // Postage Stamp Offset + DWORD ScanOffset; // Scanline Table Offset + BYTE AttributesType; // Attributes Type +} TGAEXTENSION; + +// The Color Correction Table is an array of 2048 Bytes in length, which +// contians 256 entries used to store the values used for color remapping. +typedef struct _aTGACOLORCORRECTIONTABLE +{ + SHORT Alpha; // Alpha Channel Seldom Used + SHORT Red; // Red Value of Correction + SHORT Green; // Green Value of Correction + SHORT Blue; // Green Value of Correction +} TGACOLORCORRECTIONTABLE; + +#define TRIALVERSION -1 // LIB was not initialized with a registered key + +#define IMGOK 0 // no err +#define MEMERR 1 // out of mem +#define FILEOPENERR 2 // error on file open +#define FILEREADERR 3 // error on file read +#define FILEWRITEERR 4 // error on file write +#define BADPARAMERR 5 // bad user param +#define INVALIDBMPERR 6 // bad BMP file +#define BMPRLEERR 7 // we don't do compressed (RLE) BMP files +#define INVALIDGIFERR 8 // bad GIF file +#define INVALIDJPGERR 9 // bad JPG file +#define IMGDCERR 10 // error with device context +#define IMGDIBERR 11 // problem with a GetDIBits call +#define NOGIFERR 12 // GIF support disabled +#define IMGNORESOURCE 13 // resource not found +#define CALLBACKCANCEL 14 // callback returned FALSE - operation aborted +#define INVALIDPNGERR 15 // bad PNG file +#define PNGCREATEERR 16 // internal PNG lib behavior - contact smaller animals s.w. +#define IMGDLLINTERNAL 17 // misc unexpected behavior error - contact smaller animals s.w. +#define IMGFONTERR 18 // trouble creating a font object +#define INTTIFFERR 19 // misc internal TIFF error +#define INVALIDTIFFERR 20 // invalid TIFF file +#define TIFFLZWNOTSUPPORTED 21 // this will not read TIFF-LZW iamges +#define INVALIDPCXERR 22 // invalid PCX image +#define CREATEBMPERR 23 // a call to the fn CreateCompatibleBitmap failed +#define IMGNOLINES 24 // end of an image while using single-line de/compression +#define GETDIBERR 25 // error during a call to GetDIBits +#define DEVOPNOSUPPORT 26 // device does not support an operation required by this function +#define INVALIDWMF 27 // invalid windows metafile +#define DEPTHMISMATCHERR 28 // the file was not of the requested bit-depth +#define INVALIDTGAERR 35 // Invalid TGA File +#define NOTGATHUMBNAIL 36 // No TGA Thumbnail in the file + +#pragma pack() + +#endif + +class TGAFile +{ + +public: + + // parameters + __int32 m_error; + +public: + + // operations + + TGAFile(); + + BOOL IsFileTGA(const char * fileName); + + LPVOID LoadTGA( const char *fileName, // Name of file + UINT32 *width, // Width in Pixel + UINT32 *height); // Height + + HGLOBAL LoadTGA8Bit(const char *fileName, // Name of File + UINT32 *width, // Width in pixels + UINT32 *height, // Height + RGBQUAD *pal); // Palette of RGBQUADS + + BOOL GetTGADims(const char *fileName, + UINT32 *width, + UINT32 *height); + + BOOL SaveTGA32(const char * fileName, // output path + BYTE *inBuf, // RGB buffer + UINT32 width, // size + UINT32 height); + + BOOL Save8BitTGA(const char * fileName, // output path + BYTE *colormappedbuffer, // one BYTE per pixel colomapped image + UINT32 width, // Width of image + UINT32 height, // Height of image + __int32 colors, // number of colors (number of RGBQUADs) + RGBQUAD *colormap); // array of RGBQUADs + + HGLOBAL LoadTGAThumbnail(const char *fileName, // Name of file + UINT32 *width, // Width in Pixel + UINT32 *height); // Height + +private: + + // Parameters + + char TGA_ImageIDFeild[256];// Text in file + BYTE TGA_Attribute; // Number of attribute bytes per pixel + // i.e. 1 for T16 and 8 for T32 + UINT32 mode; // Mode of current Read or Write + + // RLE Decompression Variables + BYTE Red, // Stores pixel value for + Grn, // RLE series of oixels + Blu, + Alpha; + UINT32 l; // Used when 8 bit files use RLE + __int32 RLECount, RLEFlag; // Indicates whether the RLE series + // is still going or is finished + +private: + + // Operations + + __int32 TGA_GetFileVersion(FILE *fp); // Determines whether this is a V1.0 + // or V2.0 TGA File + BOOL TGA_GetMapEntry(BYTE *Red, // Get the Color Values out of the + BYTE *Green, // Color map in the TGA File + BYTE *Blue, // Return TRUE on Success + BYTE *Alpha, + FILE *fp, + UINT32 Depth); + + // version that takes a file ptr + BOOL TGA_GetPixelValue(BYTE *Red, // Get and parse a single pixel value + BYTE *Grn, // from the TGA file. Handles Unencoding + BYTE *Blu, // of RLE encoded files. + BYTE *Alp, // plus Alpha (08jan00/bgw) + FILE *fp, + UINT32 PixelDepth, + RGBQUAD *CMap); + + // version that takes a buffer ptr + BOOL TGA_GetPixelValue(BYTE *Red, // Get and parse a single pixel value + BYTE *Grn, // from the TGA file. Handles Unencoding + BYTE *Blu, // of RLE encoded files. + BYTE *Alp, // plus Alpha (08jan00/bgw) + BYTE ** ppTGAData, + UINT32 PixelDepth, + RGBQUAD *CMap); +}; + +//#define MAX_IMAGEREAD_BUFFER 65535 +//BYTE * gpImageReadBuffer = NULL; +//long glImageReadBufferSize = 0; +//BYTE * gpImageReadBufPos = NULL; +// +// +//typedef struct tagTGAColorComponents +// { +// BYTE red; +// BYTE green; +// BYTE blue; +// BYTE alpha; +// } TGAColorComponents; + +/* TGA File REader Classs Implementation File + This Implementation Allows the reading of TGA (Targa) Files + into an RGB buffer. Also the class allows an RGB Buffer to be + written to a TGA File. There is also a function to determine + the dimensions of a TGA file. + + Created By: Timothy A. Bish + Created On: 08/17/98 + +*/ +//////////////////////////////////////////////////////////////////////////// +// No Much going on here +TGAFile::TGAFile() + { + m_error = IMGOK; + } + + +//////////////////////////////////////////////////////////////////////////// +// GetTGADimns +// Find dims of the image in a TGA file +// Returns - TRUE on success +BOOL +TGAFile::GetTGADims(const char * fileName, UINT32 * width, UINT32 * height) + { + // for safety + *width = 0; + *height = 0; + FILE * fp; + TGAHEADER tgahd; + + // Init the file Header to all zeros + ZeroMemory(&tgahd, sizeof(tgahd)); + + // init + m_error = IMGOK; + fp = fopen(fileName, "rb"); + + if(fp == NULL) + { + m_error = FILEOPENERR; + return FALSE; + } + + // Get the Header + if(fread(&tgahd, 1, sizeof(TGAHEADER), fp) != sizeof(TGAHEADER)) + { + m_error = FILEREADERR; + fclose(fp); + return FALSE; + } + + // Check fo valid data in structure + if(tgahd.PixelDepth > 32) + { + // I don't do Pixel Depths Bigger than 32 + m_error = INVALIDTGAERR; + fclose(fp); + return NULL; + } + + // Anything other than the standard TGA types + // and I quit + switch(tgahd.ImageType) + { + case TGA_MAPRGBTYPE: + case TGA_RAWRGBTYPE: + case TGA_RAWMONOTYPE: + case TGA_MAPENCODETYPE: + case TGA_RAWENCODETYPE: + case TGA_MONOENCODETYPE: + break; + + default: + m_error = INVALIDTGAERR; + fclose(fp); + return NULL; + } + + // Grab the Image dimensions + *width = tgahd.Width; + *height = tgahd.Height; + fclose(fp); + return TRUE; + } + +/***************************************************************************** +* NAME: +* TGAFile::IsFileTGA +* +* DESCRIPTION: +* Description goes here... +* +*******************************************************************************/ +BOOL +TGAFile::IsFileTGA(const char * fileName) + { + TGAHEADER tgahd; + FILE * fp; + + ZeroMemory(&tgahd, sizeof(tgahd)); + fp = fopen(fileName, "rb"); + long rc = fread(&tgahd, 1, sizeof(TGAHEADER), fp); + fclose(fp); + + if( (rc == sizeof(TGAHEADER)) && // must be big enough for a header... + (tgahd.PixelDepth == 32) && // 32-bit TGAs only + (tgahd.ImageType == TGA_RAWRGBTYPE) ) // Raw RGBA format only + return(TRUE); + + return(FALSE); + } + +//////////////////////////////////////////////////////////////////////////// +// LoadTGA +// load a .TGA file - 1,4,8,24,32 bit +// allocates and returns an RGB buffer containing the image. +// modifies width and height accordingly - NULL, 0, 0 on error +LPVOID +TGAFile::LoadTGA(const char * fileName, UINT32 * width, UINT32 * height) + { + LPVOID pNew = NULL; + BYTE * pRGB = NULL; + BYTE Alpha; + + TGAHEADER tgahd; + + BYTE TGA_Origin = 0; + + RGBQUAD CColMap[256]; + + // for safety + *width = 0; + *height = 0; + + // init + m_error = IMGOK; + + // Init the file Header to all zeros + ZeroMemory(&tgahd, sizeof(tgahd)); + FILE * fp; + fp = fopen(fileName, "rb"); + + if(fp == NULL) + { + m_error = FILEOPENERR; + return FALSE; + } + + // Read the TGA Header + long rc = fread(&tgahd, 1, sizeof(TGAHEADER), fp); + + if(rc != sizeof(TGAHEADER)) + { + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + + // Check fo valid data in structure + if((tgahd.PixelDepth> 32) || (tgahd.PixelDepth<8)) + { + // I don't do Pixel Depths Bigger than 32 + m_error = INVALIDTGAERR; + fclose(fp); + return NULL; + } + + // Anything other than the standard TGA types + // and I quit + switch(tgahd.ImageType) + { + case TGA_MAPRGBTYPE: + case TGA_RAWRGBTYPE: + case TGA_RAWMONOTYPE: + case TGA_MAPENCODETYPE: + case TGA_RAWENCODETYPE: + case TGA_MONOENCODETYPE: + break; + + default: + m_error = INVALIDTGAERR; + fclose(fp); + return NULL; + } + + // Set the number of Color Planes + if(tgahd.ImageType == TGA_RAWMONOTYPE) + { + mode = GREYSC; + } + else + { + mode = COLOUR; + } + + // Read the ID Descriptor if present + if(tgahd.IDLength != 0) + { + // Read the TGA Comments + long rc = fread(&TGA_ImageIDFeild, 1, tgahd.IDLength, fp); + + if(rc != tgahd.IDLength) + { + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + } + + // Parse the Image Descriptor + TGA_Attribute = (BYTE)(tgahd.ImageDescriptor & 0x0f); + TGA_Origin = (BYTE)((tgahd.ImageDescriptor & 0x20) / 32); + + // If present read the color map + if(tgahd.ColorMapType != 0) + { + // Get the color map + for(__int32 i = 0; i < (tgahd.CMapStart + tgahd.CMapLength); i++) + { + TGA_GetMapEntry(&CColMap[i].rgbRed, &CColMap[i].rgbGreen, &CColMap[i].rgbBlue, &Alpha, fp, tgahd.CMapDepth); + } + + // If the TGA file actually needs the color map + // Set the mode to show this + if((tgahd.ImageType != TGA_RAWRGBTYPE) && (tgahd.ImageType != TGA_RAWMONOTYPE) && (tgahd.ImageType != TGA_RAWENCODETYPE)) + mode = mode | MAPPED; + } + + // Check Run Length Encoding + if((tgahd.ImageType == TGA_MAPENCODETYPE) || (tgahd.ImageType == TGA_RAWENCODETYPE) || (tgahd.ImageType == TGA_MONOENCODETYPE)) + mode = mode | RLENCO; + + long lImgDataSize = (tgahd.Height * tgahd.Width) * (tgahd.PixelDepth / 8); + + // Allocate the memory buffers + pNew = malloc(lImgDataSize); +// pNew = (LPVOID) theApp.m_TGABuffer.GetBuffer((size_t)(tgahd.Width * tgahd.Height * 4)); + + if(pNew == NULL) + { + m_error = MEMERR; + fclose(fp); + return NULL; + } + else + pRGB = (BYTE *)pNew; + + // RGB from image Data + //DWORD destOffset = 0; + + RLECount = 0; + RLEFlag = 0; + +// // +// // (re-)alocate a local image buffer to read the TGA formatted +// // data into. this avoids the huge critical-section delays in +// // every call to getc(). +// // +// long lBufSize = lImgDataSize + 16; // slop +// if(lBufSize < MAX_IMAGEREAD_BUFFER) +// lBufSize = MAX_IMAGEREAD_BUFFER; +// if(glImageReadBufferSize < lBufSize) +// { +// if(gpImageReadBuffer) +// { +// free(gpImageReadBuffer); +// gpImageReadBuffer = NULL; // tidy +// gpImageReadBufPos = NULL; +// } +// +// glImageReadBufferSize = lBufSize; +// gpImageReadBuffer = (BYTE *) malloc(glImageReadBufferSize); +// +// if(!gpImageReadBuffer) +// { +// glImageReadBufferSize = 0; +// return NULL; // v. bad news. +// } +// TRACE("* (Re-)Allocated local TGA data buffer: %d bytes\n", glImageReadBufferSize); +// } + + // + // Read the TGA format data into the local buffer + // + long lTGABytesRead = fread(pRGB, 1, lImgDataSize, fp); + if(lTGABytesRead != lImgDataSize) + { +// ASSERT(0); + //free(pNew); + return NULL; + } + + // Grab the image dimensions + *width = tgahd.Width; + *height = tgahd.Height; + + // Clean Up + fclose(fp); + m_error = IMGOK; + return pNew; + +/////////////// +// all out...dorks didn't realize that TGA's memory format == DIBSections! +////////////// +#if 0 + // + // Read the TGA format data into the local buffer + // + long lTGABytesRead = fread(gpImageReadBuffer, 1, lImgDataSize, fp); + if(lTGABytesRead != lImgDataSize) + { + ASSERT(0); + return NULL; + } + + gpImageReadBufPos = gpImageReadBuffer; + + // copy DWORDs instead of bytes... + UINT32 * pPixel = (UINT32 *) pRGB; + UINT32 * pReadBufPixel = (UINT32 *) gpImageReadBufPos; + + for(UINT32 row = 0; row < tgahd.Height; row++) + { + for(UINT32 col = 0; col < tgahd.Width; col++) + { + + // Reset RLE Counters + BYTE Red, Grn, Blu, Alp; + TGA_GetPixelValue(&Red, &Grn, &Blu, &Alp, &gpImageReadBufPos, tgahd.PixelDepth, CColMap); + + // Invert if the image origin is in Bottom left + if(TGA_Origin != 0) + { // Bottom Left Origin + destOffset = ((tgahd.Height -1) -row) * tgahd.Width * 4 + col * 4; + *(pRGB + destOffset + 0) = Red; + *(pRGB + destOffset + 1) = Grn; + *(pRGB + destOffset + 2) = Blu; + *(pRGB + destOffset + 3) = Alp; + } + else + { // Top Left Origin + *(pRGB + destOffset + 0) = Red; + *(pRGB + destOffset + 1) = Grn; + *(pRGB + destOffset + 2) = Blu; + *(pRGB + destOffset + 3) = Alp; + destOffset += 4; + } + #if 0 // even faster! + // Red = *(gpImageReadBufPos++); + // Grn = *(gpImageReadBufPos++); + // Blu = *(gpImageReadBufPos++); + // Alp = *(gpImageReadBufPos++); + // + // *(pRGB + destOffset + 0) = Red; + // *(pRGB + destOffset + 1) = Grn; + // *(pRGB + destOffset + 2) = Blu; + // *(pRGB + destOffset + 3) = Alp; + // destOffset += 4; + + // TGAColorComponents rgbaPixel; + + *(pPixel++) = *(pReadBufPixel++); + + // BYTE * p = pRGB + destOffset; + // *(p++) = *(gpImageReadBufPos++); + // *(p++) = *(gpImageReadBufPos++); + // *(p++) = *(gpImageReadBufPos++); + // *(p++) = *(gpImageReadBufPos++); + // destOffset += 4; + #endif + } // loop col + } // loop row + + // Grab the image dimensions + *width = tgahd.Width; + *height = tgahd.Height; + + // Clean Up + fclose(fp); + m_error = IMGOK; + return pNew; +#endif + } + + +/////////////////////////////////////////////////////////////////////////////////// +// LoadTGA8Bit +// Loads in an 8 Bit buffer and the color palette that is +// associated with that buffer, if the image is 8 Bit, else +// it sets global error to DEPTHMISMATCHERR +HGLOBAL +TGAFile::LoadTGA8Bit(const char * fileName, // Name of File +UINT32 * width, // Width in pixels +UINT32 * height, // Height +RGBQUAD * pal) // Palette of RGBQUADS + { + HGLOBAL hNew = NULL; + BYTE * pRGB = NULL; + BYTE Alpha; + + TGAHEADER tgahd; + + BYTE TGA_Origin = 0; + + // for safety + *width = 0; + *height = 0; + + // init + m_error = IMGOK; + + // Init the file Header to all zeros + ZeroMemory(&tgahd, sizeof(tgahd)); + + // Init the Palette to all zeros + ZeroMemory(pal, sizeof(pal)); + FILE * fp; + fp = fopen(fileName, "rb"); + + if(fp == NULL) + { + m_error = FILEOPENERR; + return FALSE; + } + else + { + // Read the TGA Header + long rc = fread(&tgahd, 1, sizeof(TGAHEADER), fp); + + if(rc != sizeof(TGAHEADER)) + { + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + + // Check fo valid data in structure + if(tgahd.PixelDepth> 8) + { + // Not an 8bit Image + m_error = DEPTHMISMATCHERR; + fclose(fp); + return NULL; + } + + // Anything other than the standard TGA types + // and I quit + switch(tgahd.ImageType) + { + case TGA_MAPRGBTYPE: + case TGA_MAPENCODETYPE: + break; + + default: + m_error = DEPTHMISMATCHERR; + fclose(fp); + return NULL; + } + + // Set the Color Mode + if(tgahd.ImageType == TGA_RAWMONOTYPE) + { + mode = GREYSC; + } + else + { + mode = COLOUR; + } + + // Read the ID Descriptor if present + if(tgahd.IDLength != 0) + { + // Read the TGA Comments + long rc = fread(&TGA_ImageIDFeild, 1, tgahd.IDLength, fp); + + if(rc != tgahd.IDLength) + { + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + } + + // Parse the Image Descriptor + TGA_Attribute = (BYTE)(tgahd.ImageDescriptor & 0x0f); + TGA_Origin = (BYTE)((tgahd.ImageDescriptor & 0x20) / 32); + + // If present read the color map + if(tgahd.ColorMapType != 0) + { + // Get the color map + for(__int32 i = 0; i < (tgahd.CMapStart + tgahd.CMapLength); i++) + { + TGA_GetMapEntry(&pal[i].rgbRed, &pal[i].rgbGreen, &pal[i].rgbBlue, &Alpha, fp, tgahd.CMapDepth); + } + } + else + { + m_error = INVALIDTGAERR; + fclose(fp); + return NULL; + } + + // Check Run Length Encoding + if((tgahd.ImageType == TGA_MAPENCODETYPE) || (tgahd.ImageType == TGA_RAWENCODETYPE) || (tgahd.ImageType == TGA_MONOENCODETYPE)) + mode = mode | RLENCO; + + // Allocate the memory buffers + hNew = GlobalAlloc(GHND, tgahd.Width * tgahd.Height); + + if(hNew == NULL) + { + m_error = MEMERR; + fclose(fp); + return NULL; + } + else + { + pRGB = (BYTE *)GlobalLock(hNew); + + if(pRGB == NULL) + { + m_error = MEMERR; + fclose(fp); + return NULL; + } + } + + // RGB from image Data + DWORD destOffset = 0; + + RLECount = 0; + RLEFlag = 0; + + for(UINT32 row = 0; row < tgahd.Height; row++) + { + for(UINT32 col = 0; col < tgahd.Width; col++) + { + BYTE Red, Grn, Blu, Alp; + + // Reset RLE Counters + TGA_GetPixelValue(&Red, &Grn, &Blu, &Alp, fp, tgahd.PixelDepth, pal); + + // Invert if the image origin is in Bottom left + if(TGA_Origin == 0) + { // Bottom Left Origin + *(pRGB + destOffset) = Red; + destOffset = ((tgahd.Height -1) -row) * tgahd.Width + col; + } + else + { // Top Left Origin + *(pRGB + destOffset) = Red; + destOffset++; + } + } + } + } + + // Grab the image dimensions + *width = tgahd.Width; + *height = tgahd.Height; + + // Clean Up + GlobalUnlock(hNew); + fclose(fp); + m_error = IMGOK; + return hNew; + } + + +/////////////////////////////////////////////////////////////////////////////////// +// LoadTGAThumbNail +// Checks the TGA file for the existance of a thumbnail image +// Reads and returns it in a 24 Bit RGB Buffer if a thumbnail +// exists. Returns NULL if there isn't one sets TGANOTHUMBNAIL +HGLOBAL +TGAFile::LoadTGAThumbnail + ( + const char * fileName, // Name of file + UINT32 * width, // Width in Pixel + UINT32 * height // Height + ) + { + HGLOBAL hNew = NULL; + + TGAHEADER tgahd; + TGAFOOTER tgaft; + TGAEXTENSION tgaext; + + BYTE stampWidth = 0, stampHeight = 0; + long lResult; + + // for safety + *width = 0; + *height = 0; + + // init + m_error = IMGOK; + + // Init the file structs + ZeroMemory(&tgahd, sizeof(tgahd)); + ZeroMemory(&tgaft, sizeof(tgaft)); + ZeroMemory(&tgaext, sizeof(tgaext)); + FILE * fp; + fp = fopen(fileName, "rb"); + + if(fp == NULL) + { + m_error = FILEOPENERR; + return FALSE; + } + else + { + // Read the TGA Header + long rc = fread(&tgahd, 1, sizeof(TGAHEADER), fp); + + if(rc != sizeof(TGAHEADER)) + { + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + + // Check fo valid data in structure + if((tgahd.PixelDepth> 32) || (tgahd.PixelDepth<8)) + { + // I don't do Pixel Depths Bigger than 32 + m_error = INVALIDTGAERR; + fclose(fp); + return NULL; + } + + // Anything other than the standard TGA types + // and I quit + switch(tgahd.ImageType) + { + case TGA_MAPRGBTYPE: + case TGA_RAWRGBTYPE: + case TGA_RAWMONOTYPE: + case TGA_MAPENCODETYPE: + case TGA_RAWENCODETYPE: + case TGA_MONOENCODETYPE: + break; + + default: + m_error = INVALIDTGAERR; + fclose(fp); + return NULL; + } + + // Set the number of Color Planes + if(tgahd.ImageType == TGA_RAWMONOTYPE) + { + mode = GREYSC; + } + else + { + mode = COLOUR; + } + + // Check for file Version + // Seek the last 26 bytes of the file + if(fseek(fp, -26, SEEK_END)) + { + // Error Quit + fclose(fp); + return NULL; + } + + // Read in the last 26 Bytes of the File + lResult = fread(&tgaft, 1, sizeof(TGAFOOTER), fp); + + if(lResult != sizeof(TGAFOOTER)) + { + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + + // Check for the Marker at the end of the file + lResult = strcmp(tgaft.Signature, "TRUEVISION-XFILE."); + + if(lResult != 0) + { + // Not V2.0 File no Thumbnail + m_error = NOTGATHUMBNAIL; + fclose(fp); + return NULL; + } + + // Check for the existance of an extension area + if(tgaft.ExtensionOffset == 0) + { + // No Thumbnail in this file + m_error = NOTGATHUMBNAIL; + fclose(fp); + return NULL; + } + + // Seek the extension area + if(fseek(fp, tgaft.ExtensionOffset, SEEK_SET)) + { + // Error Quit + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + + // Read in the last 26 Bytes of the File + lResult = fread(&tgaext, 1, sizeof(TGAEXTENSION), fp); + + if(lResult != sizeof(TGAEXTENSION)) + { + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + + // Seek the thumbnail image + if(fseek(fp, tgaext.StampOffset, SEEK_SET)) + { + // Error Quit + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + + // Read the Width and Height from the first two bytes + // of the postage stamp data. + fread(&stampWidth, 1, 1, fp); + fread(&stampHeight, 1, 1, fp); + + if((stampWidth <= 0) || (stampWidth> 64) || (stampHeight <= 0) || (stampHeight> 64)) + { + m_error = INVALIDTGAERR; + fclose(fp); + return NULL; + } + } + + // Clean Up + *width = stampWidth; + *height = stampHeight; + fclose(fp); + + // Return Okay Image + return hNew; + } + + +/////////////////////////////////////////////////////////////////////////////////// +// SaveTGA32 +// Saves the buffer as a 32 bit True Color Image in TGA format +BOOL +TGAFile::SaveTGA32 + ( + const char * fileName, // output path + BYTE * inBuf, // BGR buffer + UINT32 width, // size in pixels + UINT32 height + ) + { + long lResult = 0; + + TGAHEADER tgahd; + m_error = IMGOK; + + // Init the file Header to all zeros + ZeroMemory(&tgahd, sizeof(tgahd)); + + if(inBuf == NULL) + { + m_error = BADPARAMERR; + return FALSE; + } + + if((width == 0) || (height == 0)) + { + m_error = BADPARAMERR; + return FALSE; + } + + // Initialize the Header for the File + tgahd.IDLength = 0; + tgahd.ColorMapType = 0; + tgahd.ImageType = TGA_RAWRGBTYPE; + tgahd.CMapStart = 0; + tgahd.CMapLength = 0; + tgahd.CMapDepth = 0; + tgahd.XOffset = 0; + tgahd.YOffset = 0; + tgahd.Width = (WORD)width; + tgahd.Height = (WORD)height; + tgahd.PixelDepth = 32; + tgahd.ImageDescriptor = 0; + + // Open a file to write + FILE * fp = fopen(fileName, "wb"); + + if(fp == NULL) + { + m_error = FILEOPENERR; + return FALSE; + } + + // Write the Header to File. + if((lResult = fwrite(&tgahd, 1, sizeof(TGAHEADER), fp)) != 18) + { + fclose(fp); + m_error = FILEWRITEERR; + return FALSE; + } + + // Wrte the Bytes to file + DWORD destOffset = 0; + BYTE temp = 0; + DWORD rowStride = tgahd.Width * 4; + + for(UINT32 row = 0; row < tgahd.Height; row++) + { + //DWORD rowOffset = rowStride * row; + DWORD rowOffset = rowStride *((tgahd.Height -1) -row); + + for(UINT32 col = 0; col < tgahd.Width; col++) + { + destOffset = rowOffset + 4 * col; + temp = *(inBuf + destOffset + 2); + + if(fwrite(&temp, 1, 1, fp) != 1) + { + m_error = FILEWRITEERR; + fclose(fp); + return FALSE; + } + temp = *(inBuf + destOffset + 1); + + if(fwrite(&temp, 1, 1, fp) != 1) + { + m_error = FILEWRITEERR; + fclose(fp); + return FALSE; + } + temp = *(inBuf + destOffset + 0); + + if(fwrite(&temp, 1, 1, fp) != 1) + { + m_error = FILEWRITEERR; + fclose(fp); + return FALSE; + } + + if(fwrite(&temp, 1, 1, fp) != 1) + { + m_error = FILEWRITEERR; + fclose(fp); + return FALSE; + } + } + } + + // Cleanup + fclose(fp); + m_error = IMGOK; + return TRUE; + } + + +/////////////////////////////////////////////////////////////////////////////////// +// Save8BitTGA +// Save's to an 8 Bit Color mapped file using the Palette +// passed in to the function. +BOOL +TGAFile::Save8BitTGA(const char * fileName, // output path +BYTE * inBuf, // one BYTE per pixel colomapped image +UINT32 width, // Width of Image +UINT32 height, // Height of Image +__int32 colors, // number of colors (number of RGBQUADs) +RGBQUAD * colormap) // array of RGBQUADs + { + long lResult = 0; + + TGAHEADER tgahd; + + // Init + m_error = IMGOK; + + // Init the file Header to all zeros + ZeroMemory(&tgahd, sizeof(tgahd)); + + if(inBuf == NULL) + { + m_error = BADPARAMERR; + return FALSE; + } + + if((width == 0) || (height == 0)) + { + m_error = BADPARAMERR; + return FALSE; + } + + if(colormap == NULL) + { + m_error = BADPARAMERR; + return FALSE; + } + + // Initialize the Header for the File + tgahd.IDLength = 0; + tgahd.ColorMapType = 1; + tgahd.ImageType = TGA_MAPRGBTYPE; + tgahd.CMapStart = 0; + tgahd.CMapLength = (SHORT)colors; + tgahd.CMapDepth = 24; + tgahd.XOffset = 0; + tgahd.YOffset = 0; + tgahd.Width = (WORD)width; + tgahd.Height = (WORD)height; + tgahd.PixelDepth = 8; + tgahd.ImageDescriptor = 0x28; + + // Open a file to write + FILE * fp = fopen(fileName, "wb"); + + if(fp == NULL) + { + m_error = FILEOPENERR; + return FALSE; + } + + // Write the Header to File. + if((lResult = fwrite(&tgahd, 1, sizeof(TGAHEADER), fp)) != 18) + { + m_error = FILEWRITEERR; + fclose(fp); + return FALSE; + } + + // Write out the Colormap + for(__int32 i = 0; i < colors; i++) + { + putc(colormap[i].rgbBlue, fp); + putc(colormap[i].rgbGreen, fp); + putc(colormap[i].rgbRed, fp); + } + + // Wrte the Bytes to file + DWORD destOffset = 0; + BYTE temp = 0; + + for(UINT32 row = 0; row < tgahd.Height; row++) + { + for(UINT32 col = 0; col < tgahd.Width; col++) + { + temp = *(inBuf + destOffset + 0); + + if(fwrite(&temp, 1, 1, fp) != 1) + { + m_error = FILEWRITEERR; + fclose(fp); + return FALSE; + } + destOffset += 1; + } + } + + // Cleanup + fclose(fp); + m_error = IMGOK; + return TRUE; + } + + +//////////////////////////////////////////////////////////////////////////////////// +// TGA_GetMapEntry +// Get the Color Values out of the +// Color map in the TGA File +// Return 0 on Success +BOOL +TGAFile::TGA_GetMapEntry(BYTE * Red, BYTE * Green, BYTE * Blue, BYTE * Alpha, FILE * fp, UINT32 Depth) + { + UINT32 j, k, l; + BYTE i, r, g, b, a = 0; + long lResult; + + switch(Depth) + { + case 8: // Greyscale Read and Triplicate + lResult = fread(&i, 1, 1, fp); + + // Check for error + if(lResult != 1) + { + m_error = FILEREADERR; + return FALSE; + } + + // Set RGB Values + r = i; + g = i; + b = i; + break; + + case 16: // 5 bits each of Red, Green, and Blue + + case 15: // Watch for the Byte order + lResult = fread(&j, 1, 1, fp); + lResult = lResult + fread(&k, 1, 1, fp); + + // Check for error + if(lResult != 2) + { + m_error = FILEREADERR; + return FALSE; + } + l = j + k * 256; + b = (BYTE)(((l >> 10) & 31) << 3); + g = (BYTE)(((l >> 5) & 31) << 3); + r = (BYTE)((l & 31) << 3); + break; + + case 32: // Read the Alpha bit a Throw it away + + case 24: // Eight bits for each Red, Green and Blue + lResult = fread(&i, 1, 1, fp); + r = i; + lResult = lResult + fread(&i, 1, 1, fp); + g = i; + lResult = lResult + fread(&i, 1, 1, fp); + b = i; + + // Check for error + if(lResult != 3) + { + m_error = FILEREADERR; + return FALSE; + } + + if(Depth == 32) + { + lResult = fread(&i, 1, 1, fp); + + if(lResult != 1) + { + m_error = FILEREADERR; + return FALSE; + } + + // Stroe Alpha bit + a = i; + } + break; + + default: + // Some Other Pixel Depth Which I don't support + return FALSE; + } + *Red = r; + *Green = g; + *Blue = b; + *Alpha = a; + + // Reutrn No Error + return TRUE; + } + + +//////////////////////////////////////////////////////////////////////////////////// +// TGA_GetFileVersion +// Retrieves the Version of the TGA File +// BYTES 8-23 of a Version 2.0 Footer will be equal +// to "TRUEVISION-XFILE" as ASCII +// Returns - Version number 1 or 2 +__int32 +TGAFile::TGA_GetFileVersion(FILE * fp) + { + long result; + fpos_t pos; + + TGAFOOTER tgaft; + + // Save the Current position of the File Stream + if(fgetpos(fp, &pos)) + { + // Error Quit + return FILEREADERR; + } + + // Seek the last 26 bytes of the file + if(fseek(fp, -26, SEEK_END)) + { + // Error Quit + return FILEREADERR; + } + + // Read in the last 26 Bytes of the File + result = fread(&tgaft, 1, sizeof(TGAFOOTER), fp); + + if(result != sizeof(TGAFOOTER)) + { + m_error = FILEREADERR; + return FILEREADERR; + } + + // Return the File Stream to its initial position + if(fsetpos(fp, &pos)) + { + // Error Quit + return FILEREADERR; + } + + // Check for the Marker at the end of the file + if(!strcspn(tgaft.Signature, "TRUEVISION-XFILE")) + { + // Marker found its V2.0 TGA + return TGA_VERSIONTWO; + } + + // No Marker was found Assume V1.0 TGA + return TGA_VERSIONONE; + } + + +//////////////////////////////////////////////////////////////////////////////////// +// TGA_getPixelValue +// Retreve a pixel value from the buffer and parse +// the value if its RLE encoded. Returns the RGB +// value of the pixel. +// Retruns - TRUE on success +BOOL +TGAFile::TGA_GetPixelValue + ( + BYTE * rRed, + BYTE * rGrn, + BYTE * rBlu, + BYTE * rAlp, + BYTE ** ppTGAData, + UINT32 PixelDepth, + RGBQUAD * CColMap + ) + { + // + // Buffered TGAs are always 32-bit, + // so go direct from file to RGBA + // + *rRed = *((*ppTGAData)++); + *rGrn = *((*ppTGAData)++); + *rBlu = *((*ppTGAData)++); + *rAlp = *((*ppTGAData)++); + + return TRUE; + } + +//////////////////////////////////////////////////////////////////////////////////// +// TGA_getPixelValue +// Retreve a pixel value from the file and parse +// the value if its RLE encoded. Returns the RGB +// value of the pixel. +// Retruns - TRUE on success +BOOL +TGAFile::TGA_GetPixelValue(BYTE * rRed, BYTE * rGrn, BYTE * rBlu, BYTE * rAlp, FILE * fp, UINT32 PixelDepth, RGBQUAD * CColMap) + { + BYTE i, j, k; + long lResult; + + // Check for Run Length Encoding + if((mode & RLENCO) != 0) + { + if(RLECount == 0) + { // Restrat the rum + lResult = fread(&i, 1, 1, fp); + + if(lResult != 1) + { + m_error = FILEREADERR; + return FALSE; + } + RLEFlag = (i & 0x80) >> 7; + + if(RLEFlag == 0) + { // Stream of unencoded pixels + RLECount = i + 1; + } + else + { // Single Pixel Replicated + RLECount = i-127; + } + RLECount--; // ecrement count and get pixel + } + else + { + // I have already read the count and at least the first pixel + RLECount--; + + if(RLEFlag != 0) + { + // Replicated Pixels + goto PixelEncode; + } + } + } + + // Rea the appropiate number of BYTES and break into RGB + switch(PixelDepth) + { + case 8: // Greyscale Read 1 Byte and Triplicate + lResult = fread(&i, 1, 1, fp); + + if(lResult != 1) + { + m_error = FILEREADERR; + return FALSE; + } + Red = i; + Grn = i; + Blu = i; + l = i; + break; + + case 16: // 1 Bit alpha not used + + case 15: // Five bits each for RGB watch byte ordering + lResult = fread(&j, 1, 1, fp); + lResult = lResult + fread(&k, 1, 1, fp); + + // Check for error + if(lResult != 2) + { + m_error = FILEREADERR; + return FALSE; + } + l = j + k * 256; + Blu = (BYTE)(((l >> 10) & 31) << 3); + Grn = (BYTE)(((l >> 5) & 31) << 3); + Red = (BYTE)((l & 31) << 3); + break; + + case 24: // Eight bits each for RGB + lResult = fread(&i, 1, 1, fp); + Red = i; + lResult = lResult + fread(&i, 1, 1, fp); + Grn = i; + lResult = lResult + fread(&i, 1, 1, fp); + Blu = i; + + // opaque alpha (08jan00/bgw) + Alpha = 0xFF; + + // Check for error + if(lResult != 3) + { + m_error = FILEREADERR; + return FALSE; + } + break; + + case 32: // With alpha (08jan00/bgw) + lResult = fread(&i, 1, 1, fp); + Red = i; + lResult = lResult + fread(&i, 1, 1, fp); + Grn = i; + lResult = lResult + fread(&i, 1, 1, fp); + Blu = i; + lResult = lResult + fread(&i, 1, 1, fp); + Alpha = i; + + // Check for error + if(lResult != 4) + { + m_error = FILEREADERR; + return FALSE; + } + break; + + default: // Unknown number of bis per pixel + m_error = INVALIDTGAERR; + return NULL; + } + +PixelEncode: // Set the actual pixel values + + if((mode & MAPPED) == MAPPED) + { + // Remap Color Mapped Pixels + *rRed = CColMap[l].rgbRed; + *rGrn = CColMap[l].rgbGreen; + *rBlu = CColMap[l].rgbBlue; + *rAlp = 0xFF; // opaque always (08jan00/bgw) + } + else + { + // Set Unmapped Values + *rRed = Red; + *rGrn = Grn; + *rBlu = Blu; + *rAlp = Alpha; + } + return TRUE; + } + +extern "C" unsigned char * LoadTGAFile +( + const char * filename, + int * width, + int * height +) +{ + TGAFile tgaFile; + return (unsigned char *)tgaFile.LoadTGA(filename, (UINT32 *)width, (UINT32 *)height); +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/TGAFile.h b/code/gamespy/gt2/gt2action/TGAFile.h new file mode 100644 index 00000000..1dfd7369 --- /dev/null +++ b/code/gamespy/gt2/gt2action/TGAFile.h @@ -0,0 +1,22 @@ +/* TGA File REader Classs Implementation File + This Implementation Allows the reading of TGA (Targa) Files + into an RGB buffer. Also the class allows an RGB Buffer to be + written to a TGA File. There is also a function to determine + the dimensions of a TGA file. + + Created By: Timothy A. Bish + Created On: 08/17/98 + +*/ + +#ifndef TGAFILEH +#define TGAFILEH + +unsigned char * LoadTGAFile +( + const char * filename, + int * width, + int * height +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aClient.c b/code/gamespy/gt2/gt2action/gt2aClient.c new file mode 100644 index 00000000..9c8c94d7 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aClient.c @@ -0,0 +1,715 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include +#include +#include "gt2aMain.h" +#include "gt2aClient.h" +#include "gt2aParse.h" +#include "gt2aDisplay.h" +#include "gt2aSound.h" + +#define CLIENT_THINK_TIME 30 + +static GT2Socket Socket; // The socket used to connect to the server +static GT2Connection Connection; // The connection to the server. +float localRotation; // The local rotation, >=0, <360. +char serverAddress[128]; // Address of the server. +int localMotion; // STILL, FORWARD, BACKWARD. +int localTurning; // STILL, LEFT, RIGHT. +Player players[MAX_PLAYERS]; // The list of players. +GT2Bool connected; // True once we received the start message. +int localIndex = -1; // The local player's index into the players table. +unsigned long lastServerUpdate; // The last time we received an update from the server. +char localNick[MAX_NICK] = "Player"; // The local player's nick. +CObject cObjects[MAX_OBJECTS]; // The list of objects. +UpdateInfo updateHistory[UPDATE_HISTORY_LEN]; // Time diff for past updates +int updateHistoryStart; // The starting index of the history. +int ClientNumAsteroids; // The number of asteroids we're holding. +static unsigned short nextServerUpdateID; // The expected ID of the next update. +static unsigned short nextClientUpdateID; // The ID of our next update. + +// Stats. +///////// +int reliableBytesSentClient; +int reliableBytesReceivedClient; +int unreliableBytesSentClient; +int unreliableBytesReceivedClient; +int reliableMessagesSentClient; +int reliableMessagesReceivedClient; +int unreliableMessagesSentClient; +int unreliableMessagesReceivedClient; + +static void ClientSocketErrorCallback +( + GT2Socket socket +) +{ + printf("Server socket error\n"); + GSI_UNUSED(socket); +} + +static void ClientConnectedCallback +( + GT2Connection connection, + GT2Result result, + GT2Byte * message, + int len +) +{ + if(result != GT2Success) + { + printf("Connection failed (%d", result); + if(result == GT2Rejected) + printf(": %s)\n", message); + else + printf(")\n"); + exit(1); + } + GSI_UNUSED(len); + GSI_UNUSED(connection); +} + +static void ClientReceivedCallback +( + GT2Connection connection, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + char buffer[MAX_NICK + 16]; + GTMessageType type; + int rcode; + + // Check for no message. + //////////////////////// + if(!message) + return; + + // Get the message type. + //////////////////////// + type = gtEncodedMessageType((char *)message); + + // New client? + ////////////// + if(type == MSG_S_ADDCLIENT) + { + byte newPlayerIndex; + Player * newPlayer; + char nick[MAX_NICK]; + + // Decode it. + ///////////// + rcode = gtDecode(MSG_S_ADDCLIENT_STR, (char *)message, len, + &newPlayerIndex, + nick); + if(rcode == -1) + return; + + // Check the index. + /////////////////// + assert((newPlayerIndex >= 0) && (newPlayerIndex < MAX_PLAYERS)); + if((newPlayerIndex < 0) || (newPlayerIndex >= MAX_PLAYERS)) + return; + assert(!players[newPlayerIndex].used); + + // Add the client. + ////////////////// + newPlayer = &players[newPlayerIndex]; + memset(newPlayer, 0, sizeof(Player)); + newPlayer->used = GT2True; + + // Set the nick. + //////////////// + strncpy(newPlayer->nick, nick, MAX_NICK); + newPlayer->nick[MAX_NICK - 1] = '\0'; + + // Display a join message. + ////////////////////////// + sprintf(buffer, "%s joined", newPlayer->nick); + DisplayChat(buffer); + } + // Delete client? + ///////////////// + else if(type == MSG_S_DELCLIENT) + { + byte delPlayerIndex; + + // Decode it. + ///////////// + rcode = gtDecode(MSG_S_DELCLIENT_STR, (char *)message, len, + &delPlayerIndex); + if(rcode == -1) + return; + + // Check the index. + /////////////////// + assert((delPlayerIndex >= 0) && (delPlayerIndex < MAX_PLAYERS)); + if((delPlayerIndex < 0) || (delPlayerIndex >= MAX_PLAYERS)) + return; + assert(players[delPlayerIndex].used); + + // Display a part message. + ////////////////////////// + sprintf(buffer, "%s left", players[delPlayerIndex].nick); + DisplayChat(buffer); + + // Delete the client. + ///////////////////// + players[delPlayerIndex].used = GT2False; + } + // Connection attempt finished? + /////////////////////////////// + else if(type == MSG_S_START) + { + byte index; + + // Decode it. + ///////////// + rcode = gtDecode(MSG_S_START_STR, (char *)message, len, + &index); + if(rcode == -1) + return; + + // Check the index. + /////////////////// + assert((index >= 0) && (index < MAX_PLAYERS)); + if((index < 0) || (index >= MAX_PLAYERS)) + return; + + // Set the local index. + /////////////////////// + localIndex = index; + + // We finished connecting. + ////////////////////////// + connected = GT2True; + } + // Server update? + ///////////////// + else if(type == MSG_S_UPDATE) + { + Player * player; + CObject * object; + byte updatedClients; + byte updatedObjects; + byte index; + unsigned short packedPosition[2]; + int score; + byte forward; + byte backward; + byte right; + byte left; + byte dead; + byte type; + int time; + int i; + unsigned long now; + unsigned long diff; + unsigned short updateID; + unsigned short packedRotation; + + // We got an update. + //////////////////// + now = current_time(); + diff = (now - lastServerUpdate); + lastServerUpdate = now; + + // Decode the header. + ///////////////////// + rcode = gtDecode(MSG_S_UPDATE_STR, (char *)message, len, + &updateID, + &updatedClients, + &updatedObjects); + if(rcode == -1) + return; + message += rcode; + len -= rcode; + + // Check for first update. + ////////////////////////// + if(updateHistoryStart == -1) + { + nextServerUpdateID = (updateID + 1); + updateHistoryStart = 0; + } + else + { + int numDropped; + UpdateInfo * info; + + // Check for out of order. + ////////////////////////// + if(updateID < nextServerUpdateID) + return; + + // Fill in the dropped updates. + /////////////////////////////// + numDropped = (updateID - nextServerUpdateID); + nextServerUpdateID = (updateID + 1); + + // Fill in the dropped updates. + /////////////////////////////// + while(numDropped--) + { + info = &updateHistory[updateHistoryStart++]; + updateHistoryStart %= UPDATE_HISTORY_LEN; + + info->diff = -1; + info->len = 0; + } + + // Fill in this update. + /////////////////////// + info = &updateHistory[updateHistoryStart++]; + updateHistoryStart %= UPDATE_HISTORY_LEN; + + info->diff = diff; + info->len = (len + rcode); + } + + // Go through each updated client. + ////////////////////////////////// + for(i = 0 ; i < updatedClients ; i++) + { + // Decode the update. + ///////////////////// + rcode = gtDecodeNoType(MSG_S_UPDATE_CLIENT_STR, (char *)message, len, + &index, + &packedPosition[0], + &packedPosition[1], + &packedRotation, + &score, + &forward, + &backward, + &right, + &left, + &dead); + if(rcode == -1) + return; + message += rcode; + len -= rcode; + + // Check the index. + /////////////////// + assert(index >= 0); + assert(index < MAX_PLAYERS); + if((index < 0) || (index >= MAX_PLAYERS)) + return; + + // Cache the player. + //////////////////// + player = &players[index]; + + // Copy the values in. + ////////////////////// + SetV2f( + player->position, + UnsignedShortToPosition(packedPosition[0]), + UnsignedShortToPosition(packedPosition[1])); + player->rotation = UnsignedShortToRotation(packedRotation); + player->score = score; + if(forward) + player->motion = FORWARD; + else if(backward) + player->motion = BACKWARD; + else + player->motion = STILL; + if(right) + player->turning = RIGHT; + else if(left) + player->turning = LEFT; + else + player->turning = STILL; + player->dead = dead; + + // Update the roll. + /////////////////// + if(player->dead) + { + player->roll = 0; + } + else if(left) + { + if(player->roll > -1) + { + player->roll -= (diff / 1000.0); + if(player->roll < -1) + player->roll = -1; + } + } + else if(right) + { + if(player->roll < 1) + { + player->roll += (diff / 1000.0); + if(player->roll > 1) + player->roll = 1; + } + } + else if(player->roll > 0) + { + player->roll -= (diff / 1000.0); + if(player->roll < 0) + player->roll = 0; + } + else if(player->roll < 0) + { + player->roll += (diff / 1000.0); + if(player->roll > 0) + player->roll = 0; + } + } + + // Go through the objects. + ////////////////////////// + for(i = 0 ; i < updatedObjects ; i++) + { + // Decode the object. + ///////////////////// + rcode = gtDecodeNoType(MSG_S_UPDATE_OBJECT_STR, (char *)message, len, + &type, + &packedPosition[0], + &packedPosition[1], + &packedRotation, + &time); + if(rcode == -1) + return; + message += rcode; + len -= rcode; + + // Cache the object. + //////////////////// + object = &cObjects[i]; + object->used = GT2True; + + // Copy in the values. + ////////////////////// + object->type = type; + SetV2f( + object->position, + UnsignedShortToPosition(packedPosition[0]), + UnsignedShortToPosition(packedPosition[1])); + object->rotation = UnsignedShortToRotation(packedRotation); + object->totalTime = time; + } + + // Mark the rest of the objects as unused. + ////////////////////////////////////////// + for( ; i < MAX_OBJECTS ; i++) + cObjects[i].used = GT2False; + } + // Chat? + //////// + else if(type == MSG_S_CHAT) + { + char buffer[256]; + + // Decode it. + ///////////// + rcode = gtDecode(MSG_S_CHAT_STR, (char *)message, len, + &buffer); + if(rcode == -1) + return; + + // We got a chat message. + ///////////////////////// + DisplayChat(buffer); + } + // Sound? + ///////// + else if(type == MSG_S_SOUND) + { + byte sound; + + // Decode it. + ///////////// + rcode = gtDecode(MSG_S_SOUND_STR, (char *)message, len, + &sound); + if(rcode == -1) + return; + + // We got a sound event. + //////////////////////// + PlaySoundEffect(sound); + } + // NumAsteroids? + //////////////// + else if(type == MSG_S_NUMASTEROIDS) + { + byte total; + + // Decode it. + ///////////// + rcode = gtDecode(MSG_S_NUMASTEROIDS_STR, (char *)message, len, + &total); + if(rcode == -1) + return; + + // We got a new total. + ////////////////////// + ClientNumAsteroids = total; + } + GSI_UNUSED(reliable); + GSI_UNUSED(connection); +} + +static void ClientClosedCallback +( + GT2Connection connection, + GT2CloseReason reason +) +{ + printf("Connection closed (%d)\n", reason); + GSI_UNUSED(connection); +} + +void SendUpdate +( + void +) +{ + if(connected) + { + char buffer[64]; + int rcode; + + // Encode the messsage. + /////////////////////// + rcode = gtEncode(MSG_C_UPDATE, MSG_C_UPDATE_STR, buffer, sizeof(buffer), + nextClientUpdateID++, + RotationToUnsignedShort(localRotation), + localMotion == FORWARD ? 1 : 0, + localMotion == BACKWARD ? 1 : 0, + localTurning == RIGHT ? 1 : 0, + localTurning == LEFT ? 1 : 0); + if(rcode != -1) + { + // Send the message. + //////////////////// + gt2Send(Connection, (GT2Byte *)buffer, rcode, GT2False); + } + } +} + +void SendPress +( + const char * value +) +{ + if(connected) + { + char buffer[32]; + int rcode; + + // Encode the message. + ////////////////////// + rcode = gtEncode(MSG_C_PRESS, MSG_C_PRESS_STR, buffer, sizeof(buffer), + value); + if(rcode != -1) + { + // Send the message. + //////////////////// + gt2Send(Connection, (GT2Byte *)buffer, rcode, GT2True); + } + } +} + +void SendChat +( + const char * message +) +{ + if(connected) + { + char buffer[CHAT_MAX + 8]; + int rcode; + + // Encode the message. + ////////////////////// + rcode = gtEncode(MSG_C_CHAT, MSG_C_CHAT_STR, buffer, sizeof(buffer), + message); + + if(rcode != -1) + { + // Send the message. + //////////////////// + gt2Send(Connection, (GT2Byte *)buffer, rcode, GT2True); + } + } +} + +void ClientThink +( + unsigned long now +) +{ + static unsigned long lastUpdate; + static unsigned long lastTurn; + unsigned long diff; + + // Think. + ///////// + gt2Think(Socket); + + // Are we connected? + //////////////////// + if(!connected) + return; + + // For the first update, just set the time. + /////////////////////////////////////////// + if(!lastUpdate) + { + lastUpdate = now; + return; + } + + // How long since the last update? + ////////////////////////////////// + diff = (now - lastUpdate); + + // Check for an update. + /////////////////////// + if(diff >= CLIENT_THINK_TIME) + { + // Send an update. + ////////////////// + SendUpdate(); + + // Update the last send time. + ///////////////////////////// + lastUpdate = now; + } + + // How long since the last turn? + //////////////////////////////// + diff = (now - lastTurn); + + // Update our rotation. + /////////////////////// + if((localIndex != -1) && !players[localIndex].dead) + localRotation = ComputeNewRotation(localRotation, localTurning, diff, PLAYER_TURN_SPEED); + + // Update the last turn time. + ///////////////////////////// + lastTurn = now; +} + +static void ClientSendMonitor +( + GT2Connection connection, + int filterID, + const GT2Byte * message, + int len, + GT2Bool reliable +) +{ + // Update stats. + //////////////// + if(reliable) + { + reliableBytesSentClient += len; + reliableMessagesSentClient++; + } + else + { + unreliableBytesSentClient += len; + unreliableMessagesSentClient++; + } + + // We're done with the message. + /////////////////////////////// + gt2FilteredSend(connection, filterID, message, len, reliable); +} + +static void ClientReceiveMonitor +( + GT2Connection connection, + int filterID, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + // Update stats. + //////////////// + if(reliable) + { + reliableBytesReceivedClient += len; + reliableMessagesReceivedClient++; + } + else + { + unreliableBytesReceivedClient += len; + unreliableMessagesReceivedClient++; + } + + // We're done with the message. + /////////////////////////////// + gt2FilteredReceive(connection, filterID, message, len, reliable); +} + +GT2Bool InitializeClient +( + void +) +{ + GT2ConnectionCallbacks connectionCallbacks; + GT2Result result; + char buffer[256]; + int rcode; + + // Setup callback structs. + ////////////////////////// + memset(&connectionCallbacks, 0, sizeof(GT2ConnectionCallbacks)); + connectionCallbacks.connected = ClientConnectedCallback; + connectionCallbacks.received = ClientReceivedCallback; + connectionCallbacks.closed = ClientClosedCallback; + + // Init stuff. + ////////////// + lastServerUpdate = 0; + updateHistoryStart = -1; + nextServerUpdateID = 0; + nextClientUpdateID = 0; + + // If there was no server, we're connecting to ourselves. + ///////////////////////////////////////////////////////// + if(!serverAddress[0]) + strcpy(serverAddress, "127.0.0.1" PORT_STRING); + + // Create the socket. + ///////////////////// + result = gt2CreateSocket(&Socket, NULL, 0, 0, ClientSocketErrorCallback); + if(result != GT2Success) + return GT2False; + + // Setup the initial data. + ////////////////////////// + rcode = gtEncode(MSG_C_INITIAL, MSG_C_INITIAL_STR, buffer, sizeof(buffer), + localNick); + if(rcode == -1) + return GT2False; + + // Connect. + /////////// + result = gt2Connect(Socket, &Connection, serverAddress, (GT2Byte *)buffer, rcode, 0, &connectionCallbacks, GT2False); + if(result != GT2Success) + return GT2False; + + // Add our traffic monitors. + //////////////////////////// + gt2AddSendFilter(Connection, ClientSendMonitor); + gt2AddReceiveFilter(Connection, ClientReceiveMonitor); + + return (Connection != NULL); +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aClient.h b/code/gamespy/gt2/gt2action/gt2aClient.h new file mode 100644 index 00000000..f6ef701e --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aClient.h @@ -0,0 +1,92 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2ACLIENT_H_ +#define _GT2ACLIENT_H_ + +#include "gt2aMath.h" +#include "gt2aLogic.h" + +#define UPDATE_HISTORY_LEN 250 + +typedef struct Player +{ + GT2Bool used; // If this slot is in use or not. + V2f position; // The current position (0 <= x,y < MAP_MAX). + float rotation; // Client's view angle (0 <= rotation < 360). + int motion; // The client's current motion (STILL, FORWARD, BACKWARD). + int turning; // The client's current tunring direction (STILL, LEFT, RIGHT). + char nick[MAX_NICK]; // The client's nick. + int score; // The client's score. + GT2Bool dead; // True if this client is currently dead. + float roll; // How much to roll them (-1->0->1). +} Player; + +typedef struct CObject +{ + GT2Bool used; // If this slot is in use or not. + ObjectType type; // The type of object. + V2f position; // The object's position. + float rotation; // The object's rotation. + unsigned long totalTime; // The amount of time this object has existed. +} CObject; + +typedef struct UpdateInfo +{ + int diff; // Time since the last update was received. + int len; // Length of the update, in bytes. +} UpdateInfo; + +extern float localRotation; +extern char serverAddress[128]; +extern int localMotion; +extern int localTurning; +extern Player players[MAX_PLAYERS]; +extern GT2Bool connected; +extern int localIndex; +extern unsigned long lastServerUpdate; +extern char localNick[MAX_NICK]; +extern CObject cObjects[MAX_OBJECTS]; +extern UpdateInfo updateHistory[UPDATE_HISTORY_LEN]; +extern int updateHistoryStart; +extern int ClientNumAsteroids; + +// Stats. +///////// +extern int reliableBytesSentClient; +extern int reliableBytesReceivedClient; +extern int unreliableBytesSentClient; +extern int unreliableBytesReceivedClient; +extern int reliableMessagesSentClient; +extern int reliableMessagesReceivedClient; +extern int unreliableMessagesSentClient; +extern int unreliableMessagesReceivedClient; + +GT2Bool InitializeClient +( + void +); + +void ClientThink +( + unsigned long now +); + +void SendPress +( + const char * value +); + +void SendChat +( + const char * message +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aDisplay.c b/code/gamespy/gt2/gt2action/gt2aDisplay.c new file mode 100644 index 00000000..c8173f39 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aDisplay.c @@ -0,0 +1,1655 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include "gt2aMain.h" +#include "gt2aDisplay.h" +#include "gt2aClient.h" +#include "gt2aMath.h" +#include "gt2aInput.h" +#include "gt2aLogic.h" +#include "TGAFile.h" + +#define TITLE "GameSpy GT2Action by Dan 'Mr. Pants' Schoenblum" +#define FONT GLUT_STROKE_ROMAN +#define TEXT_SCALE 10 +#define CHAT_LINES 3 +#define CHAT_SCROLL_TIME (3.5 * 1000) +#define NUM_STARS 1000 +#define FRAME_HISTORY_LEN 250 +#define NUM_ROCKET_TEXTURES 4 +#define NUM_EXPLOSION_TEXTURES 2 +#define NUM_SHIP_TEXTURES 2 +#define NUM_ASTEROID_TEXTURES 3 +#define NUM_MINE_TEXTURES 3 +#define NUM_SPINNER_TEXTURES 3 +#define WINDOW_MIN 0 +#define WINDOW_MAX 10000 +#define POSITION_HISTORY_LEN 5 +#define MAX_FLICKER 50 +#define STARS_SCALE 4 +#define RADAR_SIZE 1500 +#define FULL_VIEW (20000.0 * Zoom) +#define HALF_VIEW (FULL_VIEW / 2.0) +#define ZOOM_IN_SPEED 0.4 +#define ZOOM_OUT_SPEED 0.4 +#define ZOOM_MIN 1.0 +#define ZOOM_MAX 1.5 + +typedef struct Star +{ + V2f position; + V3b color; + byte alpha; + byte flicker; +} Star; + +GT2Bool fullScreen; + +GT2Bool DrawGraphsOption; +GT2Bool DrawUpdateTimeOption = GT2True; +GT2Bool DrawUpdateLengthOption = GT2True; +GT2Bool DrawFrameTimeOption = GT2True; +GT2Bool DrawMarksOption = GT2True; +GT2Bool DrawFPSOption; +GT2Bool DrawWithPredictionOption = GT2True; +GT2Bool DrawBackgroundOption = GT2True; +GT2Bool DrawStarsOption = GT2True; +GT2Bool DrawBoundingCirclesOption = GT2False; +GT2Bool DrawRadarOption = GT2True; +float RadarScaleOption = 1; +float RadarPointScaleOption = 2; +GT2Bool ViewClippingOption = GT2True; + +int screenWidth = 500; +int screenHeight = 500; + +static char chatLines[CHAT_LINES][CHAT_MAX]; +static unsigned long lastChatScroll; + +static unsigned long Now; +static unsigned long Diff; + +static GLuint backgroundTexture; +static GLuint shipTextures[NUM_SHIP_TEXTURES]; +static GLuint explosionTextures[NUM_EXPLOSION_TEXTURES]; +static GLuint rocketTextures[NUM_ROCKET_TEXTURES]; +static GLuint asteroidTextures[NUM_ASTEROID_TEXTURES]; +static GLuint mineTextures[NUM_MINE_TEXTURES]; +static GLuint spinnerTextures[NUM_SPINNER_TEXTURES]; + +static Star stars[NUM_STARS]; + +static int FrameHistory[FRAME_HISTORY_LEN]; +static int FrameHistoryStart; + +static V2f localPosition; + +static float Zoom = 1.0; + +static void ScrollChat +( + void +) +{ + int i; + + // First find where to start the copying. + ///////////////////////////////////////// + for(i = 0 ; i < (CHAT_LINES - 1) ; i++) + if(!chatLines[i][0]) + break; + + // Move all lines 1 step towards the end of the array. + ////////////////////////////////////////////////////// + for( ; i > 0 ; i--) + strcpy(chatLines[i], chatLines[i - 1]); + + // Clear the first line. + //////////////////////// + chatLines[0][0] = '\0'; +} + +static void RemoveOldestChat +( + void +) +{ + int i; + + // No lines? + //////////// + if(!chatLines[0][0]) + return; + + // Find the oldest line. + //////////////////////// + for(i = (CHAT_LINES - 1) ; !chatLines[i][0] && (i > 0) ; i--); + + // Remove it. + ///////////// + chatLines[i][0] = '\0'; +} + +static void AddChatLine +( + const char * message +) +{ + // Do scrolling. + //////////////// + ScrollChat(); + + // Copy in the new line. + //////////////////////// + strncpy(chatLines[0], message, CHAT_MAX); + chatLines[0][CHAT_MAX - 1] = '\0'; + + // Update the scroll time. + ////////////////////////// + lastChatScroll = current_time(); +} + +static int GetStringWidth +( + const char * string +) +{ + int width = 0; + + if(!string) + return 0; + + while(*string) + width += glutStrokeWidth(FONT, *string++); + + return (width * TEXT_SCALE); +} + +static void DrawString +( + const char * string, + int x, + int y, + const V3b color, + float scale +) +{ + scale *= TEXT_SCALE; + glColor3ubv(color); + glPushMatrix(); + glTranslatef(x, y, 0); + glScalef(scale, scale, 1); + while(*string) + glutStrokeCharacter(FONT, *string++); + glPopMatrix(); +} + +static int GetCharacterWidth +( + char ch +) +{ + int width; + + width = glutStrokeWidth(FONT, ch); + width *= TEXT_SCALE; + + return width; +} + +static void DrawCharacter +( + char ch, + int x, + int y, + const V3b color +) +{ + glColor3ubv(color); + glPushMatrix(); + glTranslatef(x, y, 0); + glScalef(TEXT_SCALE, TEXT_SCALE, 1); + glutStrokeCharacter(FONT, ch); + glPopMatrix(); +} + +void DisplayChat +( + const char * message +) +{ + // Add this to the chat buffer. + /////////////////////////////// + AddChatLine(message); +} + +static void DrawChat +( + void +) +{ + const char * str; + int x; + int y; + int i; + float scale; + + // Settings. + //////////// + x = 100; + y = 200; + scale = 0.3; + + // Draw all the chat messages. + ////////////////////////////// + for(i = 0 ; (i < CHAT_LINES) && chatLines[i][0] ; i++) + { + DrawString(chatLines[i], x, y, White, scale); + y += 500; + } + + // Get the current chat buffer. + /////////////////////////////// + str = GetChatBuffer(); + if(str) + DrawString(str, x, WINDOW_MAX - 500, White, scale); +} + +static void DrawBoundingCircle +( + float radius +) +{ + // Check the bounding circles option. + ///////////////////////////////////// + if(DrawBoundingCirclesOption) + { + int i; + V2f point; + + // Draw in white. + ///////////////// + glColor3f(1, 1, 1); + + // Draw the circle (circle, sphere, whatever). + ////////////////////////////////////////////// +#if 1 + glBegin(GL_LINE_LOOP); + for(i = 0 ; i < 360 ; i += 30) + { + RotationToVector(point, i); + ScaleV2f(point, point, radius); + glVertex2fv(point); + } + glEnd(); +#else + glutWireSphere(radius, 16, 16); +#endif + + // Draw the forward line. + ///////////////////////// + glBegin(GL_LINES); + glVertex2f(0, 0); + glVertex2f(0, radius * 2); + glEnd(); + } +} + +static void DrawPlayer +( + Player * player, + const V2f position, + float rotation +) +{ + int width; + static const float textScale = 0.25; + V2f textPosition; + float diff; + static char text[MAX_NICK + 32]; + + // Get the text to show. + //////////////////////// + sprintf(text, "%s: %d", player->nick, player->score); + + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); + glTranslatef(position[0], position[1], 0); + + width = GetStringWidth(text); + width *= textScale; + textPosition[0] = -(width / 2); +#if 0 + textPosition[1] = 700; +#else + if((position[0] + textPosition[0]) < 20) + textPosition[0] = (20 - position[0]); + diff = (MAP_MAX - (position[0] + textPosition[0] + width)); + if(diff < 0) + textPosition[0] += diff; + if(position[1] < (MAP_MAX - 1000)) + textPosition[1] = 700; + else + textPosition[1] = -1000; +#endif + DrawString(text, textPosition[0], textPosition[1], White, textScale); + + if(!player->dead) + { + glRotatef(rotation, 0, 0, -1); + + glPushMatrix(); + if(player->roll) + { + glScalef(1, 1, .005); + glRotatef(35 * player->roll, 0, 1, 0); + } + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + + if(player->motion) + glBindTexture(GL_TEXTURE_2D, shipTextures[1]); + else + glBindTexture(GL_TEXTURE_2D, shipTextures[0]); + + glColor4f(1, 1, 1, 1); + + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-500, -300); + glTexCoord2f(1, 0); + glVertex2f(500, -300); + glTexCoord2f(1, 1); + glVertex2f(500, 700); + glTexCoord2f(0, 1); + glVertex2f(-500, 700); + glEnd(); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glPopMatrix(); + } + + DrawBoundingCircle(PLAYER_RADIUS); + glPopMatrix(); +} + +static void Predict +( + V2f newPosition, + float * rotation, + Player * player +) +{ + GT2Bool local; + + assert(newPosition); + assert(rotation); + assert(player); + + // Is this the local player? + //////////////////////////// + local = (player == &players[localIndex]); + + // Should we predict? + ///////////////////// + if(DrawWithPredictionOption && !player->dead && player->motion) + { + // Predict new position. + //////////////////////// + ComputeNewPosition( + newPosition, + player->position, + player->motion, + player->rotation, + Diff, + PLAYER_SPEED, + GT2True); + + // Predict rotation if not local. + ///////////////////////////////// + if(!local) + *rotation = ComputeNewRotation(player->rotation, player->turning, Diff, PLAYER_TURN_SPEED); + } + else + { + // Just use the actual position. + //////////////////////////////// + CopyV2f(newPosition, player->position); + + // Use the real rotation if not local. + ////////////////////////////////////// + if(!local) + *rotation = player->rotation; + } + + // If local, use our rotation. + ////////////////////////////// + if(local) + *rotation = localRotation; +} + +static void DrawPlayers +( + void +) +{ + int i; + Player * player; + V2f position; + float rotation; + float distance; + + // First draw the other players. + //////////////////////////////// + for(i = 0 ; i < MAX_PLAYERS ; i++) + { + player = &players[i]; + + // Check if we need to draw the player. + /////////////////////////////////////// + if(player->used && (i != localIndex)) + { + // Predict the new position. + //////////////////////////// + Predict(position, &rotation, player); + + // Check if the players are close enough. + ///////////////////////////////////////// + if(ViewClippingOption) + { + distance = DistanceV2f(localPosition, position); + if(distance >= FULL_VIEW) + continue; + } + + // Do the actual drawing. + ///////////////////////// + DrawPlayer(player, position, rotation); + } + } + + // Draw the local player. + ///////////////////////// + assert((localIndex >= 0) && (localIndex < MAX_PLAYERS)); + player = &players[localIndex]; + + // Do the actual drawing. + // The position was already set in SetMapView. + ////////////////////////////////////////////// + DrawPlayer(player, localPosition, localRotation); +} + +static void DrawRocket +( + CObject * rocket +) +{ + V2f position; + + // Are we using prediction? + /////////////////////////// + if(DrawWithPredictionOption) + { + // Predict new position. + //////////////////////// + ComputeNewPosition( + position, + rocket->position, + FORWARD, + rocket->rotation, + Diff, + ROCKET_SPEED, + GT2False); + } + else + { + // Just use the actual position. + //////////////////////////////// + CopyV2f(position, rocket->position); + } + + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); + glTranslatef(position[0], position[1], 0); + + glRotatef(rocket->rotation, 0, 0, -1); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + + glBindTexture(GL_TEXTURE_2D, rocketTextures[RandomInt(0, NUM_ROCKET_TEXTURES - 1)]); + + glColor4f(1, 1, 1, 1); + + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-350, -350); + glTexCoord2f(1, 0); + glVertex2f(350, -350); + glTexCoord2f(1, 1); + glVertex2f(350, 350); + glTexCoord2f(0, 1); + glVertex2f(-350, 350); + glEnd(); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + + DrawBoundingCircle(ROCKET_RADIUS); + glPopMatrix(); +} + +static void DrawMine +( + CObject * mine +) +{ + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); + glTranslatef(mine->position[0], mine->position[1], 0); + + glRotatef(mine->rotation, 0, 0, -1); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + + glBindTexture(GL_TEXTURE_2D, mineTextures[(int)mine->position[0] % NUM_MINE_TEXTURES]); + + glColor4f(1, 1, 1, 1); + + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-800, -800); + glTexCoord2f(1, 0); + glVertex2f(800, -800); + glTexCoord2f(1, 1); + glVertex2f(800, 800); + glTexCoord2f(0, 1); + glVertex2f(-800, 800); + glEnd(); + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + + DrawBoundingCircle(MINE_RADIUS); + glPopMatrix(); +} + +static void DrawAsteroid +( + CObject * asteroid +) +{ + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); + glTranslatef(asteroid->position[0], asteroid->position[1], 0); + + glRotatef(asteroid->rotation, 0, 0, -1); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + + //glBindTexture(GL_TEXTURE_2D, asteroidTextures[(int)asteroid->position[0] % NUM_ASTEROID_TEXTURES]); + glBindTexture(GL_TEXTURE_2D, asteroidTextures[1]); + + glColor4f(1, 1, 1, 1); + + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-800, -800); + glTexCoord2f(1, 0); + glVertex2f(800, -800); + glTexCoord2f(1, 1); + glVertex2f(800, 800); + glTexCoord2f(0, 1); + glVertex2f(-800, 800); + glEnd(); + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + + DrawBoundingCircle(ASTEROID_RADIUS); + glPopMatrix(); +} + +static void DrawExplosion +( + CObject * explosion +) +{ + unsigned long totalTime; + + totalTime = (explosion->totalTime + Diff); + + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); + glTranslatef(explosion->position[0], explosion->position[1], 0); + + glPushMatrix(); + glScalef(totalTime / 100.0, totalTime / 100.0, 1); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + + glColor4f(1, 1, 1, 1); + + glBindTexture(GL_TEXTURE_2D, explosionTextures[1]); + + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-700, -700); + glTexCoord2f(1, 0); + glVertex2f(700, -700); + glTexCoord2f(1, 1); + glVertex2f(700, 700); + glTexCoord2f(0, 1); + glVertex2f(-700, 700); + glEnd(); + + glRotatef(totalTime, 0, 0, -1); + + glBindTexture(GL_TEXTURE_2D, explosionTextures[0]); + + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-700, -700); + glTexCoord2f(1, 0); + glVertex2f(700, -700); + glTexCoord2f(1, 1); + glVertex2f(700, 700); + glTexCoord2f(0, 1); + glVertex2f(-700, 700); + glEnd(); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glPopMatrix(); + + DrawBoundingCircle(EXPLOSION_RADIUS); + glPopMatrix(); +} + +static void DrawObjects +( + void +) +{ + int i; + CObject * object; + float distance; + + // Draw all the objects. + //////////////////////// + for(i = 0 ; i < MAX_OBJECTS ; i++) + { + object = &cObjects[i]; + + // Is the object in use? + //////////////////////// + if(object->used) + { + if(ViewClippingOption) + { + // Is the player close enough to see it? + //////////////////////////////////////// + distance = DistanceV2f(localPosition, object->position); + if(distance >= FULL_VIEW) + continue; + } + + // Draw it based on type. + ///////////////////////// + if(object->type == ObjectRocket) + DrawRocket(object); + else if(object->type == ObjectMine) + DrawMine(object); + else if(object->type == ObjectAsteroid) + DrawAsteroid(object); + else if(object->type == ObjectExplosion) + DrawExplosion(object); + } + } +} + +static void DrawConnecting +( + void +) +{ + static const char * text = "Connecting"; + static int width = -1; + static const V3b textColor = { 0, 204, 0}; + static unsigned long lastSpin; + static int spinnerIndex = -1; + static const char * spinnerText = "|/-\\"; + static const V3b spinnerColor = { 0, 204, 0 }; + char spinnerChar; + int spinnerX; + + // Get the width of the string if we don't already have it. + /////////////////////////////////////////////////////////// + if(width == -1) + width = GetStringWidth(text); + + // Draw the string. + /////////////////// + DrawString(text, (WINDOW_MAX - width) / 2, (WINDOW_MAX / 2), textColor, 1); + + // Init if its the first time. + ////////////////////////////// + if(spinnerIndex == -1) + { + spinnerIndex = 0; + lastSpin = Now; + } + // Check if its time to spin the spinner. + ///////////////////////////////////////// + else if((Now - lastSpin) >= 250) + { + spinnerIndex++; + spinnerIndex %= 4; + lastSpin = Now; + } + + // Get the char to show. + //////////////////////// + spinnerChar = spinnerText[spinnerIndex]; + + // Get the x position of the char. + ////////////////////////////////// + spinnerX = ((WINDOW_MAX - GetCharacterWidth(spinnerChar)) / 2); + + // Draw the spinner. + //////////////////// + DrawCharacter(spinnerChar, spinnerX, 3000, spinnerColor); +} + +static int ReadLittleInt +( + FILE * file +) +{ + byte bytes[4]; + int i; + + if(fread(bytes, 1, 4, file) != 4) + return -1; + + i = bytes[0]; + i |= (bytes[1] << 8); + i |= (bytes[2] << 16); + i |= (bytes[3] << 24); + + return i; +} + +static GT2Bool LoadTexture +( + const char * filename, + GLuint * texture, + GT2Bool useAlpha +) +{ + int width; + int height; + GLubyte * bytes; + int len; + int i; + GLubyte temp; + int format; + + // Load the file. + ///////////////// + bytes = LoadTGAFile(filename, &width, &height); + if(!bytes) + return GT2False; + + // Convert from BGRA to RGBA. + ///////////////////////////// + len = (width * height * 4); + for(i = 0 ; i < len ; i += 4) + { + temp = bytes[i]; + bytes[i] = bytes[i + 2]; + bytes[i + 2] = temp; + } + + // Generate and bind the texture. + ///////////////////////////////// + glGenTextures(1, texture); + glBindTexture(GL_TEXTURE_2D, *texture); + + // Load it from memory. + /////////////////////// + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + if(useAlpha) + format = 4; + else + format = 3; + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bytes); + + // Set to decal mode. + ///////////////////// + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + // Setup the min/mag filters. + ///////////////////////////// + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + free(bytes); + + return GT2True; +} + +static void DrawBackground +( + void +) +{ + if(!DrawBackgroundOption) + return; + + glEnable(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, backgroundTexture); + + glColor4f(1, 1, 1, 1); + + glBegin(GL_QUADS); +#if 0 + glTexCoord2f(0, 0); + glVertex2f(MAP_MIN, MAP_MIN); + glTexCoord2f(1, 0); + glVertex2f(MAP_MAX, MAP_MIN); + glTexCoord2f(1, 1); + glVertex2f(MAP_MAX, MAP_MAX); + glTexCoord2f(0, 1); + glVertex2f(MAP_MIN, MAP_MAX); +#else + glTexCoord2f(0.01, 0.01); + glVertex2f(MAP_MIN, MAP_MIN); + glTexCoord2f(0.99, 0.01); + glVertex2f(MAP_MAX, MAP_MIN); + glTexCoord2f(0.99, 0.99); + glVertex2f(MAP_MAX, MAP_MAX); + glTexCoord2f(0.01, 0.99); + glVertex2f(MAP_MIN, MAP_MAX); +#endif + glEnd(); + + glDisable(GL_TEXTURE_2D); +} + +static void InitStar +( + unsigned long now, + Star * star +) +{ + int color; + + star->position[0] = RandomFloat(MAP_MIN, MAP_MAX, GT2True); + star->position[1] = RandomFloat(MAP_MIN, MAP_MAX, GT2True); + + color = RandomInt(0, 19); + if(color < 15) + CopyV3b(star->color, White); + else if(color < 17) + CopyV3b(star->color, Red); + else if(color < 19) + CopyV3b(star->color, Yellow); + else + CopyV3b(star->color, Blue); + star->alpha = RandomInt(0, 255); + star->flicker = RandomInt(1, MAX_FLICKER); +} + +static void DrawStars +( + void +) +{ + int i; + int alpha; + V2f position; + + if(!DrawStarsOption) + return; + + // Figure out the eye position. + /////////////////////////////// + CopyV2f(position, localPosition); + if(position[0] < HALF_VIEW) + position[0] = HALF_VIEW; + else if(position[0] > (MAP_MAX - HALF_VIEW)) + position[0] = (MAP_MAX - HALF_VIEW); + if(position[1] < HALF_VIEW) + position[1] = HALF_VIEW; + else if(position[1] > (MAP_MAX - HALF_VIEW)) + position[1] = (MAP_MAX - HALF_VIEW); + + // Draw the stars. + ////////////////// + glEnable(GL_BLEND); + glPushMatrix(); + glTranslatef(position[0] / STARS_SCALE, position[1] / STARS_SCALE, 0); + + glBegin(GL_POINTS); + for(i = 0 ; i < NUM_STARS ; i++) + { + alpha = stars[i].alpha; + alpha += RandomInt(-stars[i].flicker, stars[i].flicker); + alpha = ClampInt(alpha, 0, 255); + glColor4ub(stars[i].color[0], stars[i].color[1], stars[i].color[2], (byte)alpha); + glVertex2fv(stars[i].position); + } + glEnd(); + glPopMatrix(); + glDisable(GL_BLEND); +} + +static void DrawSpinner +( + void +) +{ + float rotation; + float scale; + + scale = (Now % 1001); + scale -= 50; + scale /= 50; + + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); + glTranslatef(MAP_HALF, MAP_HALF, 0); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + +#if 1 + glPushMatrix(); + rotation = (Now % 360); + glRotatef(rotation, 0, 0, 1); + glBindTexture(GL_TEXTURE_2D, spinnerTextures[0]); + glColor4f(1, 1, 1, 1); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-SPINNER_COORD, -SPINNER_COORD); + glTexCoord2f(1, 0); + glVertex2f(SPINNER_COORD, -SPINNER_COORD); + glTexCoord2f(1, 1); + glVertex2f(SPINNER_COORD, SPINNER_COORD); + glTexCoord2f(0, 1); + glVertex2f(-SPINNER_COORD, SPINNER_COORD); + glEnd(); + glPopMatrix(); +#endif + + glPushMatrix(); + rotation = (Now % (360 * 8)); + rotation /= 8; + glRotatef(rotation, 0, 0, 1); + glScalef(scale, -scale, 1); + glBindTexture(GL_TEXTURE_2D, spinnerTextures[2]); + glColor4f(1, 1, 1, 0.8); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-SPINNER_COORD, -SPINNER_COORD); + glTexCoord2f(1, 0); + glVertex2f(SPINNER_COORD, -SPINNER_COORD); + glTexCoord2f(1, 1); + glVertex2f(SPINNER_COORD, SPINNER_COORD); + glTexCoord2f(0, 1); + glVertex2f(-SPINNER_COORD, SPINNER_COORD); + glEnd(); + glPopMatrix(); + + glPushMatrix(); + rotation = (Now % (360 * 7)); + rotation /= 7; + glRotatef(rotation, 0, 0, 1); + glScalef(scale, -scale, 1); + glBindTexture(GL_TEXTURE_2D, spinnerTextures[1]); + glColor4f(1, 1, 1, 0.8); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-SPINNER_COORD, -SPINNER_COORD); + glTexCoord2f(1, 0); + glVertex2f(SPINNER_COORD, -SPINNER_COORD); + glTexCoord2f(1, 1); + glVertex2f(SPINNER_COORD, SPINNER_COORD); + glTexCoord2f(0, 1); + glVertex2f(-SPINNER_COORD, SPINNER_COORD); + glEnd(); + glPopMatrix(); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + + DrawBoundingCircle(SPINNER_RADIUS); + glPopMatrix(); +} + +static void DrawGraphs +( + void +) +{ + int i; + int diff; + int len; + int maxDiff = 0; + int maxLen = 0; + + if(updateHistoryStart == -1) + return; + + if(!DrawGraphsOption) + return; + + glPushMatrix(); + glScalef(20, 20, 1); + if(DrawUpdateTimeOption) + { + glColor3ubv(Green); + glBegin(GL_LINE_STRIP); + for(i = 0 ; i < UPDATE_HISTORY_LEN ; i++) + { + diff = updateHistory[(updateHistoryStart + i) % UPDATE_HISTORY_LEN].diff; + + if(diff > maxDiff) + maxDiff = diff; + + if(diff == -1) + { + glColor3ubv(Red); + glVertex2f(i * 2, 50); + glColor3ubv(Green); + } + else + { + if(diff == -1) + diff = 0; + glVertex2f(i * 2, diff / 2); + } + } + glEnd(); + } + + if(DrawUpdateLengthOption) + { + glColor3ubv(Purple); + glBegin(GL_LINE_STRIP); + for(i = 0 ; i < UPDATE_HISTORY_LEN ; i++) + { + len = updateHistory[(updateHistoryStart + i) % UPDATE_HISTORY_LEN].len; + + if(len > maxLen) + maxLen = len; + + glVertex2f(i * 2, len / 8); + } + glEnd(); + } + + if(DrawFrameTimeOption) + { + glColor3ubv(Yellow); + glBegin(GL_LINE_STRIP); + for(i = 0 ; i < FRAME_HISTORY_LEN ; i++) + { + diff = FrameHistory[(FrameHistoryStart + i) % FRAME_HISTORY_LEN]; + glVertex2f(i * 2, diff / 2); + } + glEnd(); + } + + if(DrawMarksOption) + { + glColor3ubv(White); + glBegin(GL_LINES); + for(i = 0 ; i < screenHeight; i += 25) + { + glVertex2f(screenWidth - 10, i); + glVertex2f(screenWidth, i); + } + glEnd(); + } + + glPopMatrix(); +} + +static void DrawFPS +( + unsigned long diff +) +{ + if(DrawFPSOption && diff) + { + static int counter; + static char buf[64]; + if(++counter == 10) + { + counter = 0; + sprintf(buf, "FPS: %d", 1000 / diff); + } + DrawString(buf, WINDOW_MAX - 1500, 600, Grey, 0.2); + } + + { + char buf[64]; + sprintf(buf, "%d asteroid%s", ClientNumAsteroids, + (ClientNumAsteroids==1) ? "" : "s"); + DrawString(buf, WINDOW_MAX - 3500, 600, Grey, 0.2); + } +} + +static void DrawRadar +( + void +) +{ + int i; + Player * player; + CObject * object; + GLdouble plane0[4] = { 0, 1, 1, 0 }; + GLdouble plane1[4] = { 1, 0, 1, 0 }; + + if(!DrawRadarOption) + return; + + glPushMatrix(); + // Move to the upper-right corner. + ////////////////////////////////// + glTranslatef( + WINDOW_MAX - (RADAR_SIZE * RadarScaleOption), + WINDOW_MAX - (RADAR_SIZE * RadarScaleOption), + 0); + + // Scale so we can draw in map space. + ///////////////////////////////////// + glScalef( + RADAR_SIZE / MAP_MAX * RadarScaleOption, + RADAR_SIZE / MAP_MAX * RadarScaleOption, + 1); + +#if 1 + // Enable textures. + /////////////////// + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glColor4f(1, 1, 1, 0.5); + + // Draw a background. + ///////////////////// + glBindTexture(GL_TEXTURE_2D, backgroundTexture); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(0, 0); + glTexCoord2f(1, 0); + glVertex2f(MAP_MAX, 0); + glTexCoord2f(1, 1); + glVertex2f(MAP_MAX, MAP_MAX); + glTexCoord2f(0, 1); + glVertex2f(0, MAP_MAX); + glEnd(); + + // Draw the spinner. + //////////////////// + glPushMatrix(); + glTranslatef(MAP_HALF, MAP_HALF, 0); + glRotatef(Now % 3600, 0, 0, 1); + + glBindTexture(GL_TEXTURE_2D, spinnerTextures[0]); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-SPINNER_COORD, -SPINNER_COORD); + glTexCoord2f(1, 0); + glVertex2f(SPINNER_COORD, -SPINNER_COORD); + glTexCoord2f(1, 1); + glVertex2f(SPINNER_COORD, SPINNER_COORD); + glTexCoord2f(0, 1); + glVertex2f(-SPINNER_COORD, SPINNER_COORD); + glEnd(); + glPopMatrix(); + + // Disable textures. + //////////////////// + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); +#endif + + // Draw a box around the radar. + /////////////////////////////// + glColor3ubv(Yellow); + glBegin(GL_LINE_STRIP); + glVertex2f(0, MAP_MAX); + glVertex2f(0, 0); + glVertex2f(MAP_MAX, 0); + glEnd(); + + // Setup the clipping planes. + ///////////////////////////// + glEnable(GL_CLIP_PLANE0); + glEnable(GL_CLIP_PLANE1); + glClipPlane(GL_CLIP_PLANE0, plane0); + glClipPlane(GL_CLIP_PLANE1, plane1); + + // Scale points. + //////////////// + if(RadarPointScaleOption != 1) + glPointSize(RadarPointScaleOption); + + glBegin(GL_POINTS); + for(i = 0 ; i < MAX_OBJECTS ; i++) + { + object = &cObjects[i]; + + if(!object->used) + continue; + + if(object->type == ObjectRocket) + glColor3ubv(Red); + else if(object->type == ObjectMine) + glColor3ubv(Yellow); + else if(object->type == ObjectAsteroid) + glColor3ubv(White); + else + continue; + + glVertex2fv(object->position); + } + + glColor3ubv(Orange); + for(i = 0 ; i < MAX_PLAYERS ; i++) + { + if(i == localIndex) + continue; + + player = &players[i]; + + if(!player->used) + continue; + + glVertex2fv(player->position); + } + + glColor3ubv(Green); + glVertex2fv(players[localIndex].position); + glEnd(); + + // Unscale points. + ////////////////// + if(RadarPointScaleOption != 1) + glPointSize(1); + glPopMatrix(); + + glDisable(GL_CLIP_PLANE0); + glDisable(GL_CLIP_PLANE1); +} + +static void SetMapView +( + void +) +{ + V2f corner; + Player * player; + float rotation; + static V2f positionHistory[POSITION_HISTORY_LEN]; + static int positionHistoryStart = -1; + int i; + + // Get the local player. + //////////////////////// + player = &players[localIndex]; + + // Predict the local player's position. + /////////////////////////////////////// + Predict(localPosition, &rotation, player); + + // Put this position in the history. + //////////////////////////////////// + if(positionHistoryStart == -1) + { + positionHistoryStart = 0; + for(i = 0 ; i < POSITION_HISTORY_LEN ; i++) + CopyV2f(positionHistory[i], localPosition); + } + else + { + CopyV2f(positionHistory[positionHistoryStart++], localPosition); + positionHistoryStart %= POSITION_HISTORY_LEN; + } + + // Average out the position history to get the display position. + //////////////////////////////////////////////////////////////// + SetV2f(localPosition, 0, 0); + for(i = 0 ; i < POSITION_HISTORY_LEN ; i++) + AddV2f(localPosition, localPosition, positionHistory[i]); + ScaleV2f(localPosition, localPosition, (1.0 / POSITION_HISTORY_LEN)); + + // Set the left and bottom of the view. + /////////////////////////////////////// + SubScalarV2f(corner, localPosition, HALF_VIEW); + + // Don't show anything outside the map. + /////////////////////////////////////// + ClampV2f(corner, corner, 0, MAP_MAX - FULL_VIEW); + + glLoadIdentity(); + glViewport(0, 0, screenWidth, screenHeight); + gluOrtho2D(corner[0], corner[0] + FULL_VIEW, corner[1], corner[1] + FULL_VIEW); +} + +static void SetWindowView +( + void +) +{ + glLoadIdentity(); + glViewport(0, 0, screenWidth, screenHeight); + gluOrtho2D(0, WINDOW_MAX, 0, WINDOW_MAX); +} + +static void Display +( + void +) +{ + static unsigned long lastDisplay; + unsigned long now; + unsigned long diff; + + // Get the current time. + //////////////////////// + now = current_time(); + + // Get the time difference. + /////////////////////////// + diff = (now - lastDisplay); + + // Update the history. + ////////////////////// + if(FrameHistoryStart == -1) + FrameHistoryStart = 0; + else + { + FrameHistory[FrameHistoryStart++] = diff; + FrameHistoryStart %= FRAME_HISTORY_LEN; + } + + // New last display. + //////////////////// + lastDisplay = now; + + // Set the zoom. + //////////////// + if(localMotion == STILL) + { + if(Zoom > ZOOM_MIN) + { + Zoom -= (ZOOM_IN_SPEED * diff / 1000); + if(Zoom < ZOOM_MIN) + Zoom = ZOOM_MIN; + } + } + else + { + if(Zoom < ZOOM_MAX) + { + Zoom += (ZOOM_OUT_SPEED * diff / 1000); + if(Zoom > ZOOM_MAX) + Zoom = ZOOM_MAX; + } + } + + // Clear the color buffer. + ////////////////////////// + glClear(GL_COLOR_BUFFER_BIT); + + // We want to know how long since the last server update. + ///////////////////////////////////////////////////////// + Now = now; + Diff = (now - lastServerUpdate); + + // Are we connected to the server? + ////////////////////////////////// + if(connected) + { + // Set the view for drawing in map space. + ///////////////////////////////////////// + SetMapView(); + + // Draw the ground. + /////////////////// + DrawBackground(); + + // Draw some stars. + /////////////////// + DrawStars(); + + // Draw the spinner. + //////////////////// + DrawSpinner(); + + // Draw all the objects. + //////////////////////// + DrawObjects(); + + // Draw all the players. + //////////////////////// + DrawPlayers(); + + // Set the view for drawing in window space. + //////////////////////////////////////////// + SetWindowView(); + + // Draw the radar. + ////////////////// + DrawRadar(); + + // Draw the chat. + ///////////////// + DrawChat(); + + // Draw graphs. + /////////////// + DrawGraphs(); + + // Show the framerate. + ////////////////////// + DrawFPS(diff); + } + else + { + // Set the view for drawing in window space. + //////////////////////////////////////////// + SetWindowView(); + + // Draw the connecting screen. + ////////////////////////////// + DrawConnecting(); + } + + // Saw the front and back buffers. + ////////////////////////////////// + glutSwapBuffers(); +} + +static void Reshape +( + int width, + int height +) +{ + screenWidth = width; + screenHeight = height; + + // Setup a simple 2D orthographic view. + /////////////////////////////////////// + glLoadIdentity(); + glViewport(0, 0, width, height); + gluOrtho2D(0, MAP_MAX, 0, MAP_MAX); +} + +void InitializeDisplay +( + void +) +{ + int i; + unsigned long now; + char buffer[32]; + + // Init glut window. + //////////////////// + + glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_ALPHA); + + if(fullScreen) + { + glutGameModeString("640x480:32"); + glutEnterGameMode(); + //glutFullScreen(); + glutSetCursor(GLUT_CURSOR_NONE); + } + else + { + glutInitWindowSize(screenWidth, screenHeight); + glutInitWindowPosition(200, 200); + glutCreateWindow(TITLE); + } + // Set display callbacks. + ///////////////////////// + glutDisplayFunc(Display); + glutReshapeFunc(Reshape); + + // Set the background color. + //////////////////////////// + glClearColor(0, 0, 0, 0); + + // Smooth points. + ///////////////// + glEnable(GL_POINT_SMOOTH); + glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); + + // Don't show the backs of polygons. + //////////////////////////////////// + glEnable(GL_CULL_FACE); + + // We always use the same blend. + //////////////////////////////// + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Load textures. + ///////////////// + printf("Loading textures..."); + LoadTexture("images/space.tga", &backgroundTexture, GT2False); + printf("."); + for(i = 0 ; i < NUM_SHIP_TEXTURES ; i++) + { + sprintf(buffer, "images/ship%d.tga", i); + LoadTexture(buffer, &shipTextures[i], GT2True); + printf("."); + } + for(i = 0 ; i < NUM_EXPLOSION_TEXTURES ; i++) + { + sprintf(buffer, "images/explosion%d.tga", i); + LoadTexture(buffer, &explosionTextures[i], GT2True); + printf("."); + } + for(i = 0 ; i < NUM_ROCKET_TEXTURES ; i++) + { + sprintf(buffer, "images/rocket%d.tga", i); + LoadTexture(buffer, &rocketTextures[i], GT2True); + printf("."); + } + for(i = 0 ; i < NUM_ASTEROID_TEXTURES ; i++) + { + sprintf(buffer, "images/asteroid%d.tga", i); + LoadTexture(buffer, &asteroidTextures[i], GT2True); + printf("."); + } + for(i = 0 ; i < NUM_MINE_TEXTURES ; i++) + { + sprintf(buffer, "images/mine%d.tga", i); + LoadTexture(buffer, &mineTextures[i], GT2True); + printf("."); + } + for(i = 0 ; i < NUM_MINE_TEXTURES ; i++) + { + sprintf(buffer, "images/spinner%d.tga", i); + LoadTexture(buffer, &spinnerTextures[i], GT2True); + printf("."); + } + printf("\n"); + + // Get the time. + //////////////// + now = current_time(); + + // Setup stars. + /////////////// + for(i = 0 ; i < NUM_STARS ; i++) + InitStar(now, &stars[i]); + + // Setup frame history. + /////////////////////// + FrameHistoryStart = -1; +} + +void ShutdownDisplay +( + void +) +{ + if(fullScreen) + { + glutLeaveGameMode(); + } +} + +void DisplayThink +( + unsigned long now +) +{ + static unsigned long last; + unsigned long diff; + + // Get the frame time. + ////////////////////// + now = current_time(); + diff = (now - last); + if(diff < 5) + return; + last = now; + + // Is it time to scroll the chat? + ///////////////////////////////// + if((now - lastChatScroll) > CHAT_SCROLL_TIME) + { + RemoveOldestChat(); + lastChatScroll = now; + } + + // Update the frame. + //////////////////// + glutPostRedisplay(); +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aDisplay.h b/code/gamespy/gt2/gt2action/gt2aDisplay.h new file mode 100644 index 00000000..885a989b --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aDisplay.h @@ -0,0 +1,54 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2ADISPLAY_H_ +#define _GT2ADISPLAY_H_ + +extern GT2Bool fullScreen; + +extern GT2Bool DrawGraphsOption; +extern GT2Bool DrawUpdateTimeOption; +extern GT2Bool DrawUpdateLengthOption; +extern GT2Bool DrawFrameTimeOption; +extern GT2Bool DrawMarksOption; +extern GT2Bool DrawFPSOption; +extern GT2Bool DrawWithPredictionOption; +extern GT2Bool DrawBackgroundOption; +extern GT2Bool DrawStarsOption; +extern GT2Bool DrawBoundingCirclesOption; +extern GT2Bool DrawRadarOption; +extern float RadarScaleOption; +extern float RadarPointScaleOption; +extern GT2Bool ViewClippingOption; + +extern int screenWidth; +extern int screenHeight; + +void InitializeDisplay +( + void +); + +void ShutdownDisplay +( + void +); + +void DisplayThink +( + unsigned long now +); + +void DisplayChat +( + const char * message +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aInput.c b/code/gamespy/gt2/gt2action/gt2aInput.c new file mode 100644 index 00000000..b2a68bd5 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aInput.c @@ -0,0 +1,634 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include +#include "gt2aMain.h" +#include "gt2aInput.h" +#include "gt2aClient.h" +#include "gt2aDisplay.h" +#include "gt2aSound.h" + +#define TOGGLE(b) { ((b) = !(b)); printf( #b " = %s\n", (b)?"ON":"OFF"); } + +GT2Bool upPressed; +GT2Bool downPressed; +GT2Bool leftPressed; +GT2Bool rightPressed; + +GT2Bool UseJoystick = GT2False; + +void SetKeyboardNormal(void); +void SetKeyboardChat(void); + +static char chatBuffer[CHAT_MAX]; +static int nChatChars; +static GT2Bool chatting; + +static void KeyboardPress +( + unsigned char key, + int x, + int y +) +{ + // Lowercase it. + //////////////// + key = tolower(key); + + switch(key) + { + case 0x1B: // esc + ShutdownDisplay(); + exit(1); + + case 't': + SetKeyboardChat(); + break; + + case 'p': + TOGGLE(DrawWithPredictionOption); + break; + + case 'c': + TOGGLE(ViewClippingOption); + break; + + case 'w': + if(localIndex != -1) + { + char buffer[32]; + Player * player; + + player = &players[localIndex]; + sprintf(buffer, "%.0f %.0f %.0f", player->position[0], player->position[1], player->rotation); + DisplayChat(buffer); + } + break; + + case 'j': + TOGGLE(UseJoystick); + break; + + case 'g': + TOGGLE(DrawGraphsOption); + break; + + case 'r': + TOGGLE(DrawBoundingCirclesOption); + break; + } +} + +static void KeyboardRelease +( + unsigned char key, + int x, + int y +) +{ + // Lowercase it. + //////////////// + key = tolower(key); + + switch(key) + { + case 'b': + SendPress("mine"); + break; + + case ' ': + SendPress("rocket"); + break; + } +} + +static void SpecialKeyboardPress +( + int key, + int dummyx, + int dummyy +) +{ + switch(key) + { + case GLUT_KEY_UP: + upPressed = GT2True; + localMotion = FORWARD; + break; + + case GLUT_KEY_DOWN: + downPressed = GT2True; + localMotion = BACKWARD; + break; + + case GLUT_KEY_LEFT: + leftPressed = GT2True; + localTurning = LEFT; + break; + + case GLUT_KEY_RIGHT: + rightPressed = GT2True; + localTurning = RIGHT; + break; + } +} + +static void SpecialKeyboardRelease +( + int key, + int dummyx, + int dummyy +) +{ + switch(key) + { + case GLUT_KEY_UP: + upPressed = GT2False; + if(downPressed) + localMotion = BACKWARD; + else + localMotion = STILL; + break; + + case GLUT_KEY_DOWN: + downPressed = GT2False; + if(upPressed) + localMotion = FORWARD; + else + localMotion = STILL; + break; + + case GLUT_KEY_LEFT: + leftPressed = GT2False; + if(rightPressed) + localTurning = RIGHT; + else + localTurning = STILL; + break; + + case GLUT_KEY_RIGHT: + rightPressed = GT2False; + if(leftPressed) + localTurning = LEFT; + else + localTurning = STILL; + break; + } +} + +const char * GetChatBuffer +( + void +) +{ + if(chatting) + return (const char *)chatBuffer; + + return NULL; +} + +static void ChatAddChar +( + char c +) +{ + if(nChatChars < (CHAT_MAX - 1)) + { + chatBuffer[nChatChars++] = c; + chatBuffer[nChatChars] = '\0'; + } +} + +static void ChatBackspace +( + void +) +{ + if(nChatChars) + chatBuffer[--nChatChars] = '\0'; +} + +static void ChatClear +( + void +) +{ + chatBuffer[0] = '\0'; + nChatChars = 0; +} + +static void ChatSend +( + void +) +{ + assert(nChatChars < CHAT_MAX); + + // Send it. + /////////// + if(nChatChars > 0) + SendChat(chatBuffer); +} + +static void ChatKeyboardPress +( + unsigned char key, + int x, + int y +) +{ + switch(key) + { + case 0x1B: // esc + // Back to normal keyboard handling. + //////////////////////////////////// + SetKeyboardNormal(); + + return; + + case 0x0D: // enter + // Send the current chat message. + ///////////////////////////////// + ChatSend(); + + // Back to normal keyboard handling. + //////////////////////////////////// + SetKeyboardNormal(); + + return; + + case 0x08: // backspace + case 0x7F: // delete + ChatBackspace(); + break; + } + + // Is it printable? + /////////////////// + if(isprint(key)) + { + // Add it to the buffer. + //////////////////////// + ChatAddChar(key); + } +} + +static void ChatKeyboardRelease +( + unsigned char key, + int x, + int y +) +{ + switch(key) + { + } +} + +void SetKeyboardNormal +( + void +) +{ + // Ignore key repeats. + ////////////////////// + glutSetKeyRepeat(GLUT_KEY_REPEAT_OFF); + + // Set glut callbacks. + ////////////////////// + glutKeyboardFunc(KeyboardPress); + glutKeyboardUpFunc(KeyboardRelease); + + // We're not chatting. + ////////////////////// + chatting = GT2False; +} + +void SetKeyboardChat +( + void +) +{ + // We don't care about key repeats now. + /////////////////////////////////////// + glutSetKeyRepeat(GLUT_KEY_REPEAT_DEFAULT); + + // Set glut callbacks. + ////////////////////// + glutKeyboardFunc(ChatKeyboardPress); + glutKeyboardUpFunc(ChatKeyboardRelease); + + // Clear the chat buffer. + ///////////////////////// + ChatClear(); + + // We're chatting. + ////////////////// + chatting = GT2True; +} +static void MouseMotionPassive(int x, int y) +{ + static int x_hold = 0; + static int y_hold = 0; + + if(x_hold) + { + localRotation += -(x_hold - x); + + } + + if( (x <= 0) || (x >= 639) ) + glutWarpPointer(320,240); + + x_hold = x; + y_hold = y; +} + +static void MouseButton(int button, int state, int x, int y) +{ + switch(button) + { + case GLUT_LEFT_BUTTON: + if(state == GLUT_DOWN) + SendPress("rocket"); + break; + case GLUT_RIGHT_BUTTON: + switch(state) + { + case GLUT_DOWN: + upPressed = GT2True; + localMotion = FORWARD; + break; + case GLUT_UP: + upPressed = GT2False; + localMotion = STILL; + break; + } + break; + case GLUT_MIDDLE_BUTTON: + switch(state) + { + case GLUT_DOWN: + upPressed = GT2True; + localMotion = BACKWARD; + break; + case GLUT_UP: + upPressed = GT2False; + localMotion = STILL; + break; + } + break; + } +} + + +static void JoystickProcess(unsigned int buttonMask, int x, int y, int z) +{ + static BYTE buttonA = GT2False; + + + if(!UseJoystick) + return; + + if(buttonMask & GLUT_JOYSTICK_BUTTON_A) + { + if(buttonA == GT2False) + { + SendPress("rocket"); + buttonA = GT2True; + } + } + else + buttonA = GT2False; + + if (x < -50) + localTurning = LEFT; + else if(x > 50) + localTurning = RIGHT; + else + localTurning = STILL; + + if(z < -300) + localMotion = FORWARD; + else if(z > 300) + localMotion = BACKWARD; + else + localMotion = STILL; + +} + +static int MainMenu; +enum +{ + MainToggleSound, + MainToggleViewClipping +}; +static void MainMenuCallback +( + int item +) +{ + switch(item) + { + case MainToggleSound: + ToggleSound(); + break; + case MainToggleViewClipping: + TOGGLE(ViewClippingOption); + break; + } +} + +static int GraphMenu; +enum +{ + GraphToggleGraphs, + GraphToggleUpdateTime, + GraphToggleUpdateLength, + GraphToggleFrameTime, + GraphToggleMarks +}; +static void GraphMenuCallback +( + int item +) +{ + switch(item) + { + case GraphToggleGraphs: + TOGGLE(DrawGraphsOption); + break; + case GraphToggleUpdateTime: + TOGGLE(DrawUpdateTimeOption); + break; + case GraphToggleUpdateLength: + TOGGLE(DrawUpdateLengthOption); + break; + case GraphToggleFrameTime: + TOGGLE(DrawFrameTimeOption); + break; + case GraphToggleMarks: + TOGGLE(DrawMarksOption); + break; + } +} + +static int DrawMenu; +enum +{ + DrawToggleFPS, + DrawTogglePrediction, + DrawToggleBackground, + DrawToggleStars, + DrawToggleBoundingCircles +}; +static void DrawMenuCallback +( + int item +) +{ + switch(item) + { + case DrawToggleFPS: + TOGGLE(DrawFPSOption); + break; + case DrawTogglePrediction: + TOGGLE(DrawWithPredictionOption); + break; + case DrawToggleBackground: + TOGGLE(DrawBackgroundOption); + break; + case DrawToggleStars: + TOGGLE(DrawStarsOption); + break; + case DrawToggleBoundingCircles: + TOGGLE(DrawBoundingCirclesOption); + break; + } +} + +static int RadarMenu; +enum +{ + RadarDrawRadar, + RadarSmallRadar, + RadarMediumRadar, + RadarLargeRadar, + RadarSmallPoints, + RadarMediumPoints, + RadarLargePoints +}; +static void RadarMenuCallback +( + int item +) +{ + switch(item) + { + case RadarDrawRadar: + TOGGLE(DrawRadarOption); + break; + case RadarSmallRadar: + RadarScaleOption = 0.5; + break; + case RadarMediumRadar: + RadarScaleOption = 1; + break; + case RadarLargeRadar: + RadarScaleOption = 2; + break; + case RadarSmallPoints: + RadarPointScaleOption = 1; + break; + case RadarMediumPoints: + RadarPointScaleOption = 2; + break; + case RadarLargePoints: + RadarPointScaleOption = 3; + break; + } +} + +void InitializeInput +( + void +) +{ + // Set the keyboard to normal. + ////////////////////////////// + SetKeyboardNormal(); + + // The glut handlers for "special" keys don't change. + ///////////////////////////////////////////////////// + glutSpecialFunc(SpecialKeyboardPress); + glutSpecialUpFunc(SpecialKeyboardRelease); + + + // The glut handlers for mouse input + //////////////////////////////////// + glutPassiveMotionFunc(MouseMotionPassive); + glutMotionFunc(MouseMotionPassive); + glutMouseFunc (MouseButton); + + // The glut handler for joystick input + ////////////////////////////////////// + glutJoystickFunc(JoystickProcess, 1); + + // Setup the menu. + ////////////////// + MainMenu = glutCreateMenu(MainMenuCallback); + + // Graph sub-menu. + ////////////////// + GraphMenu = glutCreateMenu(GraphMenuCallback); + glutSetMenu(GraphMenu); + glutAddMenuEntry("Toggle Graphs", GraphToggleGraphs); + glutAddMenuEntry("Toggle Update Time", GraphToggleUpdateTime); + glutAddMenuEntry("Toggle Update Length", GraphToggleUpdateLength); + glutAddMenuEntry("Toggle Frame Time", GraphToggleFrameTime); + glutAddMenuEntry("Toggle Marks", GraphToggleMarks); + glutSetMenu(MainMenu); + glutAddSubMenu("Graph", GraphMenu); + + // Draw sub-menu. + ///////////////// + DrawMenu = glutCreateMenu(DrawMenuCallback); + glutSetMenu(DrawMenu); + glutAddMenuEntry("Toggle FPS", DrawToggleFPS); + glutAddMenuEntry("Toggle Prediction", DrawTogglePrediction); + glutAddMenuEntry("Toggle Background", DrawToggleBackground); + glutAddMenuEntry("Toggle Stars", DrawToggleStars); + glutAddMenuEntry("Toggle Bounding Circles", DrawToggleBoundingCircles); + glutSetMenu(MainMenu); + glutAddSubMenu("Draw", DrawMenu); + + // Radar sub-menu. + ////////////////// + RadarMenu = glutCreateMenu(RadarMenuCallback); + glutSetMenu(RadarMenu); + glutAddMenuEntry("Draw Radar", RadarDrawRadar); + glutAddMenuEntry("Small Radar", RadarSmallRadar); + glutAddMenuEntry("Medium Radar", RadarMediumRadar); + glutAddMenuEntry("Large Radar", RadarLargeRadar); + glutAddMenuEntry("Small Points", RadarSmallPoints); + glutAddMenuEntry("Medium Points", RadarMediumPoints); + glutAddMenuEntry("Large Points", RadarLargePoints); + glutSetMenu(MainMenu); + glutAddSubMenu("Radar", RadarMenu); + + // Main menu. + ///////////// + glutAddMenuEntry("Toggle Sound", MainToggleSound); + glutAddMenuEntry("Toggle View Clipping", MainToggleViewClipping); + + // Attach the menu. + /////////////////// + glutAttachMenu(GLUT_RIGHT_BUTTON); +} diff --git a/code/gamespy/gt2/gt2action/gt2aInput.h b/code/gamespy/gt2/gt2action/gt2aInput.h new file mode 100644 index 00000000..cadafa0c --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aInput.h @@ -0,0 +1,24 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2AINPUT_H_ +#define _GT2AINPUT_H_ + +void InitializeInput +( + void +); + +const char * GetChatBuffer +( + void +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aLogic.c b/code/gamespy/gt2/gt2action/gt2aLogic.c new file mode 100644 index 00000000..14603fa7 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aLogic.c @@ -0,0 +1,860 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include "gt2aMain.h" +#include "gt2aLogic.h" +#include "gt2aMath.h" +#include "gt2aSound.h" +#include "gt2aServer.h" + +#define DEATH_MESSAGES 0 + +SObject sObjects[MAX_OBJECTS]; +int numObjects; +static unsigned long Now; +static unsigned long Diff; +static int NumAsteroids; + +static void ExplodeObject(SObject * object); +static void InitAsteroid(SObject * asteroid); + +static SObject * AddObject +( + ObjectType type +) +{ + int i; + SObject * object; + + // Find an open slot. + ///////////////////// + for(i = 0 ; i < MAX_OBJECTS ; i++) + if(!sObjects[i].used) + break; + + // Nothing open? + //////////////// + if(i == MAX_OBJECTS) + return NULL; + + // Clear it. + //////////// + object = &sObjects[i]; + memset(object, 0, sizeof(SObject)); + object->used = GT2True; + object->type = type; + object->owner = -1; + + // Set the start time. + ////////////////////// + object->startTime = Now; + + // One more object. + /////////////////// + numObjects++; + assert(numObjects <= MAX_OBJECTS); + + return &sObjects[i]; +} + +static void RemoveObject +( + SObject * object +) +{ + assert(object); + if(!object) + return; + + // Mark this object for removal. + //////////////////////////////// + object->remove = GT2True; +} + +static void ClientKilled +( + Client * killed, + Client * killer +) +{ +#if DEATH_MESSAGES + char buffer[MAX_NICK + MAX_NICK + 32]; +#endif + + assert(killed); + + // Is there a killer? + ///////////////////// + if(killer) + { + // Self kill? + ///////////// + if(killer == killed) + { + // Subtract a point. + //////////////////// + killer->score--; + } + else + { + // Give the killer a point. + /////////////////////////// + killer->score++; + } + } + + // Kill the killed. + /////////////////// + killed->dead = GT2True; + killed->spawnTime = (Now + DEATH_TIME); + + // Play the death sound. + //////////////////////// + SendSound(SOUND_DIE, -1); + +#if DEATH_MESSAGES + // Let everyone know. + ///////////////////// + if(killer == killed) + sprintf(buffer, "%s killed himself", killed->nick); + else if(killer) + sprintf(buffer, "%s killed %s", killer->nick, killed->nick); + else + sprintf(buffer, "%s was killed", killed->nick); + BroadcastText(buffer, -1, GT2True); +#endif +} + +/////////// +//ROCKET // +/////////// + +static void RocketThink +( + SObject * rocket +) +{ + assert(rocket); + + // Move the rocket. + /////////////////// + ComputeNewPosition( + rocket->position, + rocket->position, + FORWARD, + rocket->rotation, + Diff, + ROCKET_SPEED, + GT2False); + + // Check if we should disappear. + //////////////////////////////// + if((rocket->position[0] < (MAP_MIN - MAP_EXTRA)) || + (rocket->position[0] > (MAP_MAX + MAP_EXTRA)) || + (rocket->position[1] < (MAP_MIN - MAP_EXTRA)) || + (rocket->position[1] > (MAP_MAX + MAP_EXTRA))) + { + RemoveObject(rocket); + } +} + +static void RocketTouchObject +( + SObject * rocket, + SObject * object +) +{ + // Don't explode if its another rocket with the same owner. + /////////////////////////////////////////////////////////// + if((object->type == ObjectRocket) && (object->owner == rocket->owner)) + return; + + // We pass through explosions. + ////////////////////////////// + if(object->type == ObjectExplosion) + return; + + // Explode the object. + ////////////////////// + ExplodeObject(object); + + // Remove the rocket. + ///////////////////// + RemoveObject(rocket); +} + +static void RocketTouchClient +( + SObject * rocket, + Client * client +) +{ + // Check for death. + /////////////////// + if(client->dead) + return; + + // Check for self. + ////////////////// + if(rocket->owner == client->index) + return; + + // Kill the client. + /////////////////// + if(rocket->owner == -1) + ClientKilled(client, NULL); + else + ClientKilled(client, &clients[rocket->owner]); + + // Explode the rocket. + ////////////////////// + ExplodeObject(rocket); +} + +////////// +// MINE // +////////// + +static void MineThink +( + SObject * mine +) +{ + // Check for detonate. + ////////////////////// + if((Now - mine->startTime) > MINE_TIME) + { + // Explode the mine. + //////////////////// + ExplodeObject(mine); + } + else + { + // Rotate the mine. + /////////////////// + mine->rotation = ComputeNewRotation(mine->rotation, RIGHT, Diff, MINE_TURN_SPEED); + + // Check if a second has passed. + //////////////////////////////// + if((int)((Now - mine->startTime) / 1000) > mine->count) + { + // One more full second. + //////////////////////// + mine->count++; + + // Make a sound during the last 3 seconds. + ////////////////////////////////////////// + if(mine->count >= ((MINE_TIME / 1000) - 3)) + SendSound(SOUND_MINE, -1); + } + } +} + +static void MineTouchObject +( + SObject * mine, + SObject * object +) +{ + // Ignore explosions. + ///////////////////// + if(object->type == ObjectExplosion) + return; + + // Blow up the object. + ////////////////////// + ExplodeObject(object); + + // Remove the mine. + /////////////////// + RemoveObject(mine); +} + +static void MineTouchClient +( + SObject * mine, + Client * client +) +{ + // Check if its armed itself. + ///////////////////////////// + if((Now - mine->startTime) < MINE_ARM_TIME) + return; + + // Check for death. + /////////////////// + if(client->dead) + return; + + // Kill the client. + /////////////////// + if(mine->owner == -1) + ClientKilled(client, NULL); + else + ClientKilled(client, &clients[mine->owner]); + + // Explode the mine. + //////////////////// + ExplodeObject(mine); +} + +////////////// +// ASTEROID // +////////////// + +static void AsteroidThink +( + SObject * asteroid +) +{ + // Rotate the asteroid. + /////////////////////// + asteroid->rotation = ComputeNewRotation(asteroid->rotation, LEFT, Diff, ASTEROID_TURN_SPEED); + + // Move the asteroid. + ///////////////////// + ComputeNewPosition(asteroid->position, asteroid->position, FORWARD, asteroid->heading, Diff, asteroid->speed, GT2False); + + // If it goes past the edge, loop it around. + //////////////////////////////////////////// + if(asteroid->position[0] > MAP_MAX + (2*asteroid->radius)) + asteroid->position[0] = MAP_MIN - (2*asteroid->radius); + if(asteroid->position[0] < MAP_MIN - (2*asteroid->radius)) + asteroid->position[0] = MAP_MAX + (2*asteroid->radius); + + if(asteroid->position[1] > MAP_MAX + (2*asteroid->radius)) + asteroid->position[1] = MAP_MIN - (2*asteroid->radius); + if(asteroid->position[1] < MAP_MIN - (2*asteroid->radius)) + asteroid->position[1] = MAP_MAX + (2*asteroid->radius); + +} + +static void AsteroidTouchObject +( + SObject * asteroid, + SObject * object +) +{ + // Explode on asteroid impact. + ////////////////////////////// + if(object->type == ObjectAsteroid) + { + ExplodeObject(asteroid); + ExplodeObject(object); + } +} + +static void AsteroidTouchClient +( + SObject * asteroid, + Client * client +) +{ + // Check for death. + /////////////////// + if(client->dead) + return; + + // Kill the client. + /////////////////// + ClientKilled(client, NULL); + + // Explode the asteroid. + //////////////////////// + ExplodeObject(asteroid); +} + +static void SpawnAsteroids +( + void +) +{ + SObject * asteroid; + int i; + + NumAsteroids = 0; + for(i = 0 ; i < NUM_ASTEROIDS ; i++) + { + asteroid = AddObject(ObjectAsteroid); + if(asteroid) + InitAsteroid(asteroid); + } +} + +static void AsteroidDie +( + SObject * asteroid +) +{ + // One less asteroid. + ///////////////////// + NumAsteroids--; + + // If they're all gone, make new ones. + ////////////////////////////////////// + if(!NumAsteroids) + SpawnAsteroids(); +} + +static void InitAsteroid +( + SObject * asteroid +) +{ + asteroid->position[0] = RandomFloat(MAP_MIN, MAP_MAX, GT2True); + asteroid->position[1] = RandomFloat(MAP_MIN, MAP_MAX, GT2True); + asteroid->rotation = RandomFloat(0, 360, GT2False); + asteroid->think = AsteroidThink; + asteroid->touchObject = AsteroidTouchObject; + asteroid->touchClient = AsteroidTouchClient; + asteroid->die = AsteroidDie; + asteroid->radius = ASTEROID_RADIUS; + asteroid->speed = RandomFloat(ASTEROID_SPEED_MIN, ASTEROID_SPEED_MAX, GT2False); + asteroid->heading = asteroid->rotation; + + NumAsteroids++; +} + +/////////////// +// EXPLOSION // +/////////////// + +static void ExplosionThink +( + SObject * explosion +) +{ + // Check for done. + ////////////////// + if((Now - explosion->startTime) > EXPLOSION_TIME) + { + // Delete the explosion. + //////////////////////// + RemoveObject(explosion); + } +} + +static void ExplosionTouchObject +( + SObject * explosion, + SObject * object +) +{ +} + +static void ExplosionTouchClient +( + SObject * explosion, + Client * client +) +{ + // Check if we're still dangerous. + ////////////////////////////////// + if((Now - explosion->startTime) > EXPLOSION_DANGER_TIME) + return; + + // Check for death. + /////////////////// + if(client->dead) + return; + + // Kill the client. + /////////////////// + if(explosion->owner == -1) + ClientKilled(client, NULL); + else + ClientKilled(client, &clients[explosion->owner]); +} + +static SObject * AddExplosion +( + V2f position, + int owner +) +{ + SObject * explosion; + + // Add an explosion. + //////////////////// + explosion = AddObject(ObjectExplosion); + if(!explosion) + return NULL; + + // Setup the explosion. + /////////////////////// + explosion->owner = owner; + CopyV2f(explosion->position, position); + explosion->rotation = RandomFloat(0, 360, GT2False); + explosion->think = ExplosionThink; + explosion->touchObject = ExplosionTouchObject; + explosion->touchClient = ExplosionTouchClient; + explosion->radius = EXPLOSION_RADIUS; + + // Send a sound effect. + /////////////////////// + SendSound(SOUND_EXPLOSION, -1); + + return explosion; +} + +static void ExplodeObject +( + SObject * object +) +{ + // Don't explode explosions. + //////////////////////////// + if(object->type == ObjectExplosion) + return; + + // Mark this object for explosion. + ////////////////////////////////// + object->explode = GT2True; + + if(object->type != ObjectAsteroid) + { + // Mark for removal. + //////////////////// + RemoveObject(object); + } +} + +static void ClientThink +( + Client * client +) +{ + // Dead? + //////// + if(client->dead) + { + // Time to spawn? + ///////////////// + if(Now > client->spawnTime) + ClientSpawn(client); + } + else if(client->numAsteroids) + { + static V2f spinnerPosition = { MAP_HALF, MAP_HALF }; + float distance; + + // Check for the spinner. + ///////////////////////// + distance = DistanceV2f(spinnerPosition, client->position); + if(distance < (PLAYER_RADIUS + SPINNER_RADIUS)) + { + // Drop off our asteroids. + ////////////////////////// + client->score += client->numAsteroids; + client->numAsteroids = 0; + SendNumAsteroids(client->numAsteroids, client->index); + SendSound(SOUND_PICKUP, client->index); + } + } +} + +void ClientPress +( + int clientIndex, + const char * button +) +{ + SObject * object; + Client * client; + V2f forward; + + assert(clientIndex >= 0); + assert(clientIndex < MAX_CLIENTS); + assert(clients[clientIndex].used); + + // Set the Now time. + //////////////////// + Now = current_time(); + + client = &clients[clientIndex]; + + // Check the button. + //////////////////// + if(strcasecmp(button, "mine") == 0) + { + // Add a mine object. + ///////////////////// + object = AddObject(ObjectMine); + if(object) + { + // Set its properties. + ////////////////////// + object->owner = clientIndex; + CopyV2f(object->position, client->position); + object->rotation = client->rotation; + object->think = MineThink; + object->touchObject = MineTouchObject; + object->touchClient = MineTouchClient; + object->radius = MINE_RADIUS; + + // Move it so its behind the client. + ////////////////////////////////////// + RotationToVector(forward, object->rotation); + ScaleV2f(forward, forward, -500); + AddV2f(object->position, object->position, forward); + + // Play the mine sound. + /////////////////////// + SendSound(SOUND_MINE, -1); + } + } + else if(strcasecmp(button, "rocket") == 0) + { + // Add a rocket object. + /////////////////////// + object = AddObject(ObjectRocket); + if(object) + { + // Set its properties. + ////////////////////// + object->owner = clientIndex; + CopyV2f(object->position, client->position); + object->rotation = client->rotation; + object->think = RocketThink; + object->touchObject = RocketTouchObject; + object->touchClient = RocketTouchClient; + object->radius = ROCKET_RADIUS; + + // Move it so its ahead of the client. + ////////////////////////////////////// + RotationToVector(forward, object->rotation); + ScaleV2f(forward, forward, 300); + AddV2f(object->position, object->position, forward); + + // Play the rocket sound. + ///////////////////////// + SendSound(SOUND_ROCKET, -1); + } + } +} + +void ObjectThink +( + SObject * object +) +{ + int i; + SObject * other; + Client * client; + + // Think. + ///////// + object->think(object); + + // Check for client touches. + //////////////////////////// + if(object->touchClient) + { + float range; + + range = (object->radius + PLAYER_RADIUS); + + for(i = 0 ; i < MAX_CLIENTS ; i++) + { + client = &clients[i]; + + // Check for in use. + //////////////////// + if(!client->used) + continue; + + // Check for a touch. + ///////////////////// + if(DistanceV2f(object->position, client->position) <= range) + { + // Touched. + /////////// + object->touchClient(object, client); + } + } + } + + // Check for object touches. + //////////////////////////// + if(object->touchObject) + { + for(i = 0 ; i < MAX_OBJECTS ; i++) + { + other = &sObjects[i]; + + // Check for in use. + //////////////////// + if(!other->used) + continue; + + // Check for self. + ////////////////// + if(other == object) + continue; + + // Check for a touch. + ///////////////////// + if(DistanceV2f(object->position, other->position) <= (object->radius + other->radius)) + { + // Touched. + /////////// + object->touchObject(object, other); + } + } + } +} + +void ObjectsThink +( + unsigned long now, + unsigned long diff +) +{ + Client * client; + SObject * object; + int i; + + Now = now; + Diff = diff; + + // Go through the list of clients. + ////////////////////////////////// + for(i = 0 ; i < MAX_CLIENTS ; i++) + { + client = &clients[i]; + + // Is it a real client? + /////////////////////// + if(client->used) + { + // Think. + ///////// + ClientThink(client); + } + } + + // Think for all the objects. + ///////////////////////////// + for(i = 0 ; i < MAX_OBJECTS ; i++) + { + object = &sObjects[i]; + + // Is the object in use? + //////////////////////// + if(object->used) + { + // Think. + ///////// + ObjectThink(object); + } + } + + // Check for exploding or removed objects. + ////////////////////////////////////////// + for(i = 0 ; i < MAX_OBJECTS ; i++) + { + object = &sObjects[i]; + + // Is the object in use? + //////////////////////// + if(object->used) + { + // Explode the object? + ////////////////////// + if(object->explode) + { + // Create an explosion. + /////////////////////// + AddExplosion(object->position, object->owner); + + // It exploded. + /////////////// + object->explode = GT2False; + + + // If it is an asteroid, then move it to the edge + ///////////////////////////////////////////////// + if(object->type == ObjectAsteroid) + { + object->position[0] = RandomFloat(MAP_MIN, MAP_MAX, GT2True); + object->position[1] = MAP_MIN - object->radius; + } + + + } + + // Remove the object? + ///////////////////// + if(object->remove) + { + // Call its die function. + ///////////////////////// + if(object->die) + object->die(object); + + if(object->used) + { + // We're not using this object anymore. + /////////////////////////////////////// + object->used = GT2False; + + // One less object. + /////////////////// + numObjects--; + assert(numObjects >= 0); + } + } + } + } +} + +void ClientSpawn +( + Client * client +) +{ + SObject * asteroid; + + // Throw away his asteroids. + //////////////////////////// + for( ; client->numAsteroids ; client->numAsteroids--) + { + asteroid = AddObject(ObjectAsteroid); + if(asteroid) + { + InitAsteroid(asteroid); + asteroid->position[0] = client->position[0]; + asteroid->position[0] += RandomFloat(-5000, 5000, GT2True); + asteroid->position[1] = client->position[1]; + asteroid->position[1] += RandomFloat(-5000, 5000, GT2True); + ClampV2f(asteroid->position, asteroid->position, MAP_MIN, MAP_MAX); + } + } + SendNumAsteroids(client->numAsteroids, client->index); + + // Pick a random starting point. + //////////////////////////////// + client->position[0] = RandomFloat(MAP_MIN, MAP_MAX, GT2True); + client->position[1] = RandomFloat(MAP_MIN, MAP_MAX, GT2True); + + // Not dead. + //////////// + client->dead = GT2False; +} + +void InitializeLogic +( + void +) +{ + // Add a bunch of asteroids. + //////////////////////////// + SpawnAsteroids(); +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aLogic.h b/code/gamespy/gt2/gt2action/gt2aLogic.h new file mode 100644 index 00000000..b389d682 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aLogic.h @@ -0,0 +1,119 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2ALOGIC_H_ +#define _GT2ALOGIC_H_ + +#include "gt2aServer.h" + +#define PLAYER_SPEED 7000 // units/second +#define PLAYER_TURN_SPEED 0.14 // degrees/millisecond +#define PLAYER_RADIUS 200 +#define PLAYER_MAX_ASTEROIDS 50 +#define DEATH_TIME 1000 +#define PRESS_TIME 100 // time between presses + +#define MAX_OBJECTS 256 +#define ROCKET_RADIUS 250 +#define ROCKET_SPEED 10500 +#define MINE_TIME 30000 +#define MINE_ARM_TIME 1000 +#define MINE_TURN_SPEED 0.36 // degrees/millisecond +#define MINE_RADIUS 200//141 +#define NUM_ASTEROIDS 50 +#define ASTEROID_TURN_SPEED 0.18 +#define ASTEROID_TURN_RANGE 0.24 +#define ASTEROID_RADIUS 350 +#define ASTEROID_SPEED_MIN 0 +#define ASTEROID_SPEED_MAX 5000 +#define EXPLOSION_RADIUS 900 +#define EXPLOSION_TIME 350 +#define EXPLOSION_DANGER_TIME 250 +#define SPINNER_RADIUS 5000 +#define SPINNER_COORD (SPINNER_RADIUS * 1.414) + +typedef enum +{ + ObjectRocket, + ObjectMine, + ObjectAsteroid, + ObjectExplosion, + NumObjects +} ObjectType; + +typedef void (* Think) +( + struct SObject * self +); + +typedef void (* TouchObject) +( + struct SObject * self, + struct SObject * object +); + +typedef void (* Die) +( + struct SObject * self +); + +typedef void (* TouchClient) +( + struct SObject * self, + struct Client * client +); + +typedef struct SObject +{ + GT2Bool used; // If this slot is in use or not. + ObjectType type; // The type of object. + int owner; // The client that owns this object, or -1. + V2f position; // The object's position. + float rotation; // The object's rotation. + unsigned long startTime; // The time this object was created. + Think think; // This object's think function. + TouchObject touchObject; // Called when touching another object. + TouchClient touchClient; // Called when touching a client. + Die die; // Called when the object dies. + int count; // Generic counter. + float radius; // This object's radius (for collision). + GT2Bool explode; // Explode this object when done thinking. + GT2Bool remove; // Remove this object when done thinking. + + float heading; // The direction the object is moving + float speed; // The speed of the object +} SObject; + +extern SObject sObjects[MAX_OBJECTS]; +extern int numObjects; + +void ObjectsThink +( + unsigned long now, + unsigned long diff +); + +void ClientPress +( + int clientIndex, + const char * button +); + +void ClientSpawn +( + Client * client +); + +void InitializeLogic +( + void +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aMain.c b/code/gamespy/gt2/gt2action/gt2aMain.c new file mode 100644 index 00000000..9584ee61 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aMain.c @@ -0,0 +1,276 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include +#include +#include +#include "gt2aMain.h" +#include "gt2aDisplay.h" +#include "gt2aClient.h" +#include "gt2aServer.h" +#include "gt2aInput.h" +#include "gt2aSound.h" +#include "../../ghttp/ghttp.h" + +const V3b Red = { 255, 0, 0 }; +const V3b Green = { 0, 255, 0 }; +const V3b Blue = { 0, 0, 255 }; +const V3b Yellow = { 255, 255, 0 }; +const V3b Orange = { 255, 128, 0 }; +const V3b Purple = { 255, 0, 255 }; +const V3b Black = { 0, 0, 0 }; +const V3b White = { 255, 255, 255 }; +const V3b Grey = { 128, 128, 128 }; + +static GT2Bool host = GT2True; +static GT2Bool dedicated; + +void Log +( + const char * format, + ... +) +{ +#if 1 + FILE * fp; + va_list args; + + va_start(args, format); + + fp = fopen("gt2a.log", "at"); + if(fp) + { + fprintf(fp, "%08d: ", (current_time() % 100000000)); + vfprintf(fp, format, args); + fclose(fp); + } + + va_end(args); +#endif +} + +static void Idle +( + void +) +{ + msleep(1); + glutPostRedisplay(); +} + +static void Timer +( + int value +) +{ + unsigned long now; + + // Reset the timer. + /////////////////// + glutTimerFunc(10, Timer, 0); + + // Get the current time. + //////////////////////// + now = current_time(); + + // Let the subsystems think. + //////////////////////////// + if(host) + ServerThink(now); + if(!dedicated) + ClientThink(now); + DisplayThink(now); +} + +static GHTTPBool NaminatorCallback +( + GHTTPRequest request, + GHTTPResult result, + char * buffer, + GHTTPByteCount bufferLen, + void * param +) +{ + if(result == GHTTPSuccess) + { + char * str; + + // Set the nick. + //////////////// + strncpy(localNick, buffer, MAX_NICK); + localNick[MAX_NICK - 1] = '\0'; + + // Cap off the newline. + /////////////////////// + str = strchr(localNick, '\n'); + if(str) + *str = '\0'; + + // Strip out stuff that'll mess up our + // key\value message passing. + ////////////////////////////////////// + while(str = strchr(localNick, '\\')) + *str = '/'; + + printf("%s\n", localNick); + } + else + { + printf("Error\n"); + } + + return GHTTPTrue; +} + +static void ParseArgs +( + int argc, + char ** argv +) +{ + GT2Bool gotNick = GT2False; + int i; + + for(i = 1 ; i < argc ; i++) + { + // Connect. + /////////// + if(strncasecmp(argv[i], "-c", 2) == 0) + { + if(++i < argc) + { + // Get the address to connect to. + ///////////////////////////////// + strcpy(serverAddress, argv[i]); + if(!strchr(serverAddress, ':')) + strcat(serverAddress, PORT_STRING); + host = GT2False; + } + } + // Fullscreen. + ////////////// + else if(strcasecmp(argv[i], "-full") == 0) + { + fullScreen = GT2True; + } + // Nick. + //////// + else if((strcasecmp(argv[i], "-nick") == 0) || + (strcasecmp(argv[i], "-name") == 0)) + { + if(++i < argc) + { + char * str; + + // Set the nick. + //////////////// + strncpy(localNick, argv[i], MAX_NICK); + localNick[MAX_NICK - 1] = '\0'; + + // Strip out stuff that'll mess up our + // key\value message passing. + ////////////////////////////////////// + while(str = strchr(localNick, '\\')) + *str = '/'; + + // We got a nick. + ///////////////// + gotNick = GT2True; + } + } + // Dedicated server. + //////////////////// + else if(strcasecmp(argv[i], "-dedicated") == 0) + { + dedicated = GT2True; + } + // Width. + ///////// + else if(strcasecmp(argv[i], "-width") == 0) + { + if(++i < argc) + screenWidth = atoi(argv[i]); + } + // Height. + ////////// + else if(strcasecmp(argv[i], "-height") == 0) + { + if(++i < argc) + screenHeight = atoi(argv[i]); + } + } + + // If we didn't get a nick, get a naminator nick. + ///////////////////////////////////////////////// + if(!gotNick) + { + printf("Getting naminator name..."); + ghttpGetFile("http://www.planetquake.com/excessive/name.asp", GHTTPTrue, NaminatorCallback, NULL); + } +} + +static GT2Bool Initialize +( + void +) +{ + // Init the display. + //////////////////// + InitializeDisplay(); + + // Init input handling. + /////////////////////// + InitializeInput(); + + // Init sound. + ////////////// + InitializeSound(); + + // Start the server if we're hosting. + ///////////////////////////////////// + if(host) + { + if(!InitializeServer()) + { + printf("Failed to initialize server!\n"); + return GT2False; + } + } + + // Start the client if not dedicated. + ///////////////////////////////////// + if(!dedicated) + { + if(!InitializeClient()) + { + printf("Failed to initialize client!\n"); + return GT2False; + } + } + + return GT2True; +} + +int main +( + int argc, + char ** argv +) +{ + srand(time(NULL)); + glutInit(&argc, argv); + ParseArgs(argc, argv); + glutTimerFunc(10, Timer, 0); + glutIdleFunc(Idle); + if(!Initialize()) + return 1; + glutMainLoop(); + return 0; +} diff --git a/code/gamespy/gt2/gt2action/gt2aMain.h b/code/gamespy/gt2/gt2action/gt2aMain.h new file mode 100644 index 00000000..555928a2 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aMain.h @@ -0,0 +1,85 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2AMAIN_H_ +#define _GT2AMAIN_H_ + +#include +#include +#include "../../nonport.h" +#include "../gt2.h" +#include "../gt2Encode.h" + +#define PORT_STRING ":12345" + +#define MAX_PLAYERS 64 + +#define BACKWARD -1 +#define STILL 0 +#define FORWARD 1 + +#define LEFT -1 +#define RIGHT 1 + +#define MAP_MIN 0 +#define MAP_MAX 100000.0 +#define MAP_HALF (MAP_MAX / 2) +#define MAP_EXTRA (MAP_MAX / 10) + +#define MAX_NICK 32 + +#define CHAT_MAX 64 + +#define MSG_C_INITIAL 1 +#define MSG_C_INITIAL_STR "s" +#define MSG_C_UPDATE 2 +#define MSG_C_UPDATE_STR "ppzzzz" +#define MSG_C_PRESS 3 +#define MSG_C_PRESS_STR "s" +#define MSG_C_CHAT 4 +#define MSG_C_CHAT_STR "s" +#define MSG_S_ADDCLIENT 1001 +#define MSG_S_ADDCLIENT_STR "bs" +#define MSG_S_DELCLIENT 1002 +#define MSG_S_DELCLIENT_STR "b" +#define MSG_S_START 1003 +#define MSG_S_START_STR "b" +#define MSG_S_UPDATE 1004 +#define MSG_S_UPDATE_STR "pbb" +#define MSG_S_UPDATE_CLIENT_STR "bpppizzzzz" +#define MSG_S_UPDATE_OBJECT_STR "bpppi" +#define MSG_S_CHAT 1005 +#define MSG_S_CHAT_STR "s" +#define MSG_S_SOUND 1006 +#define MSG_S_SOUND_STR "b" +#define MSG_S_NUMASTEROIDS 1007 +#define MSG_S_NUMASTEROIDS_STR "b" + +typedef unsigned char byte; +typedef float V2f[2]; +typedef byte V3b[3]; + +extern const V3b Red; +extern const V3b Green; +extern const V3b Blue; +extern const V3b Yellow; +extern const V3b Orange; +extern const V3b Purple; +extern const V3b Black; +extern const V3b White; +extern const V3b Grey; + +void Log +( + const char * format, + ... +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aMath.c b/code/gamespy/gt2/gt2action/gt2aMath.c new file mode 100644 index 00000000..0ea17969 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aMath.c @@ -0,0 +1,364 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include +#include +#include +#include "gt2aMain.h" +#include "gt2aMath.h" + +#define PI 3.1415926535897932384626433832795 +#define PI_DIV_180 (PI / 180) + +byte * CopyV3b +( + V3b dest, + const V3b src +) +{ + dest[0] = src[0]; + dest[1] = src[1]; + dest[2] = src[2]; + + return dest; +} + +byte * SetV3b +( + V3b dest, + byte src0, + byte src1, + byte src2 +) +{ + dest[0] = src0; + dest[1] = src1; + dest[2] = src2; + + return dest; +} + +float * CopyV2f +( + V2f dest, + const V2f src +) +{ + dest[0] = src[0]; + dest[1] = src[1]; + + return dest; +} + +float * SetV2f +( + V2f dest, + float src0, + float src1 +) +{ + dest[0] = src0; + dest[1] = src1; + + return dest; +} + +float * ScaleV2f +( + V2f dest, + const V2f src, + float scale +) +{ + dest[0] = (src[0] * scale); + dest[1] = (src[1] * scale); + + return dest; +} + +float * AddV2f +( + V2f dest, + const V2f src0, + const V2f src1 +) +{ + dest[0] = (src0[0] + src1[0]); + dest[1] = (src0[1] + src1[1]); + + return dest; +} + +float * SubV2f +( + V2f dest, + const V2f src0, + const V2f src1 +) +{ + dest[0] = (src0[0] - src1[0]); + dest[1] = (src0[1] - src1[1]); + + return dest; +} + +float * SubScalarV2f +( + V2f dest, + const V2f src, + float scalar +) +{ + dest[0] = (src[0] - scalar); + dest[1] = (src[1] - scalar); + + return dest; +} + +float LenV2f +( + const V2f src +) +{ + return (float)sqrt((src[0] * src[0]) + (src[1] * src[1])); +} + +float DistanceV2f +( + const V2f src0, + const V2f src1 +) +{ + V2f temp; + + SubV2f(temp, src0, src1); + + return LenV2f(temp); +} + +float * ClampV2f +( + V2f dest, + const V2f src, + float min, + float max +) +{ + float temp; + int i; + + for(i = 0 ; i < 2 ; i++) + { + temp = src[i]; + if(temp > max) + dest[i] = max; + else if(temp < min) + dest[i] = min; + else + dest[i] = temp; + } + + return dest; +} + +float ClampRotation +( + float rotation +) +{ + while(rotation >= 360) + rotation -= 360; + while(rotation < 0) + rotation += 360; + return rotation; +} + +int ClampInt +( + int num, + int min, + int max +) +{ + if(num < min) + num = min; + else if(num > max) + num = max; + return num; +} + +float * RotationToVector +( + V2f dest, + float rotation +) +{ + // Convert to radians. + ////////////////////// + rotation = (float)(rotation * PI_DIV_180); + + dest[0] = (float)sin(rotation); + dest[1] = (float)cos(rotation); + + return dest; +} + +float * ComputeNewPosition +( + V2f dest, + const V2f position, + int motion, + float rotation, + unsigned long diff, + int unitsPerSec, + GT2Bool clamp +) +{ + V2f forward; + float scale; + + // Check for no motion. + /////////////////////// + if(!motion) + { + CopyV2f(dest, position); + return dest; + } + + // Get the direction we're facing. + ////////////////////////////////// + RotationToVector(forward, rotation); + + // Scale it based on the amount of time. + //////////////////////////////////////// + scale = (float)(diff * unitsPerSec / 1000); + if(motion == BACKWARD) + scale = -scale; + ScaleV2f(forward, forward, scale); + + // Update the position. + /////////////////////// + AddV2f(dest, position, forward); + + // Clamp it. + //////////// + if(clamp) + ClampV2f(dest, dest, 0, MAP_MAX); + + return dest; +} + +float ComputeNewRotation +( + float rotation, + int turning, + unsigned long diff, + float degreesPerMilliSec +) +{ + // Turn. + //////// + rotation += (turning * (diff * degreesPerMilliSec)); + + // Clamp it to between 0 and 360. + ///////////////////////////////// + while(rotation >= 360) + rotation -= 360; + while(rotation < 0) + rotation += 360; + + return rotation; +} + +float RandomFloat +( + float minFloat, + float maxFloat, + GT2Bool maxInclusive +) +{ + float num; + + num = rand(); + num *= (maxFloat - minFloat); + num /= (RAND_MAX + (maxInclusive?0:1)); + num += minFloat; + + return num; +} + +int RandomInt +( + int minInt, + int maxInt +) +{ + return (minInt + ((rand() * (maxInt - minInt + 1)) / (RAND_MAX + 1))); +} + +unsigned short RotationToUnsignedShort +( + float rotation +) +{ + rotation *= 0xFFFF; + rotation /= 360; + if(rotation < 0) + rotation = 0; + if(rotation > 0xFFFF) + rotation = 0xFFFF; + + return (unsigned short)rotation; +} + +float UnsignedShortToRotation +( + unsigned short packedRotation +) +{ + float rotation; + + rotation = packedRotation; + rotation *= 360; + rotation /= 0xFFFF; + + return ClampRotation(rotation); +} + +unsigned short PositionToUnsignedShort +( + float position +) +{ + position += (MAP_EXTRA / 2); + position *= 0xFFFF; + position /= (MAP_MAX + MAP_EXTRA); + if(position < 0) + position = 0; + else if(position > USHRT_MAX) + position = USHRT_MAX; + + return (unsigned short)position; +} + +float UnsignedShortToPosition +( + unsigned short packedPosition +) +{ + float position; + + position = packedPosition; + position *= (MAP_MAX + MAP_EXTRA); + position /= 0xFFFF; + position -= (MAP_EXTRA / 2); + + return position; +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aMath.h b/code/gamespy/gt2/gt2action/gt2aMath.h new file mode 100644 index 00000000..04e0a3fb --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aMath.h @@ -0,0 +1,161 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2AMATH_H_ +#define _GT2AMATH_H_ + +#include +#include "gt2aMain.h" + +byte * CopyV3b +( + V3b dest, + const V3b src +); + +byte * SetV3b +( + V3b dest, + byte src0, + byte src1, + byte src2 +); + +float * CopyV2f +( + V2f dest, + const V2f src +); + +float * SetV2f +( + V2f dest, + float src0, + float src1 +); + +float * ScaleV2f +( + V2f dest, + const V2f src, + float scale +); + +float * AddV2f +( + V2f dest, + const V2f src0, + const V2f src1 +); + +float * SubV2f +( + V2f dest, + const V2f src0, + const V2f src1 +); + +float * SubScalarV2f +( + V2f dest, + const V2f src, + float scalar +); + +float LenV2f +( + const V2f src +); + +float DistanceV2f +( + const V2f src0, + const V2f src1 +); + +float * ClampV2f +( + V2f dest, + const V2f src, + float min, + float max +); + +float ClampRotation +( + float rotation +); + +int ClampInt +( + int num, + int min, + int max +); + +float * RotationToVector +( + V2f dest, + float rotation +); + +float * ComputeNewPosition +( + V2f dest, + const V2f position, + int motion, + float rotation, + unsigned long diff, + int unitsPerMilliSec, + GT2Bool clamp +); + +float ComputeNewRotation +( + float rotation, + int turning, + unsigned long diff, + float degreesPerMilliSec +); + +float RandomFloat +( + float minFloat, + float maxFloat, + GT2Bool maxInclusive +); + +int RandomInt +( + int minInt, + int maxInt +); + +unsigned short RotationToUnsignedShort +( + float rotation +); + +float UnsignedShortToRotation +( + unsigned short packedRotation +); + +unsigned short PositionToUnsignedShort +( + float position +); + +float UnsignedShortToPosition +( + unsigned short packedPosition +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aParse.c b/code/gamespy/gt2/gt2action/gt2aParse.c new file mode 100644 index 00000000..472ade22 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aParse.c @@ -0,0 +1,79 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include +#include "gt2aParse.h" + +static char buffer[256]; + +static char * StripKeyValue +( + char * input +) +{ + int i; + int c; + + // Get the key. + /////////////// + for(i = 0 ; i < (sizeof(buffer) - 1) ; i++) + { + c = *input++; + if((c == '\\') || (c == '\0')) + break; + buffer[i] = (char)c; + } + buffer[i] = '\0'; + + return buffer; +} + +char * ParseKeyValue +( + char * input, + const char * key +) +{ + char * str; + int len; + + if(!input) + return NULL; + + // Are we looking for the first key? + //////////////////////////////////// + if(!key) + { + // Does it start with a '\'? + //////////////////////////// + if(*input++ != '\\') + return NULL; + + // Get the key. + /////////////// + return StripKeyValue(input); + } + + // Find the key. + //////////////// + buffer[0] = '\\'; + len = strlen(key); + memcpy(buffer + 1, key, len); + buffer[++len] = '\\'; + buffer[++len] = '\0'; + str = strstr(input, buffer); + if(!str) + return NULL; + str += strlen(buffer); + + // Get the key. + /////////////// + return StripKeyValue(str); +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aParse.h b/code/gamespy/gt2/gt2action/gt2aParse.h new file mode 100644 index 00000000..9ae3b12a --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aParse.h @@ -0,0 +1,26 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2APARSE_H_ +#define _GT2APARSE_H_ + +// Search the input for "\key\". +// Returns the value following the key, +// or NULL if the key wasn't found. +// If key is NULL, returns the first key +// in input, or NULL if there's an error. +///////////////////////////////////////// +char * ParseKeyValue +( + char * input, + const char * key +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aServer.c b/code/gamespy/gt2/gt2action/gt2aServer.c new file mode 100644 index 00000000..e0b2eff8 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aServer.c @@ -0,0 +1,746 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include "gt2aMain.h" +#include "gt2aServer.h" +#include "gt2aParse.h" +#include "gt2aMath.h" +#include "gt2aSound.h" +#include "gt2aLogic.h" + +#define SERVER_THINK_TIME 30 + +static GT2Socket Socket; +Client clients[MAX_CLIENTS]; +static int numClients; +static GT2ConnectionCallbacks connectionCallbacks; +static unsigned short nextServerUpdateID; + +static void Broadcast +( + const char * message, + int len, + int exceptIndex, + GT2Bool reliable +) +{ + int i; + + for(i = 0 ; i < MAX_CLIENTS ; i++) + if(clients[i].used && (i != exceptIndex)) + gt2Send(clients[i].connection, (const GT2Byte *)message, len, reliable); +} + +void BroadcastText +( + const char * message, + int exceptIndex, + GT2Bool reliable +) +{ + static char messageCopy[CHAT_MAX]; + static char buffer[256]; + char * slash; + int rcode; + + // Copy off the chat message. + ///////////////////////////// + strncpy(messageCopy, message, CHAT_MAX); + messageCopy[CHAT_MAX - 1] = '\0'; + + // Because of the way gt2a is sending messages + // we need to change \ to /. + ////////////////////////////////////////////// + while(slash = strchr(messageCopy, '\\')) + *slash = '/'; + + // Encode the message. + ////////////////////// + rcode = gtEncode(MSG_S_CHAT, MSG_S_CHAT_STR, buffer, sizeof(buffer), + messageCopy); + if(rcode != -1) + { + // Send this chat message out to everyone. + ////////////////////////////////////////// + Broadcast(buffer, rcode, -1, reliable); + } +} + +static int AddClient +( + void +) +{ + int i; + + // Find an open slot. + ///////////////////// + for(i = 0 ; i < MAX_CLIENTS ; i++) + if(!clients[i].used) + break; + + // Nothing open? + //////////////// + if(i == MAX_CLIENTS) + return -1; + + // Clear it. + //////////// + memset(&clients[i], 0, sizeof(Client)); + clients[i].index = i; + clients[i].used = GT2True; + + // One more client. + /////////////////// + numClients++; + assert(numClients <= MAX_CLIENTS); + + return i; +} + +static void RemoveClient +( + int clientIndex +) +{ + // Not in use anymore. + ////////////////////// + clients[clientIndex].used = GT2False; + + // One less client. + /////////////////// + numClients--; + assert(numClients >= 0); +} + +static void SendAddClient +( + int sendClientIndex, + int newClientIndex +) +{ + char buffer[256]; + Client * sendClient; + Client * newClient; + int rcode; + + assert((sendClientIndex >= 0) && (sendClientIndex < MAX_CLIENTS)); + sendClient = &clients[sendClientIndex]; + assert(sendClient->used); + assert(sendClient->connection); + + assert((newClientIndex >= 0) && (newClientIndex < MAX_CLIENTS)); + newClient = &clients[newClientIndex]; + assert(newClient->used); + assert(newClient->connection); + + // Encode the message. + ///////////////////// + rcode = gtEncode(MSG_S_ADDCLIENT, MSG_S_ADDCLIENT_STR, buffer, sizeof(buffer), + newClientIndex, + newClient->nick); + if(rcode != -1) + { + // Send the message. + //////////////////// + gt2Send(sendClient->connection, (const GT2Byte *)buffer, rcode, GT2True); + } +} + +static void SendDelClient +( + int sendClient, + int delClient +) +{ + char buffer[32]; + int rcode; + + assert((sendClient >= 0) && (sendClient < MAX_CLIENTS)); + assert(clients[sendClient].used); + assert(clients[sendClient].connection); + assert((delClient >= 0) && (delClient < MAX_CLIENTS)); + + // Encode the message. + ///////////////////// + rcode = gtEncode(MSG_S_DELCLIENT, MSG_S_DELCLIENT_STR, buffer, sizeof(buffer), + delClient); + if(rcode != -1) + { + // Send the message. + //////////////////// + gt2Send(clients[sendClient].connection, (const GT2Byte *)buffer, rcode, GT2True); + } +} + +static void SendStart +( + int sendClient +) +{ + char buffer[32]; + int rcode; + + assert((sendClient >= 0) && (sendClient < MAX_CLIENTS)); + assert(clients[sendClient].used); + assert(clients[sendClient].connection); + + // Encode the message. + ///////////////////// + rcode = gtEncode(MSG_S_START, MSG_S_START_STR, buffer, sizeof(buffer), + sendClient); + if(rcode != -1) + { + // Send the message. + //////////////////// + gt2Send(clients[sendClient].connection, (const GT2Byte *)buffer, rcode, GT2True); + } +} + +void SendSound +( + int sound, + int sendClient +) +{ + char buffer[32]; + int rcode; + + assert(sendClient >= -1); + assert(sendClient < MAX_CLIENTS); +#ifdef _DEBUG + if(sendClient != -1) + { + assert(clients[sendClient].used); + assert(clients[sendClient].connection); + } +#endif + + // Encode the message. + ///////////////////// + rcode = gtEncode(MSG_S_SOUND, MSG_S_SOUND_STR, buffer, sizeof(buffer), + sound); + if(rcode != -1) + { + // Send the message. + //////////////////// + if(sendClient == -1) + Broadcast(buffer, rcode, -1, GT2False); + else + gt2Send(clients[sendClient].connection, (const GT2Byte *)buffer, rcode, GT2False); + } +} + +void SendNumAsteroids +( + int total, + int sendClient +) +{ + char buffer[32]; + int rcode; + + assert(sendClient >= -1); + assert(sendClient < MAX_CLIENTS); + assert(clients[sendClient].used); + assert(clients[sendClient].connection); + assert(total >= 0); + assert(total <= PLAYER_MAX_ASTEROIDS); + + // Encode the message. + ///////////////////// + rcode = gtEncode(MSG_S_NUMASTEROIDS, MSG_S_NUMASTEROIDS_STR, buffer, sizeof(buffer), + (byte)total);; + if(rcode != -1) + { + // Send the message. + //////////////////// + gt2Send(clients[sendClient].connection, (const GT2Byte *)buffer, rcode, GT2True); + } +} + +static void SendUpdates +( + void +) +{ + int i; + char * buffer; + int len; + static char update[1024 * 8]; + Client * client; + SObject * object; + unsigned long now; + int rcode; + + // Get the current time. + //////////////////////// + now = current_time(); + + // Setup the encoding parameters. + ///////////////////////////////// + buffer = update; + len = sizeof(update); + + // First encode the header. + /////////////////////////// + rcode = gtEncode(MSG_S_UPDATE, MSG_S_UPDATE_STR, buffer, len, + nextServerUpdateID++, + numClients, + numObjects); + if(rcode == -1) + return; + buffer += rcode; + len -= rcode; + + // Encode each client. + ////////////////////// + for(i = 0 ; i < MAX_CLIENTS ; i++) + { + if(clients[i].used) + { + client = &clients[i]; + + rcode = gtEncodeNoType(MSG_S_UPDATE_CLIENT_STR, buffer, len, + i, + PositionToUnsignedShort(client->position[0]), + PositionToUnsignedShort(client->position[1]), + RotationToUnsignedShort(client->rotation), + client->score, + client->motion == FORWARD ? 1 : 0, + client->motion == BACKWARD ? 1 : 0, + client->turning == RIGHT ? 1 : 0, + client->turning == LEFT ? 1 : 0, + client->dead); + if(rcode == -1) + return; + buffer += rcode; + len -= rcode; + } + } + + // Encode each object. + ////////////////////// + for(i = 0 ; i < MAX_OBJECTS ; i++) + { + if(sObjects[i].used) + { + object = &sObjects[i]; + + rcode = gtEncodeNoType(MSG_S_UPDATE_OBJECT_STR, buffer, len, + object->type, + PositionToUnsignedShort(object->position[0]), + PositionToUnsignedShort(object->position[1]), + RotationToUnsignedShort(object->rotation), + (now - object->startTime)); + if(rcode == -1) + return; + buffer += rcode; + len -= rcode; + } + } + + // Send the update. + /////////////////// + Broadcast(update, sizeof(update) - len, -1, GT2False); +} + +static void ServerReceivedCallback +( + GT2Connection connection, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + int clientIndex; + Client * client; + GTMessageType type; + int rcode; + + // Check for no message. + //////////////////////// + if(!message) + return; + + // Which client was this? + ///////////////////////// + clientIndex = (int)gt2GetConnectionData(connection); + assert((clientIndex >= 0) && (clientIndex < MAX_CLIENTS)); + if((clientIndex < 0) || (clientIndex >= MAX_CLIENTS)) + return; + assert(clients[clientIndex].used); + if(!clients[clientIndex].used) + return; + client = &clients[clientIndex]; + + // Get the message type. + //////////////////////// + type = gtEncodedMessageType((char *)message); + + // Update? + ////////// + if(type == MSG_C_UPDATE) + { + unsigned short updateID; + unsigned long now; + byte forward; + byte backward; + byte right; + byte left; + unsigned short packedRotation; + + // Get the time we received it. + /////////////////////////////// + now = current_time(); + + // Decode it. + ///////////// + rcode = gtDecode(MSG_C_UPDATE_STR, (char *)message, len, + &updateID, + &packedRotation, + &forward, + &backward, + &right, + &left); + if(rcode == -1) + return; + + // Set the rotation. + //////////////////// + client->rotation = UnsignedShortToRotation(packedRotation); + + // Interpret the motion & turning bits. + /////////////////////////////////////// + if(forward) + client->motion = FORWARD; + else if(backward) + client->motion = BACKWARD; + else + client->motion = STILL; + if(right) + client->turning = RIGHT; + else if(left) + client->turning = LEFT; + else + client->turning = STILL; + + // Check if we should move the client. + ////////////////////////////////////// + if(client->lastUpdate && !client->dead && client->motion) + { + unsigned long diff; + + // Get how long since the last update. + ////////////////////////////////////// + diff = (now - client->lastUpdate); + + // Compute the new position. + //////////////////////////// + ComputeNewPosition( + client->position, + client->position, + client->motion, + client->rotation, + diff, + PLAYER_SPEED, + GT2True); + } + + // Set the last update time. + //////////////////////////// + client->lastUpdate = now; + } + // Press? + ///////// + else if(type == MSG_C_PRESS) + { + unsigned long now; + char press[64]; + + // A dead client can't fire. + //////////////////////////// + if(client->dead) + return; + + // Get the current time. + //////////////////////// + now = current_time(); + + // Is this too soon? + //////////////////// + if((now - client->lastPress) < PRESS_TIME) + return; + + // Decode it. + ///////////// + rcode = gtDecode(MSG_C_PRESS_STR, (char *)message, len, + press); + if(rcode == -1) + return; + + // The last press. + ////////////////// + client->lastPress = now; + + // Do the press. + //////////////// + ClientPress(clientIndex, press); + } + // Chat? + //////// + else if(type == MSG_C_CHAT) + { + char buffer[MAX_NICK + CHAT_MAX + 16]; + + // Start forming the outgoing chat message. + /////////////////////////////////////////// + sprintf(buffer, "%s: ", client->nick); + + // Decode the incoming message. + /////////////////////////////// + rcode = gtDecode(MSG_C_CHAT_STR, (char *)message, len, + buffer + strlen(buffer)); + if(rcode == -1) + return; + + // Send this chat message out to everyone. + ////////////////////////////////////////// + BroadcastText(buffer, -1, GT2True); + } +} + +static void ServerClosedCallback +( + GT2Connection connection, + GT2CloseReason reason +) +{ + int clientIndex; + int i; + SObject * object; + + // Which client was this? + ///////////////////////// + clientIndex = (int)gt2GetConnectionData(connection); + assert((clientIndex >= 0) && (clientIndex < MAX_CLIENTS)); + if((clientIndex < 0) || (clientIndex >= MAX_CLIENTS)) + return; + assert(clients[clientIndex].used); + if(!clients[clientIndex].used) + return; + + // Remove this client. + ////////////////////// + RemoveClient(clientIndex); + + // Send the remove client message. + ////////////////////////////////// + for(i = 0 ; i < MAX_CLIENTS ; i++) + if(clients[i].used) + SendDelClient(i, clientIndex); + + // Mark all owned objects as not owned. + /////////////////////////////////////// + for(i = 0 ; i < MAX_OBJECTS ; i++) + { + object = &sObjects[i]; + if(object->used && (object->owner == clientIndex)) + object->owner = -1; + } +} + +static void ServerConnectAttemptCallback +( + GT2Socket socket, + GT2Connection connection, + unsigned int ip, + unsigned short port, + int latency, + GT2Byte * message, + int len +) +{ + int newClientIndex; + Client * newClient; + int i; + char nick[MAX_NICK]; + int rcode; + + // Make sure we got a message. + ////////////////////////////// + if(!message) + { + gt2Reject(connection, (const GT2Byte *)"No initial data received", -1); + return; + } + + // Check the message type. + ////////////////////////// + if(gtEncodedMessageType((char *)message) != MSG_C_INITIAL) + { + gt2Reject(connection, (const GT2Byte *)"Bad message type", -1); + return; + } + + // Decode the data. + /////////////////// + rcode = gtDecode(MSG_C_INITIAL_STR, (char *)message, len, + nick); + if(rcode == -1) + { + gt2Reject(connection, (const GT2Byte *)"Bad message data", -1); + return; + } + + // Add the client to the list. + ////////////////////////////// + newClientIndex = AddClient(); + if(newClientIndex == -1) + { + gt2Reject(connection, (const GT2Byte *)"Server full", -1); + return; + } + newClient = &clients[newClientIndex]; + + // Accept the connection. + ///////////////////////// + if(!gt2Accept(connection, &connectionCallbacks)) + { + // Remove the client. + ///////////////////// + RemoveClient(newClientIndex); + return; + } + + // Set the connection's data to its index. + ////////////////////////////////////////// + gt2SetConnectionData(connection, (void *)newClientIndex); + + // Set some properties for the new client. + ////////////////////////////////////////// + newClient->connection = connection; + strncpy(newClient->nick, nick, MAX_NICK); + newClient->nick[MAX_NICK - 1] = '\0'; + + // Spawn the client. + //////////////////// + ClientSpawn(newClient); + + // Send new client message for the clients already connected. + // Also send new client messages for new client to other clients. + ///////////////////////////////////////////////////////////////// + for(i = 0 ; i < MAX_CLIENTS ; i++) + { + if(clients[i].used) + { + SendAddClient(i, newClientIndex); + if(i != newClientIndex) + SendAddClient(newClientIndex, i); + } + } + + // It has no asteroids. + /////////////////////// + SendNumAsteroids(0, newClientIndex); + + // Tell the new client it can start. + //////////////////////////////////// + SendStart(newClientIndex); +} + +static void ServerSocketErrorCallback +( + GT2Socket socket +) +{ + printf("Server socket error\n"); +} + +GT2Bool InitializeServer +( + void +) +{ + GT2Result result; + int i; + + // Setup callback structs. + ////////////////////////// + memset(&connectionCallbacks, 0, sizeof(GT2ConnectionCallbacks)); + connectionCallbacks.received = ServerReceivedCallback; + connectionCallbacks.closed = ServerClosedCallback; + + // Set client indices. + ////////////////////// + for(i = 0 ; i < MAX_CLIENTS ; i++) + clients[i].index = i; + + // Create the socket. + ///////////////////// + result = gt2CreateSocket(&Socket, PORT_STRING, 0, 0, ServerSocketErrorCallback); + if(result != GT2Success) + return GT2False; + + // Listen. + ////////// + gt2Listen(Socket, ServerConnectAttemptCallback); + + // Initialize the logic. + //////////////////////// + InitializeLogic(); + + return GT2True; +} + +void ServerThink +( + unsigned long now +) +{ + static GT2Bool firstTime = GT2True; + static unsigned long last; + unsigned long diff; + + // Think. + ///////// + gt2Think(Socket); + + // The first time through just store the time. + // This is so diff is accurate the second time through. + /////////////////////////////////////////////////////// + if(firstTime) + { + firstTime = GT2False; + last = now; + return; + } + + // How long since the last update? + ////////////////////////////////// + diff = (now - last); + + // Check for an update. + /////////////////////// + if(diff < SERVER_THINK_TIME) + return; + + // New last update. + /////////////////// + last = now; + + // Process objects. + /////////////////// + ObjectsThink(now, diff); + + // Send updates. + //////////////// + SendUpdates(); +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aServer.h b/code/gamespy/gt2/gt2action/gt2aServer.h new file mode 100644 index 00000000..d02b7a0c --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aServer.h @@ -0,0 +1,65 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2ASERVER_H_ +#define _GT2ASERVER_H_ + +#define MAX_CLIENTS MAX_PLAYERS + +typedef struct Client +{ + GT2Bool used; // If this slot is in use or not. + int index; // This client's clientIndex. + GT2Connection connection; // The GT2Connection for this client. + V2f position; // The current position (0 <= x,y < MAP_MAX). + float rotation; // Client's view angle (0 <= rotation < 360). + int motion; // Client's current motion (STILL, FORWARD, BACKWARD). + int turning; // Client's current turning direction (STILL, LEFT, RIGHT). + unsigned long lastUpdate; // Last update received from this client. + char nick[MAX_NICK]; // The client's nick. + int score; // The client's score. + GT2Bool dead; // True if this client was killed and not respawned. + unsigned long spawnTime; // If dead, spawn at this time. + unsigned long lastPress; // When the last press event was received. + int numAsteroids; // The number of asteroids being held by this player. +} Client; + +extern Client clients[MAX_CLIENTS]; + +GT2Bool InitializeServer +( + void +); + +void ServerThink +( + unsigned long now +); + +void SendSound +( + int sound, + int sendClient +); + +void SendNumAsteroids +( + int total, + int sendClient +); + +void BroadcastText +( + const char * message, + int exceptIndex, + GT2Bool reliable +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aSound.c b/code/gamespy/gt2/gt2action/gt2aSound.c new file mode 100644 index 00000000..8a68fbce --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aSound.c @@ -0,0 +1,543 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include +#include +#include "gt2aMain.h" +#include "gt2aSound.h" +#include "gt2aMath.h" +#include "../../darray.h" + +#define MAX_DUPLICATES 8 + +static GT2Bool playSounds; +static LPDIRECTSOUND DirectSound; +static DArray SoundList; + +typedef struct SoundEffect +{ + LPDIRECTSOUNDBUFFER SoundBuffers[MAX_DUPLICATES]; + int numDuplicates; +} SoundEffect; + +// The order of this list must match up with the order +// of the SOUND_* enums in gt2aSound.h +////////////////////////////////////////////////////// +const char * soundFiles[] = +{ + "sounds\\explosion.wav", + "sounds\\mine.wav", + "sounds\\die.wav", + "sounds\\rocket.wav", + "sounds\\pickup.wav", + NULL +}; + +// Adapted from a Microsoft DirectSound sample. +/////////////////////////////////////////////// +static HRESULT ReadMMIO +( + HMMIO hmmioIn, + MMCKINFO * pckInRIFF, + WAVEFORMATEX ** ppwfxInfo +) +{ + MMCKINFO ckIn; // chunk info. for general use. + PCMWAVEFORMAT pcmWaveFormat; // Temp PCM structure to load in. + + *ppwfxInfo = NULL; + + if( ( 0 != mmioDescend( hmmioIn, pckInRIFF, NULL, 0 ) ) ) + return E_FAIL; + + if( (pckInRIFF->ckid != FOURCC_RIFF) || + (pckInRIFF->fccType != mmioFOURCC('W', 'A', 'V', 'E') ) ) + return E_FAIL; + + // Search the input file for for the 'fmt ' chunk. + ckIn.ckid = mmioFOURCC('f', 'm', 't', ' '); + if( 0 != mmioDescend(hmmioIn, &ckIn, pckInRIFF, MMIO_FINDCHUNK) ) + return E_FAIL; + + // Expect the 'fmt' chunk to be at least as large as ; + // if there are extra parameters at the end, we'll ignore them + if( ckIn.cksize < (LONG) sizeof(PCMWAVEFORMAT) ) + return E_FAIL; + + // Read the 'fmt ' chunk into . + if( mmioRead( hmmioIn, (HPSTR) &pcmWaveFormat, + sizeof(pcmWaveFormat)) != sizeof(pcmWaveFormat) ) + return E_FAIL; + + // Allocate the waveformatex, but if its not pcm format, read the next + // word, and thats how many extra bytes to allocate. + if( pcmWaveFormat.wf.wFormatTag == WAVE_FORMAT_PCM ) + { + if( NULL == ( *ppwfxInfo = (WAVEFORMATEX *)malloc(sizeof(WAVEFORMATEX)))) + return E_FAIL; + + // Copy the bytes from the pcm structure to the waveformatex structure + memcpy( *ppwfxInfo, &pcmWaveFormat, sizeof(pcmWaveFormat) ); + (*ppwfxInfo)->cbSize = 0; + } + else + { + // Read in length of extra bytes. + WORD cbExtraBytes = 0L; + if( mmioRead( hmmioIn, (CHAR*)&cbExtraBytes, sizeof(WORD)) != sizeof(WORD) ) + return E_FAIL; + + *ppwfxInfo = (WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX) + cbExtraBytes); + if( NULL == *ppwfxInfo ) + return E_FAIL; + + // Copy the bytes from the pcm structure to the waveformatex structure + memcpy( *ppwfxInfo, &pcmWaveFormat, sizeof(pcmWaveFormat) ); + (*ppwfxInfo)->cbSize = cbExtraBytes; + + // Now, read those extra bytes into the structure, if cbExtraAlloc != 0. + if( mmioRead( hmmioIn, (CHAR*)(((BYTE*)&((*ppwfxInfo)->cbSize))+sizeof(WORD)), + cbExtraBytes ) != cbExtraBytes ) + { + free(*ppwfxInfo); + *ppwfxInfo = NULL; + return E_FAIL; + } + } + + // Ascend the input file out of the 'fmt ' chunk. + if( 0 != mmioAscend( hmmioIn, &ckIn, 0 ) ) + { + free(*ppwfxInfo); + *ppwfxInfo = NULL; + return E_FAIL; + } + + return S_OK; +} + +// Adapted from a Microsoft DirectSound sample. +/////////////////////////////////////////////// +static HRESULT WaveOpenFile +( + CHAR * strFileName, + HMMIO * phmmioIn, + WAVEFORMATEX ** ppwfxInfo, + MMCKINFO * pckInRIFF +) +{ + HRESULT hr; + HMMIO hmmioIn = NULL; + + if( NULL == ( hmmioIn = mmioOpen( strFileName, NULL, MMIO_ALLOCBUF|MMIO_READ ) ) ) + return E_FAIL; + + if( FAILED( hr = ReadMMIO( hmmioIn, pckInRIFF, ppwfxInfo ) ) ) + { + mmioClose( hmmioIn, 0 ); + return hr; + } + + *phmmioIn = hmmioIn; + + return S_OK; +} + +// Adapted from a Microsoft DirectSound sample. +/////////////////////////////////////////////// +static HRESULT WaveStartDataRead +( + HMMIO * phmmioIn, + MMCKINFO * pckIn, + MMCKINFO * pckInRIFF +) +{ + // Seek to the data + if( -1 == mmioSeek( *phmmioIn, pckInRIFF->dwDataOffset + sizeof(FOURCC), + SEEK_SET ) ) + return E_FAIL; + + // Search the input file for for the 'data' chunk. + pckIn->ckid = mmioFOURCC('d', 'a', 't', 'a'); + if( 0 != mmioDescend( *phmmioIn, pckIn, pckInRIFF, MMIO_FINDCHUNK ) ) + return E_FAIL; + + return S_OK; +} + +// Adapted from a Microsoft DirectSound sample. +/////////////////////////////////////////////// +static HRESULT WaveReadFile +( + HMMIO hmmioIn, + UINT cbRead, + BYTE * pbDest, + MMCKINFO * pckIn, + UINT * cbActualRead +) +{ + MMIOINFO mmioinfoIn; // current status of + UINT cbDataIn; + DWORD cT; + + *cbActualRead = 0; + + if( 0 != mmioGetInfo( hmmioIn, &mmioinfoIn, 0 ) ) + return E_FAIL; + + cbDataIn = cbRead; + if( cbDataIn > pckIn->cksize ) + cbDataIn = pckIn->cksize; + + pckIn->cksize -= cbDataIn; + + for(cT = 0; cT < cbDataIn; cT++ ) + { + // Copy the bytes from the io to the buffer. + if( mmioinfoIn.pchNext == mmioinfoIn.pchEndRead ) + { + if( 0 != mmioAdvance( hmmioIn, &mmioinfoIn, MMIO_READ ) ) + return E_FAIL; + + if( mmioinfoIn.pchNext == mmioinfoIn.pchEndRead ) + return E_FAIL; + } + + // Actual copy. + *((BYTE*)pbDest+cT) = *((BYTE*)mmioinfoIn.pchNext); + mmioinfoIn.pchNext++; + } + + if( 0 != mmioSetInfo( hmmioIn, &mmioinfoIn, 0 ) ) + return E_FAIL; + + *cbActualRead = cbDataIn; + return S_OK; +} + +// If restoreBuffer is not NULL, restore it! +//////////////////////////////////////////// +static GT2Bool LoadSound +( + int sound, + const char * filename, + LPDIRECTSOUNDBUFFER restoreBuffer +) +{ + int rcode; + HRESULT result; + LPWAVEFORMATEX waveFormat; + HMMIO IOHandle; + MMCKINFO dataChunkInfo; + MMCKINFO parentChunkInfo; + DSBUFFERDESC bufferDescriptor; + SoundEffect soundEffect; + LPVOID lockedBuffer; + DWORD lockedBufferLen; + UINT numBytes; + LPDIRECTSOUNDBUFFER soundBuffer; + + // Open the file. + ///////////////// + rcode = WaveOpenFile((char *)filename, &IOHandle, &waveFormat, &parentChunkInfo); + if(rcode) + return GT2False; + + // Prepare to read the data chunk. + ////////////////////////////////// + rcode = WaveStartDataRead(&IOHandle, &dataChunkInfo, &parentChunkInfo); + if(rcode) + return GT2False; + + // If we're not restoring, create the buffer. + ///////////////////////////////////////////// + if(!restoreBuffer) + { + // Clear our sound object. + ////////////////////////// + memset(&soundEffect, 0, sizeof(SoundEffect)); + soundEffect.numDuplicates = 1; + + // Describe the desired buffer. + /////////////////////////////// + memset(&bufferDescriptor, 0, sizeof(DSBUFFERDESC)); + bufferDescriptor.dwSize = sizeof(DSBUFFERDESC); + bufferDescriptor.dwFlags = DSBCAPS_STATIC; + bufferDescriptor.dwBufferBytes = dataChunkInfo.cksize; + bufferDescriptor.lpwfxFormat = waveFormat; + + // Create the buffer. + ///////////////////// + result = IDirectSound_CreateSoundBuffer( + DirectSound, + &bufferDescriptor, + &soundEffect.SoundBuffers[0], + NULL); + if(FAILED(result)) + { + mmioClose(IOHandle, 0); + return GT2False; + } + + // Set the sound buffer pointer. + //////////////////////////////// + soundBuffer = soundEffect.SoundBuffers[0]; + } + else + { + // Set the sound buffer pointer. + //////////////////////////////// + soundBuffer = restoreBuffer; + } + + // Lock the buffer. + /////////////////// + result = IDirectSoundBuffer_Lock( + soundBuffer, + 0, + 0, + &lockedBuffer, + &lockedBufferLen, + NULL, + NULL, + DSBLOCK_ENTIREBUFFER); + if(FAILED(result)) + { + mmioClose(IOHandle, 0); + return GT2False; + } + + // Read the data into the buffer. + ///////////////////////////////// + rcode = WaveReadFile( + IOHandle, + lockedBufferLen, + (BYTE *)lockedBuffer, + &dataChunkInfo, + &numBytes); + if(rcode) + { + mmioClose(IOHandle, 0); + return GT2False; + } + + // Unlock the buffer. + ///////////////////// + IDirectSoundBuffer_Unlock( + soundBuffer, + lockedBuffer, + lockedBufferLen, + NULL, + 0); + + // Close the file. + ////////////////// + mmioClose(IOHandle, 0); + + // Add this sound to the list if its new. + ///////////////////////////////////////// + if(!restoreBuffer) + ArrayAppend(SoundList, &soundEffect); + + return GT2True; +} + +GT2Bool InitializeSound +( + void +) +{ + HRESULT result; + HWND hWnd; + int i; + + // Init the sound list. + /////////////////////// + SoundList = ArrayNew(sizeof(SoundEffect), 0, NULL); + if(!SoundList) + return GT2False; + + // Init direct sound. + ///////////////////// + result = DirectSoundCreate(NULL, &DirectSound, NULL); + if(FAILED(result)) + return GT2False; + + // Get the window handle. + ///////////////////////// + hWnd = GetForegroundWindow(); + if(!hWnd) + hWnd = GetDesktopWindow(); + + // Set the cooperative level. + ///////////////////////////// + result = IDirectSound_SetCooperativeLevel(DirectSound, hWnd, DSSCL_PRIORITY); + if(FAILED(result)) + return GT2False; + + // Load the sounds. + /////////////////// + for(i = 0 ; i < NUM_SOUNDS ; i++) + { + assert(soundFiles[i]); + if(!soundFiles[i]) + return GT2False; + LoadSound(i, soundFiles[i], NULL); + } + assert(!soundFiles[i]); + + // Turn on sound. + ///////////////// + playSounds = GT2True; + + return GT2True; +} + +void CleanupSound +( + void +) +{ + // Cleanup the sound list. + ////////////////////////// + ArrayFree(SoundList); + + // Cleanup DirectSound. + /////////////////////// + if(DirectSound) + { + IDirectSound_Release(DirectSound); + DirectSound = NULL; + } + + // COM crap. + //////////// + CoUninitialize(); +} + +void ToggleSound +( + void +) +{ + playSounds = !playSounds; +} + +void PlaySoundEffect +( + int sound +) +{ + SoundEffect * soundEffect; + HRESULT result; + DWORD status; + int i; + + // Make sure we're playing sounds. + ////////////////////////////////// + if(!playSounds) + return; + + // Get the correct sound. + ///////////////////////// + soundEffect = (SoundEffect *)ArrayNth(SoundList, sound); + assert(soundEffect); + if(!soundEffect) + return; + + // Find a free buffer. + ////////////////////// + for(i = 0 ; i < soundEffect->numDuplicates ; i++) + { + // Get the current status. + ////////////////////////// + result = IDirectSoundBuffer_GetStatus(soundEffect->SoundBuffers[i], &status); + if(FAILED(result)) + return; + + // Did it lose its buffer? + ////////////////////////// + if(status & DSBSTATUS_BUFFERLOST) + { + // Restore the buffer. + ////////////////////// + result = IDirectSoundBuffer_Restore(soundEffect->SoundBuffers[i]); + if(FAILED(result)) + continue; + + // Fill the sound data back in. + /////////////////////////////// + if(!LoadSound(sound, soundFiles[sound], soundEffect->SoundBuffers[i])) + continue; + } + + // Is it free? + ////////////// + if(!(status & DSBSTATUS_PLAYING)) + break; + } + + // Did we not find anything? + //////////////////////////// + if(i == soundEffect->numDuplicates) + { + // Are we at max duplicates? + //////////////////////////// + if(i == MAX_DUPLICATES) + { + // Just use a random buffer. + //////////////////////////// + i = RandomInt(0, MAX_DUPLICATES - 1); + } + else + { + // Make a new duplicate. + //////////////////////// + result = IDirectSound_DuplicateSoundBuffer( + DirectSound, + soundEffect->SoundBuffers[0], + &soundEffect->SoundBuffers[i]); + if(FAILED(result)) + return; + + // One more duplicate. + ////////////////////// + soundEffect->numDuplicates++; + } + } + + // Make sure the buffer is rewound. + /////////////////////////////////// + IDirectSoundBuffer_SetCurrentPosition(soundEffect->SoundBuffers[i], 0); + + // Play it. + /////////// + result = IDirectSoundBuffer_Play(soundEffect->SoundBuffers[i], 0, 0, 0); + + // Check for a lost buffer. + /////////////////////////// + if(result == DSERR_BUFFERLOST) + { + // Restore the buffer. + ////////////////////// + result = IDirectSoundBuffer_Restore(soundEffect->SoundBuffers[i]); + if(FAILED(result)) + return; + + // Fill the sound data back in. + /////////////////////////////// + if(!LoadSound(sound, soundFiles[sound], soundEffect->SoundBuffers[i])) + return; + + // Try playing again. + ///////////////////// + IDirectSoundBuffer_Play(soundEffect->SoundBuffers[i], 0, 0, 0); + } +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aSound.h b/code/gamespy/gt2/gt2action/gt2aSound.h new file mode 100644 index 00000000..f5d3ca6c --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aSound.h @@ -0,0 +1,44 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2ASOUND_H_ +#define _GT2ASOUND_H_ + +enum +{ + SOUND_EXPLOSION, + SOUND_MINE, + SOUND_DIE, + SOUND_ROCKET, + SOUND_PICKUP, + NUM_SOUNDS +}; + +GT2Bool InitializeSound +( + void +); + +void CleanupSound +( + void +); + +void ToggleSound +( + void +); + +void PlaySoundEffect +( + int sound +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/images/asteroid0.tga b/code/gamespy/gt2/gt2action/images/asteroid0.tga new file mode 100644 index 0000000000000000000000000000000000000000..4bb42e95e58b5506fc236318182951026e3c045b GIT binary patch literal 65580 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 IyLN8#Z!X;qo&W#< literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/images/asteroid2.tga b/code/gamespy/gt2/gt2action/images/asteroid2.tga new file mode 100644 index 0000000000000000000000000000000000000000..a3164341de56df41a61cfcc6347460d9f6fc0cf9 GIT binary patch literal 65580 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 literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/images/explosion0.tga b/code/gamespy/gt2/gt2action/images/explosion0.tga new file mode 100644 index 0000000000000000000000000000000000000000..a47afbb690ab7ec6663c13f8701d2e1be35e5710 GIT binary patch literal 262188 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%0A;TC7o|zlCXi4E668$Je z-*Frc<2AqN#OCKCx%3!UuH5H3+8$+X#vT#+mn-4g$Yrd79>FQl1x|9wh(Eek_W!Nu z3`i!t3;p+VHxT`ftxLbcbjXK>rIp2?*9m=0wTB zN!Pq=%V^AZKre41S@cu!f5h?lFeyzrwKV!3H)N~N-SES(_jV@Z_6F=?-Uff~edv6- zg}dAH;omt4oz5ry;7X@qtK$OrzfXmSe`j>UE|0DGp&z)8N!XJ+6&l|yUt?z;eUHch zHf@7VnMEFV<)P?}9`}~(*Z-AFeyohnzEX51{bPUj0KY^Z-=?=Eh2{E(YD?;u(R2#* zzwEo1+4wg8WV&yp2S@jsp=j3tOYW=A=Te_@g%4{3bA?yKx9|FX<OnemG9S6eoMD_tAomr-_|ON7o2`_Ex(25!7v~wnf0Jf3zA+D$%klVD5<|> zcLjAj4w4rupf5Fo)~b5};aR$e#5-DGUnP6qCCw6?BMb0hM8PP|1Cu7Dh27teQ|VFMah9kqf!j@)nzAL|eK8)fFM)8&=SLN9|ENt*9M}C__ zEZ%noqo0-8SYCG5r#lbPck8Xp{H#+P`yn2zaPn}^%cHp;a!$~KUYfG*$}>K|g`>1( z%Bi`r=l@XJ`R!|Hr(2-u-_IWYx18s%LDmbJKr?oq%NKs@@)vyK znoj?iwI6!UxTk6JH(l#OpCAVUKk&ua<(|&^e>nU>6VU0?a;*zWvytz5*0nc1h-}vl zuEw2l6Y}RR#e-c_VY`1O!OCNUU^sO}X9ayy`o^^GXpBofx6Zq!{XO_#{pSO+{k{xH zO8<8KPP@K_8cu!O`FL&g3$OOGc2d~ztvLTtnuQHcVZU634NmcXKk(uFcQnFT**?7Q zZ+wTJCcp5Y^H9bYOi!TJ;E8y~z4lW%+|14sSZCMBBfbFE3+z8-w6tscX`1zv99;M6na1Uy1KCEl-$U^ z>n_IsL!9X!M4u1mf%tO#GuRWr3m8sEN9U=W&yeNIt@KaM^T=(K|IHmAcIEbb-;LOV z^BcD0qZ;(RNb3T;p_Tj5J8?Vhd^LN+3!(X(;=0TG!}qtz#ZB?z;0U`|*b+f{8CoW^ z3+#<$y6@<3q65fwBr6tUU;K>FrMbrx^(tX+syl5BW8HUIn-~rae{v1i=K44_yjuPR z!5Ey%)6dHA%lG4ac;Bz`{jkdN%aGqsx9>4Hh3#i$3Eup2GkPev(Hd8&MAMrDJ%<$|J&Pf6nA5 zGxleoTlf;>Ip7t*Zh9+rmrAsE2|LVD`l2hf{Em6y8t6UOx&*td0s0*x^a&JWp5#jI z_bx-n7`ogydkfugAGjhqV{_+nhddo#@(Va?&iKIfoBlPjX!O;k|L%&-=!nQ|d2uA$ zEZhT_t_9)F@Tc$e@o4yI#Fd3DuizTXFc?eo%Tk(t1|!$}jc}IVU>4-+{RV4w(L1b; z^?QE7@MsK(w~O&3{!ht%i{_Wlw!;4fMx-Q&ic{HYs#J$yN{ z;kQCxXDjxc;@`rv|JYY9#%4$Uab$XL$KK8r@cNvE9>@#f1!R4%oC4p*Zrlg1`ab;O z@N>t=agY3iYhCsau49G2p|cYn@#6KI?`P64X2UZ!n{)q6*~jE0+nULPDGmY$Kf|Tr z%Up|>+Zd;?;aTY#TNbu7#p`EbOSf_@taLq!H#&myS$?@zMq^$J%T;-PSbi(F9?v@% zyBXgv-(KhcWekHe^Yhl8GU|1zkNtXFRwva}cPQKqf>2!1skCQcPQaBuDD59<3FwBb zC_EMVl4MKa!lgdwP%f_2FDh*Hd+7{~;r&kwGtd)qAUs@qqeo14fEQy+=t0KzgX{~g z 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 bHr;r$)i+;jleIQjf9#fPthMgyi+KMJ5+-~w literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/images/explosion1.tga b/code/gamespy/gt2/gt2action/images/explosion1.tga new file mode 100644 index 0000000000000000000000000000000000000000..f857cd2e342964943649580f61155b057ede00a4 GIT binary patch literal 262188 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 literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/images/mine0.tga b/code/gamespy/gt2/gt2action/images/mine0.tga new file mode 100644 index 0000000000000000000000000000000000000000..62f0fc078cec3d82d0c41de02c8c6fec531b609e GIT binary patch literal 65580 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 literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/images/mine2.tga b/code/gamespy/gt2/gt2action/images/mine2.tga new file mode 100644 index 0000000000000000000000000000000000000000..3469a023fd08f13618aa8b696c60aa9d91225d01 GIT binary patch literal 65580 zcmeI&$!kwv25A0Sh{p+G&VNI#EBE5qM{;y2(M#Xy9Qmmc8%V>d&lI-lVj7SO|fRp zn%KN~bDTJFBKGau7u&aQkKMa>$I+ulc(rl=g!!$VMA=$vZZw&HtyfQ zABPVgP9EC&HERp!H*DCjm_B`a%3boBTqo~|H8J0@V@G<8e5V%7pFclVu3VX1@IHD2 zJ%QJjFJGRXQwMI|ycr7?EXZFXdqlBag8>5u1aYTM)8Faq>({T3ZQHiR#*G_O{I_o1 z8f(|CP5a69b?epz`^bN6@ESFL_3G8h31bEJ^FGX+I&~@@KYncdi^Tt&Idd8Y4<4Ly zeZqtZF@F5`m^*iFiuJBtyHYJbc<^9~CB6RQ#fz!8XFYz;o;|77ljr0=ap(6HD^{eO z#|D0mA3vVP2AFy9;6cz=ip_s1{XJj5el4r4tc-yJ2d3IRW5$e>`{XM%ot`~!-n`@@ z%YFI)J)W47%hU#9juVU!XJSw9=NK0*Tu5UT}C&ZSgIYoSvpK=ThYw@as8RV#;uxS1*MRe4-HNUh3bOuh;eDJzK9{y`rkBD%E{@KNhI@f^Q&Ur_52YT5LY;q)9zB}dwQCpMx^+uyb=B3? zQD0vl4Gj&kaN)w%v07HY$yxH3xmz|5B=3oJHt#3C{LV)F=@HZb>ISbd=Eu_AyLVIW zQws`p{-yrCojP@T+p}lS=+mc9F#cnX&iIY`p5^J8GiOq)$X9wev8Ntn@#c~79j{@7 ze8&Ty&&GiqlUhODc=YH|aLjx6?xj9JuPF666zcr*Yq)Q_ckdpZJ9iGofYf^OlleR2 zG-6K9vtD=S&YhIE*;t?aC+_4nYrE&qpHGi`mR`X7ve$WJKksX4X-V_O>({TRN5-0k zI{#At-hA%XckI|Px^(H1#%qk#nU4`m@|ph37>x}F#F5;l&y&j-A(z>Bok#YO>-eCy zvybBvdyYdd!2rkSGo>DbLZ1KU&!2zE$NsN={raVPPF!BRcoEN@JxdO5-MW?5=P<#1 zoqA4OsqI)`A2DPdh`92cHC_4xy`Nrz0dk+uavWZN{rYt>Hf72b<6q!;Z+>mQzOJq= z#fF+pUuI2?zD_TA_3Bk?|0m|;F)__@ntePc?};~N_$|eXK79BP@87>S_C*%^%mRIz__1C??$Zm%*?-prV$J;=?hA3Bck$xI zsTS}#>N_gi?-htQo`>ykkaNW3f!+J4&f&UZv5&mZX#_R&C{;?ufK@AW$l&16!NI}7p+m+Fet>=e zKZYOedH;*A9tRO8!Fw<8xFk)6-~0dHBXY`6sw#5P)WKRr z4P+Nf%IA-SF+D4HagdLxoA#p4K^r^gA=Vr~Z`Qo1Uflk0R=E+!;CAf&0d;L4PYAn3 zF*j#@!Sow`zCIRy*7qlV)IPLF;QA!ZqS3#NA6`ICe0K1I9rwhdKN!Ii>d)s{cQ;xe z@U^k^lMQ=i`N2^O5rWvsl$Te&j$JKlGp3b#Hh;qW_^kascg- z18MrR|G5W9<7fXve~I;v^>5Z5b3keO^ZI`{|HT|2`t`r;jnWj~eet(n@6_p3HkS5W zX~((S58s=_a-0e0hmQCT1T}y_4Z!zD#n1)zW6`>e1M8h&uj|^4%#RVjwcQufdG-KC za7$u`j_d*WKq5bKkR&VeBPM_Uh6NsA|4XU+FfZu;QrW{?=y4q1da919qk2F+pdR=i Z9^iiixVgTa+)d}x^6L2he0n)KF`ucJp>F^H literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/images/rocket1.tga b/code/gamespy/gt2/gt2action/images/rocket1.tga new file mode 100644 index 0000000000000000000000000000000000000000..0c3841ac9af4a2799a5539ea56bc9ade7c1b5976 GIT binary patch literal 16428 zcmeI4u}T9$5QYaE1&b6GDJ&GOusGD>iWC+J1eFjmVAKdHQdn4|u(S|tQYzTlSO_*+ zY8|oH&L{8%e1~!V)rrSJ#7cB$4u;*k+;M#K|2y379U+<^-D*^$`6*^556^*nz&+p| za1Xc#e$N9$LngYGl>gp6|I)pPo`ipBlAq{a*hTmd59V?}bSvzypZvBwKy)YEZ+vyd zd~yO0yhuEn@c_}8@ILd!1&17b)PXY-{ONj^v-Sa_O;%c?n9b@uoyB!K-535 zTjK}2)<1aO8h_sZR{7Qc7Wv)(U(Y|TzrFhB{_nE&`e%{9wSUa@&LLOjodEc=dMB*; zSb6^G^amq;woDvC&{UN`+~TXxZ%mkoc3U?VlI3?j&D6(N7vCk;2v-f{0|T4zX5D-?X2!L+Ktxc;@(b#7Hh&AVEPvNkMsKiqt3% zkR~1C0qC3}d4v?<324$qlleCpv#bP4bvky&(rEYWv(DfD|GW0xo=o(N^sz~s^n96_ z$;&nH40r}S1D*lTKyw)&`p!f@MDpg2*`{L=eHH$1Ci#hOgk6mPS8ej8iO&M=qhG&6 zq2I(Y+j5M*bO zbhFmrA$HXvR}J8|dfuO%h5q`^EVS4LcVO?W@ro35LdgR&W<3|>tNz4v@utS4f_`wLC)PH?meGjeVK=;js9~@v!`N4@= z@C$pltg@Q>Vh>Y(Y-6tLRJ||;zz#j)_vF-f{`=p5?!Vpq!~58OjD1VvKj!bf?gw_u z{HlSK`9JmFB0tUn@%;~WYy4o>`45@5#vkY3D!=C6BERSV?)Q)TZ*Ttd{7>2X{AZDW zba>8#$tm|`O#uAWU=ptRSo!@kIjI-?(7+l${NIu5-pGK({KJ1}0PmrJHUBmLItN(e z*ZjkOi{l?_Ug|y00j>G3>%ZywFU|q7zy4Q#_r9P$Nqy#}rJ2-J`Xgz_p`X*5 zPs~yp=Z6-!4+LXC*S?Ya7yb26=*N2H*vIuwu-A1RTIENJ*K%Y|nKT1v!EK2hT4)B4 z1B?97pwl_wxYCT|zKslM{;iDr)KgvmNT2uZJnnmQAKgdKfM>un;2CIZ2K3(m_ICHT S-Veva(Yu!i+rzh8ujmiXIIl_o literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/images/rocket3.tga b/code/gamespy/gt2/gt2action/images/rocket3.tga new file mode 100644 index 0000000000000000000000000000000000000000..65350bbb2ba1a154bf4ced57893c78e1779edde2 GIT binary patch literal 16428 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 literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/images/ship1.tga b/code/gamespy/gt2/gt2action/images/ship1.tga new file mode 100644 index 0000000000000000000000000000000000000000..7581e35633abe6873392f940bd271dbdbb7c3d15 GIT binary patch literal 262188 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 z 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�S z%z)lzz?-+1=jw^6n-R8J_e7qf&a*$yj*ahsf|o<0U@)vic;suyw^!rzgW&2iKrB`s6E&o2&v0l_e;|M5#I>hQy* z%bKOnIP=62PagTKqsRT1ZI)w}H*0{`>Tle>6Krp29WQpaAnl+P(Uy&PDJT?Xa{?UI zP3RsN)hvC+nJ12T^2nEK{xV12JV$!och2s&Klas@HlCWvDycwxVlskOhr_fs4rNXk z`YvD7EPckACysdXWcFqD^{ju{cKG}`F>R@IogOc!ZbZuNOoW8J3Zp3w<<2kBKQg6R z`iwJA9P#AI`pNu@2K21~Ub{ch+R^K1KDCfh=)l_8O^Aw4fW4|2J;P&~rO!C?#1Svo z{Po>;nOo`Kq6W@)_1DD}2y*zoQ)ykd%1pZnFIclfGi=`+qeal}i{_1$ynpY(5012cE;KU+}V z(7SbCF1BpXfYFkKqVjqSj!tTpKI6<2NBkmJtmsHIpnnbY4PMz_@x}Su)@nDBw(f=P zSThDjrZr2SapsAWe(S&A(nIOtgBqBd`}gVYp|Q3zeSa+MJ6wh%RV}zUbX~Lb8E2lj z2YK?l=twlMG&FGa`i+G4o`KQa@&?#y+-UC{(ky+(nU`KKjb2M1rH>EOz~JwH9h1w zdif{~%+1d~slLPe8QMqTBqur&4TuIr14~Z>KL7tDzO^|f$+F4v#)hDGUbn1|S?%*5 D{{Zid literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/images/space.tga b/code/gamespy/gt2/gt2action/images/space.tga new file mode 100644 index 0000000000000000000000000000000000000000..9ae59d86578431aff273d5f04ae5ae9a42cb4c4e GIT binary patch literal 262188 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

mngd#&>SIv*GQhl$g>72eSR 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 literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/images/spinner0.tga b/code/gamespy/gt2/gt2action/images/spinner0.tga new file mode 100644 index 0000000000000000000000000000000000000000..50018bed0b25dd24197d8c89e1bc380d7aec1f03 GIT binary patch literal 65580 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 St!p=GP2HujcKxbp|MlN?t7sko literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/images/spinner1.tga b/code/gamespy/gt2/gt2action/images/spinner1.tga new file mode 100644 index 0000000000000000000000000000000000000000..424da03a9e6eed90ee1131671beae44f8d4f4166 GIT binary patch literal 262188 zcmeF(=d-2PecgGG5+zcmvLyjjI5%_-=!6Cuu@kx*-N-qk5s3@}3;+lQkN`zelqiX^ zEXkB@S+ZSad&>5BUUtqK{wn|Np;_UG%Y~PyUzx@?S1mu&BX80}Bl-G_cUXLIVp8EHtptz(NBH4J$ zoG)~+sKG)59}Nx6`nP`nnfQ1;`s_HzqVFG#zAyUp)7Phdo9kB^;Nj8dKbv!WVjcRN zch}kny52%-KYdzT^!&ro^Ll;WSl{w+{aoMwT&~Td_xpVJj?Oi2opB!+j~5#GaA;)F zho8(otmQ@5$M1f<%-i++&*yk8JmcBEx7Kffwbozg?I%-ji@tvFeeL&kE$@xq=igb+ z`@ivyPYwLSaO}GK-5dIQV57eS8s9=IAG}r;J^0D#!CHRzov-Kn8@*l~{m;*%_jw+F zZ=m}?pYzW6TcGoHopD?%zJ&&Vax}Q;-`3px9)753(b6RnpxETC!_YWo|@-7*ZOz5k^g;e5fFz@dG zbM`kt*P0j0Y`o4FT3pm%p@Da%fmuI|o*VtxyU)jb&)59T^S>N^WjZ>B>*F5a z9q-#e%s0>bU2lx1$79TkzQ41+FY5NwUANIgJ+8lu_1`%5_dehETi)+H&;RNSu5~Zq zfA8?V_ebx~`oC*7jxjFBjkn`M%Rk*(Ui5$S{jYb7qrd9CaU6f|_j})O{jcSHzBi7z z#`~W0+rWF^`|;oX%w5JcdpyS0<1u!}i~cWau+YH9H8AU+`l_Dm$d~=S&$s-`%f98j z&F{ZH2iN<%pZ|UL`!~P&`kTML@7Q%aHm-4;jeX(&jrV=wkwvXW|I}CYTYcD(Px+pA zdA@(!^Y?Fr-<;v^=iGiU`oI6~=kNV`zy9yo^%{qwUlJ`J4BDYvAG>k9z_C`+eZO{`T+R`{N!k`d=K@zL579J>PuK7d2bd zj0V>7e*JD7z1i;e7vK6^?T*BWE!e$@SGjC-Nw zg_ai@cxM{mqk5_y>o>RG;d*lPJ`eLUPxC&HUlM+M%I{=^{lIn4_cX{6Bi1-+wn4e{YKO?g91) zdqDpkp#QC~|IOe0UCTJ`0o@0>SB-nXqW7Ec{i0?+-8CD%-*4-z|3@!6=2M>a+uy(6 z`}Xs*-}t`!d_NgjnjY8xo<;v|=)VDs{-3u8^zQ)ieH8rPy=mdGpKgzRSo>dn)=TyO z_}%aK*T|DRI{M$YpO=0A`J4ZJ^CyAti);1;e*<{F-+q7p`!~eSYmL%m1|X!L%+vwT+%*8klD`t9dqe&%bx@%el?tPD(YT<`ns zKYD-sZ~x;S&~?Y({*7_g_wsnZble(UXtef)?qBHM`xW0`Hiuj6Z`wEwbsWtuJ2f_X0L7Je4KOM@%x?AV*dAEHy@Q6kNT^2>AL>s z!_oWoKmYQ+@40XOQsDWmQyjzJ0>1n8zVCj0-+iRt{eJK3eKBD4S>ye2)VFIH$J#g+ z(?;lf^Tu)h+2bEaev36e-21?+CT7pyi~e=a>T@IB@4xl)K7X%-Z8NOqd^zyGe-re# zzCEM=#_w;3`oH^2{omjFVi8wkG`<%(uQx~6>AJ0rv7YbVwAR??=8fZgJ?>gY*BZlm zu`cfacr@|jsPm|S^^V`mc&a&CrRVM zT>D0QywUN!=wH{W-e{JNd9Z%uP2Xw1+5NrG&%Dp$e9!Z{!=5S5-CGXzzi+>N!~S6& z@PGG!S^tZnerb&FHs8+Eu{j&N)>`ANms_Wf;+;qDkMGZ}F&jspoqeux8{cg$7Vm#> zHJsPS@mi1R!g2M{>mA>V9**#Io>u>kR zcxI11-}vS8ZtAgk_s3tqYH+<#k3G{~kK_36yY1n)#(LiOZ+1NFf7i1d>ivB=#{L0& z!M3n9tc3ms82@|U?C$`-FZh4-&U>l%)~&TPcl}lGcI|N-V;Zk-$*84OH-+eyy-S+$4 z?|%Q=ot3aN`1bStfpBoZ?*RM5YFG~a_dotOUzzteK=n@Z^?!4IH~DrBbsfE5AM+wl z&NAVK0u_tFPO;o_nUpYmJ?! zt zG;e)z_{NC~&DHo`Iy?6Grm$WhMr&|jL4j(hH!XV5jg z>v(qF)#SF=cdL!X z`}4iL*}BYXj|Qu~dcNnzUh7zoR=dW|x!G98=R2=8>w$i2s{d-3&S{cP`Iw)#ggxQX z;M>pp^}q8SVJUdl-wbqKZJW>B)~S}o0C_i8d5$q}*J1nM-9{h{&!IREv5M1P?^gd{cT{b*2+FG?gjO~_~laVH0O6x zyN%R$@yy0GSuM6F)!;aedUV}+y+4kfqvJd6ulE|4`MZ97Q2qD&Lw}%UTBTk8mbWwP z4YS_2AM6UtfyV2JSuNN5)*ZFG*}R&Iy0l)c@0go)nxj1jjlo!&cYWX7d6<9Iz;7Ub z2iXga^6cnXjXcNCeWyM*dd3_u$N0{;4_H&(yN`4~?ml1-=sqAYt8j)4%;~3A@ALa7DOs zIv&jE-Xzd8{qlXiZ?5{RYg%6%a@$Nko^39zliGFO)>clc>qR*%ox=(cPP{aN^LF*_+xpe=kZWepMd*|Qv z{Jq}&YLl*Md@bMiThu+EeyFC$ai0I{gZ1L+{XTClb2l~`5(7<*-lw^K`}@29K(6sU zjs(8}_J%EC66ly``rWE-&D}h$XKisbrpCD5*yo!=Yuo*+drW<8{dYqB&wu>ryUwpm zVL5CKyQb{Oh;2a~+*=8r>z>ek!~Ov0vwJ{)JJ<*8Ird$(9QOdZthEQIslKYsJE`q@ zwO`y{e~+Qh)?Yv9lio*%tU;m8$=V2P8*DYapsPDT6 z?8!CSr)T<|{btcB^}N@N7{C4M zz|%WJy)Xa0!RVTM>ajg+3+lESya)S7_k?jD@cW|vw`SvLtj$Ai_9nTisotS=9c!yr z*ldj#&%g7UsW)xwL%l!iFTF$eaoQcd;oU;#BlG;f(H>V1#?C3m#@Co=jNWLEM(O!L z@V)nY|El2k|7BrUSP9kZJpY?lj@7BP->v^!Uvq*N_yUce*To|b*voj#Zxl=dKh~4= zrE~k)a&XN!30nhC%bDNzg-fT*?g86#9iI2zYEUP1K6CeP0e>TiO&!PY{G!_|>a6a1FnYiGtiP($`rmzcr@kM@_Ozol%4T~z#%K+?X?)_T zcj(aXU0OdFeDALg*987|&AzZD(C+wqmFN1sk}GfWKjbKn>T@%?Z0z0@ySc<}-m$jU z9=$IY-&bDiZyCPiVg9wZ?GHT6)4PMb-E-X0^TqRrf;xELGwylMZ-DJ#Ygh@ZL7j}w zm#5fg#3L>>v?r;fz3P{y=tuc;CQCzCUk2(BIh}Z>A4oggNRTG0;>yhGXH{U@!3Pr}yPh?b2=G zKN^`POfkE5ng+-4uHpWxQvfZ4K*xIml=9dNpL!Gw?s{R4dg+`+QOEEqMQH(0Z#s z|LRGIsL_x^Y|8BT(IDGxD-Wxe=YSI@V$jlJJ}u{VC}%As)><-`AzushVh zM{_Kv5xE`_Mp4XGihrbw{CJo zy>9HWdd~BIYu0hS8ZYjzufz3fq}KE~YE*yfFCO4|eMR?y?&WKFe!Tu}{a-zZ(Y&-X zdcR)Tn(KRm@8HpJGF%@{O^QjmS~WAor2k>-wp-YGto%{fz5rcX9vS*GSFPUJvw1f2!4foAk9> zb?iF-*(1E8hhvY&WB1zQvA>(?9WB&L;-fF?i-k7nbrLQObbU1N#F;6lrz3y(Cel64 zF9mw8&UsLt{Mql7^=9=sFZRah+2*W{<6b3RbN5?6dcGdA4~c0Kcz=7?8RTYP!?AEG z$P4y?`2u`MizNwB_n#3$Zg3hE8#YVMl*o^vjzKBso- z);e??YvY>p)^?u%)fDsUy}0)7>%xzt|Mf)Qz+-%_r+Qn>8fn0NeXhoTF30HmM(6GG z*x$|cPXEP7C+4%}7#HpF$+oaBTpoP?c;sxjF`NrG1bf1j!E;-}?*@B^IZ*%8xxD0H zUF)xM}Ir`_AZB=Vb2t?9tifX<5Ny&ln>5?Q^CGqZ}TkQ zuZERjG%s9}J3OOqj@57y)WbJjJ@mP~*6;S**!{prBYn=f{Pn8PKy{#=g(iv`myhLm zWHWkMJiD=dq4iaBJ!p^gjP~`I&-fkm09w{>_ux6tdyn?ZZ11gME$eQkN8X_q^U+oH z)|lv(hgJja`~KMjPKFx?_=Jb}WLH=T-W$JP@~M91VcphgUd*jsb8XFdVb=H7;Va+P zrLZMz5BsNx+wYP?p}*garvrbV4g79AH=N7sSn%za_ugP$ezu?3TgLrH9Io}AJ<#3* zvB6$d|Eq_*iXM#Uy>s_Fs$FYRUCsKR9%fO`(zl+3wmG7WzP25dW~TtqvySA4%)I#W9ojy|1```dxGEpyl^V;$xY$L z>G_e2{I(p%?_BfY|7urVE);KLlcT)pUu>{HnsfAgeZMW&?Nfe-@H~(6_08e-aO*I- zCVn_}@0K7Rxt$Fs!--&Dle_$^!QYnz-d8K}=p`KYeC(}wJu80C$wf`nz5dn*$ayWs z{#OHfNMj?_g|RvuM>Q@R(aYkQjrprJHL7>}phtRFuh9WL&iG{YkZG)hC@MoV!J)u9qtWxhFhob`fXGA-LrByGjKA;u-Dnw z4u-u!e%7*Pd!|;+Rebt7OJg^>rzU0A|MD%(-+B8!j{LvTJ)k=29-z);GrCzkySC3- z&yH*PU+?LF_vx9IOTQVeJN6u1jhdhVednk4)tVdajc2`Qj(Wc_(Q^H7eg5~o=37r}tKqV6G@K0len;T_ zVlTKooC&9cZ~CFY--p8$!5(&C%C3y|luN_5DZKBuyk3bxebfv3BwvWP$35@0ape8_ zzrO)SudCZgz3*Bz&^q$R?>knb9XG0-#dGt0);f-3Yppl>q;LF26V);;(egM} zUq*VTt?@U2dCWKKe`~MTAJ2%(`!pmTbBn2a4L#GgwZ+C`e83;40`G&DT-y@t6QdvK z+`M8Hr!_YdpLe`F#@4u6E8pRzV0~Wpy}mir`w!$8_fM~j`FQZ${vapst_1T;g5Nqu z@VoQUzvIO;NX{&uW(dTtz1t1{2~y;eQ87y41w)6L@k zg}v1}8pk?2)|=69^ujCsj?pnb6Zi<-6KITPX21XDgWh(%#&DrtdB!_ps{d(l*8kQO zqc~Q>k#K#uG29T&h690b_`x_E4P;BlszUIdl=9*VCb>hw4!9H|#5aZq9 zzVKk+f8(9ux^Qja{cXX%CU(Cgy65sX`h9O+z3yCXiap7`iF#YikbgbhWBb`GecrS6 zwYsUn?AW?^uE%|Dp3kd|*|A#bxL#it_urZS>oq;10Y0M{dg3h_=6gCEu^-SSoz42c zy{C<_=NsuI9eAhO5feS~7!C88_~@J`_-jwNGMo+P!kOUrGT({WbMhJeZ%%QFaWnpJ zy~M>A>NUpHeX;xHw%qsIAIHL-;gRt0bi6;~9pT1sG#m=P``+_?SAX%>|Mj;TsJ-(Z zJMXa(^}aauNgYSe*PBN5fZX7@SzK>#)UG}5v9b5ZvCpaBJg?7Q8+$@G3xB`!8q{~j z**aIxdR0Bsk?%`2%tO9Kyw=~G^%d=p{vW>)?f=;G#xk#;V|?DJ7R5!k{S8pv^Tw9I z>->Y8g5S^lvlQw_{`a0(VNS7Dm)4#a(|Aqo3%kjOzzIA2TA9jW1pyrL={PntZ=8J_Z9JwYd>{-4#0IL7$Ke<0`K2Hj1T6R|IdY^!SDb2-&}rQ zsWG~Lb-(3*HBoDM80&3gYv$N<9s6v3JbJSFFzQ3~p${YVbbDp&I~~XOJI}ne^nP4- zt>aN6i{rKZGp{ydO~;;%eW4$oplw}N68PwNI1ubtJalp3BeBX0zKdd(uQ+Mjdb2T!MUBK! z|L|X9Fqiq&Vq4e~cvuYL;d}n(bKiU3ckRw_I$R%)1$)?$;QcLODVVeVZ`}5;e#`4= z^%1`qA>Mk`J;+fFdu;5po^Q>KtwFCviM2_govFnLY0O*6aJdHg9ZPi}6Q2 z+DI*CYdQ8oPpcOi;W6Ie75=Zbd1)yuPq7!!Cq33n-2*z(n>j&mG+C{!7q^(j>3Q#W zU+TACJO{&xaC4}quFvt+VP9Ac_9;2kM}D7+**tO;BR#`5|`; z2k4$weFwcmkF(6~1Do-`9K_}wI`8jov00brt_l6d-koE=0qg}Q18?mNTZ7oW!$*88 zX7h-bx2-X&L+94Fc5C7r?0x%*{C9`F;Y8r=N5hlhsqk3v&3FCAaCs2(wkfkQsr3l2 z_nR+X@V>pazghGQ?dNO`)q(3Ho_F4Ht@rx=>^RP~nRjR3+swPOF)YqE?tQfyYhS(4 zH+&E3Gg_xJ`lCml;T?Kk3M*6S*S*pIbXmr|fQR_ZI@R2I{S&X)=-Zt2Api07;c#uZ zJ=_!S4|jzd!>MpGc*b{G?2T7WVm7C^8q*k0*R_A~8y~g~YB337xH6m$eEvjuE<78a z3XcS_`CDLDSPtrEFYWIUxrj%t#bmTz5u;;!pZq(H`<7T+)6M#6v(Jw;dN;?`{h-!< zH*tTI-)+r}^Zd^T{7{W`51@UT^?QFQtOk1kKhZJW(kALt;|L$|@jS1sRZ}rGCUc8N zEWaK2=}0&e?hOxxM+49C;%(tXxGZ>9++yW5-+i9qp~h1!wl=QYuRJF|-uHg@!AXv< z4mX4wgLPjRcsR%B!ZpGE<9CQXsK4+1rt02l{?X&=CN{k&Vo|^Hi5&BN<5#QZuvR@6 z*EjdQ{7I`9eH3~@HAF|W#i#WxP4+h^?eqIe*fJfR)3aylsqP))eqhe(*LoY#sb|F3 zm>LVu@X@}&TX%#z!vo=bcp~uLErB;ZweT#YWWY`h-uAbv(@zndXv2B@!Q!M81{-_t2jp@DC=KDSWdmZm$zV~jg z(tFhZJO;v@7TRh>u=c;Ra0RMCzKDLf^H_}hfd8aX$xBj2x z*l%k7yEdE)=chcH@t)w@dsPr`W0Q-R$#2SO^jl*zB&3Ega_?8ldv~j z8tef)c60FEe=yt`jt0+&&9|G^%wXUN|i_Br#(c~3YQ?g$SD-~Z1A z-ftW?<@i9@8kU21ef#CnnAFK0w~@VZws&fzo~^0-7s|^$&suB5Jh;&NYt6g3zIM$& zjJ287P<1f+pZ7=q)9UDdzp3p5biEpu!b;%He%tID{6^cK4E$e>t*0~3iO0L<;az(Q z-+=$DCl0^AdH>$P`*%;zugK`Ri-YfXeI}0e*5?uHi`)A0mzTNJVPCNKTpu0?eE(8- zF`N%KgsX#R#Go!0^1fP&%eohe%{|ZPS^MSP9vj_LKhJeO@4@xfwrd*OpT67YXZ73b z^Tyd{7v~?&eWAUh4O*y{sv$b8CVc<+hW6=rOV}Cwd)BVNe@3`xAMlOzz4l)HUyWL; znp{gC;`5w$yzl$VzdXqM_2F`kw}*q_T(~dXA8rYk2eI(0_xkNOr<|>^*825L*SEGc z$O;Y>Imo(w$yY*`PaoEH9yT8ASc(1;_P+abfJ@cOasF6Jijv;oa zBh24k>#uX~|3VOlcO1iY&v?GIP%G;DHp4g7X< zcqBX&P6i$ohq(Fwmx4H~PqPS3R>HqY1h z_CxR*&yMwIJQs?o{`0IH%mMSsO>JNedtLWmId{GKS|418_w}jB*>T@%&#c?Mp#2et zI-z}Cuea})rWf;Wd>`42|7nLNeE*ii&cN@y&tty52Lj!@wk3F$7U`(pm1>WUs-?B` zPDAum|I=tSZQlMC=P~P_3iaT*9P=R$)_cC+Ji>FdYW;a@yJlV57rV90F$ufE(O{jY z!so&>;oe})9l@IR3h%)C^^;t>e!XJ7Qh(2$kDl|M_<6G4H^2Psi|TG&WA{BVs2xVH z%TaISsW$ctn5)O`TL;!`?Zh$0;$E4}ZQ;v}`|`(Ct7>fA$7lOVtMwmE(A8?7bN;XQ z59b({O|Nau$OF|q&Cy!FcQjP}to5y!^*@b*F6o^A>3b5c4L1ef{=38Jus8fp-~-S; zEsKRty9bT+Ruk97>3Q?ZV-gO9Q{lewYgc4hemmTZ>`1SY9d!LivxbM7`q>MuKpIE_vPx?_0FZnJ#QX*Xl*fyYmC+P z#@D#zv{rw|y>9Hm;`qn2SJ0i_pkB}~eQj^`u|1^)T3QOb0)HI{e1CbkBJhaoo}szw ziw8#SR5$g67{hNu3rq|guTu@uEDulx+g~G zYGFO7hnS6ihpdLJ!QQkjOoDsr(Rb=|V{vc%?cjRX6o)v)Tby@4GWNdSjXjoc>)m5@ z>2dq87=NV6_p26auJ#!HuV1tN*HhmO+M=oLVNam_%L4zy@!sG*4UPUcN55Uw&3fKw zT%PleIm{^*`lRjs;kNKp;7|Uy5A6v2C}z+wzj(iKn7{Q^2YRJ%^Ye+{5BtKAaBuix zcs0BfUJTYc5X7+*eE;n?-tV4gjcUGY8C!pQtaz*|Hhvbbd5q%d9@cAecW=aV^2H>G zM{TYPC&D%1aIg<-339U!m}B(2xqIC`^Yy;i)KIM8bFmxct3MrGgPQ7}I#h4-Cjd#t>PyEc^ zH-+cIYvGOXQg|?&4wnaejrBY)Z|jJ4t^Ar}^Rg~qd5{0Z%Aew23ERV#Am7!{+&nX4 z{YlWHgW=k6ESw5Ahued_(lx#FoHcm7>+-yL^G4X=#U=wOgWVCif}m4)k>ff`k<3p zZFCQ)Cd9B-zr`jVv513D%uS#Ku%^4uNl$wqPP3wm-q@cg~u(V#|8g?px$*F4_sdXpS?{Y!JKpXRle(LHh6 ztLy`M40Y{s&t22AUkK`}hw850^}geg*6Tu=m{-fiwY5DouQuvC^6{KQ{q=)4^-JIW zUZ^*CX*sL}y4e$aYpw`~124G8qcrWkSzVY9=C{^by>5JB5(iJwoNp98^1*%Kv*GD* zf4DIm3HAW7iIrx(<6U|*mt4(nlq-!kN6$}!ybgrB!ZYD(;k(n(Uf`M-thE}vXYY`^ zb>t`Z+5D_2?@tBKsf8MK-w`t(Um1>uTf_PAKzJ!U7oHDKhP#9NHwAkUZV5*N-#<2m z@1GrT{zSMl91r$6Yzte0KDLhX6N|mVHMw3A%*BKE_(h<@XhkRe5_er$(c|2Uwq>B z{8I4l@o-moF}xAJ72XOjhlj$kAQm<8d!u>twfwqHb93&x_0&LJy#xD)b(e#+PlemV zqv6ZJ`mcr8!>i$o;S0f@pazeHC&N?0JL+Lno6k;1$DZjm&psT~?L;^}9k0vC>qo;4 z!5(uqsI6zV2XTt~*Mr(YJ{|41j>n$MTQAjMEq*rl^hj?$Hszv>W!|;kTZ})d(?)91 zS{l_`-9HiZrv9%t^ir?+g9lf_uHZY#|CfhL!*aMd@TzyJgO2=fPU~3jLUD>qES{$= zKIW6dfi52l^v|C({X}>)_%4cNDO?ihwCj#*%9-Be>N{nAUS18`!ZqQ>a7Xx3cr)hb_T7)=?k6HeYpYJ-IrUi@kv-)Wh>?rJj?ZrZ^bR1>f~o!WY8VL-&A}b1a7D z{ka^ggV8nbc&6v|yV}^#?hI#rq>)Vc;_`}9gWqh9_Y`ff_~Ao9`dt()+al2 zd|BXyD}r<0@hzn5-v~578{P#Sn72K?Q18Se7P_G+bMpVeK&y`gx_=?iy6@n#fp*10 zpY^3YXic7@e)v+ZVuUr8!q%`o(7H8V4_^y>!V~)f-`fx6>0N7xSKPEKM{CL%a^M#^ zcy|)i%X-T}UPl6-JrEuaeEimwuVm!!^WjXmJ%~>&#Prw{^$@dnjn1D8ezTki-Z35z zXM^{i3ZA(?+!w5Gj~LJIonE^$M!P^13#(QCC(^GR^M_4(x-tHCb@dZK~u1LLu4 zYWTBL)Da)M02k8G;@U4)POQ_ZIP1tLCM1_vO#`_54bXFAdj)?ScRHhOJ?DI2euu^YE?b)W=-sgF4+1P6yA*O$>S_jyr?>=J6?> z$NBKU6my#Uo?tI9*R|n55XWbNy-n@x3)_P0&h8=|oM}LJx7# zf_GH|HLU-Av-CiJXc+v@Bl@S0ytOm>0nP-oucoa`>nvgykHJ<>+6Mj)_7>c zdi2BpTLR6V3%-GL*ze@?Iet3$E?N8Hz|Zoq-aMViS-#e(PnUBnH(q)&@S(Y{3A@51 z$jS5a>D=b0S^4?i$=7rBgn7MxESw1Z^h|hl%I|0VR`^P=<~PFI;j2?TcSpD(>ocC6VqL!X+eLnp;QcM(vT$v-{RL=3qFzx~RwAceK?3s&Fqj!qhsxd9A zyZY;c-sqElf!=*jCqbXD40Lle91aJAeL(N`1bWnWUh$sy&1z)mbKw&~ZAbLYxe@KbydE#E&DZ`|m+Duoe?F+a9@PKc z18ClNO&@oLBZ0O~glmJIUly(iw5I2@_UnO<&7-IG8EcL)jK}VI&hxZF*Yr)NzE{?! zx7))#flv6KZru~#Zv^qmg+|8tdu$!~itpkeUO7Dz-VC1)HwPYC4*Vj2b@ASq13ik@ zT;dh$Y7mbYzYx9?{wRDmyd6Fp9u4Qi{o!c1A>0}4ImY9mbr#3oU=Lgh@)WoA_{!QH z)l2QgU4I+BBMh~7dk#(uQNi*dg8$GWJS8mh5+t35syx(C>+ zLI0CL*Lr&_(1P9{pN{qd8llzt-+Ov)Uj4gJuf!q_y71n`Q|Q+BZW1mF)_yWP8y*RC zx;=QV8lxfejrlsZzI^3pe%{y}9uCikSHpurj`DEdo>Kph@jGt)>~o%*1U{`-Ue59N zr@WHU-g1B7DeLYC_O{iq64clEuHc&EUYq1tJZkYH%+H}$CRJ6Mr=ab=<`jy;*}_u~A&^+0Q)Zmlcdsd@dc4}Wipe)-1Gkp5R6r*f?C zXTl9Z|BnRE==HCKUkTnfw_dhi^LplEiTDkAp`sm6W-yGfy-wS^ptnpl^ zpZL3L$d9Lu@(`On)O_+cHyn3A5|_303~KCLy<0!~p}F8aYp5%{Z}hC|=AMLI;p!mH zYr=ux+Safo>Y%J_3a)|9q9qD=!+iF=A}VD^_KT(;KnH@GSbW5K*O|U5Ac5b`7=4T zW_vdF$2Bo{mY=*|&03pwwuM`QZ_kV2wm_fm(XekCO_`%wv1ZqC&3*Io*1_;Xcq@EA zJQYrby}?@boOryaPMwS1_)cxi$75T$`xm-beFT$u1bKFJEMIxo{~g<-#rPw= z&elS$)UGvct!bzAuSWDq-*<;=!|`w?@V>qq&j$V87bZbZ_0t@kPw%XwZ?nA;i~Drz zo%(>Ut<8(e!P*an=fa(#n(~ZqnDZuuhc|*b|17*6o()IB!C>vxAZB$EtGb)7qZ+9jKd6Uw_!f@c zQ!jZnp7z+g?s>+u`T%=@arBs)I2VK3*l*+lajr~{c~+jDX|4xyd|kLD+#2ZMhH!Gq ziHtZFn)~(~%iDa`>b&N5Ol$HLm-y90E+hH?c|r}KC+$ybu^2DB2dH1`sm9fy`d4#$ z;v29dTpf-Fea1~e-;MSJzOl#CiT6gmwWro=zuF(?UEgyw;9dTp4d2(D;g0ZPI3M_T zS6B-DMp?IO^NKlX!kRQrb1Pv>I2s-a-wgjCycNuSPq-p%4a>p$=29DRs+nUk+H)ph zci0ouif&vhl8FT z3#Wsg)Ah|kpM4YbwH~ww@SS<+oc63?O?_%l9J^+1J@=gVM*knm`D1}c9}2!XzNf2G zmNG7fYKWfYWNxEae0#*VHF%%@|1kV8d@p=CycA9ad&?xyyLZK}UgA?5*E<(oKN?;S zKL~#m-VEYA8jb|Ix2|}-t48t_o7~4*v^Qce<9*k9exY&f@8~7#S-1C{H!r^v#0GIT zR_FTBJwh#4!oF}U(1W_s$Ccsoa5!8St_?@SRl)OW;N3FG@pAZ`pno(+1C80JR-YIU ze`_ZXv{oH2RGXjnd##bW>3>mY{-^i)|B@VU5Bx^U`l#negFWDuaD6xs^pIBQh;R7# zm#3IdFZIp(V-K7+2G8)AIr=TKF3)};@U1=PXxKSLe3Kwnnyp{WYd&kcz8q-Ke0PVJ z!(W8I4u2k=4)!$u_g%U;cvoG_V-CKjVe>o~o($g)e-PdX-oGR8g7~ayF0sj{bJBBl zs}^E)u0G=Ln(E>n#+uDLp7+c+Z)@CjJFoNBse7+n`O(;Z>M864zS+w`-+3H|!)3uW zsF7=Gadq%J;L@-yYzgXUbZ#zlTJM*G8i)yMp<152p{%4eaT3#n;vSY#+n|J+{8*X~EjG zbWeCSJQJP{);<^PL3;x&O@ej3Uu{^AmRvX2&R`Ea8D0->2Ht-wyc8}ED?u(|P!Fhu zYfFLF&xfxD-v3f~J{$|?-yOum_dL)0ywv&ZvGSB}>)_aRv8t1Je>LcXbIv?g=UF@zKox3hKdD6zga46`N^KD^Q*c;TF|E~+`>o=2U z+}{z@YfD%TYGrL}=%d=p9pdZgxIJ!NnxFj3nB(I3|I=Ib(Ha@mZuI}BbFK&V|8kDo zGri{bGvSu$sPFb~nxJ*R1NfIt^pkF?V{6Uph3k5$$DXhMkLCEK@a6E8@M^d(So_u> z7FrUk^?1l!;3>Kl6K(M%&G}9}5#9)Y9Nr8M2ltkOwY@7A^Lbwlli`XWE_&k=@5{#;a-tPF;-&h3 zM~)u|p9_B&UJmq0zqD(8@2Q3Oy|+79@9n_*M(5WBH4%gS>DHdXll7&%;oO{JbZ+## z`dCx^>bM-_vt@cNCUbjc-u&{jrjbXxR_m})T-G$NxY0Gltfu-b=B;5R zh`oE5>#o^1uL@TO+OY??W^dRV_JrMGdr)h0nx6*cg|06*z4_TGdJNiu97bkyT%7;6 z{!=gYR9Cg8F}2qNy`()FbWH2}!kKW>lxs5T-6VL9cl8kV0N)F`p(|^x)dT(W4Bc64 z5>AAN!WY7K!}r6VgxA8e;cTEiFi#gR$J=IpN>9Ts#3w_p0 zI^7qJhFin!(_{VYchDXV`v7gwCe7GithH9`?$vkJq=mELe0Vmz6}}Vf0lfS9V14?U z1nbCSoS!C|mwZ;j;o!Y*27c}wyyYA0d2y%#P4goEi{VehKMCG_AkeQ|<>@`^%Y%P- z)BDRoF4i_WFO%uNSr$DXh|cut+V&zVP0Y2F+#hr5@p~1!`P5F2)wTXtbM@C(qu%Nz&GW-8;kIzy^jz=gQ$LqO zwMd_Or(f1uD{e7(#`@l+ce*$gUJI{>SHqu#?}aDBT|qqbB6fP1o!>R{(&8js7M=;; z2`_{Lfro!H(6$(>{qB3dP2%``_-EnmAa`0;6VEljdPffGuqAl^^pra@-WskA>bNf) z3P*zZ)bCh$F39(lU>~?UcwS9bgMFaCwT#Ziq85-Zzlck1XY1m8t$w(!Zm9Ro;k^0% zLXOQ}-&ZHJ!n3GuekaHLeRzs|)m`u8`R_A}a1TXT0#YkoS|qxDNX5Ff-o zGB59s%C#|%+2i*r2lKYKYHEMw*Y>Y{HS&mFT^Vi*w}UC`&ey2y!+_-222GJGZ22k4J)=$%L2eWH9@4P%A#;3yD;g&#qzZV)KJ*lnt)L|03J`es~ z_9t)dw4k99PSKvg&V__L67yX{x>JRS!=DB-T&1EClG%o@IrVq{G;%P;alP6;F-h09M^^`!=2&PU>*Cw9|Z3_ z5$*`$zB}9$z7*aJUkrBz^}arw2%fzy+!M^*JwTjl%P&74n4Ihu%XTWuy6g zn4VMHk>)av_`BN=vV(gzD@(+#c!J%+2@Xmwb(Lkq0&>&6k4!l8o?UQxZ zikAkhZEyFUUK^LgYM_gI!#Bd$!&PBtppi+i4zHU-F0GfG#J&`GV81R`Bed;j!>icsx8G?g>YOKJXCV@M8O^C-o2y|4g`O z6MSs?j#{a?`HJ~U_kdr?H8m3pg6jhoHG{OZOx>IpqVFYx}q z41XPX^r3KN;Cau}@g&?H{wn-U_(Hfk>;0IWDDY$2ksMoK`v6#Ilf4^YN~^)sPO1KR-nu`H^n)jRtA{rf`4Y zorl9?;nC^&$&81BzO~0RR(+VW>ois|=%4HEd)B&oZJw2IP55khBUtZP=pL{w$DUaV zbYjnGPBh~>FWniQ3U7sf6aHyzIydAzVMSZ>&jB-61 zR)aazLJa60#7kn*^N)p#Ho?d9tiE|y9zQq5xnus+OY_q}wWN-Y;kxtoTCX5i{uej@ zi`O?r4s_*Rd$l#XHtp&=#J1UdHk12%{j69=#E<6FvDeocH=DEP&DH%=4U5|H1bx!X zBwQZOhI4^0X#L*sV0a|l9u5Y2*E8?wFAabm^v!x>tYV-WYrE%}`jH;^m*&3_UJlk1 z%Ynd)#$Ca>;;k;tLqj{m{%~!0DtsyYOg&%~M!r`zQ#6z#SZ>S)cz*0{vBQN*uj^}gfc8gI6jYNt26s}8P1?yk#GTymUD zM>)G@&#nKfsm?(+qjuyWu2Ekb&F6!BPP~OSst>u8USI28-``9g-sya5=h&!sdZTYN zqA$C{@o+4h4%dgfr#zJLzOXNNR?qZ`#`KrJpl{~3hPA~y#!&t6i=I!y6~Q{+3SSAd ze?##5!@6R!?@WStX-^(Tv5R36mco%>|M=tZm*M`v({k;a*X8&-;q&3jz>}W}a`NnF zgZ<%^V7~3a-2JAB!J5Ao%f{_Urjfg&xLY*KktdNR1e11bggl$d*9n^&Ry5oIjpCa zYO81TsQx>{f#5rOdEgCRxhc@z{o$IhEzp==(wBGnP#0#h8w~? z;pU(wUk%TPQ$d`oVG`u1uKcA>Jk@o?JKL8Fowv?nZ?4An@RQTI#b4AJ)`VkzrZcq^ zmwr@3^}qY|zh0I7?xL2MU_{ljpt8fYEGt(L84k2}`$##ub`UgT^JbyQP5 z^Bq%ry4ewE?}~6J>Tg4 zWPa$$&rfNuT$iu&>a*vrxnC`c*)w|Cc>0X%-m}N4ulnx`mxU{*#|JaEC-yLVq}q69 zPmqfqu7uTK9yR%uU_I-KXRMv$#y4-Aeb%`-%q3>?@~M6rOa1S@V=??@;CC_Uy<@#W zb!b!r{x_$0&7n51W^7KKMemW{LApq z!)Jqatw%@VpPk!$w6ZlE4d(c(z{|_wlc8GWkvqck;b`D-TI9Dg;Ye5tp0}1<)ZQG` zsk+fRoqEo>Z#W!_(>-x{cZ|0^n7yXgdLow9uxC2%&$u(}nsPGZ1L4cz<#2B}5iSph z!&Skauq{l2{KRGN5{sOSWxZaxKZ|$iQhu$g`WM$mj&%sSPJxWDBKbr z36F-;fj?HlYSzqIJk}t%1p;+W(ZCc>R9I7 zNIdd__2kza=fySen&-usQxCXhL4<(%Pt#Yjj{#tK!_A)%nD2EwPK)+MA7e zULNn)wboRuMeLBrm`i(MoPDOxwO8*&-qo4!nL6u(Z$!U?^a9^@-yq(*KRgxg4!m<< z%GQj&J9~rYKNIv*+!yMPd-P$=rNG-~15e%;o(nI8KM#KqT({1aKoj&Z*21ycnUChh z`FG)e4)&A>!xcfk_5t2H8suVqY!AO3Xmwuf`sle+!F)Fdbx;HExi^^}tHWxbb!&R} zq77hOy|#ClL!9brA8-uo><(i1Qus=ED%>0F18SvT$HKla3F^|{Dte=S#d>2cHxj33 z8msweseX41^Y_?2b*Bv;H-g7U)CyvCU9a2kdO)k%5E)b(wL7UkkJ--3?>FGdMe0nAv4JX6(fw#5>`m~>z zTdZrvB?dX`we`G9tA4xQ5U!c>YR10}UkvBMo*+)zst%0SFpuZ0(boF5Bfdm!8x_6BpP^{0ba_>nh_YNmDsHJ$I?#}hjz53sE@0drN*0Scu!)D{1m&5yUO@{yJFq>aZj5fmp@Qx7AhaXdk*Z+!pv=?4J#M zYi{q@>#R8m)~7kO9&6fqHVxjv__AEVuxb;Q7?+^CKF9&~AT@~L1AjCqf7taq#)t+_d9 z)>x`xdBd8t(REvs#?t*jjD5~Comb81sy$jSz7OJlwJlDyfqJwCt-W*C(;T) znXCDCREy2lqkS{Kc+`8;7;n*??*>mF3+DrEJrn5u!SG_Bv%3PnZ4bOfr}A4%-})j3 z&)9#g)oWmh>tG?}@JjB~;w8>MKg%d$7>@)XBTSc`MW&)jn8*kk9OH)8aMJk{7W zbHKB$)vT5_66=rSnd*+_#4B$3HaE4Fn^Au5p>=8A&%C& zqi>EqfMzG*c<@boCHU5SC46~0zMAp=K=;dmubN-;YX8J&?e@ZP@5z%V#AW2go#CGF z$KgMOzYWiauZ6!0e-*wT>_t}x@6fh6X4i2|4b{~Ayuj<|-!6U=tV}tW5xc{_uoT=E zr<$9GkG;PdtSxu@g8BA@C#RT`XWds*^T7LRF0R(ZD0W!Ky^fB>ZVqdn2>m_wM2_tT z=fgAMbK&V=e-fj(!HX*9IuqdhUU z{QM)iYdyid0~16}{C z@b9MMzt8xeru=Ui@y&2oxIB1Q4b9^l_nEL9_J=(|oRgp*{NVYOVDI3|rEntf*5g6m z4~9cQUFcA)ye~K2G6&4Z=d0n4pf;z2`8@~UW4U=wt;{9wu`Z5XD<97>JSzt8+m~(# zw+8jUXNp*#4lf1wb_DBM56#)ya&BMd#V{Mw?74Run^7M-I@SX*kGNJ1kE3VkomTq1 z`|6F({Z8VXjr;vLZ!N?rZh6Xu2k5bSQFFR+tzOrI?hi)S)uOZ><}9xDSbfxeqqXsz zIK5|%#@il8UJJW&*6Uz&xRvG9Q^LxSAsZi42OewU8hTZ8EJjSYpv5}Ytw$U zPpz{xtWC$h5wuLd{BujVE!-6NqTbfs44wT|O+v(JbN z=Ia{sV)&r1iA{`Rm6tr^thO{kbG*&x7?0`2cYyAU&g}`_)o)Z^#$M}aEj6_MX6n$G zTU(=ftRZIc_(pCGv~@0=4s?7p+!LM;uY^a#(XbqNellfSM%SFvyS&szoz{z040PJ{ z?L&ux`L+Zex;mT<;`)C0+wdR4KMK6>9X0KGAItGlke58=Z2sGW9IYunx$}$lc=$hu zw*#N)i)Y4~o7Y^>5BtmB@N#%A*bD5D4+nL$rZ}u24s)$n4{LlP&<;PZ1oP|&ldwJP z5B7j(!V5ud#Niq5$Xg$b_GGou6ETb38dxuu@&4@d&dsIf@)EQ8i)(A)8PCJ}rLm3c zewdEOoSQF=@F>01|MuHo9dKUn>qntKeerI6K8~*U9{s4BTFv%iGc{@s<`O%d(I+j_ z+QD!n?3y0?J$+YrG5C%CXxJB)gXgydy7T+yvu2&Q5*`w&h{>Rhi=*v(5HV&a>Xuo@+;$9D&FJQrRFUkDF{y+JKJFSo8~?0mDMzWDuPetm&C zs-NW?p9s%}$HN`dbJs2l_JPa8wqOoD)(bgzACkiu&uk1EIhRA{8NF}bqHaCz=$+Df zY-GI;`kCe{C&*c^z@NNIFY3+z_HcdE1HH2!gRbB{ZRrhtpniAFeqw~{)*1V!hOJ+Z zH(Dd_$-$c96^j_{Q}ovFj(E3*GvUSXTJW7aAMOpG4_^+n_*9_3gQ5A+oY;)b(H4m7NRYEpg(%7*B!fNuHv3Gs-fzqbyojg+i0EiK>XIDF|pMD^d@$C zTME0vE#V8{Yk{`E5&kIre)w+qe)v+jI`GbFpkew`8}qAyT3E-rbSdt&VpelHmAhJe zGVqdmsIl1ghv&lo9sY-KeGtF=%xnHF;kEFe!nt5B_slPb&g%OMLHv3sCV5%kd?R}4Iki(GBjnv0?#}T&;n{FL zJQ8jXcTC5#87~c!pic6&zO}7etTpB}#<7uO@q1rB^O$$fHF&pdWUUXwGh@DLr;a?N zUo=WfdPOhw5dF}6{lApsXqc#6c`1j#-7}t{LHQ}-FkHfzXUkslM z^02NL)VOnsO+WNcf7{!Y93Ku32DR|MYwBnndGeum+q2HuQEcM(9PD-aqbK$e{n!!C zg(s$1@7`cfxFg&a#Gw}6u{M3e+^{|^Sx-J)e>0=^yni9ozl_g$w`^w35Bga#H=m!) zG1Rg>(__8TbKg+?q8}RJh2?>z9Mh0}-Sc{7bPn&^1N5H`^p7Ws{k*d5cJwZ+80Tf*r;V_yv43SS9-Foj>f8@?Cp5j5%hM}ywo8Fqzh!xh0?`-2+r zkhSF{2lbRsz2*6vgZ17H|2q7y;r|T(F?=)F8`Rpn@=^n}5c@0P{|bK{wgqdr?>V_{ z59fpDJo7i?E^r_zWEX~*(jJ#nFs4v^Q z_R+cHchXNi;Wc_Tzj>TDA8X4)OtgGupzFt`Jeu+R|J$yeK3jI(lxUTsRf35Bq|3dnS=Qx_*cjoU%SKd*Jz2WBYM0hUn{!`(Z@a+6ruH{ruw5B-69$-GY zG5?siF^;WVi?x`i*G8XLF+R7Ib-q`gY25bJzGkdvrIJcTJ&(i^o+lT9MPxMMddKFEb4iASH z!b9QFaA$aDpuy+EYvHr=>vv^5A0C_E`<;xR4}TgyH|M#G<}$~P;b?eAxHowIZ^M5J ze-QMIJAypPv$)6ox|aJ>!5aTNd?(xx^oc&JhVo~Bhr-!lFZT7_@NdH9VBd@WH^&bJ zc~=|x;muj#z0ZWFgIuYhx?UGN2R%cr8q1q&GDF48@WR=KNm2H}1jS=%96?FZq>gc~>uW zFZ#eNY=nJtT+hP#9G?#a-|q>uL+9qy1IAvi2eh{B3$5vP^4NVCVUN8>r`0&miS1~( zJ#{1(t+gA?@&icK5z&-Ty)z-9o8*}TPJ<^`}>DL~d zLA-u_*qCEKdxN-d3D1Tvhi`^I7{>4B`me*6!qvSbseC#CSQF`+tSM47?#e^{}VD=lX|&`n)Y14(|zn7{vUp;GG`{eWzpd zB|pA*rmuxl;id3$xH+id(Xbxm&Y7&1_Tb&lw`#PG=R9u>@0(LD%ztM%8_tAB!&Bjj zaATMS`m=}Xr23)J=5Z@=tcq{d{V_l0@8@w`_3rob{l;%kg@!Vu+7rAt_5jzQ3G*ARr>;L5n)Ah6HvYx?n|(HyeVLnA zXmBmqgI=Jg@y{%*2lwfZe?Aa+@sEa)_g@O{4)l8WeEd|#Ul01hH-a9ZFMK@QAM_h* ziDNNd`FCG`xF>u!{EuKB=i}+%d(VyU{%}64m-?u~^WooySA#vst9;7a_+GE&#+-bm zu6+64@X27VTjt!5QLa2=PO+?-vvYU!oF1dzbg(bb%gy1|@Q!d>xG(4dPX@WSADW|& z>V-zcYJK~%e|zX!yE%$`Gj?;W*K4kEKk`;+0Pq=S0Sn z;Z&eMqwm*)-w@_g!*NvKvBu6pafnBJVs3t1FW&2Sn~mPn-)Kh<06kw9?1wJJ;4_We zpSWrA+3;fcba*8^7VZi>spr|dXL;AXGvTIiM>rRF=y;GTd$+g6SncV@f}VPJ_|5RQ z;m?EhPX%!;{%-IdZ>bB9em49?cqVwKIrpAt7uWS!p2hX{a6Y^celdJ7ygNJ;c=Vk? zUd;LCFxF++yuD{#dzO27w$56RGkTFjIkL9gJ{j%^?yHBr={ZHLv?&JjuG&}E-^pl? zi|k~+H~QUiFY-1*2ar>}l^)d${0i638g-lndQ~SLI39SP4sQ%<3ZM4|b@wixjnET% zPi?JD*Wy`>v0C1${+e^o+nf0z7VEmUH+$B5>`{McpF5UoUb-jny}j9|J$q&r_(D#6 zHs^K0Zwgq)8vi(meOcVT^X$HGF8qG@ci~&%$)LBGS56l7E=O{EDg0^ptME!t7cp7i z-kQtiqoeyj5v*@d&x8+!4~Cb+6XBttmYxxZ=a=oHbB<&4F2~-LBlGMDdfIx>3(kbg z!G7-yCxY)iUu_u8qej(;d~7wAF|Ms#n|G0|y!XBQ%($OLZ_xsc*8g(M6ZEU*^4>bC z%|=i=x~czp<)(0RxOG0>kWsJT3*H(r51qP4tNOh4T3<2ot++5=Z#U2O-u>Cvn}fa9 zo6U!3cug$gqtye!Uipeod5#{%+1_Cu@p-p%o68EcI@(6l>+|m|pt2by!9oqxwL5&Uv+EFw9cSh;~w*)=FNRM=?=1@;MQ2(_s3+~Z~ z`DxO6{3MRX(s;xtPW#zTzCCZh-Zi)Rx@Yr@`LU*+$9sGvK6?-|E%LuPSJ!P$YWU*$9C_VZ@ce&KRn+&8Rf3fmwcA?fYE#H0raXKYk@A*>iWRvehZum zYI;j}M^I0FLOto3hiG#>90~h^`+7Y+wg=D}k6KsF^&j>3x%J;ko$Ph9_kCv_aadPe z?NMF-YHBX$@@;QF8|3bM_}%c|f^+=&;OyrW&yI0=Uw!23vGBR@-SCa@Y`8s`Q+?!G z+@2p}HJ|;Rdxsdul9^z0u0i1})3zLobp zQ@_fK_2k06t-RmojB@H-dyqTYruC19Z-oCG{wTZ@lp9-!tA3 zGwoQmDt5X(QBkDJA_n9CpJ&uTLwZ)h&% zQ*PC|V}AoI_JC?ef_-6Q`DrU&~_2ozG;-qhTdVl!6@K@o#gue)%3Uq%g z*iXH`EJpX}-@N;RdHLmo;cLO%-v}=T9x~_81Rh-6mpIjtr{&e!{N;K*+z?&~9|_k7 zIp^Dd5_lQjr;%mzcRk;$OW!e?18OHfN5Z*q{~WQO4|fEbkvsjaG4Q{7Tfe$&jm2Vb zU4I&{oC~@_wJV2EJ9#b5wNdVATpehLCTRz*`o{7v|a@LI6v?o}Se-+30-urJ!T-+h5EZwzww>*068?}yI^{Z?*P&8NO{ z(>SdyUVikRv-i{CnV^31?#$Mo>R<1QNsbm{_1QkGBOlcay?D<&=2Oc<;X-f@*vnnv z{2W@jHSoWh>wj{fj`jxoFuz!=w^@yr#W~&k` z^ne4w?@_u^Q{Go+_2!KS!sR)SWc1zXa4`7oNtd7jpWQze)I3SSD(hYR6w*a-TY z`OUK!lX%1-hW5Y%xt7x>0{{O}_)d5^91mg;%i>%e8p|9^V`eU#R(qZ-P!ysM=;@DuIwe6`Wh*-W#0gF349`9NEj=BWE!;h}IjJQ(P~ zvnRqVc;84b^nG(U8TeopXvsQa0F8)=X3#k3jyHcYc*b7rmB(nu2>0yOGku>A%meqj z$DV0MYwLN=QL#C{9tvLw-wOXa{C42m6X9^s)68WpYcJ~2J^7b!=d?KXhm*lPUkQI6 zJ{4{XvtWPA<}$Z^c7NuyuDo0q%=y9a@gVj+VcfUp>``vzZq*vr@tt*weA0_|yPh10 z`HnefGoB08i2L#+r(IXP=7IPj7CExF&0<>2?ezKY^(JqSL$q%4>e`59JwQ#`2RiEW zbj>f$={@0ectU=TW7cPfK!@Yq9!SkmAE%5iR zsvgi@&_2*ww*S6)UK6!}Izg>LGjyZ}@G)(v?Yrh94cs1X3HJnl2Rt6`50`>7!uRKb z?=OcZf<8ef=L6rFpC|PJ&=}pmJ751!M5yt7%n-A7|E{!q{}st0+MgQGz& zv>xa}o#mEJ)YaPczqRdAKJcUAM}j@uQ`c+^)3{n!%W@-skjrJLquN8>)l?nSq`i3@ z>npl(mh#_`z(?w?AN1Nt$8>mK&;xjh$L|U5`TSUTA-p%dFFYCU4SK|(uo3u#@3A)L zSVo#U9C*;P;-N+TNqn?!^x1vz0Uh2Fo(}I19|&I!|1x|&d_8{7Lw;@WLWjV%7fPL8e8#y^<}Le3Zwt6xi_eR`K=`{r-S}V180I-sev9T2i2CiA%Er- z&sO%`n5J=cUr;Z|=g4?ZzUh*NtbxJ^njlXKOOjLE$9KXd~|-jk#RlHgC5`< z;Avi`N6@G7z2Sr5r8zHVd_FuJ&IVdC-@Y&l)^n}b(Dk`F=QCQz^ZSE$+BasohWEsJ zAzTa}2%iq$4E*=)`S?2-dELF=4zGn@3NHjP-x}!Le0oxSB)^+!QJ#N1=%0JT-QksB z-QNguBxk=6dPaKZO7mLthl85f^NH|icz1Xx*e~Qq%slwkAP%0}%KYQLJnua|ws&*M zgSa=s;W_%yZQ;(KSJQ~+-V*3Y4;ZyYbM?O-L!%hip02l_kLGdNsFt8J8qx#Q7V1&@ znSRwx{nYFr>+IouQI@~QA-piiE9UuX|-{Ze=_ygxi1c$MGj zia$;V=Zd-b(K-HHcxBGzjOPOVIfsr1@A}LecLZMhSor<$t?={`Nn?au|d;)f3f-v3UJ3%yIe%+1R^BY!y8=2$hqwbg?U z_Xhiv8+kU$ha6rC*0sL6sbA+_7Wb-q_CgoVfu9L)3Fd3v=|LR!qvy!Cbq@s3c;5cJ zFK(KWM{~*%EjFL4_Pi>#>HcyJ%1br0ILbBT9crKt(>2CxHBu`zQ#&-l% z*Lwb*Ko7j+OnN%JCpi=7EeJD5|JoA>|efexo-<)gl$nEHVYp#lCy1(<>$S>qv zZRKAb3jg=oNRw(sKTyY?3Ox&qg)Y@w57-E&!kO?ucsx82c>Z#DFkB3DdVlc!`vZS} zB)k~j7oH1`h4%*EeJcD)__grs;q&urBR(5G89e);@VW4r@LKqK@UD6FCGp)Hcw{|@ zov!V%K9r|7U4iD*J90$#&cAcv+3-8zo8i;pW8q!lrm!b?RzLk`fj88{e9PAK9WU|| z---Kt&>Js@N5WHc?#p;G@TU6cN$S09-p%e=Q~iH3s4v7?{ z!&ig%`TtBf8uo>aFbm?huOACr^@=>opFYW#Zwvdw^+DhIVt6flBm73NzjubN%_BT2 zmz%9?9raNIp7RdR$=ikSVBr6!gIqiuP6ahExB98~cDyyN$^Yh&BV(`i0r|5Z`#KZ& z|4@+IS&$QP(1iEwQ@-fRp6dU`XWi|@IeCA3-fzC-SkBu6=omhaxTjaEncC?A)kC!~ zjx?eN@PnEk3^#^%gfoHvZwSYN@30=|nQ!h6j|SfVQ26AWk7fLLcz5u<=V&6h09?72X$~2oDA8pA35fFI^Y@X`o5_-KzfN=|_XTzCFwWFNj5)UkqOh`p@UW zD}g`d!+tk{-vIJyo>l9%2K*+c;?+A&1asXUG|jSUta9-t-*VGn7x{N(f`(zgH?IhO5D>kTd#9-E$^e}eJ<|P zj(Vw^v*ag2`v85R_xc;5eLydmg}vc;pjqBO5Y_^H&@zqi;^E-?i{ZZTOn7O|iy8Uq z{@|RUee)d&`vc9O8{ufMw|m2LK_8G4ezPz2urIN0 zWnYcKv)ygaY`oLBT0d9H zjeMi|H}+od(~o*NOXyV{X}|Py_2NFg(1;$O{`EP3_P4L|!|%iEf@fU2=lfH^nL<~W z17AH9-WBN0JZoVV^bQ`RHP_~GZLW>b^*ry|eejeR#chwYc%^*#UXJY9dt$fdde|St zd?Dyvp9o(JzY#tb9uCKXnCzFYHe1s=ydsxkeajsC!{NYl=6ycg7xXth!oI|)W~0Zp zxmD6Ko#;Xh)silZsMgve+@lp|D=pHlbE`h*o1SO1gg!&RJjEw_!qMPdp)KBbO?y1g z2j()G&sw|!I^`Xs&(?5l^jZA&Qyp$qclD+B&1FxGm-h9n=fkVvtKnC}D?#jg13$=( zbyuzFzVEF8d2EiF!{fPkF}ypxC-9;ECXe>rn%JY-Vbyx|lJ7mIj!BIMtrcJ!Pz&D(t}8^z=~`)h6G(_F2k_~m^)@c-$+`^J;OJ+T}Np6Q-j7uqm? zYa^fS0oKQ|*rt1Ld|q0gYD69Db@$Y99DT3uv_m)bc|BY2)2nCb)##jYug}vP9q9+o zPiKn0;IlbAYn|$(y@+kIysXOCbbqy$u0?Lts5+_N#~QYd<1;){4bcJ} z=?AW9yWXb{&(dAB;=Agfw*6)>@&ZqEF8UG&8sl#5zk3lEAMn0D|@OJ-$vG>`Lp3Y;nU$a z!)xKg;ql;m{lR(7kLIzDt;Aw&&(?ePs^|4@joWi=jc0t?BSed8%GI zZuWe2)jq(_^)zj)(t-EP+cm12YKWfbXQvwK{B&q;(5>^1ulQ>g_}Xvp`@+lNwZQuy z2|RgA;0u4F`|Zn%yd%EuWwSi>J8{^n-0&SAId@Nodjs$LjUk>r!S_A?#SDGN+H$nj zwLNETh*=)QppVr5_O%`k1n2zz`RJPe^#u3)ouX%Xx3v~a^V?cl$9mS@YMjl}H13X< zU(Y;NTk5{30pD#Umg=OM>A015$7jY^Xp`3Yo|pK|@BHmbLb=dZ*IS7{I=N>W}*4vf4z_Yedli#F?m;B<@ZYd-z=`_yY1ES`ypR6 zR$o+iuUFh%nR)__gp__?7Un@Nn=ue1G5t@0!D0Jj27h z+}@Ge9S}6(i_5~;py;5u%_SM*3_$d{u`bB_GE9?-O8HdGio6Q@AJR8>VIcC zZ;Pp;81$h7LB9B34&;>o&7lu2p8fJ-o#w{6V;s}OH-&hgoI^}kq}2YciDdVli& zAKqH-C5WZ`2U{pWOyOy0nYtT%s)Sq@!r7yw7(vliL}or{S9ud zt(?R4oime{&9gt84iARQ;el{z&TScQ37*k=^b5Zkyw7LmyP7teT@kq7JH`Vi6t`7vhyE*jV z+dq=)&xBXQbK&7|FCP@SOACc}CASgge87!CC*w!2ft(cy~A# zPJ}(ddw#E*%e>AzYc-C|a?20m;T66zkF%7w&xSj~z2Uz3cvnWf=homkvGbdCd93H^ zW^1ncuK5u^&yN0g4LP$O|MR!z00Yd{c1td(Ry8 zzIAAOEBg|=*u>*~@rcE|;m+ zGg@_S@0oKXqciu;;LOKI0`Gq+d?@G%X9I7}0#oj(hDrW~#XzX#-3T%M74 zy~#X!O8qa!MgMO#Zx(|eBtcp z6LCAsA#U-B^ZIaScrft4Yu@KG9^)^5HJ>=HHm{9|Pt7N(~CTWZ}iOQ|K_UoYwfMQo&2rZ!*u`edqi_Oj*IyjzZ+}P z>s2-Bd!DP_`cB6_v$&?uzU#a6Mi=#RuZ_NgHM&kU>U$ccNB-e`TBAAsZl%SGveM@Cv$hcepk&@)8qTD$M+ZC?K{;M zZGpEyZ*YM71zO_^+H*G1<9fI$@c&)m>F}QLQg}IhaQ^wpj1Pnxf_`x@ z@O{sFe&l<8v$h!Qule4}X#etI-}Yi2m=pG(huMezwKv=v?g{sYcLw+D(R1FBFZq$f z#$^8HeJgQ!#`CSE*v4F&8`gof#V5ynFHfWQ-LL=kWi=982VPeXCaJ{XRF2{cce&YTbL|Jbic6nP+*RzUO7|8ts72eRjW}d)@PxXL|P29(~f~ zk#HisE8G?M|M~D@cp>opyKnolm+ryu zYTmyX?hSW_Q-QZU<5_v(t@^TgH@{qOCqBxh^C^&qjxQSlja=6f}1 z-ptXs8k_jacJnwrzq$6B*N*kx_<6iutbhG$Y#%Uop7D9l&gOt>dJ72X@3pN|h^bpN)%UwebI%shI?=r!@# zi}>xo`P+^bz2jZEG*3On|J|?O94Et_f%h+j8-qQKe}jmJ|9Mg#Vb1#OYT_E}Dn|QV ztdZJ?gU_2M*YahA@9jsAvPbhYuI6nVw-f91?Do80&8h7w)COu^ZH~UBEA;!0?o}hB zPH1w32K0vNw0**T8bl5PY1!{h=Z+6HxEB6I)>jg)GJ|{2w zqje+g2V-5WX?=0DFB{cJeDc3)}jc3KSl~~4S#8tdMYGJIcJgASn z@HbqmSNnj`XK{+JIWuST*0shMrt9t3ZB@>i-;VOWhi^&R0txHH@x9tih`N5aGN@rjI&ho=Jnzh};) z8Se?+y*cpz{!pKd=NP@)hyBS5ZSS_$6oV<^WT}cCmag= z&mU*Q9pO^AJcrNAlk;mM&pYRR=h=G#Z<*(Ya5Nm4^UpK#p|v4iKC>@)B1{E zjm4p6kT2f1FQ0wy-TJ>eqrIKRImSF)uhwJhX_WWUT6C=UjkE-H0}Vj^i@LfVwK3|W z{-*;z?sst1_IeCAa*>+IAE)k$^G+IN%>yg69EJwQD6BtA9Q7aE&)+n;ul$DQu&2l0LN zEcJf%s1`-NOZ`9g0QFW+b*;CJ?)k3xj6OpPJVobzzw+}e@Lt)IYxilKAKiBr8{In= zj)YqRpIi+04qVFhU4du#{B$@OP6Y4n3$tKueMev6CB3Fpv(@yd*XHj{d49XdwY>6- zxvj$^)|C%^NX%k~-z9MN+w0BYP&g3eL?7j|)?w^V;@ob0i_e>bCd91ofq%zqpG#}a z|Mmvg;-iVaTWy(pC;42Aclvzgnm6CZMenQ2W<8+wSIbeq?endBYwj7EP=EBdG|%zh z{=knkeSJ6_js;%jaie=U8jc6|orC<(8yCZ+aCf*6&Idl>d2@MhE$AtHuKyJA@Sga^ zCtf2Tf$t$Fa&@)(s*mJP?tVO2hd-?Ex5g~Ug*@m1_NNCp_v?N4?f*cqAMrPC^P0b& zy4qO#-l&h!|JEqYkI(X@)^e!+MSS)vzA>lOnsvJ7G`>E$kn_d*s>fK*UiW@`w>qk` z`Z{}fhYo4DznOjJG5Rj`8t*&1`2S#_XTGNUQ{l$IzdTO=wD0_@-`zVG?g;0@UExx= zJ6s57gLhAd{d0IlY+?{E&-L7_f9#1qY1!WE`R3|swAmcXq5R1mf5=I5XB3a#Brp5I ziD19>ZY=gCR~x}QYG6LGi@PyjO{|OWjIqmy`HRns$elRa+r(DyyKnE!t$Z|A>nyH4 zeZICisiu4W*W+qRr}g@Ht-hr_zTT%@_zipA{QBoJqJ81#=HJoyTG$&7gu~%v;MaPe z?wxylk27=5WyGD~Vz_HQUdVVpxX&ZdOZEh7iL2fhGtZ6Qu`heIf4am@b?JTc$SvJl zqyD#c(F=H4p2T`E91Z$mdw^WHwkLhlv))q&bBf(MyAgN2H^#r{b!#o&6N6azUtD8f zb>IFM{cnw(=DG1s(1Y zP@r}Gr)~a+-?>Kj>Sz6d51jwJa3<&tXM=lZLVJk$%)K{=OME;wdcXRtFX?&o&`xzJ zxAjTakXL!EKje^)n@73ZAN(fr8_52;U%v^Q`|_xNnZsP|Th_AXPUGgIYNLBFclWY5 z${Wqp|6*znsAlZn_nz~-KDAr1ig#jrIZ)r$xM!5QsuAjY^=mziG>1jq@)uq5KfkX9 zS~c#SUmJUUAlIY+opn45p7)stj)lYFVBiZJ4c<4p?-|ebGv9k|PY{pToR>VtYY;z; zV$`M_^bFmpkLcT6^giZP{@Qzt?W6tuByaYl$LV+a0PoA)b;0xIP!F-2U)r-?>+z5b4|;8!oE3t3%)%Rc$U_W2cD<%dfzkN^S(a9%hoV|*D_k0cdYGNJoP-^ zJNLzl`rkg5`Cl$ZA6>0RtO&fT$|UmV3^e{?gB?vLIdNk`&~CyS{4Nqpz&L z)mRsw>;BsZJDR^*vF3>Ui3f5zj=rO-#%aEt#J3pl^m+TWZjFtjPSr`h)R4~59zd%! z$Y0L>dY>)Aw~i@Kz3`PdCTjd_(*SaUI-a%pdJaxC=RzcJVHAqUR=(fe20!%qDr_QqfD zk7Ij)vDb20>~qX-_wU)xWB%R9^EAiR=W(&l`g8rSeso5!%QRd6``zkHrcphhzXOaT z{a&x=Gwltt+#6ZXXS9EK-g~^F2i5cSwz1ys*fXe_T;}y_rN`!V^u{vp%b~q(1n2yz z;M_kMjs`i9Q+w`S7x!16^(^Ku>+jST-Gi~7HQLi6UAsQ7hWdFt_jfD4o$haX|L-)? zI=9AZ+xpQKA8n-%vzaiZ79-M`}?&ulsI0MIXeU&GwJ7YcA z{Z)V6_ipQ;erX=X(BD$x?Qeeh(Es)Z=l;vruG)c?j-vjk`{chxK zc+T_iZr?YKzVKYuPtb z=VA|_H&lPsHgDFa&UDbZbGx5?&jV#V!+PCu)cWZ0`o8P-xyH5l8^4;{ZBH!fuDM&~ z`}R7y@SA8Y>^~3Jf*!Rxp6UH-m&4X+tTBBR{u;g3&($V>^7rET&ii_V`_*rMTXc@j+p)c1D`V%X7e+r^ zE6pwD%G%-(uRS&w@?r1#p}&RJ!rrhy91T3L4;T-GJz+igcha8!@@Br~uKw?S`R(=C z=Q6K14;}kVKX;sbd+mI?o4IYhT1VRAr$yQm+^>g6{nIGTmiCBh*R$J2Zy49=`}IM` z#@@Z`W`4|NUTZWr))kArb>DnnoZtNK-!}a2Ul04jp@D|VRy=5=xOeQ6B+Z0x;h{Qk*B>(^RW>($({vHmkwqxFT)@Llnp>yEu| zH2)&w{XW}g#8(>gwa}h9&EI@94twLxp8f56dI10W?eF)z|E94X_Jjk0=Z(evSDW2O^{#Z? zn6I?AtNFfjci-Z)5B-b((H_v>JM}*w^FLqr{};74*L=T!j?WvxZv=lU_}jnUH+TD_ zb(?oFUCmy$@=o{L(Y|}VY~0G6(=*quUe_wOi?yx?d%vH@vEDV(>t^uK_we)HNr zUuhikG+nVW^S!X=?y0fsUp$IM|NA?@zyJCD z@4tuoo8I}q5qMq?aJ?SfAHCl>SDpKEb1n0+nEPd)ujTsF+}F0gJI(JhJ=C{G-;bYp zyPxaxRb$`p8as`3H{Ne7ViPz2^Zv5)zkdhtfB*jsyzjUDEbN)HG5_q`_iugvTZwmi z=5OYG@$AOFR?X4uh&}BI^Tb}t{$(&_(toY zd*Z#G`&T;q`!~Nc-M{(yn}0UI_wM)q5^Bcj&|N3gt`#tabd%ynY{r=YX?|l9L`t-l`@p?V44~+l6 z9)A~%zxmBQGJ3!LXY{@pCoN1lm^83E8mRYs&W`7OeOcdkw>`fLocsEP_q=buu3@bAd-jW^aW&4V?z>a>DUVYg$Mc@gdfwKD^=5rt&zy0g~t~HL0WAgu`(Wy^e8x8b4Z4apDMjtNoe}DHo z!~5U(oc;c;cm5Z?hx?=dy*K{tKd#ppCf{G1nonMu@>&hl|NVVEp8xze`m%k%*xvv= z>@4r!{{8QL{9ey@^quF>-~Rn~|E{x{_r=<{r*X=~q=B7lVDx`~1B^YOzUiGzX5pM?|?4b)d1m;DB)?-$4V zzyJQ{H-LWw9R2UQ)cc<4+0XZ*=UtC6G_J-wjgxjK4eYK4>M`T!z0sG8-uIbz^#JGi z;@<$gUi>!!=nwsyzyFRnt}(9dx^YeQ-ragn`JD1e1NB$OMgNW7>~;GGyuuZY&)qtxj5_uZ}gl+PdRd@lOS=h1)l{o=^0^?m=%pP&8a@4x-^Z-3r*jh^}acY^wU zam{6MO4V zu8js(d2aM&ulpN)@$B#4{QV8U?`8D8UNQdr{;IXd`%~SoP2H!wPI(;v-OvwzY5_4erh z_Jw|D?0bvzPPKjgYdht9%K6o5VDxW2ZS21dE{@|fefMhDp5B{uJ89sJ*Fb&TarF4& zb)V@t)$fh3-&BLC2D_nwdV1Bk8*5B+Oj?~ZFlk`Yz@&jm1Cs_O4NMxCG%#sk(!ivF zNduDxCJjs)m^3hHVA8;(fk^|C1||(m8kjUNX<*X8q=88TlLjUYOd6OpFlk`Yz@&jm z1Cs_O4NMxCG%#sk(!ivFNduDxCJjs)m^3hHVA8;(fk^|C1||(m8kjUNX<*X8q=88T zlLjUYOd6OpFlk`Yz@&jm1Cs_O4NMxCG%#sk(!ivFNduDxCJjUb{okXPAG`4QrAID3 Q_`tCzFJ8Lu!tpo#Uw~f)jQ{`u literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/images/spinner2.tga b/code/gamespy/gt2/gt2action/images/spinner2.tga new file mode 100644 index 0000000000000000000000000000000000000000..550b672b861d2c9d10fd7846e0f369ca690aa3c3 GIT binary patch literal 262188 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 WKmYjmfBUEZ{g*%f_0RwOr@sK-GKwhx literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/messages.txt b/code/gamespy/gt2/gt2action/messages.txt new file mode 100644 index 00000000..5f1122aa --- /dev/null +++ b/code/gamespy/gt2/gt2action/messages.txt @@ -0,0 +1,49 @@ +CLIENT-to-server messages: +=initial-data + byte[3] color + string nick +=update + unsigned short id + unsigned short packedRotation + bit forward + bit backward + bit right + bit left +=press + string button +=chat + string message + +SERVER-to-client messages: +=addclient + byte index + string nick +=delclient + byte index +=start + byte index +=update + unsigned short id + byte numclients + byte numobjects + for each client + byte index + unsigned short[2] packedPosition + unsigned short packedRotation + int score + bit forward + bit backward + bit right + bit left + bit dead + for each object + byte type + unsigned short[2] packedPosition + unsigned short packedRotation + int time +=chat + string message +=sound + byte index +=numasteroids + byte total diff --git a/code/gamespy/gt2/gt2action/sounds/die.wav b/code/gamespy/gt2/gt2action/sounds/die.wav new file mode 100644 index 0000000000000000000000000000000000000000..68fcdda1e040b52d0639c47299d9582dc72ccde1 GIT binary patch literal 49260 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�BV*;Ja;4K$97l;Is-$g=95!{Ohcer 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Ð_uJ})dECa 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 SrZ*o{Tr#7ed6)KG!T$g+((riz literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/sounds/pickup.wav b/code/gamespy/gt2/gt2action/sounds/pickup.wav new file mode 100644 index 0000000000000000000000000000000000000000..a00a969f79733f3815e12a2c96d888a1eb9bfb2f GIT binary patch literal 8204 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 VS;V-EtogGd#?AbA_Pp5u{3muJyVn2! literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/sounds/rocket.wav b/code/gamespy/gt2/gt2action/sounds/rocket.wav new file mode 100644 index 0000000000000000000000000000000000000000..dd3102fdb5c6758d21fb7d083d8c32dbd2db77fb GIT binary patch literal 23902 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 Vg7^&c?4n8W8F`=PPtPZa{{t73RI300 literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/todo.txt b/code/gamespy/gt2/gt2action/todo.txt new file mode 100644 index 00000000..56c11da2 --- /dev/null +++ b/code/gamespy/gt2/gt2action/todo.txt @@ -0,0 +1,23 @@ +- add S->C event support ("\event\explosion\pos\280 640") +- save settings (nick, color, prediction, etc.) +- improve comments +- slow bouncy lasers +- map/walls +- safe spawns +- better feedback for dedicated server (not just "connecting") +- sound doesn't worked when launched from debugger but not debugging (Ctrl-F5) +- add bytes/sec to net graph +- write filter to analyze byte frequencies +- show message on server shutdown +- only send changed values for players/objects +- assert, darray.c, line 120 +- do prediction at start of frame, then use that for drawing and radar +- do adjustments based on when a server update should have arrived? +- second background moving at different speed? +- position smoothing causes weirdness when respawning +- cleaner menu setup system? +- random number and positions for spinner +- AddRocket, AddMine, etc. +- smarter server updates + - for each object, determine what values are dynamic + - pre-determine what values will be sent for each object-type diff --git a/code/gamespy/gt2/gt2hostmig/gt2hostmig.c b/code/gamespy/gt2/gt2hostmig/gt2hostmig.c new file mode 100644 index 00000000..0b4e6cf4 --- /dev/null +++ b/code/gamespy/gt2/gt2hostmig/gt2hostmig.c @@ -0,0 +1,674 @@ +/****** +gt2hostmig.c +GameSpy Transport 2 SDK +GameSpy Query & Reporting 2 SDK + +Copyright 2000 GameSpy Industries, Inc + +****** + + This sample demonstrates the use of the Transport 2 SDK to do host migration. + It also uses the Query & Reporting 2 SDK to report the host to the + Master Server. + + Please see the GameSpy Transport 2 SDK documentation for more + information + +******/ + + +/************* +** INCLUDES ** +*************/ +#include +#include "../gt2.h" +// Needed for the QR2 portion of code +#include "../../qr2/qr2.h" + +/************ +** DEFINES ** +************/ +#define PORT 12345 +#define MAX_CLIENTS 8 +#define TIMEOUT (30 * 1000) +#define SEND_TIME 500// (10 * 1000) + +#define REPORT + +/************ +** GLOBALS ** +************/ +GT2Bool hosting; +GT2Bool quit; +GT2ConnectionCallbacks connectionCallbacks; +char messageKey[128]; +char messageValue[128]; +unsigned int backupHost; +int backupHostIndex; +GT2Bool gotBackupHost; + +#ifdef REPORT +//////////////////////////////////////////////////// +//////////////////////////////////////////////////// +// Defines for QR2 +#define QR2_GAME_VERSION _T("2.00") +#define QR2_GAME_NAME _T("gmtest") +#define QR2_MAX_PLAYERS MAX_CLIENTS +#define QR2_BASE_PORT 11111 +#define QR2_HOSTNAME _T("My Host") +// end defines +#endif + +GT2Connection clients[MAX_CLIENTS]; +int numClients; + +/********************** +** UTILITY FUNCTIONS ** +**********************/ +GT2Bool ParseMessage +( + const char * message, + int len +) +{ + const char * keyStart; + const char * valueStart; + int copyLen; + int i; + + // Validate the message. + for(i = 0 ; i < (len - 1) ; i++) + { + if(!message[i]) + return GT2False; + } + if(message[i]) + return GT2False; + if(message[0] != '\\') + return GT2False; + + // Find the key/value starts. + keyStart = &message[1]; + valueStart = strchr(keyStart, '\\'); + if(!valueStart) + return GT2False; + valueStart++; + + // Copy in the key. + copyLen = (valueStart - keyStart - 1); + if(copyLen >= sizeof(messageKey)) + return GT2False; + memcpy(messageKey, keyStart, copyLen); + messageKey[copyLen] = '\0'; + + // Copy in the value. + if(strlen(valueStart) >= sizeof(messageValue)) + return GT2False; + strcpy(messageValue, valueStart); + + return GT2True; +} + +const char * GetConnectionName +( + GT2Connection connection +) +{ + return gt2AddressToString(gt2GetRemoteIP(connection), 0, NULL); +} + +void TellBackupHost +( + int index +) +{ + char buffer[32]; + GT2Connection connection = clients[index]; + + // Let him know who the backup host is. + sprintf(buffer, "\\backuphost\\%s", gt2AddressToString(backupHost, 0, NULL)); + gt2Send(connection, (const GT2Byte *)buffer, -1, GT2True); +} + +void NewBackupHost +( + int index +) +{ + int i; + GT2Connection connection = clients[index]; + + // Let him know. + gt2Send(connection, (const GT2Byte *)("\\backuphost\\"), -1, GT2True); + backupHost = gt2GetRemoteIP(connection); + backupHostIndex = index; + + // Let others know. + for(i = 0 ; i < MAX_CLIENTS ; i++) + { + if(clients[i] && (i != index)) + TellBackupHost(i); + } +} + +/****************** +** QR2 CALLBACKS ** +******************/ +#ifdef REPORT + +void server_key_callback(int keyid, qr2_buffer_t outbuf, void *userdata) +{ + switch (keyid) + { + case HOSTNAME_KEY: + qr2_buffer_add(outbuf, QR2_HOSTNAME); + break; + case GAMEVER_KEY: + qr2_buffer_add(outbuf, QR2_GAME_VERSION); + break; + case HOSTPORT_KEY: + qr2_buffer_add_int(outbuf, PORT); + break; + case NUMPLAYERS_KEY: + qr2_buffer_add_int(outbuf, numClients); + break; + case MAXPLAYERS_KEY: + qr2_buffer_add_int(outbuf, QR2_MAX_PLAYERS); + break; + + default: + qr2_buffer_add(outbuf, _T("")); + } + + GSI_UNUSED(userdata); +} + +// Called when a player key needs to be reported +void playerkey_callback(int keyid, int index, qr2_buffer_t outbuf, void *userdata) +{ + //check for valid index + if (index >= numClients) + { + qr2_buffer_add(outbuf, _T("")); + return; + } + switch (keyid) + { + case PLAYER__KEY: + qr2_buffer_add(outbuf, GetConnectionName(clients[index])); + break; + default: + qr2_buffer_add(outbuf, _T("")); + break; + } + + GSI_UNUSED(userdata); +} + +// Called when a team key needs to be reported +void teamkey_callback(int keyid, int index, qr2_buffer_t outbuf, void *userdata) +{ + qr2_buffer_add(outbuf, _T("")); + + GSI_UNUSED(userdata); + GSI_UNUSED(index); + GSI_UNUSED(keyid); +} + +// Called when we need to report the list of keys we report values for +void keylist_callback(qr2_key_type keytype, qr2_keybuffer_t keybuffer, void *userdata) +{ + //need to add all the keys we support + switch (keytype) + { + case key_server: + qr2_keybuffer_add(keybuffer, HOSTNAME_KEY); + qr2_keybuffer_add(keybuffer, GAMEVER_KEY); + qr2_keybuffer_add(keybuffer, HOSTPORT_KEY); + qr2_keybuffer_add(keybuffer, NUMPLAYERS_KEY); + qr2_keybuffer_add(keybuffer, MAXPLAYERS_KEY); + break; + case key_player: + qr2_keybuffer_add(keybuffer, PLAYER__KEY); + break; + // no team keys are added since there is no team play in this game + case key_team: + break; + } + + GSI_UNUSED(userdata); +} + +// Called when we need to report the number of players and teams +int count_callback(qr2_key_type keytype, void *userdata) +{ + if (keytype == key_player) + return numClients; + else if (keytype == key_team) + // 0 = Zero teams + return 0; + else + return 0; + + GSI_UNUSED(userdata); +} + +// Called if our registration with the GameSpy master server failed +void adderror_callback(qr2_error_t error, gsi_char *errmsg, void *userdata) +{ + _tprintf(_T("Error adding server: %d, %s\n"), error, errmsg); + + GSI_UNUSED(userdata); +} + +#endif + +/********************* +** SOCKET CALLBACKS ** +*********************/ +void ConnectAttemptCallback +( + GT2Socket socket, + GT2Connection connection, + unsigned int ip, + unsigned short port, + int latency, + GT2Byte * message, + int len +) +{ + int i; + + // Do we have a spot open? + for(i = 0 ; i < MAX_CLIENTS ; i++) + { + if(!clients[i]) + break; + } + if(i == MAX_CLIENTS) + { + // Nothing open, reject. + gt2Reject(connection, (const GT2Byte *)("Server full"), -1); + printf("Rejected client, server full\n"); + return; + } + + // We have a spot. + if(!gt2Accept(connection, &connectionCallbacks)) + return; + + // Add him to the list. + clients[i] = connection; + numClients++; + + // Store the index in the connection data. + gt2SetConnectionData(connection, (void *)i); + + printf("Connection accepted from %s\n", GetConnectionName(connection)); + + // Is this the only client? + if(numClients == 1) + { + // He's the backup host. + NewBackupHost(i); + } + else + { + // Let him know who the backup host is. + TellBackupHost(i); + } + + GSI_UNUSED(len); + GSI_UNUSED(message); + GSI_UNUSED(latency); + GSI_UNUSED(port); + GSI_UNUSED(ip); + GSI_UNUSED(socket); +} + +void SocketErrorCallback +( + GT2Socket socket +) +{ + printf("socket error!\n"); + quit = GT2True; + + GSI_UNUSED(socket); +} + +/************************* +** CONNECTION CALLBACKS ** +*************************/ +void ConnectedCallback +( + GT2Connection connection, + GT2Result result, + GT2Byte * message, + int len +) +{ + if(result != GT2Success) + { + printf("Connection failed: "); + if(result == GT2OutOfMemory) + printf("OutOfMemory\n"); + else if(result == GT2Rejected) + printf("Rejected: %s\n", message); + else if(result == GT2NetworkError) + printf("NetworkError\n"); + else if(result == GT2AddressError) + printf("AddressError\n"); + else if(result == GT2DuplicateAddress) + printf("DuplicateAddress\n"); + else if(result == GT2TimedOut) + printf("TimedOut\n"); + else if(result == GT2NegotiationError) + printf("NegotiationError\n"); + else + printf("Unknown error\n"); + + quit = GT2True; + return; + } + + printf("Connection accepted\n"); + + GSI_UNUSED(len); + GSI_UNUSED(connection); +} + +void ReceivedCallback +( + GT2Connection connection, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + if(!ParseMessage((const char *)message, len)) + { + printf("Error parsing incoming message from %s\n", GetConnectionName(connection)); + return; + } + + // Check for quit. + if(strcasecmp(messageKey, "quit") == 0) + { + printf("Got remote quit: %s\n", messageValue); + quit = GT2True; + return; + } + + // Are we the host? + if(hosting) + { + // Check for echo. + if(strcasecmp(messageKey, "echo") == 0) + { + // Send back the same. + gt2Send(connection, message, len, reliable); + return; + } + } + else + { + // Check for echo reply. + if(strcasecmp(messageKey, "echo") == 0) + return; + + // Check for a backup-host. + if(strcasecmp(messageKey, "backuphost") == 0) + { + printf("New backup host: %s\n", messageValue); + + // Get the backup IP. + gt2StringToAddress(messageValue, &backupHost, NULL); + gotBackupHost = GT2True; + return; + } + } + + // This is an unknown message type. + printf("Unknown message key: %s = %s\n", messageKey, messageValue); +} + +void ClosedCallback +( + GT2Connection connection, + GT2CloseReason reason +) +{ + // Are we the host? + if(hosting) + { + int i; + + // Get the index. + i = (int)gt2GetConnectionData(connection); + + // Clear it from the list. + clients[i] = NULL; + numClients--; + + // Was this the backup host? + if(i == backupHostIndex) + { + // Pick a new backup host. + if(numClients) + { + for(i = 0 ; i < MAX_CLIENTS ; i++) + if(clients[i]) + { + NewBackupHost(i); + break; + } + } + } + + printf("Client connection with %s closed: %d\n", GetConnectionName(connection), reason); + } + else + { + printf("Connection closed\n"); + + // Quit out of here, we'll check for a backup host in main(). + quit = GT2True; + } +} + +/************** +** FUNCTIONS ** +**************/ +GT2Bool StartHosting +( + void +) +{ + qr2_error_t rcode; + GT2Socket socket; + GT2Result result; + + // Clear. + quit = GT2False; + hosting = GT2True; + numClients = 0; + memset(clients, 0, sizeof(GT2Connection) * MAX_CLIENTS); + + + // create the socket + result = gt2CreateSocket(&socket, gt2AddressToString(0, PORT, NULL), 0, 0, SocketErrorCallback); + if(result != GT2Success) + { + printf("Failed to create socket (%d)\n", result); + return GT2False; + } + + // start listening + printf("Starting to listen...\n"); + gt2Listen(socket, ConnectAttemptCallback); + +#ifdef REPORT + // Start reporting. + printf("Starting reporting...\n"); + + rcode = qr2_init(NULL, NULL, QR2_BASE_PORT, "gmtest", "HA6zkS", 1, 1, server_key_callback, playerkey_callback, + teamkey_callback, keylist_callback, count_callback, adderror_callback, NULL); + + if(rcode != e_qrnoerror) + return GT2False; +#endif + + // Think. + while(!quit) + { + gt2Think(socket); + +#ifdef REPORT + qr2_think(NULL); +#endif + msleep(10); + } + +#ifdef REPORT + qr2_shutdown(NULL); +#endif + + return GT2True; +} + +GT2Bool StartConnecting +( + const char * server +) +{ + GT2Connection connection; + GT2Socket socket; + GT2Result result; + char address[256]; + unsigned long lastSendTime = 0; + unsigned long now; + + // Clear. + quit = GT2False; + hosting = GT2False; + gotBackupHost = GT2False; + + // Setup the address with the port. + sprintf(address, "%s:%d", server, PORT); + + // create the socket + result = gt2CreateSocket(&socket, NULL, 0, 0, SocketErrorCallback); + if(result != GT2Success) + { + printf("Error creating the socket! (%d)\n", result); + return GT2False; + } + + // Connect to the host. + printf("Connecting to %s (timeout after %d seconds)...\n", address, TIMEOUT / 1000); + result = gt2Connect(socket, &connection, address, NULL, 0, TIMEOUT, &connectionCallbacks, GT2True); + if(result != GT2Success) + { + printf("Error creating the connection! (%d)\n", result); + return GT2False; + } + + // Think. + while(!quit) + { + // Check for sending a new message. + now = current_time(); + if((now - lastSendTime) > SEND_TIME) + { + // Send an echo message. + gt2Send(connection, (const GT2Byte *)("\\echo\\Hello."), -1, GT2False); + + // New last send time. + lastSendTime = now; + } + + // Think. + gt2Think(socket); + + // Yield. + msleep(10); + } + + return GT2True; +} + +int main +( + int argc, + char ** argv +) +{ + // Set the callbacks. + memset(&connectionCallbacks, 0, sizeof(GT2ConnectionCallbacks)); + connectionCallbacks.connected = ConnectedCallback; + connectionCallbacks.received = ReceivedCallback; + connectionCallbacks.closed = ClosedCallback; + + // Host if no args. + if(argc < 2) + { + if(!StartHosting()) + printf("Failed to start hosting\n"); + } + else + { + // Do the initial connect. + if(!StartConnecting(argv[1])) + { + printf("Failed to connect\n"); + } + else + { + // Do host migration stuff. + GT2Bool done = GT2False; + do + { + // Do we have a backup host? + if(gotBackupHost) + { + // Are we the backup host? + if(!backupHost) + { + printf("Starting up backup host...\n"); + + // Start hosting. + if(!StartHosting()) + { + printf("Failed to start backup host.\n"); + done = GT2True; + } + } + else + { + const char * address = gt2AddressToString(backupHost, 0, NULL); + + printf("Connecting to backup host %s...\n", address); + + // Start connecting to the new host. + if(!StartConnecting(address)) + { + printf("Failed to connect to backup host.\n"); + done = GT2True; + } + } + } + else + { + printf("No backup host, quitting\n"); + done = GT2True; + } + } + while(!done); + } + } + + getchar(); + + return 0; +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2nat/gt2nat.c b/code/gamespy/gt2/gt2nat/gt2nat.c new file mode 100644 index 00000000..b5473537 --- /dev/null +++ b/code/gamespy/gt2/gt2nat/gt2nat.c @@ -0,0 +1,408 @@ +/****** +gt2nat.c +GameSpy Transport 2 SDK + +Copyright 2000 GameSpy Industries, Inc + +****** + + This sample demonstrates sharing a UDP socket with the Query & Reporting 2 SDK + to enable developers to create games that can be hosted behind a NAT. + + Please see the GameSpy Query & Reporting 2 SDK documentation for more + information + +******/ + + +/******** +INCLUDES +********/ +#include "../gt2.h" +#include "../../qr2/qr2.h" + +#include +#include +#include + +#if defined(_WIN32) && !defined(UNDER_CE) +#include +#endif + +/******** +DEFINES +********/ +#define QR2_GAME_VERSION "2.00" +#define QR2_GAME_NAME "gmtest" +#define QR2_MAX_PLAYERS 32 +#define QR2_BASE_PORT 26900 +#define QR2_RANKINGSON_KEY 100 + +#ifdef _WIN32_WCE +void RetailOutputA(CHAR *tszErr, ...); +#define printf RetailOutputA +#endif + +/******** +TYPDEFS +********/ +//representative of a game player structure +typedef struct +{ + char pname[80]; + int pfrags; + int pdeaths; + int pskill; + int pping; + char pteam[80]; +} player_t; + +//representative of a game data structure +typedef struct +{ + player_t players[QR2_MAX_PLAYERS]; + char mapname[20]; + char hostname[120]; + char gamemode[200]; + char gametype[30]; + int locationid; + int numplayers; + int maxplayers; + int fraglimit; + int timelimit; + int teamplay; + int rankingson; + int hostport; +} gamedata_t; + +/******** +GLOBAL VARS +********/ + +//just to give us bogus data +char *constnames[QR2_MAX_PLAYERS]={"Joe Player","L33t 0n3","Raptor","Gr81","Flubber","Sarge","Void","runaway","Ph3ar","wh00t","gr1nder","Mace","stacy","lamby","Thrush"}; +gamedata_t gamedata; + +// Called when a server key needs to be reported +void serverkey_callback(int keyid, qr2_buffer_t outbuf, void *userdata) +{ + switch (keyid) + { + case HOSTNAME_KEY: + qr2_buffer_add(outbuf, gamedata.hostname); + break; + case GAMEVER_KEY: + qr2_buffer_add(outbuf, QR2_GAME_VERSION); + break; + case HOSTPORT_KEY: + qr2_buffer_add_int(outbuf, gamedata.hostport); + break; + case MAPNAME_KEY: + qr2_buffer_add(outbuf, gamedata.mapname); + break; + case GAMETYPE_KEY: + qr2_buffer_add(outbuf, gamedata.gametype); + break; + case NUMPLAYERS_KEY: + qr2_buffer_add_int(outbuf, gamedata.numplayers); + break; + case MAXPLAYERS_KEY: + qr2_buffer_add_int(outbuf, gamedata.maxplayers); + break; + case GAMEMODE_KEY: + qr2_buffer_add(outbuf, gamedata.gamemode); + break; + case TEAMPLAY_KEY: + qr2_buffer_add_int(outbuf, gamedata.teamplay); + break; + case FRAGLIMIT_KEY: + qr2_buffer_add_int(outbuf, gamedata.fraglimit); + break; + case TIMELIMIT_KEY: + qr2_buffer_add_int(outbuf, gamedata.timelimit); + break; + case QR2_RANKINGSON_KEY: + qr2_buffer_add_int(outbuf, gamedata.rankingson); + break; + default: + qr2_buffer_add(outbuf, _T("")); + } + + GSI_UNUSED(userdata); +} + +// Called when a player key needs to be reported +void playerkey_callback(int keyid, int index, qr2_buffer_t outbuf, void *userdata) +{ + //check for valid index + if (index >= gamedata.numplayers) + { + qr2_buffer_add(outbuf, _T("")); + return; + } + switch (keyid) + { + case PLAYER__KEY: + qr2_buffer_add(outbuf, gamedata.players[index].pname); + break; + case SKILL__KEY: + qr2_buffer_add_int(outbuf, gamedata.players[index].pskill); + break; + case SCORE__KEY: + qr2_buffer_add_int(outbuf, gamedata.players[index].pfrags); + break; + case DEATHS__KEY: + qr2_buffer_add_int(outbuf, gamedata.players[index].pdeaths); + break; + case PING__KEY: + qr2_buffer_add_int(outbuf, gamedata.players[index].pping); + break; + case TEAM__KEY: + qr2_buffer_add(outbuf, gamedata.players[index].pteam); + break; + default: + qr2_buffer_add(outbuf, _T("")); + break; + } + + GSI_UNUSED(userdata); +} + +// Called when a team key needs to be reported +void teamkey_callback(int keyid, int index, qr2_buffer_t outbuf, void *userdata) +{ + qr2_buffer_add(outbuf, _T("")); + + GSI_UNUSED(userdata); + GSI_UNUSED(index); + GSI_UNUSED(keyid); +} + +// Called when we need to report the list of keys we report values for +void keylist_callback(qr2_key_type keytype, qr2_keybuffer_t keybuffer, void *userdata) +{ + //need to add all the keys we support + switch (keytype) + { + case key_server: + qr2_keybuffer_add(keybuffer, HOSTNAME_KEY); + qr2_keybuffer_add(keybuffer, GAMEVER_KEY); + qr2_keybuffer_add(keybuffer, HOSTPORT_KEY); + qr2_keybuffer_add(keybuffer, MAPNAME_KEY); + qr2_keybuffer_add(keybuffer, GAMETYPE_KEY); + qr2_keybuffer_add(keybuffer, NUMPLAYERS_KEY); + qr2_keybuffer_add(keybuffer, NUMTEAMS_KEY); + qr2_keybuffer_add(keybuffer, MAXPLAYERS_KEY); + qr2_keybuffer_add(keybuffer, GAMEMODE_KEY); + qr2_keybuffer_add(keybuffer, TEAMPLAY_KEY); + qr2_keybuffer_add(keybuffer, FRAGLIMIT_KEY); + qr2_keybuffer_add(keybuffer, TIMELIMIT_KEY); + break; + case key_player: + qr2_keybuffer_add(keybuffer, PLAYER__KEY); + qr2_keybuffer_add(keybuffer, SCORE__KEY); + qr2_keybuffer_add(keybuffer, SKILL__KEY); + qr2_keybuffer_add(keybuffer, DEATHS__KEY); + qr2_keybuffer_add(keybuffer, PING__KEY); + qr2_keybuffer_add(keybuffer, TEAM__KEY); + break; + case key_team: + break; + } + + GSI_UNUSED(userdata); +} + +// Called when we need to report the number of players and teams +int count_callback(qr2_key_type keytype, void *userdata) +{ + if (keytype == key_player) + return gamedata.numplayers; + else if (keytype == key_team) + return 0; + else + return 0; + + GSI_UNUSED(userdata); +} + +// Called if our registration with the GameSpy master server failed +void adderror_callback(qr2_error_t error, gsi_char *errmsg, void *userdata) +{ + _tprintf(_T("Error adding server: %d, %s\n"), error, errmsg); + + GSI_UNUSED(userdata); +} + +/*********** +init_game +Initialize the sample data structures with bogus data +************/ +static void init_game(void) +{ + int i; + int team; + + srand((unsigned int) current_time() ); + gamedata.numplayers = rand() % 15; + gamedata.maxplayers = QR2_MAX_PLAYERS; + for (i = 0 ; i < gamedata.numplayers ; i++) + { + strcpy(gamedata.players[i].pname, constnames[i]); + gamedata.players[i].pfrags = rand() % 32; + gamedata.players[i].pdeaths = rand() % 32; + gamedata.players[i].pskill = rand() % 1000; + gamedata.players[i].pping = rand() % 500; + team = rand() % 3; + if (team == 0) + strcpy(gamedata.players[i].pteam,"Red"); + else if (team == 1) + strcpy(gamedata.players[i].pteam,"Blue"); + else if (team == 2) + strcpy(gamedata.players[i].pteam,""); + } + strcpy(gamedata.mapname,"gmtmap1"); + strcpy(gamedata.gametype,"arena"); + strcpy(gamedata.hostname,"GameMaster Arena Server"); + strcpy(gamedata.gamemode,"openplaying"); + gamedata.fraglimit = 0; + gamedata.timelimit = 40; + gamedata.teamplay = 1; + gamedata.locationid = 1; + gamedata.rankingson = 1; + gamedata.hostport = 25000; +} + +/******* + DoGameStuff +Simulate whatever else a game server does +********/ +void DoGameStuff(void) +{ + msleep(10); +} + +GT2Bool UnrecognizedMessageCallback(GT2Socket socket, unsigned int ip, unsigned short port, GT2Byte * message, int len) +{ + static char buffer[8 * 1024]; + struct sockaddr_in saddr; + + if(!len || !message || ((message[0] != QR_MAGIC_1) && (message[1] != QR_MAGIC_2) && (message[0] != '\\'))) + return GT2False; + + // we want to make sure it is NUL-terminated + len = min(len, (sizeof(buffer) - 1)); + memcpy(buffer, message, len); + buffer[len] = '\0'; + + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = ip; + saddr.sin_port = htons(port); + //qr_parse_query(NULL, buffer, len, (struct sockaddr *)&saddr); + qr2_parse_query(NULL, buffer, len, (struct sockaddr *)&saddr); + return GT2True; + + GSI_UNUSED(socket); +} + +void ConnectAttemptCallback +( + GT2Socket socket, + GT2Connection connection, + unsigned int ip, + unsigned short port, + int latency, + GT2Byte * message, + int len +) +{ + printf("Connection attempt from %s (%d ping)\n", gt2AddressToString(ip, port, NULL), latency); + + gt2Reject(connection, NULL, 0); + + GSI_UNUSED(len); + GSI_UNUSED(message); + GSI_UNUSED(socket); +} + +/******************* + main +Simulates a main program loop +First, initializes the Q&R items, then enters a main loop +*****************/ +#if defined(_PS2) +int test_main(int argc, char **argp) +#else +int main(int argc, char **argp) +#endif +{ + char secret_key[9]; + GT2Socket socket; + GT2Result result; + int natNegotiate = 1; + result = gt2CreateSocket(&socket, gt2AddressToString(0, QR2_BASE_PORT, NULL), 0, 0, NULL); + if(result != GT2Success) + return -1; + + gt2Listen(socket, ConnectAttemptCallback); + + //set the secret key, in a semi-obfuscated manner + secret_key[0] = 'H'; + secret_key[1] = 'A'; + secret_key[2] = '6'; + secret_key[3] = 'z'; + secret_key[4] = 'k'; + secret_key[5] = 'S'; + secret_key[6] = '\0'; + + qr2_register_key(QR2_RANKINGSON_KEY, _T("rankingson")); + /* + //call qr_init_socket with the socket and gamename + if (qr_init_socket(NULL,gt2GetSocketSOCKET(socket), QR2_GAME_NAME, secret_key, basic_callback, + info_callback, rules_callback, players_callback, NULL) != 0) + { + printf("Error starting Q&R SDK\n"); + return -1; + } + */ + + // call the qr2_init_socket with the socket and gamename + if (qr2_init_socket(NULL, gt2GetSocketSOCKET(socket), QR2_BASE_PORT, QR2_GAME_NAME, secret_key, 1, natNegotiate, + serverkey_callback, playerkey_callback, teamkey_callback, keylist_callback, count_callback, + adderror_callback, NULL) != e_qrnoerror) + + { + printf("Error starting QR2 SDK\n"); + return -1; + } + // set the unrecognized message callback + gt2SetUnrecognizedMessageCallback(socket, UnrecognizedMessageCallback); + + init_game(); + + printf("Press any key to quit\n"); +#if defined(_WIN32) && !defined(UNDER_CE) + while (!_kbhit()) +#else + while (1) +#endif + { + DoGameStuff(); + //process our game networking + gt2Think(socket); + //check for / process incoming queries + //qr_process_queries(NULL); + qr2_think(NULL); + } + //let gamemaster know we are shutting down + strcpy(gamedata.gamemode,"exiting"); + //qr_send_exiting(NULL); + //qr_shutdown(NULL); + qr2_shutdown(NULL); + gt2CloseSocket(socket); + return 0; + + GSI_UNUSED(argp); + GSI_UNUSED(argc); +} diff --git a/code/gamespy/gt2/gt2proxy/gt2proxy.c b/code/gamespy/gt2/gt2proxy/gt2proxy.c new file mode 100644 index 00000000..85903e4a --- /dev/null +++ b/code/gamespy/gt2/gt2proxy/gt2proxy.c @@ -0,0 +1,534 @@ + +#include "../gt2.h" +#include "../../darray.h" +#include + + +#define STATS 1 + +GT2Socket Socket; +char RemoteAddress[256]; +char LocalAddress[256]; +GT2Bool Quit; +DArray ServerSockets; + +#if STATS +time_t startTime; +int numConnectAttempts; +int connectResults[8]; +int clientReliableMessagesSent; +int serverReliableMessagesSent; +int clientUnreliableMessagesSent; +int serverUnreliableMessagesSent; +int clientReliableBytesSent; +int serverReliableBytesSent; +int clientUnreliableBytesSent; +int serverUnreliableBytesSent; +int clientCloses; +int serverCloses; +#endif + +void FreeServerSocket(void * elem) +{ + GT2Socket socket = *(GT2Socket *)elem; + + gt2CloseSocket(socket); +} + +int ServerSocketCompare(const void * elem1, const void * elem2) +{ + GT2Socket socket1 = *(GT2Socket *)elem1; + GT2Socket socket2 = *(GT2Socket *)elem2; + + if(socket1 == socket2) + return 0; + return 1; +} + +void RemoveServerSocket(GT2Socket socket) +{ + int index; + + // find it first + index = ArraySearch(ServerSockets, &socket, ServerSocketCompare, 0, 0); + if(index != NOT_FOUND) + ArrayDeleteAt(ServerSockets, index); +} + +/* CLIENT CONNECTIONS */ + +void ClientReceivedCallback +( + GT2Connection connection, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + GT2Connection serverConnection; + + // The server connection for this client connection. + //////////////////////////////////////////////////// + serverConnection = (GT2Connection)gt2GetConnectionData(connection); + + // Pass the data along. + /////////////////////// + gt2Send(serverConnection, message, len, reliable); + +#if STATS + // Update stats. + //////////////// + if(reliable) + { + clientReliableMessagesSent++; + clientReliableBytesSent += len; + } + else + { + clientUnreliableMessagesSent++; + clientUnreliableBytesSent += len; + } +#endif +} + +void ClientClosedCallback +( + GT2Connection connection, + GT2CloseReason reason +) +{ + GT2Connection serverConnection; + + // The server connection for this client connection. + //////////////////////////////////////////////////// + serverConnection = (GT2Connection)gt2GetConnectionData(connection); + if(serverConnection) + { + // We don't want them to try closing us.... + /////////////////////////////////////////// + gt2SetConnectionData(serverConnection, NULL); + + // Close the connection to the server. + ////////////////////////////////////// + gt2CloseConnection(serverConnection); + } + +#if STATS + // Update stats. + //////////////// + if(reason == GT2LocalClose) + clientCloses++; +#endif +} + +GT2ConnectionCallbacks ClientConnectionCallbacks = +{ + NULL, + ClientReceivedCallback, + ClientClosedCallback +}; + +/* SERVER CONNECTIONS */ + +void ServerSocketErrorCallback +( + GT2Socket socket +) +{ + printf("Server socket error\n"); + Quit = GT2True; + + GSI_UNUSED(socket); +} + +void ServerConnectedCallback +( + GT2Connection connection, + GT2Result result, + GT2Byte * message, + int len +) +{ + GT2Connection clientConnection; + + // The client connection for this server connection. + //////////////////////////////////////////////////// + clientConnection = (GT2Connection)gt2GetConnectionData(connection); + + // Check the result. + //////////////////// + if(result == GT2Success) + { + // Accept it. + ///////////// + if(!gt2Accept(clientConnection, &ClientConnectionCallbacks)) + gt2CloseConnection(connection); + } + else + { + if(result == GT2Rejected) + gt2Reject(clientConnection, message, len); + else + gt2Reject(clientConnection, (const GT2Byte *)("Proxy failed to connect to server."), -1); + + // Close the socket. + //////////////////// + RemoveServerSocket(gt2GetConnectionSocket(connection)); + } + +#if STATS + // Update stats. + //////////////// + connectResults[result]++; +#endif +} + +void ServerReceivedCallback +( + GT2Connection connection, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + GT2Connection clientConnection; + + // The client connection for this server connection. + //////////////////////////////////////////////////// + clientConnection = (GT2Connection)gt2GetConnectionData(connection); + + // Send it the data. + //////////////////// + gt2Send(clientConnection, message, len, reliable); + +#if STATS + // Update stats. + //////////////// + if(reliable) + { + serverReliableMessagesSent++; + serverReliableBytesSent += len; + } + else + { + serverUnreliableMessagesSent++; + serverUnreliableBytesSent += len; + } +#endif +} + +void ServerClosedCallback +( + GT2Connection connection, + GT2CloseReason reason +) +{ + GT2Connection clientConnection; + + // The client connection for this server connection. + //////////////////////////////////////////////////// + clientConnection = (GT2Connection)gt2GetConnectionData(connection); + if(clientConnection) + { + // We don't want them to try closing us.... + /////////////////////////////////////////// + gt2SetConnectionData(clientConnection, NULL); + + // Close the connection. + //////////////////////// + gt2CloseConnection(clientConnection); + } + + // Close the socket. + //////////////////// + RemoveServerSocket(gt2GetConnectionSocket(connection)); + +#if STATS + // Update stats. + //////////////// + if(reason == GT2LocalClose) + serverCloses++; +#endif +} + +GT2ConnectionCallbacks ServerConnectionCallbacks = +{ + ServerConnectedCallback, + ServerReceivedCallback, + ServerClosedCallback +}; + +/* LISTENER */ + +void SocketErrorCallback +( + GT2Socket socket +) +{ + printf("Socket error (incoming connections socket)\n"); + Quit = GT2True; + + GSI_UNUSED(socket); +} + +void ConnectAttemptCallback +( + GT2Socket socket, + GT2Connection connection, + unsigned int ip, + unsigned short port, + int latency, + GT2Byte * message, + int len +) +{ + GT2Socket serverSocket; + GT2Connection serverConnection; + GT2Result result; + + // Create a socket for this connection. + /////////////////////////////////////// + result = gt2CreateSocket(&serverSocket, NULL, 0, 0, NULL); + if(result != GT2Success) + { + gt2Reject(connection, (const GT2Byte *)("Proxy failed to create a new socket."), -1); + return; + } + + // Connect to the real server. + ////////////////////////////// + result = gt2Connect(serverSocket, &serverConnection, RemoteAddress, message, len, 0, &ServerConnectionCallbacks, GT2False); + if(result != GT2Success) + { + gt2Reject(connection, (const GT2Byte *)("Proxy failed to connect to server."), -1); + gt2CloseSocket(serverSocket); + return; + } + + // Add the socket to the array. + /////////////////////////////// + ArrayAppend(ServerSockets, &serverSocket); + + // The user data for each connection is the other. + ////////////////////////////////////////////////// + gt2SetConnectionData(serverConnection, connection); + gt2SetConnectionData(connection, serverConnection); + +#if STATS + // Update stats. + //////////////// + numConnectAttempts++; +#endif + + GSI_UNUSED(latency); + GSI_UNUSED(port); + GSI_UNUSED(ip); + GSI_UNUSED(socket); +} + +#ifdef WIN32 +BOOL WINAPI CtrlHandler +( + DWORD type +) +{ + // Quit. + //////// + Quit = GT2True; + + // We handled it. + ///////////////// + return TRUE; + + GSI_UNUSED(type); +} +#endif + +#if STATS +void DisplayStats(void) +{ + time_t runTime; + time_t days; + time_t hours; + time_t minutes; + time_t seconds; + time_t ctimeTime; + int numAccepted; + int numRejected; + int total; + + // Do the time stuff. + ///////////////////// + ctimeTime = startTime; + runTime = (time(NULL) - startTime); + seconds = (runTime % 60); + runTime /= 60; + minutes = (runTime % 60); + runTime /= 60; + hours = (runTime % 24); + runTime /= 24; + days = runTime; + + // Do some other stuff. + /////////////////////// + numAccepted = connectResults[GT2Success]; + numRejected = connectResults[GT2Rejected]; + + // Print stats. + /////////////// + printf("Proxying since %s", gsiSecondsToString(&ctimeTime)); + printf("(%d days, %d hours, %d minutes, %d seconds)\n", + days, + hours, + minutes, + seconds); + printf("%d connect attempts, %d (%d%%) accepted, %d (%d%%) rejected\n", + numConnectAttempts, + numAccepted, + (numConnectAttempts)?(numAccepted * 100) / numConnectAttempts:0, + numRejected, + (numConnectAttempts)?(numRejected * 100) / numConnectAttempts:0); + total = (clientReliableMessagesSent + clientUnreliableMessagesSent); + printf("client: %d messages, %d (%d%%) reliable, %d (%d%%) unreliable\n", + total, + clientReliableMessagesSent, + (total)?(clientReliableMessagesSent * 100) / total:0, + clientUnreliableMessagesSent, + (total)?(clientUnreliableMessagesSent * 100) / total:0); + total = (serverReliableMessagesSent + serverUnreliableMessagesSent); + printf("server: %d messages, %d (%d%%) reliable, %d (%d%%) unreliable\n", + total, + serverReliableMessagesSent, + (total)?(serverReliableMessagesSent * 100) / total:0, + serverUnreliableMessagesSent, + (total)?(serverUnreliableMessagesSent * 100) / total:0); + total = (clientReliableBytesSent + clientUnreliableBytesSent); + printf("client: %d bytes, %d (%d%%) reliable, %d (%d%%) unreliable\n", + total, + clientReliableBytesSent, + (total)?(clientReliableBytesSent * 100) / total:0, + clientUnreliableBytesSent, + (total)?(clientUnreliableBytesSent * 100) / total:0); + total = (serverReliableBytesSent + serverUnreliableBytesSent); + printf("server: %d bytes, %d (%d%%) reliable, %d (%d%%) unreliable\n", + total, + serverReliableBytesSent, + (total)?(serverReliableBytesSent * 100) / total:0, + serverUnreliableBytesSent, + (total)?(serverUnreliableBytesSent * 100) / total:0); + total = (clientCloses + serverCloses); + printf("%d closes, %d (%d%%) client, %d (%d%%) server\n", + total, + clientCloses, + (total)?(clientCloses * 100) / total:0, + serverCloses, + (total)?(serverCloses * 100) / total:0); +} +#endif + +int main +( + int argc, + char ** argv +) +{ + GT2Result result; + int num; + int i; +#if STATS + unsigned int lastStatsTime = 0; + unsigned int now; +#endif + + // Check args. + ////////////// + if((argc < 2) || (argc > 3)) + { + printf("%s [localhost][:port]\n", argv[0]); + return 1; + } + + // First is the remote address. + /////////////////////////////// + strcpy(RemoteAddress, argv[1]); + + // Second is the local address. + /////////////////////////////// + if(argc >= 3) + strcpy(LocalAddress, argv[2]); + + // Create the array of server sockets. + ////////////////////////////////////// + ServerSockets = ArrayNew(sizeof(GT2Socket), 10, FreeServerSocket); + if(!ServerSockets) + { + printf("Failed to create the array of server sockets\n"); + return 1; + } + + // Create the socket. + ///////////////////// + result = gt2CreateSocket(&Socket, LocalAddress, 0, 0, SocketErrorCallback); + if(result != GT2Success) + { + printf("Error creating the socket (%d)\n", result); + return 1; + } + + // Start listening. + /////////////////// + gt2Listen(Socket, ConnectAttemptCallback); + + // Show the port. + ///////////////// + printf("Listening on port %d\n", gt2GetLocalPort(Socket)); + + // For win32, setup a ctrl-c handler. + ///////////////////////////////////// + SetConsoleCtrlHandler(CtrlHandler, TRUE); + + // We're starting. + ////////////////// + startTime = time(NULL); + + // Loop until we quit. + ////////////////////// + while(!Quit) + { +#if STATS + // Get the current time. + //////////////////////// + now = current_time(); + + // Display stats? + ///////////////// + if((now - lastStatsTime) >= 30000) + { + DisplayStats(); + lastStatsTime = now; + } +#endif + + // Yield. + ///////// + msleep(1); + + // Think. + ///////// + gt2Think(Socket); + + // Let the server sockets think. + //////////////////////////////// + num = ArrayLength(ServerSockets); + for(i = (num - 1) ; i >= 0 ; i--) + gt2Think(*(GT2Socket *)ArrayNth(ServerSockets, i)); + } + +#if STATS + // Display some final stats. + //////////////////////////// + DisplayStats(); +#endif + + return 0; +} diff --git a/code/gamespy/hashtable.c b/code/gamespy/hashtable.c new file mode 100644 index 00000000..9313e503 --- /dev/null +++ b/code/gamespy/hashtable.c @@ -0,0 +1,229 @@ +/* + * + * File: hashtable.c + * --------------- + * David Wright + * 10/8/98 + * + * See hashtable.h for function comments + * Implmentation is straight-forward, using a fixed dynamically allocated + * array for the buckets, and a DArray for each individual bucket + */ + +#include +#include +#include "darray.h" +#include "hashtable.h" + +#ifdef _MFC_MEM_DEBUG +#define _CRTDBG_MAP_ALLOC 1 +#include +#endif + + +#ifdef _NO_NOPORT_H_ + #define gsimalloc malloc + #define gsifree free + #define gsirealloc realloc + #include +#else + #include "nonport.h" //for gsimalloc/realloc/free/assert +#endif + + +struct HashImplementation +{ + DArray *buckets; + int nbuckets; + TableElementFreeFn freefn; + TableHashFn hashfn; + TableCompareFn compfn; +}; + +HashTable TableNew(int elemSize, int nBuckets, + TableHashFn hashFn, TableCompareFn compFn, + TableElementFreeFn freeFn) +{ + return TableNew2(elemSize, nBuckets, 4, hashFn, compFn, freeFn); +} +HashTable TableNew2(int elemSize, int nBuckets, int nChains, + TableHashFn hashFn, TableCompareFn compFn, + TableElementFreeFn freeFn) +{ + HashTable table; + int i; + + assert(hashFn); + assert(compFn); + assert(elemSize); + assert(nBuckets); + + table = (HashTable)gsimalloc(sizeof(struct HashImplementation)); + assert(table); + + table->buckets = (DArray *)gsimalloc(nBuckets * sizeof(DArray)); + assert(table->buckets); + for (i = 0; i < nBuckets; i++) //ArrayNew will assert if allocation fails + table->buckets[i] = ArrayNew(elemSize, nChains, freeFn); + table->nbuckets = nBuckets; + table->freefn = freeFn; + table->compfn = compFn; + table->hashfn = hashFn; + + return table; +} + + +void TableFree(HashTable table) +{ + int i; + + assert(table); + + if (NULL == table ) + return; + + for (i = 0 ; i < table->nbuckets ; i++) + ArrayFree(table->buckets[i]); + gsifree(table->buckets); + gsifree(table); +} + + +int TableCount(HashTable table) +{ + int i, count = 0; + + assert(table); + + if (NULL == table ) + return count; + + for (i = 0 ; i < table->nbuckets ; i++) + count += ArrayLength(table->buckets[i]); + + return count; +} + + +void TableEnter(HashTable table, const void *newElem) +{ + int hash, itempos; + + assert(table); + + if (NULL == table ) + return; + + hash = table->hashfn(newElem, table->nbuckets); + itempos = ArraySearch(table->buckets[hash], newElem, table->compfn, 0,0); + if (itempos == NOT_FOUND) + ArrayAppend(table->buckets[hash], newElem); + else + ArrayReplaceAt(table->buckets[hash], newElem, itempos); +} + +int TableRemove(HashTable table, const void *delElem) +{ + int hash, itempos; + + assert(table); + + if (NULL == table ) + return 0; + + hash = table->hashfn(delElem, table->nbuckets); + itempos = ArraySearch(table->buckets[hash], delElem, table->compfn, 0,0); + if (itempos == NOT_FOUND) + return 0; + else + ArrayDeleteAt(table->buckets[hash], itempos); + return 1; +} + +void *TableLookup(HashTable table, const void *elemKey) +{ + int hash, itempos; + + assert(table); + + if (NULL == table ) + return NULL; + + hash = table->hashfn(elemKey, table->nbuckets); + itempos = ArraySearch(table->buckets[hash], elemKey, table->compfn, 0, + 0); + if (itempos == NOT_FOUND) + return NULL; + else + return ArrayNth(table->buckets[hash], itempos); +} + + +void TableMap(HashTable table, TableMapFn fn, void *clientData) +{ + int i; + + assert(table); + assert(fn); + + if (NULL == table || NULL == fn) + return; + + for (i = 0 ; i < table->nbuckets ; i++) + ArrayMap(table->buckets[i], fn, clientData); + +} + +void TableMapSafe(HashTable table, TableMapFn fn, void *clientData) +{ + int i; + + assert(fn); + + for (i = 0 ; i < table->nbuckets ; i++) + ArrayMapBackwards(table->buckets[i], fn, clientData); + +} + +void * TableMap2(HashTable table, TableMapFn2 fn, void *clientData) +{ + int i; + void * pcurr; + + assert(fn); + + for (i = 0 ; i < table->nbuckets ; i++) + { + pcurr = ArrayMap2(table->buckets[i], fn, clientData); + if(pcurr) + return pcurr; + } + + return NULL; +} + +void * TableMapSafe2(HashTable table, TableMapFn2 fn, void *clientData) +{ + int i; + void * pcurr; + + assert(fn); + + for (i = 0 ; i < table->nbuckets ; i++) + { + pcurr = ArrayMapBackwards2(table->buckets[i], fn, clientData); + if(pcurr) + return pcurr; + } + + return NULL; +} + +void TableClear(HashTable table) +{ + int i; + + for (i = 0 ; i < table->nbuckets ; i++) + ArrayClear(table->buckets[i]); +} diff --git a/code/gamespy/hashtable.h b/code/gamespy/hashtable.h new file mode 100644 index 00000000..d6c8a4a3 --- /dev/null +++ b/code/gamespy/hashtable.h @@ -0,0 +1,231 @@ + #ifndef _HASHTABLE_H +#define _HASHTABLE_H + +/* File: hashtable.h + * ------------------ + * Defines the interface for the HashTable ADT. + * The HashTable allows the client to store any number of elements of any + * type in a hash table for fast storage and retrieval. The client specifies + * the size (in bytes) of the elements that will be stored in the table when + * it is created. Thereafter the client and the HashTable refer to elements + * via (void*) ptrs. The HashTable imposes no upper bound on the number of + * elements and deals with all its own memory management. + * + * The client-supplied information (in the form of the number of buckets + * to use and the hashing function to be applied to each element) is employed + * to divide elements in buckets with hopefully only few collisions, resulting + * in Enter & Lookup performance in constant-time. The HashTable also supports + * iterating over all elements by use of mapping function. + */ + +/* Type: HashTable + * ---------------- + * Defines the HashTable type itself. The client can declare variables of type + * HashTable, but these variables must be initialized with the result of + * TableNew. The HashTable is implemented with pointers, so all client + * copies in variables or parameters will be "shallow" -- they will all + * actually point to the same HashTable structure. Only calls to TableNew + * create new tables. The struct declaration below is "incomplete"- the + * implementation details are literally not visible in the client .h file. + */ +typedef struct HashImplementation *HashTable; + + +/* TableHashFn + * ----------- + * TableHashFn is a pointer to a client-supplied function which the + * HashTable uses to hash elements. The hash function takes a (const void*) + * pointer to an element and the number of buckets and returns an int, + * which represents the hash code for this element. The returned hash code + * should be within the range 0 to numBuckets-1 and should be stable (i.e. + * an element's hash code should not change over time). + * For best performance, the hash function should be designed to + * uniformly distribute elements over the available number of buckets. + */ +typedef int (*TableHashFn)(const void *elem, int numBuckets); + + +/* TableCompareFn + * -------------- + * TableCompareFn is a pointer to a client-supplied function which the + * HashTable uses to compare elements. The comparator takes two + * (const void*) pointers (these will point to elements) and returns an int. + * The comparator should indicate the ordering of the two elements + * using the same convention as the strcmp library function: + * If elem1 is "less than" elem2, return a negative number. + * If elem1 is "greater than" elem2, return a positive number. + * If the two elements are "equal", return 0. + */ +#if defined(WIN32) +// explicitly set __cdecl so that Win devs can change default calling convention +typedef int (__cdecl *TableCompareFn)(const void *elem1, const void *elem2); +#else +typedef int (*TableCompareFn)(const void *elem1, const void *elem2); +#endif + + /* TableMapFn + * ---------- + * TableMapFn defines the space of functions that can be used to map over + * the elements in a HashTable. A map function is called with a pointer to + * the element and a client data pointer passed in from the original caller. + */ +typedef void (*TableMapFn)(void *elem, void *clientData); + + /* TableMapFn2 + * ---------- + * Same as TableMapFn, but can return 0 to stop the mapping. + * Used by TableMap2. + */ +typedef int (*TableMapFn2)(void *elem, void *clientData); + + +/* TableElementFreeFn + * ------------------ + * TableElementFreeFn defines the space of functions that can be used as the + * clean-up function for each element as it is deleted from the array + * or when the entire array of elements is freed. The cleanup function is + * called with a pointer to an element about to be deleted. + */ +typedef void (*TableElementFreeFn)(void *elem); + +#ifdef __cplusplus +extern "C" { +#endif + +/* TableNew + * -------- + * Creates a new HashTable with no entries and returns it. The elemSize + * parameter specifies the number of bytes that a single element of the + * table should take up. For example, if you want to store elements of type + * Binky, you would pass sizeof(Binky) as this parameter. An assert is + * raised if this size is not greater than 0. + * + * The nBuckets parameter specifies the number of buckets that the elements + * will be partitioned into. Once a HashTable is created, this number does + * not change. The nBuckets parameter must be in synch with the behavior of + * the hashFn, which must return a hash code between 0 and nBuckets-1. + * The hashFn parameter specifies the function that is called to retrieve the + * hash code for a given element. See the type declaration of TableHashFn + * above for more information. An assert is raised if nBuckets is not + * greater than 0. + * + * The compFn is used for testing equality between elements. See the + * type declaration for TableCompareFn above for more information. + * + * The elemFreeFn is the function that will be called on an element that is + * about to be overwritten (by a new entry in TableEnter) or on each element + * in the table when the entire table is being freed (using TableFree). This + * function is your chance to do any deallocation/cleanup required, + * (such as freeing any pointers contained in the element). The client can pass + * NULL for the cleanupFn if the elements don't require any handling on free. + * An assert is raised if either the hash or compare functions are NULL. + * + * nChains is the number of chains to allocate initially in each bucket + * + */ + +HashTable TableNew(int elemSize, int nBuckets, + TableHashFn hashFn, TableCompareFn compFn, + TableElementFreeFn freeFn); + +HashTable TableNew2(int elemSize, int nBuckets, int nChains, + TableHashFn hashFn, TableCompareFn compFn, + TableElementFreeFn freeFn); + + + /* TableFree + * ---------- + * Frees up all the memory for the table and its elements. It DOES NOT + * automatically free memory owned by pointers embedded in the elements. This + * would require knowledge of the structure of the elements which the HashTable + * does not have. However, it will iterate over the elements calling + * the elementFreeFn earlier supplied to TableNew and therefore, the client, + * who knows what the elements are,can do the appropriate deallocation of any + * embedded pointers through that function. + * After calling this, the value of what table points to is undefined. + */ +void TableFree(HashTable table); + + +/* TableCount + * ---------- + * Returns the number of elements currently in the table. + */ +int TableCount(HashTable table); + + + +/* TableEnter + * ---------- + * Enters a new element into the table. Uses the hash function to determine + * which bucket to place the new element. Its contents are copied from the + * memory pointed to by newElem. If there is already an element in the table + * which is determined to be equal (using the comparison function) this will + * use the contents of the new element to replace the previous element, + * calling the free function on the replaced element. + */ +void TableEnter(HashTable table, const void *newElem); + +/* TableRemove + * ---------- + * Remove a element frin the table. If the element does not exist + * the function returns 0. If it exists, it returns 1 and calls the + * free function on the removed element. + */ +int TableRemove(HashTable table, const void *delElem); + + +/* TableLookup + * ---------- + * Returns a pointer to the table element which matches the elemKey parameter + * (equality is determined by the comparison function). If there is no + * matching element, returns NULL. Calling this function does not + * re-arrange or change contents of the table or modify elemKey in any way. + */ +void *TableLookup(HashTable table, const void *elemKey); + + + +/* TableMap + * ----------- + * Iterates through each element in the table (in any order) and calls the + * function fn for that element. The function is called with the address of + * the table element and the clientData pointer. The clientData value allows + * the client to pass extra state information to the client-supplied function, + * if necessary. If no client data is required, this argument should be NULL. + * An assert is raised if the map function is NULL. + */ +void TableMap(HashTable table, TableMapFn fn, void *clientData); + +/* TableMapSafe + * ----------- + * Same as TableMap, but allows elements to be freed during the mapping. + */ +void TableMapSafe(HashTable table, TableMapFn fn, void *clientData); + +/* TableMap2 + * ----------- + * Same as TableMap, but allows the mapping to be stopped by returning 0 + * from the mapping function. If the mapping was stopped, the element + * it was stopped at will be returned. If it wasn't stopped, then NULL + * will be returned. + */ +void * TableMap2(HashTable table, TableMapFn2 fn, void *clientData); + +/* TableMapSafe2 + * ----------- + * Same as TableMap2, but allows elements to be freed during the mapping. + */ +void * TableMapSafe2(HashTable table, TableMapFn2 fn, void *clientData); + +/* TableClear + * ----------- + * Clears all the elements in the table without freeing it + */ +void TableClear(HashTable table); + +#ifdef __cplusplus +} +#endif + +#endif //_HASHTABLE_H diff --git a/code/gamespy/md5.h b/code/gamespy/md5.h new file mode 100644 index 00000000..0fccee0c --- /dev/null +++ b/code/gamespy/md5.h @@ -0,0 +1,81 @@ +/* MD5.H - header file for MD5C.C + */ + +/* Copyright (C) 1991, RSA Data Security, Inc. All rights reserved. + + License to copy and use this software is granted provided that it + is identified as the "RSA Data Security, Inc. MD5 Message-Digest + Algorithm" in all material mentioning or referencing this software + or this function. + + License is also granted to make and use derivative works provided + that such works are identified as "derived from the RSA Data + Security, Inc. MD5 Message-Digest Algorithm" in all material + mentioning or referencing the derived work. + + RSA Data Security, Inc. makes no representations concerning either + the merchantability of this software or the suitability of this + software for any particular purpose. It is provided "as is" + without express or implied warranty of any kind. + + These notices must be retained in any copies of any part of this + documentation and/or software. + */ + +/* GLOBAL.H - RSAREF types and constants + */ + +/* PROTOTYPES should be set to one if and only if the compiler supports + function argument prototyping. +The following makes PROTOTYPES default to 0 if it has not already + + been defined with C compiler flags. + */ + +#ifndef _MD5_H_ +#define _MD5_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef PROTOTYPES +#define PROTOTYPES 1 +#endif + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +typedef unsigned int UINT4; + +/* PROTO_LIST is defined depending on how PROTOTYPES is defined above. +If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it + returns an empty list. + */ +#if PROTOTYPES +#define PROTO_LIST(list) list +#else +#define PROTO_LIST(list) () +#endif + +/* MD5 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD5_CTX; + +void MD5Init PROTO_LIST ((MD5_CTX *)); +void MD5Update PROTO_LIST ((MD5_CTX *, unsigned char *, unsigned int)); +void MD5Final PROTO_LIST ((unsigned char [16], MD5_CTX *)); +void MD5Print PROTO_LIST ((unsigned char [16], char[33])); +void MD5Digest PROTO_LIST ((unsigned char *, unsigned int, char[33])); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/md5c.c b/code/gamespy/md5c.c new file mode 100644 index 00000000..de31fc6a --- /dev/null +++ b/code/gamespy/md5c.c @@ -0,0 +1,354 @@ +/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm + */ + +/* Copyright (C) 1991, RSA Data Security, Inc. All rights reserved. + + License to copy and use this software is granted provided that it + is identified as the "RSA Data Security, Inc. MD5 Message-Digest + Algorithm" in all material mentioning or referencing this software + or this function. + + License is also granted to make and use derivative works provided + that such works are identified as "derived from the RSA Data + Security, Inc. MD5 Message-Digest Algorithm" in all material + mentioning or referencing the derived work. + + RSA Data Security, Inc. makes no representations concerning either + the merchantability of this software or the suitability of this + software for any particular purpose. It is provided "as is" + without express or implied warranty of any kind. + + These notices must be retained in any copies of any part of this + documentation and/or software. + */ + +//#include "global.h" +#include "md5.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(_NITRO) + +/* Constants for MD5Transform routine. + */ +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +static void MD5Transform PROTO_LIST ((UINT4 [4], unsigned char [64])); +static void Encode PROTO_LIST ((unsigned char *, UINT4 *, unsigned int)); +static void Decode PROTO_LIST ((UINT4 *, unsigned char *, unsigned int)); +static void MD5_memcpy PROTO_LIST ((POINTER, POINTER, unsigned int)); +static void MD5_memset PROTO_LIST ((POINTER, int, unsigned int)); + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. + Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +/* MD5 initialization. Begins an MD5 operation, writing a new context. + */ +void MD5Init (MD5_CTX *context) +{ + context->count[0] = context->count[1] = 0; + + /* Load magic initialization constants. + */ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD5 block update operation. Continues an MD5 message-digest operation, + processing another message block, and updating the context. + */ +void MD5Update ( + MD5_CTX *context, /* context */ + unsigned char *input, /* input block */ + unsigned int inputLen /* length of input block */ +) +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3)) < ((UINT4)inputLen << 3)) + context->count[1]++; + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible. + */ + if (inputLen >= partLen) { + MD5_memcpy ((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD5Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD5Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + MD5_memcpy + ((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i); +} + +/* MD5 finalization. Ends an MD5 message-digest operation, writing the + the message digest and zeroizing the context. + */ +void MD5Final ( + unsigned char digest[16], /* message digest */ + MD5_CTX *context /* context */ + ) +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64. + */ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD5Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD5Update (context, bits, 8); + + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information. + */ + MD5_memset ((POINTER)context, 0, sizeof (*context)); +} + +/* MD5 basic transformation. Transforms state based on block. + */ +static void MD5Transform (UINT4 state[4], unsigned char block[64]) +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF ( a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF ( d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF ( c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF ( b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF ( a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF ( d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF ( c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF ( b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF ( a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF ( d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF ( c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF ( b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF ( a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF ( d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF ( c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF ( b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG ( a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG ( d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG ( c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG ( b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG ( a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG ( d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG ( c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG ( b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG ( a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG ( d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG ( c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG ( b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG ( a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG ( d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG ( c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG ( b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH ( a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH ( d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH ( c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH ( b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH ( a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH ( d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH ( c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH ( b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH ( a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH ( d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH ( c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH ( b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH ( a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH ( d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH ( c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH ( b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II ( a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II ( d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II ( c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II ( b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II ( a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II ( d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II ( c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II ( b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II ( a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II ( d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II ( c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II ( b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II ( a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II ( d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II ( c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II ( b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. + */ + MD5_memset ((POINTER)x, 0, sizeof (x)); +} + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void Encode (unsigned char *output, UINT4 *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. + */ +static void Decode (UINT4 *output, unsigned char *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | + (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + +/* Note: Replace "for loop" with standard memcpy if possible. + */ +static void MD5_memcpy (POINTER output, POINTER input, unsigned int len) +{ + memcpy(output, input, len); +/* unsigned int i; + + for (i = 0; i < len; i++) + output[i] = input[i];*/ +} + +/* Note: Replace "for loop" with standard memset if possible. + */ +static void MD5_memset (POINTER output, int value, unsigned int len) +{ + memset(output, value, len); + /* unsigned int i; + + for (i = 0; i < len; i++) + ((char *)output)[i] = (char)value; */ +} + +#endif + +void MD5Print (unsigned char digest[16], char output[33]) +{ + static const char hex_digits[] = "0123456789abcdef"; + unsigned int i; + + for (i = 0; i < 16; i++) + { + output[i*2 ] = hex_digits[digest[i] / 16]; + output[i*2+1] = hex_digits[digest[i] % 16]; + } + output[32] = '\0'; +} + +void MD5Digest (unsigned char *input, unsigned int len, char output[33]) +{ + MD5_CTX ctx; + unsigned char digest[16]; + + MD5Init(&ctx); + MD5Update(&ctx, input, len); + MD5Final(digest, &ctx); + MD5Print(digest, output); + +} +#ifdef __cplusplus +} +#endif diff --git a/code/gamespy/natneg/NATify.c b/code/gamespy/natneg/NATify.c new file mode 100644 index 00000000..3dbe6120 --- /dev/null +++ b/code/gamespy/natneg/NATify.c @@ -0,0 +1,383 @@ +#include +#include "nninternal.h" +#include "NATify.h" + +gsi_bool gotERT1, gotERT2, gotERT3; +gsi_bool gotADDRESS1a, gotADDRESS1b, gotADDRESS2, gotADDRESS3; + +// prototypes for compilers that complain +const char * AddressToString(unsigned int ip, unsigned short port, char string[22]); +unsigned int NameToIp(const char *name); + + +const char * AddressToString(unsigned int ip, unsigned short port, char string[22]) +{ + static char strAddressArray[2][22]; + static int nIndex; + char * strAddress; + + if(string) + strAddress = string; + else + { + nIndex ^= 1; + strAddress = strAddressArray[nIndex]; + } + + if(ip) + { + IN_ADDR inAddr; + + inAddr.s_addr = ip; + + if(port) + sprintf(strAddress, "%s:%d", inet_ntoa(inAddr), port); + else + sprintf(strAddress, "%s", inet_ntoa(inAddr)); + } + else if(port) + sprintf(strAddress, ":%d", port); + else + strAddress[0] = '\0'; + + return strAddress; +} + +static unsigned int GetLocalIP() +{ + int num_local_ips; + struct hostent *phost; + struct in_addr *addr; + unsigned int localip = 0; + phost = getlocalhost(); + if (phost == NULL) + return 0; + for (num_local_ips = 0 ; ; num_local_ips++) + { + if (phost->h_addr_list[num_local_ips] == 0) + break; + addr = (struct in_addr *)phost->h_addr_list[num_local_ips]; + if (addr->s_addr == htonl(0x7F000001)) + continue; + localip = addr->s_addr; + + if(IsPrivateIP(addr)) + return localip; + } + return localip; //else a specific private address wasn't found - return what we've got +} + +static unsigned short GetLocalPort(SOCKET sock) +{ + int ret; + struct sockaddr_in saddr; + int saddrlen = sizeof(saddr); + ret = getsockname(sock,(struct sockaddr *)&saddr, &saddrlen); + if (gsiSocketIsError(ret)) + return 0; + return saddr.sin_port; +} + +unsigned int NameToIp(const char *name) +{ + unsigned int ret; + struct hostent *hent; + + ret = inet_addr(name); + + if (ret == INADDR_NONE) + { + hent = gethostbyname(name); + if (!hent) + return 0; + ret = *(unsigned int *)hent->h_addr_list[0]; + } + return ret; +} + +int DiscoverReachability(SOCKET sock, unsigned int ip, unsigned short port, int portType) +{ + NatNegPacket p; + SOCKADDR_IN addr; + int len = sizeof(SOCKADDR_IN); + int success = 1; + + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Comment, + "(%d) Sending ERT Request to %s\n", portType, AddressToString(ip, port, NULL)); + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = ip; + addr.sin_port = htons(port); + + memset(&p, 0, sizeof(p)); + p.magic[0] = NN_MAGIC_0; + p.magic[1] = NN_MAGIC_1; + p.magic[2] = NN_MAGIC_2; + p.magic[3] = NN_MAGIC_3; + p.magic[4] = NN_MAGIC_4; + p.magic[5] = NN_MAGIC_5; + p.version = NN_PROTVER; + p.packettype = NN_NATIFY_REQUEST; + p.cookie = (int)htonl(NATIFY_COOKIE); + p.Packet.Init.porttype = (unsigned char)portType; + + success = sendto(sock, (const char *)&p, sizeof(p), 0, (SOCKADDR *)&addr, sizeof(addr)); + + GSI_UNUSED(len); + return(success); +} + +int DiscoverMapping(SOCKET sock, unsigned int ip, unsigned short port, int portType, int id) +{ + NatNegPacket p; + SOCKADDR_IN addr; + int len = sizeof(SOCKADDR_IN); + int success; + + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Comment, + "Sending ADDRESS CHECK %d to %s\n", id, AddressToString(ip, port, NULL)); + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = ip; + addr.sin_port = htons(port); + + memset(&p, 0, sizeof(p)); + p.magic[0] = NN_MAGIC_0; + p.magic[1] = NN_MAGIC_1; + p.magic[2] = NN_MAGIC_2; + p.magic[3] = NN_MAGIC_3; + p.magic[4] = NN_MAGIC_4; + p.magic[5] = NN_MAGIC_5; + p.version = NN_PROTVER; + p.packettype = NN_ADDRESS_CHECK; + p.cookie = (int)htonl(id); + p.Packet.Init.porttype = (unsigned char)portType; + + success = sendto(sock, (const char *)&p, sizeof(p), 0, (SOCKADDR *)&addr, sizeof(addr)); + + GSI_UNUSED(len); + return(success); +} + +void OutputMapping(const AddressMapping * theMap) +{ + if(theMap == NULL) + { + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Comment, + "ERROR: Port mapping not available."); + } + else + { + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Comment, + "Discovered map: [%s] -> [%s]\n", AddressToString(theMap->privateIp, theMap->privatePort, NULL), + AddressToString(theMap->publicIp, theMap->publicPort, NULL)); + } +} + +static int Think(SOCKET sock, NAT * nat) +{ + static char data[NNINBUF_LEN]; + int length; + unsigned char ptype; + NatNegPacket p; + struct sockaddr_in saddr; + int saddrlen = sizeof(struct sockaddr_in); + + // Is natification complete? + if(gotERT1 && gotERT2 && gotERT3 && + gotADDRESS1a && gotADDRESS1b && gotADDRESS2 && gotADDRESS3) + { + // Don't need to wait any longer, got all the stuff. + return(gsi_false); + } + + // Process incoming data if there is any. + if(sock != INVALID_SOCKET) + { + // Check if there is data. + while(CanReceiveOnSocket(sock)) + { + length = recvfrom(sock, data, NNINBUF_LEN, 0, (struct sockaddr *)&saddr, &saddrlen); + + if (gsiSocketIsError(length)) + { + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "RECV SOCKET ERROR: %d\n", GOAGetLastError(sock)); + break; + } + + if (memcmp(data, NNMagicData, NATNEG_MAGIC_LEN) != 0) + return(gsi_true); + + ptype = *(unsigned char *)(data + offsetof(NatNegPacket, packettype)); + + if (length < INITPACKET_SIZE) + return(gsi_true); + + if(ptype == NN_ERTTEST) + { + memcpy(&p, data, INITPACKET_SIZE); + + switch(p.Packet.Init.porttype) + { + case NN_PT_NN1: + // Got the solicited ERT reply. + gotERT1 = gsi_true; + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Comment, + "(1) Got the solicited ERT from: %s\n", AddressToString(saddr.sin_addr.s_addr, ntohs(saddr.sin_port), NULL)); + break; + case NN_PT_NN2: + // Got the unsolicited IP ERT reply. + nat->ipRestricted = gsi_false; + gotERT2 = gsi_true; + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Comment, + "(2) Got the unsolicited (address) ERT from: %s\n", AddressToString(saddr.sin_addr.s_addr, ntohs(saddr.sin_port), NULL)); + break; + case NN_PT_NN3: + // Got the unsolicited IP&Port ERT reply. + nat->portRestricted = gsi_false; + gotERT3 = gsi_true; + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Comment, + "(3) Got the unsolicited (port) ERT from: %s\n", AddressToString(saddr.sin_addr.s_addr, ntohs(saddr.sin_port), NULL)); + break; + } + + } + else if(ptype == NN_ADDRESS_REPLY) + { + memcpy(&p, data, INITPACKET_SIZE); + + p.cookie = (int)ntohl(p.cookie); + + switch(p.cookie) + { + case packet_map1a: + gotADDRESS1a = gsi_true; break; + case packet_map1b: + gotADDRESS1b = gsi_true; break; + case packet_map2: + gotADDRESS2 = gsi_true; break; + case packet_map3: + gotADDRESS3 = gsi_true; break; + } + + nat->mappings[p.cookie].privateIp = GetLocalIP(); + nat->mappings[p.cookie].privatePort = ntohs(GetLocalPort(sock)); + nat->mappings[p.cookie].publicIp = p.Packet.Init.localip; + nat->mappings[p.cookie].publicPort = ntohs(p.Packet.Init.localport); + + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Comment, + "Got ADDRESS REPLY %d from: %s\n", p.cookie, AddressToString(saddr.sin_addr.s_addr, ntohs(saddr.sin_port), NULL)); + OutputMapping(&nat->mappings[p.cookie]); + } + + if (sock == INVALID_SOCKET) + break; + } + } + + return(gsi_true); +} + +int NatifyThink(SOCKET sock, NAT * nat) +{ + return(Think(sock, nat)); +} + +gsi_bool DetermineNatType(NAT * nat) +{ + // Initialize. + nat->natType = unknown; + nat->promiscuity = promiscuity_not_applicable; + nat->qr2Compatible = gsi_true; + + // Some of the address mappings are crucial in determining the NAT type. + // If we don't have them, then we should not proceed. + if(nat->mappings[packet_map1a].publicIp == 0 || + nat->mappings[packet_map2].publicIp == 0 || + nat->mappings[packet_map3].publicIp == 0) + return(gsi_false); + + // Is there a NAT? + if(!nat->portRestricted && + !nat->ipRestricted && + (nat->mappings[packet_map1a].publicIp == nat->mappings[packet_map1a].privateIp)) + { + nat->natType = no_nat; + } + else if(nat->mappings[packet_map1a].publicIp == nat->mappings[packet_map1a].privateIp) + { + nat->natType = firewall_only; + } + else + { + // What type of NAT is it? + if(!nat->ipRestricted && + !nat->portRestricted && + (abs(nat->mappings[packet_map3].publicPort - nat->mappings[packet_map2].publicPort) >= 1)) + { + nat->natType = symmetric; + nat->promiscuity = promiscuous; + } + else if(nat->ipRestricted && + !nat->portRestricted && + (abs(nat->mappings[packet_map3].publicPort - nat->mappings[packet_map2].publicPort) >= 1)) + { + nat->natType = symmetric; + nat->promiscuity = port_promiscuous; + } + else if(!nat->ipRestricted && + nat->portRestricted && + (abs(nat->mappings[packet_map3].publicPort - nat->mappings[packet_map2].publicPort) >= 1)) + { + nat->natType = symmetric; + nat->promiscuity = ip_promiscuous; + } + else if(nat->ipRestricted && + nat->portRestricted && + (abs(nat->mappings[packet_map3].publicPort - nat->mappings[packet_map2].publicPort) >= 1)) + { + nat->natType = symmetric; + nat->promiscuity = not_promiscuous; + } + else if(nat->portRestricted) + nat->natType = port_restricted_cone; + else if(nat->ipRestricted && !nat->portRestricted) + nat->natType = restricted_cone; + else if(!nat->ipRestricted && !nat->portRestricted) + nat->natType = full_cone; + else + nat->natType = unknown; + } + + // What is the port mapping behavior? + if(nat->mappings[packet_map1a].publicPort == nat->mappings[packet_map1a].privatePort && + nat->mappings[packet_map2].publicPort == nat->mappings[packet_map2].privatePort && + nat->mappings[packet_map3].publicPort == nat->mappings[packet_map3].privatePort) + // Using private port as the public port. + nat->mappingScheme = private_as_public; + else if(nat->mappings[packet_map1a].publicPort == nat->mappings[packet_map2].publicPort && + nat->mappings[packet_map2].publicPort == nat->mappings[packet_map3].publicPort) + // Using the same public port for all requests from the same private port. + nat->mappingScheme = consistent_port; + else if(nat->mappings[packet_map1a].publicPort == nat->mappings[packet_map1a].privatePort && + nat->mappings[packet_map3].publicPort - nat->mappings[packet_map2].publicPort == 1) + // Using private port as the public port for the first mapping. + // Using an incremental (+1) port mapping scheme there after. + nat->mappingScheme = mixed; + else if(nat->mappings[packet_map3].publicPort - nat->mappings[packet_map2].publicPort == 1) + // Using an incremental (+1) port mapping scheme. + nat->mappingScheme = incremental; + else + // Unrecognized port mapping scheme. + nat->mappingScheme = unrecognized; + + if(nat->mappings[packet_map1b].publicPort != 0 && nat->mappings[packet_map1a].publicPort != nat->mappings[packet_map1b].publicPort) + { + // NOTE: The NAT maps different ports for the same destination. + // Game servers may not be properly reported to the GameSpy backend. + nat->qr2Compatible = gsi_false; + } + + return(gsi_true); +} diff --git a/code/gamespy/natneg/NATify.h b/code/gamespy/natneg/NATify.h new file mode 100644 index 00000000..217d9611 --- /dev/null +++ b/code/gamespy/natneg/NATify.h @@ -0,0 +1,45 @@ +#if !defined(AFX_NATIFY_H__B8FF4369_8789_4674_8569_3D52CE8CA890__INCLUDED_) +#define AFX_NATIFY_H__B8FF4369_8789_4674_8569_3D52CE8CA890__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + + +#define NATIFY_COOKIE 777 +#define NATIFY_TIMEOUT 10000 +#define NATIFY_STATUS_STEPS (NATIFY_TIMEOUT / 1000) + 7 + +typedef enum { packet_map1a, packet_map2, packet_map3, packet_map1b, NUM_PACKETS } NatifyPacket; +typedef enum { no_nat, firewall_only, full_cone, restricted_cone, port_restricted_cone, symmetric, unknown, NUM_NAT_TYPES } NatType; +typedef enum { promiscuous, not_promiscuous, port_promiscuous, ip_promiscuous, promiscuity_not_applicable, NUM_PROMISCUITY_TYPES } NatPromiscuity; +typedef enum { unrecognized, private_as_public, consistent_port, incremental, mixed, NUM_MAPPING_SCHEMES } NatMappingScheme; + +typedef struct _AddressMapping { + unsigned int privateIp; + unsigned short privatePort; + unsigned int publicIp; + unsigned short publicPort; +} AddressMapping; + +typedef struct _NAT { + char brand[32]; + char model[32]; + char firmware[64]; + gsi_bool ipRestricted; + gsi_bool portRestricted; + NatPromiscuity promiscuity; + NatType natType; + NatMappingScheme mappingScheme; + AddressMapping mappings[4]; + gsi_bool qr2Compatible; +} NAT; + +int DiscoverReachability(SOCKET sock, unsigned int ip, unsigned short port, int portType); +int DiscoverMapping(SOCKET sock, unsigned int ip, unsigned short port, int portType, int id); +int NatifyThink(SOCKET sock, NAT * nat); +gsi_bool DetermineNatType(NAT * nat); +void OutputMapping(const AddressMapping * theMap); + + +#endif // !defined(AFX_NATIFY_H__B8FF4369_8789_4674_8569_3D52CE8CA890__INCLUDED_) diff --git a/code/gamespy/natneg/changelog.txt b/code/gamespy/natneg/changelog.txt new file mode 100644 index 00000000..b7935d81 --- /dev/null +++ b/code/gamespy/natneg/changelog.txt @@ -0,0 +1,111 @@ +Changelog for: GameSpy NAT Negotiation SDK +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 2.03.01 DES FEATURE Support for match queuing +12-12-2007 2.03.00 RMV RELEASE Released to Developer Site +08-06-2007 2.02.00 RMV RELEASE Released to Developer Site +07-10-2007 2.01.03 RMV OTHER Raised test Project warning levels (to 4) and fixed compiler warnings +06-13-2007 2.01.02 SAH FIX Fixed a bug where a fast report return could cancel NN prior to completion +05-16-2007 2.01.01 DES FIX Replaced two while(1) statements +12-15-2006 2.01.00 MJW RELEASE Released to Developer Site +12-13-2006 2.00.09 SN FIX Named a union to get rid of a warning +11-13-2006 2.00.08 SAH FIX Fixed a bug where negotiateSock was never closed, even when using a gamesocket +10-05-2006 2.00.07 SAH FIX Updated MacOSX Makefile +09-28-2006 2.00.06 SAH FIX Fixed PS3 project to work with PS3 095.00x SDK; changed included libaries in linker input. +08-24-2006 2.00.05 SAH FIX Fixed VC7 Project file; removed ';' after PRE_ALIGN call in simpletest to get rid of compiler warning +08-18-2006 2.00.04 SN FIX Renamed a parameter of the OutputMapping function due to standard c++ lib conflict +08-02-2006 2.00.03 SAH RELEASE Releasing to developer site +07-31-2006 2.00.03 SAH FIX Fixed PS3 project file - added post-build step to create *.SELF for execution + SAH FIX Fixed Linux makefile +07-25-2006 2.00.02 SAH FIX Fixed NITRO project, include for crt0.o now above others so it add correct file + SAH FIX Fixed some implicit typecasts and unused variables + SAH FIX Changed all instances of BOOL to gsi_bool, FALSE to gsi_false, TRUE to gsi_true + SAH FIX Made some functions static, or included prototypes to get rid of compiler warnings + SAH FIX Fixed PS3, PSP, PS2 projects to include Natify.c,h +07-24-2006 2.00.01 SAH FIX Removed #ifdef _PS3 for socket calls (changed platformSocket.h to typecast calls) +07-21-2006 2.00.00 BMS FEATURE New version 3 of the NN protocol - SDK is now version 2 +07-06-2006 1.00.45 SAH FIX Fixed PSP project file to not explicitly include the PSP linker file +06-30-2006 1.00.44 SAH FIX Fixed NITRO project & linker command file (to work with CW 2.0/NitroSDK 3.1) + SAH FIX Fixed Linux makefile +05-31-2006 1.00.43 SAH RELEASE Releasing to developer site + SAH FIX Fixed Linux makefile +05-30-2006 1.00.42 SAH FIX Fixed PS3 projects to work with PS3(084_001 SDK) + Added PRE_ALIGN, POST_ALIGN for mem_managed to simpletest +05-25-2006 1.00.41 SAH FIX Changed PSP project warning levels +05-22-2006 1.00.40 SAH FIX Changed ESTRING to unsigned int to remove warnings +05-19-2006 1.00.39 SAH FIX Added gsTestMain.c to nitro CodeWarrior project +05-15-2006 1.00.38 SAH FIX Added "PS3 Release" configuration to project +05-09-2006 1.00.37 SN FIX Changed the output statements in simple to be more descriptive +04-25-2006 1.00.36 SAH RELEASE Releasing to developer site +04-24-2006 1.00.36 SAH FIX Fixed Nitro Project files to work on build machine +04-20-2006 1.00.35 SAH FIX Moved GSI_UNUSED call above return statement +04-20-2006 1.00.34 SAH FIX Added _PS3 wrapper typecast for socket calls +02-13-2006 1.00.33 DDW FIX Fixed crash when NN DNS address fails to resolve +01-27-2006 1.00.32 SN RELEASE Releasing to developer site +01-27-2006 1.00.32 SN OTHER Added psp prodg project and solution to sgv +12-22-2005 1.00.32 SN OTHER Cleaned up project and added any missing code common code +11-17-2005 1.00.31 DES FIX Added error check to simpletest. + DES FIX Updated Nitro Makefile. +11-14-2005 1.00.30 DES FIX Updated the OSX Makefile. + DES FEATURE Added GSI_DOMAIN_NAME support. +11-09-2005 1.00.29 BED FEATURE Added PS3 makefile for simpletest. +10-07-2005 1.00.28 DES FEATURE Reduced number of retries, so failures timeout sooner. + DES FEATURE Added packet types for address check feature. +09-25-2005 1.00.27 DES FEATURE Updated DS support + DES FIX Don't send additional ping packets if not needed + DES FEATURE Added game-specific DNS +07-28-2005 1.00.26 SN RELEASE Releasing to developer site. +07-15-2005 1.00.26 SN FIX Fixed included file for availability check +06-03-2005 1.00.25 SN RELEASE Releasing to developer site. +05-04-2005 1.00.25 SN OTHER Created Visual Studio .NET project +04-28-2005 1.00.25 SN RELEASE Releasing to developer site. +04-27-2005 1.00.25 DES RELEASE Limited release to Nintendo DS developers. +04-25-2005 1.00.25 DES CLEANUP Replaced old DP() code with debug logging. +04-04-2005 1.00.24 SN RELEASE Releasing to developer site. +03-14-2005 1.00.24 DES FEATURE Nintendo DS support +01-27-2003 1.00.23 DES FIX Fixed custom SN sendto and moved it to nonport +09-16-2004 1.00.22 SN RELEASE Releasing to developer site. +08-27-2004 1.00.22 DES CLEANUP Fixed warnings under OSX + DES CLEANUP Removed #pragma comment for linking with winsock (in nonport now) + DES CLEANUP Updated Win32 project configurations +08-25-2004 1.00.21 DES FEATURE Added OSX makefile +08-05-2004 1.00.20 SN FIX Releasing to developer site. +07-19-2004 1.00.20 SN FIX Updated code with explicit casts to remove implicit cast error + when compiling at highest level and warnings treated as errors. +06-18-2004 1.00.19 BED RELEASE Releasing to developer site. +06-17-2004 1.00.19 BED FEATURE Added PS2 Insock (LibNet) support +11-10-2003 1.00.18 DES RELEASE Releasing to developer site. +11-07-2003 1.00.18 DES FIX Updated the linux and PS2 makefiles. +11-04-2003 1.00.17 DES FEATURE Added availability check code. +10-30-2003 1.00.16 DES FEATURE Pass the gamename to the backend when connecting or searching. +07-24-2003 1.00.15 DES RELEASE Releasing to developer site. +07-18-2003 1.00.15 BED FEATURE Added CodeWarrior (PS2) sample project file. + BED CLEANUP General cleanup to remove CodeWarrior warnings. +07-17-2003 1.00.14 DES CLEANUP Cleaned up the PS2 Makefile, it now uses Makefile.commmon. +07-16-2003 1.00.13 BED FIX Added #ifndef to avoid #pragma that PS2 doesn't understand + DES FIX Changed a few __mips64 checks to _PS2 checks. + BED FEATURE Added ProDG sample project files. +05-09-2003 1.00.12 DES CLEANUP Removed Dreamcast support. +04-21-2003 1.00.11 DES FIX Changed WSAGetLastError call in a DP macro to GOAGetLastError. +04-07-2003 1.00.10 DES FIX Fixed byte alignment issues with InitPacket. + RELEASE Releasing to developer site. +03-26-2003 1.00.09 DES RELEASE Releasing to developer site. +03-24-2003 1.00.09 DES OTHER Changed interal IP code to use the common code's IsPrivateIP. +03-21-2003 1.00.08 DDW FEATURE Added support for reporting "internal" address to allow for + negotiation of two users behind the same NAT. + Added NNFreeNegotiateList function to free all memory once done. +03-17-2003 1.00.07 DES FIX Added extern "C" to natneg.h for when compiling with C++. +02-05-2003 1.00.06 DES RELEASE Releasing to developer site. +02-05-2003 1.00.06 DES CLEANUP Switched to use the common code CanReceiveOnSocket and CanSendOnSocket. +12-19-2002 1.00.05 DES RELEASE Releasing to developer site. +12-16-2002 1.00.05 DES CLEANUP Removed calls to GOAClearSocketError. +12-13-2002 1.00.04 DES FEATURE Added PS2 eenet stack support. +12-03-2002 1.00.03 DES RELEASE Releasing to developer site. +12-03-2002 1.00.03 DES FEATURE Added a Linux Makefile. +11-22-2002 1.00.02 DES RELEASE Releasing to developer site. +11-21-2002 1.00.02 DES FIX Hardcoded packet struct sizes to get around PS2/compiler packing bug. +11-20-2002 1.00.01 DES FEATURE Added support for compiling on the PS2. +09-26-2002 1.00.00 DES RELEASE Limited release on developer site +09-25-2002 1.00.00 DDW OTHER Changelog started diff --git a/code/gamespy/natneg/natneg.c b/code/gamespy/natneg/natneg.c new file mode 100644 index 00000000..2f778e75 --- /dev/null +++ b/code/gamespy/natneg/natneg.c @@ -0,0 +1,806 @@ +#include "nninternal.h" +#include "../darray.h" +#include "../common/gsAvailable.h" +#include +#include +#include "NATify.h" + +unsigned char NNMagicData[] = {NN_MAGIC_0, NN_MAGIC_1, NN_MAGIC_2, NN_MAGIC_3, NN_MAGIC_4, NN_MAGIC_5}; +struct _NATNegotiator +{ + SOCKET negotiateSock; + SOCKET gameSock; + int cookie; + int clientindex; + NegotiateState state; + int initAckRecv[4]; + int retryCount; + int maxRetryCount; + unsigned long retryTime; + unsigned int guessedIP; + unsigned short guessedPort; + unsigned char gotRemoteData; + unsigned char sendGotRemoteData; + NegotiateProgressFunc progressCallback; + NegotiateCompletedFunc completedCallback; + void *userdata; + NegotiateResult result; + SOCKET connectedSocket; + struct sockaddr_in remoteaddr; +}; + +typedef struct _NATNegotiator *NATNegotiator; + +DArray negotiateList = NULL; + +char *Matchup1Hostname; +char *Matchup2Hostname; +char *Matchup3Hostname; + +unsigned int matchup1ip = 0; +unsigned int matchup2ip = 0; +unsigned int matchup3ip = 0; + +NAT nat; +NatDetectionResultsFunc natifyCallback; +static SOCKET mappingSock = INVALID_SOCKET; +static SOCKET ertSock = INVALID_SOCKET; +static gsi_time natifyStartTime; +static gsi_bool activeNatify = gsi_false; +static NatType natType = unknown; +static NatMappingScheme natMappingScheme = unrecognized; + + +static NATNegotiator FindNegotiatorForCookie(int cookie) +{ + int i; + if (negotiateList == NULL) + return NULL; + for (i = 0 ; i < ArrayLength(negotiateList) ; i++) + { + //we go backwards in case we need to remove one.. + NATNegotiator neg = (NATNegotiator)ArrayNth(negotiateList, i); + if (neg->cookie == cookie) + return neg; + } + return NULL; +} + +static NATNegotiator AddNegotiator() +{ + + struct _NATNegotiator _neg; + + + memset(&_neg, 0, sizeof(_neg)); + + if (negotiateList == NULL) + negotiateList = ArrayNew(sizeof(_neg), 4, NULL); + + ArrayAppend(negotiateList, &_neg); + + return (NATNegotiator)ArrayNth(negotiateList, ArrayLength(negotiateList) - 1); +} + +static void RemoveNegotiator(NATNegotiator neg) +{ + int i; + for (i = 0 ; i < ArrayLength(negotiateList) ; i++) + { + //we go backwards in case we need to remove one.. + if (neg == (NATNegotiator)ArrayNth(negotiateList, i)) + { + ArrayRemoveAt(negotiateList, i); + return; + + } + } +} + +void NNFreeNegotiateList() +{ + if (negotiateList != NULL) + { + ArrayFree(negotiateList); + negotiateList = NULL; + } +} + +static int CheckMagic(char *data) +{ + return (memcmp(data, NNMagicData, NATNEG_MAGIC_LEN) == 0); +} + +static void SendPacket(SOCKET sock, unsigned int toaddr, unsigned short toport, void *data, int len) +{ + struct sockaddr_in saddr; + saddr.sin_family = AF_INET; + saddr.sin_port = htons(toport); + saddr.sin_addr.s_addr = toaddr; + sendto(sock, (char *)data, len, 0, (struct sockaddr *)&saddr, sizeof(saddr)); +} + +static unsigned int GetLocalIP() +{ + int num_local_ips; + struct hostent *phost; + struct in_addr *addr; + unsigned int localip = 0; + phost = getlocalhost(); + if (phost == NULL) + return 0; + for (num_local_ips = 0 ; ; num_local_ips++) + { + if (phost->h_addr_list[num_local_ips] == 0) + break; + addr = (struct in_addr *)phost->h_addr_list[num_local_ips]; + if (addr->s_addr == htonl(0x7F000001)) + continue; + localip = addr->s_addr; + + if(IsPrivateIP(addr)) + return localip; + } + return localip; //else a specific private address wasn't found - return what we've got +} + +static unsigned short GetLocalPort(SOCKET sock) +{ + int ret; + struct sockaddr_in saddr; + int saddrlen = sizeof(saddr); + + ret = getsockname(sock,(struct sockaddr *)&saddr, &saddrlen); + + if (gsiSocketIsError(ret)) + return 0; + return saddr.sin_port; +} + +static void SendReportPacket(NATNegotiator neg) +{ + NatNegPacket p; + + memcpy(p.magic, NNMagicData, NATNEG_MAGIC_LEN); + p.version = NN_PROTVER; + p.packettype = NN_REPORT; + p.cookie = (int)htonl(neg->cookie); + p.Packet.Report.clientindex = (unsigned char)neg->clientindex; + p.Packet.Report.negResult = (unsigned char)(neg->result==nr_success?gsi_true:gsi_false); + p.Packet.Report.natType = natType; + p.Packet.Report.natMappingScheme = natMappingScheme; + + if(strlen(__GSIACGamename) > 0) + memcpy(&p.Packet.Report.gamename, __GSIACGamename, sizeof(p.Packet.Report.gamename)); + + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "Sending REPORT to %s:%d (result: %d)\n", inet_ntoa(*(struct in_addr *)&matchup1ip), MATCHUP_PORT1, p.Packet.Report.negResult); + + SendPacket(neg->negotiateSock, matchup1ip, MATCHUP_PORT1, &p, REPORTPACKET_SIZE); +} + +static void StartReport(NATNegotiator neg, NegotiateResult result, SOCKET socket, struct sockaddr_in *remoteaddr) +{ + neg->result = result; + neg->connectedSocket = socket; + if(remoteaddr != NULL) + memcpy(&neg->remoteaddr, remoteaddr, sizeof(neg->remoteaddr)); + + if(result == nr_inittimeout || result == nr_deadbeatpartner) + { + neg->state = ns_finished; +// neg->state = ns_reportack; + neg->completedCallback(neg->result, neg->connectedSocket, (struct sockaddr_in *)&neg->remoteaddr, neg->userdata); + + // Close the socket here - no need to keep it open, connected Socket is INVALID + //neg->negotiateSock = INVALID_SOCKET; + NNCancel(neg->cookie); + } + else + { + SendReportPacket(neg); + neg->state = ns_reportsent; + neg->retryTime = current_time() + REPORT_RETRY_TIME; + neg->retryCount = 0; + neg->maxRetryCount = REPORT_RETRY_COUNT; + } +} + +static void SendInitPackets(NATNegotiator neg) +{ + char buffer[INITPACKET_SIZE + sizeof(__GSIACGamename)]; + + NatNegPacket * p = (NatNegPacket *)buffer; + unsigned int localip; + unsigned short localport; + int packetlen; + + memcpy(p->magic, NNMagicData, NATNEG_MAGIC_LEN); + p->version = NN_PROTVER; + p->packettype = NN_INIT; + p->cookie = (int)htonl((unsigned int)neg->cookie); + p->Packet.Init.clientindex = (unsigned char)neg->clientindex; + p->Packet.Init.usegameport = (unsigned char)((neg->gameSock == INVALID_SOCKET) ? 0 : 1); + localip = ntohl(GetLocalIP()); + //ip + buffer[INITPACKET_ADDRESS_OFFSET] = (char)((localip >> 24) & 0xFF); + buffer[INITPACKET_ADDRESS_OFFSET+1] = (char)((localip >> 16) & 0xFF); + buffer[INITPACKET_ADDRESS_OFFSET+2] = (char)((localip >> 8) & 0xFF); + buffer[INITPACKET_ADDRESS_OFFSET+3] = (char)(localip & 0xFF); + //port (this may not be determined until the first packet goes out) + buffer[INITPACKET_ADDRESS_OFFSET+4] = 0; + buffer[INITPACKET_ADDRESS_OFFSET+5] = 0; + // add the gamename to all requests + strcpy(buffer + INITPACKET_SIZE, __GSIACGamename); + packetlen = (INITPACKET_SIZE + (int)strlen(__GSIACGamename) + 1); + if (p->Packet.Init.usegameport && !neg->initAckRecv[NN_PT_GP]) + { + p->Packet.Init.porttype = NN_PT_GP; + + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "Sending INIT (GP) to %s:%d...\n", inet_ntoa(*(struct in_addr *)&matchup1ip), MATCHUP_PORT1); + + SendPacket(neg->gameSock, matchup1ip, MATCHUP_PORT1, p, packetlen); + } + + if (!neg->initAckRecv[NN_PT_NN1]) + { + p->Packet.Init.porttype = NN_PT_NN1; + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "Sending INIT (NN1) to %s:%d...\n", inet_ntoa(*(struct in_addr *)&matchup1ip), MATCHUP_PORT1); + + SendPacket(neg->negotiateSock, matchup1ip, MATCHUP_PORT1, p, packetlen); + } + + //this should be determined now... + localport = ntohs(GetLocalPort((p->Packet.Init.usegameport) ? neg->gameSock : neg->negotiateSock)); + buffer[INITPACKET_ADDRESS_OFFSET+4] = (char)((localport >> 8) & 0xFF); + buffer[INITPACKET_ADDRESS_OFFSET+5] = (char)(localport & 0xFF); + + if (!neg->initAckRecv[NN_PT_NN2]) + { + p->Packet.Init.porttype = NN_PT_NN2; + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "Sending INIT (NN2) to %s:%d...\n", inet_ntoa(*(struct in_addr *)&matchup2ip), MATCHUP_PORT2); + + SendPacket(neg->negotiateSock, matchup2ip, MATCHUP_PORT2, p, packetlen); + } + + if (!neg->initAckRecv[NN_PT_NN3]) + { + p->Packet.Init.porttype = NN_PT_NN3; + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "Sending INIT (NN3) to %s:%d...\n", inet_ntoa(*(struct in_addr *)&matchup3ip), MATCHUP_PORT3); + + SendPacket(neg->negotiateSock, matchup3ip, MATCHUP_PORT3, p, packetlen); + } + + neg->retryTime = current_time() + INIT_RETRY_TIME; + neg->maxRetryCount = INIT_RETRY_COUNT; +} + +static void SendPingPacket(NATNegotiator neg) +{ + NatNegPacket p; + + memcpy(p.magic, NNMagicData, NATNEG_MAGIC_LEN); + p.version = NN_PROTVER; + p.packettype = NN_CONNECT_PING; + p.cookie = (int)htonl((unsigned int)neg->cookie); + p.Packet.Connect.remoteIP = neg->guessedIP; + p.Packet.Connect.remotePort = htons(neg->guessedPort); + p.Packet.Connect.gotyourdata = neg->gotRemoteData; + p.Packet.Connect.finished = (unsigned char)((neg->state == ns_connectping) ? 0 : 1); + +////////////// +// playing with a way to re-sync with the NAT's port mappings in the case the guess is off: +//if(neg->retryCount >= 3 && neg->retryCount % 3 == 0) neg->guessedPort++; +////////////// + + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "Sending PING to %s:%d (got remote data: %d)\n", inet_ntoa(*(struct in_addr *)&neg->guessedIP), neg->guessedPort, neg->gotRemoteData); + SendPacket((neg->gameSock != INVALID_SOCKET) ? neg->gameSock : neg->negotiateSock, neg->guessedIP, neg->guessedPort, &p, CONNECTPACKET_SIZE); + neg->retryTime = current_time() + PING_RETRY_TIME; + neg->maxRetryCount = PING_RETRY_COUNT; + if(neg->gotRemoteData) + neg->sendGotRemoteData = 1; +} + +static gsi_bool CheckNatifyStatus(SOCKET sock) +{ + gsi_bool active = gsi_true; + gsi_bool success = gsi_false; + + if(sock != INVALID_SOCKET) + { + if(current_time() - natifyStartTime < NATIFY_TIMEOUT) + active = NatifyThink(sock, &nat); + else + active = gsi_false; + + if(!active) + { + success = DetermineNatType(&nat); + natifyCallback(success, nat); + + natType = nat.natType; + natMappingScheme = nat.mappingScheme; + + // Clean up natify's socks. + if (mappingSock != INVALID_SOCKET) + closesocket(mappingSock); + mappingSock = INVALID_SOCKET; + if (ertSock != INVALID_SOCKET) + closesocket(ertSock); + ertSock = INVALID_SOCKET; + } + } + + return(active); +} + +NegotiateError NNBeginNegotiation(int cookie, int clientindex, NegotiateProgressFunc progresscallback, NegotiateCompletedFunc completedcallback, void *userdata) +{ + return NNBeginNegotiationWithSocket(INVALID_SOCKET, cookie, clientindex, progresscallback, completedcallback, userdata); +} + +static unsigned int NameToIp(const char *name) +{ + unsigned int ret; + struct hostent *hent; + + ret = inet_addr(name); + + if (ret == INADDR_NONE) + { + hent = gethostbyname(name); + if (!hent) + return 0; + ret = *(unsigned int *)hent->h_addr_list[0]; + } + return ret; +} + +static unsigned int ResolveServer(const char * overrideHostname, const char * defaultHostname) +{ + const char * hostname; + char hostnameBuffer[64]; + + if(overrideHostname == NULL) + { + snprintf(hostnameBuffer, sizeof(hostnameBuffer), "%s.%s", __GSIACGamename, defaultHostname); + hostname = hostnameBuffer; + } + else + { + hostname = overrideHostname; + } + + return NameToIp(hostname); +} + +static int ResolveServers() +{ + if (matchup1ip == 0) + { + matchup1ip = ResolveServer(Matchup1Hostname, MATCHUP1_HOSTNAME); + } + + if (matchup2ip == 0) + { + matchup2ip = ResolveServer(Matchup2Hostname, MATCHUP2_HOSTNAME); + } + + if (matchup3ip == 0) + { + matchup3ip = ResolveServer(Matchup3Hostname, MATCHUP3_HOSTNAME); + } + + if (matchup1ip == 0 || matchup2ip == 0 || matchup3ip == 0) + return 0; + + return 1; +} + +NegotiateError NNStartNatDetection(NatDetectionResultsFunc resultscallback) +{ + // check if the backend is available + if(__GSIACResult != GSIACAvailable) + return ne_socketerror; + if (!ResolveServers()) + return ne_dnserror; + + activeNatify = gsi_true; + natifyCallback = resultscallback; + natifyStartTime = current_time(); + + // Assume this for now. + nat.ipRestricted = gsi_true; + nat.portRestricted = gsi_true; + + // Socket to use for external reach tests. + ertSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + // Socket to use for determining how traffic is mapped. + mappingSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + // Send reachability packets. + DiscoverReachability(ertSock, matchup1ip, MATCHUP_PORT1, NN_PT_NN1); + DiscoverReachability(ertSock, matchup1ip, MATCHUP_PORT1, NN_PT_NN2); + DiscoverReachability(ertSock, matchup2ip, MATCHUP_PORT2, NN_PT_NN3); + + // Send mapping packets. + DiscoverMapping(mappingSock, matchup1ip, MATCHUP_PORT1, NN_PT_NN1, packet_map1a); + DiscoverMapping(mappingSock, matchup1ip, MATCHUP_PORT1, NN_PT_NN1, packet_map1b); + DiscoverMapping(mappingSock, matchup2ip, MATCHUP_PORT2, NN_PT_NN2, packet_map2); + DiscoverMapping(mappingSock, matchup3ip, MATCHUP_PORT3, NN_PT_NN3, packet_map3); + + return ne_noerror; +} + +NegotiateError NNBeginNegotiationWithSocket(SOCKET gamesocket, int cookie, int clientindex, NegotiateProgressFunc progresscallback, NegotiateCompletedFunc completedcallback, void *userdata) +{ + NATNegotiator neg; + + // check if the backend is available + if(__GSIACResult != GSIACAvailable) + return ne_socketerror; + if (!ResolveServers()) + return ne_dnserror; + + neg = AddNegotiator(); + if (neg == NULL) + return ne_allocerror; + neg->gameSock = gamesocket; + neg->clientindex = clientindex; + neg->cookie = cookie; + neg->progressCallback = progresscallback; + neg->completedCallback = completedcallback; + neg->userdata = userdata; + neg->negotiateSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + neg->retryCount = 0; + neg->gotRemoteData = 0; + neg->sendGotRemoteData = 0; + neg->guessedIP = 0; + neg->guessedPort = 0; + neg->maxRetryCount = 0; + neg->result = nr_noresult; + if (neg->negotiateSock == INVALID_SOCKET) + { + RemoveNegotiator(neg); + return ne_socketerror; + } + SendInitPackets(neg); + +#if defined(GSI_COMMON_DEBUG) + { + struct sockaddr_in saddr; + int namelen = sizeof(saddr); + + getsockname(neg->negotiateSock, (struct sockaddr *)&saddr, &namelen); + + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "Negotiate Socket: %d\n", ntohs(saddr.sin_port)); + } +#endif + + return ne_noerror; +} + +void NNCancel(int cookie) +{ + NATNegotiator neg = FindNegotiatorForCookie(cookie); + if (neg == NULL) + return; + if (neg->negotiateSock != INVALID_SOCKET) + closesocket(neg->negotiateSock); + neg->negotiateSock = INVALID_SOCKET; + neg->state = ns_canceled; +} + +static void NegotiateThink(NATNegotiator neg) +{ + //check for any incoming data + static char indata[NNINBUF_LEN]; //256 byte input buffer + struct sockaddr_in saddr; + int saddrlen = sizeof(struct sockaddr_in); + int error; + + if(activeNatify) + { + activeNatify = CheckNatifyStatus(mappingSock); + activeNatify = CheckNatifyStatus(ertSock); + } + + if(neg == NULL) + return; + + if (neg->state == ns_canceled) //we need to remove it + { + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Memory, GSIDebugLevel_Notice, + "Removing canceled negotiator\n"); + RemoveNegotiator(neg); + return; + } + + if (neg->negotiateSock != INVALID_SOCKET) + { + //first, socket processing + while (CanReceiveOnSocket(neg->negotiateSock)) + { + error = recvfrom(neg->negotiateSock, indata, NNINBUF_LEN, 0, (struct sockaddr *)&saddr, &saddrlen); + + if (gsiSocketIsError(error)) + { + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "RECV SOCKET ERROR: %d\n", GOAGetLastError(neg->negotiateSock)); + break; + } + + NNProcessData(indata, error, &saddr); + if (neg->state == ns_canceled) + break; + + if (neg->negotiateSock == INVALID_SOCKET) + break; + } + } + + if (neg->state == ns_initsent || neg->state == ns_connectping) //see if we need to resend init packets + { + if (current_time() > neg->retryTime) + { + if (neg->retryCount > neg->maxRetryCount) + { + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "RETRY FAILED...\n"); + if(neg->state == ns_initsent) + StartReport(neg, nr_inittimeout, INVALID_SOCKET, NULL); + else + StartReport(neg, nr_pingtimeout, INVALID_SOCKET, NULL); + } else + { + + neg->retryCount++; + if (neg->state == ns_initsent) //resend init packets + SendInitPackets(neg); + else + SendPingPacket(neg); //resend ping packet + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "[retry]\n"); + } + + } + } + + if (neg->state == ns_finished && current_time() > neg->retryTime) //check if it is ready to be removed + { + struct sockaddr_in saddr; + saddr.sin_family = AF_INET; + saddr.sin_port = htons(neg->guessedPort); + saddr.sin_addr.s_addr = neg->guessedIP; + + // now that we've finished processing, send off the report + if (neg->gameSock == INVALID_SOCKET) + StartReport(neg, nr_success, neg->negotiateSock, (struct sockaddr_in *)&saddr); + else + StartReport(neg, nr_success, neg->gameSock, (struct sockaddr_in *)&saddr); + } + + if (neg->state == ns_initack && current_time() > neg->retryTime) //see if the partner has timed out + { + StartReport(neg, nr_deadbeatpartner, INVALID_SOCKET, NULL); + } + + // Have we timed out sending the result report. + if(neg->state == ns_reportsent && current_time() > neg->retryTime) + { + if(neg->retryCount > neg->maxRetryCount) + { + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "REPORT retry FAILED...\n"); + + neg->completedCallback(neg->result, neg->connectedSocket, (struct sockaddr_in *)&neg->remoteaddr, neg->userdata); + + if (neg->gameSock == INVALID_SOCKET) + neg->negotiateSock = INVALID_SOCKET; //no gameSock, so don't let NNCancel close this socket + + NNCancel(neg->cookie); //mark to-be-canceled + } + else + { + SendReportPacket(neg); //resend report packet + neg->retryCount++; + neg->retryTime = current_time() + REPORT_RETRY_TIME; + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "[retry]\n"); + } + } +} + +void NNThink() +{ + int i; + + if(negotiateList == NULL || ArrayLength(negotiateList) == 0) + { + NegotiateThink(NULL); + return; + } + + for(i = ArrayLength(negotiateList) - 1 ; i >= 0 ; i--) + { + //we go backwards in case we need to remove one.. + NegotiateThink((NATNegotiator)ArrayNth(negotiateList, i)); + } +} + +static void SendConnectAck(NATNegotiator neg, struct sockaddr_in *toaddr) +{ + NatNegPacket p; + + memcpy(p.magic, NNMagicData, NATNEG_MAGIC_LEN); + p.version = NN_PROTVER; + p.packettype = NN_CONNECT_ACK; + p.cookie = (int)htonl((unsigned int)neg->cookie); + p.Packet.Init.clientindex = (unsigned char)neg->clientindex; + + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "Sending connect ack...\n"); + SendPacket(neg->negotiateSock, toaddr->sin_addr.s_addr, ntohs(toaddr->sin_port), &p, INITPACKET_SIZE); +} + +static void ProcessConnectPacket(NATNegotiator neg, NatNegPacket *p, struct sockaddr_in *fromaddr) +{ + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "Got connect packet (finish code: %d), guess: %s:%d\n", p->Packet.Connect.finished, inet_ntoa(*(struct in_addr *)&p->Packet.Connect.remoteIP), ntohs(p->Packet.Connect.remotePort)); + //send an ack.. + if (p->Packet.Connect.finished == FINISHED_NOERROR) //don't need to ack any errors + SendConnectAck(neg, fromaddr); + + + if (neg->state >= ns_connectping) + return; //don't process it any further + + if (p->Packet.Connect.finished != FINISHED_NOERROR) //call the completed callback with the error code + { + NegotiateResult errcode; + errcode = nr_unknownerror; //default if unknown + if (p->Packet.Connect.finished == FINISHED_ERROR_DEADBEAT_PARTNER) + errcode = nr_deadbeatpartner; + else if (p->Packet.Connect.finished == FINISHED_ERROR_INIT_PACKETS_TIMEDOUT) + errcode = nr_inittimeout; + StartReport(neg, errcode, INVALID_SOCKET, NULL); + return; + } + + neg->guessedIP = p->Packet.Connect.remoteIP; + neg->guessedPort = ntohs(p->Packet.Connect.remotePort); + neg->retryCount = 0; + + neg->state = ns_connectping; + neg->progressCallback(neg->state, neg->userdata); + + SendPingPacket(neg); +} + +static void ProcessPingPacket(NATNegotiator neg, NatNegPacket *p, struct sockaddr_in *fromaddr) +{ + if (neg->state < ns_connectping) + return; + + //update our guessed ip and port + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "Got ping from: %s:%d (gotmydata: %d, finished: %d)\n", inet_ntoa(fromaddr->sin_addr), ntohs(fromaddr->sin_port), p->Packet.Connect.gotyourdata, p->Packet.Connect.finished); + + neg->guessedIP = fromaddr->sin_addr.s_addr; + neg->guessedPort = ntohs(fromaddr->sin_port); + neg->gotRemoteData = 1; + + if (!p->Packet.Connect.gotyourdata) //send another packet until they have our data + SendPingPacket(neg); + else //they have our data, and we have their data - it's a connection! + { + if (neg->state == ns_connectping) //advance it + { + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "CONNECT FINISHED\n"); + + if(!neg->sendGotRemoteData) + SendPingPacket(neg); + + //we need to leave it around for a while to process any incoming data. + neg->state = ns_finished; + neg->retryTime = current_time() + FINISHED_IDLE_TIME; + + + } else if (!p->Packet.Connect.finished) + SendPingPacket(neg); + } +} + +static void ProcessInitPacket(NATNegotiator neg, NatNegPacket *p, struct sockaddr_in *fromaddr) +{ + switch (p->packettype) + { + case NN_INITACK: + //mark our init as ack'd + if (p->Packet.Init.porttype > NN_PT_NN3) + return; //invalid port + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "Got init ack for port %d\n", p->Packet.Init.porttype); + neg->initAckRecv[p->Packet.Init.porttype] = 1; + if (neg->state == ns_initsent) //see if we can advance to negack + { + if (neg->initAckRecv[NN_PT_NN1] != 0 && neg->initAckRecv[NN_PT_NN2] != 0 && neg->initAckRecv[NN_PT_NN3] != 0 && + (neg->gameSock == INVALID_SOCKET || neg->initAckRecv[NN_PT_GP] != 0)) + { + neg->state = ns_initack; + neg->retryTime = current_time() + PARTNER_WAIT_TIME; + neg->progressCallback(neg->state, neg->userdata); + } + } + break; + + case NN_ERTTEST: + //we just send the packet back where it came from.. + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "Got ERT\n"); + p->packettype = NN_ERTACK; + SendPacket(neg->negotiateSock, fromaddr->sin_addr.s_addr, ntohs(fromaddr->sin_port), p, INITPACKET_SIZE); + break; + + case NN_REPORT_ACK: + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "Got REPORT ACK\n"); + neg->state = ns_reportack; + + neg->completedCallback(neg->result, neg->connectedSocket, (struct sockaddr_in *)&neg->remoteaddr, neg->userdata); + + if (neg->gameSock == INVALID_SOCKET) + neg->negotiateSock = INVALID_SOCKET; //no gameSock, so don't let NNCancel close this socket + + NNCancel(neg->cookie); + break; + } +} + +void NNProcessData(char *data, int len, struct sockaddr_in *fromaddr) +{ + NatNegPacket p; + NATNegotiator neg; + unsigned char ptype; + + if (!CheckMagic(data)) + return; //invalid packet + + ptype = *(unsigned char *)(data + offsetof(NatNegPacket, packettype)); + + gsDebugFormat(GSIDebugCat_NatNeg, GSIDebugType_Network, GSIDebugLevel_Notice, + "Process data, packet type: %d, %d bytes (%s:%d)\n", ptype, len, inet_ntoa(fromaddr->sin_addr), ntohs(fromaddr->sin_port)); + + if (ptype == NN_CONNECT || ptype == NN_CONNECT_PING) + { //it's a connect packet + if (len < CONNECTPACKET_SIZE) + return; + memcpy(&p, data, CONNECTPACKET_SIZE); + neg = FindNegotiatorForCookie((int)ntohl((unsigned int)p.cookie)); + if (neg) + { + if (ptype == NN_CONNECT) + ProcessConnectPacket(neg, &p, fromaddr); + else + ProcessPingPacket(neg, &p, fromaddr); + } + + + } else //it's an init packet + { + if (len < INITPACKET_SIZE) + return; + memcpy(&p, data, INITPACKET_SIZE); + neg = FindNegotiatorForCookie((int)ntohl((unsigned int)p.cookie)); + if (neg) + ProcessInitPacket(neg, &p, fromaddr); + } +} diff --git a/code/gamespy/natneg/natneg.h b/code/gamespy/natneg/natneg.h new file mode 100644 index 00000000..753f67b1 --- /dev/null +++ b/code/gamespy/natneg/natneg.h @@ -0,0 +1,152 @@ + +/****** +GameSpy NAT Negotiation SDK + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + + Please see the GameSpy NAT Negotiation SDK documentation for more + information + +******/ + + +#ifndef _NATNEG_H_ +#define _NATNEG_H_ +#include "../common/gsCommon.h" +#include "NATify.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* +NAT Negotiation Packet Magic Bytes +These bytes will start each incoming packet that is part of the NAT Negotiation SDK. +If you are sharing a game socket with the SDK, you can use these bytes to determine when to +pass a packet to NNProcessData +*/ +#define NATNEG_MAGIC_LEN 6 +#define NN_MAGIC_0 0xFD +#define NN_MAGIC_1 0xFC +#define NN_MAGIC_2 0x1E +#define NN_MAGIC_3 0x66 +#define NN_MAGIC_4 0x6A +#define NN_MAGIC_5 0xB2 + +// This external array contains all 6 magic bytes - you can use it with memcmp to quickly check incoming packets for the bytes +extern unsigned char NNMagicData[]; + +/* +Possible states for the SDK. The two you will be notified for are: +ns_initack - when the NAT Negotiation server acknowledges your connection request +ns_connectping - when direct negotiation with the other client has started +*/ +typedef enum {ns_initsent, ns_initack, ns_connectping, ns_finished, ns_canceled, ns_reportsent, ns_reportack } NegotiateState; + +/* +Possible reslts of the negotiation. +nr_success: Successful negotiation, other parameters can be used to continue communications with the client +nr_deadbeatpartner: Partner did not register with the NAT Negotiation Server +nr_inittimeout: Unable to communicate with NAT Negotiation Server +nr_unknownerror: NAT Negotiation server indicated an unknown error condition +*/ +typedef enum {nr_success, nr_deadbeatpartner, nr_inittimeout, nr_pingtimeout, nr_unknownerror, nr_noresult } NegotiateResult; + +/* +Possible errors that can be returned when starting a negotiation +ne_noerror: No error +ne_allocerror: Memory allocation failed +ne_socketerror: Socket allocation failed +ne_dnserror: DNS lookup failed +*/ +typedef enum {ne_noerror, ne_allocerror, ne_socketerror, ne_dnserror} NegotiateError; + + +//Callback prototype for your progress function +typedef void (*NegotiateProgressFunc)(NegotiateState state, void *userdata); + +//Callback prototype for your negotiation completed function +typedef void (*NegotiateCompletedFunc)(NegotiateResult result, SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *userdata); + +//Callback prototype for your NAT detection results function +typedef void (*NatDetectionResultsFunc)(gsi_bool success, NAT nat); + + +/* +NNBeginNegotiation +------------------- +Starts the negotiation process. +cookie: Shared cookie value that both players will use so that the NAT Negotiation Server can match them up. +clientindex: One client must use clientindex 0, the other must use clientindex 1. +progresscallback: Callback function that will be called as the state changes +completedcallback: Callback function that will be called when negotiation is complete. +userdata: Pointer for your own use that will be passed into the callback functions. +*/ +NegotiateError NNBeginNegotiation(int cookie, int clientindex, NegotiateProgressFunc progresscallback, NegotiateCompletedFunc completedcallback, void *userdata); + + +/* +NNBeginNegotiationWithSocket +------------------- +Starts the negotiation process using the socket provided, which will be shared with the game. +Incoming traffic is not processed automatically - you will need to read the data off the socket and pass NN packets to NNProcessData +*/ +NegotiateError NNBeginNegotiationWithSocket(SOCKET gamesocket, int cookie, int clientindex, NegotiateProgressFunc progresscallback, NegotiateCompletedFunc completedcallback, void *userdata); + + +/* +NNThink +------------------- +Processes any negotiation requests that are in progress +*/ +void NNThink(); + + +/* +NNProcessData +------------------- +When sharing a socket with the NAT Negotiation SDK, you must read incoming data and pass packets that start the the NN magic bytes +to this function for processing, along with the address they came from. +*/ +void NNProcessData(char *data, int len, struct sockaddr_in *fromaddr); + + +/* +NNCancel +------------------- +Cancels a NAT Negotiation request in progress +*/ +void NNCancel(int cookie); + + +/* +NNFreeNegotiateList +------------------- +De-allocates the memory used by for the negotiate list when you are done with NAT Negotiation. +The list will be re-allocated at a later time if you start additional negotiations. +If any negotiations are outstanding this will cancel them. +*/ +void NNFreeNegotiateList(); + + +/* +NNStartNatDetection +------------------- +Detects if there is network address translation going on between the machine and the Internet. +*/ +NegotiateError NNStartNatDetection(NatDetectionResultsFunc resultscallback); + + +//Used for over-riding the default negotiation hostnames. Should not be used normally. +extern char *Matchup2Hostname; +extern char *Matchup1Hostname; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/natneg/nninternal.h b/code/gamespy/natneg/nninternal.h new file mode 100644 index 00000000..34e6ca97 --- /dev/null +++ b/code/gamespy/natneg/nninternal.h @@ -0,0 +1,109 @@ +#ifndef _NNINTERNAL_H_ +#define _NNINTERNAL_H_ + +#include "natneg.h" +#define MATCHUP1_HOSTNAME "natneg1." GSI_DOMAIN_NAME +#define MATCHUP2_HOSTNAME "natneg2." GSI_DOMAIN_NAME +#define MATCHUP3_HOSTNAME "natneg3." GSI_DOMAIN_NAME +#define MATCHUP_PORT1 27901 +#define MATCHUP_PORT2 27901 +#define MATCHUP_PORT3 27901 + +#define FINISHED_NOERROR 0 +#define FINISHED_ERROR_DEADBEAT_PARTNER 1 +#define FINISHED_ERROR_INIT_PACKETS_TIMEDOUT 2 + +#define INIT_RETRY_TIME 500 +#define INIT_RETRY_COUNT 10 +#define NNINBUF_LEN 512 +#define PING_RETRY_TIME 700 +#define PING_RETRY_COUNT 7 +#define FINISHED_IDLE_TIME 5000 +#define PARTNER_WAIT_TIME 60000 +#define REPORT_RETRY_TIME 1000 +#define REPORT_RETRY_COUNT 5 + +#define NN_PROTVER 3 +//#define NN_PROTVER 2 + +#define NN_PT_GP 0 +#define NN_PT_NN1 1 +#define NN_PT_NN2 2 +#define NN_PT_NN3 3 + +#define NN_INIT 0 +#define NN_INITACK 1 +#define NN_ERTTEST 2 +#define NN_ERTACK 3 +#define NN_STATEUPDATE 4 +#define NN_CONNECT 5 +#define NN_CONNECT_ACK 6 +#define NN_CONNECT_PING 7 +#define NN_BACKUP_TEST 8 +#define NN_BACKUP_ACK 9 +#define NN_ADDRESS_CHECK 10 +#define NN_ADDRESS_REPLY 11 +#define NN_NATIFY_REQUEST 12 +#define NN_REPORT 13 +#define NN_REPORT_ACK 14 + +#if !defined(_PS2) && !defined(_NITRO) +#pragma pack(1) +#endif + + +#define INITPACKET_SIZE BASEPACKET_SIZE + 9 +#define INITPACKET_ADDRESS_OFFSET BASEPACKET_SIZE + 3 +typedef struct _InitPacket +{ + unsigned char porttype; + unsigned char clientindex; + unsigned char usegameport; + unsigned int localip; + unsigned short localport; +} InitPacket; + +#define REPORTPACKET_SIZE BASEPACKET_SIZE + 61 +typedef struct _ReportPacket +{ + unsigned char porttype; + unsigned char clientindex; + unsigned char negResult; + NatType natType; + NatMappingScheme natMappingScheme; + char gamename[50]; +} ReportPacket; + +#define CONNECTPACKET_SIZE BASEPACKET_SIZE + 8 +typedef struct _ConnectPacket +{ + unsigned int remoteIP; + unsigned short remotePort; + unsigned char gotyourdata; + unsigned char finished; +} ConnectPacket; + +#define BASEPACKET_SIZE 12 +#define BASEPACKET_TYPE_OFFSET 7 +typedef struct _NatNegPacket { + // Base members: + unsigned char magic[NATNEG_MAGIC_LEN]; + unsigned char version; + unsigned char packettype; + int cookie; + + union + { + InitPacket Init; + ConnectPacket Connect; + ReportPacket Report; + } Packet; + +} NatNegPacket; + + +#if !defined(_PS2) && !defined(_NITRO) +#pragma pack() +#endif + +#endif diff --git a/code/gamespy/nonport.c b/code/gamespy/nonport.c new file mode 100644 index 00000000..54b0a96c --- /dev/null +++ b/code/gamespy/nonport.c @@ -0,0 +1,18 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Deprecated. Stubbed here for backwards compatability + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "common/gsPlatform.c" +#include "common/gsPlatformSocket.c" +#include "common/gsPlatformThread.c" +#include "common/gsPlatformUtil.c" +#include "common/gsDebug.c" +#include "common/gsAssert.c" +#include "common/gsMemory.c" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/code/gamespy/nonport.h b/code/gamespy/nonport.h new file mode 100644 index 00000000..36b6fcb3 --- /dev/null +++ b/code/gamespy/nonport.h @@ -0,0 +1,17 @@ +/****** +nonport.h +GameSpy Common Code + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +******/ + +#ifndef _NONPORT_H_ +#define _NONPORT_H_ + + +#include "common/gsCommon.h" + + +#endif diff --git a/code/gamespy/pinger/pinger.h b/code/gamespy/pinger/pinger.h new file mode 100644 index 00000000..a87c44f9 --- /dev/null +++ b/code/gamespy/pinger/pinger.h @@ -0,0 +1,117 @@ +/* +GameSpy Ping SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PINGER_H_ +#define _PINGER_H_ + +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/************ +** DEFINES ** +************/ +// This controls the size of UDP pings. +// The protocol takes up 8 of these bytes. +// The rest can be used by the app. +////////////////////////////////////////// +#ifndef PINGER_UDP_PING_SIZE +#define PINGER_UDP_PING_SIZE 32 +#endif + +// Value for ping if a ping timed-out. +////////////////////////////////////// +#define PINGER_TIMEOUT -1 + +/********** +** TYPES ** +**********/ +typedef enum { PINGERFalse, PINGERTrue } PINGERBool; + +/************** +** CALLBACKS ** +**************/ +// A function of this type is used as: +// a param to pingerInit, gets called when an uninitiated UDP ping is received +// a param to pingerPing, gets called when the ping is completed, or times out +// if the ping times out, ping is PINGER_TIMEOUT +// IP and port are in network byte order +////////////////////////////////////////////////////////////////////////////// +typedef void (* pingerGotPing)(unsigned int IP, + unsigned short port, + int ping, + const char * data, + int len, + void * param); + +// When a ping is sent, this callback gets called so that +// the application can use the ping bytes not being used +// by the protocol. +// The application can write up to len bytes to data. +// IP and port are in network byte order. +///////////////////////////////////////////////////////// +typedef void (* pingerSetData)(unsigned int IP, + unsigned short port, + char * data, + int len, + void * param); + +/************** +** FUNCTIONS ** +**************/ + +// Called once to initialize the pinger library. +// localPort is in host byte order +// localAddress=NULL : don't specify a local interface +// localPort=0 : not doing UDP pings +////////////////////////////////////////////////////// +PINGERBool pingerInit(const char * localAddress, + unsigned short localPort, + pingerGotPing pinged, + void * pingedParam, + pingerSetData setData, + void * setDataParam); + +// Called to clean-up when done with pinger. +//////////////////////////////////////////// +void pingerShutdown(void); + +// Called to do processing. +// The more frequently this function is called, +// the more accurate the pings will be. +// +// Example: if this func is called once every 20ms, and a ping +// is returned as 100ms, then the ping is in the range 90-110. +////////////////////////////////////////////////////////////// +void pingerThink(void); + +// Does a ping. If blocking, does not return until the ping is completed. +// timeout specifes how long (in milliseconds) to wait for the ping reply. +// A value of 0 means wait forever. Note, this is dangerous because pings +// are not reliable. A blocking ping with a 0 timeout will never return if +// the ping is lost. +// port=0 : ICMP ping (this is NOT currently supported) +// port!=0 : UDP ping +// IP and port are in network byte order +////////////////////////////////////////////////////////////////////////// +void pingerPing(unsigned int IP, + unsigned short port, + pingerGotPing reply, + void * replyParam, + PINGERBool blocking, + gsi_time timeout); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/pinger/pingerMain.c b/code/gamespy/pinger/pingerMain.c new file mode 100644 index 00000000..d6b8297e --- /dev/null +++ b/code/gamespy/pinger/pingerMain.c @@ -0,0 +1,1003 @@ +/* +GameSpy Ping SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/**************** +** DEFINITIONS ** +***************** + +UPP = UDP Ping Protocol + +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include "pinger.h" +#include "../darray.h" + +/************ +** DEFINES ** +************/ +#define PI_MAGIC 0x91 +#define PI_VERSION 0x01 + +#define PI_TRIP2_TIMEOUT 10000 + +#define PI_DATA_MAX_LEN (PINGER_UDP_PING_SIZE - sizeof(piUDPPing)) + +/********** +** TYPES ** +**********/ +// u_char must be 1 byte unsigned, and unsigned short must be 2 bytes unsigned. +//////////////////////////////////////////////////////////////////////// +typedef unsigned char uint8; +typedef unsigned short uint16; + +// This is the internal data for a UDP ping. +// This is an exact copy of what gets sent over the network, minus filler bytes. +//////////////////////////////////////////////////////////////////////////////// +typedef struct piUDPPing +{ + uint8 magic; // magic number - helps to ignore bad packets + uint8 version; // version - UPP version + uint16 trip; // trip number - will always be 1, 2, or 3 + uint16 ID_A; // this ID is used by A (the source for the first dgram) + uint16 ID_B; // this ID is used by B (the destination for the first dgram) +} piUDPPing; + +// This is a ping on which we're waiting for a reply. +///////////////////////////////////////////////////// +typedef struct piActivePing +{ + PINGERBool originator; // true if we sent the first dgram + uint16 ID; // unique ID for this ping + uint16 expectedTrip; // expected trip # of the reply + gsi_time timestamp; // time we sent the ping + gsi_time timeout; // time this ping expires + unsigned int remoteIP; // IP of the other end of this ping + unsigned short remotePort; // port of the other end of this ping + pingerGotPing reply; // call this callback if we're the originator (otherwise call pingerPinged) + void * replyParam; // the extra param passed to reply +} piActivePing; + +// This is a queued gotPing callback. +///////////////////////////////////// +typedef struct piQueuedCallback +{ + unsigned int IP; + unsigned short port; + int ping; + char * data; + int len; + void * param; + pingerGotPing callback; +} piQueuedCallback; + +/************ +** GLOBALS ** +************/ +static PINGERBool piInitialized = PINGERFalse; +static SOCKET piSocket = INVALID_SOCKET; +static pingerGotPing piPingerPinged; +static void * piPingerPingedParam; +static pingerSetData piPingerSetData; +static void * piPingerSetDataParam; +static PINGERBool piSettingData; +static PINGERBool piUDPEnabled; +static uint16 piNextID; +static DArray piActivePingList; +static gsi_time piLastThinkTime; +static DArray piCallbacks; + +/************** +** FUNCTIONS ** +**************/ +static uint16 piGetNextID(void) +{ + // Store the next ID. + ///////////////////// + uint16 ID = piNextID; + + // Increment the ID. + //////////////////// + if(piNextID == USHRT_MAX) + piNextID = 1; + else + piNextID++; + + return ID; +} + +static PINGERBool piBytesToPing(unsigned char * buffer, piUDPPing * udpPing, char * data) +{ + assert(buffer != NULL); + assert(udpPing != NULL); + assert(data != NULL); + + // Magic. + udpPing->magic = *buffer++; + + // Version. + udpPing->version = *buffer++; + + // Trip. + udpPing->trip = (unsigned short)(*buffer++ << 8); + udpPing->trip |= *buffer++; + + // ID_A. + udpPing->ID_A = (unsigned short)(*buffer++ << 8); + udpPing->ID_A |= *buffer++; + + // ID_B. + udpPing->ID_B = (unsigned short)(*buffer++ << 8); + udpPing->ID_B |= *buffer++; + + // Check for the magic number. + // This asserted on 11/17/00 at aprox. 1:40PM. + // on a ping from 205.251.192.203:13139. + // There were 2 empty bytes at the start of + // the buffer, then the normal message followed. + // Same thing on 12/14/00 at approx. 5PM. + // From 24.156.198.26 + // JED 12/18/00 15:20, again from 24.156.198.26 + // 2001.Jan.15.JED - again from 24.156.198.26 + // 2001.Jan.23.JED - 213.65.117.53 (@11:15am) + // 2001.Mar.26.JED - 24.159.107.0 (@7:30pm) + // PANTS|04.20.00 - commented out per JED's request. + //////////////////////////////////////////////////// + //assert(udpPing->magic == PI_MAGIC); + if(udpPing->magic != PI_MAGIC) + return PINGERFalse; + + // Check the version. + ///////////////////// + assert(udpPing->version == PI_VERSION); + if(udpPing->version != PI_VERSION) + return PINGERFalse; + + // Check the trip. + ////////////////// + assert((udpPing->trip >= 1) && (udpPing->trip <= 3)); + if((udpPing->trip < 1) || (udpPing->trip > 3)) + return PINGERFalse; + + // Fill in the data. + //////////////////// + memcpy(data, buffer, PI_DATA_MAX_LEN); + + return PINGERTrue; +} + +static void piPingToBytes(piUDPPing * udpPing, unsigned char * buffer) +{ + assert(udpPing != NULL); + assert(buffer != NULL); + + // Magic. + *buffer++ = udpPing->magic; + + // Version. + *buffer++ = udpPing->version; + + // Trip. + *buffer++ = (unsigned char)((udpPing->trip & 0xFF00) >> 8); + *buffer++ = (unsigned char)(udpPing->trip & 0x00FF); + + // ID_A. + *buffer++ = (unsigned char)((udpPing->ID_A & 0xFF00) >> 8); + *buffer++ = (unsigned char)(udpPing->ID_A & 0x00FF); + + // ID_B. + *buffer++ = (unsigned char)((udpPing->ID_B & 0xFF00) >> 8); + *buffer++ = (unsigned char)(udpPing->ID_B & 0x00FF); +} + +static PINGERBool piSendPing(SOCKADDR_IN * to, uint16 trip, uint16 ID_A, uint16 ID_B, const char * data) +{ + unsigned char buffer[PINGER_UDP_PING_SIZE]; + piUDPPing udpPing; + int rcode; + + assert(to != NULL); + assert((trip >= 1) && (trip <= 3)); + assert(!((trip == 2) && (ID_A == 0))); + + // Construct the outgoing ping. + /////////////////////////////// + udpPing.magic = PI_MAGIC; + udpPing.version = PI_VERSION; + udpPing.trip = trip; + udpPing.ID_A = ID_A; + udpPing.ID_B = ID_B; + piPingToBytes(&udpPing, buffer); + if(data != NULL) + memcpy(buffer + sizeof(piUDPPing), data, PI_DATA_MAX_LEN); + else + memset(buffer + sizeof(piUDPPing), 0, PI_DATA_MAX_LEN); + + // Send the outgoing ping. + ////////////////////////// + rcode = sendto(piSocket, (char *)buffer, PINGER_UDP_PING_SIZE, 0, (SOCKADDR *)to, sizeof(SOCKADDR_IN)); + // Did it send ok? + ////////////////// + if(rcode != PINGER_UDP_PING_SIZE) + return PINGERFalse; + + return PINGERTrue; +} + +static PINGERBool piSocketInit(const char * localAddress, + unsigned short localPort) +{ + int rcode; + SOCKADDR_IN sockaddr; + int bFlag; + //int iLastError; + + assert(localPort != 0); + + // Setup sockets. + ///////////////// + SocketStartUp(); + + // Setup the address. + ///////////////////// + memset(&sockaddr, 0, sizeof(SOCKADDR_IN)); + sockaddr.sin_family = AF_INET; + sockaddr.sin_port = htons(localPort); + if(localAddress != NULL) + { + unsigned int IP; + + // Check for "a.b.c.d". + /////////////////////// + IP = inet_addr(localAddress); + if(IP == INADDR_NONE) + { + HOSTENT * hostent; + + // Check for "machine.host.domain". + /////////////////////////////////// + hostent = gethostbyname(localAddress); + if(hostent == NULL) + { +// iLastError = WSAGetLastError(); + assert(0); + return PINGERFalse; + } + + // Grab the IP. + /////////////// + assert(IP != 0); + IP = *(unsigned int *)hostent->h_addr_list[0]; + } + + // Got the IP. + ////////////// + sockaddr.sin_addr.s_addr = IP; + } + else + { + // Any local IP. + //////////////// + sockaddr.sin_addr.s_addr = INADDR_ANY; + } + + // Create the socket. + ///////////////////// + piSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if(piSocket == INVALID_SOCKET) + { +// iLastError = GOAGetLastError(piSocket); + assert(0); + return PINGERFalse; + } + + // Allow reuse of the socket if not closed properly before. + /////////////////////////////////////////////////////////// + bFlag = 1; +#ifndef INSOCK // PS2 INSOCK network layer does not support reuse addr + rcode = setsockopt(piSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&bFlag, sizeof(bFlag) ); +#endif + /*if(gsiSocketIsError(rcode)) + { + iLastError = WSAGetLastError(); + assert(0); + return PINGERFalse; + }*/ + + // Bind the socket. + /////////////////// + rcode = bind(piSocket, (SOCKADDR *)&sockaddr, sizeof(SOCKADDR_IN)); + if (gsiSocketIsError(rcode)) + { +// iLastError = GOAGetLastError(piSocket); + assert(0); + return PINGERFalse; + } + + return PINGERTrue; +} + +static void piQueueCallback +( + unsigned int IP, + unsigned short port, + int ping, + const char * data, + int len, + void * param, + pingerGotPing callbackFunc +) +{ + piQueuedCallback callback; + + assert(callbackFunc); + if(!callbackFunc) + return; + + // Setup the callback. + ////////////////////// + callback.IP = IP; + callback.port = port; + callback.ping = ping; + callback.len = len; + callback.param = param; + callback.callback = callbackFunc; + + // Copy the data. + ///////////////// + if(data) + { + callback.data = (char *)gsimalloc((unsigned int)len); + if(!callback.data) + return; + memcpy(callback.data, data, (unsigned int)len); + } + else + { + callback.data = NULL; + } + + // Add it. + ////////// + ArrayAppend(piCallbacks, &callback); +} + +static void piCallCallbacks(void) +{ + piQueuedCallback * callback; + piQueuedCallback callbackCopy; + + while(ArrayLength(piCallbacks) > 0) + { + // Get the info. + //////////////// + callback = (piQueuedCallback *)ArrayNth(piCallbacks, 0); + assert(callback); + if(!callback) + return; + + // Copy the info. + ///////////////// + memcpy(&callbackCopy, callback, sizeof(piQueuedCallback)); + callback = &callbackCopy; + + // Remove it from the list. + /////////////////////////// + ArrayDeleteAt(piCallbacks, 0); + + // Call it. + /////////// + callback->callback(callback->IP, callback->port, callback->ping, callback->data, callback->len, callback->param); + + // gsifree it. + /////////// + gsifree(callback->data); + } +} + +PINGERBool pingerInit(const char * localAddress, + unsigned short localPort, + pingerGotPing pinged, + void * pingedParam, + pingerSetData setData, + void * setDataParam) +{ + // Check if we're already initialized. + ////////////////////////////////////// + assert(!piInitialized); + + // Make sure the UDP ping size is at least as large as the protocol struct. + /////////////////////////////////////////////////////////////////////////// +#if !defined(_NITRO) + assert(PINGER_UDP_PING_SIZE >= sizeof(piUDPPing)); +#endif + + // Check if we're already intialized. + ///////////////////////////////////// + if(piInitialized) + return PINGERFalse; + + // An empty localAddress is them same as a NULL. + //////////////////////////////////////////////// + if((localAddress != NULL) && (localAddress[0] == '\0')) + localAddress = NULL; + + // Initialize data. + /////////////////// + piPingerPinged = pinged; + piPingerPingedParam = pingedParam; + piPingerSetData = setData; + piPingerSetDataParam = setDataParam; + piSettingData = PINGERFalse; + piUDPEnabled = (PINGERBool)(localPort != 0); + piNextID = 1; + piLastThinkTime = 0; + + // Initialize the active ping list. + /////////////////////////////////// + piActivePingList = ArrayNew(sizeof(piActivePing), 0, NULL); + if(piActivePingList == NULL) + return PINGERFalse; + + // Initialize the callbacks list. + ///////////////////////////////// + piCallbacks = ArrayNew(sizeof(piQueuedCallback), 0, NULL); + if(!piCallbacks) + { + ArrayFree(piActivePingList); + return PINGERFalse; + } + + // Setup the UDP socket. + //////////////////////// + if(piUDPEnabled) + { + if(!piSocketInit(localAddress, localPort)) + { + assert(0); + + // Check for partial intialization. + /////////////////////////////////// + if(piSocket != INVALID_SOCKET) + { + closesocket(piSocket); + piSocket = INVALID_SOCKET; + } + + // Shut down sockets. + ///////////////////// + SocketShutDown(); + + // gsifree the lists. + ////////////////// + ArrayFree(piActivePingList); + ArrayFree(piCallbacks); + + return PINGERFalse; + } + } + + // We're initialized! + ///////////////////// + piInitialized = PINGERTrue; + + return PINGERTrue; +} + +void pingerShutdown(void) +{ + if(!piInitialized) + return; + + // Check for setting data. + ////////////////////////// + if(piSettingData) + return; + + // Shut down the socket. + //////////////////////// + if(piSocket != INVALID_SOCKET) + { + closesocket(piSocket); + piSocket = INVALID_SOCKET; + } + + // Shut down sockets. + ///////////////////// + SocketShutDown(); + + // gsifree the active ping list. + ///////////////////////////// + ArrayFree(piActivePingList); + + // gsifree the callbacks list. + /////////////////////////// + ArrayFree(piCallbacks); + + // Not initialized anymore. + /////////////////////////// + piInitialized = PINGERFalse; +} + +static int GS_STATIC_CALLBACK piFindActivePingCompareFn(const void *elem1, const void *elem2) +{ + piActivePing * activePing1 = (piActivePing *)elem1; + piActivePing * activePing2 = (piActivePing *)elem2; + assert(activePing1 != NULL); + assert(activePing2 != NULL); + assert(activePing1->ID != 0); + assert(activePing2->ID != 0); + + return (activePing1->ID - activePing2->ID); +} + +static piActivePing * piFindActivePing(uint16 ID, int * index) +{ + piActivePing key; + int n; + + // Search for a matching ID. + //////////////////////////// + key.ID = ID; + n = ArraySearch(piActivePingList, &key, piFindActivePingCompareFn, 0, 0); + if(index != NULL) + *index = n; + + // Find it? + /////////// + if(n == NOT_FOUND) + return NULL; + + // Found it! + //////////// + return (piActivePing *)ArrayNth(piActivePingList, n); +} + +static int piCalculatePing(gsi_time sendTime, gsi_time recvTime) +{ + int ping; + + // Simple ping calculation. + /////////////////////////// + ping = (int)(recvTime - sendTime); + + // Take off some time based on the last time we called + // the think function. + ////////////////////////////////////////////////////// + if(piLastThinkTime != 0) + { +#if 0 + ping -= ((int)(recvTime - piLastThinkTime) / 2); + if(ping < 0) + ping = 0; +#endif + } + + return ping; +} + +static void piProcessTrip1(piUDPPing * udpPing, const char * data, SOCKADDR_IN * from, gsi_time recvTime) +{ + char dataOut[PI_DATA_MAX_LEN]; + uint16 ID; + + // Validity check. + // udpPing->ID_A != 0 was triggered by a ping + // from 213.105.94.117 sometime during 7/3-7/02 + /////////////////////////////////////////////// + assert(udpPing->trip == 1); + //assert(udpPing->ID_A != 0); + if(udpPing->ID_A == 0) + return; + assert(udpPing->ID_B == 0); + if(udpPing->ID_B != 0) + return; + + // Do we want a reply? + ////////////////////// + if(piPingerPinged != NULL) + { + // Get an ID. + ///////////// + ID = piGetNextID(); + } + else + { + // No reply wanted. + /////////////////// + ID = 0; + } + + // Setup the data. + ////////////////// + memset(dataOut, 0, PI_DATA_MAX_LEN); + if(piPingerSetData != NULL) + { + piSettingData = PINGERTrue; + piPingerSetData(from->sin_addr.s_addr, from->sin_port, dataOut, PI_DATA_MAX_LEN, piPingerSetDataParam); + piSettingData = PINGERFalse; + } + + // Send the return ping (trip 2). + ///////////////////////////////// + piSendPing(from, 2, udpPing->ID_A, ID, dataOut); + + // Do we need to add an active ping? + //////////////////////////////////// + if(piPingerPinged != NULL) + { + piActivePing activePing; + + // Setup the active ping object. + //////////////////////////////// + activePing.originator = PINGERFalse; + activePing.ID = ID; + activePing.expectedTrip = 3; + activePing.timestamp = current_time(); + activePing.timeout = (activePing.timestamp + PI_TRIP2_TIMEOUT); + activePing.remoteIP = from->sin_addr.s_addr; + activePing.remotePort = from->sin_port; + activePing.reply = NULL; + activePing.replyParam = NULL; + + // Add it to the list. + ////////////////////// + ArrayAppend(piActivePingList, &activePing); + } + + GSI_UNUSED(data); + GSI_UNUSED(recvTime); +} + +static void piProcessTrip2(piUDPPing * udpPing, const char * data, SOCKADDR_IN * from, gsi_time recvTime) +{ + char dataOut[PI_DATA_MAX_LEN]; + int index; + piActivePing * activePing; + + // Validity check. + // ID_A != 0 was triggered by a ping from 205.251.192.203. + // 12/4/00 at approx. 5pm + // This is the same IP as the shifted ping assert above. + ////////////////////////////////////////////////////////// + assert(udpPing->trip == 2); + assert(udpPing->ID_A != 0); + if(udpPing->ID_A == 0) + return; + + // Were we waiting for this? + //////////////////////////// + activePing = piFindActivePing(udpPing->ID_A, &index); + if(activePing == NULL) + return; + + // Setup the data. + ////////////////// + memset(dataOut, 0, PI_DATA_MAX_LEN); + if(piPingerSetData != NULL) + { + piSettingData = PINGERTrue; + piPingerSetData(from->sin_addr.s_addr, from->sin_port, dataOut, PI_DATA_MAX_LEN, piPingerSetDataParam); + piSettingData = PINGERFalse; + } + + // Do we send a reply? + ////////////////////// + if(udpPing->ID_B != 0) + { + // Send the return ping. + //////////////////////// + piSendPing(from, 3, 0, udpPing->ID_B, dataOut); + } + + // Call the callback? + ///////////////////// + if(activePing->reply != NULL) + { + // Calculate the ping. + ////////////////////// + int ping = piCalculatePing(activePing->timestamp, recvTime); + + // Add the callback. + //////////////////// + piQueueCallback(from->sin_addr.s_addr, from->sin_port, ping, data, PI_DATA_MAX_LEN, activePing->replyParam, activePing->reply); + } + + // gsifree the active ping. + //////////////////////// + ArrayDeleteAt(piActivePingList, index); +} + +static void piProcessTrip3(piUDPPing * udpPing, const char * data, SOCKADDR_IN * from, gsi_time recvTime) +{ + int index; + piActivePing * activePing; + + // Validity check. + ////////////////// + assert(udpPing->trip == 3); + assert(udpPing->ID_A == 0); + if(udpPing->ID_A != 0) + return; + assert(udpPing->ID_B != 0); + if(udpPing->ID_B == 0) + return; + + // Were we waiting for this? + //////////////////////////// + activePing = piFindActivePing(udpPing->ID_B, &index); + if(activePing == NULL) + return; + + // Call the callback. + ///////////////////// + assert(piPingerPinged != NULL); + if(piPingerPinged != NULL) + { + // Calculate the ping. + ////////////////////// + int ping = piCalculatePing(activePing->timestamp, recvTime); + + // Call it. + /////////// + piPingerPinged(from->sin_addr.s_addr, from->sin_port, ping, data, PI_DATA_MAX_LEN, piPingerPingedParam); + } + + // gsifree the active ping. + //////////////////////// + ArrayDeleteAt(piActivePingList, index); +} + +static void piProcessPing(piUDPPing * udpPing, const char * data, SOCKADDR_IN * from, gsi_time recvTime) +{ + assert(udpPing != NULL); + assert(data != NULL); + assert(from != NULL); + + // Process based on trip num. + ///////////////////////////// + if(udpPing->trip == 1) + piProcessTrip1(udpPing, data, from, recvTime); + else if(udpPing->trip == 2) + piProcessTrip2(udpPing, data, from, recvTime); + else if(udpPing->trip == 3) + piProcessTrip3(udpPing, data, from, recvTime); +} + +static void piProcessIncoming(void) +{ + int rcode; + unsigned char buffer[PINGER_UDP_PING_SIZE]; + SOCKADDR_IN from; + int len; + gsi_time recvTime; + piUDPPing udpPing; + char data[PI_DATA_MAX_LEN]; + + // Check for incoming dgrams. + ///////////////////////////// + while(piInitialized && CanReceiveOnSocket(piSocket)) + { + // Read the dgram. + ////////////////// + len = sizeof(SOCKADDR_IN); + + rcode = recvfrom(piSocket, (char *)buffer, PINGER_UDP_PING_SIZE, 0, (SOCKADDR *)&from, &len); + + // Check for an error. + ////////////////////// + if (gsiSocketIsError(rcode)) + { + if(GOAGetLastError(piSocket) == WSAEMSGSIZE) + { + // Ignore "too big" errors. + /////////////////////////// + rcode = PINGER_UDP_PING_SIZE; + } + else + { + // Something's wrong, get the hell out of here! + /////////////////////////////////////////////// + return; //ERRCON + } + } + else if(rcode < PINGER_UDP_PING_SIZE) + { + // The ping was too small. + ////////////////////////// + return; //ERRCON + } + + // What time did we receive it? + /////////////////////////////// + recvTime = current_time(); + + // Convert the buffer into a ping. + ////////////////////////////////// + if(piBytesToPing(buffer, &udpPing, data)) + { + // Process the dgram. + ///////////////////// + piProcessPing(&udpPing, data, &from, recvTime); + } + } + + // Last time we checked for incoming data. + ////////////////////////////////////////// + piLastThinkTime = current_time(); +} + +static void piCheckTimeouts(void) +{ + gsi_time now; + piActivePing * activePing; + int len; + int n; + + // Get the number of active pings. + ////////////////////////////////// + len = ArrayLength(piActivePingList); + + // Get out if no active pings. + ////////////////////////////// + if(len == 0) + return; + + // Get the current time. + //////////////////////// + now = current_time(); + + // Go through the list backwards. + ///////////////////////////////// + for(n = (len - 1) ; n >= 0 ; n--) + { + // Get the nth element of the list. + /////////////////////////////////// + activePing = (piActivePing *)ArrayNth(piActivePingList, n); + assert(activePing != NULL); + if(activePing != NULL) + { + // Does it have a timeout? + ////////////////////////// + if(activePing->timeout != 0) + { + // Has it timed out? + //////////////////// + if(activePing->timeout <= now) + { + // Do we need to do a failed callback? + ////////////////////////////////////// + if((activePing->originator) && (activePing->reply != NULL)) + { + // Queue the callback. + ////////////////////// + piQueueCallback(activePing->remoteIP, activePing->remotePort, PINGER_TIMEOUT, NULL, 0, activePing->replyParam, activePing->reply); + } + + // Kill it! + /////////// + ArrayDeleteAt(piActivePingList, n); + } + } + } + } +} + +void pingerThink(void) +{ + // If not initialized, don't do anything. + ///////////////////////////////////////// + if(!piInitialized) + return; + + // Check for setting data. + ////////////////////////// + if(piSettingData) + return; + + // Process incoming data. + ///////////////////////// + piProcessIncoming(); + + // Check for timeouts. + ////////////////////// + piCheckTimeouts(); + + // Call queued callbacks. + ///////////////////////// + piCallCallbacks(); +} + +void pingerPing(unsigned int IP, + unsigned short port, + pingerGotPing reply, + void * replyParam, + PINGERBool blocking, + gsi_time timeout) +{ + SOCKADDR_IN to; + uint16 ID; + + assert(piInitialized); + assert(IP != 0); + + // ICMP not supported yet! + ////////////////////////// + assert(port != 0); + assert(piUDPEnabled); + assert(piSocket != INVALID_SOCKET); + + // Check for setting data. + ////////////////////////// + if(piSettingData) + return; + + // Construct the outgoing address. + ////////////////////////////////// + memset(&to, 0, sizeof(SOCKADDR_IN)); + to.sin_family = AF_INET; + to.sin_port = htons(port); + to.sin_addr.s_addr = IP; + + // Get an ID for this ping. + /////////////////////////// + ID = piGetNextID(); + + // Send the outgoing ping. + ////////////////////////// + if(piSendPing(&to, 1, ID, 0, NULL)) + { + piActivePing activePing; + + // Setup the active ping object. + //////////////////////////////// + activePing.originator = PINGERTrue; + activePing.ID = ID; + activePing.expectedTrip = 2; + activePing.timestamp = current_time(); + if(timeout == 0) + activePing.timeout = 0; + else + activePing.timeout = (activePing.timestamp + timeout); + activePing.remoteIP = IP; + activePing.remotePort = port; + activePing.reply = reply; + activePing.replyParam = replyParam; + + // Add it to the list. + ////////////////////// + ArrayAppend(piActivePingList, &activePing); + } + + // Is this blocking? + //////////////////// + if(blocking) + { + // Keep going until this ping has been de-listed. + ///////////////////////////////////////////////// + while(piFindActivePing(ID, NULL) != NULL) + { + // Think. + ///////// + pingerThink(); + + // Yield. + ///////// + msleep(1); + } + + // Call callbacks. + ////////////////// + piCallCallbacks(); + } +} diff --git a/code/gamespy/pt/changelog.txt b/code/gamespy/pt/changelog.txt new file mode 100644 index 00000000..5f7aeccf --- /dev/null +++ b/code/gamespy/pt/changelog.txt @@ -0,0 +1,95 @@ +Changelog for: GameSpy Patching & Tracking SDK +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 1.04.00 RMV RELEASE Released to Developer Site +08-06-2007 1.03.00 RMV RELEASE Released to Developer Site +07-16-2007 1.02.01 RMV FIX Removed gsTestMain from Mac Makefile + 1.02.01 RMV OTHER Removed FilePlanetInfo calls from pttestc.c +07-11-2007 1.02.01 RMV FIX Fixed pttestc Project file to get rid of Unicode warnings +12-15-2006 1.02.00 MJW RELEASE Released to Developer Site +10-30-2006 1.01.44 SN FIX Modified the ptCreateCheckPatchTrackUsageReqW to use correct corresponding ascii function + Also modified ptCreateCheckPatchTrackUsageReqA to conform with the rest of the SDK where + the request is being saved in a variable +10-30-2006 1.01.43 SAH OTHER Changed PTA_MAX_STRING_SIZE to 2048 +10-05-2006 1.01.42 SAH FIX Updated MacOSX Makefile +09-28-2006 1.01.41 SAH FIX Fixed PS3 project to work with PS3 095.00x SDK; changed included libaries in linker input. +08-02-2006 1.01.40 SAH RELEASE Releasing to developer site +07-31-2006 1.01.40 SAH FIX Fixed PS3 project file - added post-build step to create *.SELF for execution +07-25-2006 1.01.39 SAH FIX Fixed NITRO project, include for crt0.o now above others so it add correct file +07-06-2006 1.01.38 SAH FIX Fixed PSP project file to not explicitly include the PSP linker file +07-06-2006 1.01.37 SAH FIX Fixed Linux makefile to work with pthreads (for http asynch DNS lookup) + SAH FIX Fixed NITRO project & linker command file (to work with CW 2.0/NitroSDK 3.1) +05-31-2006 1.01.36 SAH RELEASE Releasing to developer site + SAH FIX Fixed Linux makefile +05-30-2006 1.01.35 SAH FIX Fixed PS3 projects to work with PS3(084_001 SDK) +05-25-2006 1.01.34 SAH FIX Changed PSP project warning levels +05-19-2006 1.01.33 SAH FIX Added gsTestMain.c to nitro CodeWarrior project +05-15-2006 1.01.32 SAH FIX Added "PS3 Release" configuration to project +04-25-2006 1.01.31 SAH RELEASE Releasing to developer site +04-24-2006 1.01.31 SAH FIX Moved definition ptCheckForPatchAndTrackUsageA so it was defined before used +04-24-2006 1.01.30 SAH FIX Fixed Nitro project files to work on build machine +04-20-2006 1.01.30 SAH FIX Added necessary files to VC6 project, set to warning level 4 +04-14-2006 1.01.29 SAH FIX Added gsXML and other files to the project to compile for release +01-30-2006 1.01.28 SN FEATURE Added a function similar to ptCheckForPatchAndTrackUsage with a return of a handle + to pass to ghttpRequestThink. +01-27-2006 1.01.27 SN RELEASE Releasing to developer site +01-27-2006 1.01.27 SN FIX Added PSP to test_main define in pttestc + SN OTHER Added psp prodg project and solution to sgv +12-22-2005 1.01.26 SN OTHER Cleaned up project file and added latest common code if needed +11-17-2005 1.01.25 DES FIX Updated Nitro makefile. +11-14-2005 1.01.24 DES FIX Updated the OSX Makefile. + DES FEATURE Added GSI_DOMAIN_NAME support. +09-21-2005 1.01.25 DES FEATURE Updated DS support +07-28-2005 1.01.24 SN RELEASE Releasing to developer site. +07-28-2005 1.01.24 SN FIX Added new common code in pt ps2 project +06-03-2005 1.01.23 SN RELEASE Releasing to developer site. +05-09-2005 1.01.23 SN FIX Fixed common code includes in source files + Fixed project to use new common code +05-04-2005 1.01.22 SN OTHER Created Visual Studio .NET project +04-28-2005 1.01.22 SN RELEASE Releasing to developer site. +04-27-2005 1.01.22 DES RELEASE Limited release to Nintendo DS developers. +04-25-2005 1.01.22 DES FEATURE Added common debug support to pttestc +04-04-2005 1.01.21 SN RELEASE Releasing to developer site. +03-14-2005 1.01.21 DES FEATURE Nintendo DS support +11-24-2004 1.01.20 SN FIX Fixed download URL with 100 char limit to be 256. + Made URL character string local to functions that use it. + added a 256 char size constant +09-16-2004 1.01.19 SN RELEASE Releasing to developer site. +08-27-2004 1.01.19 DES CLEANUP Fixed warnings under OSX + DES CLEANUP General Unicode cleanup + DES CLEANUP Updated Win32 project configurations + DES CLEANUP Updated OSX Makefile +08-04-2004 1.01.18 SN RELEASE Releasing to developer site. +07-19-2004 1.01.18 SN FIX Updated code with explicit casts to remove implicit cast error + when compiling at highest level and warnings treated as errors. +06-18-2004 1.01.18 BED RELEASE Releasing to developer site. + BED FEATURE Added PS2 Insock support +11-10-2003 1.01.17 DES RELEASE Releasing to developer site. +11-07-2003 1.01.17 BED FIX Updated CodeWarrior project file. +11-07-2003 1.01.16 DES FIX Updated linux and PS2 makefiles. +11-04-2003 1.01.15 DES FEATURE Added availability check code. +10-29-2003 1.01.14 BED FIX Fixed bug with incorrect data types in callback. + DES FEATURE Now passes the gamename along with the request. +10-22-2003 1.01.13 BED RELEASE Releasing to developer site. (UNIQUE NICK AND UNICODE SUPPORT) +10-22-2003 1.01.13 BED FIX Removed script compiler warnings in preparation for release. + BED FEATURE Added Unicode support to the sample. +10-09-2003 1.01.12 BED FIX Added ghttpCleanup call to pttestc.c to prevent memory leak. +09-08-2003 1.01.11 BED FEATURE Added UTF-8 wrapper for UNICODE support +07-24-2003 1.01.10 DES RELEASE Releasing to developer site. +07-23-2003 1.01.10 BED FEATURE Added Linux sample Makefile. +07-18-2003 1.01.09 BED FEATURE Added CodeWarrior (PS2) sample project file. + BED CLEANUP General cleanup to remove CodeWarrior warnings. +07-17-2003 1.01.08 DES CLEANUP Cleaned up the PS2 Makefile, it now uses Makefile.commmon. +07-16-2003 1.01.07 DES FIX Changed a __mips64 check to a _PS2 check. + BED FEATURE Added ProDG sample project files. +07-11-2003 1.01.06 BED FIX Added a success message in the pttest sample for PS2. +07-10-2003 1.01.05 BED CLEANUP Switch from local UNUSED_PARAM to standard GSI_UNUSED macro. +05-09-2003 1.01.04 DES CLEANUP Removed Dreamcast support. +04-15-2003 1.01.03 JED CLEANUP Removed a few DevStudio Level4 warnings +12-19-2002 1.01.02 DES RELEASE Releasing to developer site. +12-19-2002 1.01.02 DES CLEANUP Removed assert.h include. +12-13-2002 1.01.01 DES FEATURE Added PS2 eenet stack support. + CLEANUP Cleaned up code to remove PS2 compiler warnings. +09-25-2002 1.01.00 DDW OTHER Changelog started diff --git a/code/gamespy/pt/fpupdate/fpupdate.exe b/code/gamespy/pt/fpupdate/fpupdate.exe new file mode 100644 index 0000000000000000000000000000000000000000..d0a3dfe39ed2924c1636b7d152f6b9cc872109dd GIT binary patch literal 224768 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

HR}V|Bo1E>?1^$XoQ#yo%}G` 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%0DH{}EO!NOwCGBBnN5?qqQ*=~4{+ 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≠s&f8F91cqV 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 jkKm}*9zPTK +#include "pt.h" +#include "../ghttp/ghttp.h" +#include "../common/gsStringUtil.h" +#include "../common/gsAvailable.h" + +/************ +** DEFINES ** +************/ +#define PTA_DEFAULT_VERCHECK_URL "http://motd." GSI_DOMAIN_NAME "/motd/vercheck.asp" +#define PTA_DEFAULT_MOTD_URL "http://motd." GSI_DOMAIN_NAME "/motd/motd.asp" +#define PTA_DEFAULT_FILEPLANET_URL "http://www.fileplanet.com/dlfileraw.asp" +#define MAX_MIRRORS 32 +#define PTA_MAX_STRING_SIZE 2048 + +char gPTAVercheckURL[PTA_MAX_STRING_SIZE]; +char gPTAMOTDURL[PTA_MAX_STRING_SIZE]; +char gPTAFilePlanetURL[PTA_MAX_STRING_SIZE]; + +/********** +** TYPES ** +**********/ +typedef struct ptaPatchData +{ + ptPatchCallback callback; + void * param; +} ptaPatchData; + +typedef struct ptaFilePlanetInfoData +{ + int fileID; + ptFilePlanetInfoCallback callback; + void * param; +} ptaFilePlanetInfoData; + +/************ +** GLOBALS ** +************/ +//static char URL[PTA_MAX_STRING_SIZE]; + +/************** +** FUNCTIONS ** +**************/ +static const char * ptaGetKeyValue +( + const char * buffer, + const char * key +) +{ + static char value[PTA_MAX_STRING_SIZE]; + const char * str; + int len; + + str = strstr(buffer, key); + if(!str) + return NULL; + str += strlen(key); + len = (int)strcspn(str, "\\"); + len = min(len, (int)sizeof(value) - 1); + memcpy(value, str, (unsigned int)len); + value[len] = '\0'; + + return value; +} + +static char Line[PTA_MAX_STRING_SIZE]; +static int ptaFillLine +( + const char * buffer +) +{ + char * str = Line; + int i; + + // Skip white space. + //////////////////// + for(i = 0 ; isspace(*buffer) ; i++) + buffer++; + + // Check for EOF. + ///////////////// + if(*buffer == '\0') + return EOF; + + // Copy off the line. + ///////////////////// + for( ; *buffer && ((*buffer != 0x0A) && (*buffer != 0x0D)) ; i++) + { + if(i == sizeof(Line) - 1) + { + // This line is too big for our buffer. + /////////////////////////////////////// + assert(0); + return EOF; + } + *str++ = *buffer++; + } + *str = '\0'; + + return i; +} + +static void ptaCallPatchCallback +( + ptaPatchData * data, + PTBool available, + PTBool mandatory, + const char * versionName, + int fileID, + const char * downloadURL +) +{ + if(data->callback) + { +#ifndef GSI_UNICODE + data->callback(available, mandatory, versionName, fileID, downloadURL, data->param); +#else + unsigned short versionName_W[255]; + unsigned short downloadURL_W[PTA_MAX_STRING_SIZE*2]; + UTF8ToUCS2String(versionName, versionName_W); + AsciiToUCS2String(downloadURL, downloadURL_W); + data->callback(available, mandatory, versionName_W, fileID, downloadURL_W, data->param); +#endif + } + + gsifree(data); +} + +// already returns ghttptrue +static GHTTPBool ptaPatchFailed +( + ptaPatchData * data +) +{ + ptaCallPatchCallback(data, PTFalse, PTFalse, "", 0, ""); + + return GHTTPTrue; +} + +static void ptaCallFilePlanetInfoCallback +( + ptaFilePlanetInfoData * data, + PTBool found, + const char * description, + const char * size, + int numMirrors, + char ** mirrorNames, + char ** mirrorURLs +) +{ + int i; + + if(data->callback) + { + if (!found) + data->callback(data->fileID, PTFalse, NULL, NULL, 0, NULL, NULL, data->param); + else + { +#ifndef GSI_UNICODE + data->callback(data->fileID, found, description, size, numMirrors, (const char**)mirrorNames, (const char**)mirrorURLs, data->param); +#else + unsigned short description_W[255]; + unsigned short size_W[1024]; + unsigned short** mirrorNames_W; + unsigned short** mirrorURLs_W; + int i; + + UTF8ToUCS2String(description, description_W); + AsciiToUCS2String(size, size_W); + mirrorNames_W = UTF8ToUCS2StringArrayAlloc((const UTF8String *)mirrorNames, numMirrors); + mirrorURLs_W = UTF8ToUCS2StringArrayAlloc((const UTF8String *)mirrorURLs, numMirrors); + data->callback(data->fileID, found, description_W, size_W, numMirrors, (const unsigned short**)mirrorNames_W, (const unsigned short**)mirrorURLs_W, data->param); + + for (i=0; i < numMirrors; i++) + { + gsifree(mirrorNames[i]); + gsifree(mirrorURLs[i]); + } + gsifree(mirrorNames); + gsifree(mirrorURLs); +#endif + } + } + + for(i = 0 ; i < numMirrors ; i++) + { + gsifree(mirrorNames[i]); + gsifree(mirrorURLs[i]); + } + + gsifree(data); +} + +// already returns ghttptrue +static GHTTPBool ptaFilePlanetInfoFailed +( + ptaFilePlanetInfoData * data +) +{ + ptaCallFilePlanetInfoCallback(data, PTFalse, NULL, NULL, 0, NULL, NULL); + + return GHTTPTrue; +} + +static GHTTPBool ptaPatchCompletedCallback +( + GHTTPRequest request, + GHTTPResult result, + char * buffer, + GHTTPByteCount bufferLen, + void * param +) +{ + ptaPatchData * data = (ptaPatchData *)param; + const char * value; + PTBool mandatory; + int fileID; + char versionName[PTA_MAX_STRING_SIZE]; + char downloadURL[PTA_MAX_STRING_SIZE]; + + GSI_UNUSED(bufferLen); + GSI_UNUSED(request); + + // Check for success. + ///////////////////// + if(result != GHTTPSuccess) + return ptaPatchFailed(data); + + // Check for a patch. + ///////////////////// + value = ptaGetKeyValue(buffer, "\\newver\\"); + if(!value || !atoi(value)) + return ptaPatchFailed(data); + + // Check the mandatory flag. + //////////////////////////// + value = ptaGetKeyValue(buffer, "\\lockout\\"); + mandatory = (value && atoi(value)); + + // Get the file id. + /////////////////// + value = ptaGetKeyValue(buffer, "\\fpfileid\\"); + if(value) + fileID = atoi(value); + else + fileID = 0; + + // Get the name. + //////////////// + value = ptaGetKeyValue(buffer, "\\newvername\\"); + if(value) + { + strncpy(versionName, value, sizeof(versionName)); + versionName[sizeof(versionName) - 1] = '\0'; + } + else + versionName[0] = '\0'; + + // Get the URL. + /////////////// + value = ptaGetKeyValue(buffer, "\\dlurl\\"); + if(value) + { + strncpy(downloadURL, value, sizeof(downloadURL)); + downloadURL[sizeof(downloadURL) - 1] = '\0'; + } + else + downloadURL[0] = '\0'; + + // Call the callback. + ///////////////////// + ptaCallPatchCallback(data, PTTrue, mandatory, versionName, fileID, downloadURL); + + return GHTTPTrue; +} + +PTBool ptCheckForPatchA +( + int productID, + const char * versionUniqueID, + int distributionID, + ptPatchCallback callback, + PTBool blocking, + void * param +) +{ + int charsWritten; + char aURL[PTA_MAX_STRING_SIZE]; + ptaPatchData * data; + + // check if the backend is available + if(__GSIACResult != GSIACAvailable) + return PTFalse; + + // Check the arguments. + /////////////////////// + assert(versionUniqueID); + if(!versionUniqueID) + return PTFalse; + assert(callback); + if(!callback) + return PTFalse; + + // override hostname? + if (gPTAVercheckURL[0] == '\0') + sprintf(gPTAVercheckURL, PTA_DEFAULT_VERCHECK_URL); + + // Store some data. + /////////////////// + data = (ptaPatchData *)gsimalloc(sizeof(ptaPatchData)); + if(!data) + return PTFalse; + memset(data, 0, sizeof(ptaPatchData)); + data->callback = callback; + data->param = param; + + // Build the URL. + ///////////////// + charsWritten = + snprintf(aURL, PTA_MAX_STRING_SIZE, + "%s?productid=%d&versionuniqueid=%s&distid=%d&gamename=%s", + gPTAVercheckURL, productID, versionUniqueID, distributionID, + __GSIACGamename); + + assert(charsWritten >= 0); + if (charsWritten < 0) + return PTFalse; + + // Send the request. + //////////////////// + if((ghttpGetFileA(aURL, (GHTTPBool)blocking, ptaPatchCompletedCallback, data) == (unsigned char)(-1)) && !blocking) + return PTFalse; + + return PTTrue; +} +#ifdef GSI_UNICODE +PTBool ptCheckForPatchW +( + int productID, + const unsigned short* versionUniqueID, + int distributionID, + ptPatchCallback callback, + PTBool blocking, + void * param +) +{ + char versionUniqueID_A[255]; + UCS2ToUTF8String(versionUniqueID, versionUniqueID_A); + return ptCheckForPatchA(productID, versionUniqueID_A, distributionID, callback, blocking, param); +} +#endif + +PTBool ptTrackUsageA +( + int userID, + int productID, + const char * versionUniqueID, + int distributionID, + PTBool blocking +) +{ + int charsWritten; + char aURL[PTA_MAX_STRING_SIZE]; + + // check if the backend is available + if(__GSIACResult != GSIACAvailable) + return PTFalse; + + // Check the arguments. + /////////////////////// + assert(versionUniqueID); + if(!versionUniqueID) + return PTFalse; + + // override hostname? + if (gPTAMOTDURL[0] == '\0') + sprintf(gPTAMOTDURL, PTA_DEFAULT_MOTD_URL); + + // Build the URL. + ///////////////// + charsWritten = snprintf(aURL, PTA_MAX_STRING_SIZE, + "%s?userid=%d&productid=%d&versionuniqueid=%s&distid=%d&uniqueid=%s&gamename=%s", + gPTAMOTDURL, userID, productID, versionUniqueID, distributionID, GOAGetUniqueID(), + __GSIACGamename); + assert(charsWritten >= 0); + if (charsWritten < 0) + return PTFalse; + // Send the info. + ///////////////// + if(ghttpGetFileA(aURL, (GHTTPBool)blocking, NULL, NULL) == -1) + return PTFalse; + + return PTTrue; +} +#ifdef GSI_UNICODE +PTBool ptTrackUsageW +( + int userID, + int productID, + const unsigned short* versionUniqueID, + int distributionID, + PTBool blocking +) +{ + char versionUniqueID_A[255]; + UCS2ToUTF8String(versionUniqueID, versionUniqueID_A); + return ptTrackUsageA(userID, productID, versionUniqueID_A, distributionID, blocking); +} +#endif + +PTBool ptCreateCheckPatchTrackUsageReqA +( + int userID, + int productID, + const char * versionUniqueID, + int distributionID, + ptPatchCallback callback, + PTBool blocking, + int *request, + void * param +) +{ + int charsWritten; + char aURL[PTA_MAX_STRING_SIZE]; + ptaPatchData * data; + + // check if the backend is available + if(__GSIACResult != GSIACAvailable) + return PTFalse; + + // Check the arguments. + /////////////////////// + assert(versionUniqueID); + if(!versionUniqueID) + return PTFalse; + assert(callback); + if(!callback) + return PTFalse; + + // Store some data. + /////////////////// + data = (ptaPatchData *)gsimalloc(sizeof(ptaPatchData)); + if(!data) + return PTFalse; + memset(data, 0, sizeof(ptaPatchData)); + data->callback = callback; + data->param = param; + + // override hostname? + if (gPTAVercheckURL[0] == '\0') + sprintf(gPTAVercheckURL, PTA_DEFAULT_VERCHECK_URL); + + // Build the URL. + ///////////////// + charsWritten = snprintf(aURL, PTA_MAX_STRING_SIZE, + "%s?userid=%d&productid=%d&versionuniqueid=%s&distid=%d&uniqueid=%s&gamename=%s", + gPTAVercheckURL, userID, productID, versionUniqueID, distributionID, GOAGetUniqueID(), + __GSIACGamename); + + assert(charsWritten >= 0); + if (charsWritten < 0) + return PTFalse; + + // Send the request. + //////////////////// + *request = ghttpGetFileA(aURL, (GHTTPBool)blocking, ptaPatchCompletedCallback, data); + if (*request == -1) + return PTFalse; + return PTTrue; +} + +#ifdef GSI_UNICODE +PTBool ptCreateCheckPatchTrackUsageReqW +( + int userID, + int productID, + const unsigned short * versionUniqueID, + int distributionID, + ptPatchCallback callback, + PTBool blocking, + int *request, + void * param + ) +{ + char versionUniqueID_A[255]; + UCS2ToUTF8String(versionUniqueID, versionUniqueID_A); + return ptCreateCheckPatchTrackUsageReqA(userID, productID, versionUniqueID_A, distributionID, callback, blocking, request, param); +} +#endif + +PTBool ptCheckForPatchAndTrackUsageA +( + int userID, + int productID, + const char * versionUniqueID, + int distributionID, + ptPatchCallback callback, + PTBool blocking, + void * param +) +{ + GHTTPRequest aRequest; + + // create the request and send it. + /////////////////////////////////// + return ptCreateCheckPatchTrackUsageReqA(userID, productID, versionUniqueID, distributionID, callback, blocking, &aRequest, param); + +} + +#ifdef GSI_UNICODE +PTBool ptCheckForPatchAndTrackUsageW +( + int userID, + int productID, + const unsigned short * versionUniqueID, + int distributionID, + ptPatchCallback callback, + PTBool blocking, + void * param +) +{ + char versionUniqueID_A[255]; + UCS2ToUTF8String(versionUniqueID, versionUniqueID_A); + return ptCheckForPatchAndTrackUsageA(userID, productID, versionUniqueID_A, distributionID, callback, blocking, param); +} +#endif + +static GHTTPBool ptaFilePlanetCompletedCallback +( + GHTTPRequest request, + GHTTPResult result, + char * buffer, + GHTTPByteCount bufferLen, + void * param +) +{ + ptaFilePlanetInfoData * data = (ptaFilePlanetInfoData *)param; + int len; + char description[256]; + char size[64]; + char * mirrorNames[MAX_MIRRORS]; + char * mirrorURLs[MAX_MIRRORS]; + int i; + char * str; + + // check if the backend is available + if(__GSIACResult != GSIACAvailable) + return GHTTPFalse; + + GSI_UNUSED(request); + GSI_UNUSED(bufferLen); + + // Check for success. + ///////////////////// + if(result != GHTTPSuccess) + return ptaFilePlanetInfoFailed(data); + + // Get the description. + /////////////////////// + len = ptaFillLine(buffer); + if(len == EOF) + return ptaFilePlanetInfoFailed(data); + buffer += len; + strncpy(description, Line, sizeof(description)); + description[sizeof(description) - 1] = '\0'; + + // Get the size. + //////////////// + len = ptaFillLine(buffer); + if(len == EOF) + return ptaFilePlanetInfoFailed(data); + buffer += len; + strncpy(size, Line, sizeof(size)); + size[sizeof(size) - 1] = '\0'; + + // Get the mirrors. + /////////////////// + for(i = 0 ; (i < MAX_MIRRORS) && ((len = ptaFillLine(buffer)) != EOF) ; ) + { + // Adjust the buffer. + ///////////////////// + buffer += len; + + // Find the tab. + //////////////// + str = strchr(Line, '\t'); + if(!str) + continue; + + // Copy off the name. + ///////////////////// + len = (str - Line); + mirrorNames[i] = (char *)gsimalloc((unsigned int)len + 1); + if(!mirrorNames[i]) + break; + memcpy(mirrorNames[i], Line, (unsigned int)len); + mirrorNames[i][len] = '\0'; + + // Copy off the URL. + //////////////////// + str++; + len = (int)strlen(str); + mirrorURLs[i] = (char *)gsimalloc((unsigned int)len + 1); + if(!mirrorURLs[i]) + { + gsifree(mirrorNames[i]); + break; + } + strcpy(mirrorURLs[i], str); + + // One more mirror. + /////////////////// + i++; + } + + // Call the callback. + ///////////////////// + ptaCallFilePlanetInfoCallback(data, PTTrue, description, size, i, mirrorNames, mirrorURLs); + + return GHTTPTrue; +} + +// 9/7/2004 (xgd) ptLookupFilePlanetInfo() deprecated; per case 2724. +// +PTBool ptLookupFilePlanetInfo +( + int fileID, + ptFilePlanetInfoCallback callback, + PTBool blocking, + void * param +) +{ + char aURL[PTA_MAX_STRING_SIZE]; + ptaFilePlanetInfoData * data; + + // Check the arguments. + /////////////////////// + assert(callback); + if(!callback) + return PTFalse; + + // override hostname? + if (gPTAFilePlanetURL[0] == '\0') + sprintf(gPTAFilePlanetURL, PTA_DEFAULT_FILEPLANET_URL); + + // Store some data. + /////////////////// + data = (ptaFilePlanetInfoData *)gsimalloc(sizeof(ptaFilePlanetInfoData)); + if(!data) + return PTFalse; + memset(data, 0, sizeof(ptaFilePlanetInfoData)); + data->callback = callback; + data->param = param; + data->fileID = fileID; + + // Build the URL. + ///////////////// + // 11/24/2004 - Added By Saad Nader + // Now using string size as limit for printing + // also null terminate string automatically + /////////////////////////////////////////////// + snprintf(aURL, PTA_MAX_STRING_SIZE, + "%s?file=%d&gamename=%s", gPTAFilePlanetURL, fileID, __GSIACGamename); + + + // Send the request. + //////////////////// + if((ghttpGetFileA(aURL, (GHTTPBool)blocking, ptaFilePlanetCompletedCallback, data) == -1) && !blocking) + return PTFalse; + + return PTTrue; +} diff --git a/code/gamespy/qr2/changelog.txt b/code/gamespy/qr2/changelog.txt new file mode 100644 index 00000000..42149dc2 --- /dev/null +++ b/code/gamespy/qr2/changelog.txt @@ -0,0 +1,155 @@ +Changelog for: GameSpy Query & Reporting 2 SDK +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 3.04.00 RMV RELEASE Released to Developer Site +12-12-2007 3.03.07 DES FIX No longer overwrites the availability check if not needed. +12-10-2007 3.03.06 SAH OTHER Updated Nitro project, fixed up compilation errors in sample +12-07-2007 3.03.05 SN FIX Modified sample to use AppDebug +12-07-2007 3.03.05 SAH OTHER Added NatNeg to VC6 project to remove compiler error +11-19-2007 3.03.04 SAH FIX Changed qr2_shutdown, it now frees the negotiate list for ACE +11-05-2007 3.03.03 DES FEATURE Added clientconnected callback (ACE functionality) +08-28-2007 3.03.02 SAH FIX Removed qr2_parse_queryW, it was incorrectly trying to convert to UTF8 +08-22-2007 3.03.01 RMV OTHER Added Unicode configurations to qr2csample projects + Modified qr2csample.c to make it more thorough and developer friendly (and added ReadMe) +08-06-2007 3.03.00 RMV RELEASE Released to Developer Site +07-10-2007 3.02.03 RMV OTHER Raised qr2csample Project warning levels (to 4) and fixed compiler warnings +01-25-2007 3.02.02 SAH FIX Added COUNTRY_KEY and REGION_KEY to reserved keys list + 3.02.02 SAH FIX Added internal function for SB to check if a key is a Query-Master-Only key +01-16-2007 3.02.01 DES FEATURE Added X360 support +12-15-2006 3.02.00 MJW RELEASE Released to Developer Site +11-10-2006 3.01.34 JR RELEASE Limited release +10-23-2006 3.01.34 DES RELEASE Limited release +10-10-2006 3.01.34 DES FEATURE Added ability to call qr2_register_keyA/W directly +10-05-2006 3.01.33 SAH FIX Updated MacOSX Makefile +10-05-2006 3.01.32 DES FIX Changed method of incrementing progress->mCurKeyType to work with VS2K5 +09-28-2006 3.01.31 SAH FIX Fixed PS3 project to work with PS3 095.00x SDK; changed included libaries in linker input. +08-22-2006 3.01.30 SAH FIX Removed static caching of local ips - fixes bug where DCHP allocates a new ip between games +08-02-2006 3.01.29 SAH RELEASE Releasing to developer site +07-31-2006 3.01.29 SAH FIX Fixed PS3 project file - added post-build step to create *.SELF for execution +07-25-2006 3.01.28 SAH FIX Fixed NITRO project, include for crt0.o now above others so it add correct file +07-24-2006 3.01.27 SAH FIX Removed #ifdef _PS3 for socket calls (changed platformSocket.h to typecast calls) +07-06-2006 3.01.26 SAH FIX Fixed PSP project file to not explicitly include the PSP linker file +07-06-2006 3.01.25 SAH FIX Fixed Linux makefile to work with pthreads (http asynch DNS lookups) + SAH FIX Fixed NITRO project & linker command file (to work with CW 2.0/NitroSDK 3.1) +06-09-2006 3.01.24 MJ FEATURE Added NN_GROUP_ID_KEY for new nat negotiation SDK +06-06-2006 3.01.23 SAH FIX moved the (len<4) check for ip-verify queries into the IF block +05-31-2006 3.01.22 SAH RELEASE Releasing to developer site + SAH FIX Fixed Linux makefile +05-30-2006 3.01.20 SAH FIX Fixed PS3 projects to work with PS3(084_001 SDK) +05-25-2006 3.01.19 SAH FIX Moved GSI_UNUSED in qr2csample + SAH FIX Changed PSP project warning levels +05-24-2006 3.01.18 SAH FIX redid previous change, added necessary #ifdefs +05-23-2006 3.01.17 SAH FIX Added gsSetDebug for PS3, and GSI_UNUSED statements to qr2csample.c +05-19-2006 3.01.16 SAH FIX Added gsTestMain.c to nitro CodeWarrior project +05-15-2006 3.01.15 SAH FIX Added "PS3 Release" configuration to project +04-25-2006 3.01.14 SAH RELEASE Releasing to developer site +04-24-2006 3.01.14 SAH FIX Fixed Nitro project files to work on build machine +04-20-2006 3.01.13 SAH FIX Commented out unnecessary variables +04-20-2006 3.01.12 SAH FIX Added _PS3 wrapper typecast for recvfrom calls +04-18-2006 3.01.11 SAH FIX Added || defined(_PS3) to qr2csample.c for PS3 support +01-27-2006 3.01.10 SN RELEASE Releasing to developer site +12-28-2005 3.01.10 SN FIX Modified test application to use new memory manager code +12-22-2005 3.01.09 SN OTHER Cleaned up project file and added missing common code files +11-15-2005 3.01.08 DES FIX Updated DS support. +11-14-2005 3.01.07 DES FIX Updated OSX support. + DES FEATURE Added GSI_DOMAIN_NAME support. +10-24-2005 3.01.06 SN FIX Fixed timer on keep alives causing them to be sent after every heartbeat +10-12-2005 3.01.06 BED RELEASE Releasing to developer site. +10-11-2005 3.01.05 BED FEATURE Added query challege/response to prevent ip spoofing. +09-21-2005 3.01.04 DES FEATURE Updated DS support +08-18-2005 3.01.03 BED CLEANUP Updated LINUX makefile for new common code and Fedora-Redhat +07-28-2005 3.01.02 SN RELEASE Releasing to developer site. +07-28-2005 3.01.02 SN FIX fixed code causing warning in qr2csample +06-03-2005 3.01.01 SN RELEASE Releasing to developer site. +05-05-2005 3.01.01 BED FIX Updated file pathes to use new common folder +05-04-2005 3.01.00 SN OTHER Created Visual Studio .NET project +04-29-2005 3.01.00 BED FEATURE Added support for multi-packet qr2 responses. (Full keys direct only) + BED FIX Individual response packets are limited to 1400 bytes (reasonable udp size) + BED FIX Heartbeats now truncated to server keys only when larger than 1400 bytes. +04-28-2005 3.00.45 SN RELEASE Releasing to developer site. +04-27-2005 3.00.45 DES RELEASE Limited release to Nintendo DS developers. +04-25-2005 3.00.45 DES CLEANUP Minor debug logging cleanup. +04-04-2005 3.00.44 SN RELEASE Releasing to developer site. +03-14-2005 3.00.44 DES FEATURE Nintendo DS support +01-27-2005 3.00.43 DES FIX Fixed custom SN sendto and moved it to nonport +09-28-2004 3.00.42 DES FIX Cleaned up an unused var warning when GSI_MEM_MANAGED was off. +09-23-2004 3.00.41 DES FIX Fixed source control issues with qr2.dsw and qr2csample.dsp. +09-16-2004 3.00.40 SN RELEASE Releasing to developer site. +09-03-2003 3.00.40 BED FEATURE Added support for GSI_COMMON_DEBUG and GSI_MEM_MANAGED modes. +08-27-2004 3.00.39 DES CLEANUP Removed MacOS style includes + DES CLEANUP Fixed warnings under OSX + DES CLEANUP Updated Win32 project configurations + DES CLEANUP General Unicode cleanup + DES FEATURE Added OSX Makefile +08-25-2004 3.00.38 DES FEATURE Added OSX makefile +08-05-2004 3.00.37 SN RELEASE Releasing to developer site. +07-19-2004 3.00.37 SN FIX Updated code with explicit casts to remove implicit cast error + when compiling at highest level and warnings treated as errors. +07-15-2004 3.00.36 BED FIX Now must manually call qr2_internal_key_list_free when using GSI_UNICODE +06-18-2004 3.00.35 BED FIX Removed confusing key name from sample. + BED FIX Added some comments to sample, cleaned up Unicode support in sample. +06-18-2004 3.00.34 BED RELEASE Releasing to developer site. + BED FIX Fixed bug with handling of "No Challenge" error + BED FEATURE Added #defines to simulate hard and soft firewalls +11-10-2003 3.00.33 DES RELEASE Releasing to developer site. +11-07-2003 3.00.33 DES FIX Updated the linux makefile. +10-27-2003 3.00.32 BED FEATURE Added CodeWarrior Unicode switch for PS2. +10-24-2003 3.00.31 BED FEATURE SendStateChanged() will now be queued instead of dropped when sent too soon. +10-24-2003 3.00.30 BED FIX Sample file updated, missing from last release due to directory mismatch + BED FIX Removed misc warnings +10-22-2003 3.00.29 BED RELEASE Releasing to developer site. (UNIQUE NICK AND UNICODE SUPPORT) +10-22-2003 3.00.28 BED FIX Removed some compiler warnings on strict setting. +09-09-2003 3.00.27 BED FEATURE Added UTF-8 wrapper -- define GSI_UNICODE + OTHER Added stringutil.c and stringutil.h to sample project files +08-25-2003 3.00.26 DES FIX Updated to be compatible with new public IP and port encoding. +08-18-2003 3.00.25 DES FEATURE Added a callback for getting your public reporting address. +07-24-2003 3.00.24 DES RELEASE Releasing to developer site. +07-21-2003 3.00.24 BED FIX Moved VisualStudio workspace into appropriate directory. +07-18-2003 3.00.23 BED FEATURE Added CodeWarrior (PS2) sample project file. + BED CLEANUP General cleanup to remove CodeWarrior warnings. +07-17-2003 3.00.22 DES CLEANUP Cleaned up the PS2 Makefile, it now uses Makefile.commmon. +07-16-2003 3.00.21 DES FIX Changed a __mips64 check to a _PS2 check. + BED FEATURE Added ProDG sample project files. +06-11-2003 3.00.20 DES RELEASE Releasing to developer site. +05-09-2003 3.00.20 DES CLEANUP Removed Dreamcast support. + FIX Metrowerks for Win32 is no longer falsely identified as MacOS. +05-07-2003 3.00.19 DES RELEASE Releasing to developer site. +05-06-2003 3.00.19 DES FIX Old style replies were not correctly handling callbacks that didn't add a value. +04-07-2003 3.00.18 DES FIX Fixed the PID__KEY define (changed to 27 from 26). +03-11-2003 3.00.17 DES OTHER Split socket creation into a seperate internal function, needed for Peer. +02-28-2003 3.00.16 DES RELEASE Releasing to developer site. +02-27-2003 3.00.16 DES FIX If not using NULL as the qr2_t parameter, qr2_shutdown() would access + the structure after it was freed. +02-05-2003 3.00.15 DES RELEASE Releasing to developer site. +02-05-2003 3.00.15 DES CLEANUP Switched to use CanReceiveOnSocket instead of select. +02-04-2003 3.00.14 DES RELEASE Relasing to developer site. +02-04-2003 3.00.14 DDW FIX Added an extra \ to the "final" key in the GOA-style reply to fix illegal format +01-31-2003 3.00.13 DES FEATURE Added a check which adds an empty string to the buffer if nothing was added in + a call to the server_key_callback, player_key_callback, or team_key_callback. + Apps no longer need to handle the case of an unrecognized key. +01-20-2003 3.00.12 DES OTHER Changed to use new .master.gamespy.com master server naming convention. +01-16-2003 3.00.11 JED OTHER renamed a static var in qr2.c that was causing linker name conflicts in GS Arcade +01-06-2003 3.00.10 DES OTHER Moved a few things from qr2.c to qr2.h for CDKey SDK integration. +12-19-2002 3.00.09 DES RELEASE Releasing to developer site. +12-19-2002 3.00.09 DES CLEANUP Removed assert.h include. +12-16-2002 3.00.08 DES FEATURE Added a check to prevent statechanges from being sent too frequently. + This is to help alleviate the problem of servers that flood our master server. +12-13-2002 3.00.07 DES FEATURE Added PS2 eenet stack support. +12-11-2002 3.00.06 DES CLEANUP Changed from using gethostname to getlocalhost for getting local IPs. +12-06-2002 3.00.05 DES RELEASE Releasing to developer site. +12-06-2002 3.00.05 DDW FIX Added new heartbeat type so that if restarting a crashed server + the nochallenge error will not be triggered. +12-03-2002 3.00.04 DES RELEASE Releasing to developer site. +12-03-2002 3.00.04 DES FEATURE Added a Linux Makefile. +11-22-2002 3.00.03 DES RELEASE Releasing to developer site. +11-20-2002 3.00.03 DES FIX Only call SocketShutDown if we called SocketStartup. +11-20-2002 3.00.02 DES FEATURE Added support for compiling on the PS2. +09-26-2002 3.00.01 DES RELEASE Limited release on developer site +09-25-2002 3.00.01 DDW OTHER Changelog started +09-23-2002 3.00.01 DDW RELEASE Release to EAPAC for Generals (Peer) +09-18-2002 3.00.01 DDW RELEASE Release to EAPAC for Generals +09-18-2002 3.00.01 DDW FEATURE Added 20-sec keep-alive packets +09-06-2002 3.00.00 DDW RELEASE Release to EAPAC for Generals + diff --git a/code/gamespy/qr2/qr2.c b/code/gamespy/qr2/qr2.c new file mode 100644 index 00000000..4918dc98 --- /dev/null +++ b/code/gamespy/qr2/qr2.c @@ -0,0 +1,1753 @@ +/******** +INCLUDES +********/ +#include "../common/gsCommon.h" +#include "../common/gsAvailable.h" +#include "qr2.h" +#include "qr2regkeys.h" +#include "../natneg/natneg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __MWERKS__ // Codewarrior requires function prototypes +qr2_error_t qr2_initW(/*[out]*/qr2_t *qrec, const unsigned short *ip, int baseport, const unsigned short *gamename, + const unsigned short *secret_key, int ispublic, int natnegotiate, qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, qr2_playerteamkeycallback_t team_key_callback, qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, qr2_adderrorcallback_t adderror_callback, void *userdata); +qr2_error_t qr2_init_socketW(/*[out]*/qr2_t *qrec, SOCKET s, int boundport, const unsigned short *gamename, + const unsigned short *secret_key, int ispublic, int natnegotiate, qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, qr2_playerteamkeycallback_t team_key_callback, qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, qr2_adderrorcallback_t adderror_callback, void *userdata); +qr2_error_t qr2_init_socketA(/*[out]*/qr2_t *qrec, SOCKET s, int boundport, const char *gamename, const char *secret_key, + int ispublic, int natnegotiate, qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, void *userdata); +qr2_error_t qr2_initA(/*[out]*/qr2_t *qrec, const char *ip, int baseport, const char *gamename, const char *secret_key, + int ispublic, int natnegotiate, qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, void *userdata); +#endif + +/******** +DEFINES +********/ +#define MASTER_PORT 27900 +#define MASTER_ADDR "master." GSI_DOMAIN_NAME + //#define MASTER_ADDR "207.199.80.230" +#define FIRST_HB_TIME 10000 /* 10 sec */ +#define HB_TIME 60000 /* 1 minute */ +#define KA_TIME 20000 /* 20 sec */ +#define MIN_STATECHANGED_HB_TIME 10000 /* 10 sec */ +#define MAX_FIRST_COUNT 4 /* 4 tries */ +#define MAX_DATA_SIZE 1400 +#define INBUF_LEN 256 +#define PUBLIC_ADDR_LEN 12 +#define QR2_OPTION_USE_QUERY_CHALLENGE 128 + +#define PACKET_QUERY 0x00 +#define PACKET_CHALLENGE 0x01 +#define PACKET_ECHO 0x02 +#define PACKET_ECHO_RESPONSE 0x05 // 0x05, not 0x03 (order) +#define PACKET_HEARTBEAT 0x03 +#define PACKET_ADDERROR 0x04 +#define PACKET_CLIENT_MESSAGE 0x06 +#define PACKET_CLIENT_MESSAGE_ACK 0x07 +#define PACKET_KEEPALIVE 0x08 +#define PACKET_PREQUERY_IP_VERIFY 0x09 + + +#define MAX_LOCAL_IP 5 + +//magic bytes for nat negotiation message +#define NATNEG_MAGIC_LEN 6 +#define NN_MAGIC_0 0xFD +#define NN_MAGIC_1 0xFC +#define NN_MAGIC_2 0x1E +#define NN_MAGIC_3 0x66 +#define NN_MAGIC_4 0x6A +#define NN_MAGIC_5 0xB2 + +// ex flags are the 11th byte in the query packet +// Old queries will end at 10 bytes. +#define QR2_EXFLAG_SPLIT (1<<0) + +// Some other settings for split packet responses +#define QR2_SPLITNUM_MAX 7 +#define QR2_SPLITNUM_FINALFLAG (1<<7) + + +/******** +TYPEDEFS +********/ +typedef unsigned char uchar; + +struct qr2_keybuffer_s +{ + uchar keys[MAX_REGISTERED_KEYS]; + int numkeys; +}; + +struct qr2_buffer_s +{ + char buffer[MAX_DATA_SIZE]; + int len; +}; + +#define AVAILABLE_BUFFER_LEN(a) (MAX_DATA_SIZE - (a)->len) + +/******** +VARS +********/ +struct qr2_implementation_s static_qr2_rec = {INVALID_SOCKET}; +static qr2_t current_rec = &static_qr2_rec; +char qr2_hostname[64]; + +static int num_local_ips = 0; +static struct in_addr local_ip_list[MAX_LOCAL_IP]; + + +/******** +PROTOTYPES +********/ +static void send_heartbeat(qr2_t qrec, int statechanged); +static void send_keepalive(qr2_t qrec); +static int get_sockaddrin(const char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent); +static void qr2_check_queries(qr2_t qrec); +static void qr2_check_send_heartbeat(qr2_t qrec); +static void enum_local_ips(); +static void qr2_expire_ip_verify(qr2_t qrec); +qr2_error_t qr2_create_socket(/*[out]*/SOCKET *sock, const char *ip, /*[in/out]*/int * port); + +/****************************************************************************/ +/* PUBLIC FUNCTIONS */ +/****************************************************************************/ + +/* qr2_init: Initializes the sockets, etc. Returns an error value +if an error occured, or 0 otherwise */ +qr2_error_t qr2_init_socketA(/*[out]*/qr2_t *qrec, SOCKET s, int boundport, const char *gamename, const char *secret_key, + int ispublic, int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, + void *userdata) +{ + char hostname[64]; + int ret; + int i; + qr2_t cr; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_init_socket()\r\n"); + + if (qrec == NULL) + { + cr = &static_qr2_rec; + } + else + { + *qrec = (qr2_t)gsimalloc(sizeof(struct qr2_implementation_s)); + cr = *qrec; + } + srand((unsigned int)current_time()); + strcpy(cr->gamename,gamename); + strcpy(cr->secret_key,secret_key); + cr->qport = boundport; + cr->lastheartbeat = 0; + cr->lastka = 0; + cr->hbsock = s; + cr->listed_state = 1; + cr->udata = userdata; + cr->server_key_callback = server_key_callback; + cr->player_key_callback = player_key_callback; + cr->team_key_callback = team_key_callback; + cr->key_list_callback = key_list_callback; + cr->playerteam_count_callback = playerteam_count_callback; + cr->adderror_callback = adderror_callback; + cr->nn_callback = NULL; + cr->cm_callback = NULL; + cr->cdkeyprocess = NULL; + cr->ispublic = ispublic; + cr->read_socket = 0; + cr->nat_negotiate = natnegotiate; + cr->publicip = 0; + cr->publicport = 0; + cr->pa_callback = NULL; + cr->cc_callback = NULL; + cr->userstatechangerequested = 0; + cr->backendoptions = 0; + + for (i = 0 ; i < REQUEST_KEY_LEN ; i++) + cr->instance_key[i] = (char)(rand() % 0xFF); + for (i = 0 ; i < RECENT_CLIENT_MESSAGES_TO_TRACK ; i++) + cr->client_message_keys[i] = -1; + cr->cur_message_key = 0; + + memset(cr->ipverify, 0, sizeof(cr->ipverify)); + + //if (num_local_ips == 0) - caching IPs can result in problems if DHCP has allocated a new one + enum_local_ips(); + + if (ispublic) + { + int override = qr2_hostname[0]; + if(!override) + sprintf(hostname, "%s.master." GSI_DOMAIN_NAME, gamename); + ret = get_sockaddrin(override?qr2_hostname:hostname, MASTER_PORT, &(cr->hbaddr), NULL); + + if (ret == 1) + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "%s resolved to %s\r\n", override?qr2_hostname:hostname, inet_ntoa(cr->hbaddr.sin_addr)); + } + else + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError, + "Failed on DNS lookup for %s \r\n", override?qr2_hostname:hostname); + } + } + else //don't need to look up + ret = 1; + if (!ret) + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_HotError, + "qr2_init_socket() returned failed (DNS error)\r\n"); + return e_qrdnserror; + } + else + { + return e_qrnoerror; + } + + +} + +qr2_error_t qr2_init_socketW(/*[out]*/qr2_t *qrec, SOCKET s, int boundport, const unsigned short *gamename, const unsigned short *secret_key, + int ispublic, int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, + void *userdata) +{ + char gamename_A[255]; + char secretkey_A[255]; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_init_socketW()\r\n"); + + UCS2ToAsciiString(gamename, gamename_A); + UCS2ToAsciiString(secret_key, secretkey_A); + + return qr2_init_socketA(qrec, s, boundport, gamename_A, secretkey_A, ispublic, natnegotiate, + server_key_callback, player_key_callback, team_key_callback, + key_list_callback, playerteam_count_callback, adderror_callback, userdata); +} + +qr2_error_t qr2_create_socket(/*[out]*/SOCKET *sock, const char *ip, /*[in/out]*/int * port) +{ + struct sockaddr_in saddr; + SOCKET hbsock; + int maxport; + int lasterror = 0; + int baseport = *port; + +#if defined(_LINUX) + unsigned int saddrlen; +#else + int saddrlen; +#endif + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_create_socket()\r\n"); + + SocketStartUp(); + + hbsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (INVALID_SOCKET == hbsock) + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError, + "Failed to create heartbeat socket\r\n"); + return e_qrwsockerror; + } + + maxport = baseport + NUM_PORTS_TO_TRY; + while (baseport < maxport) + { + get_sockaddrin(ip,baseport,&saddr,NULL); + if (saddr.sin_addr.s_addr == htonl(0x7F000001)) //localhost -- we don't want that! + saddr.sin_addr.s_addr = INADDR_ANY; + + lasterror = bind(hbsock, (struct sockaddr *)&saddr, sizeof(saddr)); + if (lasterror == 0) + break; //we found a port + baseport++; + } + + if (lasterror != 0) //we weren't able to find a port + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError, + "Failed to bind() query socket\r\n"); + return e_qrbinderror; + } + + if (baseport == 0) //we bound it dynamically + { + saddrlen = sizeof(saddr); + + lasterror = getsockname(hbsock,(struct sockaddr *)&saddr, &saddrlen); + + if (lasterror) + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError, + "Query socket bind() success, but getsockname() failed\r\n"); + return e_qrbinderror; + } + baseport = ntohs(saddr.sin_port); + } + + *sock = hbsock; + *port = baseport; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Query socket created and bound to port %d\r\n", *port); + + return e_qrnoerror; +} + +qr2_error_t qr2_initA(/*[out]*/qr2_t *qrec, const char *ip, int baseport, const char *gamename, const char *secret_key, + int ispublic, int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, + void *userdata) +{ + SOCKET hbsock; + qr2_error_t ret; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_init()\r\n"); + + ret = qr2_create_socket(&hbsock, ip, &baseport); + if(ret != e_qrnoerror) + { + SocketShutDown(); + return ret; + } + + ret = qr2_init_socketA(qrec, hbsock, baseport, gamename, secret_key, ispublic, natnegotiate, server_key_callback, player_key_callback, team_key_callback, key_list_callback, playerteam_count_callback, adderror_callback, userdata); + if (qrec == NULL) + qrec = ¤t_rec; + (*qrec)->read_socket = 1; + + return ret; +} + +qr2_error_t qr2_initW(/*[out]*/qr2_t *qrec, const unsigned short *ip, int baseport, const unsigned short *gamename, const unsigned short *secret_key, + int ispublic, int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, + void *userdata) +{ + char ip_A[255]; + char gamename_A[255]; + char secretkey_A[255]; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_initW()\r\n"); + + if (ip != NULL) // NULL value is valid for IP + UCS2ToAsciiString(ip, ip_A); + UCS2ToAsciiString(gamename, gamename_A); + UCS2ToAsciiString(secret_key, secretkey_A); + + return qr2_initA(qrec, (ip!=NULL)?ip_A:NULL, baseport, gamename_A, secretkey_A, ispublic, natnegotiate, server_key_callback, player_key_callback, team_key_callback, key_list_callback, playerteam_count_callback, adderror_callback, userdata); +} + +void qr2_register_natneg_callback(qr2_t qrec, qr2_natnegcallback_t nncallback) +{ + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_register_natneg_callback()\r\n"); + + if (qrec == NULL) + qrec = current_rec; + qrec->nn_callback = nncallback; + +} +void qr2_register_clientmessage_callback(qr2_t qrec, qr2_clientmessagecallback_t cmcallback) +{ + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_register_clientmessage_callback()\r\n"); + + if (qrec == NULL) + qrec = current_rec; + qrec->cm_callback = cmcallback; +} +void qr2_register_publicaddress_callback(qr2_t qrec, qr2_publicaddresscallback_t pacallback) +{ + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_register_publicaddress_callback()\r\n"); + + if (qrec == NULL) + qrec = current_rec; + qrec->pa_callback = pacallback; +} +void qr2_register_clientconnected_callback(qr2_t qrec, qr2_clientconnectedcallback_t cccallback) +{ + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_register_clientconnected_callback()\r\n"); + + if (qrec == NULL) + qrec = current_rec; + qrec->cc_callback = cccallback; +} + +void qr2_register_denyresponsetoip_callback(qr2_t qrec, qr2_denyqr2responsetoipcallback_t dertoipcallback) +{ + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_register_denyresponsetoip_callback()\r\n"); + + if (qrec == NULL) + qrec = current_rec; + qrec->denyresp2_ip_callback = dertoipcallback; +} + + +/* qr2_think: Processes any waiting queries, and sends a +heartbeat if needed */ +void qr2_think(qr2_t qrec) +{ + if (qrec == NULL) + qrec = current_rec; + if (qrec->ispublic) + qr2_check_send_heartbeat(qrec); + qr2_check_queries(qrec); + qr2_expire_ip_verify(qrec); + NNThink(); +} + +/* qr2_check_queries: Processes any waiting queries */ +void qr2_check_queries(qr2_t qrec) +{ + static char indata[INBUF_LEN]; //256 byte input buffer + struct sockaddr_in saddr; + int error; + +#if defined(_LINUX) + unsigned int saddrlen = sizeof(struct sockaddr_in); +#else + int saddrlen = sizeof(struct sockaddr_in); +#endif + + if (!qrec->read_socket) + return; //not our job + + while(CanReceiveOnSocket(qrec->hbsock)) + { + //else we have data + error = (int)recvfrom(qrec->hbsock, indata, (INBUF_LEN - 1), 0, (struct sockaddr *)&saddr, &saddrlen); + + if (gsiSocketIsNotError(error)) + { + indata[error] = '\0'; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Received %d bytes on query socket\r\n", error); + gsDebugBinary(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_RawDump, + indata, error); + + qr2_parse_queryA(qrec, indata, error, (struct sockaddr *)&saddr); + } + else if (error == 0) + { + // socket closed? + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "CanReceiveOnSocket() returned true, but recvfrom return 0!\r\n", error); + } + else + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "CanReceiveOnSocket() returned true, but recvfrom failed!\r\n", error); + } + } +} + +/* check_send_heartbeat: Perform any scheduled outgoing +heartbeats */ +void qr2_check_send_heartbeat(qr2_t qrec) +{ + gsi_time tc = current_time(); + + if (INVALID_SOCKET == qrec->hbsock) + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError, + "HBSock is invalid\r\n"); + return; //no sockets to work with! + } + + //check if we need to send a heartbet + if (qrec->listed_state > 0 && tc - qrec->lastheartbeat > FIRST_HB_TIME) + { //check to see if we haven't gotten a query yet + if (qrec->listed_state >= MAX_FIRST_COUNT) + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError, + "No response from master, generating NoChallengeResponse error\r\n"); + + qrec->listed_state = 0; //we failed to get a challenge! let them know +#ifndef GSI_UNICODE + qrec->adderror_callback(e_qrnochallengeerror, "No challenge value was received from the master server.", qrec->udata); +#else + qrec->adderror_callback(e_qrnochallengeerror, L"No challenge value was received from the master server.", qrec->udata); +#endif + return; + } else + { + send_heartbeat(qrec, 3); + qrec->listed_state++; + } + } + else if (qrec->userstatechangerequested && (tc - qrec->lastheartbeat > MIN_STATECHANGED_HB_TIME)) + send_heartbeat(qrec,1); // Send out pending statechange request + else if (tc - qrec->lastheartbeat > HB_TIME || qrec->lastheartbeat == 0 || tc < qrec->lastheartbeat) + send_heartbeat(qrec,0); // Send out a normal hearbeat + + if (current_time() - qrec->lastka > KA_TIME) //send a keep alive (to keep NAT port mappings the same if possible) + send_keepalive(qrec); +} + +/* qr2_send_statechanged: Sends a statechanged heartbeat, call when +your gamemode changes */ +void qr2_send_statechanged(qr2_t qrec) +{ + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_send_statechanged()\r\n"); + + if (qrec == NULL) + qrec = current_rec; + if (!qrec->ispublic) + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Warning, + "Requested send statechange for LAN game, discarding\r\n"); + return; + } + if (current_time() - qrec->lastheartbeat < MIN_STATECHANGED_HB_TIME) + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Notice, + "Queing statechange for later send (too soon)\r\n"); + + // Queue up the statechange and send later + qrec->userstatechangerequested = 1; + return; // don't allow the server to spam statechanges + } + + send_heartbeat(qrec, 1); + qrec->userstatechangerequested = 0; // clear the flag in case a queued statechange was still pending +} + + +/* qr2_shutdown: Cleans up the sockets and shuts down */ +void qr2_shutdown(qr2_t qrec) +{ + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_StackTrace, + "qr2_shutdown()\r\n"); + + if (qrec == NULL) + qrec = current_rec; + if (qrec->ispublic) + send_heartbeat(qrec, 2); + if (INVALID_SOCKET != qrec->hbsock && qrec->read_socket) //if we own the socket + { + closesocket(qrec->hbsock); + } + qrec->hbsock = INVALID_SOCKET; + qrec->lastheartbeat = 0; + if(qrec->read_socket) //if we own the socket + { + SocketShutDown(); + } + if (qrec != &static_qr2_rec) //need to gsifree it, it was dynamically allocated + { + gsifree(qrec); + } + + // free ACE negotiate list + NNFreeNegotiateList(); //comment out if ACE is not being used + + // BD: Removed - Peer SDK repeatedly calls qr2_shutdown, but + // keys should only be deallocated once. + + // Developers should call this manually (when in GSI_UNICODE mode) + // qr2_internal_key_list_free(); +} + + +gsi_bool qr2_keybuffer_add(qr2_keybuffer_t keybuffer, int keyid) +{ + // mj these are codetime not runtime errors, changing to assert + if (keybuffer->numkeys >= MAX_REGISTERED_KEYS) + return gsi_false; + if (keyid < 1 || keyid > MAX_REGISTERED_KEYS) + return gsi_false; + + keybuffer->keys[keybuffer->numkeys++] = (uchar)keyid; + return gsi_true; +} + +gsi_bool qr2_buffer_add_int(qr2_buffer_t outbuf, int value) +{ + char temp[20]; + sprintf(temp, "%d", value); + return qr2_buffer_addA(outbuf, temp); +} + +gsi_bool qr2_buffer_addA(qr2_buffer_t outbuf, const char *value) +{ + GS_ASSERT(outbuf) + GS_ASSERT(value) + { + int copylen; + copylen = (int)strlen(value) + 1; + if (copylen > AVAILABLE_BUFFER_LEN(outbuf)) + copylen = AVAILABLE_BUFFER_LEN(outbuf); //max length we can fit in the buffer + if (copylen <= 0) + return gsi_false; //no space + memcpy(outbuf->buffer + outbuf->len, value, (unsigned int)copylen); + outbuf->len += copylen; + outbuf->buffer[outbuf->len - 1] = 0; //make sure it's null terminated + return gsi_true; + } +} +#if defined(GSI_UNICODE) +gsi_bool qr2_buffer_addW(qr2_buffer_t outbuf, const unsigned short *value) +{ + char value_A[4096]; + UCS2ToUTF8String(value, value_A); + return qr2_buffer_addA(outbuf, value_A); +} +#endif + + +static void enum_local_ips() +{ + struct hostent *phost; + phost = getlocalhost(); + if (phost == NULL) + return; + for (num_local_ips = 0 ; num_local_ips < MAX_LOCAL_IP ; num_local_ips++) + { + if (phost->h_addr_list[num_local_ips] == 0) + break; + memcpy(&local_ip_list[num_local_ips], phost->h_addr_list[num_local_ips], sizeof(struct in_addr)); + } +} + +/****************************************************************************/ + + +/* Return a sockaddrin for the given host (numeric or DNS) and port) +Returns the hostent in savehent if it is not NULL */ +static int get_sockaddrin(const char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent) +{ + struct hostent *hent = NULL; + + saddr->sin_family = AF_INET; + saddr->sin_port = htons((unsigned short)port); + if (host == NULL) + saddr->sin_addr.s_addr = INADDR_ANY; + else + saddr->sin_addr.s_addr = inet_addr(host); + + if (saddr->sin_addr.s_addr == INADDR_NONE && strcmp(host,"255.255.255.255") != 0) + { + hent = gethostbyname(host); + if (!hent) + return 0; + saddr->sin_addr.s_addr = *(unsigned int *)hent->h_addr_list[0]; + } + if (savehent != NULL) + *savehent = hent; + return 1; + +} + + + +/*****************************************************************************/ +/* Various encryption / encoding routines */ + +static void swap_byte ( uchar *a, uchar *b ) +{ + uchar swapByte; + + swapByte = *a; + *a = *b; + *b = swapByte; +} + +static uchar encode_ct ( uchar c ) +{ + if (c < 26) return (uchar)('A'+c); + if (c < 52) return (uchar)('a'+c-26); + if (c < 62) return (uchar)('0'+c-52); + if (c == 62) return (uchar)('+'); + if (c == 63) return (uchar)('/'); + + return 0; +} + +static void gs_encode ( uchar *ins, int size, uchar *result ) +{ + int i,pos; + uchar trip[3]; + uchar kwart[4]; + + i=0; + while (i < size) + { + for (pos=0 ; pos <= 2 ; pos++, i++) + if (i < size) trip[pos] = *ins++; + else trip[pos] = '\0'; + kwart[0] = (unsigned char)( (trip[0]) >> 2); + kwart[1] = (unsigned char)((((trip[0]) & 3) << 4) + ((trip[1]) >> 4)); + kwart[2] = (unsigned char)((((trip[1]) & 15) << 2) + ((trip[2]) >> 6)); + kwart[3] = (unsigned char)( (trip[2]) & 63); + for (pos=0; pos <= 3; pos++) *result++ = encode_ct(kwart[pos]); + } + *result='\0'; +} + +static void gs_encrypt ( uchar *key, int key_len, uchar *buffer_ptr, int buffer_len ) +{ + short counter; + uchar x, y, xorIndex; + uchar state[256]; + + for ( counter = 0; counter < 256; counter++) state[counter] = (uchar) counter; + + x = 0; y = 0; + for ( counter = 0; counter < 256; counter++) + { + y = (uchar)((key[x] + state[counter] + y) % 256); + x = (uchar)((x + 1) % key_len); + swap_byte ( &state[counter], &state[y] ); + } + + x = 0; y = 0; + for ( counter = 0; counter < buffer_len; counter ++) + { + x = (uchar)((x + buffer_ptr[counter] + 1) % 256); + y = (uchar)((state[x] + y) % 256); + swap_byte ( &state[x], &state[y] ); + xorIndex = (uchar)((state[x] + state[y]) % 256); + buffer_ptr[counter] ^= state[xorIndex]; + } +} + +/*****************************************************************************/ +/* NAT Negotiation support */ + +static void NatNegProgressCallback(NegotiateState state, void *userdata) +{ + // we don't do anything here + GSI_UNUSED(state); + GSI_UNUSED(userdata); +} + +static void NatNegCompletedCallback(NegotiateResult result, SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *userdata) +{ + qr2_t qrec = (qr2_t)userdata; + + if(qrec->cc_callback) + { + if(result == nr_success) + { + qrec->cc_callback(gamesocket, remoteaddr, qrec->udata); + } + } +} + +/*****************************************************************************/ + + +static void qr_add_packet_header(qr2_buffer_t buf, char ptype, char *reqkey) +{ + buf->buffer[0] = ptype; + memcpy(buf->buffer + 1, reqkey, REQUEST_KEY_LEN); + buf->len = REQUEST_KEY_LEN + 1; +} + +#define MAX_CHALLENGE 64 +static void compute_challenge_response(qr2_t qrec, qr2_buffer_t buf, char *challenge, int challengelen) +{ + char encrypted_val[MAX_CHALLENGE + 1]; //don't need to null terminate + + if(challengelen < 1) + return; // invalid, need room for the NUL + if (challengelen > (MAX_CHALLENGE + 1)) + return; //invalid + if (challenge[challengelen - 1] != 0) + return; //invalid - must be NTS + + strcpy(encrypted_val, challenge); + gs_encrypt((uchar *)qrec->secret_key, (int)strlen(qrec->secret_key), (uchar *)encrypted_val, challengelen - 1); + gs_encode((uchar *)encrypted_val,challengelen - 1, (uchar *)(buf->buffer + buf->len)); + buf->len += (int)strlen(buf->buffer + buf->len) + 1; + +} + +static void handle_public_address(qr2_t qrec, char * buffer) +{ + unsigned int ip; + unsigned int portTemp; + unsigned short port; + + // get the public ip and port as the master server sees it + sscanf(buffer, "%08X%04X", &ip, &portTemp); + port = (unsigned short)portTemp; + ip = htonl(ip); + + // sanity check + if((ip == 0) || (port == 0)) + return; + +#ifdef GSI_COMMON_DEBUG + { + IN_ADDR addr; + addr.s_addr = ip; + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice, + "Received public address (%s:%d)\r\n", inet_ntoa(addr), port); + } +#endif + + // has anything changed? + if((qrec->publicip != ip) || (qrec->publicport != port)) + { + qrec->publicip = ip; + qrec->publicport = port; + qrec->pa_callback(ip, port, qrec->udata); + } +} + +static void qr_build_partial_query_reply(qr2_t qrec, qr2_buffer_t buf, qr2_key_type keytype, int keycount, uchar *keys) +{ + struct qr2_keybuffer_s kb; + int playerteamcount; + unsigned short cttemp; + int i; + int pindex; + const char *k; + int len; + + kb.numkeys = 0; + if (keycount == 0) + return; //no keys wanted + + if (keytype == key_player || keytype == key_team) //need to add the player/team counts + { + if (AVAILABLE_BUFFER_LEN(buf) < sizeof(cttemp)) + return; //no more space + playerteamcount = qrec->playerteam_count_callback(keytype, qrec->udata); + cttemp = htons((unsigned short)playerteamcount); + memcpy(buf->buffer + buf->len, &cttemp, sizeof(cttemp)); + buf->len += sizeof(cttemp); + } else + playerteamcount = 1; + + if (keycount == 0xFF) //need to get the list of keys + { + qrec->key_list_callback(keytype, &kb, qrec->udata); + //add all the keys + for (i = 0 ; i < kb.numkeys ; i++) + { + k = qr2_registered_key_list[kb.keys[i]]; + if (k == NULL) + k = "unknown"; + qr2_buffer_addA(buf, k); + if (keytype == key_server) //add the server values + { + len = buf->len; + qrec->server_key_callback(kb.keys[i], buf, qrec->udata); + if(len == buf->len) + qr2_buffer_addA(buf, ""); + } + } + //add an extra null + if (AVAILABLE_BUFFER_LEN(buf) < 1) + return; //no space + buf->buffer[buf->len++] = 0; + keycount = kb.numkeys; + keys = kb.keys; + if (keytype == key_server) + return; //already added the keys + } + for (pindex = 0 ; pindex < playerteamcount ; pindex++) + { + for (i = 0 ; i < keycount ; i++) + { + len = buf->len; + if (keytype == key_server) //add the server keys + qrec->server_key_callback(keys[i], buf, qrec->udata); + else if (keytype == key_player) + qrec->player_key_callback(keys[i], pindex, buf, qrec->udata); + else if (keytype == key_team) + qrec->team_key_callback(keys[i], pindex, buf, qrec->udata); + if(len == buf->len) + qr2_buffer_addA(buf, ""); + } + } +} + +static void qr_build_query_reply(qr2_t qrec, qr2_buffer_t buf, int serverkeycount, uchar *serverkeys, int playerkeycount, uchar *playerkeys, int teamkeycount, uchar *teamkeys) +{ + qr_build_partial_query_reply(qrec, buf, key_server, serverkeycount, serverkeys); + qr_build_partial_query_reply(qrec, buf, key_player, playerkeycount, playerkeys); + qr_build_partial_query_reply(qrec, buf, key_team, teamkeycount, teamkeys); +} + + +struct QRSplitQueryProgress +{ + qr2_key_type mCurKeyType; + int mCurPacketNum; + int mCurKeyIndex; // serverkey index, playerkey index, teamkey index + int mCurSubCount; // number of players or number of teams + int mCurSubIndex; // current player num or current team num + struct qr2_keybuffer_s mKeyBuffer; // keybuffer, for key name indexing +}; + + +// return values: +// gsi_true = send buffer, then call this function again +// gsi_false = don't send buffer, don't call this function again +static gsi_bool qr_build_split_query_reply(qr2_t qrec, qr2_buffer_t buf, struct QRSplitQueryProgress* progress) +{ + unsigned char* packetNumPos = NULL; // Used to store the byte position of the packet number + + // Make sure the key type is valid + // (The key type is set to invalid when all keys have been processed.) + //if (progress->mCurKeyType < 0 ||progress->mCurKeyType >= key_type_count) + if (progress->mCurKeyType >= key_type_count) + return gsi_false; // stop processing + + // check buffer space + // (buffer should only contain header at this point) + if (AVAILABLE_BUFFER_LEN(buf) < 32) + return gsi_false; // no space? + + // Dump the split packet "header" + qr2_buffer_addA(buf, "splitnum"); + packetNumPos = (unsigned char*)&buf->buffer[buf->len++]; + *packetNumPos = (gsi_u8)progress->mCurPacketNum++; + + // Resume dumping at key_type level + while (progress->mCurKeyType < key_type_count) + { + // Get the list of keys if we don't have it already + if (progress->mKeyBuffer.numkeys == 0) + qrec->key_list_callback(progress->mCurKeyType, &progress->mKeyBuffer, qrec->udata); + + // Get the list of players/teams if we don't have it already + if (progress->mCurSubCount == 0 && progress->mCurKeyType != key_server) + progress->mCurSubCount = qrec->playerteam_count_callback(progress->mCurKeyType, qrec->udata); + + // check buffer space + if (AVAILABLE_BUFFER_LEN(buf) < 100) + return gsi_true; //no space + + // Write the key type + buf->buffer[buf->len++] = (char)progress->mCurKeyType; + + // For each key + while(progress->mCurKeyIndex < progress->mKeyBuffer.numkeys) + { + // check buffer space + int aRegisteredKeyIndex = progress->mKeyBuffer.keys[progress->mCurKeyIndex]; + const char* aKeyName = qr2_registered_key_list[aRegisteredKeyIndex]; + + // Write the key name + if (gsi_is_false( qr2_buffer_addA(buf,aKeyName) )) + return gsi_true; // send, then try again + + if (progress->mCurKeyType == key_server) + { + // write the key value + qrec->server_key_callback(aRegisteredKeyIndex, buf, qrec->udata); + + // make sure the key was written + if (AVAILABLE_BUFFER_LEN(buf) < 1) + return gsi_true; //ran out of space! retry this key/value next packet + } + else + { + if (AVAILABLE_BUFFER_LEN(buf) < 1) + return gsi_true; //ran out of space, retry this key/value next packet + + // Non-split packets implicitly being with player/team number zero, + // split packet explicitly specify the starting number + buf->buffer[buf->len++] = (char)progress->mCurSubIndex; + + // For each player/team + while(progress->mCurSubIndex < progress->mCurSubCount) + { + // dump the value into the buffer + if (progress->mCurKeyType == key_player) + qrec->player_key_callback(aRegisteredKeyIndex, progress->mCurSubIndex, buf, qrec->udata); + else if (progress->mCurKeyType == key_team) + qrec->team_key_callback(aRegisteredKeyIndex, progress->mCurSubIndex, buf, qrec->udata); + + // make sure the key was written + if (AVAILABLE_BUFFER_LEN(buf) < 1) + return gsi_true; //ran out of space, try again next packet + + // move onto the next player/team value + progress->mCurSubIndex++; + } + // append a null to signify end of this team/player key + if (AVAILABLE_BUFFER_LEN(buf) > 0) + buf->buffer[buf->len++] = '\0'; + } + // move onto next key + progress->mCurKeyIndex++; + progress->mCurSubIndex = 0; + } + + // append a null to signify end of this key_type section + if (AVAILABLE_BUFFER_LEN(buf) > 0) + buf->buffer[buf->len++] = '\0'; + + // Move onto next key type + progress->mCurKeyType = (qr2_key_type)(progress->mCurKeyType + 1); + progress->mCurKeyIndex = 0; + progress->mCurSubCount = 0; + progress->mCurSubIndex = 0; + progress->mKeyBuffer.numkeys = 0; + } + + // Add the "final" flag to the packet number + *packetNumPos |= QR2_SPLITNUM_FINALFLAG; + return gsi_true; // function will bail without sending next iteration +} + +static void qr_process_query(qr2_t qrec, qr2_buffer_t buf, uchar *qdata, int len, struct sockaddr* sender) +{ + uchar serverkeycount; + uchar playerkeycount; + uchar teamkeycount; + uchar exflags = 0; + + uchar *serverkeys = NULL; + uchar *playerkeys = NULL; + uchar *teamkeys = NULL; + if (len < 3) + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Discarding invalid query (too short#1: %d bytes total)\r\n", len); + return; //invalid + } + serverkeycount = qdata[0]; + qdata++; + len--; + if (serverkeycount != 0 && serverkeycount != 0xFF) + { + serverkeys = qdata; + qdata += serverkeycount; + len -= serverkeycount; + } + if (len < 2) + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Discarding invalid query (too short#2: %d bytes remain)\r\n", len); + return; //invalid + } + playerkeycount = qdata[0]; + qdata++; + len--; + if (playerkeycount != 0 && playerkeycount != 0xFF) + { + playerkeys = qdata; + qdata += playerkeycount; + len -= playerkeycount; + } + if (len < 1) + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Discarding invalid query (too short#3: %d bytes remain)\r\n", len); + return; //invalid + } + teamkeycount = qdata[0]; + qdata++; + len--; + if (teamkeycount != 0 && teamkeycount != 0xFF) + { + teamkeys = qdata; + qdata += teamkeycount; + len -= teamkeycount; + } + if (len < 0) + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Discarding invalid query (too short#4: %d bytes remain)\r\n", len); + return; //invalid + } + + // check the exflags + if (len > 0) + { + exflags = qdata[0]; + len--; + } + + // Support split queries? + if ((exflags & QR2_EXFLAG_SPLIT)==QR2_EXFLAG_SPLIT) + { + struct QRSplitQueryProgress progress; + progress.mCurPacketNum = 0; + progress.mCurKeyType = key_server; + progress.mCurKeyIndex = 0; + progress.mCurSubCount = 0; + progress.mCurSubIndex = 0; + progress.mKeyBuffer.numkeys = 0; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Building query reply (split packet supported)\r\n"); + + // Send packets as long as we need to + while (gsi_true == qr_build_split_query_reply(qrec, buf, &progress)) + { + sendto(qrec->hbsock, buf->buffer, buf->len, 0, sender, sizeof(struct sockaddr_in)); + buf->len = 5; // reset buffer but preserve 5-byte qr2 header + if (progress.mCurPacketNum > QR2_SPLITNUM_MAX) + return; // more than 7 isn't supported (likely a bug if you hit it) + } + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Finished split query reply (%d packets)\r\n", progress.mCurPacketNum); + } + else + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Building query reply (single packet)\r\n"); + qr_build_query_reply(qrec, buf, serverkeycount, serverkeys, playerkeycount, playerkeys, teamkeycount, teamkeys); + sendto(qrec->hbsock, buf->buffer, buf->len, 0, sender, sizeof(struct sockaddr_in)); + } + + GSI_UNUSED(sender); +} + +/* +static void qr_build_partial_old_query_reply(qr2_t qrec, qr2_buffer_t buf, qr2_key_type keytype) +{ + char tempkeyname[128]; + struct qr2_keybuffer_s kb; + int playerteamcount; + int i; + int pindex; + const char *k; + int len; + + kb.numkeys = 0; + + if (keytype == key_player || keytype == key_team) //need to add the player/team counts + { + playerteamcount = qrec->playerteam_count_callback(keytype, qrec->udata); + } else + playerteamcount = 1; + + qrec->key_list_callback(keytype, &kb, qrec->udata); + //add all the keys + for (i = 0 ; i < kb.numkeys ; i++) + { + k = qr2_registered_key_list[kb.keys[i]]; + if (k == NULL) + k = "unknown"; + if (keytype == key_server) //add the server values + { + qr2_buffer_addA(buf, k); + buf->buffer[buf->len - 1] = '\\'; + len = buf->len; + qrec->server_key_callback(kb.keys[i], buf, qrec->udata); + if(len == buf->len) + qr2_buffer_addA(buf, ""); + buf->buffer[buf->len - 1] = '\\'; + } else //need to look it up for each player/team + { + + for (pindex = 0 ; pindex < playerteamcount ; pindex++) + { + sprintf(tempkeyname, "%s%d", k, pindex); + qr2_buffer_addA(buf, tempkeyname); + buf->buffer[buf->len - 1] = '\\'; + len = buf->len; + if (keytype == key_player) + qrec->player_key_callback(kb.keys[i], pindex, buf, qrec->udata); + else if (keytype == key_team) + qrec->team_key_callback(kb.keys[i], pindex, buf, qrec->udata); + if(len == buf->len) + qr2_buffer_addA(buf, ""); + buf->buffer[buf->len - 1] = '\\'; + } + } + } +} +*/ + +//we just build a status reply, since we don't have equivalent callbacks +/*static void qr_process_old_query(qr2_t qrec, qr2_buffer_t buf) +{ + buf->len = 1; + buf->buffer[0] = '\\'; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Comment, + "Processing QR1 style query\r\n"); + + qr_build_partial_old_query_reply(qrec, buf, key_server); + qr_build_partial_old_query_reply(qrec, buf, key_player); + qr_build_partial_old_query_reply(qrec, buf, key_team); + qr2_buffer_addA(buf, "final\\\\queryid\\1.1"); + buf->len--; //remove the final null; +}*/ + +static void qr_process_client_message(qr2_t qrec, char *buf, int len) +{ + unsigned char natNegBytes[NATNEG_MAGIC_LEN] = {NN_MAGIC_0,NN_MAGIC_1,NN_MAGIC_2,NN_MAGIC_3,NN_MAGIC_4,NN_MAGIC_5}; + int i; + int isnatneg = 1; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Notice, + "Processing client message\r\n"); + + //see if it's a natneg request.. + if (len >= NATNEG_MAGIC_LEN + 4) + { + for (i = 0 ; i < NATNEG_MAGIC_LEN ; i++) + if ((unsigned char)buf[i] != natNegBytes[i]) + { + isnatneg = 0; + break; + } + } else + isnatneg = 0; + if (isnatneg) + { + int cookie; + memcpy(&cookie, buf + NATNEG_MAGIC_LEN, 4); + cookie = (int)ntohl((unsigned int)cookie); + + // check if we should do natneg + if(qrec->cc_callback) + { + NegotiateError error; + + if(__GSIACResult != GSIACAvailable) + { + // fake that we passed the availability check + __GSIACResult = GSIACAvailable; + strcpy(__GSIACGamename, qrec->gamename); + } + + // do the negotiation + error = NNBeginNegotiationWithSocket(qrec->hbsock, cookie, 1, NatNegProgressCallback, NatNegCompletedCallback, qrec); + if(error != ne_noerror) + { + // we can ignore errors + } + } + else if (qrec->nn_callback) + { + qrec->nn_callback(cookie, qrec->udata); + } + } else + if (qrec->cm_callback) + { +#ifndef GSI_UNICODE + qrec->cm_callback(buf, len, qrec->udata); +#else + unsigned short* buf_W; + buf_W = UTF8ToUCS2StringAlloc(buf); + qrec->cm_callback(buf_W, wcslen(buf_W), qrec->udata); +#endif + } +} + +static int qr_got_recent_message(qr2_t qrec, int msgkey) +{ + int i; + for (i = 0 ; i < RECENT_CLIENT_MESSAGES_TO_TRACK ; i++) + { + if (qrec->client_message_keys[i] == msgkey) + return 1; + } + //else, add it to the list + qrec->cur_message_key = (qrec->cur_message_key + 1) % RECENT_CLIENT_MESSAGES_TO_TRACK; + qrec->client_message_keys[qrec->cur_message_key] = msgkey; + return 0; +} + +// Send a random value to the user, to verify IP address +static gsi_bool qr2_process_ip_verify(qr2_t qrec, struct qr2_buffer_s* buf, struct sockaddr_in* sender) +{ + int i=0; + gsi_time now = current_time(); + int firstFreeIndex = -1; + int numDuplicates = 0; + + // if the query challenge is disabled, return 0 as the challenge + if ((qrec->backendoptions & QR2_OPTION_USE_QUERY_CHALLENGE) == 0) + { + qr2_buffer_add_int(buf, 0); + return gsi_true; + } + + // check if this ip/port combo is already in the list + for (; i < QR2_IPVERIFY_ARRAY_SIZE; i++) + { + // Mark the first free index when/if found + if (firstFreeIndex == -1 && qrec->ipverify[i].addr.sin_addr.s_addr == 0) + firstFreeIndex = i; + // Count any indexes that match this IP/port + if (qrec->ipverify[i].addr.sin_addr.s_addr == sender->sin_addr.s_addr && + qrec->ipverify[i].addr.sin_port == sender->sin_port) + { + numDuplicates++; + } + } + + // discard if too many duplicates or no index found + if (numDuplicates > QR2_IPVERIFY_MAXDUPLICATES) + return gsi_false; + if (firstFreeIndex == -1) + return gsi_false; // no free indexes + + + // create a random challenge for this ip/port combo + qrec->ipverify[firstFreeIndex].addr = *sender; + qrec->ipverify[firstFreeIndex].challenge = htonl( (rand() << 16) | rand() ); + qrec->ipverify[firstFreeIndex].createtime = now; + + qr2_buffer_add_int(buf, (int)qrec->ipverify[firstFreeIndex].challenge); + return gsi_true; // buffer ready to be sent +} + +// Check if the returned ipverify value matches the random we sent earlier +// If it matches, remove it +static gsi_bool qr2_check_ip_verify(qr2_t qrec, struct sockaddr_in* sender, gsi_u32 ipverify) +{ + int i=0; + for (; i < QR2_IPVERIFY_ARRAY_SIZE; i++) + { + if (qrec->ipverify[i].addr.sin_addr.s_addr == sender->sin_addr.s_addr && + qrec->ipverify[i].addr.sin_port == sender->sin_port) + { + if (qrec->ipverify[i].challenge == ipverify) + { + // reset structure + qrec->ipverify[i].addr.sin_addr.s_addr = 0; + qrec->ipverify[i].addr.sin_port = 0; + return gsi_true; + } + //else + // keep searching...a single IP may have multiple outstanding challenges. + } + } + return gsi_false; +} + +// Expire old verify attempts +static void qr2_expire_ip_verify(qr2_t qrec) +{ + int i=0; + gsi_time now = current_time(); + + for (; i < QR2_IPVERIFY_ARRAY_SIZE; i++) + { + if (qrec->ipverify[i].addr.sin_addr.s_addr != 0 && (now - qrec->ipverify[i].createtime > QR2_IPVERIFY_TIMEOUT)) + qrec->ipverify[i].addr.sin_addr.s_addr = 0; + } +} + +/* parse_query: parse an incoming query and reply to each query */ +void qr2_parse_queryA(qr2_t qrec, char *query, int len, struct sockaddr *sender) +{ + struct qr2_buffer_s buf; + char ptype; + char *reqkey; + char *pos; + gsi_u32 ipverify = 0; + int i; + + buf.len = 0; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_StackTrace, + "qr2_parse_queryA()\r\n"); + + if (qrec == NULL) + qrec = current_rec; + if (query[0] == 0x3B) /* a cdkey query */ + { + if (qrec->cdkeyprocess != NULL) + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice, + "Forwarding cdkey query onto cdkey sdk\r\n"); + qrec->cdkeyprocess(query, len, sender); + } + else + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Received cdkey query but not using qr2-cdkey integration!\r\n"); + } + return; + } + /*if (query[0] == '\\') //it's a QR1-style query + { + qr_process_old_query(qrec, &buf); + sendto(qrec->hbsock, buf.buffer, buf.len, 0, sender, sizeof(struct sockaddr_in)); + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Sent %d bytes as QR1 query response\r\n", buf.len); + gsDebugBinary(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_RawDump, + buf.buffer, buf.len); + + return; + }*/ + if((len >= 6) && (memcmp(query, NNMagicData, NATNEG_MAGIC_LEN) == 0)) /* a natneg query */ + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice, + "Forwarding natneg query onto natneg sdk\r\n"); + NNProcessData(query, len, (struct sockaddr_in*)sender); + return; + } + + if (len < 7) + return; //too small to be valid + //check the magic... + if ((uchar)query[0] != QR_MAGIC_1 || (uchar)query[1] != QR_MAGIC_2) + return; + +//#define SIMULATE_HARD_FIREWALL +#if defined(SIMULATE_HARD_FIREWALL) + // ignore all QR2 packets on this port + // generates "no challenge" error + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Warning, + "SIMULATE_HARD_FIREWALL defined, ignoring QR2 packet\r\n"); + return; +#endif + +//#define SIMULATE_FIREWALL +#if defined(SIMULATE_FIREWALL) + // ignore messages that are not from the gamespy master + { + unsigned int aMasterAddr = (cr->hbaddr.sin_addr); + if (((SOCKADDR_IN*)sender)->sin_addr.s_addr != aMasterAddr) + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Warning, + "SIMULATE_FIREWALL defined, ignoring non-master QR2 packet\r\n"); + return; + } + } +#endif +//#if defined(QR2_IP_FILTER) + if (qrec->denyresp2_ip_callback) + { + unsigned int aMasterAddr = (qrec->hbaddr.sin_addr.s_addr); + unsigned int senderAddr = ((SOCKADDR_IN*)sender)->sin_addr.s_addr; //in hbo + if ((aMasterAddr & 0xffff) != (senderAddr & 0xffff)) //if sender ip is not in gamespy subnet /16 (fake) + { + int deny_result = 0; + qrec->denyresp2_ip_callback(qrec->udata, senderAddr, &deny_result); + if (deny_result) + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Warning, + "QR2_IP_FILTER defined, ignoring QR2 packet from denied ip\r\n"); + return; + } + } + } +//#endif //#if defined(QR2_IP_FILTER) + + if (qrec->listed_state > 0) + qrec->listed_state = 0; + + ptype = query[2]; + reqkey = &query[3]; + pos = query + 7; + len -= 7; + + qr_add_packet_header(&buf, ptype, reqkey); + switch (ptype) + { + case PACKET_PREQUERY_IP_VERIFY: + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice, + "Received IP verify challenge request\r\n"); + if (gsi_is_true(qr2_process_ip_verify(qrec, &buf, (struct sockaddr_in*)sender))) + break; // break so that we send below + else + return; // otherwise return and discard buf + + + case PACKET_QUERY: + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice, + "Processing query packet\r\n"); + + // When using query challenge option, verify that the client sent a PREQUERY_IP_VERIFY + if ((qrec->backendoptions & QR2_OPTION_USE_QUERY_CHALLENGE) == QR2_OPTION_USE_QUERY_CHALLENGE) + { + if (len < 4) + return; // too small for an ip-verify query + + ipverify = ntohl(*(gsi_u32*)pos); + pos += 4; + len -= 4; + + // Has this client verified their IP? (prevent IP spoofing) + if (gsi_is_false(qr2_check_ip_verify(qrec, (struct sockaddr_in*)sender, ipverify))) + { + // Don't send an error. As nice as the debug info would be, + // the incompatible SBs will interpret it as a server response. + return; + } + } + + // qr_process_query now sends packets + qr_process_query(qrec, &buf, (uchar *)pos, len, sender); + return; + case PACKET_CHALLENGE: + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice, + "Processing challenge packet\r\n"); + + // Check the instance key (to prove this packet came from the master + for (i = 0 ; i < REQUEST_KEY_LEN ; i++) + { + if (reqkey[i] != qrec->instance_key[i]) + return; //not a valid instance key + } + + //calculate the challenge + if (len >= (PUBLIC_ADDR_LEN + 3)) + { + unsigned int backendoptions; + + // read options, then public address + sscanf(pos + len - (PUBLIC_ADDR_LEN + 3), "%02x", &backendoptions); + qrec->backendoptions = (gsi_u8)backendoptions; + + #ifdef QR2_DEBUG_FORCE_USE_QUERY_CHALLENGE + qrec->backendoptions = QR2_OPTION_USE_QUERY_CHALLENGE; + #endif + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Notice, + "Received setting options: %d\r\n", qrec->backendoptions); + + if(qrec->pa_callback) + handle_public_address(qrec, pos + len - (PUBLIC_ADDR_LEN + 1)); + else + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice, + "Discarding public address (no callback set)\r\n"); + } + } + compute_challenge_response(qrec, &buf, pos, len); + break; + case PACKET_ECHO: + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice, + "Processing echo packet\r\n"); + + //now add the echo data + if (len > 32) + len = 32; //max 32 bytes + buf.buffer[0] = PACKET_ECHO_RESPONSE; + memcpy(buf.buffer + buf.len, pos, (size_t)len); + buf.len += len; + break; + + case PACKET_ADDERROR: + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Processing adderror packet\r\n"); + + if (qrec->listed_state == -1) + return; //we already got an error message + //verify the instance code + for (i = 0 ; i < REQUEST_KEY_LEN ; i++) + { + if (reqkey[i] != qrec->instance_key[i]) + return; //not a valid instance key + } + if (len < 2) + return; //not a valid message + qrec->listed_state = -1; +#ifndef GSI_UNICODE + qrec->adderror_callback((qr2_error_t)*pos, pos + 1, qrec->udata); +#else + { + unsigned short* message_W; + message_W = UTF8ToUCS2StringAlloc(pos+1); + qrec->adderror_callback((qr2_error_t)*pos, message_W, qrec->udata); + gsifree(message_W); + } +#endif + return; //we don't need to send anything back for this type of message + case PACKET_CLIENT_MESSAGE: + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice, + "Processing clientmessage packet\r\n"); + + //verify the instance code + for (i = 0 ; i < REQUEST_KEY_LEN ; i++) + { + if (reqkey[i] != qrec->instance_key[i]) + return; //not a valid instance key + } + if (len < 4) //no message key? + return; + buf.buffer[0] = PACKET_CLIENT_MESSAGE_ACK; + //add the msg key + memcpy(buf.buffer + buf.len, pos, (size_t)4); + buf.len += 4; + //see if we've recently gotten this same message, to help avoid dupes + memcpy(&i, pos, (size_t)4); + if (!qr_got_recent_message(qrec, i)) + qr_process_client_message(qrec, pos + 4, len - 4); + //send an ack response + break; + case PACKET_KEEPALIVE: + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Processing keepalive packet\r\n"); + return; //if we get a keep alive, ignore it and return (just used to tell us the server knows about us) + default: + return; //not valid type + + } + //send the reply + sendto(qrec->hbsock, buf.buffer, buf.len, 0, sender, sizeof(struct sockaddr_in)); + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Sent %d bytes as QR2 query response\r\n", buf.len); + gsDebugBinary(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_RawDump, + buf.buffer, buf.len); +} + +/* NOT necessary since qr2_parse_query uses char not unsigned short +#if defined(GSI_UNICODE) +void qr2_parse_queryW(qr2_t qrec, unsigned short *query, int len, struct sockaddr *sender) +{ + char query_A[4096]; + UCS2ToUTF8String(query, query_A); + qr2_parse_queryA(qrec, query_A, len, sender); +} +#endif*/ + + +/* send_keepalive: Send a keepalive packet to the hbmaster3 */ +static void send_keepalive(qr2_t qrec) +{ + struct qr2_buffer_s buf; + buf.len = 0; + qr_add_packet_header(&buf, PACKET_KEEPALIVE, qrec->instance_key); + sendto(qrec->hbsock, buf.buffer, buf.len, 0, (struct sockaddr *)&(qrec->hbaddr), sizeof(struct sockaddr_in)); + + //set the ka time to now + qrec->lastka = current_time(); + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Sent keepalive to master\r\n", buf); +} + +/* send_heartbeat: Sends a heartbeat to the gamemaster, +adds \statechanged\ if statechanged != 0 */ +static void send_heartbeat(qr2_t qrec, int statechanged) +{ + struct qr2_buffer_s buf; + //int ret; + int i; + char ipkey[20]; + + buf.len = 0; + qr_add_packet_header(&buf, PACKET_HEARTBEAT, qrec->instance_key); + //now we add our special keys + for (i = 0 ; i < num_local_ips ; i++) + { + sprintf(ipkey, "localip%d", i); + qr2_buffer_addA(&buf, ipkey); + qr2_buffer_addA(&buf, inet_ntoa(local_ip_list[i])); + } + qr2_buffer_addA(&buf, "localport"); + qr2_buffer_add_int(&buf, qrec->qport); + qr2_buffer_addA(&buf, "natneg"); + qr2_buffer_addA(&buf, qrec->nat_negotiate ? "1" : "0"); + if(statechanged) + { + qr2_buffer_addA(&buf, "statechanged"); + qr2_buffer_add_int(&buf, statechanged); + } + qr2_buffer_addA(&buf, "gamename"); + qr2_buffer_addA(&buf, qrec->gamename); + if(qrec->pa_callback) + { + qr2_buffer_addA(&buf, "publicip"); + qr2_buffer_add_int(&buf, (int)qrec->publicip); + qr2_buffer_addA(&buf, "publicport"); + qr2_buffer_add_int(&buf, qrec->publicport); + } + + //add the rest of our keys + if (statechanged != 2) //don't need if we are exiting + { + // The hbmaster will crap out if the packet is malformed + // which might happen if the buffer isn't large enough + // So first copy dump the keys into a temporary buffer + struct qr2_buffer_s temp; + memcpy(temp.buffer, buf.buffer, (size_t)buf.len); + temp.len = buf.len; + qr_build_query_reply(qrec, &temp, 0xFF, NULL, 0xFF, NULL, 0xFF, NULL); + + // If we maxxed out the packet, try again using only the server keys + if(AVAILABLE_BUFFER_LEN(&temp) < 1) + { + temp.len = buf.len; + qr_build_query_reply(qrec, &temp, 0xFF, NULL, 0, NULL, 0, NULL); + } + // copy temp back into buffer + memcpy(buf.buffer, temp.buffer, (size_t)temp.len); + buf.len = temp.len; + } + else + { + // PANTS - 2002.6.28 + // add an extra NUL to end the server keys + if (AVAILABLE_BUFFER_LEN(&buf) >= 1) + buf.buffer[buf.len++] = 0; + } + + //ret = (int)sendto(qrec->hbsock, buf.buffer, buf.len, 0, (struct sockaddr *)&(qrec->hbaddr), sizeof(struct sockaddr_in)); + sendto(qrec->hbsock, buf.buffer, buf.len, 0, (struct sockaddr *)&(qrec->hbaddr), sizeof(struct sockaddr_in)); + + //set the ka time and hb time to now + qrec->lastka = qrec->lastheartbeat = current_time(); + + // clear the pending heartbeat request flag + if (statechanged != 0) + qrec->userstatechangerequested = 0; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Sent heartbeat to master (size %d)\r\n", buf.len); + gsDebugBinary(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_RawDump, + buf.buffer, buf.len); +} + +#ifdef __cplusplus +} +#endif + + diff --git a/code/gamespy/qr2/qr2.h b/code/gamespy/qr2/qr2.h new file mode 100644 index 00000000..47b08812 --- /dev/null +++ b/code/gamespy/qr2/qr2.h @@ -0,0 +1,453 @@ +#ifndef _QR2_H_ +#define _QR2_H_ + +#include "../common/gsCommon.h" + + +/********** +qr2regkeys.h contains defines for all of the reserved keys currently available. +***********/ +#include "qr2regkeys.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef GSI_UNICODE +#define qr2_init qr2_initA +#define qr2_init_socket qr2_init_socketA +#define qr2_parse_query qr2_parse_queryA +#define qr2_buffer_add qr2_buffer_addA +#else +#define qr2_init qr2_initW +#define qr2_init_socket qr2_init_socketW +#define qr2_parse_query qr2_parse_queryW +#define qr2_buffer_add qr2_buffer_addW +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +extern "C" { +#endif + +//no need to escape strings, more flexible querying, less bandwidth, no need to space check buffers + + +/******** +ERROR CONSTANTS +--------------- +These constants are returned from qr2_init and the error callback to signal an error condition +***************/ +typedef enum +{e_qrnoerror, //no error occured +e_qrwsockerror, //a standard socket call failed (exhausted resources?) +e_qrbinderror, //the SDK was unable to find an available port to bind on +e_qrdnserror, //a DNS lookup (for the master server) failed +e_qrconnerror, //the server is behind a nat and does not support negotiation +e_qrnochallengeerror, //no challenge was received from the master - either the master is down, or a firewall is blocking UDP +qr2_error_t_count +} qr2_error_t; + + +/******** +KEY TYPES +--------------- +Server information is reported in key/value pairs. There are three key types: +key_server - General information about the game in progress +key_player - Information about a specific player +key_team - Information about a specific team +***************/ +typedef enum {key_server, key_player, key_team, key_type_count} qr2_key_type; + +/********* +NUM_PORTS_TO_TRY +---------------- +This value is the maximum number of ports that will be scanned to +find an open query port, starting from the value passed to qr2_init +as the base port. Generally there is no reason to modify this value. +***********/ +#define NUM_PORTS_TO_TRY 100 + + +/********* +MAGIC VALUES +---------------- +These values will be used at the start of all QR2 query packets. If you are processing query +data on your game socket, you can use these bytes to determine if a packet should be forwarded +to the QR2 SDK for processing. +***********/ +#define QR_MAGIC_1 0xFE +#define QR_MAGIC_2 0xFD + +/* The app can resolve the master server hostname for this +game itself and store the IP here before calling qr2_init. +For more information, contact devsupport@gamespy.com. */ +extern char qr2_hostname[64]; + +/*********** +qr2_t +---- +This abstract type is used to instantiate multiple instances of the +Query & Reporting SDK (for example, if you are running multiple servers +in the same process). +For most games, you can ignore this value and pass NULL in to all functions +that require it. A single global instance will be used in this case. +************/ +typedef struct qr2_implementation_s *qr2_t; + +/*********** +qr2_keybuffer_t +--------------- +This structure is used to store a list of keys when enumerating available keys. +Use the qr2_keybuffer_add function to add keys to the list. +************/ +typedef struct qr2_keybuffer_s *qr2_keybuffer_t; + + +/*********** +qr2_buffer_t +------------ +This structure stores data that will be sent back to a client in response to +a query. Use the qr2_buffer_add functions to add data to the buffer in your callbacks. +************/ +typedef struct qr2_buffer_s *qr2_buffer_t; + +typedef struct qr2_ipverify_node_s *qr2_ipverify_node_t; + + +/******** +qr2_serverkeycallback_t +------------------- +This is the prototype for one of the callback functions you will need to provide. +The serverkey callback is called when a client requests information about a specific +server key. +[keyid] is the key being requested. +[outbuf] is the destination buffer for the value information. Use qr2_buffer_add to report the value. +[userdata] is the pointer that was passed into qr2_init. You can use this for an + object or structure pointer if needed. +If you don't have a value for the provided keyid, you should add a empty ("") string to the buffer. +********/ +typedef void (*qr2_serverkeycallback_t)(int keyid, qr2_buffer_t outbuf, void *userdata); + +/******** +qr2_playerteamkeycallback_t +------------------- +This is the prototype for two of the callback functions you will need to provide. +The player key callback is called when a client requests information about a specific key +for a specific player. +The team key callback is called when a client requests the value for a team key. +[keyid] is the key being requested. +[index] is the 0-based index of the player or team being requested. +[outbuf] is the destination buffer for the value information. Use qr2_buffer_add to report the value. +[userdata] is the pointer that was passed into qr2_init. You can use this for an + object or structure pointer if needed. +If you don't have a value for the provided keyid, you should add a empty ("") string to the buffer. +********/ +typedef void (*qr2_playerteamkeycallback_t)(int keyid, int index, qr2_buffer_t outbuf, void *userdata); + + +/******** +qr2_keylistcallback_t +------------------- +This is the prototype for one of the callback functions you will need to provide. +The key list callback is called when the SDK needs to determine all of the keys you game has +values for. +[keytype] is the type of keys being requested (server, player, team). You should only add keys + of this type to the keybuffer. +[keybuffer] is the structure that holds the list of keys. Use qr2_keybuffer_add to add a key + to the buffer. +[userdata] is the pointer that was passed into qr2_init. You can use this for an + object or structure pointer if needed. +********/ +typedef void (*qr2_keylistcallback_t)(qr2_key_type keytype, qr2_keybuffer_t keybuffer, void *userdata); + + +/******** +qr2_countcallback_t +------------------- +This is the prototype for one of the callback functions you will need to provide. +The count callback is used by the SDK to get a count of player or teams on the server. +[keytype] should be used to determine whether the player or team count is being requested (key_player or key_team will be passed) +[userdata] is the pointer that was passed into qr2_init. You can use this for an + object or structure pointer if needed. +If your game does not support teams, you can return 0 for the count of teams. +********/ +typedef int (*qr2_countcallback_t)(qr2_key_type keytype, void *userdata); + +/******** +qr2_adderrorcallback_t +------------------- +This is the prototype for one of the callback functions you will need to provide. +The add error callback is called in response to a message from the master server indicating a problem listing the server. +[error] is a code that can be used to determine the specific listing error. +[errmsg] is a human-readable error string returned from the master server. +[userdata] is the pointer that was passed into qr2_init. You can use this for an + object or structure pointer if needed. +The most common error that will be returned is if the master is unable to list the server due to a firewall or proxy +that would block incoming game packets. +********/ +typedef void (*qr2_adderrorcallback_t)(qr2_error_t error, gsi_char *errmsg, void *userdata); + + +//todo - document +typedef void (*qr2_natnegcallback_t)(int cookie, void *userdata); +typedef void (*qr2_clientmessagecallback_t)(gsi_char *data, int len, void *userdata); +typedef void (*qr2_publicaddresscallback_t)(unsigned int ip, unsigned short port, void *userdata); +typedef void (*qr2_clientconnectedcallback_t)(SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *userdata); + +//#if defined(QR2_IP_FILTER) +typedef void (*qr2_denyqr2responsetoipcallback_t)(void *userdata, unsigned int sender_ip, int * result); +//#endif //#if defined(QR2_IP_FILTER) + +void qr2_register_natneg_callback(qr2_t qrec, qr2_natnegcallback_t nncallback); +void qr2_register_clientmessage_callback(qr2_t qrec, qr2_clientmessagecallback_t cmcallback); +void qr2_register_publicaddress_callback(qr2_t qrec, qr2_publicaddresscallback_t pacallback); +void qr2_register_clientconnected_callback(qr2_t qrec, qr2_clientconnectedcallback_t cccallback); + +//#if defined(QR2_IP_FILTER) +void qr2_register_denyresponsetoip_callback(qr2_t qrec, qr2_denyqr2responsetoipcallback_t dertoipcallback); +//#endif //#if defined(QR2_IP_FILTER) + + +/***************** +QR2_REGISTER_KEY +-------------------- +Use this function to register custom server, player, and team keys that your server reports. +[keyid] is the ID number you have chosen for this key. The first NUM_RESERVED_KEYS (50) keys are reserved, all other + keyid values up to MAX_REGISTERED_KEYS (254) are available for your use. +[key] is the string name of the key. Player keys should end in "_" (such as "score_") and team keys should end in "_t". +All custom keys should be registered prior to calling qr2_init. Reserved keys are already registered and should not be +passed to this function. +*******************/ +void qr2_register_key(int keyid, const gsi_char *key); + + +/************ +QR2_INIT +-------- +This function initializes the Query and Reporting SDK and prepares the SDK to accept incoming +queries and send heartbeats to the master server. +[qrec] if not null, will be filled with the qr2_t instance for this server. + If you are not using more than one instance of the Query & Reporting SDK you + can pass in NULL for this value. +[ip] is an optional parameter that determines which dotted IP address to bind to on + a multi-homed machine. You can pass NULL to bind to all IP addresses. + If your game networking supports binding to user-specified IPs, you should make sure the same IP + is bound by the Query and Reporting SDK. +[baseport] is the port to accept queries on. If baseport is not available, the + Query and Reporting SDK will scan for an available port in the range of + baseport -> baseport + NUM_PORTS_TO_TRY + Optionally, you can pass in 0 to have a port chosen automatically + (makes it harder for debugging/testing). +[gamename] is the unique gamename that you were given +[secretkey] is your unique secret key +[ispublic] is 1 if the server should send heartbeats to the GameSpy master server and be publicly listed, + If 0, the server will only be available for LAN browsing +[natnegotiate] is 1 if the server supports GameSpy's NAT-negotiation technology (or another similar technology) + which allows hosting behind a NAT. If you do not support NAT-negotiation (i.e. a 3rd party handshake server), + pass 0 to prevent the server from being listed if it is behind a NAT that cannot be traversed by outside clients. +[qr2_*_callback] are your callback functions, these cannot be NULL +[userdata] is an optional, implementation specific parameter that will be + passed to all callback functions. Use it to store an object or structure + pointer if needed. + +Returns +e_qrnoerror is successful, otherwise one of the qr2_error_t constants above. +************/ +qr2_error_t qr2_init(/*[out]*/qr2_t *qrec, const gsi_char *ip, int baseport, const gsi_char *gamename, const gsi_char *secret_key, + int ispublic, int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, + void *userdata); + +/************ +QR2_INIT_SOCKET +-------- +This version of qr2_init allows the game to specify the UDP socket to use for +sending heartbeats and query replies. This enables the game and the QR2 SDK to +share a single UDP socket for all networking, which can make hosting games +behind a NAT proxy possible (see the documentation for more information). +You must also use qr2_parse_query to pass in any data received for the QR SDK +on the socket, since the SDK will not try to read any data off the socket directly. +[s] is the UDP socket to use for heartbeats and query replies. It must be a valid + socket and should be bound to a port before calling qr_init_socket. It can be + blocking or non-blocking. +[boundport] is the local port that the socket is bound to. +All other parameters are the same as described in qr2_init. +************/ +qr2_error_t qr2_init_socket(/*[out]*/qr2_t *qrec, SOCKET s, int boundport, const gsi_char *gamename, const gsi_char *secret_key, + int ispublic, int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, + void *userdata); + +/******************* +QR2_THINK +------------------- +This function should be called somewhere in your main program loop to +process any pending server queries and send a heartbeat if needed. + +Query replies are very latency sensative, so you should make sure this +function is called at least every 100ms while your game is in progress. +The function has very low overhead and should not cause any performance +problems. +Unless you are using multiple instances of the SDK, you should pass NULL +for qrec. +********************/ +void qr2_think(qr2_t qrec); + +/******************* +QR2_PARSE_QUERY +------------------- +Use only with qr2_init_socket to pass in data that is destined for the Q&R SDK +from your game socket. +You still need to call qr2_think in your main loop, and just call this +function whenever data is received. +Unless you are using multiple instances of the SDK, you should pass NULL +for qrec. +[query] is the packet of query data received from the client. +[len] is the length of the data +[sender] is the address that the query is received from. The QR SDK will reply +directly to that address using the socket provided in qr2_init_socket. +*******************/ +void qr2_parse_query(qr2_t qrec, gsi_char *query, int len, struct sockaddr *sender); + +/***************** +QR2_SEND_STATECHANGED +-------------------- +This function forces a "statechanged" heartbeat to be sent immediately. +Use it any time you have changed the gamestate of your game to signal the +master to update your status. +Unless you are using multiple instances of the SDK, you should pass NULL +for qrec. +*******************/ +void qr2_send_statechanged(qr2_t qrec); + +/***************** +QR2_SHUTDOWN +------------ +This function closes the sockets created in qr_init and takes care of +any misc. cleanup. You should try to call it when before exiting the server process. +An "exiting" statechanged heartbeat will automatically be sent to assist in quickly +de-listing the server. +If you pass in a qrec that was returned from qr_init, all resources associated +with that qrec will be freed. If you passed NULL into qr_int, you can pass +NULL in here as well. +******************/ +void qr2_shutdown(qr2_t qrec); + + + +/***************** +QR2_KEYBUFFER_ADD +------------ +Use this function to add a registered key to the key buffer when asked to provide +a list of supported keys. +******************/ +gsi_bool qr2_keybuffer_add(qr2_keybuffer_t keybuffer, int keyid); + +/***************** +QR2_BUFFER_ADD / ADD_INT +------------ +These functions are used to add a key's value to the outgoing buffer when +requested in a callback function. +******************/ +gsi_bool qr2_buffer_add(qr2_buffer_t outbuf, const gsi_char *value); +gsi_bool qr2_buffer_add_int(qr2_buffer_t outbuf, int value); + + +/* for CDKey SDK integration */ +#define REQUEST_KEY_LEN 4 +#define RECENT_CLIENT_MESSAGES_TO_TRACK 10 +typedef void (*cdkey_process_t)(char *buf, int len, struct sockaddr *fromaddr); + +/* ip verification / spoof prevention */ +#define QR2_IPVERIFY_TIMEOUT 4000 // timeout after 4 seconds round trip time +#define QR2_IPVERIFY_ARRAY_SIZE 200 // allowed outstanding queryies in those 4 seconds +#define QR2_IPVERIFY_MAXDUPLICATES 5 // allow maximum of 5 requests per IP/PORT +struct qr2_ipverify_info_s +{ + struct sockaddr_in addr; // addr = 0 when not in use + gsi_u32 challenge; + gsi_time createtime; +}; + +struct qr2_implementation_s +{ + SOCKET hbsock; + char gamename[64]; + char secret_key[64]; + char instance_key[REQUEST_KEY_LEN]; + qr2_serverkeycallback_t server_key_callback; + qr2_playerteamkeycallback_t player_key_callback; + qr2_playerteamkeycallback_t team_key_callback; + qr2_keylistcallback_t key_list_callback; + qr2_countcallback_t playerteam_count_callback; + qr2_adderrorcallback_t adderror_callback; + qr2_natnegcallback_t nn_callback; + qr2_clientmessagecallback_t cm_callback; + qr2_publicaddresscallback_t pa_callback; + qr2_clientconnectedcallback_t cc_callback; +//#if defined(QR2_IP_FILTER) + qr2_denyqr2responsetoipcallback_t denyresp2_ip_callback; +//#endif //#if defined(QR2_IP_FILTER) + + + gsi_time lastheartbeat; + gsi_time lastka; + int userstatechangerequested; + int listed_state; + int ispublic; + int qport; + int read_socket; + int nat_negotiate; + struct sockaddr_in hbaddr; + cdkey_process_t cdkeyprocess; + int client_message_keys[RECENT_CLIENT_MESSAGES_TO_TRACK]; + int cur_message_key; + unsigned int publicip; + unsigned short publicport; + void *udata; + + gsi_u8 backendoptions; // received from server inside challenge packet + struct qr2_ipverify_info_s ipverify[QR2_IPVERIFY_ARRAY_SIZE]; +}; + +// These need to be defined, even in GSI_UNICODE MODE +void qr2_parse_queryA(qr2_t qrec, char *query, int len, struct sockaddr *sender); +gsi_bool qr2_buffer_addA(qr2_buffer_t outbuf, const char *value); +qr2_error_t qr2_initA(/*[out]*/qr2_t *qrec, const char *ip, int baseport, const char *gamename, const char *secret_key, + int ispublic, int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, + void *userdata); +qr2_error_t qr2_init_socketA(/*[out]*/qr2_t *qrec, SOCKET s, int boundport, const char *gamename, const char *secret_key, + int ispublic, int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, + void *userdata); + + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/code/gamespy/qr2/qr2csample/ReadMe.txt b/code/gamespy/qr2/qr2csample/ReadMe.txt new file mode 100644 index 00000000..44ee06ca --- /dev/null +++ b/code/gamespy/qr2/qr2csample/ReadMe.txt @@ -0,0 +1,29 @@ +**qr2csample** + +Command line inputs: Optional + - ip (x.x.x.x:port) => specify only if you want to override your IP (otherwise qr2 will set for you) + +Summary: This cross-platform test app reports a sample server to the backend. Once reporting, the server info +(including player/team info) can be retrieved using the ServerBrowsing SDK. After 30 seconds, the +mapname is updated and qr2_send_statechanged is called so that the master retrieves it. The sample +shuts down after 60 seconds, removing the server from the master list. To ensure that the server is +reporting correctly, you can check the master server list here (look for hostname "Gamespy QR2 Sample"): +http://net.gamespy.com/masterserver/?gamename=gmtest&fields=%5Chostname%5Chostport%5Cgamever%5Cmapname%5Cgametype%5Cgamemode%5Cnumplayers%5Cmaxplayers%5Cgravity%5Crankingon%5Cnumteams&overridemaster=&filter= + +Dependencies: use master server list site for verification, or you can run concurrently with sbctest to +have all the reported keys queried and displayed (wait 6-10 seconds after starting qr2csample to ensure +it is listed on the Master Server before running sbctest) + +For debug output, add GSI_COMMON_DEBUG to the preprocessor definitions. + +For Unicode, add GSI_UNICODE to the preprocessor definitions (or use the Visual Studio Project's Unicode +configuration). + +Internal functions used: + _tprintf - Unicode compatible version of printf + _tcscpy - Unicode compatible version of strcpy + _tcscmp - Unicode compatible version of strcmp + _T - used on all string literals for Unicode compatibility + msleep - Cross-platform compatible version of sleep + GSI_UNUSED - used to avoid unreferenced variable compiler warnings + diff --git a/code/gamespy/qr2/qr2csample/qr2csample.c b/code/gamespy/qr2/qr2csample/qr2csample.c new file mode 100644 index 00000000..5e8fe306 --- /dev/null +++ b/code/gamespy/qr2/qr2csample/qr2csample.c @@ -0,0 +1,536 @@ +/*********************** +qrcsample.c +GameSpy Query & Reporting SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + + See the ReadMe file for qrcsample info, and consult the GameSpy Query & Reporting 2 + SDK documentation for more information on implementing the qr2 SDK. + +************************/ + +/******** +INCLUDES +********/ +#include "../qr2.h" + +/******** +DEFINES +********/ +// set some of the fixed server keys +#define GAME_VERSION _T("2.00") +#define GAME_NAME _T("gmtest") +#define MAX_PLAYERS 32 +#define MAX_TEAMS 2 +#define BASE_PORT 11111 + +// ensure cross-platform compatibility for printf +#ifdef _WIN32_WCE + void RetailOutputA(CHAR *tszErr, ...); + #define printf RetailOutputA +#elif defined(_NITRO) + #include "../../common/nitro/screen.h" + #define printf Printf + #define vprintf VPrintf +#endif + +// define our additional keys, making sure not to overwrite the reserved standard key ids +// standard keys use 0-NUM_RESERVED_KEYS (defined in qr2regkeys.h) +#define GRAVITY_KEY 100 +#define RANKINGON_KEY 101 +#define TIME__KEY 102 +#define AVGPING_T_KEY 103 + +/******** +TYPDEFS +********/ +//representative of a game player structure +typedef struct +{ + gsi_char pname[80]; + int pfrags; + int pdeaths; + int ptime; + int pping; + int pteam; +} player_t; + +//representative of a team structure +typedef struct +{ + gsi_char tname[80]; + int tscore; + int avgping; + +} team_t; + +//representative of a game data structure +typedef struct +{ + player_t players[MAX_PLAYERS]; + team_t teams[MAX_TEAMS]; + gsi_char mapname[20]; + gsi_char hostname[120]; + gsi_char gamemode[200]; + gsi_char gametype[30]; + int numteams; + int numplayers; + int maxplayers; + int fraglimit; + int timelimit; + int teamplay; + int rankingson; + int gravity; + int hostport; +} gamedata_t; + +/******** +GLOBAL VARS +********/ +// just to give us bogus data +gsi_char *constnames[MAX_PLAYERS]= +{ + _T("Joe Player"), _T("L33t 0n3"), _T("Raptor"), _T("Gr81"), + _T("Flubber"), _T("Sarge"), _T("Void"), _T("runaway"), + _T("Ph3ar"), _T("wh00t"), _T("gr1nder"),_T("Mace"), + _T("stacy"), _T("lamby"), _T("Thrush"), _T("Leeroy") +}; +gamedata_t gamedata; // to store all the server/player/teamkeys + +/******** +DEBUG OUTPUT +********/ +#ifdef GSI_COMMON_DEBUG + static void DebugCallback(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel, const char * theTokenStr, + va_list theParamList) + { + GSI_UNUSED(theLevel); + printf("[%s][%s] ", + gGSIDebugCatStrings[theCat], + gGSIDebugTypeStrings[theType]); + + vprintf(theTokenStr, theParamList); + } + + #ifdef GSI_UNICODE + static void AppDebug(const unsigned short* format, ...) + { + // Construct text, then pass in as ASCII + unsigned short buf[1024]; + char tmp[2056]; + va_list aList; + va_start(aList, format); + vswprintf(buf, 1024, format, aList); + + UCS2ToAsciiString(buf, tmp); + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Misc, GSIDebugLevel_Notice, + "%s", tmp); + } + #else + static void AppDebug(const char* format, ...) + { + va_list aList; + va_start(aList, format); + gsDebugVaList(GSIDebugCat_App, GSIDebugType_Misc, GSIDebugLevel_Notice, + format, aList); + } + #endif +#else + #define AppDebug _tprintf +#endif + +/******** +PROTOTYPES - To prevent warnings on codewarrior strict compile +********/ +void serverkey_callback(int keyid, qr2_buffer_t outbuf, void *userdata); +void playerkey_callback(int keyid, int index, qr2_buffer_t outbuf, void *userdata); +void teamkey_callback(int keyid, int index, qr2_buffer_t outbuf, void *userdata); +void keylist_callback(qr2_key_type keytype, qr2_keybuffer_t keybuffer, void *userdata); +int count_callback(qr2_key_type keytype, void *userdata); +void adderror_callback(qr2_error_t error, gsi_char *errmsg, void *userdata); +void nn_callback(int cookie, void *userdata); +void cm_callback(gsi_char *data, int len, void *userdata); +void cc_callback(SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *userdata); +void DoGameStuff(gsi_time totalTime); +int test_main(int argc, char **argp); + +// called when a server key needs to be reported +void serverkey_callback(int keyid, qr2_buffer_t outbuf, void *userdata) +{ + AppDebug("Reporting server keys\n"); + + switch (keyid) + { + case HOSTNAME_KEY: + qr2_buffer_add(outbuf, gamedata.hostname); + break; + case GAMEVER_KEY: + qr2_buffer_add(outbuf, GAME_VERSION); + break; + case HOSTPORT_KEY: + qr2_buffer_add_int(outbuf, gamedata.hostport); + break; + case MAPNAME_KEY: + qr2_buffer_add(outbuf, gamedata.mapname); + break; + case GAMETYPE_KEY: + qr2_buffer_add(outbuf, gamedata.gametype); + break; + case NUMPLAYERS_KEY: + qr2_buffer_add_int(outbuf, gamedata.numplayers); + break; + case NUMTEAMS_KEY: + qr2_buffer_add_int(outbuf, gamedata.numteams); + break; + case MAXPLAYERS_KEY: + qr2_buffer_add_int(outbuf, gamedata.maxplayers); + break; + case GAMEMODE_KEY: + qr2_buffer_add(outbuf, gamedata.gamemode); + break; + case TEAMPLAY_KEY: + qr2_buffer_add_int(outbuf, gamedata.teamplay); + break; + case FRAGLIMIT_KEY: + qr2_buffer_add_int(outbuf, gamedata.fraglimit); + break; + case TIMELIMIT_KEY: + qr2_buffer_add_int(outbuf, gamedata.timelimit); + break; + case GRAVITY_KEY: + qr2_buffer_add_int(outbuf, gamedata.gravity); + break; + case RANKINGON_KEY: + qr2_buffer_add_int(outbuf, gamedata.rankingson); + break; + default: + qr2_buffer_add(outbuf, _T("")); + } + + GSI_UNUSED(userdata); +} + +// called when a player key needs to be reported +void playerkey_callback(int keyid, int index, qr2_buffer_t outbuf, void *userdata) +{ + AppDebug("Reporting player keys\n"); + + //check for valid index + if (index >= gamedata.numplayers) + { + qr2_buffer_add(outbuf, _T("")); + return; + } + switch (keyid) + { + case PLAYER__KEY: + qr2_buffer_add(outbuf, gamedata.players[index].pname); + break; + case SCORE__KEY: + qr2_buffer_add_int(outbuf, gamedata.players[index].pfrags); + break; + case DEATHS__KEY: + qr2_buffer_add_int(outbuf, gamedata.players[index].pdeaths); + break; + case PING__KEY: + qr2_buffer_add_int(outbuf, gamedata.players[index].pping); + break; + case TEAM__KEY: + qr2_buffer_add_int(outbuf, gamedata.players[index].pteam); + break; + case TIME__KEY: + qr2_buffer_add_int(outbuf, gamedata.players[index].ptime); + break; + default: + qr2_buffer_add(outbuf, _T("")); + break; + } + + GSI_UNUSED(userdata); +} + +// called when a team key needs to be reported +void teamkey_callback(int keyid, int index, qr2_buffer_t outbuf, void *userdata) +{ + AppDebug("Reporting team keys\n"); + + //check for valid index + if (index >= gamedata.numteams) + { + qr2_buffer_add(outbuf, _T("")); + return; + } + switch (keyid) + { + case TEAM_T_KEY: + qr2_buffer_add(outbuf, gamedata.teams[index].tname); + break; + case SCORE_T_KEY: + qr2_buffer_add_int(outbuf, gamedata.teams[index].tscore); + break; + case AVGPING_T_KEY: + qr2_buffer_add_int(outbuf, gamedata.teams[index].avgping); + break; + default: + qr2_buffer_add(outbuf, _T("")); + break; + } + + GSI_UNUSED(userdata); +} + +// called when we need to report the list of keys we report values for +void keylist_callback(qr2_key_type keytype, qr2_keybuffer_t keybuffer, void *userdata) +{ + AppDebug("Reporting keylist\n"); + + //need to add all the keys we support + switch (keytype) + { + case key_server: + qr2_keybuffer_add(keybuffer, HOSTNAME_KEY); + qr2_keybuffer_add(keybuffer, GAMEVER_KEY); + qr2_keybuffer_add(keybuffer, HOSTPORT_KEY); + qr2_keybuffer_add(keybuffer, MAPNAME_KEY); + qr2_keybuffer_add(keybuffer, GAMETYPE_KEY); + qr2_keybuffer_add(keybuffer, NUMPLAYERS_KEY); + qr2_keybuffer_add(keybuffer, NUMTEAMS_KEY); + qr2_keybuffer_add(keybuffer, MAXPLAYERS_KEY); + qr2_keybuffer_add(keybuffer, GAMEMODE_KEY); + qr2_keybuffer_add(keybuffer, TEAMPLAY_KEY); + qr2_keybuffer_add(keybuffer, FRAGLIMIT_KEY); + qr2_keybuffer_add(keybuffer, TIMELIMIT_KEY); + qr2_keybuffer_add(keybuffer, GRAVITY_KEY); //a custom key + qr2_keybuffer_add(keybuffer, RANKINGON_KEY); //a custom key + break; + case key_player: + qr2_keybuffer_add(keybuffer, PLAYER__KEY); + qr2_keybuffer_add(keybuffer, SCORE__KEY); + qr2_keybuffer_add(keybuffer, DEATHS__KEY); + qr2_keybuffer_add(keybuffer, PING__KEY); + qr2_keybuffer_add(keybuffer, TEAM__KEY); + qr2_keybuffer_add(keybuffer, TIME__KEY); //a custom key + break; + case key_team: + qr2_keybuffer_add(keybuffer, TEAM_T_KEY); + qr2_keybuffer_add(keybuffer, SCORE_T_KEY); + qr2_keybuffer_add(keybuffer, AVGPING_T_KEY); //a custom key + break; + default: break; + } + + GSI_UNUSED(userdata); +} + +// called when we need to report the number of players and teams +int count_callback(qr2_key_type keytype, void *userdata) +{ + AppDebug("Reporting number of players/teams\n"); + + if (keytype == key_player) + return gamedata.numplayers; + else if (keytype == key_team) + return gamedata.numteams; + else + return 0; + + GSI_UNUSED(userdata); +} + +// called if our registration with the GameSpy master server failed +void adderror_callback(qr2_error_t error, gsi_char *errmsg, void *userdata) +{ + GS_ASSERT(errmsg) + AppDebug("Error adding server: %d, %s\n", error, errmsg); + GSI_UNUSED(userdata); +} + +// called when a client wants to connect using nat negotiation +// (Nat Negotiation must be enabled in qr2_init) +void nn_callback(int cookie, void *userdata) +{ + AppDebug("Got natneg cookie: %d\n", cookie); + GSI_UNUSED(userdata); +} + +// called when a client sends a message to the server through qr2 (not commonly used) +void cm_callback(gsi_char *data, int len, void *userdata) +{ + AppDebug("Got %d bytes from client\n", len); + GSI_UNUSED(data); + GSI_UNUSED(userdata); +} + +// called when a client has connected +void cc_callback(SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *userdata) +{ + AppDebug("Client connected from %s:%d\n", inet_ntoa(remoteaddr->sin_addr), ntohs(remoteaddr->sin_port)); + GSI_UNUSED(gamesocket); + GSI_UNUSED(userdata); +} + +// initialize the gamedata structure with bogus data +static void init_game(void) +{ + int i; + AppDebug("Generating game data\n"); + srand((unsigned int) current_time() ); + gamedata.numplayers = rand() % 15 + 1; + gamedata.maxplayers = MAX_PLAYERS; + for (i = 0 ; i < gamedata.numplayers ; i++) + { + _tcscpy(gamedata.players[i].pname, constnames[i]); + gamedata.players[i].pfrags = rand() % 32; + gamedata.players[i].pdeaths = rand() % 32; + gamedata.players[i].ptime = rand() % 1000; + gamedata.players[i].pping = rand() % 500; + gamedata.players[i].pteam = rand() % 2; + } + gamedata.numteams = 2; + for (i = 0 ; i < gamedata.numteams ; i++) + { + gamedata.teams[i].tscore = rand() % 500; + gamedata.teams[i].avgping = rand() % 500; + } + + _tcscpy(gamedata.teams[0].tname,_T("Red")); + _tcscpy(gamedata.teams[1].tname,_T("Blue")); + _tcscpy(gamedata.mapname,_T("gmtmap1")); + _tcscpy(gamedata.gametype,_T("arena")); + _tcscpy(gamedata.hostname,_T("GameSpy QR2 Sample")); + _tcscpy(gamedata.gamemode,_T("openplaying")); + + gamedata.fraglimit = 0; + gamedata.timelimit = 40; + gamedata.teamplay = 1; + gamedata.rankingson = 1; + gamedata.gravity = 800; + gamedata.hostport = 25000; +} + +// simulate whatever else a game server does +void DoGameStuff(gsi_time totalTime) +{ + // After 30 seconds, we will change the game map and call qr2_send_statechanged + // This should only be called after major changes such as mapname or gametype, and + // cannot be applied more than once every 10 seconds (subsequent calls will be delayed + // when necessary) + static int stateChanged = 0; + if (!stateChanged && totalTime > 30000) { + AppDebug("Mapname changed, calling qr2_send_statechanged\n"); + _tcscpy(gamedata.mapname,_T("gmtmap2")); + qr2_send_statechanged(NULL); + stateChanged = 1; + } + + msleep(10); +} + + +int test_main(int argc, char **argp) +{ + /* qr2_init parameters */ + gsi_char secret_key[9]; // your title's assigned secret key + gsi_char ip[255]; // to manually set local IP + const int isPublic = 1; // set to '0' for a LAN game + const int isNatNegSupported = 1; // set to '0' if you don't support Nat Negotiation + gsi_time aStartTime = 0; // for sample, so we don't run forever + void * userData = NULL; // optional data that will be passed to the callback functions + + // for debug output on these platforms +#if defined (_PS3) || defined (_PS2) || defined (_PSP) || defined(_NITRO) + #ifdef GSI_COMMON_DEBUG + // Define GSI_COMMON_DEBUG if you want to view the SDK debug output + // Set the SDK debug log file, or set your own handler using gsSetDebugCallback + //gsSetDebugFile(stdout); // output to console + gsSetDebugCallback(DebugCallback); + + // Set debug levels + gsSetDebugLevel(GSIDebugCat_All, GSIDebugType_All, GSIDebugLevel_Verbose); + #endif +#endif + + //set the secret key, in a semi-obfuscated manner + secret_key[0] = 'H'; + secret_key[1] = 'A'; + secret_key[2] = '6'; + secret_key[3] = 'z'; + secret_key[4] = 'k'; + secret_key[5] = 'S'; + secret_key[6] = '\0'; + + // register our custom keys (you do not have to register the reserved standard keys) + AppDebug("Registering custom keys\n"); + qr2_register_key(GRAVITY_KEY, _T("gravity") ); + qr2_register_key(RANKINGON_KEY, _T("rankingon")); + qr2_register_key(TIME__KEY, _T("time_") ); // player keys always end with '_' + qr2_register_key(AVGPING_T_KEY, _T("avgping_t")); // team keys always end with '_t' + + // create some random game data + init_game(); + + // Check if we want to override our IP (otherwise qr2 will set for us) +#ifndef GSI_UNICODE + if (argc>1) + strcpy(ip, argp[1]); +#else + if (argc>1) + AsciiToUCS2String(argp[1], ip); +#endif + + AppDebug("Initializing SDK; server should show up on the master list within 6-10 sec.\n"); + //Call qr_init with the query port number and gamename, default IP address, and no user data + //Pass NULL for the qrec parameter (first parameter) as long as you're running a single game + //server instance per process + //Reference gt2nat sample for qr2_init_socket implementation + if (qr2_init(NULL,argc>1?ip:NULL,BASE_PORT,GAME_NAME, secret_key, isPublic, isNatNegSupported, + serverkey_callback, playerkey_callback, teamkey_callback, + keylist_callback, count_callback, adderror_callback, userData) != e_qrnoerror) + { + printf("Error starting query sockets\n"); + return -1; + } + + // Set a function to be called when we receive a game specific message + qr2_register_clientmessage_callback(NULL, cm_callback); + + // Set a function to be called when we receive a nat negotiation request + qr2_register_natneg_callback(NULL, nn_callback); + + // Set a function to be called when a client has connected + qr2_register_clientconnected_callback(NULL, cc_callback); + + // Enter the main loop + AppDebug("Sample will quit after 60 seconds\n"); + aStartTime = current_time(); + while ((current_time() - aStartTime) < 60000) + { + gsi_time totalTime = current_time() - aStartTime; // used to change the game state after 30 seconds + + // An actual game would do something between "thinks" + DoGameStuff(totalTime); + + //check for / process incoming queries + //should be called every 10-100 ms; quicker calls produce more accurate ping measurements + qr2_think(NULL); + } + + AppDebug("Shutting down - server will be removed from the master server list\n"); + //let gamemaster know we are shutting down (removes dead server entry from the list) + qr2_shutdown(NULL); + + +#ifdef GSI_UNICODE + // In Unicode mode we must perform additional cleanup + qr2_internal_key_list_free(); +#endif + + // Finished + return 0; +} + diff --git a/code/gamespy/qr2/qr2csample/qr2nitrocw/Nitro.lcf b/code/gamespy/qr2/qr2csample/qr2nitrocw/Nitro.lcf new file mode 100644 index 00000000..998f6b00 --- /dev/null +++ b/code/gamespy/qr2/qr2csample/qr2nitrocw/Nitro.lcf @@ -0,0 +1,493 @@ +#--------------------------------------------------------------------------- +# Project: NitroSDK - tools - makelcf +# File: ARM9-TS.lcf.template +# +# Copyright 2003-2006 Nintendo. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# proprietary information of Nintendo of America Inc. and/or Nintendo +# Company Ltd., and are protected by Federal copyright law. They may +# not be disclosed to third parties or copied or duplicated in any form, +# in whole or in part, without the prior written consent of Nintendo. +# +# $Log: ARM9-TS.lcf.template,v $ +# Revision 1.34 04/06/2006 09:02:36 kitase_hirotake +# support for .itcm.bss and .dtcm.bss +# +# Revision 1.33 03/30/2006 23:59:22 AM yasu +# changed creation year +# +# Revision 1.32 03/29/2006 13:14:22 AM yasu +# support for overlays in CWVER 2.x +# +# Revision 1.31 11/24/2005 01:16:47 yada +# change start address of mainEX arena from 0x2400000 to 0x23e0000 +# +# Revision 1.30 09/02/2005 04:14:22 AM yasu +# Old symbols were redefined so they can be used even under SDK2.2 +# +# Revision 1.29 08/31/2005 09:34:57 AM yasu +# Corrected a problem where code would not function normally when using section names such as section_BSS +# +# Revision 1.28 08/26/2005 11:22:16 AM yasu +# overlay support for ITCM/DTCM +# +# Revision 1.27 06/20/2005 12:29:20 AM yasu +# Changed Surffix to Suffix +# +# Revision 1.26 06/14/2005 09:03:42 yada +# fix around minus value of SDK_STACKSIZE +# +# Revision 1.25 04/13/2005 12:51:00 terui +# Change SDK_AUTOLOAD.DTCM.START 0x027c0000 -> 0x027e0000 +# +# Revision 1.24 03/30/2005 00:02:14 yosizaki +# fix copyright header. +# +# Revision 1.23 03/25/2005 12:54:59 AM yasu +# Include .version section +# +# Revision 1.22 10/03/2004 02:00:56 AM yasu +# Output component file list for compstatic tool +# +# Revision 1.21 09/27/2004 05:28:21 AM yasu +# Support .sinit +# +# Revision 1.20 09/09/2004 11:49:20 AM yasu +# Support compstatic in default +# +# Revision 1.19 09/06/2004 06:40:00 AM yasu +# Add labels for digest +# +# Revision 1.18 08/20/2004 06:19:59 AM yasu +# DTCM moves to 0x027c0000 at default +# +# Revision 1.17 08/02/2004 10:38:53 AM yasu +# Add autoload-done callback address in overlaydefs +# +# Revision 1.16 07/26/2004 02:22:32 AM yasu +# Change DTCM address to 0x023c0000 +# +# Revision 1.15 07/26/2004 00:08:27 AM yasu +# Fix label of exception table +# +# Revision 1.14 07/24/2004 05:42:25 AM yasu +# Set default values for SDK_AUTOGEN_xTCM_START +# +# Revision 1.13 07/23/2004 11:32:14 AM yasu +# Define labels for __exception_table_start__ and _end__ +# +# Revision 1.12 07/12/2004 12:21:08 AM yasu +# Check size of ITCM/DTCM +# +# Revision 1.11 07/10/2004 04:10:26 AM yasu +# Support command 'Library' +# +# Revision 1.10 07/02/2004 08:13:02 AM yasu +# Support OBJECT( ) +# +# Revision 1.9 07/01/2004 12:54:38 yasu +# support ITCM/DTCM/WRAM autoload +# +# Revision 1.8 07/01/2004 10:41:46 yasu +# support autoload +# +# Revision 1.7 06/02/2004 07:35:37 yasu +# Set libsyscall.a in FORCE_ACTIVE +# Put NitroMain at the top of ROM image +# +# Revision 1.6 06/02/2004 04:56:28 yasu +# Change to fit to new ROM map of TS +# +# Revision 1.5 2004/06/01 06:12:00 miya +# add padding at top of ROM image. +# +# Revision 1.4 04/26/2004 12:16:48 yasu +# add KEEP_SECTIONS +# +# Revision 1.3 04/20/2004 07:41:32 yasu +# Set STATICINIT instead of .ctor temporarily +# +# Revision 1.2 04/14/2004 07:16:42 yasu +# add ALIGN(32) for convenience to handle cache line +# +# Revision 1.1 04/06/2004 01:59:54 yasu +# newly added +# +# $NoKeywords: $ +#--------------------------------------------------------------------------- +MEMORY +{ + main (RWX) : ORIGIN = 0x02000000, LENGTH = 0x0 > main.sbin + ITCM (RWX) : ORIGIN = 0x01ff8000, LENGTH = 0x0 >> main.sbin + DTCM (RWX) : ORIGIN = 0x027e0000, LENGTH = 0x0 >> main.sbin + binary.AUTOLOAD_INFO (RWX) : ORIGIN = 0, LENGTH = 0x0 >> main.sbin + binary.STATIC_FOOTER (RWX) : ORIGIN = 0, LENGTH = 0x0 >> main.sbin + + main_defs (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 > main_defs.sbin + main_table (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 > main_table.sbin + dummy.MAIN_EX (RW) : ORIGIN = 0x023e0000, LENGTH = 0x0 + arena.MAIN (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 + arena.MAIN_EX (RW) : ORIGIN = AFTER(dummy.MAIN_EX), LENGTH = 0x0 + arena.ITCM (RW) : ORIGIN = AFTER(ITCM), LENGTH = 0x0 + arena.DTCM (RW) : ORIGIN = AFTER(DTCM), LENGTH = 0x0 + binary.MODULE_FILES (RW) : ORIGIN = 0x0, LENGTH = 0x0 > component.files + check.ITCM (RWX) : ORIGIN = 0x0, LENGTH = 0x08000 > itcm.check + check.DTCM (RW) : ORIGIN = 0x0, LENGTH = 0x04000 > dtcm.check +} + +FORCE_ACTIVE +{ + SVC_SoftReset +} + +KEEP_SECTION +{ + .sinit +} + +SECTIONS +{ + ############################ STATIC ################################# + .main: + { + ALIGNALL(4); . = ALIGN(32); # Fit to cache line + + # + # TEXT BLOCK: READ ONLY + # + SDK_STATIC_START =.; + SDK_STATIC_TEXT_START =.; + #:::::::::: text/rodata + libsyscall.a (.text) + crt0.o (.text) + crt0.o (.rodata) + * (.version) + OBJECT(NitroMain,*) + GROUP(ROOT) (.text) + . = ALIGN(4); + * (.exception) + . = ALIGN(4); + SDK_STATIC_ETABLE_START =.; + EXCEPTION + SDK_STATIC_ETABLE_END =.; + . = ALIGN(4); + GROUP(ROOT) (.init) + . = ALIGN(4); + GROUP(ROOT) (.rodata) + . = ALIGN(4); + + SDK_STATIC_SINIT_START =.; + #:::::::::: ctor + GROUP(ROOT) (.ctor) + GROUP(ROOT) (.sinit) + WRITEW 0; + #:::::::::: ctor + SDK_STATIC_SINIT_END =.; + + #:::::::::: text/rodata + . = ALIGN(32); + SDK_STATIC_TEXT_END =.; + + # + # DATA BLOCK: READ WRITE + # + SDK_STATIC_DATA_START =.; + #:::::::::: data + GROUP(ROOT) (.sdata) + . = ALIGN(4); + GROUP(ROOT) (.data) + . = ALIGN(4); + SDK_OVERLAY_DIGEST =.; + # NO DIGEST + SDK_OVERLAY_DIGEST_END =.; + #:::::::::: data + . = ALIGN(32); + SDK_STATIC_DATA_END =.; + SDK_STATIC_END =.; + + SDK_STATIC_TEXT_SIZE = SDK_STATIC_TEXT_END - SDK_STATIC_TEXT_START; + SDK_STATIC_DATA_SIZE = SDK_STATIC_DATA_END - SDK_STATIC_DATA_START; + SDK_STATIC_SIZE = SDK_STATIC_END - SDK_STATIC_START; + __sinit__ = SDK_STATIC_SINIT_START; # for static initializer + __exception_table_start__ = SDK_STATIC_ETABLE_START; # for exception table + __exception_table_end__ = SDK_STATIC_ETABLE_END; # for exception table + } > main + + .main.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_STATIC_BSS_START =.; + #:::::::::: bss + GROUP(ROOT) (.sbss) + . = ALIGN(4); + GROUP(ROOT) (.bss) + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_STATIC_BSS_END = .; + SDK_STATIC_BSS_SIZE = SDK_STATIC_BSS_END - SDK_STATIC_BSS_START; + + } >> main + + + ############################ AUTOLOADS ############################## + SDK_AUTOLOAD.ITCM.START = 0x01ff8000; + SDK_AUTOLOAD.ITCM.END = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD.ITCM.BSS_END = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD.ITCM.SIZE = 0; + SDK_AUTOLOAD.ITCM.BSS_SIZE = 0; + SDK_AUTOLOAD.DTCM.START = 0x027e0000; + SDK_AUTOLOAD.DTCM.END = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD.DTCM.BSS_END = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD.DTCM.SIZE = 0; + SDK_AUTOLOAD.DTCM.BSS_SIZE = 0; + SDK_AUTOLOAD_START = SDK_STATIC_END; + SDK_AUTOLOAD_SIZE = 0; + SDK_AUTOLOAD_NUMBER = 2; + + .ITCM: + { + ALIGNALL(4); . = ALIGN(32); + + # + # TEXT BLOCK: READ ONLY + # + SDK_AUTOLOAD_ITCM_ID =0; + SDK_AUTOLOAD.ITCM.ID =0; + SDK_AUTOLOAD.ITCM.START =.; + SDK_AUTOLOAD.ITCM.TEXT_START =.; + #:::::::::: text/rodata + . = ALIGN(4); + * (.itcm) + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: text/rodata + SDK_AUTOLOAD.ITCM.TEXT_END =.; + + # + # DATA BLOCK: READ WRITE BLOCK + # + SDK_AUTOLOAD.ITCM.DATA_START =.; + #:::::::::: data + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: data + . = ALIGN(32); + SDK_AUTOLOAD.ITCM.DATA_END =.; + SDK_AUTOLOAD.ITCM.END =.; + + SDK_AUTOLOAD.ITCM.TEXT_SIZE = SDK_AUTOLOAD.ITCM.TEXT_END - SDK_AUTOLOAD.ITCM.TEXT_START; + SDK_AUTOLOAD.ITCM.DATA_SIZE = SDK_AUTOLOAD.ITCM.DATA_END - SDK_AUTOLOAD.ITCM.DATA_START; + SDK_AUTOLOAD.ITCM.SIZE = SDK_AUTOLOAD.ITCM.END - SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SDK_AUTOLOAD.ITCM.SIZE; + + } > ITCM + + .ITCM.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_AUTOLOAD.ITCM.BSS_START = .; + #:::::::::: bss + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + * (.itcm.bss) + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_AUTOLOAD.ITCM.BSS_END = .; + + SDK_AUTOLOAD.ITCM.BSS_SIZE = SDK_AUTOLOAD.ITCM.BSS_END - SDK_AUTOLOAD.ITCM.BSS_START; + + } >> ITCM + + .DTCM: + { + ALIGNALL(4); . = ALIGN(32); + + # + # TEXT BLOCK: READ ONLY + # + SDK_AUTOLOAD_DTCM_ID =1; + SDK_AUTOLOAD.DTCM.ID =1; + SDK_AUTOLOAD.DTCM.START =.; + SDK_AUTOLOAD.DTCM.TEXT_START =.; + #:::::::::: text/rodata + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: text/rodata + SDK_AUTOLOAD.DTCM.TEXT_END =.; + + # + # DATA BLOCK: READ WRITE BLOCK + # + SDK_AUTOLOAD.DTCM.DATA_START =.; + #:::::::::: data + . = ALIGN(4); + . = ALIGN(4); + * (.dtcm) + . = ALIGN(4); + #:::::::::: data + . = ALIGN(32); + SDK_AUTOLOAD.DTCM.DATA_END =.; + SDK_AUTOLOAD.DTCM.END =.; + + SDK_AUTOLOAD.DTCM.TEXT_SIZE = SDK_AUTOLOAD.DTCM.TEXT_END - SDK_AUTOLOAD.DTCM.TEXT_START; + SDK_AUTOLOAD.DTCM.DATA_SIZE = SDK_AUTOLOAD.DTCM.DATA_END - SDK_AUTOLOAD.DTCM.DATA_START; + SDK_AUTOLOAD.DTCM.SIZE = SDK_AUTOLOAD.DTCM.END - SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SDK_AUTOLOAD.DTCM.SIZE; + + } > DTCM + + .DTCM.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_AUTOLOAD.DTCM.BSS_START = .; + #:::::::::: bss + . = ALIGN(4); + . = ALIGN(4); + * (.dtcm.bss) + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_AUTOLOAD.DTCM.BSS_END = .; + + SDK_AUTOLOAD.DTCM.BSS_SIZE = SDK_AUTOLOAD.DTCM.BSS_END - SDK_AUTOLOAD.DTCM.BSS_START; + + } >> DTCM + + + SDK_AUTOLOAD_ITCM_START = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD_ITCM_END = SDK_AUTOLOAD.ITCM.END; + SDK_AUTOLOAD_ITCM_BSS_END = SDK_AUTOLOAD.ITCM.BSS_END; + SDK_AUTOLOAD_ITCM_SIZE = SDK_AUTOLOAD.ITCM.SIZE; + SDK_AUTOLOAD_ITCM_BSS_SIZE = SDK_AUTOLOAD.ITCM.BSS_SIZE; + SDK_AUTOLOAD_DTCM_START = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD_DTCM_END = SDK_AUTOLOAD.DTCM.END; + SDK_AUTOLOAD_DTCM_BSS_END = SDK_AUTOLOAD.DTCM.BSS_END; + SDK_AUTOLOAD_DTCM_SIZE = SDK_AUTOLOAD.DTCM.SIZE; + SDK_AUTOLOAD_DTCM_BSS_SIZE = SDK_AUTOLOAD.DTCM.BSS_SIZE; + + ############################ AUTOLOAD_INFO ########################## + .binary.AUTOLOAD_INFO: + { + WRITEW ADDR(.ITCM); + WRITEW SDK_AUTOLOAD.ITCM.SIZE; + WRITEW SDK_AUTOLOAD.ITCM.BSS_SIZE; + WRITEW ADDR(.DTCM); + WRITEW SDK_AUTOLOAD.DTCM.SIZE; + WRITEW SDK_AUTOLOAD.DTCM.BSS_SIZE; + } > binary.AUTOLOAD_INFO + + SDK_AUTOLOAD_LIST = SDK_AUTOLOAD_START + SDK_AUTOLOAD_SIZE; + SDK_AUTOLOAD_LIST_END = SDK_AUTOLOAD_START + SDK_AUTOLOAD_SIZE + SIZEOF(.binary.AUTOLOAD_INFO); + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SIZEOF(.binary.AUTOLOAD_INFO); + + ############################ STATIC_FOOTER ########################## + .binary.STATIC_FOOTER: + { + WRITEW 0xdec00621; # LE(0x2106C0DE) = NITRO CODE + WRITEW _start_ModuleParams - ADDR(.main); + WRITEW 0; # NO DIGEST + } > binary.STATIC_FOOTER + + ############################ OVERLAYS ############################### + SDK_OVERLAY_NUMBER = 0; + + + ############################ MAIN EX ################################## + # MAIN EX Area + .dummy.MAIN_EX: + { + . = ALIGN(32); + } > dummy.MAIN_EX + + ############################ ARENA ################################## + .arena.MAIN: + { + . = ALIGN(32); + SDK_SECTION_ARENA_START =.; + } > arena.MAIN + + .arena.MAIN_EX: + { + . = ALIGN(32); + SDK_SECTION_ARENA_EX_START =.; + } > arena.MAIN_EX + + .arena.ITCM: + { + . = ALIGN(32); + SDK_SECTION_ARENA_ITCM_START =.; + } > arena.ITCM + + .arena.DTCM: + { + . = ALIGN(32); + SDK_SECTION_ARENA_DTCM_START =.; + } > arena.DTCM + + ############################ OVERLAYDEFS ############################ + .main_defs: + { + ### main module information + WRITEW ADDR(.main); # load address + WRITEW _start; # entry address + WRITEW SDK_STATIC_SIZE + SDK_AUTOLOAD_SIZE; # size of module + WRITEW _start_AutoloadDoneCallback; # callback autoload done + + ### overlay filename + + } > main_defs + + + ############################ OVERLAYTABLE ########################### + .main_table: + { + + } > main_table + + + ############################ OTHERS ################################# + SDK_MAIN_ARENA_LO = SDK_SECTION_ARENA_START; + SDK_IRQ_STACKSIZE = 4096; # allocated in DTCM + SDK_SYS_STACKSIZE = 0; # when 0 means all remains of DTCM + + # Module filelist + .binary.MODULE_FILES: + { + WRITES ("main.sbin"); + WRITES ("main_defs.sbin"); + WRITES ("main_table.sbin"); + } > binary.MODULE_FILES + + # ITCM/DTCM size checker => check AUTOLOAD_ITCM/DTCM + .check.ITCM: + { + . = . + SDK_AUTOLOAD_ITCM_SIZE + SDK_AUTOLOAD_ITCM_BSS_SIZE; + } > check.ITCM + + SDK_SYS_STACKSIZE_SIGN = (SDK_SYS_STACKSIZE < 0x80000000) * 2 - 1; + .check.DTCM: + { + . = . + SDK_AUTOLOAD_DTCM_SIZE + SDK_AUTOLOAD_DTCM_BSS_SIZE; + . = . + SDK_IRQ_STACKSIZE + SDK_SYS_STACKSIZE * SDK_SYS_STACKSIZE_SIGN; + } > check.DTCM + +} diff --git a/code/gamespy/qr2/qr2csample/qr2nitrocw/ROM-TS.rsf b/code/gamespy/qr2/qr2csample/qr2nitrocw/ROM-TS.rsf new file mode 100644 index 00000000..cec9e1db --- /dev/null +++ b/code/gamespy/qr2/qr2csample/qr2nitrocw/ROM-TS.rsf @@ -0,0 +1,116 @@ +#---------------------------------------------------------------------------- +# Project: NitroSDK - include +# File: ROM-TS.lsf +# +# Copyright 2003-2005 Nintendo. All rights reserved. +# +# These coded insructions, statements, and computer programs contain +# proprietary information of Nintendo of America Inc. and/or Nintendo +# Company Ltd., and are protected by Federal copyright law. They may +# not be disclosed to third parties or copied or duplicated in any form, +# in whole or in part, without the prior written consent of Nintendo. +# +# $Log: ROM-TS.rsf,v $ +# Revision 1.6 2005/04/05 23:52:58 yosizaki +# fix copyright date. +# +# Revision 1.5 2005/04/05 12:16:10 yosizaki +# support RomSpeedType parameter. +# +# Revision 1.4 2004/09/21 02:18:49 yasu +# Add default banner +# +# Revision 1.3 2004/09/09 11:39:09 yasu +# Unified ROM-TS and ROM-TS-C, also ROM-TEG and ROM-TEG-C +# +# Revision 1.2 2004/05/26 12:03:38 yasu +# add :r option to get basename for supporting IDE with makerom +# +# Revision 1.1 2004/04/06 01:59:59 yasu +# newly added +# +# $NoKeywords: $ +#---------------------------------------------------------------------------- +# +# Nitro ROM SPEC FILE +# + +Arm9 +{ + Static "$(MAKEROM_ARM9:r).sbin$(COMPSUFFIX9)" + OverlayDefs "$(MAKEROM_ARM9:r)_defs.sbin$(COMPSUFFIX9)" + OverlayTable "$(MAKEROM_ARM9:r)_table.sbin$(COMPSUFFIX9)" + Elf "$(MAKEROM_ARM9:r).nef" +} + +Arm7 +{ + Static "$(MAKEROM_ARM7:r).sbin$(COMPSUFFIX7)" + OverlayDefs "$(MAKEROM_ARM7:r)_defs.sbin$(COMPSUFFIX7)" + OverlayTable "$(MAKEROM_ARM7:r)_table.sbin$(COMPSUFFIX7)" + Elf "$(MAKEROM_ARM7:r).nef" +} + +Property +{ + ### + ### Settings for FinalROM + ### + #### BEGIN + # + # TITLE NAME: Your product name within 12bytes + # + #TitleName "YourAppName" + + # + # MAKER CODE: Your company ID# in 2 ascii words + # issued by NINTENDO + # + #MakerCode "00" + + # + # REMASTER VERSION: Mastering version + # + #RomVersion 0 + + # + # ROM SPEED TYPE: [MROM/1TROM/UNDEFINED] + # + RomSpeedType $(MAKEROM_ROMSPEED) + + # + # ROM SIZE: in bit [64M/128M/256M/512M/1G/2G] + # + #RomSize 128M + #RomSize 256M + + # + # ROM PADDING: TRUE if finalrom + # + #RomFootPadding TRUE + + # + # ROM HEADER TEMPLATE: Provided to every product by NINTENDO + # + #RomHeaderTemplate ./etc/rom_header.template.sbin + + # + # BANNER FILE: generated from Banner Spec File + # + #BannerFile ./etc/myGameBanner.bnr + BannerFile $(NITROSDK_ROOT)/include/nitro/specfiles/default.bnr + + ### + ### + ### + #### END +} + +RomSpec +{ + Offset 0x00000000 + Segment ALL + HostRoot $(MAKEROM_ROMROOT) + Root / + File $(MAKEROM_ROMFILES) +} diff --git a/code/gamespy/qr2/qr2csample/qr2ps2prodg/qr2ps2prodg.dsp b/code/gamespy/qr2/qr2csample/qr2ps2prodg/qr2ps2prodg.dsp new file mode 100644 index 00000000..21c28a0b --- /dev/null +++ b/code/gamespy/qr2/qr2csample/qr2ps2prodg/qr2ps2prodg.dsp @@ -0,0 +1,327 @@ +# Microsoft Developer Studio Project File - Name="qr2ps2prodg" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=qr2ps2prodg - Win32 Release_Insock +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "qr2ps2prodg.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "qr2ps2prodg.mak" CFG="qr2ps2prodg - Win32 Release_Insock" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "qr2ps2prodg - Win32 Debug_EENet" (based on "Win32 (x86) Console Application") +!MESSAGE "qr2ps2prodg - Win32 Debug_Insock" (based on "Win32 (x86) Console Application") +!MESSAGE "qr2ps2prodg - Win32 Debug_SNSystems" (based on "Win32 (x86) Console Application") +!MESSAGE "qr2ps2prodg - Win32 Release_EENet" (based on "Win32 (x86) Console Application") +!MESSAGE "qr2ps2prodg - Win32 Release_Insock" (based on "Win32 (x86) Console Application") +!MESSAGE "qr2ps2prodg - Win32 Release_SNSystems" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/Gamespy/GOA/qr2/qr2csample/qr2ps2prodg", ZTEDAAAA" +# PROP Scc_LocalPath "." +CPP=snCl.exe +RSC=rc.exe + +!IF "$(CFG)" == "qr2ps2prodg - Win32 Debug_EENet" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug_EENet" +# PROP BASE Intermediate_Dir "Debug_EENet" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_EENet" +# PROP Intermediate_Dir "Debug_EENet" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Debug/" /FD /debug /c +# ADD CPP /nologo /W4 /WX /Od /I "c:\usr\local\sce\ee\include\libeenet" /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "EENET" /D "SN_TARGET_PS2" /D "_DEBUG" /FD /debug -fshort-wchar /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"PS2_EE_Debug\qr2ps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 eenetctl.a ent_smap.a ent_eth.a ent_ppp.a libeenet.a libscf.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /nodefaultlib /out:"Debug_EENet\qr2ps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "qr2ps2prodg - Win32 Debug_Insock" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "qr2ps2prodg___Win32_Debug_Insock" +# PROP BASE Intermediate_Dir "qr2ps2prodg___Win32_Debug_Insock" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_Insock" +# PROP Intermediate_Dir "Debug_Insock" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "c:\usr\local\sce\ee\include\libeenet" /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "EENET" /D "SN_TARGET_PS2" /D "_DEBUG" /FD /debug /c +# ADD CPP /nologo /W4 /WX /Od /I "c:\usr\local\sce\ee\include\libeenet" /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /D "_DEBUG" /D "INSOCK" /FD /debug -fshort-wchar /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 eenetctl.a ent_smap.a ent_eth.a ent_ppp.a libeenet.a libscf.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /nodefaultlib /out:"Debug_EENet\qr2ps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 libinsck.a libnet.a libmrpc.a libkernl.a libcdvd.a libnetif.a netcnfif.a /nologo /pdb:none /debug /machine:IX86 /nodefaultlib /out:"Debug_Insock\qr2ps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "qr2ps2prodg - Win32 Debug_SNSystems" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug_SNSystems" +# PROP BASE Intermediate_Dir "Debug_SNSystems" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_SNSystems" +# PROP Intermediate_Dir "Debug_SNSystems" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Debug/" /FD /debug /c +# ADD CPP /nologo /W4 /WX /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "_DEBUG" /D "SN_SYSTEMS" /D "SN_TARGET_PS2" /FD /debug -fshort-wchar /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"PS2_EE_Debug\qr2ps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 sneetcp.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"Debug_SNSystems\qr2ps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "qr2ps2prodg - Win32 Release_EENet" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Release_EENet" +# PROP BASE Intermediate_Dir "Release_EENet" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Release_EENet" +# PROP Intermediate_Dir "Release_EENet" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /O2 /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Release/" /FD /c +# ADD CPP /nologo /W4 /WX /O2 /I "c:\usr\local\sce\ee\include\libeenet" /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "EENET" /D "SN_TARGET_PS2" /FD /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"PS2_EE_Release\qr2ps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 eenetctl.a ent_smap.a ent_eth.a ent_ppp.a libeenet.a libscf.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"Release_EENet\qr2ps2prodg.elf" /D:SN_TARGET_PS2 +# SUBTRACT LINK32 /nodefaultlib + +!ELSEIF "$(CFG)" == "qr2ps2prodg - Win32 Release_Insock" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "qr2ps2prodg___Win32_Release_Insock" +# PROP BASE Intermediate_Dir "qr2ps2prodg___Win32_Release_Insock" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Release_Insock" +# PROP Intermediate_Dir "Release_Insock" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W4 /WX /O2 /I "c:\usr\local\sce\ee\include\libeenet" /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "EENET" /D "SN_TARGET_PS2" /FD /c +# ADD CPP /nologo /W4 /WX /O2 /I "c:\usr\local\sce\ee\include\libeenet" /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /D "INSOCK" /FD /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 eenetctl.a ent_smap.a ent_eth.a ent_ppp.a libeenet.a libscf.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"Release_EENet\qr2ps2prodg.elf" /D:SN_TARGET_PS2 +# SUBTRACT BASE LINK32 /nodefaultlib +# ADD LINK32 libinsck.a libnet.a libmrpc.a libkernl.a libcdvd.a libnetif.a netcnfif.a /nologo /pdb:none /machine:IX86 /out:"Release_Insock\qr2ps2prodg.elf" /D:SN_TARGET_PS2 +# SUBTRACT LINK32 /nodefaultlib + +!ELSEIF "$(CFG)" == "qr2ps2prodg - Win32 Release_SNSystems" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Release_SNSystems" +# PROP BASE Intermediate_Dir "Release_SNSystems" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Release_SNSystems" +# PROP Intermediate_Dir "Release_SNSystems" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /O2 /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Release/" /FD /c +# ADD CPP /nologo /W4 /WX /O2 /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_SYSTEMS" /D "SN_TARGET_PS2" /FD /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"PS2_EE_Release\qr2ps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 sneetcp.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"Release_SNSystems\qr2ps2prodg.elf" /D:SN_TARGET_PS2 + +!ENDIF + +# Begin Target + +# Name "qr2ps2prodg - Win32 Debug_EENet" +# Name "qr2ps2prodg - Win32 Debug_Insock" +# Name "qr2ps2prodg - Win32 Debug_SNSystems" +# Name "qr2ps2prodg - Win32 Release_EENet" +# Name "qr2ps2prodg - Win32 Release_Insock" +# Name "qr2ps2prodg - Win32 Release_SNSystems" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\qr2csample.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\..\common\ps2\prodg\PS2_in_VC.h +# End Source File +# End Group +# Begin Group "QueryReporting2SDK" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\qr2.c +# End Source File +# Begin Source File + +SOURCE=..\..\qr2.h +# End Source File +# Begin Source File + +SOURCE=..\..\qr2regkeys.c +# End Source File +# Begin Source File + +SOURCE=..\..\qr2regkeys.h +# End Source File +# End Group +# Begin Group "GsCommon" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\common\gsAssert.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAssert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAvailable.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAvailable.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsCommon.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsDebug.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsDebug.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsMemory.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsMemory.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatform.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatform.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformSocket.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformSocket.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformThread.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformThread.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformUtil.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformUtil.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsStringUtil.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsStringUtil.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\ps2\ps2common.c +# End Source File +# End Group +# Begin Source File + +SOURCE=c:\usr\local\sce\ee\lib\app.cmd +# End Source File +# Begin Source File + +SOURCE=c:\usr\local\sce\ee\lib\crt0.s +# End Source File +# Begin Source File + +SOURCE=..\..\..\ps2common\prodg\ps2.lk +# End Source File +# End Target +# End Project diff --git a/code/gamespy/qr2/qr2csample/qr2ps2prodg/qr2ps2prodg.dsw b/code/gamespy/qr2/qr2csample/qr2ps2prodg/qr2ps2prodg.dsw new file mode 100644 index 00000000..b838df48 --- /dev/null +++ b/code/gamespy/qr2/qr2csample/qr2ps2prodg/qr2ps2prodg.dsw @@ -0,0 +1,37 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "qr2ps2prodg"=.\qr2ps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/qr2/qr2csample/qr2ps2prodg", ZTEDAAAA + . + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/qr2/qr2csample/qr2ps2prodg", ZTEDAAAA + . + end source code control +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/code/gamespy/qr2/qr2regkeys.c b/code/gamespy/qr2/qr2regkeys.c new file mode 100644 index 00000000..ff4cf290 --- /dev/null +++ b/code/gamespy/qr2/qr2regkeys.c @@ -0,0 +1,157 @@ + +#include "qr2regkeys.h" + +#include "../common/gsStringUtil.h" +#include "../common/gsDebug.h" + +#ifdef __MWERKS__ // CodeWarrior requires prototypes +void qr2_register_keyW(int keyid, const unsigned short *key); +void qr2_register_keyA(int keyid, const char *key); +#endif + +const char *qr2_registered_key_list[MAX_REGISTERED_KEYS] = +{ + "", //0 is reserved + "hostname", //1 + "gamename", //2 + "gamever", //3 + "hostport", //4 + "mapname", //5 + "gametype", //6 + "gamevariant", //7 + "numplayers", //8 + "numteams", //9 + "maxplayers", //10 + "gamemode", //11 + "teamplay", //12 + "fraglimit", //13 + "teamfraglimit",//14 + "timeelapsed", //15 + "timelimit", //16 + "roundtime", //17 + "roundelapsed", //18 + "password", //19 + "groupid", //20 + "player_", //21 + "score_", //22 + "skill_", //23 + "ping_", //24 + "team_", //25 + "deaths_", //26 + "pid_", //27 + "team_t", //28 + "score_t", //29 + "nn_groupid", //30 + + // Query From Master Only keys + "country", //31 + "region" //32 +}; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Keep a list of the unicode keys we've allocated internally so that we can free +// them when qr2 is shutdown +typedef struct QR2KeyListNodeS +{ + char* mKeyData; + struct QR2KeyListNodeS* mNextKey; +} QR2KeyListNode; + +typedef struct QR2KeyListS +{ + struct QR2KeyListNodeS* mHead; +} QR2KeyList; + +static QR2KeyList qr2_internal_key_list = { NULL }; + +void qr2_internal_key_list_append(char* theKey) +{ + QR2KeyListNode* aNewNode; + + assert(theKey != NULL); + + // Init the new node + aNewNode = (QR2KeyListNode*)gsimalloc(sizeof(QR2KeyListNode)); + aNewNode->mKeyData = theKey; + aNewNode->mNextKey = NULL; + + // Check for a NULL head + if (qr2_internal_key_list.mHead == NULL) + qr2_internal_key_list.mHead = aNewNode; + else + { + // Find the end of the list and append this node + QR2KeyListNode* aInsertPlace = qr2_internal_key_list.mHead; + while(aInsertPlace->mNextKey != NULL) + aInsertPlace = aInsertPlace->mNextKey; + + aInsertPlace->mNextKey = aNewNode; + } +} + +void qr2_internal_key_list_free() +{ + QR2KeyListNode* aNodeToFree; + QR2KeyListNode* aNextNode; + + // Free the nodes + aNodeToFree = qr2_internal_key_list.mHead; + while (aNodeToFree != NULL) + { + aNextNode = aNodeToFree->mNextKey; // Get a ptr to the next node (or will be lost) + gsifree(aNodeToFree->mKeyData); // free the string we allocated in qr2_register_keyW + gsifree(aNodeToFree); // free the current node + aNodeToFree = aNextNode; // set the current node to the next node + } + + // Initialize the list back to NULL + qr2_internal_key_list.mHead = NULL; +} + +gsi_bool qr2_internal_is_master_only_key(const char * keyname) +{ + if (strcmp(keyname,qr2_registered_key_list[COUNTRY_KEY]) == 0 || + strcmp(keyname,qr2_registered_key_list[REGION_KEY]) == 0) + return gsi_true; + + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void qr2_register_keyA(int keyid, const char *key) +{ + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_register_keyA()\r\n"); + + // Verify the key range + if (keyid < NUM_RESERVED_KEYS || keyid > MAX_REGISTERED_KEYS) + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Attempted to register invalid key %d - %s\r\n", keyid, key); + return; + } + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Comment, + "Registered key %d - %s\r\n", keyid, key); + + qr2_registered_key_list[keyid] = key; +} +void qr2_register_keyW(int keyid, const unsigned short *key) +{ + char* key_A = NULL; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_register_keyW()\r\n"); + + // Create UTF8 copy + key_A = UCS2ToUTF8StringAlloc(key); + + // Register the ascii version + qr2_register_keyA(keyid, key_A); + + // Keep track of the unicode version so we can delete it later + qr2_internal_key_list_append(key_A); +} diff --git a/code/gamespy/qr2/qr2regkeys.h b/code/gamespy/qr2/qr2regkeys.h new file mode 100644 index 00000000..b312fa15 --- /dev/null +++ b/code/gamespy/qr2/qr2regkeys.h @@ -0,0 +1,84 @@ + + +#ifndef _QR2REGKEYS_H_ +#define _QR2REGKEYS_H_ + +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +#define MAX_REGISTERED_KEYS 254 +#define NUM_RESERVED_KEYS 50 + + +#define HOSTNAME_KEY 1 +#define GAMENAME_KEY 2 +#define GAMEVER_KEY 3 +#define HOSTPORT_KEY 4 +#define MAPNAME_KEY 5 +#define GAMETYPE_KEY 6 +#define GAMEVARIANT_KEY 7 +#define NUMPLAYERS_KEY 8 +#define NUMTEAMS_KEY 9 +#define MAXPLAYERS_KEY 10 +#define GAMEMODE_KEY 11 +#define TEAMPLAY_KEY 12 +#define FRAGLIMIT_KEY 13 +#define TEAMFRAGLIMIT_KEY 14 +#define TIMEELAPSED_KEY 15 +#define TIMELIMIT_KEY 16 +#define ROUNDTIME_KEY 17 +#define ROUNDELAPSED_KEY 18 +#define PASSWORD_KEY 19 +#define GROUPID_KEY 20 +#define PLAYER__KEY 21 +#define SCORE__KEY 22 +#define SKILL__KEY 23 +#define PING__KEY 24 +#define TEAM__KEY 25 +#define DEATHS__KEY 26 +#define PID__KEY 27 +#define TEAM_T_KEY 28 +#define SCORE_T_KEY 29 +#define NN_GROUP_ID_KEY 30 + +// Query-From-Master-Only keys +// - these two values are retrieved only from the master server so we need to make +// sure not to overwrite them when querying servers directly +#define COUNTRY_KEY 31 +#define REGION_KEY 32 + + +#ifndef GSI_UNICODE + #define qr2_register_key qr2_register_keyA +#else + #define qr2_register_key qr2_register_keyW +#endif + +extern const char *qr2_registered_key_list[]; +void qr2_register_key(int keyid, const gsi_char *key); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Necessary for unicode support. Must store a copy of the UTF8 keys +// generated from qr2_register_keyW +void qr2_internal_key_list_append(char* theKey); +void qr2_internal_key_list_free(); // call this at qr2 shutdown + +// internal function used by ServerBrowser to check if a key is Query-Master-Only +gsi_bool qr2_internal_is_master_only_key(const char * keyname); + + +// Always define for direct access +void qr2_register_keyA(int keyid, const char *key); +void qr2_register_keyW(int keyid, const unsigned short *key); + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/code/gamespy/qr2/querytest.exe b/code/gamespy/qr2/querytest.exe new file mode 100644 index 0000000000000000000000000000000000000000..6d108625a490d55324016bcce4f67f392719815c GIT binary patch literal 69632 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 Mhyo!B{Ff>4pM?sllmGw# literal 0 HcmV?d00001 diff --git a/code/gamespy/sake/changelog.txt b/code/gamespy/sake/changelog.txt new file mode 100644 index 00000000..cc0be7b4 --- /dev/null +++ b/code/gamespy/sake/changelog.txt @@ -0,0 +1,57 @@ +Changelog for: GameSpy SAKE SDK +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 1.04.00 RMV RELEASE Released to Developer Site +12-11-2007 1.03.03 SN FIX Removed a check due to compiler warning +11-16-2007 1.03.02 SAH FIX Fixed mem leak in saketest.c from invalid gsCore shutdown, added memleak check +10-09-2007 1.03.01 SAH FIX Fixed mFilter and mTargetFilter to support unicode strings + SAH OTHER Added Win32 Unicode Debug & Unicode Release projects to VS2005 solution +08-06-2007 1.03.00 RMV RELEASE Released to Developer Site +08-03-2007 1.02.08 SAH FEATURE Added mCacheFlag for searchForRecords and getRowCount +07-19-2007 1.02.07 SAH FIX Fixed projects to work with new GP changes +07-03-2007 1.02.06 SAH FIX Fixed memory leak in GetRandomRecord +06-29-2007 1.02.05 SAH OTHER Added SAKEFileResult_SERVER_ERROR to error code list +06-20-2007 1.02.04 SAH FEATURE Added sakeGetRecordCount + SAH FEATURE Added new input data for sakeSearchForRecords for initial leaderboard support +06-13-2007 1.02.03 SAH FIX Fixed memory leak from not freeing BinaryData fields or UnicodeStrings +05-29-2007 1.02.02 DES FEATURE Updated saketest to include GetRandomRecord test +03-22-2007 1.02.01 DES FIX Allow NULL for mFilter and mSort with SearchForRecords + DES FEATURE Added GetRandomRecord + DES FEATURE Added special field "my_rating" + DES FEATURE Added special filter tags "@rated" and "@unrated" +03-05-2007 1.02.00 SAH RELEASE Released to Developer Site +01-22-2007 1.01.02 SAH FIX Fixed unicode for upload/download URLs and setGameName, added unicode config +01-17-2007 1.01.01 DES RELEASE Limited Release +01-16-2007 1.01.01 DES FEATURE Added X360 support +12-15-2006 1.01.00 MJW RELEASE Released to Developer Site +12-14-2006 1.00.20 MJW FIX Removed tabs from Makefile for linux (was causing it to break) +11-10-2006 1.00.19 JR RELEASE Limited Release +11-02-2006 1.00.19 SAH FIX Fixed bug where all booleans values returned "false", even when true +10-23-2006 1.00.18 DES RELEASE Limited release +10-12-2006 1.00.18 DES FIX Added explicit cast for gsimalloc +10-11-2006 1.00.17 DES FIX Fixed Unicode issue with upload/download URLs +10-05-2006 1.00.16 SAH FIX Updated Mac OSX Makefile + SAH FIX Fixed saketest to get rid of compiler warning errors for Mac +09-29-2006 1.00.15 SAH FIX Updated all sake projects to compile with GP (for saketest change) +09-28-2006 1.00.14 SAH FIX Updated saketest application to login to GP and retrieve a loginticket to authenticate sake. +09-28-2006 1.00.13 SAH FIX Fixed PS3 project to work with PS3 095.00x SDK; changed included libaries in linker input. +09-27-2006 1.00.13 SN OTHER Removed function below until development backend is available +09-20-2006 1.00.12 SN FEATURE Added an interface function allow usage of the development environment +08-04-2006 1.00.11 SN FIX Fixed Sake Read requestion function for date and time to use time_t +08-04-2006 1.00.10 SAH FIX Changed DateTime read call to gsXmlReadChildAsDateTimeElement to parse correctly +08-02-2006 1.00.09 SAH RELEASE Releasing to developer site +08-01-2006 1.00.09 SAH FIX Fixed saketest.c so that it won't crash when a specific record does not exist + SAH OTHER Added sakelinux makefile - *NOTE: unsupported in current version due to pthreads +07-31-2006 1.00.08 SAH FIX Fixed PS3 project file - added post-build step to create *.SELF for execution +07-25-2006 1.00.07 SAH FIX Fixed NITRO project, include for crt0.o now above others so it add correct file +07-06-2006 1.00.06 SAH FIX Fixed PSP project file to not explicitly include the PSP linker file +07-06-2006 1.00.05 SAH FIX Fixed NITRO project & linker command file (to work with CW 2.0/NitroSDK 3.1) +07-05-2006 1.00.04 SAH OTHER Updated saketest.c to have a larger size upload test (5K by default) +06-21-2006 1.00.03 DES FEATURE sakeSetGame now requires the game's secret key +05-31-2006 1.00.02 SAH RELEASE Releasing to developer site +05-30-2006 1.00.01 SAH FIX #ifdef PS2 Unicode hack to get rid of PS2 Unicode warnings + SAH FIX Added GSI_UNUSED calls, newline at end of files +05-18-2006 1.00.00 DES RELEASE Initial limited release + diff --git a/code/gamespy/sake/sake.h b/code/gamespy/sake/sake.h new file mode 100644 index 00000000..8a4d0a3d --- /dev/null +++ b/code/gamespy/sake/sake.h @@ -0,0 +1,352 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __SAKE_H__ +#define __SAKE_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../common/gsCommon.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +extern "C" { +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef SAKE_CALL + #define SAKE_CALL +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// URL for sake webservice +#define SAKE_MAX_URL_LENGTH 128 +extern char sakeiSoapUrl[SAKE_MAX_URL_LENGTH]; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// General +typedef struct SAKEInternal *SAKE; + +typedef enum +{ + SAKEStartupResult_SUCCESS, + SAKEStartupResult_NOT_AVAILABLE, + SAKEStartupResult_CORE_SHUTDOWN, + SAKEStartupResult_OUT_OF_MEMORY +} SAKEStartupResult; + +SAKEStartupResult SAKE_CALL sakeStartup(SAKE *sakePtr); +void SAKE_CALL sakeShutdown(SAKE sake); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Authentication +void SAKE_CALL sakeSetGame(SAKE sake, const gsi_char *gameName, int gameId, const gsi_char *secretKey); +void SAKE_CALL sakeSetProfile(SAKE sake, int profileId, const char *loginTicket); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Fields +typedef enum +{ + SAKEFieldType_BYTE, + SAKEFieldType_SHORT, + SAKEFieldType_INT, + SAKEFieldType_FLOAT, + SAKEFieldType_ASCII_STRING, + SAKEFieldType_UNICODE_STRING, + SAKEFieldType_BOOLEAN, + SAKEFieldType_DATE_AND_TIME, + SAKEFieldType_BINARY_DATA, + SAKEFieldType_INT64, + SAKEFieldType_NUM_FIELD_TYPES +} SAKEFieldType; + +typedef struct +{ + gsi_u8 *mValue; + int mLength; +} SAKEBinaryData; + +typedef union +{ + gsi_u8 mByte; + gsi_i16 mShort; + gsi_i32 mInt; + float mFloat; + char *mAsciiString; + gsi_u16 *mUnicodeString; + gsi_bool mBoolean; + time_t mDateAndTime; + SAKEBinaryData mBinaryData; + gsi_i64 mInt64; +} SAKEValue; + +typedef struct +{ + char *mName; + SAKEFieldType mType; + SAKEValue mValue; +} SAKEField; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Requests +typedef struct SAKERequestInternal *SAKERequest; + +typedef enum +{ + SAKEStartRequestResult_SUCCESS, + SAKEStartRequestResult_NOT_AUTHENTICATED, + SAKEStartRequestResult_OUT_OF_MEMORY, + SAKEStartRequestResult_BAD_INPUT, + SAKEStartRequestResult_BAD_TABLEID, + SAKEStartRequestResult_BAD_FIELDS, + SAKEStartRequestResult_BAD_NUM_FIELDS, + SAKEStartRequestResult_BAD_FIELD_NAME, + SAKEStartRequestResult_BAD_FIELD_TYPE, + SAKEStartRequestResult_BAD_FIELD_VALUE, + SAKEStartRequestResult_BAD_OFFSET, + SAKEStartRequestResult_BAD_MAX, + SAKEStartRequestResult_BAD_RECORDIDS, + SAKEStartRequestResult_BAD_NUM_RECORDIDS, + SAKEStartRequestResult_UNKNOWN_ERROR +} SAKEStartRequestResult; + +typedef enum +{ + SAKERequestResult_SUCCESS, + SAKERequestResult_SECRET_KEY_INVALID, + SAKERequestResult_SERVICE_DISABLED, + SAKERequestResult_CONNECTION_TIMEOUT, + SAKERequestResult_CONNECTION_ERROR, + SAKERequestResult_MALFORMED_RESPONSE, + SAKERequestResult_OUT_OF_MEMORY, + SAKERequestResult_DATABASE_UNAVAILABLE, + SAKERequestResult_LOGIN_TICKET_INVALID, + SAKERequestResult_LOGIN_TICKET_EXPIRED, + SAKERequestResult_TABLE_NOT_FOUND, + SAKERequestResult_RECORD_NOT_FOUND, + SAKERequestResult_FIELD_NOT_FOUND, + SAKERequestResult_FIELD_TYPE_INVALID, + SAKERequestResult_NO_PERMISSION, + SAKERequestResult_RECORD_LIMIT_REACHED, + SAKERequestResult_ALREADY_RATED, + SAKERequestResult_NOT_RATEABLE, + SAKERequestResult_NOT_OWNED, + SAKERequestResult_FILTER_INVALID, + SAKERequestResult_SORT_INVALID, + SAKERequestResult_TARGET_FILTER_INVALID, + SAKERequestResult_UNKNOWN_ERROR +} SAKERequestResult; + +typedef void (*SAKERequestCallback)(SAKE sake, SAKERequest request, SAKERequestResult result, void *inputData, void *outputData, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// get start request result +SAKEStartRequestResult SAKE_CALL sakeGetStartRequestResult(SAKE sake); + +/////////////////////////////////////////////////////////////////////////////// +// create record +typedef struct +{ + char *mTableId; + SAKEField *mFields; + int mNumFields; +} SAKECreateRecordInput; +typedef struct +{ + int mRecordId; +} SAKECreateRecordOutput; +SAKERequest SAKE_CALL sakeCreateRecord(SAKE sake, SAKECreateRecordInput *input, SAKERequestCallback callback, void *userData); + +//////////////////////////// /////////////////////////////////////////////////// +// update record +typedef struct +{ + char *mTableId; + int mRecordId; + SAKEField *mFields; + int mNumFields; +} SAKEUpdateRecordInput; +SAKERequest SAKE_CALL sakeUpdateRecord(SAKE sake, SAKEUpdateRecordInput *input, SAKERequestCallback callback, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// delete record +typedef struct +{ + char *mTableId; + int mRecordId; +} SAKEDeleteRecordInput; +SAKERequest SAKE_CALL sakeDeleteRecord(SAKE sake, SAKEDeleteRecordInput *input, SAKERequestCallback callback, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// search for records +typedef struct +{ + char *mTableId; + char **mFieldNames; + int mNumFields; + gsi_char *mFilter; + char *mSort; + int mOffset; + int mMaxRecords; + gsi_char *mTargetRecordFilter; + int mSurroundingRecordsCount; + int *mOwnerIds; + int mNumOwnerIds; + gsi_bool mCacheFlag; +} SAKESearchForRecordsInput; +typedef struct +{ + int mNumRecords; + SAKEField **mRecords; +} SAKESearchForRecordsOutput; +SAKERequest SAKE_CALL sakeSearchForRecords(SAKE sake, SAKESearchForRecordsInput *input, SAKERequestCallback callback, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// get my records +typedef struct +{ + char *mTableId; + char **mFieldNames; + int mNumFields; +} SAKEGetMyRecordsInput; +typedef struct +{ + int mNumRecords; + SAKEField **mRecords; +} SAKEGetMyRecordsOutput; +SAKERequest SAKE_CALL sakeGetMyRecords(SAKE sake, SAKEGetMyRecordsInput *input, SAKERequestCallback callback, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// get specific records +typedef struct +{ + char *mTableId; + int *mRecordIds; + int mNumRecordIds; + char **mFieldNames; + int mNumFields; +} SAKEGetSpecificRecordsInput; +typedef struct +{ + int mNumRecords; + SAKEField **mRecords; +} SAKEGetSpecificRecordsOutput; +SAKERequest SAKE_CALL sakeGetSpecificRecords(SAKE sake, SAKEGetSpecificRecordsInput *input, SAKERequestCallback callback, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// get random record +typedef struct +{ + char *mTableId; + char **mFieldNames; + int mNumFields; + gsi_char *mFilter; +} SAKEGetRandomRecordInput; +typedef struct +{ + SAKEField *mRecord; +} SAKEGetRandomRecordOutput; +SAKERequest SAKE_CALL sakeGetRandomRecord(SAKE sake, SAKEGetRandomRecordInput *input, SAKERequestCallback callback, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// rate record +typedef struct +{ + char *mTableId; + int mRecordId; + gsi_u8 mRating; +} SAKERateRecordInput; +typedef struct +{ + int mNumRatings; + float mAverageRating; +} SAKERateRecordOutput; +SAKERequest SAKE_CALL sakeRateRecord(SAKE sake, SAKERateRecordInput *input, SAKERequestCallback callback, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// get record limit +typedef struct +{ + char *mTableId; +} SAKEGetRecordLimitInput; +typedef struct +{ + int mLimitPerOwner; + int mNumOwned; +} SAKEGetRecordLimitOutput; +SAKERequest SAKE_CALL sakeGetRecordLimit(SAKE sake, SAKEGetRecordLimitInput *input, SAKERequestCallback callback, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// get record count +typedef struct +{ + char *mTableId; + gsi_char *mFilter; + gsi_bool mCacheFlag; +} SAKEGetRecordCountInput; +typedef struct +{ + int mCount; +} SAKEGetRecordCountOutput; +SAKERequest SAKE_CALL sakeGetRecordCount(SAKE sake, SAKEGetRecordCountInput *input, SAKERequestCallback callback, void *userData); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// read request utility +SAKEField * SAKE_CALL sakeGetFieldByName(const char *name, SAKEField *fields, int numFields); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Files + +#define SAKE_FILE_RESULT_HEADER "Sake-File-Result:" +#define SAKE_FILE_ID_HEADER "Sake-File-Id:" + +// Sake-File-Result from the HTTP response header +typedef enum +{ + SAKEFileResult_SUCCESS = 0, + SAKEFileResult_BAD_HTTP_METHOD = 1, + SAKEFileResult_BAD_FILE_COUNT = 2, + SAKEFileResult_MISSING_PARAMETER = 3, + SAKEFileResult_FILE_NOT_FOUND = 4, + SAKEFileResult_FILE_TOO_LARGE = 5, + SAKEFileResult_SERVER_ERROR = 6, + SAKEFileResult_UNKNOWN_ERROR +} SAKEFileResult; + +gsi_bool SAKE_CALL sakeSetFileDownloadURL(SAKE sake, gsi_char url[SAKE_MAX_URL_LENGTH]); +gsi_bool SAKE_CALL sakeSetFileUploadURL(SAKE sake, gsi_char url[SAKE_MAX_URL_LENGTH]); + +gsi_bool SAKE_CALL sakeGetFileDownloadURL(SAKE sake, int fileId, gsi_char url[SAKE_MAX_URL_LENGTH]); +gsi_bool SAKE_CALL sakeGetFileUploadURL(SAKE sake, gsi_char url[SAKE_MAX_URL_LENGTH]); + +gsi_bool SAKE_CALL sakeGetFileResultFromHeaders(const char *headers, SAKEFileResult *result); +gsi_bool SAKE_CALL sakeGetFileIdFromHeaders(const char *headers, int *fileId); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} // extern "C" +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __SAKE_H__ diff --git a/code/gamespy/sake/sakeMain.c b/code/gamespy/sake/sakeMain.c new file mode 100644 index 00000000..b59e2b09 --- /dev/null +++ b/code/gamespy/sake/sakeMain.c @@ -0,0 +1,428 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sakeMain.h" +#include "sakeRequest.h" +#include "../common/gsAvailable.h" +#include "../common/gsCore.h" + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// General + +gsi_char gSakeUploadUrlOverride[SAKE_MAX_URL_LENGTH]; +gsi_char gSakeDownloadUrlOverride[SAKE_MAX_URL_LENGTH]; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKEStartupResult SAKE_CALL sakeStartup(SAKE * sakePtr) +{ + SAKE sake; + + GS_ASSERT(sakePtr); + + // check for availability + if(__GSIACResult != GSIACAvailable) + return SAKEStartupResult_NOT_AVAILABLE; + + // check that the core is initialized + if(gsCoreIsShutdown()) + return SAKEStartupResult_CORE_SHUTDOWN; + + // allocate the sake object + sake = (SAKE)gsimalloc(sizeof(SAKEInternal)); + if(sake == NULL) + return SAKEStartupResult_OUT_OF_MEMORY; + + // init the sake object + memset(sake, 0, sizeof(SAKEInternal)); + + // store the object in the user pointer + *sakePtr = sake; + + return SAKEStartupResult_SUCCESS; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void SAKE_CALL sakeShutdown(SAKE sake) +{ + GS_ASSERT(sake); + + //TODO: ensure that there are no pending operations + // that might reference this object + + // free the struct + gsifree(sake); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Authentication + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void SAKE_CALL sakeSetGame(SAKE sake, const gsi_char * gameName, int gameId, const gsi_char *secretKey) +{ + GS_ASSERT(sake); + GS_ASSERT(gameName && (_tcslen(gameName) <= SAKEI_GAME_NAME_LENGTH)); + GS_ASSERT(gameId >= 0); + GS_ASSERT(secretKey && (_tcslen(secretKey) <= SAKEI_SECRET_KEY_LENGTH)); + +#ifdef GSI_UNICODE + // convert gamename and secretkey to ascii for executing requests + UCS2ToAsciiString(gameName, sake->mGameName); + UCS2ToAsciiString(secretKey, sake->mSecretKey); +#else + strcpy(sake->mGameName, gameName); + strcpy(sake->mSecretKey, secretKey); +#endif + + sake->mGameId = gameId; + sake->mIsGameAuthenticated = gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void SAKE_CALL sakeSetProfile(SAKE sake, int profileId, const char *loginTicket) +{ + GS_ASSERT(sake); + GS_ASSERT(loginTicket); + GS_ASSERT(strlen(loginTicket) == SAKEI_LOGIN_TICKET_LENGTH); + + sake->mProfileId = profileId; + memcpy(sake->mLoginTicket, loginTicket, SAKEI_LOGIN_TICKET_LENGTH + 1); + sake->mIsProfileAuthenticated = gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Requests + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKEStartRequestResult SAKE_CALL sakeGetStartRequestResult(SAKE sake) +{ + GS_ASSERT(sake); + + return sake->mStartRequestResult; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static SAKERequest SAKE_CALL sakeiRunRequest(SAKE sake, void *input, + SAKERequestCallback callback, void *userData, + SAKEIRequestType type, + SAKEStartRequestResult (*startRequestFunc)(SAKERequest request)) +{ + SAKERequest request; + + GS_ASSERT(sake); + + request = sakeiInitRequest(sake, type, input, callback, userData); + if(!request) + return NULL; + + sake->mStartRequestResult = startRequestFunc(request); + if(sake->mStartRequestResult != SAKEStartRequestResult_SUCCESS) + { + sakeiFreeRequest(request); + return NULL; + } + + return request; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeCreateRecord(SAKE sake, SAKECreateRecordInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_CREATE_RECORD, sakeiStartCreateRecordRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeUpdateRecord(SAKE sake, SAKEUpdateRecordInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_UPDATE_RECORD, sakeiStartUpdateRecordRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeDeleteRecord(SAKE sake, SAKEDeleteRecordInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_DELETE_RECORD, sakeiStartDeleteRecordRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeSearchForRecords(SAKE sake, SAKESearchForRecordsInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_SEARCH_FOR_RECORDS, sakeiStartSearchForRecordsRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeGetMyRecords(SAKE sake, SAKEGetMyRecordsInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_GET_MY_RECORDS, sakeiStartGetMyRecordsRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeGetSpecificRecords(SAKE sake, SAKEGetSpecificRecordsInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_GET_SPECIFIC_RECORDS, sakeiStartGetSpecificRecordsRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeGetRandomRecord(SAKE sake, SAKEGetRandomRecordInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_GET_RANDOM_RECORD, sakeiStartGetRandomRecordRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeRateRecord(SAKE sake, SAKERateRecordInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_RATE_RECORD, sakeiStartRateRecordRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeGetRecordLimit(SAKE sake, SAKEGetRecordLimitInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_GET_RECORD_LIMIT, sakeiStartGetRecordLimitRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeGetRecordCount(SAKE sake, SAKEGetRecordCountInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_GET_RECORD_COUNT, sakeiStartGetRecordCountRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// read request utility +SAKEField * SAKE_CALL sakeGetFieldByName(const char *name, SAKEField *fields, int numFields) +{ + int i; + + GS_ASSERT(name); + GS_ASSERT(fields); + GS_ASSERT(numFields >= 0); + + for(i = 0 ; i < numFields ; i++) + { + if(strcmp(fields[i].mName, name) == 0) + return &fields[i]; + } + + return NULL; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Files + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Set the URL format to be used by sakeGetFileDownloadUrl +gsi_bool SAKE_CALL sakeSetFileDownloadURL(SAKE sake, gsi_char url[SAKE_MAX_URL_LENGTH]) +{ + GS_ASSERT(sake); + GS_ASSERT(url); + + if(!sake || !url) + return gsi_false; + + _tcscpy(gSakeDownloadUrlOverride, url); + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool SAKE_CALL sakeGetFileDownloadURL(SAKE sake, int fileId, gsi_char url[SAKE_MAX_URL_LENGTH]) +{ + int rcode; + + GS_ASSERT(sake); + GS_ASSERT(fileId != 0); + GS_ASSERT(url); + GS_ASSERT(sake->mIsGameAuthenticated); + GS_ASSERT(sake->mIsProfileAuthenticated); + + if(!sake || !url || !sake->mIsGameAuthenticated || !sake->mIsProfileAuthenticated) + return gsi_false; + + if (gSakeDownloadUrlOverride[0] != '\0') + { + rcode = _tsnprintf(url, SAKE_MAX_URL_LENGTH, _T("%s?gameid=%d&pid=%d"), + gSakeDownloadUrlOverride, sake->mGameId, sake->mProfileId); + } + else + { + #ifdef GSI_UNICODE + { + // use capital %S to convert the gamename to a wide string + rcode = _tsnprintf(url, SAKE_MAX_URL_LENGTH, _T("http://%S.sake.%S/SakeFileServer/download.aspx?fileid=%d&gameid=%d&pid=%d"), + sake->mGameName, GSI_DOMAIN_NAME, fileId, sake->mGameId, sake->mProfileId); + } + #else + { + rcode = _tsnprintf(url, SAKE_MAX_URL_LENGTH, _T("http://%s.sake.%s/SakeFileServer/download.aspx?fileid=%d&gameid=%d&pid=%d"), + sake->mGameName, GSI_DOMAIN_NAME, fileId, sake->mGameId, sake->mProfileId); + } + #endif + } + + if(rcode < 0) + return gsi_false; + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool SAKE_CALL sakeSetFileUploadURL(SAKE sake, gsi_char url[SAKE_MAX_URL_LENGTH]) +{ + GS_ASSERT(sake); + GS_ASSERT(url); + + if(!sake || !url) + return gsi_false; + + _tcscpy(gSakeUploadUrlOverride, url); + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool SAKE_CALL sakeGetFileUploadURL(SAKE sake, gsi_char url[SAKE_MAX_URL_LENGTH]) +{ + int rcode; + + GS_ASSERT(sake); + GS_ASSERT(url); + GS_ASSERT(sake->mIsGameAuthenticated); + GS_ASSERT(sake->mIsProfileAuthenticated); + + if(!sake || !url || !sake->mIsGameAuthenticated || !sake->mIsProfileAuthenticated) + return gsi_false; + + if (gSakeUploadUrlOverride[0] != '\0') + { + rcode = _tsnprintf(url, SAKE_MAX_URL_LENGTH, _T("%s?gameid=%d&pid=%d"), + gSakeUploadUrlOverride, sake->mGameId, sake->mProfileId); + } + else + { + #ifdef GSI_UNICODE + { + // use capital %S to convert the gamename to a wide string + rcode = _tsnprintf(url, SAKE_MAX_URL_LENGTH, _T("http://%S.sake.%S/SakeFileServer/upload.aspx?gameid=%d&pid=%d"), + sake->mGameName, GSI_DOMAIN_NAME, sake->mGameId, sake->mProfileId); + } + #else + { + rcode = _tsnprintf(url, SAKE_MAX_URL_LENGTH, _T("http://%s.sake.%s/SakeFileServer/upload.aspx?gameid=%d&pid=%d"), + sake->mGameName, GSI_DOMAIN_NAME, sake->mGameId, sake->mProfileId); + } + #endif + } + + if(rcode < 0) + return gsi_false; + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static SAKEFileResult SAKE_CALL sakeiParseFileResult(int resultCode) +{ + if(resultCode >= SAKEFileResult_UNKNOWN_ERROR) + return SAKEFileResult_UNKNOWN_ERROR; + return (SAKEFileResult)resultCode; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool SAKE_CALL sakeiGetHeaderValueInt(const char *headers, const char *headerName, int *value) +{ + const char * header; + int rcode; + + GS_ASSERT(headers); + GS_ASSERT(headerName); + GS_ASSERT(value); +#ifdef _DEBUG + // headerName must include the trailing colon + GS_ASSERT(headerName[strlen(headerName) - 1] == ':'); +#endif + + // find this header in the list of headers + header = strstr(headers, headerName); + if(header) + { + // skip the header name + header += strlen(headerName); + + // scan in the result + rcode = sscanf(header, " %d", value); + if(rcode == 1) + return gsi_true; + } + + return gsi_false; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool SAKE_CALL sakeGetFileResultFromHeaders(const char *headers, SAKEFileResult *result) +{ + int resultCode; + gsi_bool foundResultCode; + + foundResultCode = sakeiGetHeaderValueInt(headers, SAKE_FILE_RESULT_HEADER, &resultCode); + + if(gsi_is_false(foundResultCode)) + return gsi_false; + + *result = sakeiParseFileResult(resultCode); + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool SAKE_CALL sakeGetFileIdFromHeaders(const char *headers, int *fileId) +{ + return sakeiGetHeaderValueInt(headers, SAKE_FILE_ID_HEADER, fileId); +} diff --git a/code/gamespy/sake/sakeMain.h b/code/gamespy/sake/sakeMain.h new file mode 100644 index 00000000..a0fa250d --- /dev/null +++ b/code/gamespy/sake/sakeMain.h @@ -0,0 +1,51 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __SAKEMAIN_H__ +#define __SAKEMAIN_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sake.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define SAKEI_GAME_NAME_LENGTH 15 +#define SAKEI_SECRET_KEY_LENGTH 8 +#define SAKEI_LOGIN_TICKET_LENGTH 24 + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct SAKEInternal +{ + gsi_bool mIsGameAuthenticated; + char mGameName[SAKEI_GAME_NAME_LENGTH + 1]; + int mGameId; + char mSecretKey[SAKEI_SECRET_KEY_LENGTH + 1]; + + gsi_bool mIsProfileAuthenticated; + int mProfileId; + char mLoginTicket[SAKEI_LOGIN_TICKET_LENGTH + 1]; + + SAKEStartRequestResult mStartRequestResult; +} SAKEInternal; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} // extern "C" +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __SAKEMAIN_H__ diff --git a/code/gamespy/sake/sakeRequest.c b/code/gamespy/sake/sakeRequest.c new file mode 100644 index 00000000..9e30820a --- /dev/null +++ b/code/gamespy/sake/sakeRequest.c @@ -0,0 +1,287 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sakeRequest.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +const char * GSI_SAKE_SERVICE_NAMESPACES[GSI_SAKE_SERVICE_NAMESPACE_COUNT] = +{ + "ns1=\"http://gamespy.net/sake\"" +}; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define SAKEI_SOAP_URL_FORMAT "http://%s.sake." GSI_DOMAIN_NAME "/SakeStorageServer/StorageServer.asmx" +char sakeiSoapUrl[SAKE_MAX_URL_LENGTH] = ""; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeiInitRequest(SAKE sake, SAKEIRequestType type, void *input, SAKERequestCallback callback, void *userData) +{ + SAKERequest request; + + GS_ASSERT(sake); + + // init the request init result to success + sake->mStartRequestResult = SAKEStartRequestResult_SUCCESS; + + // check for input + if(!input) + { + sake->mStartRequestResult = SAKEStartRequestResult_BAD_INPUT; + return NULL; + } + + // check for authentication + if(gsi_is_false(sake->mIsGameAuthenticated) || gsi_is_false(sake->mIsProfileAuthenticated)) + { + sake->mStartRequestResult = SAKEStartRequestResult_NOT_AUTHENTICATED; + return NULL; + } + + // allocate memory for the request object + request = (SAKERequest)gsimalloc(sizeof(SAKERequestInternal)); + if(!request) + { + sake->mStartRequestResult = SAKEStartRequestResult_OUT_OF_MEMORY; + return NULL; + } + + // init the request object + memset(request, 0, sizeof(SAKERequestInternal)); + request->mSake = sake; + request->mType = type; + request->mInput = input; + request->mCallback = callback; + request->mUserData = userData; + + return request; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void SAKE_CALL sakeiFreeRequest(SAKERequest request) +{ + GS_ASSERT(request); + + // this should already be freed by the time we get here + GS_ASSERT(request->mOutput == NULL); + + // free the request + gsifree(request); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static SAKERequestResult SAKE_CALL sakeiCheckHttpResult(GHTTPResult httpResult) +{ + switch(httpResult) + { + case GHTTPSuccess: + return SAKERequestResult_SUCCESS; + case GHTTPOutOfMemory: + return SAKERequestResult_OUT_OF_MEMORY; + default: + return SAKERequestResult_CONNECTION_ERROR; + } +} + +static SAKERequestResult SAKE_CALL sakeiCheckSakeResult(const char * sakeResult) +{ + if(strcmp(sakeResult, "Success") == 0) + return SAKERequestResult_SUCCESS; + else if(strcmp(sakeResult, "SecretKeyInvalid") == 0) + return SAKERequestResult_SECRET_KEY_INVALID; + else if(strcmp(sakeResult, "ServiceDisabled") == 0) + return SAKERequestResult_SERVICE_DISABLED; + else if(strcmp(sakeResult, "DatabaseUnavailable") == 0) + return SAKERequestResult_DATABASE_UNAVAILABLE; + else if(strcmp(sakeResult, "LoginTicketInvalid") == 0) + return SAKERequestResult_LOGIN_TICKET_INVALID; + else if(strcmp(sakeResult, "LoginTicketExpired") == 0) + return SAKERequestResult_LOGIN_TICKET_EXPIRED; + else if(strcmp(sakeResult, "TableNotFound") == 0) + return SAKERequestResult_TABLE_NOT_FOUND; + else if(strcmp(sakeResult, "RecordNotFound") == 0) + return SAKERequestResult_RECORD_NOT_FOUND; + else if(strcmp(sakeResult, "FieldNotFound") == 0) + return SAKERequestResult_FIELD_NOT_FOUND; + else if(strcmp(sakeResult, "FieldTypeInvalid") == 0) + return SAKERequestResult_FIELD_TYPE_INVALID; + else if(strcmp(sakeResult, "NoPermission") == 0) + return SAKERequestResult_NO_PERMISSION; + else if(strcmp(sakeResult, "RecordLimitReached") == 0) + return SAKERequestResult_RECORD_LIMIT_REACHED; + else if(strcmp(sakeResult, "AlreadyRated") == 0) + return SAKERequestResult_ALREADY_RATED; + else if(strcmp(sakeResult, "NotRateable") == 0) + return SAKERequestResult_NOT_RATEABLE; + else if(strcmp(sakeResult, "NotOwned") == 0) + return SAKERequestResult_NOT_OWNED; + else if(strcmp(sakeResult, "FilterInvalid") == 0) + return SAKERequestResult_FILTER_INVALID; + else if(strcmp(sakeResult, "SortInvalid") == 0) + return SAKERequestResult_SORT_INVALID; + else if(strcmp(sakeResult, "TargetFilterInvalid") == 0) + return SAKERequestResult_TARGET_FILTER_INVALID; + else + return SAKERequestResult_UNKNOWN_ERROR; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void SAKE_CALL sakeiSoapCallback(GHTTPResult httpResult, GSXmlStreamWriter requestData, GSXmlStreamReader responseData, void *userData) +{ + SAKERequest request = (SAKERequest)userData; + void *output = NULL; + SAKERequestResult result; + char resultString[32]; + + // sanity check + GS_ASSERT(request); + GS_ASSERT(request->mSake); + GS_ASSERT(request->mInfo); + if(!request || !request->mSake || !request->mInfo) + return; + + result = sakeiCheckHttpResult(httpResult); + if(result == SAKERequestResult_SUCCESS) + { + if(gsi_is_false(gsXmlMoveToStart(responseData)) || + gsi_is_false(gsXmlMoveToNext(responseData, request->mInfo->mResponseTag)) || + gsi_is_false(gsXmlReadChildAsStringNT(responseData, request->mInfo->mResultTag, resultString, sizeof(resultString)))) + { + result = SAKERequestResult_MALFORMED_RESPONSE; + } + else + { + result = sakeiCheckSakeResult(resultString); + + // fill in the output + if(result == SAKERequestResult_SUCCESS) + { + if(request->mInfo->mSakeOutputSize != 0) + { + request->mOutput = gsimalloc(request->mInfo->mSakeOutputSize); + if(request->mOutput) + { + request->mSoapResponse = responseData; + result = request->mInfo->mProcessSoapResponseFunc(request); + if(result == SAKERequestResult_SUCCESS) + output = request->mOutput; + } + else + { + result = SAKERequestResult_OUT_OF_MEMORY; + } + } + } + } + } + + // call the callback + if(request->mCallback) + request->mCallback(request->mSake, request, result, request->mInput, output, request->mUserData); + + // free data + if(request->mInfo->mFreeDataFunc) + request->mInfo->mFreeDataFunc(request); + + // free the output data + gsifree(request->mOutput); + request->mOutput = NULL; + + // free the sake request + sakeiFreeRequest(request); + + GSI_UNUSED(requestData); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static SAKEStartRequestResult SAKE_CALL sakeiSetupRequest(SAKERequest request) +{ + SAKEStartRequestResult result; + SAKEIRequestInfo * info; + + GS_ASSERT(request); + GS_ASSERT(request->mSake); + GS_ASSERT(request->mInfo); + + // store a utility pointer to the info + info = request->mInfo; + + // check the input + result = info->mValidateInputFunc(request); + if(result != SAKEStartRequestResult_SUCCESS) + return result; + + // create the xml request stream + request->mSoapRequest = gsXmlCreateStreamWriter(GSI_SAKE_SERVICE_NAMESPACES, GSI_SAKE_SERVICE_NAMESPACE_COUNT); + if(request->mSoapRequest == NULL) + return SAKEStartRequestResult_OUT_OF_MEMORY; + + // open the stream + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, request->mInfo->mFuncName); + + // this info is included with every request + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "gameid", (gsi_u32)request->mSake->mGameId); + gsXmlWriteStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "secretKey", request->mSake->mSecretKey); + gsXmlWriteStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "loginTicket", request->mSake->mLoginTicket); + + // fill in the request data + result = info->mFillSoapRequestFunc(request); + if(result != SAKEStartRequestResult_SUCCESS) + { + gsXmlFreeWriter(request->mSoapRequest); + request->mSoapRequest = NULL; + if(info->mFreeDataFunc) + info->mFreeDataFunc(request); + return result; + } + + // close the stream and writer + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, request->mInfo->mFuncName); + gsXmlCloseWriter(request->mSoapRequest); + + return SAKEStartRequestResult_SUCCESS; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void SAKE_CALL sakeiExecuteRequest(SAKERequest request) +{ + if(sakeiSoapUrl[0] == '\0') + { + int rcode; + rcode = snprintf(sakeiSoapUrl, SAKE_MAX_URL_LENGTH, SAKEI_SOAP_URL_FORMAT, request->mSake->mGameName); + GS_ASSERT(rcode >= 0); + GSI_UNUSED(rcode); + } + gsiExecuteSoap(sakeiSoapUrl, request->mInfo->mSoapAction, request->mSoapRequest, sakeiSoapCallback, request); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKEStartRequestResult SAKE_CALL sakeiStartRequest(SAKERequest request, SAKEIRequestInfo * info) +{ + SAKEStartRequestResult result; + + request->mInfo = info; + + result = sakeiSetupRequest(request); + if(result != SAKEStartRequestResult_SUCCESS) + return result; + + sakeiExecuteRequest(request); + + return SAKEStartRequestResult_SUCCESS; +} diff --git a/code/gamespy/sake/sakeRequest.h b/code/gamespy/sake/sakeRequest.h new file mode 100644 index 00000000..d2016e26 --- /dev/null +++ b/code/gamespy/sake/sakeRequest.h @@ -0,0 +1,91 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __SAKEREQUEST_H__ +#define __SAKEREQUEST_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sakeMain.h" +#include "sakeRequestInternal.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +extern "C" { +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define GSI_SAKE_SERVICE_NAMESPACE_COUNT 1 +#define GSI_SAKE_SERVICE_NAMESPACE "ns1" +#define GSI_SAKE_SERVICE_NAMESPACE_URL "http://gamespy.net/sake" +extern const char * GSI_SAKE_SERVICE_NAMESPACES[GSI_SAKE_SERVICE_NAMESPACE_COUNT]; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef enum +{ + SAKEIRequestType_CREATE_RECORD, + SAKEIRequestType_UPDATE_RECORD, + SAKEIRequestType_DELETE_RECORD, + SAKEIRequestType_SEARCH_FOR_RECORDS, + SAKEIRequestType_GET_MY_RECORDS, + SAKEIRequestType_GET_SPECIFIC_RECORDS, + SAKEIRequestType_GET_RANDOM_RECORD, + SAKEIRequestType_RATE_RECORD, + SAKEIRequestType_GET_RECORD_LIMIT, + SAKEIRequestType_GET_RECORD_COUNT +} SAKEIRequestType; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct SAKERequestInternal +{ + SAKE mSake; + SAKEIRequestType mType; + void *mInput; + void *mOutput; + SAKERequestCallback mCallback; + void *mUserData; + GSXmlStreamWriter mSoapRequest; + GSXmlStreamWriter mSoapResponse; + SAKEIRequestInfo *mInfo; +} SAKERequestInternal; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeiInitRequest(SAKE sake, SAKEIRequestType type, void *input, SAKERequestCallback callback, void *userData); +void SAKE_CALL sakeiFreeRequest(SAKERequest request); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKEStartRequestResult SAKE_CALL sakeiStartCreateRecordRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartUpdateRecordRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartDeleteRecordRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartSearchForRecordsRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartGetMyRecordsRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartGetSpecificRecordsRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartGetRandomRecordRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartRateRecordRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartGetRecordLimitRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartGetRecordCountRequest(SAKERequest request); + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} // extern "C" +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __SAKEREQUEST_H__ diff --git a/code/gamespy/sake/sakeRequestInternal.h b/code/gamespy/sake/sakeRequestInternal.h new file mode 100644 index 00000000..bd797bde --- /dev/null +++ b/code/gamespy/sake/sakeRequestInternal.h @@ -0,0 +1,65 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __SAKEREQUESTINTERNAL_H__ +#define __SAKEREQUESTINTERNAL_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sakeMain.h" +#include "../common/gsSoap.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +extern "C" { +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define SAKEI_REQUEST_SAFE_MALLOC(dest, type) SAKEI_REQUEST_SAFE_MALLOC_ARRAY(dest, type, 1) +#define SAKEI_REQUEST_SAFE_MALLOC_ARRAY(dest, type, num) {\ + dest = (type*)gsimalloc(sizeof(type)*num); /*malloc*/ \ + if(!dest) goto out_of_mem_cleanup; /*check*/ \ + memset(dest, 0, sizeof(type)*num); } /*zero*/ + +#define SAKEI_FUNC_NAME_STRINGS(func) func,\ + "SOAPAction: \"http://gamespy.net/sake/" func "\"",\ + func "Response",\ + func "Result" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct +{ + size_t mSakeOutputSize; + const char *mFuncName; + const char *mSoapAction; + const char *mResponseTag; + const char *mResultTag; + + SAKEStartRequestResult (*mValidateInputFunc)(SAKERequest request); + SAKEStartRequestResult (*mFillSoapRequestFunc)(SAKERequest request); + SAKERequestResult (*mProcessSoapResponseFunc)(SAKERequest request); + void (*mFreeDataFunc)(SAKERequest request); +} SAKEIRequestInfo; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKEStartRequestResult SAKE_CALL sakeiStartRequest(SAKERequest request, SAKEIRequestInfo * info); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} // extern "C" +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __SAKEREQUESTINTERNAL_H__ diff --git a/code/gamespy/sake/sakeRequestMisc.c b/code/gamespy/sake/sakeRequestMisc.c new file mode 100644 index 00000000..a264a871 --- /dev/null +++ b/code/gamespy/sake/sakeRequestMisc.c @@ -0,0 +1,177 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sakeRequestInternal.h" +#include "sakeRequest.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Rate Record + +static SAKEStartRequestResult SAKE_CALL sakeiRateRecordValidateInput(SAKERequest request) +{ + SAKERateRecordInput *input = (SAKERateRecordInput *)request->mInput; + + // check the tableid + if(!input->mTableId) + return SAKEStartRequestResult_BAD_TABLEID; + + return SAKEStartRequestResult_SUCCESS; +} + +static SAKEStartRequestResult SAKE_CALL sakeiRateRecordFillSoapRequest(SAKERequest request) +{ + SAKERateRecordInput *input = (SAKERateRecordInput *)request->mInput; + + // write the table id + gsXmlWriteStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "tableid", input->mTableId); + + // write the recordid + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "recordid", (gsi_u32)input->mRecordId); + + // write the rating + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "rating", (gsi_u32)input->mRating); + + return SAKEStartRequestResult_SUCCESS; +} + +static SAKERequestResult sakeiRateRecordProcessSoapResponse(SAKERequest request) +{ + SAKERateRecordOutput *output = (SAKERateRecordOutput *)request->mOutput; + + if(gsi_is_false(gsXmlReadChildAsInt(request->mSoapResponse, "numRatings", &output->mNumRatings)) || + gsi_is_false(gsXmlReadChildAsFloat(request->mSoapResponse, "averageRating", &output->mAverageRating))) + { + return SAKERequestResult_MALFORMED_RESPONSE; + } + + return SAKERequestResult_SUCCESS; +} + +SAKEStartRequestResult SAKE_CALL sakeiStartRateRecordRequest(SAKERequest request) +{ + static SAKEIRequestInfo info = + { + sizeof(SAKERateRecordOutput), + SAKEI_FUNC_NAME_STRINGS("RateRecord"), + sakeiRateRecordValidateInput, + sakeiRateRecordFillSoapRequest, + sakeiRateRecordProcessSoapResponse + }; + + return sakeiStartRequest(request, &info); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Get Record Limit + +static SAKEStartRequestResult SAKE_CALL sakeiGetRecordLimitValidateInput(SAKERequest request) +{ + SAKEGetRecordLimitInput *input = (SAKEGetRecordLimitInput *)request->mInput; + + // check the tableid + if(!input->mTableId) + return SAKEStartRequestResult_BAD_TABLEID; + + return SAKEStartRequestResult_SUCCESS; +} + +static SAKEStartRequestResult SAKE_CALL sakeiGetRecordLimitFillSoapRequest(SAKERequest request) +{ + SAKEGetRecordLimitInput *input = (SAKEGetRecordLimitInput *)request->mInput; + + // write the table id + gsXmlWriteStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "tableid", input->mTableId); + + return SAKEStartRequestResult_SUCCESS; +} + +static SAKERequestResult sakeiGetRecordLimitProcessSoapResponse(SAKERequest request) +{ + SAKEGetRecordLimitOutput *output = (SAKEGetRecordLimitOutput *)request->mOutput; + + if(gsi_is_false(gsXmlReadChildAsInt(request->mSoapResponse, "limitPerOwner", &output->mLimitPerOwner)) || + gsi_is_false(gsXmlReadChildAsInt(request->mSoapResponse, "numOwned", &output->mNumOwned))) + { + return SAKERequestResult_MALFORMED_RESPONSE; + } + + return SAKERequestResult_SUCCESS; +} + +SAKEStartRequestResult SAKE_CALL sakeiStartGetRecordLimitRequest(SAKERequest request) +{ + static SAKEIRequestInfo info = + { + sizeof(SAKEGetRecordLimitOutput), + SAKEI_FUNC_NAME_STRINGS("GetRecordLimit"), + sakeiGetRecordLimitValidateInput, + sakeiGetRecordLimitFillSoapRequest, + sakeiGetRecordLimitProcessSoapResponse + }; + + return sakeiStartRequest(request, &info); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Get Record Count + +static SAKEStartRequestResult SAKE_CALL sakeiGetRecordCountValidateInput(SAKERequest request) +{ + SAKEGetRecordCountInput *input = (SAKEGetRecordCountInput *)request->mInput; + + // check the tableid + if(!input->mTableId) + return SAKEStartRequestResult_BAD_TABLEID; + + return SAKEStartRequestResult_SUCCESS; +} + +static SAKEStartRequestResult SAKE_CALL sakeiGetRecordCountFillSoapRequest(SAKERequest request) +{ + SAKEGetRecordCountInput *input = (SAKEGetRecordCountInput *)request->mInput; + + // write the table id + gsXmlWriteStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "tableid", input->mTableId); + + // write the filter + if(input->mFilter != NULL) + gsXmlWriteTStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "filter", input->mFilter); + + // write the cache flag + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "cacheFlag", (gsi_u32)input->mCacheFlag); + + + return SAKEStartRequestResult_SUCCESS; +} + +static SAKERequestResult sakeiGetRecordCountProcessSoapResponse(SAKERequest request) +{ + SAKEGetRecordCountOutput *output = (SAKEGetRecordCountOutput *)request->mOutput; + + if(gsi_is_false(gsXmlReadChildAsInt(request->mSoapResponse, "count", &output->mCount))) + { + return SAKERequestResult_MALFORMED_RESPONSE; + } + + return SAKERequestResult_SUCCESS; +} + + +SAKEStartRequestResult SAKE_CALL sakeiStartGetRecordCountRequest(SAKERequest request) +{ + static SAKEIRequestInfo info = + { + sizeof(SAKEGetRandomRecordOutput), + SAKEI_FUNC_NAME_STRINGS("GetRecordCount"), + sakeiGetRecordCountValidateInput, + sakeiGetRecordCountFillSoapRequest, + sakeiGetRecordCountProcessSoapResponse + }; + + return sakeiStartRequest(request, &info); +} diff --git a/code/gamespy/sake/sakeRequestModify.c b/code/gamespy/sake/sakeRequestModify.c new file mode 100644 index 00000000..839e47b7 --- /dev/null +++ b/code/gamespy/sake/sakeRequestModify.c @@ -0,0 +1,290 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sakeRequestInternal.h" +#include "sakeRequest.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static SAKEStartRequestResult SAKE_CALL sakeiValidateRequestFields(SAKEField *fields, int numFields) +{ + int i; + + for(i = 0 ; i < numFields ; i++) + { + if(!fields[i].mName || !fields[i].mName[0]) + return SAKEStartRequestResult_BAD_FIELD_NAME; + if((fields[i].mType >= SAKEFieldType_NUM_FIELD_TYPES)) + return SAKEStartRequestResult_BAD_FIELD_TYPE; + if(fields[i].mType == SAKEFieldType_ASCII_STRING) + { + if(!fields[i].mValue.mAsciiString) + return SAKEStartRequestResult_BAD_FIELD_VALUE; + } + if(fields[i].mType == SAKEFieldType_UNICODE_STRING) + { + if(!fields[i].mValue.mUnicodeString) + return SAKEStartRequestResult_BAD_FIELD_VALUE; + } + if(fields[i].mType == SAKEFieldType_BINARY_DATA) + { + if(fields[i].mValue.mBinaryData.mLength < 0) + return SAKEStartRequestResult_BAD_FIELD_VALUE; + if(!fields[i].mValue.mBinaryData.mValue && (fields[i].mValue.mBinaryData.mLength > 0)) + return SAKEStartRequestResult_BAD_FIELD_VALUE; + } + } + + return SAKEStartRequestResult_SUCCESS; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void SAKE_CALL sakeiFillSoapRequestFieldValues(SAKERequest request, SAKEField *fields, int numFields) +{ + int i; + + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "values"); + + for(i = 0 ; i < numFields ; i++) + { + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "RecordField"); + gsXmlWriteStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "name", fields[i].mName); + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "value"); + + // fill-in the type-specific value struct based on the type + if(fields[i].mType == SAKEFieldType_BYTE) + { + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "byteValue"); + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "value", fields[i].mValue.mByte); + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "byteValue"); + } + else if(fields[i].mType == SAKEFieldType_SHORT) + { + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "shortValue"); + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "value", fields[i].mValue.mShort); + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "shortValue"); + } + else if(fields[i].mType == SAKEFieldType_INT) + { + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "intValue"); + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "value", (gsi_u32)fields[i].mValue.mInt); + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "intValue"); + } + else if (fields[i].mType == SAKEFieldType_INT64) + { + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "int64Value"); + gsXmlWriteInt64Element(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "value", fields[i].mValue.mInt64); + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "int64Value"); + } + else if(fields[i].mType == SAKEFieldType_FLOAT) + { + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "floatValue"); + gsXmlWriteFloatElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "value", fields[i].mValue.mFloat); + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "floatValue"); + } + else if(fields[i].mType == SAKEFieldType_ASCII_STRING) + { + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "asciiStringValue"); + gsXmlWriteStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "value", fields[i].mValue.mAsciiString); + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "asciiStringValue"); + } + else if(fields[i].mType == SAKEFieldType_UNICODE_STRING) + { + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "unicodeStringValue"); + gsXmlWriteUnicodeStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "value", fields[i].mValue.mUnicodeString); + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "unicodeStringValue"); + } + else if(fields[i].mType == SAKEFieldType_BOOLEAN) + { + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "booleanValue"); + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "value", (gsi_u32)(gsi_is_false(fields[i].mValue.mBoolean)?0:1)); + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "booleanValue"); + } + else if(fields[i].mType == SAKEFieldType_DATE_AND_TIME) + { + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "dateAndTimeValue"); + gsXmlWriteDateTimeElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "value", fields[i].mValue.mDateAndTime); + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "dateAndTimeValue"); + } + else if(fields[i].mType == SAKEFieldType_BINARY_DATA) + { + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "binaryDataValue"); + gsXmlWriteBase64BinaryElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "value", + fields[i].mValue.mBinaryData.mValue, fields[i].mValue.mBinaryData.mLength); + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "binaryDataValue"); + } + else + { + // a type isn't being handled + GS_FAIL_STR("Unhandled field type"); + } + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "value"); + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "RecordField"); + } + + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "values"); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Create Record + +static SAKEStartRequestResult SAKE_CALL sakeiCreateRecordValidateInput(SAKERequest request) +{ + SAKECreateRecordInput *input = (SAKECreateRecordInput *)request->mInput; + + // check the tableid + if(!input->mTableId) + return SAKEStartRequestResult_BAD_TABLEID; + + // check the num fields + if(input->mNumFields < 0) + return SAKEStartRequestResult_BAD_NUM_FIELDS; + + // check for NULL fields + if(!input->mFields && (input->mNumFields > 0)) + return SAKEStartRequestResult_BAD_FIELDS; + + // check the fields + return sakeiValidateRequestFields(input->mFields, input->mNumFields); +} + +static SAKEStartRequestResult SAKE_CALL sakeiCreateRecordFillSoapRequest(SAKERequest request) +{ + SAKECreateRecordInput *input = (SAKECreateRecordInput *)request->mInput; + + // write the table id + gsXmlWriteStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "tableid", input->mTableId); + + // fill in the field values + sakeiFillSoapRequestFieldValues(request, input->mFields, input->mNumFields); + + return SAKEStartRequestResult_SUCCESS; +} + +static SAKERequestResult sakeiCreateRecordProcessSoapResponse(SAKERequest request) +{ + SAKECreateRecordOutput *output = (SAKECreateRecordOutput *)request->mOutput; + + if(gsi_is_false(gsXmlReadChildAsInt(request->mSoapResponse, "recordid", &output->mRecordId))) + { + return SAKERequestResult_MALFORMED_RESPONSE; + } + + return SAKERequestResult_SUCCESS; +} + +SAKEStartRequestResult SAKE_CALL sakeiStartCreateRecordRequest(SAKERequest request) +{ + static SAKEIRequestInfo info = + { + sizeof(SAKECreateRecordOutput), + SAKEI_FUNC_NAME_STRINGS("CreateRecord"), + sakeiCreateRecordValidateInput, + sakeiCreateRecordFillSoapRequest, + sakeiCreateRecordProcessSoapResponse + }; + + return sakeiStartRequest(request, &info); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Update Record + +static SAKEStartRequestResult SAKE_CALL sakeiUpdateRecordValidateInput(SAKERequest request) +{ + SAKEUpdateRecordInput *input = (SAKEUpdateRecordInput *)request->mInput; + + // check the tableid + if(!input->mTableId) + return SAKEStartRequestResult_BAD_TABLEID; + + // check the num fields + if(input->mNumFields <= 0) + return SAKEStartRequestResult_BAD_NUM_FIELDS; + + // check for NULL fields + if(!input->mFields) + return SAKEStartRequestResult_BAD_FIELDS; + + // check the fields + return sakeiValidateRequestFields(input->mFields, input->mNumFields); +} + +static SAKEStartRequestResult SAKE_CALL sakeiUpdateRecordFillSoapRequest(SAKERequest request) +{ + SAKEUpdateRecordInput *input = (SAKEUpdateRecordInput *)request->mInput; + + // write the table id + gsXmlWriteStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "tableid", input->mTableId); + + // write the recordid + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "recordid", (gsi_u32)input->mRecordId); + + // fill in the field values + sakeiFillSoapRequestFieldValues(request, input->mFields, input->mNumFields); + + return SAKEStartRequestResult_SUCCESS; +} + +SAKEStartRequestResult SAKE_CALL sakeiStartUpdateRecordRequest(SAKERequest request) +{ + static SAKEIRequestInfo info = + { + 0, + SAKEI_FUNC_NAME_STRINGS("UpdateRecord"), + sakeiUpdateRecordValidateInput, + sakeiUpdateRecordFillSoapRequest, + NULL + }; + + return sakeiStartRequest(request, &info); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Delete Record + +static SAKEStartRequestResult SAKE_CALL sakeiDeleteRecordValidateInput(SAKERequest request) +{ + SAKEDeleteRecordInput *input = (SAKEDeleteRecordInput *)request->mInput; + + // check the tableid + if(!input->mTableId) + return SAKEStartRequestResult_BAD_TABLEID; + + return SAKEStartRequestResult_SUCCESS; +} + +static SAKEStartRequestResult SAKE_CALL sakeiDeleteRecordFillSoapRequest(SAKERequest request) +{ + SAKEDeleteRecordInput *input = (SAKEDeleteRecordInput *)request->mInput; + + // write the table id + gsXmlWriteStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "tableid", input->mTableId); + + // write the recordid + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "recordid", (gsi_u32)input->mRecordId); + + return SAKEStartRequestResult_SUCCESS; +} + +SAKEStartRequestResult SAKE_CALL sakeiStartDeleteRecordRequest(SAKERequest request) +{ + static SAKEIRequestInfo info = + { + 0, + SAKEI_FUNC_NAME_STRINGS("DeleteRecord"), + sakeiDeleteRecordValidateInput, + sakeiDeleteRecordFillSoapRequest, + NULL + }; + + return sakeiStartRequest(request, &info); +} diff --git a/code/gamespy/sake/sakeRequestRead.c b/code/gamespy/sake/sakeRequestRead.c new file mode 100644 index 00000000..15772be3 --- /dev/null +++ b/code/gamespy/sake/sakeRequestRead.c @@ -0,0 +1,674 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sakeRequestInternal.h" +#include "sakeRequest.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static SAKEStartRequestResult SAKE_CALL sakeiValidateRequestFieldNames(char **fields, int numFields) +{ + int i; + + for(i = 0 ; i < numFields ; i++) + { + if(!fields[i] || !fields[i][0]) + return SAKEStartRequestResult_BAD_FIELD_NAME; + } + + return SAKEStartRequestResult_SUCCESS; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void SAKE_CALL sakeiFillSoapRequestFieldNames(SAKERequest request, char **names, int numFields) +{ + int i; + + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "fields"); + + for(i = 0 ; i < numFields ; i++) + { + gsXmlWriteStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "string", names[i]); + } + + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "fields"); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void SAKE_CALL sakeiFillSoapRequestRecordIds(SAKERequest request, int *recordIds, int numRecordIds) +{ + int i; + + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "recordids"); + + for(i = 0 ; i < numRecordIds ; i++) + { + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "int", (gsi_u32)recordIds[i]); + } + + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "recordids"); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void SAKE_CALL sakeiFillSoapRequestOwnerIds(SAKERequest request, int *ownerIds, int numOwnerIds) +{ + int i; + + gsXmlWriteOpenTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "ownerids"); + + for(i = 0 ; i < numOwnerIds ; i++) + { + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "int", (gsi_u32)ownerIds[i]); + } + + gsXmlWriteCloseTag(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "ownerids"); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static SAKERequestResult SAKE_CALL sakeiReadOutputRecords(SAKERequest request, SAKEField ***outputRecordsPtr, int *numRecords, + int numFields, char ** fieldNames) + +{ + SAKEField **outputRecords; + SAKEField *outputRecord; + SAKEField *outputField; + int recordIndex; + int fieldIndex; + size_t size; + + // get to the start of the values + if(gsi_is_false(gsXmlMoveToChild(request->mSoapResponse, "values"))) + return SAKERequestResult_MALFORMED_RESPONSE; + + // count the number of records + *numRecords = gsXmlCountChildren(request->mSoapResponse, "ArrayOfRecordValue"); + + // check for no records + if(*numRecords == 0) + { + *outputRecordsPtr = NULL; + return SAKERequestResult_SUCCESS; + } + + // allocate the array of records + size = (sizeof(SAKEField*) * *numRecords); + outputRecords = (SAKEField**)gsimalloc(size); + if(!outputRecords) + return SAKERequestResult_OUT_OF_MEMORY; + memset(outputRecords, 0, size); + *outputRecordsPtr = outputRecords; + + // loop through the records + for(recordIndex = 0 ; recordIndex < *numRecords ; recordIndex++) + { + // advance to this record + if(gsi_is_false(gsXmlMoveToNext(request->mSoapResponse, "ArrayOfRecordValue"))) + return SAKERequestResult_MALFORMED_RESPONSE; + + // allocate the array of record fields + size = (sizeof(SAKEField) * numFields); + outputRecord = (SAKEField*)gsimalloc(size); + if(!outputRecord) + return SAKERequestResult_OUT_OF_MEMORY; + memset(outputRecord, 0, size); + outputRecords[recordIndex] = outputRecord; + + // check for the wrong number of fields in the response + if(gsXmlCountChildren(request->mSoapResponse, "RecordValue") != numFields) + return SAKERequestResult_MALFORMED_RESPONSE; + + // fill in the array of fields for this record + for(fieldIndex = 0 ; fieldIndex < numFields ; fieldIndex++) + { + // utility pointer + outputField = &outputRecord[fieldIndex]; + + // set the name for this field + outputField->mName = fieldNames[fieldIndex]; + + // move to this field + if(gsi_is_false(gsXmlMoveToNext(request->mSoapResponse, "RecordValue"))) + return SAKERequestResult_MALFORMED_RESPONSE; + + // set the type and value based on the response field + if(gsXmlMoveToChild(request->mSoapResponse, "byteValue")) + { + int value; + if(gsi_is_false(gsXmlReadChildAsInt(request->mSoapResponse, "value", &value))) + return SAKERequestResult_MALFORMED_RESPONSE; + outputField->mType = SAKEFieldType_BYTE; + outputField->mValue.mByte = (gsi_u8)value; + } + else if(gsXmlMoveToChild(request->mSoapResponse, "shortValue")) + { + int value; + if(gsi_is_false(gsXmlReadChildAsInt(request->mSoapResponse, "value", &value))) + return SAKERequestResult_MALFORMED_RESPONSE; + outputField->mType = SAKEFieldType_SHORT; + outputField->mValue.mShort = (gsi_i16)value; + } + else if(gsXmlMoveToChild(request->mSoapResponse, "intValue")) + { + int value; + if(gsi_is_false(gsXmlReadChildAsInt(request->mSoapResponse, "value", &value))) + return SAKERequestResult_MALFORMED_RESPONSE; + outputField->mType = SAKEFieldType_INT; + outputField->mValue.mInt = (gsi_i32)value; + } + else if (gsXmlMoveToChild(request->mSoapResponse, "int64Value")) + { + gsi_i64 value; + if (gsi_is_false(gsXmlReadChildAsInt64(request->mSoapResponse, "value", &value))) + return SAKERequestResult_MALFORMED_RESPONSE; + outputField->mType = SAKEFieldType_INT64; + outputField->mValue.mInt64 = value; + } + else if(gsXmlMoveToChild(request->mSoapResponse, "floatValue")) + { + float value; + if(gsi_is_false(gsXmlReadChildAsFloat(request->mSoapResponse, "value", &value))) + return SAKERequestResult_MALFORMED_RESPONSE; + outputField->mType = SAKEFieldType_FLOAT; + outputField->mValue.mFloat = value; + } + else if(gsXmlMoveToChild(request->mSoapResponse, "asciiStringValue")) + { + char *value; + int len; + if(gsi_is_false(gsXmlReadChildAsString(request->mSoapResponse, "value", (const char**)&value, &len))) + return SAKERequestResult_MALFORMED_RESPONSE; + if(value) + value[len] = '\0'; + else + value = ""; + outputField->mType = SAKEFieldType_ASCII_STRING; + outputField->mValue.mAsciiString = value; + } + else if(gsXmlMoveToChild(request->mSoapResponse, "unicodeStringValue")) + { + char *value; + gsi_u16 *valueUnicode; + int len; + if(gsi_is_false(gsXmlReadChildAsString(request->mSoapResponse, "value", (const char**)&value, &len))) + return SAKERequestResult_MALFORMED_RESPONSE; + if(value) + value[len] = '\0'; + else + value = ""; + valueUnicode = UTF8ToUCS2StringAlloc(value); + if(!valueUnicode) + return SAKERequestResult_OUT_OF_MEMORY; + outputField->mType = SAKEFieldType_UNICODE_STRING; + outputField->mValue.mUnicodeString = valueUnicode; + } + else if(gsXmlMoveToChild(request->mSoapResponse, "booleanValue")) + { + char *value; + int len; + gsi_bool boolval; + if(gsi_is_false(gsXmlReadChildAsString(request->mSoapResponse, "value", (const char**)&value, &len))) + return SAKERequestResult_MALFORMED_RESPONSE; + if(value) + { + value[len] = '\0'; + boolval = (strcmp(value,"true") == 0)?gsi_true:gsi_false; + } + else + boolval = gsi_false; //if returned a NULL value, set bool to false + outputField->mType = SAKEFieldType_BOOLEAN; + outputField->mValue.mBoolean = boolval; + } + else if(gsXmlMoveToChild(request->mSoapResponse, "dateAndTimeValue")) + { + time_t value; + if(gsi_is_false(gsXmlReadChildAsDateTimeElement(request->mSoapResponse, "value", &value))) + return SAKERequestResult_MALFORMED_RESPONSE; + outputField->mType = SAKEFieldType_DATE_AND_TIME; + outputField->mValue.mDateAndTime = value; + } + else if(gsXmlMoveToChild(request->mSoapResponse, "binaryDataValue")) + { + gsi_u8 *value; + int len; + if(gsi_is_false(gsXmlReadChildAsBase64Binary(request->mSoapResponse, "value", NULL, &len))) + return SAKERequestResult_MALFORMED_RESPONSE; + if(len > 0) + { + value = (gsi_u8*)gsimalloc((size_t)len); + if(!value) + return SAKERequestResult_OUT_OF_MEMORY; + if(gsi_is_false(gsXmlReadChildAsBase64Binary(request->mSoapResponse, "value", value, &len))) + { + gsifree(value); + return SAKERequestResult_MALFORMED_RESPONSE; + } + } + else + { + value = NULL; + len = 0; + } + outputField->mType = SAKEFieldType_BINARY_DATA; + outputField->mValue.mBinaryData.mLength = len; + outputField->mValue.mBinaryData.mValue = value; + } + else + { + GS_FAIL_STR("No recognized field type found in RecordValue"); + return SAKERequestResult_UNKNOWN_ERROR; + } + } + } + + return SAKERequestResult_SUCCESS; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void SAKE_CALL sakeiFreeOutputRecord(int numFields, SAKEField *record) +{ + int i; + + if(!record) + return; + + + //Check for binary data or unicode strings and free it if necessary + for (i = 0; i < numFields; i++) + { + if (record[i].mType == SAKEFieldType_BINARY_DATA && record[i].mValue.mBinaryData.mValue != NULL) + gsifree(record[i].mValue.mBinaryData.mValue); + if (record[i].mType == SAKEFieldType_UNICODE_STRING) + gsifree(record[i].mValue.mUnicodeString); + } + gsifree(record); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void SAKE_CALL sakeiFreeOutputRecords(int numFields, int numRecords, SAKEField **records) +{ + int i,j; + + if(!records) + return; + + for(i = 0 ; i < numRecords ; i++) + { + //Check for binary data or unicode strings and free it if necessary + for (j = 0; j < numFields; j++) + { + if (records[i][j].mType == SAKEFieldType_BINARY_DATA && records[i][j].mValue.mBinaryData.mValue != NULL) + gsifree(records[i][j].mValue.mBinaryData.mValue); + if (records[i][j].mType == SAKEFieldType_UNICODE_STRING) + gsifree(records[i][j].mValue.mUnicodeString); + } + + gsifree(records[i]); + } + gsifree(records); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Search For Records + +static SAKEStartRequestResult SAKE_CALL sakeiSearchForRecordsValidateInput(SAKERequest request) +{ + SAKESearchForRecordsInput *input = (SAKESearchForRecordsInput *)request->mInput; + + // check the tableid + if(!input->mTableId) + return SAKEStartRequestResult_BAD_TABLEID; + + // check the num fields + if(input->mNumFields <= 0) + return SAKEStartRequestResult_BAD_NUM_FIELDS; + + // check for NULL field names + if(!input->mFieldNames) + return SAKEStartRequestResult_BAD_FIELDS; + + // check the offset + if(input->mOffset < 0) + return SAKEStartRequestResult_BAD_OFFSET; + + // check the max + if(input->mMaxRecords <= 0) + return SAKEStartRequestResult_BAD_MAX; + + // check the field names + return sakeiValidateRequestFieldNames(input->mFieldNames, input->mNumFields); +} + +static SAKEStartRequestResult SAKE_CALL sakeiSearchForRecordsFillSoapRequest(SAKERequest request) +{ + SAKESearchForRecordsInput *input = (SAKESearchForRecordsInput *)request->mInput; + + // write the table id + gsXmlWriteStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "tableid", input->mTableId); + + // write the filter + if(input->mFilter != NULL) + gsXmlWriteTStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "filter", input->mFilter); + + // write the sort + if(input->mSort != NULL) + gsXmlWriteStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "sort", input->mSort); + + // write the offset + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "offset", (gsi_u32)input->mOffset); + + // write the max + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "max", (gsi_u32)input->mMaxRecords); + + // write the target record filter + if(input->mTargetRecordFilter != NULL) + gsXmlWriteTStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "targetfilter", input->mTargetRecordFilter); + + // write the surrounding record count + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "surrounding", (gsi_u32)input->mSurroundingRecordsCount); + + // fill in the ownerids + sakeiFillSoapRequestOwnerIds(request, input->mOwnerIds, input->mNumOwnerIds); + + // write the cache flag + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "cacheFlag", (gsi_u32)input->mCacheFlag); + + + // fill in the field names + sakeiFillSoapRequestFieldNames(request, input->mFieldNames, input->mNumFields); + + return SAKEStartRequestResult_SUCCESS; +} + +static SAKERequestResult sakeiSearchForRecordsProcessSoapResponse(SAKERequest request) +{ + SAKESearchForRecordsInput *input = (SAKESearchForRecordsInput *)request->mInput; + SAKESearchForRecordsOutput *output = (SAKESearchForRecordsOutput *)request->mOutput; + + // fill the output records + return sakeiReadOutputRecords(request, &output->mRecords, &output->mNumRecords, input->mNumFields, input->mFieldNames); +} + +static void sakeiSearchForRecordsFreeData(SAKERequest request) +{ + SAKESearchForRecordsInput *input = (SAKESearchForRecordsInput *)request->mInput; + SAKESearchForRecordsOutput *output = (SAKESearchForRecordsOutput *)request->mOutput; + + if(output) + sakeiFreeOutputRecords(input->mNumFields, output->mNumRecords, output->mRecords); +} + +SAKEStartRequestResult SAKE_CALL sakeiStartSearchForRecordsRequest(SAKERequest request) +{ + static SAKEIRequestInfo info = + { + sizeof(SAKESearchForRecordsOutput), + SAKEI_FUNC_NAME_STRINGS("SearchForRecords"), + sakeiSearchForRecordsValidateInput, + sakeiSearchForRecordsFillSoapRequest, + sakeiSearchForRecordsProcessSoapResponse, + sakeiSearchForRecordsFreeData + }; + + return sakeiStartRequest(request, &info); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Get My Records + +static SAKEStartRequestResult SAKE_CALL sakeiGetMyRecordsValidateInput(SAKERequest request) +{ + SAKEGetMyRecordsInput *input = (SAKEGetMyRecordsInput *)request->mInput; + + // check the tableid + if(!input->mTableId) + return SAKEStartRequestResult_BAD_TABLEID; + + // check the num fields + if(input->mNumFields <= 0) + return SAKEStartRequestResult_BAD_NUM_FIELDS; + + // check for NULL field names + if(!input->mFieldNames) + return SAKEStartRequestResult_BAD_FIELDS; + + // check the field names + return sakeiValidateRequestFieldNames(input->mFieldNames, input->mNumFields); +} + +static SAKEStartRequestResult SAKE_CALL sakeiGetMyRecordsFillSoapRequest(SAKERequest request) +{ + SAKEGetMyRecordsInput *input = (SAKEGetMyRecordsInput *)request->mInput; + + // write the table id + gsXmlWriteStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "tableid", input->mTableId); + + // fill in the field names + sakeiFillSoapRequestFieldNames(request, input->mFieldNames, input->mNumFields); + + return SAKEStartRequestResult_SUCCESS; +} + +static SAKERequestResult sakeiGetMyRecordsProcessSoapResponse(SAKERequest request) +{ + SAKEGetMyRecordsInput *input = (SAKEGetMyRecordsInput *)request->mInput; + SAKEGetMyRecordsOutput *output = (SAKEGetMyRecordsOutput *)request->mOutput; + + // fill the output records + return sakeiReadOutputRecords(request, &output->mRecords, &output->mNumRecords, input->mNumFields, input->mFieldNames); +} + +static void sakeiGetMyRecordsFreeData(SAKERequest request) +{ + SAKEGetMyRecordsInput *input = (SAKEGetMyRecordsInput *)request->mInput; + SAKEGetMyRecordsOutput *output = (SAKEGetMyRecordsOutput *)request->mOutput; + + if(output) + sakeiFreeOutputRecords(input->mNumFields, output->mNumRecords, output->mRecords); +} + +SAKEStartRequestResult SAKE_CALL sakeiStartGetMyRecordsRequest(SAKERequest request) +{ + static SAKEIRequestInfo info = + { + sizeof(SAKEGetMyRecordsOutput), + SAKEI_FUNC_NAME_STRINGS("GetMyRecords"), + sakeiGetMyRecordsValidateInput, + sakeiGetMyRecordsFillSoapRequest, + sakeiGetMyRecordsProcessSoapResponse, + sakeiGetMyRecordsFreeData + }; + + return sakeiStartRequest(request, &info); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Get Specific Records + +static SAKEStartRequestResult SAKE_CALL sakeiGetSpecificRecordsValidateInput(SAKERequest request) +{ + SAKEGetSpecificRecordsInput *input = (SAKEGetSpecificRecordsInput *)request->mInput; + + // check the tableid + if(!input->mTableId) + return SAKEStartRequestResult_BAD_TABLEID; + + // check the num recordids + if(input->mNumRecordIds <= 0) + return SAKEStartRequestResult_BAD_NUM_RECORDIDS; + + // check the recordids + if(!input->mRecordIds) + return SAKEStartRequestResult_BAD_RECORDIDS; + + // check the num fields + if(input->mNumFields <= 0) + return SAKEStartRequestResult_BAD_NUM_FIELDS; + + // check for NULL field names + if(!input->mFieldNames) + return SAKEStartRequestResult_BAD_FIELDS; + + // check the field names + return sakeiValidateRequestFieldNames(input->mFieldNames, input->mNumFields); +} + +static SAKEStartRequestResult SAKE_CALL sakeiGetSpecificRecordsFillSoapRequest(SAKERequest request) +{ + SAKEGetSpecificRecordsInput *input = (SAKEGetSpecificRecordsInput *)request->mInput; + + // write the table id + gsXmlWriteStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "tableid", input->mTableId); + + // fill in the recordids + sakeiFillSoapRequestRecordIds(request, input->mRecordIds, input->mNumRecordIds); + + // fill in the field names + sakeiFillSoapRequestFieldNames(request, input->mFieldNames, input->mNumFields); + + return SAKEStartRequestResult_SUCCESS; +} + +static SAKERequestResult sakeiGetSpecificRecordsProcessSoapResponse(SAKERequest request) +{ + SAKEGetSpecificRecordsInput *input = (SAKEGetSpecificRecordsInput *)request->mInput; + SAKEGetSpecificRecordsOutput *output = (SAKEGetSpecificRecordsOutput *)request->mOutput; + + // fill the output records + return sakeiReadOutputRecords(request, &output->mRecords, &output->mNumRecords, input->mNumFields, input->mFieldNames); +} + +static void sakeiGetSpecificRecordsFreeData(SAKERequest request) +{ + SAKEGetSpecificRecordsInput *input = (SAKEGetSpecificRecordsInput *)request->mInput; + SAKEGetSpecificRecordsOutput *output = (SAKEGetSpecificRecordsOutput *)request->mOutput; + + if(output) + sakeiFreeOutputRecords(input->mNumFields, output->mNumRecords, output->mRecords); +} + +SAKEStartRequestResult SAKE_CALL sakeiStartGetSpecificRecordsRequest(SAKERequest request) +{ + static SAKEIRequestInfo info = + { + sizeof(SAKEGetSpecificRecordsOutput), + SAKEI_FUNC_NAME_STRINGS("GetSpecificRecords"), + sakeiGetSpecificRecordsValidateInput, + sakeiGetSpecificRecordsFillSoapRequest, + sakeiGetSpecificRecordsProcessSoapResponse, + sakeiGetSpecificRecordsFreeData + }; + + return sakeiStartRequest(request, &info); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Get Random Record + +static SAKEStartRequestResult SAKE_CALL sakeiGetRandomRecordValidateInput(SAKERequest request) +{ + SAKEGetRandomRecordInput *input = (SAKEGetRandomRecordInput *)request->mInput; + + // check the tableid + if(!input->mTableId) + return SAKEStartRequestResult_BAD_TABLEID; + + // check the num fields + if(input->mNumFields <= 0) + return SAKEStartRequestResult_BAD_NUM_FIELDS; + + // check for NULL field names + if(!input->mFieldNames) + return SAKEStartRequestResult_BAD_FIELDS; + + // check the field names + return sakeiValidateRequestFieldNames(input->mFieldNames, input->mNumFields); +} + +static SAKEStartRequestResult SAKE_CALL sakeiGetRandomRecordFillSoapRequest(SAKERequest request) +{ + SAKEGetRandomRecordInput *input = (SAKEGetRandomRecordInput *)request->mInput; + + // write the table id + gsXmlWriteStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "tableid", input->mTableId); + + // write the filter + if(input->mFilter != NULL) + gsXmlWriteTStringElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "filter", input->mFilter); + + // write the max + gsXmlWriteIntElement(request->mSoapRequest, GSI_SAKE_SERVICE_NAMESPACE, "max", 1); + + // fill in the field names + sakeiFillSoapRequestFieldNames(request, input->mFieldNames, input->mNumFields); + + return SAKEStartRequestResult_SUCCESS; +} + +static SAKERequestResult sakeiGetRandomRecordProcessSoapResponse(SAKERequest request) +{ + SAKEGetRandomRecordInput *input = (SAKEGetRandomRecordInput *)request->mInput; + SAKEGetRandomRecordOutput *output = (SAKEGetRandomRecordOutput *)request->mOutput; + SAKEField **records; + int numRecords; + + // fill the output record + SAKERequestResult result = sakeiReadOutputRecords(request, &records, &numRecords, input->mNumFields, input->mFieldNames); + if(result == SAKERequestResult_SUCCESS) + { + if((records != NULL) && (numRecords > 0)) + output->mRecord = records[0]; + else + output->mRecord = NULL; + } + //free up the outer record pointer + gsifree(records); + + return result; +} + +static void sakeiGetRandomRecordFreeData(SAKERequest request) +{ + SAKEGetRandomRecordInput *input = (SAKEGetRandomRecordInput *)request->mInput; + SAKEGetRandomRecordOutput *output = (SAKEGetRandomRecordOutput *)request->mOutput; + + if(output) + sakeiFreeOutputRecord(input->mNumFields, output->mRecord); + +} + +SAKEStartRequestResult SAKE_CALL sakeiStartGetRandomRecordRequest(SAKERequest request) +{ + static SAKEIRequestInfo info = + { + sizeof(SAKEGetRandomRecordOutput), + SAKEI_FUNC_NAME_STRINGS("GetRandomRecords"), + sakeiGetRandomRecordValidateInput, + sakeiGetRandomRecordFillSoapRequest, + sakeiGetRandomRecordProcessSoapResponse, + sakeiGetRandomRecordFreeData + }; + + return sakeiStartRequest(request, &info); +} + + diff --git a/code/gamespy/sc/changelog.txt b/code/gamespy/sc/changelog.txt new file mode 100644 index 00000000..e034643f --- /dev/null +++ b/code/gamespy/sc/changelog.txt @@ -0,0 +1,50 @@ +Changelog for: GameSpy Competition SDK +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 1.04.00 RMV RELEASE Released to Developer Site +12-07-2007 1.03.01 SAH OTHER Fixed projects missing dependencies +12-03-2007 1.03.00 SN FEATURE Added Int64 support for reports +08-06-2007 1.02.00 RMV RELEASE Released to Developer Site +07-18-2007 1.01.01 RMV OTHER Created sctestmatchless projects for multiple platforms +07-17-2007 1.01.01 RMV FIX Fixed compiler warnings for PSP test project +06-15-2007 1.01.00 BMS FEATURE Updated for "matchless" ATLAS. +04-17-2007 1.00.02 SAH FEATURE Added scReportAddFloatValue + 1.00.02 SAH OTHER Changed sctest for easier testing of AuthService +03-16-2007 1.00.01 SAH FIX Fixed an issue where team indexes were not properly retrieved based on the team ID. +03-06-2007 1.00.00 SAH RELEASE Released to Developer Site +03-06-2007 0.09.08 SAH FIX Added SCResult_NO_AVAILABILITY_CHECK for unperformed checks made prior to init + 0.09.08 SAH FIX Modified sctest to include availability check. + 0.09.08 SAH FIX Fixed scServiceURL to construct the proper URL based on the __GSIACGamename +03-02-2007 0.09.07 SAH FIX Added Linux, MACOSX makefiles and Nintendo DS project +03-02-2007 0.09.06 SN FIX Fixed some compiler warnings for code warrior + SN FIX Added checks for consoles in sctest.c after testing terminates +03-02-2007 0.09.06 SN FIX Fixed an alignment issue for sctest.c on the ps2 +03-02-2007 0.09.05 SN FIX Fixed compiler warnings for sctest.c +02-28-2007 0.09.04 SAH FIX Fixed an assert, added ghttpPost.h for extern function call + 0.09.04 SAH FIX Added ScRaceSample to VS2005 project, updated sample to use AdminSite header +02-27-2007 0.09.03 SAH FEATURE Created ScRaceSample application and added it to the project +02-22-2007 0.09.02 SN FEATURE Added code to calculate an MD5 hash on the binary report before sending it + 0.09.02 SN FIX Fixed the report version number + 0.09.02 SN FIX Modified sctest to have each player submit its own snapsnot +02-21-2007 0.09.01 SAH FEATURE Added SCGameStatus enum, gamestatus is now set when calling scReportEnd + 0.09.01 SAH FIX Removed all references to isFinal, no longer used +01-31-2007 0.09.00 SAH FEATURE Added scReportAddShortValue / scReportAddByteValue + 0.09.00 SAH FIX Fixed protocol header length define for rosterSection +01-31-2007 0.08.00 SAH FEATURE TeamIndex now takes in an arena teamID (or a local index for games that don't plan to use arena) + 0.08.00 SAH FEATURE Fixed a bug where the result enum was improperly casted, causing win/loss mismatch +01-22-2007 0.07.00 SAH FIX Increasing version number to remove confusion + 0.07.00 SAH FIX Fixed Unicode issues as well as the addString function + 0.07.00 SAH FIX Added Unicode configuration in Project + 0.07.00 SAH FIX Added String tests in scTest.c +01-16-2007 0.04.03 DES FEATURE Added X360 support +01-04-2007 0.04.02 SN FIX Changed URLs to release backend +12-18-2006 0.04.01 SN FIX Added PS2, PS3, PSP projects +12-18-2006 0.04.01 SN FEATURE Added scDestroyReport for freeing reports + SN FIX Fixed sdk shutdown code, and any warnings and errors that came up when compiling as C++ code + SN FIX Updated sctest to use latest sdk interface and fixed some issues with building the report +12-15-2006 0.04.00 MJW RELEASE Released to Developer Site +12-05-2006 0.03.01 SN FIX Fixed errors and warnings on all platforms slated for release +10-20-2006 0.03.00 BED RELEASE Aplha update + FEATURE Added User data for callbacks diff --git a/code/gamespy/sc/sc.h b/code/gamespy/sc/sc.h new file mode 100644 index 00000000..145f580d --- /dev/null +++ b/code/gamespy/sc/sc.h @@ -0,0 +1,314 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __SC_H__ +#define __SC_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../common/gsCommon.h" +#include "../common/gsRC4.h" +#include "../common/gsAvailable.h" +#include "../ghttp/ghttp.h" +#include "../webservices/AuthService.h" + + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// optional to explicitly use __stdcall, __cdecl, __fastcall +#define SC_CALL + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Set this to define memory settings for the SDK +#define SC_STATIC_MEM + +// The initial (or fixed, for static memory) report buffer size +#define SC_REPORT_BUFFER_BYTES 65536 + +// URL for sc services. +#define SC_SERVICE_MAX_URL_LEN 128 +extern char scServiceURL[SC_SERVICE_MAX_URL_LEN]; + +// Session GUID size - must match backend +//#define SC_SESSION_GUID_SIZE 16 +#define SC_AUTHDATA_SIZE 16 +#define SC_SESSION_GUID_SIZE 40 +#define SC_CONNECTION_GUID_SIZE 40 + +#define SC_GUID_BINARY_SIZE 16 // convert the 40 byte string guid into an int, 2 shorts and 8 bytes + +// Limit to the number of teams +#define SC_MAX_NUM_TEAMS 64 + +// OPTIONS flags - first two bits reserved for authoritative / final flags +#define SC_OPTIONS_NONE 0 + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Result codes +typedef enum +{ + SCResult_NO_ERROR = 0, + SCResult_NO_AVAILABILITY_CHECK, + SCResult_INVALID_PARAMETERS, + SCResult_NOT_INITIALIZED, + SCResult_CORE_NOT_INITIALIZED, + SCResult_OUT_OF_MEMORY, + SCResult_CALLBACK_PENDING, + + SCResult_HTTP_ERROR, + SCResult_UNKNOWN_RESPONSE, // server reported an error which is unknown to us + SCResult_RESPONSE_INVALID, + + SCResult_REPORT_INCOMPLETE, + SCResult_REPORT_INVALID, + SCResult_SUBMISSION_FAILED, + + SCResult_UNKNOWN_ERROR, + + SCResultMax +} SCResult; + + +// Game Results +typedef enum +{ + SCGameResult_WIN, + SCGameResult_LOSS, + SCGameResult_DRAW, + SCGameResult_DISCONNECT, + SCGameResult_DESYNC, + SCGameResult_NONE, + + SCGameResultMax +} SCGameResult; + + +// Game Status +typedef enum +{ + SCGameStatus_COMPLETE, + SCGameStatus_PARTIAL, + SCGameStatus_BROKEN, + + SCGameStatusMax +} SCGameStatus; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Data types +typedef void* SCInterfacePtr; +typedef void* SCReportPtr; + + +//typedef gsi_u32 SCTeamCount; +//typedef gsi_u32 SCTeamIndex; + +//typedef gsi_u32 SCPlayerCount; +//typedef gsi_u32 SCPlayerIndex; + +//typedef gsi_u16 SCKey; + +typedef char SCHiddenData[64]; + +//typedef enum +//{ +// SCReportKeyType_SERVER, +// SCReportKeyType_TEAM, +// SCReportKeyType_PLAYER +//} SCReportKeyType; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Callbacks +typedef void (*SCCreateSessionCallback)(const SCInterfacePtr theInterface, + GHTTPResult theHttpResult, + SCResult theResult, + void * theUserData); +typedef void (*SCSetReportIntentionCallback)(const SCInterfacePtr theInterface, + GHTTPResult theHttpResult, + SCResult theResult, + void * theUserData); +typedef void (*SCSubmitReportCallback)(const SCInterfacePtr theInterface, + GHTTPResult theHttpResult, + SCResult theResult, + void * theUserData); + +/* +typedef void (*SCKeyCallback) (SCReportPtr theReport, + SCReportKeyType theKeyType, + gsi_u32 theIndex, // how many times this has been called + void* theUserParam); +*/ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Main interface functions +SCResult SC_CALL scInitialize(int theGameId, + SCInterfacePtr * theInterfaceOut); +SCResult SC_CALL scShutdown (SCInterfacePtr theInterface); +SCResult SC_CALL scThink (SCInterfacePtr theInterface); + +SCResult SC_CALL scCreateSession(SCInterfacePtr theInterface, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCCreateSessionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData); + +// This is a variation of scCreateSession that creates a "matchless" session. +// "matchless" means incoming data will be scrutinized less, and applied to stats immediately instead of when the match is over. +SCResult SC_CALL scCreateMatchlessSession(SCInterfacePtr theInterface, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCCreateSessionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData); + +SCResult SC_CALL scSetReportIntention(const SCInterfacePtr theInterface, + const gsi_u8 theConnectionId[SC_CONNECTION_GUID_SIZE], + gsi_bool isAuthoritative, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCSetReportIntentionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData); + +SCResult SC_CALL scSubmitReport (const SCInterfacePtr theInterface, + const SCReportPtr theReport, + gsi_bool isAuthoritative, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCSubmitReportCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData); + +//SCResult SC_CALL sc +SCResult SC_CALL scSetSessionId(const SCInterfacePtr theInterface, const gsi_u8 theSessionId[SC_SESSION_GUID_SIZE]); + +const char * scGetSessionId (const SCInterfacePtr theInterface); +const char * scGetConnectionId(const SCInterfacePtr theInterface); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Report generation functions + +// Create a new (empty) report +// - Specify player and team count so we can allocate memory +// and later sanity check against reported data +SCResult SC_CALL scCreateReport(const SCInterfacePtr theInterface, + gsi_u32 theHeaderVersion, + gsi_u32 thePlayerCount, + gsi_u32 theTeamCount, + SCReportPtr * theReportOut); + +// - Write global data key/values +SCResult SC_CALL scReportBeginGlobalData(SCReportPtr theReportData); +SCResult SC_CALL scReportBeginPlayerData(SCReportPtr theReportData); +SCResult SC_CALL scReportBeginTeamData (SCReportPtr theReportData); + + +// - Write player auth info and key/values +SCResult SC_CALL scReportBeginNewPlayer(SCReportPtr theReportData); +SCResult SC_CALL scReportSetPlayerData (SCReportPtr theReport, + gsi_u32 thePlayerIndex, + const gsi_u8 thePlayerConnectionId[SC_CONNECTION_GUID_SIZE], + gsi_u32 thePlayerTeamId, + SCGameResult theResult, + gsi_u32 theProfileId, + const GSLoginCertificate * theCertificate, + const gsi_u8 theAuthData[16]); + +// - Write team info and key/values +SCResult SC_CALL scReportBeginNewTeam(SCReportPtr theReportData); +SCResult SC_CALL scReportSetTeamData (SCReportPtr theReport, + gsi_u32 theTeamId, + SCGameResult theResult); + +// - Call this when you're finished writing the report +SCResult SC_CALL scReportEnd(SCReportPtr theReport, + gsi_bool isAuth, + SCGameStatus theStatus); + +// Call this to set the report as "matchless". +// This is needed if the report is being submitted to a "matchless" game session. +SCResult SC_CALL scReportSetAsMatchless(SCReportPtr theReport); + + +// Utility to record key value pairs +SCResult SC_CALL scReportAddIntValue(SCReportPtr theReportData, + gsi_u16 theKeyId, + gsi_i32 theValue); +SCResult SC_CALL scReportAddInt64Value(SCReportPtr theReportData, + gsi_u16 theKeyId, + gsi_i64 theValue); +SCResult SC_CALL scReportAddShortValue(SCReportPtr theReportData, + gsi_u16 theKeyId, + gsi_i16 theValue); +SCResult SC_CALL scReportAddByteValue(SCReportPtr theReportData, + gsi_u16 theKeyId, + gsi_i8 theValue); +SCResult SC_CALL scReportAddFloatValue(SCReportPtr theReportData, + gsi_u16 theKeyId, + float theValue); +SCResult SC_CALL scReportAddStringValue(SCReportPtr theReportData, + gsi_u16 theKeyId, + const gsi_char * theValue); +SCResult SC_CALL scDestroyReport(SCReportPtr theReport); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Peer to peer encryption utilities (Will probably be moved to common code) + +// A symmetric cipher key for peer-to-peer communication +// Will usually have one key for sending and a second key for receiving +typedef struct SCPeerCipher +{ + RC4Context mRC4; + gsi_u8 mKey[GS_CRYPT_RSA_BYTE_SIZE]; + gsi_u32 mKeyLen; + gsi_bool mInitialized; +} SCPeerCipher; + +typedef char SCPeerKeyExchangeMsg[GS_CRYPT_RSA_BYTE_SIZE]; + +SCResult SC_CALL scPeerCipherInit(const GSLoginCertificate * theLocalCert, SCPeerCipher * theCipher); + +SCResult SC_CALL scPeerCipherCreateKeyExchangeMsg(const GSLoginCertificate * theRemoteCert, + const SCPeerCipher * theCipher, + SCPeerKeyExchangeMsg theMsgOut); + +SCResult SC_CALL scPeerCipherParseKeyExchangeMsg (const GSLoginCertificate * theLocalCert, + const GSLoginPrivateData * theCertPrivateData, + const SCPeerKeyExchangeMsg theMsg, + SCPeerCipher * theCipherOut); + +// Encrypt/Decrypt in place, also the RC4 context is modified everytime encryption/decryption take place +SCResult SC_CALL scPeerCipherEncryptBuffer(SCPeerCipher * theCipher, gsi_u8 * theData, gsi_u32 theLen); +SCResult SC_CALL scPeerCipherDecryptBuffer(SCPeerCipher * theCipher, gsi_u8 * theData, gsi_u32 theLen); + +// When using UDP (non-ordered) you must supply a message num +// - This is less efficient then ecrypting an ordered stream +SCResult SC_CALL scPeerCipherEncryptBufferIV(SCPeerCipher * theCipher, gsi_u32 theMessageNum, gsi_u8 * theData, gsi_u32 theLen); +SCResult SC_CALL scPeerCipherDecryptBufferIV(SCPeerCipher * theCipher, gsi_u32 theMessageNum, gsi_u8 * theData, gsi_u32 theLen); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __SC_H__ diff --git a/code/gamespy/sc/scRaceSample/HostOrJoinDlg.cpp b/code/gamespy/sc/scRaceSample/HostOrJoinDlg.cpp new file mode 100644 index 00000000..1bd41f5b --- /dev/null +++ b/code/gamespy/sc/scRaceSample/HostOrJoinDlg.cpp @@ -0,0 +1,69 @@ +// HostOrJoinDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "ScRaceSample.h" +#include "HostOrJoinDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CHostOrJoinDlg dialog + + +CHostOrJoinDlg::CHostOrJoinDlg(CWnd* pParent /*=NULL*/) + : CDialog(CHostOrJoinDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CHostOrJoinDlg) + m_joinAddress = _T(""); + m_hostOrJoin = 0; + //}}AFX_DATA_INIT +} + + +void CHostOrJoinDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CHostOrJoinDlg) + DDX_Text(pDX, IDC_JOIN_ADDRESS, m_joinAddress); + DDX_Radio(pDX, IDC_HOST, m_hostOrJoin); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CHostOrJoinDlg, CDialog) + //{{AFX_MSG_MAP(CHostOrJoinDlg) + ON_EN_SETFOCUS(IDC_JOIN_ADDRESS, OnSetfocusJoinAddress) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CHostOrJoinDlg message handlers + +void CHostOrJoinDlg::OnOK() +{ + UpdateData(); + + // Check for no join address. + ///////////////////////////// + if((m_hostOrJoin == HOSTORJOIN_JOIN) && m_joinAddress.IsEmpty()) + { + MessageBox("Please enter the address of the host to connect to"); + return; + } + + CDialog::OnOK(); +} + +void CHostOrJoinDlg::OnSetfocusJoinAddress() +{ + // Make sure its on join. + ///////////////////////// + UpdateData(); + m_hostOrJoin = HOSTORJOIN_JOIN; + UpdateData(FALSE); +} diff --git a/code/gamespy/sc/scRaceSample/HostOrJoinDlg.h b/code/gamespy/sc/scRaceSample/HostOrJoinDlg.h new file mode 100644 index 00000000..98fbc34e --- /dev/null +++ b/code/gamespy/sc/scRaceSample/HostOrJoinDlg.h @@ -0,0 +1,51 @@ +#if !defined(AFX_HOSTORJOINDLG_H__7A7BDB42_5AA8_45CD_9DD1_0EA7FC3A723F__INCLUDED_) +#define AFX_HOSTORJOINDLG_H__7A7BDB42_5AA8_45CD_9DD1_0EA7FC3A723F__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// HostOrJoinDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CHostOrJoinDlg dialog + +#define HOSTORJOIN_HOST 0 +#define HOSTORJOIN_JOIN 1 + +class CHostOrJoinDlg : public CDialog +{ +// Construction +public: + CHostOrJoinDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CHostOrJoinDlg) + enum { IDD = IDD_HOST_OR_JOIN }; + CString m_joinAddress; + int m_hostOrJoin; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CHostOrJoinDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CHostOrJoinDlg) + virtual void OnOK(); + afx_msg void OnSetfocusJoinAddress(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_HOSTORJOINDLG_H__7A7BDB42_5AA8_45CD_9DD1_0EA7FC3A723F__INCLUDED_) diff --git a/code/gamespy/sc/scRaceSample/LoginDlg.cpp b/code/gamespy/sc/scRaceSample/LoginDlg.cpp new file mode 100644 index 00000000..7d4cf438 --- /dev/null +++ b/code/gamespy/sc/scRaceSample/LoginDlg.cpp @@ -0,0 +1,245 @@ +// LoginDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "ScRaceSample.h" +#include "LoginDlg.h" +#include "../../common/gsAvailable.h" + + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + + +CLoginDlg::CLoginDlg(CWnd* pParent /*=NULL*/) + : CDialog(CLoginDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CLoginDlg) + m_email = _T(""); + m_nick = _T(""); + m_password = _T(""); + //}}AFX_DATA_INIT +} + + +void CLoginDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CLoginDlg) + DDX_Text(pDX, IDC_EMAIL, m_email); + DDX_Text(pDX, IDC_NICK, m_nick); + DDX_Text(pDX, IDC_PASSWORD, m_password); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CLoginDlg, CDialog) + //{{AFX_MSG_MAP(CLoginDlg) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg message handlers + +GPResult checkResult; +int checkProfile; +gsi_char loginResponse[1024]; +gsi_bool loginSuccess; +gsi_bool waitForLogin; + + +void CheckUserCallback(GPConnection * connection, void * _arg, void * param) +{ + GPCheckResponseArg * arg = (GPCheckResponseArg *)_arg; + + checkResult = arg->result; + checkProfile = arg->profile; +} + +void LoginCallback(GHTTPResult httpResult, WSLoginResponse * theResponse, void * theUserData) +{ + CString debugStr; + + if (httpResult != GHTTPSuccess) + { + loginSuccess = gsi_false; + + debugStr.Format("[LoginCallback] HTTPError: %d", httpResult); + OutputDebugString(debugStr); + + sprintf(loginResponse, "HTTP Error: Player Login Failed."); + } + else if (theResponse->mLoginResult != WSLogin_Success) + { + loginSuccess = gsi_false; + + debugStr.Format("[LoginCallback] Login Result Error: %d", theResponse->mLoginResult); + OutputDebugString(debugStr); + + sprintf(loginResponse, "Server Error: Player Login Failed."); + } + else + { + SamplePlayerData * newPlayer = &gPlayerData; + + loginSuccess = gsi_true; + + + // Store the profile ID for the player + ////////////////////////////////////// + newPlayer->mProfileId = theResponse->mCertificate.mProfileId; + + // Store the Login Certificate & Private Data for later use w/ Competition + ////////////////////////////////////////////////////////////////////////// + memcpy(&newPlayer->mCertificate, &theResponse->mCertificate, sizeof(GSLoginCertificate)); + memcpy(&newPlayer->mPrivateData, &theResponse->mPrivateData, sizeof(GSLoginPrivateData)); + } + waitForLogin = gsi_true; +} + + +void CLoginDlg::OnOK() +{ + GPConnection connection; + HCURSOR hourglass; + HCURSOR lastCursor; + GPResult result; + int loginResult; + + + UpdateData(); + + // Check for no account info + //////////////////////////// + if(m_email.IsEmpty() || m_nick.IsEmpty() || m_password.IsEmpty()) + { + MessageBox("Please fill in all the account information."); + return; + } + + // Initialize GP + //////////////// + if(gpInitialize(&connection, SCRACE_PRODUCTID, WSLogin_NAMESPACE_SHARED_NONUNIQUE, GP_PARTNERID_GAMESPY) != GP_NO_ERROR) + { + MessageBox("Error initializing the login system."); + return; + } + + // Wait cursor on + ///////////////// + hourglass = LoadCursor(NULL, IDC_WAIT); + if(hourglass) + lastCursor = SetCursor(hourglass); + + // Check for the account specified + ////////////////////////////////// + result = gpCheckUser(&connection, m_nick, m_email, m_password, GP_BLOCKING, CheckUserCallback, NULL); + + // Wait cursor off + ////////////////// + if(hourglass) + SetCursor(lastCursor); + + // Destroy the GP Object + //////////////////////// + gpDestroy(&connection); + + // Check for an error + ///////////////////// + if(result != GP_NO_ERROR) + { + MessageBox("Error verifying the account."); + return; + } + + // Check the result + /////////////////// + if(checkResult != GP_NO_ERROR) + { + if(checkResult == GP_CHECK_BAD_EMAIL) + MessageBox("Invalid e-mail."); + else if(checkResult == GP_CHECK_BAD_NICK) + MessageBox("Invalid nick."); + else if(checkResult == GP_CHECK_BAD_PASSWORD) + MessageBox("Invalid password."); + else + MessageBox("Error verifying the account."); + return; + } + + // Save the login info for next time + //////////////////////////////////// + FILE * file; + file = fopen("login.txt", "wt"); + if(file) + { + fprintf(file, "%s\n%s\n%s", m_email, m_nick, m_password); + fclose(file); + } + + // Login to the Authentication Service + ////////////////////////////////////// + loginResult = wsLoginProfile(WSLogin_NAMESPACE_SHARED_NONUNIQUE, WSLogin_PARTNERCODE_GAMESPY, m_nick, m_email, m_password, "", LoginCallback, NULL); + if (loginResult != WSLogin_Success) + { + if (loginResult == WSLogin_InvalidParameters) + MessageBox("Login Failed - Invalid Parameters."); // this should not be reached + else if (loginResult == WSLogin_OutOfMemory) + MessageBox("Login Failed - Out of Memory Exception."); + else if (loginResult == WSLogin_NoAvailabilityCheck) + MessageBox("Login Failed - Availability Check Not Performed"); + else + MessageBox("Login Failed - Unknown Error."); + return; + } + + while (!waitForLogin) + { + gsCoreThink(5); //process the login and wait for callback to complete + } + + if (!loginSuccess) + { + MessageBox(loginResponse); + return; + } + + CDialog::OnOK(); +} + +BOOL CLoginDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // load login info + ////////////////// + FILE * file; + file = fopen("login.txt", "rt"); + if(file) + { + char buffer[512]; + + if(fgets(buffer, sizeof(buffer), file)) + m_email = buffer; + if(fgets(buffer, sizeof(buffer), file)) + m_nick = buffer; + if(fgets(buffer, sizeof(buffer), file)) + m_password = buffer; + + fclose(file); + + m_email.Remove('\n'); + m_nick.Remove('\n'); + m_password.Remove('\n'); + } + + UpdateData(FALSE); + + return TRUE; +} \ No newline at end of file diff --git a/code/gamespy/sc/scRaceSample/LoginDlg.h b/code/gamespy/sc/scRaceSample/LoginDlg.h new file mode 100644 index 00000000..a4fce342 --- /dev/null +++ b/code/gamespy/sc/scRaceSample/LoginDlg.h @@ -0,0 +1,52 @@ +#if !defined(AFX_LOGINDLG_H__58A8C6B5_2AD3_4C62_882E_DC1F8D104CC5__INCLUDED_) +#define AFX_LOGINDLG_H__58A8C6B5_2AD3_4C62_882E_DC1F8D104CC5__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// LoginDlg.h : header file +// + + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + +class CLoginDlg : public CDialog +{ +// Construction +public: + CLoginDlg(CWnd* pParent = NULL); // standard constructor + SamplePlayerData m_playerData; // global player data + + +// Dialog Data + //{{AFX_DATA(CLoginDlg) + enum { IDD = IDD_LOGIN }; + CString m_email; + CString m_nick; + CString m_password; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CLoginDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CLoginDlg) + virtual void OnOK(); + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_LOGINDLG_H__58A8C6B5_2AD3_4C62_882E_DC1F8D104CC5__INCLUDED_) diff --git a/code/gamespy/sc/scRaceSample/ReadMe.txt b/code/gamespy/sc/scRaceSample/ReadMe.txt new file mode 100644 index 00000000..b1744165 --- /dev/null +++ b/code/gamespy/sc/scRaceSample/ReadMe.txt @@ -0,0 +1,42 @@ +=========================================================== +================ ScRaceSample Overview ==================== +=========================================================== + +The ScRaceSample program is intended to give developers an +example for how the Competition SDK is used in practice. The +sample game is a simple 1v1 racing game using a +challenge-response mechanism. + +========== +HOW TO USE +========== +1. Login with a GameSpy account + +When starting the sample, you will first see a Dialog Box +prompting you to login. Using your GameSpy account, login with +your e-mail, profile nickname and password. If you do not have +an account, you can create a free account at www.gamespyid.com. + +2. Choose to Host or Join a game + +After the login is complete, another Dialog Box will ask if you +plan to Host or Join the game. Once another player has a game +being hosted, you can enter that IP in order to join into his/her +game. This sample is very basic and does not do any NAT +Negotiation for Hosts behind NATs, so make sure ports are open for +connections that drop unsolicited packets. + +The Sample uses a default port of 38466 for the Host, which can be +modified at the top of ScRaceSampleDlg.cpp by changing the value +of HOST_PORT_STRING. The CLIENT_PORT_STRING is set to 38467 to +allow for local joining of a session. + +3. Start the Game + +Once the two players are connected. The Host can start the game +by clicking on "Start Game". The game will countdown from 5 and +say "GO!" when the game begins. To race, players must alternate +between pressing 'Z' and 'X' on the keyboard as quickly as +possible. The progress bar will display how far you and your +opponent are from the finish. The game will wait until both +players have finished before displaying the results. \ No newline at end of file diff --git a/code/gamespy/sc/scRaceSample/ScRaceSample.cpp b/code/gamespy/sc/scRaceSample/ScRaceSample.cpp new file mode 100644 index 00000000..e0971a25 --- /dev/null +++ b/code/gamespy/sc/scRaceSample/ScRaceSample.cpp @@ -0,0 +1,68 @@ +// ScRaceSample.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "ScRaceSample.h" +#include "ScRaceSampleDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackApp + +BEGIN_MESSAGE_MAP(CLadderTrackApp, CWinApp) + //{{AFX_MSG_MAP(CLadderTrackApp) + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + + +SamplePlayerData gPlayerData; //global player data + + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackApp construction + +CLadderTrackApp::CLadderTrackApp() +{ +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CLadderTrackApp object + +CLadderTrackApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackApp initialization + +BOOL CLadderTrackApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization +/* +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif +*/ + + CScRaceSampleDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + } + else if (nResponse == IDCANCEL) + { + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/code/gamespy/sc/scRaceSample/ScRaceSample.h b/code/gamespy/sc/scRaceSample/ScRaceSample.h new file mode 100644 index 00000000..a0b9ea90 --- /dev/null +++ b/code/gamespy/sc/scRaceSample/ScRaceSample.h @@ -0,0 +1,95 @@ +// ladderTrack.h : main header file for the LADDERTRACK application +// + +#if !defined(AFX_LADDERTRACK_H__E9856F44_580A_48C0_ABFF_6FFA9BA944A3__INCLUDED_) +#define AFX_LADDERTRACK_H__E9856F44_580A_48C0_ABFF_6FFA9BA944A3__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// global defines used in the sample + +#define SCRACE_GAMENAME "sc_race" +#define SCRACE_SECRETKEY "Zc0eM6" +#define SCRACE_GAMEID 1649 +#define SCRACE_PRODUCTID 11030 + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// This represents the player data structure in your game. +typedef struct SamplePlayerData +{ + // "Normal" game data + gsi_u32 mProfileId; + GSLoginCertificate mCertificate; + GSLoginPrivateData mPrivateData; + SCPeerCipher mPeerSendCipher; // for fast encryption + SCPeerCipher mPeerRecvCipher; // for fast decryption + + SCInterfacePtr mStatsInterface; + + // Stats related data + gsi_u8 mSessionId[SC_SESSION_GUID_SIZE]; + gsi_u8 mConnectionId[SC_CONNECTION_GUID_SIZE]; + gsi_u8 mStatsAuthdata[16]; + + gsi_i32 mFrags; + gsi_i32 mScore; + gsi_i16 mDeaths; + gsi_i16 mShots; + gsi_i32 mTeam; + + // Obfuscated versions + SCHiddenData mHiddenFrags; + SCHiddenData mHiddenDeaths; + SCHiddenData mHiddenShots; + SCHiddenData mHiddenScore; + + // A simple way to block the sample's progress + gsi_u32 mWaitCount; + +} SamplePlayerData; + +extern SamplePlayerData gPlayerData; + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackApp: +// See ladderTrack.cpp for the implementation of this class +// + +class CLadderTrackApp : public CWinApp +{ +public: + CLadderTrackApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CLadderTrackApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CLadderTrackApp) + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_LADDERTRACK_H__E9856F44_580A_48C0_ABFF_6FFA9BA944A3__INCLUDED_) diff --git a/code/gamespy/sc/scRaceSample/ScRaceSample.rc b/code/gamespy/sc/scRaceSample/ScRaceSample.rc new file mode 100644 index 00000000..956b768b --- /dev/null +++ b/code/gamespy/sc/scRaceSample/ScRaceSample.rc @@ -0,0 +1,239 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\ScRaceSample.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\ScRaceSample.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_SCRACESAMPLE_DIALOG DIALOGEX 0, 0, 212, 100 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "ScRaceSample" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Exit",IDOK,155,5,50,14 + PUSHBUTTON "Logout",IDC_LOGOUT,155,21,50,14 + PUSHBUTTON "Start Race",IDC_START_RACE,10,15,50,14 + LTEXT "Info",IDC_INFO,70,15,80,8 + CONTROL "Progress1",IDC_LOCAL_PROGRESS,"msctls_progress32", + PBS_SMOOTH | WS_BORDER,7,48,80,14 + CONTROL "Progress1",IDC_REMOTE_PROGRESS,"msctls_progress32", + PBS_SMOOTH | WS_BORDER,113,48,80,14 + LTEXT "You:",IDC_STATIC,7,38,16,8 + LTEXT "Opponent:",IDC_STATIC,113,38,34,8 + LTEXT "Use Z && X to race",IDC_STATIC,70,25,57,8 +END + +IDD_HOST_OR_JOIN DIALOG DISCARDABLE 0, 0, 165, 50 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Host or Join?" +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "Host",IDC_HOST,"Button",BS_AUTORADIOBUTTON | WS_GROUP,7, + 7,31,10 + CONTROL "Join",IDC_JOIN,"Button",BS_AUTORADIOBUTTON,7,31,29,10 + EDITTEXT IDC_JOIN_ADDRESS,52,28,106,14,ES_AUTOHSCROLL | WS_GROUP + DEFPUSHBUTTON "OK",IDOK,52,7,50,14,WS_GROUP + PUSHBUTTON "Cancel",IDCANCEL,108,7,50,14 +END + +IDD_WAITING DIALOG DISCARDABLE 0, 0, 64, 45 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "ScRaceSample" +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "Cancel",IDCANCEL,7,24,50,14 + LTEXT "Please Wait...",IDC_STATIC,9,7,45,8 +END + +IDD_LOGIN DIALOG DISCARDABLE 0, 0, 147, 76 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "GameSpy Login" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_EMAIL,51,8,84,12,ES_AUTOHSCROLL + EDITTEXT IDC_NICK,51,23,84,12,ES_AUTOHSCROLL + EDITTEXT IDC_PASSWORD,51,38,84,12,ES_PASSWORD | ES_AUTOHSCROLL + DEFPUSHBUTTON "Login",IDOK,15,55,50,14 + PUSHBUTTON "Cancel",IDCANCEL,80,55,50,14 + LTEXT "email:",IDC_STATIC,10,10,19,8 + LTEXT "nick:",IDC_STATIC,10,25,16,8 + LTEXT "password:",IDC_STATIC,10,40,33,8 +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "\0" + VALUE "CompanyName", "\0" + VALUE "FileDescription", "ScRaceSample MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "ScRaceSample\0" + VALUE "LegalCopyright", "Copyright (C) 2001\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "ScRaceSample.EXE\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "ScRaceSample Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_SCRACESAMPLE_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 205 + TOPMARGIN, 7 + BOTTOMMARGIN, 93 + END + + IDD_HOST_OR_JOIN, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 158 + TOPMARGIN, 7 + BOTTOMMARGIN, 43 + END + + IDD_WAITING, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 57 + TOPMARGIN, 7 + BOTTOMMARGIN, 38 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\ScRaceSample.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/gamespy/sc/scRaceSample/ScRaceSampleDlg.cpp b/code/gamespy/sc/scRaceSample/ScRaceSampleDlg.cpp new file mode 100644 index 00000000..495a58df --- /dev/null +++ b/code/gamespy/sc/scRaceSample/ScRaceSampleDlg.cpp @@ -0,0 +1,1486 @@ +// ScRaceSampleDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "ScRaceSample.h" +#include "ScRaceSampleDlg.h" +#include "waitingDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CScRaceSampleDlg dialog + +CScRaceSampleDlg::CScRaceSampleDlg(CWnd* pParent /*=NULL*/) + : CDialog(CScRaceSampleDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CScRaceSampleDlg) + m_info = _T("Ready"); + //}}AFX_DATA_INIT + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); +} + +void CScRaceSampleDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CScRaceSampleDlg) + DDX_Control(pDX, IDC_START_RACE, m_startRace); + DDX_Control(pDX, IDC_REMOTE_PROGRESS, m_remoteProgress); + DDX_Control(pDX, IDC_LOCAL_PROGRESS, m_localProgress); + DDX_Text(pDX, IDC_INFO, m_info); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CScRaceSampleDlg, CDialog) + //{{AFX_MSG_MAP(CScRaceSampleDlg) + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_BN_CLICKED(IDC_START_RACE, OnStart) + ON_WM_DESTROY() + ON_WM_TIMER() + ON_BN_CLICKED(IDC_LOGOUT, OnLogout) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CScRaceSampleDlg message handlers + +// Pre-game messaging +#define MSG_CONNECT "is" // client-pid, nick +#define MSG_TO_HOST_CERT "ir" // host-pid, certificate +#define MSG_TO_HOST_CERT_TYPE 1 +#define MSG_TO_CLIENT_CERT "ir" // join-pid, certificate +#define MSG_TO_CLIENT_CERT_TYPE 2 +#define MSG_TO_HOST_KEYS "r" // encryption keys +#define MSG_TO_HOST_KEYS_TYPE 3 +#define MSG_TO_CLIENT_KEYS "r" // encrpytion keys +#define MSG_TO_CLIENT_KEYS_TYPE 4 + +// Game messaging +#define MSG_COUNTDOWN "i" // count +#define MSG_COUNTDOWN_TYPE 20 +#define MSG_SESSION_ID "rr" // Host session ID, connID exchange +#define MSG_SESSION_ID_TYPE 21 +#define MSG_CONNECTION_ID "r" // connection ID exhcnage +#define MSG_CONNECTION_ID_TYPE 22 +#define MSG_START_RACE "" // race start +#define MSG_START_RACE_TYPE 23 +#define MSG_PROGRESS "i" // progress +#define MSG_PROGRESS_TYPE 24 +#define MSG_END_RACE "i" // time +#define MSG_END_RACE_TYPE 25 +#define MSG_CHAT "s" // message +#define MSG_CHAT_TYPE 26 + +//#define HOST_PORT 38466 +#define HOST_PORT_STRING ":38466" +#define CLIENT_PORT_STRING ":38467" // so you can run both +#define COUNTDOWN_START 5 + +#define TIMER_THINK 100 +#define TIMER_COUNTDOWN 101 + +// Stats & SDK constants +#define SCRACE_TIMEOUT_MS 0 +#define SCRACE_SLEEP_MS 100 +#define SCRACE_AUTHORITATIVE gsi_true +#define SCRACE_COLLABORATIVE gsi_false +#define SCRACE_NUM_PLAYERS 2 +#define SCRACE_NUM_TEAMS 2 + +#define SCRACE_HOST_TEAM 7564 // fake team ids +#define SCRACE_CLIENT_TEAM 7565 // fake team ids + + +CScRaceSampleDlg * Dlg; +gsi_bool sessionCreated; +gsi_bool connIdSet; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// A utility to block until a request completes +// Used to simplify the logic flow of the sample +void waitForScCallbacks(SCInterfacePtr theInterface, int howMany) +{ + // wait for the request to complete + ///////////////////////////////////////////////////////// + gPlayerData.mWaitCount = howMany; + while (gPlayerData.mWaitCount > 0) + { + msleep(SCRACE_SLEEP_MS); + scThink(theInterface); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// Competition Callbacks +void createSessionCallback(const SCInterfacePtr theInterface, + GHTTPResult theHttpResult, + SCResult theResult, + void * theUserData) +{ + CString debugStr; + + if (theHttpResult == GHTTPSuccess && theResult == SCResult_NO_ERROR) + { + // Retrieve the Session/Connection ID to be used later + ///////////////////////////////////////////////////////// + + memcpy(gPlayerData.mSessionId, scGetSessionId(theInterface), SC_SESSION_GUID_SIZE); + memcpy(gPlayerData.mConnectionId, scGetConnectionId(theInterface), SC_CONNECTION_GUID_SIZE); + + sessionCreated = gsi_true; + + debugStr.Format("[createSessionCB] Session ID: %s\n", gPlayerData.mSessionId); + OutputDebugString(debugStr); + debugStr.Format("[createSessionCB] Connection ID: [%s]\n", gPlayerData.mConnectionId); + OutputDebugString(debugStr); + } + else + { + debugStr.Format("[createSessionCB] Error. HTTPResult: %d, SCResult: %d\r\n", + theHttpResult, theResult); + OutputDebugString(debugStr); + + Dlg->MessageBox("Error Creating Stats Session"); + Dlg->Logout(); + } + + gPlayerData.mWaitCount--; + + GSI_UNUSED(theInterface); + GSI_UNUSED(theUserData); +} + +void setReportIntentionCallback(const SCInterfacePtr theInterface, + GHTTPResult theHttpResult, + SCResult theResult, + void * theUserData) +{ + CString debugStr; + + if (theHttpResult == GHTTPSuccess && theResult == SCResult_NO_ERROR) + { + // Retrieve the connection ID to be used later + ///////////////////////////////////////////////////////// + const char * connectionId = scGetConnectionId(theInterface); + memcpy(gPlayerData.mConnectionId, connectionId, SC_CONNECTION_GUID_SIZE); + connIdSet = gsi_true; + + debugStr.Format("[setIntentionCB] Connection ID: [%s]\n", gPlayerData.mConnectionId); + OutputDebugString(debugStr); + } + else + { + debugStr.Format("[setIntentionCB] Error. HTTPResult: %d, SCResult: %d\r\n", + theHttpResult, theResult); + OutputDebugString(debugStr); + + Dlg->MessageBox("Error Initializing Stats System"); + Dlg->Logout(); + } + + gPlayerData.mWaitCount--; // one less request to wait for + + GSI_UNUSED(theInterface); + GSI_UNUSED(theUserData); +} + +void submitReportCallback(const SCInterfacePtr theInterface, + GHTTPResult theHttpResult, + SCResult theResult, + void * theUserData) +{ + CString debugStr; + + if (theHttpResult != GHTTPSuccess || theResult != SCResult_NO_ERROR) + { + debugStr.Format("[submitReportCB] Error. HTTPResult: %d, SCResult: %d\r\n", + theHttpResult, theResult); + OutputDebugString(debugStr); + + Dlg->MessageBox("Error Submitting Stats Report"); + } + + gPlayerData.mWaitCount--; // one less request to wait for + + GSI_UNUSED(theInterface); + GSI_UNUSED(theUserData); +} + +/////////////////////////////////////////////////////////////////////////////// +// GT2 Callbacks +void ConnectedCallback +( + GT2Connection connection, + GT2Result result, + GT2Byte * message, + int len +) +{ + if(result == GT2Success) + Dlg->m_state = JOIN_EXCHANGE_CERT; + else + { + Dlg->m_state = JOIN_ERROR; + Dlg->m_GT2Connection = NULL; + } +} + +void ReceivedCallback +( + GT2Connection connection, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + if(!message || !len) + return; + + GTMessageType type; + CString debugStr; + + type = gtEncodedMessageType((char*)message); + + if(type == MSG_TO_CLIENT_CERT_TYPE) + { + char remoteCert[512]; + int remoteCertLen = 512; + int pid; + + if(gtDecode(MSG_TO_CLIENT_CERT, (char*)message, len, &pid, remoteCert, &remoteCertLen) == -1) + { + Dlg->m_state = JOIN_ERROR; + return; + } + + // Parse the certificate + ///////////////////////////////////////////////////////// + wsLoginCertReadBinary(&Dlg->m_remoteCertificate, remoteCert, remoteCertLen); + + Dlg->m_remoteProfile = pid; + + // Build up and send the certificate response + ///////////////////////////////////////////////////////// + char buffer[520]; + char cert[512]; + int rcode; + gsi_u32 certLen; + + wsLoginCertWriteBinary(&gPlayerData.mCertificate, cert, sizeof(cert), &certLen); + + rcode = gtEncode(MSG_TO_HOST_CERT_TYPE, MSG_TO_HOST_CERT, buffer, sizeof(buffer), gPlayerData.mProfileId, cert, certLen); + ASSERT(rcode != -1); + gt2Send(connection, (const unsigned char*)buffer, rcode, GT2True); + + Dlg->m_state = JOIN_VERIFY_CERT; + } + else if(type == MSG_TO_HOST_CERT_TYPE) + { + char remoteCert[512]; + int remoteCertLen = 512; + int pid; + + if(gtDecode(MSG_TO_HOST_CERT, (char*)message, len, &pid, remoteCert, &remoteCertLen) == -1) + { + Dlg->m_state = HOST_ERROR; + return; + } + + // Parse the certificate + ///////////////////////////////////////////////////////// + wsLoginCertReadBinary(&Dlg->m_remoteCertificate, remoteCert, remoteCertLen); + + Dlg->m_remoteProfile = pid; + Dlg->m_state = HOST_VERIFY_CERT; + } + else if(type == MSG_TO_CLIENT_KEYS_TYPE) + { + SCPeerKeyExchangeMsg recvMsg; + int recvMsgLen = GS_CRYPT_RSA_BYTE_SIZE; + + if(gtDecode(MSG_TO_CLIENT_KEYS, (char*)message, len, recvMsg, &recvMsgLen) == -1) + { + Dlg->m_state = JOIN_ERROR; + return; + } + + // Receiving player should parse the cipher key out of it. + // - decrypting the msg requires the local player's private data + ///////////////////////////////////////////////////////// + scPeerCipherParseKeyExchangeMsg(&gPlayerData.mCertificate, &gPlayerData.mPrivateData, + recvMsg, &gPlayerData.mPeerSendCipher); + + + // Send response to host + ///////////////////////////////////////////////////////// + char buffer[512]; + int rcode; + SCPeerKeyExchangeMsg exchangeMsg; + + scPeerCipherInit(&gPlayerData.mCertificate, &gPlayerData.mPeerRecvCipher); + scPeerCipherCreateKeyExchangeMsg(&Dlg->m_remoteCertificate, &gPlayerData.mPeerRecvCipher, exchangeMsg); + + // Now send the key to the other player + ///////////////////////////////////////////////////////// + rcode = gtEncode(MSG_TO_HOST_KEYS_TYPE, MSG_TO_HOST_KEYS, buffer, sizeof(buffer), exchangeMsg, GS_CRYPT_RSA_BYTE_SIZE); + ASSERT(rcode != -1); + gt2Send(connection, (const unsigned char*)buffer, rcode, GT2True); + + Dlg->m_state = JOIN_CONNECTED; + } + else if(type == MSG_TO_HOST_KEYS_TYPE) + { + SCPeerKeyExchangeMsg exchangeMsg; + int exchangeMsgLen = GS_CRYPT_RSA_BYTE_SIZE; + + if(gtDecode(MSG_TO_HOST_KEYS, (char*)message, len, exchangeMsg, &exchangeMsgLen) == -1) + { + Dlg->m_state = HOST_ERROR; + return; + } + + // Receiving player should parse the cipher key out of it. + // - decrypting the msg requires the local player's private data + ///////////////////////////////////////////////////////// + scPeerCipherParseKeyExchangeMsg(&gPlayerData.mCertificate, &gPlayerData.mPrivateData, + exchangeMsg, &gPlayerData.mPeerSendCipher); + + + Dlg->m_state = HOST_CONNECTED; + } + else if(type == MSG_COUNTDOWN_TYPE) + { + ASSERT(!Dlg->m_hosting); + + if(gtDecode(MSG_COUNTDOWN, (char*)message, len, &Dlg->m_countdown) == -1) + { + Dlg->m_state = JOIN_ERROR; + return; + } + + Dlg->Countdown(); + } + else if(type == MSG_SESSION_ID_TYPE) + { + ASSERT(!Dlg->m_hosting); + char sessionCrypt[SC_SESSION_GUID_SIZE]; + char connCrypt[SC_CONNECTION_GUID_SIZE]; + int sidLen = SC_SESSION_GUID_SIZE; + int ccidLen = SC_CONNECTION_GUID_SIZE; + + + // Client decodes sessionID / remote connID + ///////////////////////////////////////////////////////// + if(gtDecode(MSG_SESSION_ID, (char*)message, len, sessionCrypt, &sidLen, connCrypt, &ccidLen) == -1) + { + Dlg->m_state = JOIN_ERROR; + return; + } + + debugStr.Format("[MSG_SESSION_ID_TYPE] sessionCrypt: %.40s\r\n", sessionCrypt); + OutputDebugString(debugStr); + debugStr.Format("[MSG_SESSION_ID_TYPE] connCrypt: %.40s\r\n", connCrypt); + OutputDebugString(debugStr); + + // Decrypt the sessionID / remote connID + ///////////////////////////////////////////////////////// + scPeerCipherDecryptBufferIV(&gPlayerData.mPeerRecvCipher, 1, (gsi_u8*)sessionCrypt, SC_SESSION_GUID_SIZE); + memcpy(gPlayerData.mSessionId, sessionCrypt, SC_SESSION_GUID_SIZE); + + scPeerCipherDecryptBufferIV(&gPlayerData.mPeerRecvCipher, 2, (gsi_u8*)connCrypt, SC_CONNECTION_GUID_SIZE); + memcpy(Dlg->m_remoteConnId, connCrypt, SC_CONNECTION_GUID_SIZE); + + + debugStr.Format("[MSG_SESSION_ID_TYPE] mSessionId: %s\r\n", gPlayerData.mSessionId); + OutputDebugString(debugStr); + debugStr.Format("[MSG_SESSION_ID_TYPE] m_remoteConnId: %s\r\n", Dlg->m_remoteConnId); + OutputDebugString(debugStr); + + + // Joining player sets the session ID and the report intention + ///////////////////////////////////////////////////////// + scSetSessionId(gPlayerData.mStatsInterface, gPlayerData.mSessionId); + scSetReportIntention(gPlayerData.mStatsInterface, gPlayerData.mConnectionId, SCRACE_AUTHORITATIVE, &gPlayerData.mCertificate, &gPlayerData.mPrivateData, + setReportIntentionCallback, SCRACE_TIMEOUT_MS, NULL); + + Dlg->m_state = JOIN_SEND_CONNID; + } + else if(type == MSG_CONNECTION_ID_TYPE) + { + ASSERT(Dlg->m_hosting); + char connCrypt[SC_CONNECTION_GUID_SIZE]; + int ccidLen = SC_CONNECTION_GUID_SIZE; + + // Hosting player decodes the remote conn ID for use in reporting + ///////////////////////////////////////////////////////// + if(gtDecode(MSG_CONNECTION_ID, (char*)message, len, connCrypt, &ccidLen) == -1) + { + Dlg->m_state = HOST_ERROR; + return; + } + + debugStr.Format("[MSG_CONNECTION_ID_TYPE] connCrypt: %.40s\r\n", connCrypt); + OutputDebugString(debugStr); + + + // Decrypt the remote conn ID + ///////////////////////////////////////////////////////// + scPeerCipherDecryptBufferIV(&gPlayerData.mPeerRecvCipher, 1, (gsi_u8*)connCrypt, SC_CONNECTION_GUID_SIZE); + memcpy(Dlg->m_remoteConnId, connCrypt, SC_CONNECTION_GUID_SIZE); + + + debugStr.Format("[MSG_CONNECTION_ID_TYPE] m_remoteConnId: %s\r\n", Dlg->m_remoteConnId); + OutputDebugString(debugStr); + + // at this point all of our exchanges are complete - ready for game start + ///////////////////////////////////////////////////////// + } + else if(type == MSG_START_RACE_TYPE) + { + ASSERT(!Dlg->m_hosting); + + Dlg->StartRace(); + } + else if(type == MSG_PROGRESS_TYPE) + { + if(Dlg->m_racing) + { + int progress; + + if(gtDecode(MSG_PROGRESS, (char*)message, len, &progress) != -1) + { + Dlg->m_remoteProgress.SetPos(progress); + } + } + } + else if(type == MSG_END_RACE_TYPE) + { + if(Dlg->m_racing) + { + gtDecode(MSG_END_RACE, (char*)message, len, &Dlg->m_remoteTime); + } + } +} + +void ClosedCallback +( + GT2Connection connection, + GT2CloseReason reason +) +{ + // Logout triggers this function when it's a local close + // so we need to make sure we don't loop + ///////////////////////////////////////////////////////// + if (reason != GT2LocalClose) + { + if (Dlg->m_racing && !Dlg->m_reportSent) + { + // If the connection was closed remotely, or connection errors occured + // we should report stats anyways + ////////////////////////////////////////////////////////////////////// + Dlg->MessageBox("Connection closed - Sending Broken report"); + + Dlg->m_disconnect = gsi_true; + Dlg->ReportStats(); + + // Reset progress bars + ////////////////////// + Dlg->m_localProgress.SetPos(0); + Dlg->m_remoteProgress.SetPos(0); + + // Once stats have been reported, we can logout + /////////////////////////////////////////////// + Dlg->Logout(); + } + else + { + // Reset progress bars + ////////////////////// + Dlg->m_localProgress.SetPos(0); + Dlg->m_remoteProgress.SetPos(0); + + Dlg->MessageBox("Connection closed"); + Dlg->Logout(); + } + } + else + Dlg->m_GT2Connection = NULL; +} + +void ConnectAttemptCallback +( + GT2Socket listener, + GT2Connection connection, + unsigned int ip, + unsigned short port, + int latency, + GT2Byte * message, + int len +) +{ + int pid = 0; + char nick[128]; + + // Only allow one connection. + ///////////////////////////// + if(Dlg->m_GT2Connection) + { + gt2Reject(connection, NULL, 0); + return; + } + + // Decode the pid. + ////////////////// + if(message && len) + { + if(gtDecodeNoType(MSG_CONNECT, (char*)message, len, &pid, nick) == -1) + pid = 0; + } + + // If we didn't/couldn't get the pid, reject the attempt. + ///////////////////////////////////////////////////////// + if(!pid) + { + gt2Reject(connection, NULL, 0); + return; + } + + // Accept the connection. + ///////////////////////// + GT2ConnectionCallbacks callbacks; + memset(&callbacks, 0, sizeof(GT2ConnectionCallbacks)); + callbacks.received = ReceivedCallback; + callbacks.closed = ClosedCallback; + if(!gt2Accept(connection, &callbacks)) + return; + + // Set some states. + /////////////////// + Dlg->m_remoteProfile = pid; + Dlg->m_GT2Connection = connection; + Dlg->m_state = HOST_EXCHANGE_CERT; //once connected, exchange certifications + Dlg->m_remoteNick = nick; +} + +void SocketErrorCallback +( + GT2Socket socket +) +{ + Dlg->MessageBox("Socket Error!"); + Dlg->Logout(); +} + + +BOOL CScRaceSampleDlg::SetupHosting() +{ + int rcode; + CString str; + + + GT2ConnectionCallbacks connectionCallbacks; + memset(&connectionCallbacks, 0, sizeof(GT2ConnectionCallbacks)); + connectionCallbacks.received = ReceivedCallback; + connectionCallbacks.closed = ClosedCallback; + + GT2Result aResult = gt2CreateSocket(&m_GT2Socket, HOST_PORT_STRING, 0, 0, SocketErrorCallback); + if (GT2Success != aResult) + return FALSE; + + gt2Listen(m_GT2Socket, ConnectAttemptCallback); + m_state = HOST_LISTENING; + + // Bring up the "waiting" dialog. + ///////////////////////////////// + rcode = m_waitingDlg.DoModal(); + + // If it was cancelled, try again. + ////////////////////////////////// + if(rcode != IDOK) + Logout(); + + return TRUE; +} + +BOOL CScRaceSampleDlg::SetupJoining() +{ + int rcode; + + // Setup the address to connect to. + /////////////////////////////////// + CString remoteAddress; + remoteAddress.Format("%s%s", m_hostOrJoinDlg.m_joinAddress, HOST_PORT_STRING); + + // Encode the profile id. + ///////////////////////// + char buffer[64]; + rcode = gtEncodeNoType(MSG_CONNECT, buffer, sizeof(buffer), gPlayerData.mProfileId, m_loginDlg.m_nick); + ASSERT(rcode != -1); + + // Setup the callbacks. + /////////////////////// + GT2ConnectionCallbacks callbacks; + memset(&callbacks, 0, sizeof(GT2ConnectionCallbacks)); + callbacks.connected = ConnectedCallback; + callbacks.received = ReceivedCallback; + callbacks.closed = ClosedCallback; + + // Create the socket + GT2Result aResult = gt2CreateSocket(&m_GT2Socket, CLIENT_PORT_STRING, 0, 0, SocketErrorCallback); + if (aResult != GT2Success) + { + MessageBox("Failed to create socket!"); + return FALSE; + } + + // Connect. + /////////// + m_state = JOIN_CONNECTING; + GT2Result result; + result = gt2Connect(m_GT2Socket, &m_GT2Connection, remoteAddress, (const GT2Byte *)buffer, sizeof(buffer), -1, &callbacks, GT2False); + if(!m_GT2Connection) + return FALSE; + + // Bring up the "waiting" dialog. + ///////////////////////////////// + rcode = m_waitingDlg.DoModal(); + + // If it was cancelled, try again. + ////////////////////////////////// + if(rcode != IDOK) + Logout(); + + return TRUE; +} + +BOOL CScRaceSampleDlg::SetupMatch() +{ + int rcode; + BOOL result; + + m_state = SETTING_UP; + + ASSERT(!m_GT2Connection); + ASSERT(!m_GT2Socket); + + m_state = 0; + m_remoteResponse.Empty(); + m_remoteProfile = 0; + m_GT2Connection = NULL; + m_startRace.EnableWindow(FALSE); + m_start = 0; + m_countdown = 0; + m_racing = FALSE; + m_numSteps = 0; + m_step = NONE; + m_reportSent = gsi_false; + + do + { + // Login the user + ////////////////////////////////////////////////////// + rcode = m_loginDlg.DoModal(); + if(rcode != IDOK) + { + PostQuitMessage(1); + return FALSE; + } + + // See if they want to host or join. + //////////////////////////////////// + rcode = m_hostOrJoinDlg.DoModal(); + } + while(rcode != IDOK); + + m_GT2Connection = NULL; + m_hosting = (m_hostOrJoinDlg.m_hostOrJoin == HOSTORJOIN_HOST); + + CString str; + str.Format("ScRaceSample%s", m_hosting?" (hosting)":""); + SetWindowText(str); + + if(m_hosting) + result = SetupHosting(); + else + result = SetupJoining(); + + if(result && m_hosting) + { + m_startRace.EnableWindow(); + } + + return result; +} + +BOOL CScRaceSampleDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + SetIcon(m_hIcon, TRUE); + SetIcon(m_hIcon, FALSE); + + // Basic initialization. + //////////////////////// + Dlg = this; + sessionCreated = gsi_false; + connIdSet = gsi_false; + m_GT2Connection = NULL; + m_GT2Socket = NULL; + m_state = LOGGED_OUT; + m_countdown = 0; + m_racing = FALSE; + + + // Do Availability Check - Make sure backend is available + ///////////////////////////////////////////////////////// + GSIACResult aResult = GSIACWaiting; + GSIStartAvailableCheck(SCRACE_GAMENAME); + while(aResult == GSIACWaiting) + { + aResult = GSIAvailableCheckThink(); + msleep(5); + } + + if (aResult == GSIACUnavailable) + { + MessageBox("Online service for ScRaceSample is no longer available."); + return FALSE; + } + + if (aResult == GSIACTemporarilyUnavailable) + { + MessageBox("Online service for ScRaceSample is temporarily down for maintenance."); + return FALSE; + } + + + // Initialize SDK core object - used for both the AuthService and the Competition SDK + ///////////////////////////////// + gsCoreInitialize(); + + + // Initialize the Competition SDK - all users submit a snapshot + ///////////////////////////////// + if (scInitialize(SCRACE_GAMEID, &m_interface) != SCResult_NO_ERROR) + { + MessageBox("Out of memory exception - application failed to initialize."); + return FALSE; + } + gPlayerData.mStatsInterface = m_interface; + + + // Set a think timer. + ///////////////////// + SetTimer(TIMER_THINK, 50, NULL); + + return TRUE; +} + +void CScRaceSampleDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CDialog::OnPaint(); + } +} + +HCURSOR CScRaceSampleDlg::OnQueryDragIcon() +{ + return (HCURSOR) m_hIcon; +} + +void CScRaceSampleDlg::OnLogout() +{ + if (m_racing && !m_reportSent) + { + // If we close the connection explicitly, we should report our stats + ////////////////////////////////////////////////////////////////////// + MessageBox("Logging out - Sending Broken report"); + + m_disconnect = gsi_true; + ReportStats(); + + // Reset progress bars + ////////////////////// + m_localProgress.SetPos(0); + m_remoteProgress.SetPos(0); + } + + // Clean stuff up. + ////////////////// + if(m_GT2Connection) + { + gt2CloseConnection(m_GT2Connection); + m_GT2Connection = NULL; + } + if(m_GT2Socket) + { + gt2CloseSocket(m_GT2Socket); + m_GT2Socket = NULL; + } + if(m_waitingDlg.m_hWnd && m_waitingDlg.IsWindowEnabled()) + { + m_waitingDlg.EndDialog(IDCANCEL); + } + + m_state = LOGGED_OUT; +} + +void CScRaceSampleDlg::OnDestroy() +{ + CDialog::OnDestroy(); + + if (m_racing && !m_reportSent) + { + // If we attempt to kill the app by hard-killing it or exiting while in + // the middle of a game, report it as a disconnect + ////////////////////////////////////////////////////////////////////// + MessageBox("Hard Close - Sending Broken report"); + + m_disconnect = gsi_true; + ReportStats(); + + // Reset progress bars + ////////////////////// + m_localProgress.SetPos(0); + m_remoteProgress.SetPos(0); + } + + scShutdown(m_interface); + m_interface = NULL; + + gsCoreShutdown(); + + // Wait for core shutdown + // (should be instantaneous unless you have multiple cores) + while(gsCoreIsShutdown() == GSCore_SHUTDOWN_PENDING) + { + gsCoreThink(0); + msleep(5); + } + + ghttpCleanup(); + + if (m_GT2Connection) + { + gt2CloseConnection(m_GT2Connection); + m_GT2Connection = NULL; + } + if (m_GT2Socket) + { + gt2CloseSocket(m_GT2Socket); + m_GT2Socket = NULL; + } +} + +void CScRaceSampleDlg::OnTimer(UINT nIDEvent) +{ + CString debugStr; + + if(nIDEvent == TIMER_THINK) + { + static BOOL thinking; + + if(!thinking) + { + thinking = TRUE; + + // Think so SDKs can process + ///////////////////////////////////////////// + if (m_GT2Socket) + gt2Think(m_GT2Socket); + ghttpThink(); + scThink(m_interface); + +// ********************************************************** // +// ************************* HOST LOGIC ********************* // +// ********************************************************** // + + if(m_state == HOST_EXCHANGE_CERT) + { + char buffer[520]; + char cert[512]; + int rcode; + gsi_u32 certLen; + + // Store cert in a binary buffer for easy exchange + ///////////////////////////////////////////// + wsLoginCertWriteBinary(&gPlayerData.mCertificate, cert, sizeof(cert), &certLen); + + // Exchange certificates with the other player to validate (step 1) + ///////////////////////////////////////////// + rcode = gtEncode(MSG_TO_CLIENT_CERT_TYPE, MSG_TO_CLIENT_CERT, buffer, sizeof(buffer), gPlayerData.mProfileId, cert, certLen); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)buffer, rcode, GT2True); + + // Wait for a reply + ///////////////////////////////////////////// + m_state = HOST_WAITING; + } + else if(m_state == HOST_VERIFY_CERT) + { + // Validate authentication certificates (step 2) + ///////////////////////////////////////////// + Dlg->m_remoteCertificate.mIsValid = wsLoginCertIsValid(&Dlg->m_remoteCertificate); + if (gsi_is_false(Dlg->m_remoteCertificate.mIsValid)) + { + MessageBox("Remote player has an invalid certificate, cancelling game."); + m_waitingDlg.EndDialog(IDCANCEL); + Logout(); + } + else + m_state = HOST_EXCHANGE_KEYS; + } + else if(m_state == HOST_EXCHANGE_KEYS) + { + char buffer[512]; + int rcode; + SCPeerKeyExchangeMsg exchangeMsg; + + // P2P encryption exchange keys (step 3) + ///////////////////////////////////////////// + + // Each player should create a key for receiving data from the remote player + // For extra security, we use a different encryption key for each channel + ///////////////////////////////////////////// + scPeerCipherInit(&gPlayerData.mCertificate, &gPlayerData.mPeerRecvCipher); + + // Create a key exchange message for transmitting the key to the other player + // using the remote player's certificate to encrypt the cipher + ///////////////////////////////////////////// + scPeerCipherCreateKeyExchangeMsg(&m_remoteCertificate, &gPlayerData.mPeerRecvCipher, exchangeMsg); + + // Now send the key to the other player + ///////////////////////////////////////////// + rcode = gtEncode(MSG_TO_CLIENT_KEYS_TYPE, MSG_TO_CLIENT_KEYS, buffer, sizeof(buffer), exchangeMsg, GS_CRYPT_RSA_BYTE_SIZE); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)buffer, rcode, GT2True); + + // Wait for a reply + ///////////////////////////////////////////// + m_state = HOST_WAITING; + } + else if(m_state == HOST_CONNECTED) + { + if (m_waitingDlg.m_hWnd && m_waitingDlg.IsWindowEnabled()) + m_waitingDlg.EndDialog(IDOK); + m_state = RACING; + } + else if(m_state == HOST_SEND_SESSID) + { + if(sessionCreated) + { + int rcode; + char buffer[256]; + char sessionCrypt[SC_SESSION_GUID_SIZE]; + char connCrypt[SC_CONNECTION_GUID_SIZE]; + + // Encrypt the connID/session ID to send using P2P encryption + ///////////////////////////////////////////////////////// + memcpy(sessionCrypt, gPlayerData.mSessionId, SC_SESSION_GUID_SIZE); + scPeerCipherEncryptBufferIV(&gPlayerData.mPeerSendCipher, 1, (gsi_u8*)sessionCrypt, SC_SESSION_GUID_SIZE); + + memcpy(connCrypt, gPlayerData.mConnectionId, SC_CONNECTION_GUID_SIZE); + scPeerCipherEncryptBufferIV(&gPlayerData.mPeerSendCipher, 2, (gsi_u8*)connCrypt, SC_CONNECTION_GUID_SIZE); + + debugStr.Format("[HOST_SEND_SESSID] sessionCrypt: %.40s\r\n", sessionCrypt); + OutputDebugString(debugStr); + debugStr.Format("[HOST_SEND_SESSID] connCrypt: %.40s\r\n", connCrypt); + OutputDebugString(debugStr); + + + // Now the host sends the session ID & his conn ID to the client + ///////////////////////////////////////////// + rcode = gtEncode(MSG_SESSION_ID_TYPE, MSG_SESSION_ID, buffer, sizeof(buffer), + sessionCrypt, SC_SESSION_GUID_SIZE, connCrypt, SC_CONNECTION_GUID_SIZE); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)buffer, rcode, GT2True); + + + // Once session is created, set the session ID and report intention + ///////////////////////////////////////////// + scSetSessionId(m_interface, gPlayerData.mSessionId); + scSetReportIntention(m_interface, gPlayerData.mConnectionId, SCRACE_AUTHORITATIVE, &gPlayerData.mCertificate, &gPlayerData.mPrivateData, + setReportIntentionCallback, SCRACE_TIMEOUT_MS, NULL); + + sessionCreated = gsi_false; + + // Go back to connected state + ///////////////////////////////////////////// + m_state = HOST_CONNECTED; + } + } + else if(m_state == HOST_ERROR) + { + MessageBox("Error setting up hosting"); + m_waitingDlg.EndDialog(IDCANCEL); + } + +// ********************************************************** // +// **************** JOIN (CLIENT) LOGIC ********************* // +// ********************************************************** // + + else if(m_state == JOIN_EXCHANGE_CERT) + { + // Wait for host to send cert first + ///////////////////////////////////////////// + } + else if(m_state == JOIN_VERIFY_CERT) + { + // Validate authentication certificates (step 2) + ///////////////////////////////////////////// + Dlg->m_remoteCertificate.mIsValid = wsLoginCertIsValid(&Dlg->m_remoteCertificate); + if (gsi_is_false(Dlg->m_remoteCertificate.mIsValid)) + { + MessageBox("Remote player has an invalid certificate, cancelling game."); + m_waitingDlg.EndDialog(IDCANCEL); + Logout(); + } + else + m_state = JOIN_EXCHANGE_KEYS; + } + else if(m_state == JOIN_EXCHANGE_KEYS) + { + // Wait for host to send keys first + ///////////////////////////////////////////// + } + else if(m_state == JOIN_CONNECTED) + { + if(m_waitingDlg.m_hWnd && m_waitingDlg.IsWindowEnabled()) + m_waitingDlg.EndDialog(IDOK); + } + else if(m_state == JOIN_SEND_CONNID) + { + // Once connection ID has been set, relay it to the host + ///////////////////////////////////////////// + if (connIdSet) + { + int rcode; + char buffer[128]; + char connCrypt[SC_CONNECTION_GUID_SIZE]; + + // Encrypt the connection ID to send using P2P encryption + ///////////////////////////////////////////////////////// + memcpy(connCrypt, gPlayerData.mConnectionId, SC_CONNECTION_GUID_SIZE); + scPeerCipherEncryptBufferIV(&gPlayerData.mPeerSendCipher, 1, (gsi_u8*)connCrypt, SC_CONNECTION_GUID_SIZE); + + debugStr.Format("[JOIN_SEND_CONNID] connCrypt: %.40s\r\n", connCrypt); + OutputDebugString(debugStr); + + + // Client needs to send the host his/her connection ID + ///////////////////////////////////////////// + rcode = gtEncode(MSG_CONNECTION_ID_TYPE, MSG_CONNECTION_ID, buffer, sizeof(buffer), connCrypt, SC_CONNECTION_GUID_SIZE); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)buffer, rcode, GT2True); + + connIdSet = gsi_false; + + // Go back to the connected state + ///////////////////////////////////////////// + m_state = JOIN_CONNECTED; + } + } + else if(m_state == JOIN_ERROR) + { + MessageBox("Error joining a game"); + m_waitingDlg.EndDialog(IDCANCEL); + } + + thinking = FALSE; + } + + if(m_state == LOGGED_OUT) + { + if(!Dlg->SetupMatch()) + { + MessageBox("Error setting up the match"); + Logout(); + } + } + + // Are we racing? + ///////////////// + if(m_racing) + { + // Did we finish? + ///////////////// + if(m_localTime) + { + // Did we both finish? + ////////////////////// + if(m_remoteTime) + { + // Done racing. + /////////////// + m_racing = FALSE; + + m_info = "Race Complete"; + UpdateData(FALSE); + + // Show the times. + ////////////////// + CString message; + if(m_localTime < m_remoteTime) + { + message.Format("You won!\n%0.3fs to %0.3fs", m_localTime / 1000.0, m_remoteTime / 1000.0); + m_win = gsi_true; + } + else if(m_remoteTime < m_localTime) + { + message.Format("You lost!\n%0.3fs to %0.3fs", m_localTime / 1000.0, m_remoteTime / 1000.0); + } + else + { + message.Format("You tied!\n%0.3fs", m_localTime / 1000.0); + m_tie = gsi_true; + } + MessageBox(message); + + + // Report the stats. + //////////////////// + if (!m_reportSent) + { + m_disconnect = gsi_false; + ReportStats(); + } + + m_localProgress.SetPos(0); + m_remoteProgress.SetPos(0); + } + } + else + { + char buffer[64]; + int rcode; + + // Let our opponent know how far we are. + //////////////////////////////////////// + rcode = gtEncode(MSG_PROGRESS_TYPE, MSG_PROGRESS, buffer, sizeof(buffer), m_numSteps); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)buffer, rcode, GT2False); + } + } + } + else if(nIDEvent == TIMER_COUNTDOWN) + { + m_countdown--; + if(m_countdown <= 0) + KillTimer(TIMER_COUNTDOWN); + + if(m_countdown < 0) + return; + + Countdown(); + if(!m_countdown) + StartRace(); + } + + CDialog::OnTimer(nIDEvent); +} + +void CScRaceSampleDlg::Logout() +{ + OnLogout(); +} + +void CScRaceSampleDlg::Countdown() +{ + // If report was just recently submitted, reset the submission flag + /////////////////////////////////////////////////////////////////// + if (m_reportSent) + m_reportSent = gsi_false; + + if(m_hosting) + { + int rcode; + char message[32]; + + rcode = gtEncode(MSG_COUNTDOWN_TYPE, MSG_COUNTDOWN, message, sizeof(message), m_countdown); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)message, rcode, GT2True); + } + + if(m_countdown) + { + UpdateData(); + + m_info.Format("Race starts in %ds", m_countdown); + + UpdateData(FALSE); + } +} + +void CScRaceSampleDlg::OnStart() +{ + // The countdown here could be thought of as a loading screen. + // During this loading phase - the Host will create the game session and notify + // the other players of the session ID. All players will set their report intentions. + ///////////////////////////////////////////// + if (m_hosting) + { + scCreateSession(m_interface, &gPlayerData.mCertificate, &gPlayerData.mPrivateData, + createSessionCallback, SCRACE_TIMEOUT_MS, NULL); + m_state = HOST_SEND_SESSID; + } + + // Start the countdown. + /////////////////////// + m_countdown = COUNTDOWN_START; + SetTimer(TIMER_COUNTDOWN, 1000, NULL); + Countdown(); +} + +BOOL CScRaceSampleDlg::PreTranslateMessage(MSG* pMsg) +{ + if(pMsg->message == WM_KEYDOWN) + { + int nChar = pMsg->wParam; + if((nChar == 'Z') || (nChar == 'X')) + { + if((pMsg->lParam & 0xFFFF) == 1) + { + CString str; + BOOL stepped = FALSE; + + if((nChar == 'Z') && (m_step != LEFT)) + { + m_step = LEFT; + m_numSteps++; + stepped = TRUE; + } + else if ((nChar == 'X') && (m_step != RIGHT)) + { + m_step = RIGHT; + m_numSteps++; + stepped = TRUE; + } + + if(stepped && m_racing) + { + m_localProgress.SetPos(m_numSteps); + if(m_numSteps == m_totalSteps) + { + m_localTime = (GetTickCount() - m_start); + str.Format("%0.3fs\n", m_localTime / 1000.0); + OutputDebugString(str); + //MessageBox(str); + + if(!m_remoteTime) + { + UpdateData(); + m_info = "Waiting for opponent"; + UpdateData(FALSE); + } + + // Let them know we finished. + ///////////////////////////// + char buffer[32]; + int rcode; + rcode = gtEncode(MSG_END_RACE_TYPE, MSG_END_RACE, buffer, sizeof(buffer), m_localTime); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)buffer, rcode, GT2True); + } + } + } + + return TRUE; + } + } + + return CDialog::PreTranslateMessage(pMsg); +} + +void CScRaceSampleDlg::StartRace() +{ + if(m_hosting) + { + int rcode; + char buffer[32]; + rcode = gtEncode(MSG_START_RACE_TYPE, MSG_START_RACE, buffer, sizeof(buffer)); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)buffer, rcode, GT2True); + } + + m_localTime = 0; + m_remoteTime = 0; + m_racing = TRUE; + m_numSteps = 0; + m_step = NONE; + m_racing = TRUE; + m_start = GetTickCount(); + m_totalSteps = RACE_STEPS; + m_localProgress.SetRange(0, m_totalSteps); + m_localProgress.SetPos(0); + m_remoteProgress.SetRange(0, m_totalSteps); + m_remoteProgress.SetPos(0); + + // initialize stats markers + ///////////////////////////////////////////// + m_win = gsi_false; + m_tie = gsi_false; + m_disconnect = gsi_false; + + + UpdateData(); + m_info.Format("GO!"); + UpdateData(FALSE); +} + +void CScRaceSampleDlg::ReportStats() +{ + SCReportPtr aReport = NULL; + SCResult aResult = SCResult_NO_ERROR; + SCGameResult myGameResult, opponentGameResult; + int myTeam, opponentTeam; + char myTeamName[64], opponentTeamName[64]; + int numPlayers = SCRACE_NUM_PLAYERS; + int numTeams = SCRACE_NUM_TEAMS; + + + // Determine winners and losers + ///////////////////////////////////////////// + if (!m_disconnect) + { + if (m_win) + { + myGameResult = SCGameResult_WIN; + opponentGameResult = SCGameResult_LOSS; + } + else if (!m_tie) + { + myGameResult = SCGameResult_LOSS; + opponentGameResult = SCGameResult_WIN; + } + else + { + myGameResult = SCGameResult_DRAW; + opponentGameResult = SCGameResult_DRAW; + } + } + else + { + //report disconnected game - don't report any keys + myGameResult = SCGameResult_DISCONNECT; + opponentGameResult = SCGameResult_DISCONNECT; + } + + + // Determine teams, and who is on which + ///////////////////////////////////////////// + if (m_hosting) + { + myTeam = SCRACE_HOST_TEAM; + opponentTeam = SCRACE_CLIENT_TEAM; + } + else + { + myTeam = SCRACE_CLIENT_TEAM; + opponentTeam = SCRACE_HOST_TEAM; + } + + sprintf(myTeamName, "%s's Team", m_loginDlg.m_nick); + sprintf(opponentTeamName, "%s's Team", m_remoteCertificate.mProfileNick); + + + // Create the report and begin building it + ///////////////////////////////////////////// + aResult = scCreateReport(m_interface, ATLAS_RULE_SET_VERSION, numPlayers, numTeams, &aReport); + if (aResult != SCResult_NO_ERROR) + { + MessageBox("Failed to Create Report - Out of memory"); + return; + } + + // Non-player data + ///////////////////////////////////////////// + scReportBeginGlobalData(aReport); + // no global data reported + + // Player data + ///////////////////////////////////////////// + scReportBeginPlayerData(aReport); + + // Report your data + //////////////////// + scReportBeginNewPlayer(aReport); + scReportSetPlayerData(aReport, 0, gPlayerData.mConnectionId, myTeam, + myGameResult, gPlayerData.mProfileId, &gPlayerData.mCertificate, gPlayerData.mStatsAuthdata); + if (!m_disconnect) + scReportAddIntValue(aReport, RACE_TIME, m_localTime); + + // Report opponent data + //////////////////// + scReportBeginNewPlayer(aReport); + scReportSetPlayerData(aReport, 1, m_remoteConnId, opponentTeam, + opponentGameResult, m_remoteProfile, &m_remoteCertificate, gPlayerData.mStatsAuthdata); + if (!m_disconnect) + scReportAddIntValue(aReport, RACE_TIME, m_remoteTime); + + + // Team data + ///////////////////////////////////////////// + scReportBeginTeamData(aReport); + + // Report your team data + //////////////////////// + scReportBeginNewTeam(aReport); + scReportSetTeamData(aReport, myTeam, myGameResult); + + // Report opponent team data + //////////////////////// + scReportBeginNewTeam(aReport); + scReportSetTeamData(aReport, opponentTeam, opponentGameResult); + + + // End the report and set GameStatus + if (!m_disconnect) + scReportEnd(aReport, SCRACE_AUTHORITATIVE, SCGameStatus_COMPLETE); + else + scReportEnd(aReport, SCRACE_AUTHORITATIVE, SCGameStatus_BROKEN); + + // Submit the Report + ///////////////////////////////////////////// + if (SCResult_NO_ERROR != scSubmitReport(m_interface, aReport, SCRACE_AUTHORITATIVE, &gPlayerData.mCertificate, + &gPlayerData.mPrivateData, submitReportCallback, SCRACE_TIMEOUT_MS, NULL)) + { + MessageBox("Failed to submit Stats Report - Out of memory"); + return; + } + + // To keep the logic clean, wait for submission to finish and then cleanup the report buffer + ///////////////////////////////////////////// + waitForScCallbacks(m_interface, 1); + + m_reportSent = gsi_true; //mark that we've submitted a report for this session + + // Cleanup + ///////////////////////////////////////////// + scDestroyReport(aReport); + aReport = NULL; +} diff --git a/code/gamespy/sc/scRaceSample/ScRaceSampleDlg.h b/code/gamespy/sc/scRaceSample/ScRaceSampleDlg.h new file mode 100644 index 00000000..87a24b5a --- /dev/null +++ b/code/gamespy/sc/scRaceSample/ScRaceSampleDlg.h @@ -0,0 +1,135 @@ +// ladderTrackDlg.h : header file +// + +#if !defined(AFX_LADDERTRACKDLG_H__82121F55_1FDB_427A_B616_B59EB16C5E6D__INCLUDED_) +#define AFX_LADDERTRACKDLG_H__82121F55_1FDB_427A_B616_B59EB16C5E6D__INCLUDED_ + +#include "LoginDlg.h" +#include "HostOrJoinDlg.h" +#include "..\..\GT2\gt2.h" // Added by ClassView +#include "..\..\GT2\gt2Encode.h" +#include "WaitingDlg.h" // Added by ClassView + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +///////////////////////////////////////////////////////////////////////////// +// CScRaceSampleDlg dialog + +#define LOGGED_OUT 1 +#define SETTING_UP 2 +#define RACING 3 + +#define HOST_LISTENING 11 +#define HOST_CONNECTED 12 +#define HOST_ERROR 13 +#define HOST_EXCHANGE_CERT 14 +#define HOST_VERIFY_CERT 15 +#define HOST_EXCHANGE_KEYS 16 +#define HOST_WAITING 17 +#define HOST_SEND_SESSID 18 + +#define JOIN_CONNECTING 21 +#define JOIN_CONNECTED 22 +#define JOIN_ERROR 23 +#define JOIN_EXCHANGE_CERT 24 +#define JOIN_VERIFY_CERT 25 +#define JOIN_EXCHANGE_KEYS 26 +#define JOIN_WAITING 27 +#define JOIN_SEND_CONNID 28 + + +#define NONE -1 +#define LEFT 0 +#define RIGHT 1 + +#define RACE_STEPS 60 + +extern SamplePlayerData gPlayerData; + +class CScRaceSampleDlg : public CDialog +{ +// Construction +public: + void FakeStats(); + CString m_remoteNick; + DWORD m_remoteTime; + DWORD m_localTime; + int m_totalSteps; + int m_step; + int m_numSteps; + DWORD m_start; + BOOL m_racing; + int m_countdown; + CString m_remoteResponse; + int m_state; + BOOL m_hosting; + CString m_challenge; + + // stats markers + gsi_bool m_win; + gsi_bool m_tie; + gsi_bool m_disconnect; + gsi_bool m_reportSent; + + CLoginDlg m_loginDlg; + CHostOrJoinDlg m_hostOrJoinDlg; + CWaitingDlg m_waitingDlg; + + GT2Socket m_GT2Socket; // raw socket + GT2Connection m_GT2Connection; // established connection + + SCInterfacePtr m_interface; //pointer to the stats interface + int m_remoteProfile; + GSLoginCertificate m_remoteCertificate; + gsi_u8 m_remoteConnId[SC_CONNECTION_GUID_SIZE]; + + + BOOL SetupHosting(); + BOOL SetupJoining(); + BOOL SetupMatch(); + void Countdown(); + void Logout(); + void StartRace(); + void ReportStats(); + CScRaceSampleDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CScRaceSampleDlg) + enum { IDD = IDD_SCRACESAMPLE_DIALOG }; + CButton m_startRace; + CProgressCtrl m_remoteProgress; + CProgressCtrl m_localProgress; + CString m_info; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CScRaceSampleDlg) + public: + virtual BOOL PreTranslateMessage(MSG* pMsg); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + HICON m_hIcon; + + // Generated message map functions + //{{AFX_MSG(CScRaceSampleDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + afx_msg void OnStart(); + afx_msg void OnDestroy(); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnLogout(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_LADDERTRACKDLG_H__82121F55_1FDB_427A_B616_B59EB16C5E6D__INCLUDED_) diff --git a/code/gamespy/sc/scRaceSample/StdAfx.cpp b/code/gamespy/sc/scRaceSample/StdAfx.cpp new file mode 100644 index 00000000..a891b5d9 --- /dev/null +++ b/code/gamespy/sc/scRaceSample/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// ScRaceSample.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/code/gamespy/sc/scRaceSample/StdAfx.h b/code/gamespy/sc/scRaceSample/StdAfx.h new file mode 100644 index 00000000..83bff5f8 --- /dev/null +++ b/code/gamespy/sc/scRaceSample/StdAfx.h @@ -0,0 +1,32 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__207466EE_5E75_4F57_8FC6_639F9C33B505__INCLUDED_) +#define AFX_STDAFX_H__207466EE_5E75_4F57_8FC6_639F9C33B505__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +#include "../sc.h" +//#include "atlas_Competition_Race_Sample_App_v1.h" // Admin Site generated Header file +#include "atlas_sc_race_v1.h" // Admin Site generated Header file +#include "../../gp/gp.h" + + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__207466EE_5E75_4F57_8FC6_639F9C33B505__INCLUDED_) diff --git a/code/gamespy/sc/scRaceSample/WaitingDlg.cpp b/code/gamespy/sc/scRaceSample/WaitingDlg.cpp new file mode 100644 index 00000000..eb85e08e --- /dev/null +++ b/code/gamespy/sc/scRaceSample/WaitingDlg.cpp @@ -0,0 +1,43 @@ +// WaitingDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "ScRaceSample.h" +#include "WaitingDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CWaitingDlg dialog + + +CWaitingDlg::CWaitingDlg(CWnd* pParent /*=NULL*/) + : CDialog(CWaitingDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CWaitingDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void CWaitingDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CWaitingDlg) + // NOTE: the ClassWizard will add DDX and DDV calls here + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CWaitingDlg, CDialog) + //{{AFX_MSG_MAP(CWaitingDlg) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CWaitingDlg message handlers diff --git a/code/gamespy/sc/scRaceSample/WaitingDlg.h b/code/gamespy/sc/scRaceSample/WaitingDlg.h new file mode 100644 index 00000000..e5d262bd --- /dev/null +++ b/code/gamespy/sc/scRaceSample/WaitingDlg.h @@ -0,0 +1,46 @@ +#if !defined(AFX_WAITINGDLG_H__874B6CC6_8058_4BDF_B714_8FE3610A12B4__INCLUDED_) +#define AFX_WAITINGDLG_H__874B6CC6_8058_4BDF_B714_8FE3610A12B4__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// WaitingDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CWaitingDlg dialog + +class CWaitingDlg : public CDialog +{ +// Construction +public: + CWaitingDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CWaitingDlg) + enum { IDD = IDD_WAITING }; + // NOTE: the ClassWizard will add data members here + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CWaitingDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CWaitingDlg) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_WAITINGDLG_H__874B6CC6_8058_4BDF_B714_8FE3610A12B4__INCLUDED_) diff --git a/code/gamespy/sc/scRaceSample/atlas_Competition_Race_Sample_App_v1.h b/code/gamespy/sc/scRaceSample/atlas_Competition_Race_Sample_App_v1.h new file mode 100644 index 00000000..c15b246a --- /dev/null +++ b/code/gamespy/sc/scRaceSample/atlas_Competition_Race_Sample_App_v1.h @@ -0,0 +1,27 @@ +/////////////////////////////////////////////////////////////////////////////// +// GameSpy ATLAS Competition System Header File +// +// NOTE: This is an auto-generated file, do not edit this file directly. +/////////////////////////////////////////////////////////////////////////////// + +#define ATLAS_RULE_SET_VERSION 1 + +// KEYS +// Use these key ID's to report match data for your game. + +#define RACE_TIME 1 // Player's race time for the match (milliseconds). + +/////////////////////////////////////////////////////////////////////////////// + +// STATS +// Use these stat ID's to query aggregate statistics for your game. + +#define CAREER_WINS 1 +#define CAREER_LOSSES 2 +#define BEST_RACE_TIME 3 // Player's career best race time (milliseconds). +#define WORST_RACE_TIME 4 // Player's career worst race time (milliseconds). +#define TOTAL_MATCHES 5 +#define AVERAGE_RACE_TIME 6 // Player's average race time per match (milliseconds/match). +#define CURRENT_WIN_STREAK 7 +#define CURRENT_LOSS_STREAK 8 +#define TOTAL_RACE_TIME 9 // Player's total race time for all matches (milliseconds). \ No newline at end of file diff --git a/code/gamespy/sc/scRaceSample/atlas_sc_race_v1.c b/code/gamespy/sc/scRaceSample/atlas_sc_race_v1.c new file mode 100644 index 00000000..aff4a9e4 --- /dev/null +++ b/code/gamespy/sc/scRaceSample/atlas_sc_race_v1.c @@ -0,0 +1,130 @@ +/////////////////////////////////////////////////////////////////////////////// +// GameSpy ATLAS Competition System Source File +// +// NOTE: This is an auto-generated file, do not edit this file directly. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "atlas_sc_race_v1.h" + +int atlas_rule_set_version = 1; + +int ATLAS_GET_KEY(char* keyName) +{ + if(!keyName) + return 0; + + if(!strcmp("RACE_TIME", keyName)) + return RACE_TIME; + + return 0; +} + +char* ATLAS_GET_KEY_NAME(int keyId) +{ + if(keyId <= 0) + return ""; + + if(keyId == RACE_TIME) + return "RACE_TIME"; + + return ""; +} + +int ATLAS_GET_STAT(char* statName) +{ + if(!statName) + return 0; + + if(!strcmp("CAREER_WINS", statName)) + return CAREER_WINS; + else if(!strcmp("CAREER_LOSSES", statName)) + return CAREER_LOSSES; + else if(!strcmp("BEST_RACE_TIME", statName)) + return BEST_RACE_TIME; + else if(!strcmp("WORST_RACE_TIME", statName)) + return WORST_RACE_TIME; + else if(!strcmp("TOTAL_MATCHES", statName)) + return TOTAL_MATCHES; + else if(!strcmp("AVERAGE_RACE_TIME", statName)) + return AVERAGE_RACE_TIME; + else if(!strcmp("CURRENT_WIN_STREAK", statName)) + return CURRENT_WIN_STREAK; + else if(!strcmp("CURRENT_LOSS_STREAK", statName)) + return CURRENT_LOSS_STREAK; + else if(!strcmp("TOTAL_RACE_TIME", statName)) + return TOTAL_RACE_TIME; + else if(!strcmp("CAREER_DISCONNECTS", statName)) + return CAREER_DISCONNECTS; + else if(!strcmp("DISCONNECT_RATE", statName)) + return DISCONNECT_RATE; + else if(!strcmp("CAREER_DRAWS", statName)) + return CAREER_DRAWS; + else if(!strcmp("CURRENT_DRAW_STREAK", statName)) + return CURRENT_DRAW_STREAK; + else if(!strcmp("CAREER_LONGEST_WIN_STREAK", statName)) + return CAREER_LONGEST_WIN_STREAK; + else if(!strcmp("CAREER_LONGEST_LOSS_STREAK", statName)) + return CAREER_LONGEST_LOSS_STREAK; + else if(!strcmp("CAREER_LONGEST_DRAW_STREAK", statName)) + return CAREER_LONGEST_DRAW_STREAK; + else if(!strcmp("TOTAL_COMPLETE_MATCHES", statName)) + return TOTAL_COMPLETE_MATCHES; + else if(!strcmp("RICHARD_TEST1", statName)) + return RICHARD_TEST1; + else if(!strcmp("RICHARD_TEST2", statName)) + return RICHARD_TEST2; + else if(!strcmp("RICHARD_TEST3", statName)) + return RICHARD_TEST3; + + return 0; +} +char* ATLAS_GET_STAT_NAME(int statId) +{ + if(statId <= 0) + return ""; + + if(statId == CAREER_WINS) + return "CAREER_WINS"; + else if(statId == CAREER_LOSSES) + return "CAREER_LOSSES"; + else if(statId == BEST_RACE_TIME) + return "BEST_RACE_TIME"; + else if(statId == WORST_RACE_TIME) + return "WORST_RACE_TIME"; + else if(statId == TOTAL_MATCHES) + return "TOTAL_MATCHES"; + else if(statId == AVERAGE_RACE_TIME) + return "AVERAGE_RACE_TIME"; + else if(statId == CURRENT_WIN_STREAK) + return "CURRENT_WIN_STREAK"; + else if(statId == CURRENT_LOSS_STREAK) + return "CURRENT_LOSS_STREAK"; + else if(statId == TOTAL_RACE_TIME) + return "TOTAL_RACE_TIME"; + else if(statId == CAREER_DISCONNECTS) + return "CAREER_DISCONNECTS"; + else if(statId == DISCONNECT_RATE) + return "DISCONNECT_RATE"; + else if(statId == CAREER_DRAWS) + return "CAREER_DRAWS"; + else if(statId == CURRENT_DRAW_STREAK) + return "CURRENT_DRAW_STREAK"; + else if(statId == CAREER_LONGEST_WIN_STREAK) + return "CAREER_LONGEST_WIN_STREAK"; + else if(statId == CAREER_LONGEST_LOSS_STREAK) + return "CAREER_LONGEST_LOSS_STREAK"; + else if(statId == CAREER_LONGEST_DRAW_STREAK) + return "CAREER_LONGEST_DRAW_STREAK"; + else if(statId == TOTAL_COMPLETE_MATCHES) + return "TOTAL_COMPLETE_MATCHES"; + else if(statId == RICHARD_TEST1) + return "RICHARD_TEST1"; + else if(statId == RICHARD_TEST2) + return "RICHARD_TEST2"; + else if(statId == RICHARD_TEST3) + return "RICHARD_TEST3"; + + return ""; +} + diff --git a/code/gamespy/sc/scRaceSample/atlas_sc_race_v1.h b/code/gamespy/sc/scRaceSample/atlas_sc_race_v1.h new file mode 100644 index 00000000..03346d1d --- /dev/null +++ b/code/gamespy/sc/scRaceSample/atlas_sc_race_v1.h @@ -0,0 +1,57 @@ +/////////////////////////////////////////////////////////////////////////////// +// GameSpy ATLAS Competition System Header File +// +// NOTE: This is an auto-generated file, do not edit this file directly. +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _ATLAS_SC_RACE_V1_H_ +#define _ATLAS_SC_RACE_V1_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +extern int ATLAS_GET_KEY(char* keyName); +extern char* ATLAS_GET_KEY_NAME(int keyId); +extern int ATLAS_GET_STAT(char* statName); +extern char* ATLAS_GET_STAT_NAME(int statId); + +#define ATLAS_RULE_SET_VERSION 1 + +// KEYS +// Use these key ID's to report match data for your game. + +#define RACE_TIME 1 // [TYPE: int] [DESC: Player's race time for the match (milliseconds).] + +/////////////////////////////////////////////////////////////////////////////// + +// STATS +// Use these stat ID's to query aggregate statistics for your game. + +#define CAREER_WINS 1 // [TYPE: int] +#define CAREER_LOSSES 2 // [TYPE: int] +#define BEST_RACE_TIME 3 // [TYPE: int] [DESC: Player's career best race time (milliseconds).] +#define WORST_RACE_TIME 4 // [TYPE: int] [DESC: Player's career worst race time (milliseconds).] +#define TOTAL_MATCHES 5 // [TYPE: int] +#define AVERAGE_RACE_TIME 6 // [TYPE: float] [DESC: Player's average race time per match (milliseconds/match).] +#define CURRENT_WIN_STREAK 7 // [TYPE: int] +#define CURRENT_LOSS_STREAK 8 // [TYPE: int] +#define TOTAL_RACE_TIME 9 // [TYPE: int] [DESC: Player's total race time for all matches (milliseconds).] +#define CAREER_DISCONNECTS 11 // [TYPE: int] [DESC: Player's total number of times disconnected.] +#define DISCONNECT_RATE 12 // [TYPE: float] [DESC: Player's disconnect rate (disconnects/matches).] +#define CAREER_DRAWS 13 // [TYPE: int] [DESC: Player's total number of draws (tied matches).] +#define CURRENT_DRAW_STREAK 14 // [TYPE: int] +#define CAREER_LONGEST_WIN_STREAK 15 // [TYPE: int] +#define CAREER_LONGEST_LOSS_STREAK 16 // [TYPE: int] +#define CAREER_LONGEST_DRAW_STREAK 17 // [TYPE: int] +#define TOTAL_COMPLETE_MATCHES 18 // [TYPE: int] [DESC: Total number of matches where the game went to completion (all win/loss/draw results).] +#define RICHARD_TEST1 19 // [TYPE: int] +#define RICHARD_TEST2 20 // [TYPE: int] +#define RICHARD_TEST3 21 // [TYPE: int] + + +#ifdef __cplusplus +} +#endif + +#endif // _ATLAS_SC_RACE_V1_H_ diff --git a/code/gamespy/sc/scRaceSample/res/ScRaceSample.ico b/code/gamespy/sc/scRaceSample/res/ScRaceSample.ico new file mode 100644 index 0000000000000000000000000000000000000000..7eef0bcbe6580a6f464d688906172c2d9de44262 GIT binary patch literal 1078 zcmc&zF>b>!3}jLb9s)T}@Kod(893@u8ajANzT`op9^o+)S?=nU(FD@%0s)Sg^oyC8{H z9myetc;MEP)59v(LMa~xK8Yu^jIR*H22uCFiq5%C{s7(PJi>o15i^bmX4(vPxWAio z9ryY#AU_jfnd047-@`)XzL?%iS$gQyFP{44kS9X)fN{{QoL~hO-&=q&20Zr*cxFAt PkaNE{wR~2C$NfnjhSXWT literal 0 HcmV?d00001 diff --git a/code/gamespy/sc/scRaceSample/res/ScRaceSample.rc2 b/code/gamespy/sc/scRaceSample/res/ScRaceSample.rc2 new file mode 100644 index 00000000..746d0bdb --- /dev/null +++ b/code/gamespy/sc/scRaceSample/res/ScRaceSample.rc2 @@ -0,0 +1,13 @@ +// +// SCRACESAMPLE.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/sc/scRaceSample/resource.h b/code/gamespy/sc/scRaceSample/resource.h new file mode 100644 index 00000000..5c15d825 --- /dev/null +++ b/code/gamespy/sc/scRaceSample/resource.h @@ -0,0 +1,34 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by ScRaceSample.rc +// +#define IDD_SCRACESAMPLE_DIALOG 102 +#define IDC_EMAIL 103 +#define IDC_NICK 104 +#define IDC_PASSWORD 105 +#define IDR_MAINFRAME 128 +#define IDD_LOGIN 129 +#define IDD_HOST_OR_JOIN 130 +#define IDD_WAITING 132 +#define IDC_HOST 1000 +#define IDC_LOCAL_POSITION 1000 +#define IDC_JOIN 1001 +#define IDC_REMOTE_POSITION 1001 +#define IDC_JOIN_ADDRESS 1002 +#define IDC_LOGOUT 1004 +#define IDC_START_RACE 1005 +#define IDC_INFO 1006 +#define IDC_LOCAL_PROGRESS 1007 +#define IDC_REMOTE_PROGRESS 1008 +#define IDC_UPDATE_POSITIONS 1009 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/gamespy/sc/sci.h b/code/gamespy/sc/sci.h new file mode 100644 index 00000000..db81d9d0 --- /dev/null +++ b/code/gamespy/sc/sci.h @@ -0,0 +1,34 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __SCI_H__ +#define __SCI_H__ + +/* Internal interface for S&C SDK -- Developers should use sc.h */ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sc.h" + + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Data types + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __SCI_H__ diff --git a/code/gamespy/sc/sciInterface.c b/code/gamespy/sc/sciInterface.c new file mode 100644 index 00000000..1bf05252 --- /dev/null +++ b/code/gamespy/sc/sciInterface.c @@ -0,0 +1,132 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sciInterface.h" + + +/* PRIVATE INTERFACE FUNCTIONS -- SCMAIN.C CONTAINS PUBLIC INTERFACE */ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +// This is declared as an extern so it can be overriden when testing +#define SC_SERVICE_URL_FORMAT "http://%s.comp.pubsvs." GSI_DOMAIN_NAME "/CompetitionService/CompetitionService.asmx" +char scServiceURL[SC_SERVICE_MAX_URL_LEN] = ""; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciInterfaceCreate(SCInterface** theInterfaceOut) +{ +#ifdef GSI_SC_STATIC_MEM + static SCInterface gStaticInterface; +#endif + + GS_ASSERT(theInterfaceOut != NULL); + + // Check to see if the availability check has been performed and if it has + // set the service URL prepended with the gamename + if (__GSIACResult == GSIACAvailable) + { + if (scServiceURL[0] == '\0') + snprintf(scServiceURL, SC_SERVICE_MAX_URL_LEN, SC_SERVICE_URL_FORMAT, __GSIACGamename); + } + else + return SCResult_NO_AVAILABILITY_CHECK; + +#ifdef GSI_SC_STATIC_MEM + *theInterfaceOut = &gStaticInterface; +#else + *theInterfaceOut = (SCInterface*)gsimalloc(sizeof(SCInterface)); + if (*theInterfaceOut == NULL) + { + return SCResult_OUT_OF_MEMORY; + } +#endif + + GS_ASSERT(*theInterfaceOut != NULL); + memset(*theInterfaceOut, 0, sizeof(SCInterface)); + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciInterfaceInit(SCInterface* theInterface) +{ + SCResult anInitResult = SCResult_NO_ERROR; + + GS_ASSERT(theInterface != NULL); + + anInitResult = sciWsInit(&theInterface->mWebServices, theInterface); + if (anInitResult != SCResult_NO_ERROR) + { + return anInitResult; + } + + memset(theInterface->mSessionId, 0, sizeof(theInterface->mSessionId)); + memset(theInterface->mConnectionId, 0, sizeof(theInterface->mConnectionId)); + + theInterface->mInit = gsi_true; + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void sciInterfaceDestroy(SCInterface* theInterface) +{ + GS_ASSERT(theInterface != NULL); + GS_ASSERT(theInterface->mInit); + + sciWsDestroy(&theInterface->mWebServices); + + memset(theInterface->mSessionId, 0, sizeof(theInterface->mSessionId)); + memset(theInterface->mConnectionId, 0, sizeof(theInterface->mConnectionId)); + theInterface->mSessionId[0] = 0xde; + theInterface->mSessionId[1] = 0xad; + theInterface->mConnectionId[0] = 0xde; + theInterface->mConnectionId[1] = 0xad; + + theInterface->mInit = gsi_false; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void sciInterfaceSetSessionId(SCInterface * theInterface, const char * theSessionId) +{ + GS_ASSERT(theInterface != NULL); + + if (theSessionId == NULL) + theInterface->mSessionId[0] = '\0'; + else + { + GS_ASSERT(strlen(theSessionId) < sizeof(theInterface->mSessionId)); + strcpy((char *)theInterface->mSessionId, theSessionId); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void sciInterfaceSetConnectionId(SCInterface * theInterface, const char * theConnectionId) +{ + GS_ASSERT(theInterface != NULL); + + if (theConnectionId == NULL) + theInterface->mConnectionId[0] = '\0'; + else + { + GS_ASSERT(strlen(theConnectionId) < sizeof(theInterface->mConnectionId)); + strcpy((char *)theInterface->mConnectionId, theConnectionId); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/sc/sciInterface.h b/code/gamespy/sc/sciInterface.h new file mode 100644 index 00000000..eb2562e3 --- /dev/null +++ b/code/gamespy/sc/sciInterface.h @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __SCIINTERFACE_H__ +#define __SCIINTERFACE_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sci.h" +#include "sciWebServices.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// SDK instance +typedef struct +{ + SCWebServices mWebServices; + gsi_u32 mGameId; + //gsi_u32 mOptionsFlags; + gsi_bool mInit; + const char * mServiceURL; + GSCoreMgr * mSdkCore; + gsi_u8 mSessionId[SC_SESSION_GUID_SIZE]; + gsi_u8 mConnectionId[SC_CONNECTION_GUID_SIZE]; +} SCInterface; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciInterfaceCreate (SCInterface** theInterfaceOut); +SCResult sciInterfaceInit (SCInterface* theInterface); +void sciInterfaceDestroy(SCInterface* theInterface); + +void sciInterfaceSetSessionId(SCInterface* theInterface, const char * theSessionId); +void sciInterfaceSetConnectionId(SCInterface* theInterface, const char * theConnectionId); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __SCINTERFACE_H__ diff --git a/code/gamespy/sc/sciMain.c b/code/gamespy/sc/sciMain.c new file mode 100644 index 00000000..19ec53ed --- /dev/null +++ b/code/gamespy/sc/sciMain.c @@ -0,0 +1,706 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sci.h" +#include "sciInterface.h" +#include "sciReport.h" + +#include "../md5.h" +#include "../common/gsRC4.h" + +/* PUBLIC INTERFACE FUNCTIONS - SCINTERFACE.C CONTAINS PRIVATE INTERFACE */ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scInitialize(int theGameId, SCInterfacePtr* theInterfaceOut) +{ + SCInterface* anInterface = NULL; + SCResult anInitResult = SCResult_NO_ERROR; + + // Check parameters + if (theInterfaceOut == NULL) + { + return SCResult_INVALID_PARAMETERS; + } + + // Clear out parameter + *theInterfaceOut = NULL; + + // Obtain the internal interface + anInitResult = sciInterfaceCreate(&anInterface); + if (anInitResult != SCResult_NO_ERROR) + { + return anInitResult; + } + + // Init the internal interface + anInitResult = sciInterfaceInit(anInterface); + if (anInitResult != SCResult_NO_ERROR) + { + return anInitResult; + } + + anInterface->mGameId = (gsi_u32)theGameId; + //anInterface->mOptionsFlags = (gsi_u32)theOptionsFlags; + + // Set the out parameter and return + *theInterfaceOut = anInterface; + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scShutdown(SCInterfacePtr theInterface) +{ + SCInterface* anInterface = (SCInterface*)theInterface; + + // Check parameters + if (anInterface == NULL) + { + return SCResult_INVALID_PARAMETERS; + } + + // Destroy interface if necessary + if (anInterface->mInit) + { + sciInterfaceDestroy(anInterface); + gsifree(anInterface); + } + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scThink(SCInterfacePtr theInterface) +{ + SCInterface* anInterface = (SCInterface*)theInterface; + + // Check parameters + if (anInterface == NULL) + { + return SCResult_INVALID_PARAMETERS; + } + if (!anInterface->mInit) + { + return SCResult_NOT_INITIALIZED; + } + + sciWsThink(&anInterface->mWebServices); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +const char * scGetSessionId(const SCInterfacePtr theInterface) +{ + SCInterface * anInterface = (SCInterface*)theInterface; + GS_ASSERT(theInterface != NULL); + GS_ASSERT(gsi_is_true(anInterface->mInit)); + return (char *)anInterface->mSessionId; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +const char * scGetConnectionId(const SCInterfacePtr theInterface) +{ + SCInterface * anInterface = (SCInterface*)theInterface; + GS_ASSERT(theInterface != NULL); + GS_ASSERT(gsi_is_true(anInterface->mInit)); + return (char *)anInterface->mConnectionId; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scSetSessionId(const SCInterfacePtr theInterface, const gsi_u8 theSessionId[SC_SESSION_GUID_SIZE]) +{ + SCInterface * anInterface = (SCInterface*)theInterface; + GS_ASSERT(theInterface != NULL); + GS_ASSERT(gsi_is_true(anInterface->mInit)); + + sciInterfaceSetSessionId(anInterface, (char *)theSessionId); + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// The certificate and private data may be NULL if the local client +// is an unauthenticated dedicated server +SCResult SC_CALL scCreateSession(SCInterfacePtr theInterface, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCCreateSessionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData) +{ + SCInterface * anInterface = (SCInterface*)theInterface; + GS_ASSERT(theInterface != NULL); + GS_ASSERT(theCertificate != NULL || (theCertificate==NULL && thePrivateData==NULL)); + + if (!wsLoginCertIsValid(theCertificate)) + return SCResult_INVALID_PARAMETERS; + + return sciWsCreateSession(&anInterface->mWebServices, anInterface->mGameId, theCertificate, thePrivateData, theCallback, theTimeoutMs, theUserData); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scCreateMatchlessSession(SCInterfacePtr theInterface, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCCreateSessionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData) +{ + SCInterface * anInterface = (SCInterface*)theInterface; + GS_ASSERT(theInterface != NULL); + GS_ASSERT(theCertificate != NULL || (theCertificate==NULL && thePrivateData==NULL)); + + if (!wsLoginCertIsValid(theCertificate)) + return SCResult_INVALID_PARAMETERS; + + return sciWsCreateMatchlessSession(&anInterface->mWebServices, anInterface->mGameId, theCertificate, thePrivateData, theCallback, theTimeoutMs, theUserData); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/* +SCResult SC_CALL scJoinSession(SCInterfacePtr theInterface, + GSLoginCertificate * theCertificate, + GSLoginPrivateData * thePrivateData, + gsi_u8 theSessionId[SC_SESSION_GUID_SIZE], + SCSetReportIntentionCallback theCallback, + gsi_time theTimeoutMs) +{ + SCInterface * anInterface = (SCInterface*)theInterface; + GS_ASSERT(theInterface != NULL); + + memcpy(anInterface->mSessionId, theSessionId, SC_SESSION_GUID_SIZE); + return SCResult_NO_ERROR; +}*/ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scSetReportIntention(const SCInterfacePtr theInterface, + const gsi_u8 theConnectionId[SC_CONNECTION_GUID_SIZE], + gsi_bool isAuthoritative, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCSetReportIntentionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData) +{ + SCInterface* anInterface = (SCInterface*)theInterface; + + // Check parameters + if (anInterface == NULL) + { + return SCResult_INVALID_PARAMETERS; + } + if (!theCertificate) + { + return SCResult_INVALID_PARAMETERS; + } + if (!thePrivateData) + { + return SCResult_INVALID_PARAMETERS; + } + if (!anInterface->mInit) + { + return SCResult_NOT_INITIALIZED; + } + + sciInterfaceSetConnectionId(anInterface, (const char *)theConnectionId); + // Call web service + return sciWsSetReportIntention(&anInterface->mWebServices, anInterface->mGameId, + (char *)anInterface->mSessionId, (char *)anInterface->mConnectionId, isAuthoritative, + theCertificate, thePrivateData, theCallback, theTimeoutMs, theUserData); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scSubmitReport(const SCInterfacePtr theInterface, + const SCReportPtr theReport, + gsi_bool isAuthoritative, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCSubmitReportCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData) +{ + SCInterface* anInterface = (SCInterface*)theInterface; + SCIReport *aReport = (SCIReport *)theReport; + //SCResult aResult = SCResult_NO_ERROR; + + // Check parameters + if (anInterface == NULL) + { + return SCResult_INVALID_PARAMETERS; + } + if (!anInterface->mInit) + { + return SCResult_NOT_INITIALIZED; + } + + // Generate report from report data + // aResult = sciGenerateReport(theReportData, theProfileID, &aReport); + //if (aResult != SCResult_NO_ERROR) + // return aResult; + + // Prepare the report hash + { + SCIReportHeader * header = (SCIReportHeader*)aReport->mBuffer.mData; + MD5_CTX md5; + // Clear out the checksum portion of the header so that the + // MD5 hash is calculated on the entire report without a checksum + memset(header->mChecksum, 0, sizeof(header->mChecksum)); + + MD5Init(&md5); + MD5Update(&md5, (unsigned char *)aReport->mBuffer.mData, aReport->mBuffer.mPos); + MD5Final(header->mChecksum, &md5); + } + + // Call web service + return sciWsSubmitReport(&anInterface->mWebServices, anInterface->mGameId, + (char *)anInterface->mSessionId, (char *)anInterface->mConnectionId, aReport, isAuthoritative, + theCertificate, thePrivateData, theCallback, theTimeoutMs, theUserData); + +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scCreateReport(const SCInterfacePtr theInterface, + gsi_u32 theHeaderVersion, + gsi_u32 thePlayerCount, + gsi_u32 theTeamCount, + SCReportPtr * theReportDataOut) +{ + SCResult aResult = SCResult_NO_ERROR; + SCIReport * aReport = NULL; + SCInterface * aInterface = (SCInterface*)theInterface; + + GS_ASSERT(theInterface != NULL); + GS_ASSERT(theReportDataOut != NULL); + if (theInterface == NULL) + return SCResult_INVALID_PARAMETERS; + if (theReportDataOut == NULL) + return SCResult_INVALID_PARAMETERS; + + aResult = sciCreateReport(aInterface->mSessionId, theHeaderVersion, + thePlayerCount, theTeamCount, &aReport); + if (aResult == SCResult_NO_ERROR) + *theReportDataOut = (SCReportPtr)aReport; + + return aResult; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportBeginGlobalData(SCReportPtr theReport) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + + return sciReportBeginGlobalData(aReport); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportBeginPlayerData(SCReportPtr theReport) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + + return sciReportBeginPlayerData(aReport); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportBeginTeamData(SCReportPtr theReport) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + + return sciReportBeginTeamData(aReport); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportBeginNewTeam(SCReportPtr theReport) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + //return SCResult_NO_ERROR; + return sciReportBeginNewTeam(aReport); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportBeginNewPlayer(SCReportPtr theReport) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + //return SCResult_NO_ERROR; + return sciReportBeginNewPlayer(aReport); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportEnd(SCReportPtr theReport, gsi_bool isAuth, SCGameStatus theStatus) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + + return sciReportEnd(aReport, isAuth, theStatus); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportSetPlayerData(SCReportPtr theReport, + gsi_u32 thePlayerIndex, + const gsi_u8 thePlayerConnectionId[SC_CONNECTION_GUID_SIZE], + gsi_u32 thePlayerTeamId, + SCGameResult theResult, + gsi_u32 theProfileId, + const GSLoginCertificate * theCertificate, + const gsi_u8 theAuthHash[16]) +{ + SCIReport * aReport = NULL; + SCResult aResult = SCResult_NO_ERROR; + gsi_u32 i; + gsi_bool isNewTeam = gsi_true; + + GS_ASSERT(theReport != NULL); + GS_ASSERT(theCertificate != NULL); + GSI_UNUSED(theProfileId); + aReport = (SCIReport*)theReport; + + // store the team ID here and use this in order to index the team game results + for (i=0; imNumTeamsReported; i++) + { + // has the team already been reported by another player? + if (aReport->mTeamIds[i] == thePlayerTeamId) + { + isNewTeam = gsi_false; + break; + } + } + + if (isNewTeam) + { + aReport->mNumTeamsReported++; + + // have they gone over the limit of teams? If so, this is an error + GS_ASSERT(aReport->mNumTeamsReported < SC_MAX_NUM_TEAMS); + if (aReport->mNumTeamsReported > SC_MAX_NUM_TEAMS) + return SCResult_OUT_OF_MEMORY; + + // if no problems, store the teamId + aReport->mTeamIds[aReport->mNumTeamsReported-1] = thePlayerTeamId; + } + + + if (aResult == SCResult_NO_ERROR) aResult = sciReportSetPlayerConnectionId(aReport, thePlayerIndex, thePlayerConnectionId); + if (aResult == SCResult_NO_ERROR) aResult = sciReportSetPlayerTeamIndex (aReport, thePlayerIndex, thePlayerTeamId); + if (aResult == SCResult_NO_ERROR) aResult = sciReportSetPlayerGameResult (aReport, thePlayerIndex, theResult); + if (aResult == SCResult_NO_ERROR) aResult = sciReportSetPlayerAuthInfo (aReport, thePlayerIndex, theCertificate, theAuthHash); + + return aResult; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportSetTeamData(const SCReportPtr theReport, + gsi_u32 theTeamId, + SCGameResult theResult) +{ + SCIReport * aReport = NULL; + gsi_u32 i; + gsi_i32 aTeamIndex = -1; + GS_ASSERT(theReport != NULL); + GS_ASSERT(theResult < SCGameResultMax); + + aReport = (SCIReport*)theReport; + + // redundancy check - have they gone over the max limit of teams? + GS_ASSERT(aReport->mNumTeamsReported < SC_MAX_NUM_TEAMS); + + // find the proper team index based on the teamId + for (i=0; imNumTeamsReported; i++) + { + if (aReport->mTeamIds[i] == theTeamId) + { + aTeamIndex = (gsi_i32)i; + break; + } + } + + // if no such team exists in our list, an invalid ID was given + if (aTeamIndex == -1) + return SCResult_INVALID_PARAMETERS; + + // the teamindex reported here needs to be 0 based, which is why we subtract 1 + return sciReportSetTeamGameResult(aReport, (gsi_u32)aTeamIndex, theResult); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportSetAsMatchless(const SCReportPtr theReport) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + return(sciReportSetAsMatchless(aReport)); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportAddIntValue(const SCReportPtr theReport, + gsi_u16 theKeyId, + gsi_i32 theValue) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + return sciReportAddIntValue(aReport, theKeyId, theValue); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportAddInt64Value(SCReportPtr theReport, + gsi_u16 theKeyId, + gsi_i64 theValue) +{ + SCIReport *aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport *)theReport; + return sciReportAddInt64Value(aReport, theKeyId, theValue); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportAddShortValue(const SCReportPtr theReport, + gsi_u16 theKeyId, + gsi_i16 theValue) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + return sciReportAddShortValue(aReport, theKeyId, theValue); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportAddByteValue(const SCReportPtr theReport, + gsi_u16 theKeyId, + gsi_i8 theValue) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + return sciReportAddByteValue(aReport, theKeyId, theValue); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportAddFloatValue(const SCReportPtr theReport, + gsi_u16 theKeyId, + float theValue) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + return sciReportAddFloatValue(aReport, theKeyId, theValue); + +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportAddStringValue(const SCReportPtr theReport, + gsi_u16 theKeyId, + const gsi_char * theValue) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + GS_ASSERT(theValue != NULL); + aReport = (SCIReport*)theReport; + return sciReportAddStringValue(aReport, theKeyId, theValue); + +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scDestroyReport(SCReportPtr theReport) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + + return sciDestroyReport(aReport); +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Create a random key for this cipher +SCResult SC_CALL scPeerCipherInit(const GSLoginCertificate * theLocalCert, SCPeerCipher * theCipher) +{ + // hardcoded to a 256-bit key block, although some ciphers will not use the entire block + + // What to use for a random seed? + // 1) 16 bytes from our Util_Rand seeded with current time + // 2) Hashed with 16 bytes from the cert public key (for a little extra randomness) + // 3) Repeat 1-2 for every 16 bytes of key block needed + unsigned char randBytes[32]; + int i=0; + MD5_CTX md5; + + GS_ASSERT(theCipher != NULL); + //CANNOT assert on constant expressions, they are meaningless. + //GS_ASSERT(GS_CRYPT_RSA_BYTE_SIZE >= 32); // crypt lib must support 128-bit keys. + + #if defined (GS_CRYPT_NO_RANDOM) + Util_RandSeed(0x2d2d2d2d); + #else + Util_RandSeed(current_time()); + #endif + + for (i=0; i < 32; i++) + randBytes[i] = (unsigned char)Util_RandInt(0x00, 0xFF); + + // Calc the first half, bytes 0-15 + MD5Init(&md5); + MD5Update(&md5, randBytes, 16); + MD5Update(&md5, (unsigned char*)theLocalCert->mPeerPublicKey.modulus.mData, 16); // first 16 bytes of the key + MD5Final(&theCipher->mKey[0], &md5); + + // Calc the second half, bytes 16-31 + MD5Init(&md5); + MD5Update(&md5, &randBytes[16], 16); + MD5Update(&md5, (unsigned char*)&theLocalCert->mPeerPublicKey.modulus.mData[16], 16); // last 16 bytes of the key + MD5Final(&theCipher->mKey[16], &md5); + + theCipher->mKeyLen = 32; + + RC4Init(&theCipher->mRC4, theCipher->mKey, (int)theCipher->mKeyLen); + theCipher->mInitialized = gsi_true; + + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Misc, GSIDebugLevel_Notice, "Created PeerCipher key block\r\n"); + gsDebugBinary(GSIDebugCat_App, GSIDebugType_Misc, GSIDebugLevel_Notice, (char *)theCipher->mKey, (gsi_i32)theCipher->mKeyLen); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scPeerCipherCreateKeyExchangeMsg(const GSLoginCertificate * theRemoteCert, const SCPeerCipher * theCipher, SCPeerKeyExchangeMsg theMsgOut) +{ + // Encrypt the key with the recipients public key, this makes it safe to transmit + if (0 == gsCryptRSAEncryptBuffer(&theRemoteCert->mPeerPublicKey, theCipher->mKey, theCipher->mKeyLen, (unsigned char *)theMsgOut)) + return SCResult_NO_ERROR; + else + return SCResult_UNKNOWN_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scPeerCipherParseKeyExchangeMsg (const GSLoginCertificate * theLocalCert, const GSLoginPrivateData * theCertPrivateData, const SCPeerKeyExchangeMsg theMsg, SCPeerCipher * theCipherOut) +{ + GSI_UNUSED(theLocalCert); + // Decrypt the key with the local player's private key + if (0 == gsCryptRSADecryptBuffer(&theCertPrivateData->mPeerPrivateKey, (unsigned char *)theMsg, theCipherOut->mKey, &theCipherOut->mKeyLen)) + { + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Misc, GSIDebugLevel_Notice, "Decrypted PeerCipher key block\r\n"); + gsDebugBinary(GSIDebugCat_App, GSIDebugType_Misc, GSIDebugLevel_Notice, (char *)theCipherOut->mKey, (gsi_i32)theCipherOut->mKeyLen); + RC4Init(&theCipherOut->mRC4, theCipherOut->mKey, (int)theCipherOut->mKeyLen); + theCipherOut->mInitialized = gsi_true; + return SCResult_NO_ERROR; + } + else + return SCResult_UNKNOWN_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scPeerCipherEncryptBufferIV(SCPeerCipher * theCipher, gsi_u32 theMessageNum, gsi_u8 * theData, gsi_u32 theLen) +{ + RC4Context rc4; + MD5_CTX md5; + char tempHash[16]; + + GS_ASSERT(gsi_is_true(theCipher->mInitialized)); + + // Construct a new key for this message + // - Using the same key for all messages would be extremely unsafe + MD5Init(&md5); + MD5Update(&md5, theCipher->mKey, theCipher->mKeyLen); + MD5Update(&md5, (unsigned char*)&theMessageNum, sizeof(theMessageNum)); + MD5Final((unsigned char *)tempHash, &md5); + + RC4Init(&rc4, (unsigned char *)tempHash, GS_CRYPT_MD5_HASHSIZE); + RC4Encrypt(&rc4, (const unsigned char*)theData, theData, (int)theLen); + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scPeerCipherDecryptBufferIV(SCPeerCipher * theCipher, gsi_u32 theMessageNum, gsi_u8 * theData, gsi_u32 theLen) +{ + return scPeerCipherEncryptBufferIV(theCipher, theMessageNum, theData, theLen); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scPeerCipherEncryptBuffer(SCPeerCipher * theCipher, gsi_u8 * theData, gsi_u32 theLen) +{ + // Encrypt the data using the RC4 context + GS_ASSERT(gsi_is_true(theCipher->mInitialized)); + RC4Encrypt(&theCipher->mRC4, (const unsigned char*)theData, theData, (int)theLen); + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scPeerCipherDecryptBuffer(SCPeerCipher * theCipher, gsi_u8 * theData, gsi_u32 theLen) +{ + return scPeerCipherEncryptBuffer(theCipher, theData, theLen); +} diff --git a/code/gamespy/sc/sciReport.c b/code/gamespy/sc/sciReport.c new file mode 100644 index 00000000..6c63fca9 --- /dev/null +++ b/code/gamespy/sc/sciReport.c @@ -0,0 +1,874 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sciReport.h" +#include "sciSerialize.h" +#include "../md5.h" + +#pragma warning(disable: 4267) + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciCreateReport(gsi_u8 theSessionGuid[SC_SESSION_GUID_SIZE], + gsi_u32 theHeaderVersion, + gsi_u32 thePlayerCount, + gsi_u32 theTeamCount, + SCIReport ** theReportOut) +{ + SCIReport * theNewReport; + SCIReportHeader * theReportHeader; + gsi_u8 * theReportData; + + // roster is [CCID (16) + TeamIndex (4)] * numplayers + const gsi_u32 theRosterSize = SC_GUID_BINARY_SIZE * thePlayerCount + + SC_REPORT_TEAMINDEX_LENGTH * thePlayerCount; + + GS_ASSERT(theReportOut != NULL); + + // allocate the report + //GS_ASSERT(0); // todo: memalignment + theNewReport = (SCIReport*)gsimalloc(sizeof(SCIReport)); + if (theNewReport == NULL) + return SCResult_OUT_OF_MEMORY; + memset(theNewReport, 0, sizeof(SCIReport)); + + // allocate the report buffer (holds submission data) + theReportData = (gsi_u8*)gsimalloc(SC_REPORT_BUFFER_BYTES); + if (theReportData == NULL) + { + gsifree(theNewReport); + return SCResult_OUT_OF_MEMORY; + } + memset(theReportData, 0, SC_REPORT_BUFFER_BYTES); + theNewReport->mBuffer.mIsStatic = gsi_false; + theNewReport->mBuffer.mCapacity = SC_REPORT_BUFFER_BYTES; + + // Fill in report header + theReportHeader = (SCIReportHeader*)theReportData; + memset(theReportHeader, 0, sizeof(SCIReportHeader)); + + theReportHeader->mProtocolVersion = htonl(SC_REPORT_PROTOCOL); + theReportHeader->mDeveloperVersion = htonl(theHeaderVersion); + + theReportHeader->mRosterSectionLength = theRosterSize; + theReportHeader->mAuthSectionLength = SC_REPORT_AUTHDATA_LENGTH * thePlayerCount; + theReportHeader->mResultsSectionLength = SC_REPORT_ENTITYRESULT_LENGTH * (thePlayerCount + theTeamCount); + theReportHeader->mPlayerCount = (gsi_u16)thePlayerCount; + theReportHeader->mTeamCount = (gsi_u16)theTeamCount; + //theReportHeader->mFlags = (gsi_u32)theOptionsFlags; + + // Finished, return new report + theNewReport->mReportState = SCIReportState_ROSTER; + theNewReport->mBuffer.mData = (char *)theReportData; + theNewReport->mCurEntityStartPos = -1; + theNewReport->mNumTeamsReported = 0; + + *theReportOut = theNewReport; + GSI_UNUSED(theSessionGuid); + return SCResult_NO_ERROR; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +SCResult sciDestroyReport(SCIReport *theReport) +{ + theReport->mReportState = SCIReportState_NONE; + theReport->mCurEntityKeyCount = 0; + theReport->mCurEntityStartPos = 0; +// theReport->mNumPlayersReported = 0; + theReport->mNumTeamsReported = 0; + theReport->mNumResultsReported = 0; + + gsifree(theReport->mBuffer.mData); + theReport->mBuffer.mData = NULL; + theReport->mBuffer.mCapacity = 0; + theReport->mBuffer.mLen = 0; + theReport->mBuffer.mPos = 0; + gsifree(theReport); + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportSetPlayerConnectionId(SCIReport * theReport, gsi_u32 thePlayerIndex, const gsi_u8 theConnectionId[SC_CONNECTION_GUID_SIZE]) +{ + gsi_u32 rosterDataOffset = 0; + + // Roster section is just after the header + rosterDataOffset = sizeof(SCIReportHeader); + rosterDataOffset += SC_REPORT_ROSTERDATA_LENGTH * thePlayerIndex; + + // copy the connection id + GS_ASSERT((rosterDataOffset + SC_GUID_BINARY_SIZE) < theReport->mBuffer.mCapacity); + + sciSerializeGUID((gsi_u8 *)&theReport->mBuffer.mData[rosterDataOffset], theConnectionId); + //memcpy(&theReport->mBuffer.mData[rosterDataOffset], theConnectionId, SC_CONNECTION_GUID_SIZE); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportSetPlayerTeamIndex(SCIReport * theReport, gsi_u32 thePlayerIndex, gsi_u32 theTeamIndex) +{ + //SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + gsi_u32 rosterDataOffset = 0; + + // Roster section is just after the header + rosterDataOffset = sizeof(SCIReportHeader); + rosterDataOffset += SC_REPORT_ROSTERDATA_LENGTH * thePlayerIndex; + + // copy the team index, which must appear just after the connection id + rosterDataOffset += SC_GUID_BINARY_SIZE; + GS_ASSERT((rosterDataOffset + sizeof(gsi_i32)) < theReport->mBuffer.mCapacity); + sciSerializeInt32((gsi_u8 *)&theReport->mBuffer.mData[rosterDataOffset], (gsi_i32)theTeamIndex); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportSetPlayerGameResult(SCIReport * theReport, gsi_u32 thePlayerIndex, SCGameResult theGameResult) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + gsi_u32 resultsDataOffset = 0; + + // Results section is just after the auth section + resultsDataOffset = sizeof(SCIReportHeader) + aHeader->mRosterSectionLength + aHeader->mAuthSectionLength; + resultsDataOffset += sizeof(gsi_u32) * thePlayerIndex; // 4 byte game result per player + + // copy the game result in network byte order + GS_ASSERT((resultsDataOffset + sizeof(gsi_u32)) < theReport->mBuffer.mCapacity); + sciSerializeInt32((gsi_u8 *)&theReport->mBuffer.mData[resultsDataOffset], theGameResult); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportSetPlayerAuthInfo(SCIReport * theReport, gsi_u32 thePlayerIndex, const GSLoginCertificate * theCertificate, const gsi_u8 theAuthHash[16]) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + gsi_u32 authDataOffset = 0; + + // Auth section is just after the roster + authDataOffset = sizeof(SCIReportHeader) + aHeader->mRosterSectionLength; + authDataOffset += SC_REPORT_AUTHDATA_LENGTH * thePlayerIndex; + + // copy auth data into the buffer + // &theReport->mBuffer.mData[authDataOffset] + GSI_UNUSED(theAuthHash); + GSI_UNUSED(theCertificate); + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportSetTeamGameResult(SCIReport * theReport, gsi_u32 theTeamIndex, SCGameResult theGameResult) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + gsi_u32 resultsDataOffset = 0; + + // Results section is just after the auth section + // team results are just after player results + resultsDataOffset = sizeof(SCIReportHeader) + aHeader->mRosterSectionLength + aHeader->mAuthSectionLength; + resultsDataOffset += sizeof(gsi_u32) * aHeader->mPlayerCount; // 4 byte game result per player + resultsDataOffset += sizeof(gsi_u32) * theTeamIndex; // 4 byte game result per player + + // copy the game result in network byte order + GS_ASSERT((resultsDataOffset + sizeof(gsi_i32)) < theReport->mBuffer.mCapacity); + sciSerializeInt32((gsi_u8 *)&theReport->mBuffer.mData[resultsDataOffset], theGameResult); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportSetAsMatchless(SCIReport * theReport) +{ + SCIReportHeader * aHeader; + + aHeader = (SCIReportHeader *)theReport->mBuffer.mData; + + aHeader->mFlags |= SC_REPORT_FLAG_MATCHLESS_SESSION; + + return SCResult_NO_ERROR; +} + +/* -----------UNUSED------------ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportPlayerAuth(SCIReport * theReport, + gsi_u32 thePlayerIndex, + gsi_u8 thePlayerConnectionId[SC_CONNECTION_GUID_SIZE], + gsi_u32 thePlayerTeamIndex, + gsi_u32 theProfileId, + GSLoginCertificate * theCertificate, + gsi_u8 theAuthHash[16]) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + //unsigned int len = 0; + + gsi_u32 rosterDataOffset = 0; + gsi_u32 authDataOffset = 0; + + //gsi_u8 * rosterWritePos = NULL; + + // Roster section is just after the header + rosterDataOffset = sizeof(SCIReportHeader); + + // Auth section is just after the roster + authDataOffset = sizeof(SCIReportHeader) + aHeader->mRosterSectionLength; + + // Check size and length + GS_ASSERT((authDataOffset + aHeader->mAuthSectionLength) < SC_REPORT_BUFFER_BYTES); + + // Record player index and team in roster section + // The report contains an array of players, so we offset the write position + // based on the number of players previously reported + rosterDataOffset += aHeader->mPlayerCount * (SC_GUID_BINARY_SIZE + SC_REPORT_ENTITYINDEX_LENGTH); + + // write the connection id GUID + GS_ASSERT(rosterDataOffset < authDataOffset); + sciSerializeGUID((gsi_u8 *)&theReport->mBuffer.mData[rosterDataOffset], thePlayerConnectionId); + rosterDataOffset += SC_GUID_BINARY_SIZE; + + +// GS_ASSERT(rosterDataOffset < authDataOffset); +// memcpy(&theReport->mBuffer.mData[rosterDataOffset], thePlayerConnectionId, SC_GUID_BINARY_SIZE); +// rosterDataOffset += SC_CONNECTION_GUID_SIZE; + + + GS_ASSERT(rosterDataOffset < authDataOffset); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[rosterDataOffset], (gsi_i16)thePlayerTeamIndex); + rosterDataOffset += 2; // we just wrote an Int16, so bump by two. + + GS_ASSERT(rosterDataOffset < authDataOffset); // check overrun + + // Write auth data for this player + // The report contains an array of auth data, so we offset the write position + // based on the number of players previously reported + authDataOffset += aHeader->mPlayerCount * (SC_GUID_BINARY_SIZE + SC_REPORT_ENTITYINDEX_LENGTH); + + GS_ASSERT((authDataOffset + SC_REPORT_AUTHDATA_LENGTH) < theReport->mBuffer.mCapacity); // check buffer space + + // Write auth data + //result = wsLoginCertWriteBinary(theCertificate, &theReport->mBuffer.mData[theReport->mBuffer.mPos], (SC_REPORT_BUFFER_BYTES - theReport->mBuffer.mPos), &len); + //if (gsi_is_false(result)) + // return SCResult_OUT_OF_MEMORY; + //memcpy(&theReport->mBuffer.mData[theReport->mBuffer.mPos], theAuthHash, 16); + //theReport->mBuffer.mPos += 16; + + //theReport->mNumPlayersReported++; // remember how many player's we've written data for + GSI_UNUSED(theAuthHash); + GSI_UNUSED(theCertificate); + GSI_UNUSED(theProfileId); + GSI_UNUSED(thePlayerIndex); + return SCResult_NO_ERROR; +} + +// -----------UNUSED------------ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportPlayerResult(SCIReport * theReport, + gsi_u32 thePlayerIndex, + SCGameResult theResult) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + //unsigned int len = 0; + gsi_u32 resultsOffset = 0; + + resultsOffset = sizeof(SCIReportHeader) + aHeader->mRosterSectionLength + aHeader->mAuthSectionLength; + GS_ASSERT(resultsOffset < SC_REPORT_BUFFER_BYTES); + + resultsOffset += SC_REPORT_ENTITYRESULT_LENGTH * theReport->mNumResultsReported; + sciSerializeInt32((gsi_u8 *)&theReport->mBuffer.mData[resultsOffset], theResult); + + theReport->mNumResultsReported++; + GSI_UNUSED(thePlayerIndex); + return SCResult_NO_ERROR; +} + +// -----------UNUSED------------ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportTeamResult(SCIReport * theReport, + gsi_u32 theTeamIndex, + SCGameResult theResult) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + //unsigned int len = 0; + gsi_u32 resultsOffset = 0; + + resultsOffset = sizeof(SCIReportHeader) + aHeader->mRosterSectionLength + aHeader->mAuthSectionLength; + resultsOffset += SC_REPORT_ENTITYRESULT_LENGTH * theReport->mNumResultsReported; + GS_ASSERT(resultsOffset < SC_REPORT_BUFFER_BYTES); + + resultsOffset += SC_REPORT_ENTITYRESULT_LENGTH * theReport->mNumResultsReported; + sciSerializeInt32((gsi_u8 *)&theReport->mBuffer.mData[resultsOffset], theResult); + + theReport->mNumResultsReported++; + GSI_UNUSED(theTeamIndex); + return SCResult_NO_ERROR; +}*/ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportBeginNewTeam(SCIReport * theReport) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + + //sciReportEndEntity(theReport); + + theReport->mCurEntityKeyCount = 0; + theReport->mCurEntityStartPos = (gsi_i32)theReport->mBuffer.mPos; + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + + aHeader->mTeamSectionLength += sizeof(gsi_u16); + + // Mark the start of the new entity + //theReport->mStatus.mCurEntityStart = &theReport->mBuffer.mData[theReport->mBuffer.mPos]; + //theReport->mStatus.mCurEntityType = SC_REPORT_ENTITY_TEAM; + //theReport->mBuffer.mPos += 4; // skip 4 byte for length, it will be filled in later + //aHeader->mTeamCount++; +// aHeader->mTeamDataLength += 4; // 4 bytes for the new team's length + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportBeginNewPlayer(SCIReport * theReport) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + GS_ASSERT(theReport != NULL); + + //if (theReport->mReportState == SCIReportState_ROSTER) + { + // this is the first player + + } + //GS_ASSERT(theReport->mReportState == SCIReportState_ROSTER); + + theReport->mCurEntityKeyCount = 0; + theReport->mCurEntityStartPos = (gsi_i32)theReport->mBuffer.mPos; + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + + aHeader->mPlayerSectionLength += sizeof(gsi_u16); + + + //sciReportEndEntity(theReport); + + // Mark the start of the new entity + //theReport->mStatus.mCurEntityStart = &theReport->mBuffer.mData[theReport->mBuffer.mPos]; + //theReport->mStatus.mCurEntityType = SC_REPORT_ENTITY_PLAYER; + //theReport->mBuffer.mPos += 4; // skip 4 byte for length, it will be filled in later + //sciSerializeInt32(&theReport->mBuffer.mData[theReport->mBuffer.mPos], thePlayerIndex); + //theReport->mBuffer.mPos += 4; // 4 byte index + //aHeader->mPlayerCount++; +// aHeader->mPlayerDataLength += 4; // 4 bytes for the new player's length + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportBeginGlobalData(SCIReport * theReport) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + + GS_ASSERT(theReport != NULL); + GS_ASSERT(theReport->mReportState == SCIReportState_ROSTER); + + theReport->mReportState = SCIReportState_GLOBALDATA; + //theReport->mCurEntityIndex = 0; + + // set buffer write position to start of global data + theReport->mBuffer.mPos = sizeof(SCIReportHeader) + aHeader->mRosterSectionLength + + aHeader->mAuthSectionLength + aHeader->mResultsSectionLength; + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportBeginPlayerData(SCIReport * theReport) +{ + //SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + + GS_ASSERT(theReport != NULL); + + // Must not have passed into player data state yet. + GS_ASSERT(theReport->mReportState == SCIReportState_ROSTER || + theReport->mReportState == SCIReportState_GLOBALDATA); + + // case where there is no global data + if (theReport->mReportState == SCIReportState_ROSTER) + { + } + + theReport->mReportState = SCIReportState_PLAYERDATA; + //theReport->mCurEntityIndex = 0; + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportBeginTeamData(SCIReport * theReport) +{ + GS_ASSERT(theReport != NULL); + + // Must not have passed into player data state yet. + GS_ASSERT( + theReport->mReportState == SCIReportState_ROSTER || + theReport->mReportState == SCIReportState_GLOBALDATA || + theReport->mReportState == SCIReportState_PLAYERDATA + ); + + theReport->mReportState = SCIReportState_TEAMDATA; + //theReport->mCurEntityIndex = 0; + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportEndEntity(SCIReport * theReport) +{ + //gsi_u8 * anEntityEnd = NULL; + //gsi_u32 anEntityLen = 0; + + // Calculate and update the last entities length + /*if (theReport->mStatus.mCurEntityStart != NULL) + { + anEntityEnd = &theReport->mBuffer.mData[theReport->mBuffer.mPos]; + anEntityLen = anEntityEnd - theReport->mStatus.mCurEntityStart; + sciSerializeDataLength(theReport->mStatus.mCurEntityStart, anEntityLen); + theReport->mStatus.mCurEntityStart = NULL; + }*/ + GSI_UNUSED(theReport); + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportEnd(SCIReport * theReport, gsi_bool isAuth, SCGameStatus theStatus) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + + sciReportEndEntity(theReport); + + if (gsi_is_true(isAuth)) + aHeader->mFlags |= SC_REPORT_FLAG_AUTHORITATIVE; + + // Send header in network byte order + aHeader->mGameStatus = htonl(theStatus); + + aHeader->mFlags = htonl(aHeader->mFlags); + aHeader->mPlayerCount = htons(aHeader->mPlayerCount); + aHeader->mTeamCount = htons(aHeader->mTeamCount); + aHeader->mReserved = 0; + + aHeader->mGameKeyCount = htons(aHeader->mGameKeyCount); + aHeader->mPlayerKeyCount = htons(aHeader->mPlayerKeyCount); + aHeader->mTeamKeyCount = htons(aHeader->mTeamKeyCount); + + aHeader->mRosterSectionLength = htonl(aHeader->mRosterSectionLength); + aHeader->mAuthSectionLength = htonl(aHeader->mAuthSectionLength); + aHeader->mResultsSectionLength = htonl(aHeader->mResultsSectionLength); + + aHeader->mGameSectionLength = htonl(aHeader->mGameSectionLength); + aHeader->mPlayerSectionLength = htonl(aHeader->mPlayerSectionLength); + aHeader->mTeamSectionLength = htonl(aHeader->mTeamSectionLength); + + +// aHeader->mSessionDataLength = htonl(aHeader->mSessionDataLength); +// aHeader->mPlayerDataLength = htonl(aHeader->mPlayerDataLength); +// aHeader->mTeamDataLength = htonl(aHeader->mTeamDataLength); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportAddIntValue(SCIReport * theReport, + gsi_u16 theKeyId, + gsi_i32 theValue) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + int writtenLen = 0; + gsi_i16 theKeyType = SCIKeyType_INT32; + + // calculate length of data to be written + writtenLen += sizeof(gsi_u16) + sizeof(gsi_u16); // 2 byte key ID, 2 byte key type; + writtenLen += sizeof(gsi_u32); // 4 bytes of data + + // Check room in buffer + GS_ASSERT((theReport->mBuffer.mPos + writtenLen) < theReport->mBuffer.mCapacity); + + // Update count and length markers + if (theReport->mReportState == SCIReportState_GLOBALDATA) + { + aHeader->mGameKeyCount++; + aHeader->mGameSectionLength += writtenLen; + } + else if (theReport->mReportState == SCIReportState_PLAYERDATA) + { + GS_ASSERT(theReport->mCurEntityStartPos != -1); + aHeader->mPlayerKeyCount++; + aHeader->mPlayerSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + else if (theReport->mReportState == SCIReportState_TEAMDATA) + { + aHeader->mTeamKeyCount++; + aHeader->mTeamSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + // Needs real error handling code or an assertion on a non-constant expression + //else + // GS_ASSERT(0); // invalid state for writing key/value pairs! + + // Write the data + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], (gsi_i16)theKeyId); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theKeyType); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt32((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theValue); + theReport->mBuffer.mPos += sizeof(gsi_u32); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportAddInt64Value(SCIReport * theReport, + gsi_u16 theKeyId, + gsi_i64 theValue) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + int writtenLen = 0; + gsi_i16 theKeyType = SCIKeyType_INT64; + + // calculate length of data to be written + writtenLen += sizeof(gsi_u16) + sizeof(gsi_u16); // 2 byte key ID, 2 byte key type; + writtenLen += sizeof(gsi_i64); // 4 bytes of data + + // Check room in buffer + GS_ASSERT((theReport->mBuffer.mPos + writtenLen) < theReport->mBuffer.mCapacity); + + // Update count and length markers + if (theReport->mReportState == SCIReportState_GLOBALDATA) + { + aHeader->mGameKeyCount++; + aHeader->mGameSectionLength += writtenLen; + } + else if (theReport->mReportState == SCIReportState_PLAYERDATA) + { + GS_ASSERT(theReport->mCurEntityStartPos != -1); + aHeader->mPlayerKeyCount++; + aHeader->mPlayerSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + else if (theReport->mReportState == SCIReportState_TEAMDATA) + { + aHeader->mTeamKeyCount++; + aHeader->mTeamSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + + // Write the data + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], (gsi_i16)theKeyId); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theKeyType); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt64((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theValue); + theReport->mBuffer.mPos += sizeof(gsi_i64); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportAddShortValue(SCIReport * theReport, + gsi_u16 theKeyId, + gsi_i16 theValue) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + int writtenLen = 0; + gsi_i16 theKeyType = SCIKeyType_INT16; + + // calculate length of data to be written + writtenLen += sizeof(gsi_u16) + sizeof(gsi_u16); // 2 byte key ID, 2 byte key type; + writtenLen += sizeof(gsi_u16); // 2 bytes of data + + // Check room in buffer + GS_ASSERT((theReport->mBuffer.mPos + writtenLen) < theReport->mBuffer.mCapacity); + + // Update count and length markers + if (theReport->mReportState == SCIReportState_GLOBALDATA) + { + aHeader->mGameKeyCount++; + aHeader->mGameSectionLength += writtenLen; + } + else if (theReport->mReportState == SCIReportState_PLAYERDATA) + { + GS_ASSERT(theReport->mCurEntityStartPos != -1); + aHeader->mPlayerKeyCount++; + aHeader->mPlayerSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + else if (theReport->mReportState == SCIReportState_TEAMDATA) + { + aHeader->mTeamKeyCount++; + aHeader->mTeamSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + // Needs real error handling code or an assertion on a non-constant expression + //else + // GS_ASSERT(0); // invalid state for writing key/value pairs! + + // Write the data + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], (gsi_i16)theKeyId); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theKeyType); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theValue); + theReport->mBuffer.mPos += sizeof(gsi_u16); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportAddByteValue(SCIReport * theReport, + gsi_u16 theKeyId, + gsi_i8 theValue) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + int writtenLen = 0; + gsi_i16 theKeyType = SCIKeyType_BYTE; + + // calculate length of data to be written + writtenLen += sizeof(gsi_u16) + sizeof(gsi_u16); // 2 byte key ID, 2 byte key type; + writtenLen += sizeof(gsi_u8); // 1 bytes of data + + // Check room in buffer + GS_ASSERT((theReport->mBuffer.mPos + writtenLen) < theReport->mBuffer.mCapacity); + + // Update count and length markers + if (theReport->mReportState == SCIReportState_GLOBALDATA) + { + aHeader->mGameKeyCount++; + aHeader->mGameSectionLength += writtenLen; + } + else if (theReport->mReportState == SCIReportState_PLAYERDATA) + { + GS_ASSERT(theReport->mCurEntityStartPos != -1); + aHeader->mPlayerKeyCount++; + aHeader->mPlayerSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + else if (theReport->mReportState == SCIReportState_TEAMDATA) + { + aHeader->mTeamKeyCount++; + aHeader->mTeamSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + // Needs real error handling code or an assertion on a non-constant expression + //else + // GS_ASSERT(0); // invalid state for writing key/value pairs! + + // Write the data + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], (gsi_i16)theKeyId); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theKeyType); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt8((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theValue); + theReport->mBuffer.mPos += sizeof(gsi_u8); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportAddFloatValue(SCIReport * theReport, + gsi_u16 theKeyId, + float theValue) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + int writtenLen = 0; + gsi_i16 theKeyType = SCIKeyType_FLOAT; + + // calculate length of data to be written + writtenLen += sizeof(gsi_u16) + sizeof(gsi_u16); // 2 byte key ID, 2 byte key type; + writtenLen += sizeof(float); // stored in stream as a 4 byte char array + + // Check room in buffer + GS_ASSERT((theReport->mBuffer.mPos + writtenLen) < theReport->mBuffer.mCapacity); + + // Update count and length markers + if (theReport->mReportState == SCIReportState_GLOBALDATA) + { + aHeader->mGameKeyCount++; + aHeader->mGameSectionLength += writtenLen; + } + else if (theReport->mReportState == SCIReportState_PLAYERDATA) + { + GS_ASSERT(theReport->mCurEntityStartPos != -1); + aHeader->mPlayerKeyCount++; + aHeader->mPlayerSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + else if (theReport->mReportState == SCIReportState_TEAMDATA) + { + aHeader->mTeamKeyCount++; + aHeader->mTeamSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + // Needs real error handling code or an assertion on a non-constant expression + //else + // GS_ASSERT(0); // invalid state for writing key/value pairs! + + // Write the data + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], (gsi_i16)theKeyId); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theKeyType); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeFloat((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theValue); + theReport->mBuffer.mPos += sizeof(float); + + return SCResult_NO_ERROR; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportAddStringValue(SCIReport * theReport, + gsi_u16 theKeyId, + const gsi_char * theValue) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + int writtenLen = 0, numLenBytes = 0, asciiLen = 0; + gsi_i16 theKeyType = SCIKeyType_STRING; + + // for unicode we need to store the ascii-converted string first in the buffer so as + // not to dynamically allocate space for it. Then once we have the length of it, we can + // write over the buffer + +#if defined(GSI_UNICODE) + // First check room in buffer + GS_ASSERT(((theReport->mBuffer.mPos + (_tcslen(theValue)) + (sizeof(gsi_u16)*2)) + < theReport->mBuffer.mCapacity)); + + // as long as we have enough room - add to the buffer and store length of the Ascii string + // need to remove 1 for the appended null char \0 which we want to remove + asciiLen = UCS2ToAsciiString(theValue, &theReport->mBuffer.mData[theReport->mBuffer.mPos])-1; + + // now determine how many bytes are necessary to represent len + numLenBytes = (asciiLen/127)+1; + +#else + + numLenBytes = (gsi_i32)(strlen(theValue)/127)+1; //finds out number of bytes necessary to represent len + asciiLen = (int)strlen(theValue); +#endif + + + // calculate length of data to be written + writtenLen += sizeof(gsi_u16) + sizeof(gsi_u16); // 2 byte key ID, 2 byte key type; + writtenLen += asciiLen; // 0 for empty string + writtenLen += numLenBytes; // the number of bytes necessary to represent len + + // Check room in buffer + GS_ASSERT((theReport->mBuffer.mPos + writtenLen) < theReport->mBuffer.mCapacity); + + // Update count and length markers + if (theReport->mReportState == SCIReportState_GLOBALDATA) + { + aHeader->mGameKeyCount++; + aHeader->mGameSectionLength += writtenLen; + } + else if (theReport->mReportState == SCIReportState_PLAYERDATA) + { + aHeader->mPlayerKeyCount++; + aHeader->mPlayerSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + else if (theReport->mReportState == SCIReportState_TEAMDATA) + { + aHeader->mTeamKeyCount++; + aHeader->mTeamSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + // Needs real error handling code or an assertion on a non-constant expression + //else + // GS_ASSERT(0); // invalid state for writing key/value pairs! + + // Write the data + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], (gsi_i16)theKeyId); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theKeyType); + theReport->mBuffer.mPos += sizeof(gsi_u16); + + // now prior to writing the string we need to prepend the length of the string to follow + // the .NET format of (string length)(String) + memset(&theReport->mBuffer.mData[theReport->mBuffer.mPos], asciiLen, (gsi_u32)numLenBytes); + theReport->mBuffer.mPos += numLenBytes; + +#if defined(GSI_UNICODE) + // Now strip to Ascii and write the length - subtract 1 to get rid of appended null character + theReport->mBuffer.mPos += UCS2ToAsciiString(theValue, &theReport->mBuffer.mData[theReport->mBuffer.mPos])-1; +#else + strncpy(&theReport->mBuffer.mData[theReport->mBuffer.mPos], theValue, strlen(theValue)); + theReport->mBuffer.mPos += strlen(theValue); +#endif + + return SCResult_NO_ERROR; +} + +#pragma warning(default: 4267) \ No newline at end of file diff --git a/code/gamespy/sc/sciReport.h b/code/gamespy/sc/sciReport.h new file mode 100644 index 00000000..6519a307 --- /dev/null +++ b/code/gamespy/sc/sciReport.h @@ -0,0 +1,185 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __SCIREPORT_H__ +#define __SCIREPORT_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sci.h" +#include "../hashtable.h" + + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Constants +#define SC_REPORT_PROTOCOL 2 + +// NOTE: broke up entity index in two - for later versions we will want to send the 2 byte +// team index followed by the 4 byte team ID later in the stream + + // Protocol length settings +#define SC_REPORT_ROSTERDATA_LENGTH 20 // V1.0 - 16 byte connection id + 4 byte team index +#define SC_REPORT_PLAYERINDEX_LENGTH 2 // V1.0 - 2 bytes for a player index +#define SC_REPORT_TEAMINDEX_LENGTH 4 // V1.0 - 4 bytes for a team index +#define SC_REPORT_AUTHDATA_LENGTH 16 // V1.0 - length of auth data per player +#define SC_REPORT_ENTITYRESULT_LENGTH 4 // V1.0 - 4 byte enum value + + +#define SC_REPORT_ENTITY_NONE 0 +#define SC_REPORT_ENTITY_PLAYER 1 +#define SC_REPORT_ENTITY_TEAM 2 + +// (must match server) +#define SC_REPORT_FLAG_AUTHORITATIVE (1<<0) +#define SC_REPORT_FLAG_NON_RELATIVE_RESULT (1<<1) +#define SC_REPORT_FLAG_MATCHLESS_SESSION (1<<2) +//#define SC_REPORT_FLAG_DEDICATED_HOST (1<<3) + +typedef enum +{ + SCIReportState_NONE, + SCIReportState_ROSTER, + //SCIReportState_AUTHDATA, + //SCIReportState_RESTULTS, + SCIReportState_GLOBALDATA, + SCIReportState_PLAYERDATA, + SCIReportState_TEAMDATA +} SCIReportState; + +typedef enum +{ + SCIKeyType_INT32, + SCIKeyType_INT16, + SCIKeyType_BYTE, + SCIKeyType_STRING, + SCIKeyType_FLOAT, + SCIKeyType_INT64 +} SCIKeyType; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Structs +typedef struct SCIReportHeader +{ + gsi_u32 mProtocolVersion; // To support future changes + gsi_u32 mDeveloperVersion; + gsi_u8 mChecksum[GS_CRYPT_MD5_HASHSIZE]; // Hash(session, player, team data) + gsi_u32 mGameStatus; + gsi_u32 mFlags; // Flags for authoritative, final, etc. + gsi_u16 mPlayerCount; // Players in session + gsi_u16 mTeamCount; // Teams in session + gsi_u16 mGameKeyCount; + gsi_u16 mPlayerKeyCount; + gsi_u16 mTeamKeyCount; + gsi_u16 mReserved; // pad, for 32-bit alignment + gsi_u32 mRosterSectionLength; // + gsi_u32 mAuthSectionLength; // + gsi_u32 mResultsSectionLength; // + gsi_u32 mGameSectionLength; // + gsi_u32 mPlayerSectionLength; // + gsi_u32 mTeamSectionLength; // +} SCIReportHeader; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct SCIReportBuffer +{ + gsi_bool mIsStatic; + + gsi_u32 mLen; + gsi_u32 mCapacity; + gsi_u32 mPos; + + char * mData; + +} SCIReportBuffer; + +typedef struct SCIReport +{ + SCIReportState mReportState; + + gsi_i32 mCurEntityStartPos; // where this entity's data begins + gsi_u16 mCurEntityKeyCount; // how many keys we've added for this entity + + //gsi_u32 mNumPlayersReported; // to check against expected count - not used + gsi_u32 mNumResultsReported; // to check against expected count + + gsi_u32 mNumTeamsReported; // keeps track of the number of team IDs that have been reported + gsi_u32 mTeamIds[SC_MAX_NUM_TEAMS]; // internal list of team IDs + + SCIReportBuffer mBuffer; +} SCIReport; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciCreateReport(gsi_u8 theSessionGuid[16], + gsi_u32 theHeaderVersion, + gsi_u32 thePlayerCount, + gsi_u32 theTeamCount, + SCIReport ** theReportOut); + +SCResult sciDestroyReport(SCIReport *theReport); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// ReportData functions +SCResult SC_CALL sciReportSetPlayerConnectionId(SCIReport * theReport, gsi_u32 thePlayerIndex, const gsi_u8 theConnectionId[SC_CONNECTION_GUID_SIZE]); +SCResult SC_CALL sciReportSetPlayerTeamIndex (SCIReport * theReport, gsi_u32 thePlayerIndex, gsi_u32 theTeamIndex); +SCResult SC_CALL sciReportSetPlayerGameResult (SCIReport * theReport, gsi_u32 thePlayerIndex, SCGameResult theGameResult); +SCResult SC_CALL sciReportSetPlayerAuthInfo (SCIReport * theReport, gsi_u32 thePlayerIndex, const GSLoginCertificate * theCertificate, const gsi_u8 theAuthHash[16]); +SCResult SC_CALL sciReportSetTeamGameResult (SCIReport * theReport, gsi_u32 theTeamIndex , SCGameResult theGameResult); +SCResult SC_CALL sciReportSetAsMatchless (SCIReport * theReport); + +// Key/Value data +SCResult SC_CALL sciReportBeginGlobalData(SCIReport * theReport); +SCResult SC_CALL sciReportBeginPlayerData(SCIReport * theReport); +SCResult SC_CALL sciReportBeginTeamData (SCIReport * theReport); + +SCResult SC_CALL sciReportAddIntValue(SCIReport * theReport, + gsi_u16 theKeyId, + gsi_i32 theValue); +SCResult SC_CALL sciReportAddInt64Value(SCIReport * theReport, + gsi_u16 theKeyId, + gsi_i64 theValue); +SCResult SC_CALL sciReportAddShortValue(SCIReport * theReport, + gsi_u16 theKeyId, + gsi_i16 theValue); +SCResult SC_CALL sciReportAddByteValue(SCIReport * theReport, + gsi_u16 theKeyId, + gsi_i8 theValue); +SCResult SC_CALL sciReportAddFloatValue(SCIReport * theReport, + gsi_u16 theKeyId, + float theValue); +SCResult SC_CALL sciReportAddStringValue(SCIReport * theReport, + gsi_u16 theKeyId, + const gsi_char * theValue); + +SCResult SC_CALL sciReportBeginNewTeam(SCIReport * theReport); +SCResult SC_CALL sciReportBeginNewPlayer(SCIReport * theReport); +SCResult SC_CALL sciReportEndEntity(SCIReport * theReport); +// Call when finished writing +SCResult SC_CALL sciReportEnd(SCIReport * theReport, gsi_bool isAuth, SCGameStatus theStatus); + + + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __SCREPORT_H__ diff --git a/code/gamespy/sc/sciSerialize.c b/code/gamespy/sc/sciSerialize.c new file mode 100644 index 00000000..9100ea99 --- /dev/null +++ b/code/gamespy/sc/sciSerialize.c @@ -0,0 +1,171 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sciSerialize.h" + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeInt8(gsi_u8* theCursor, gsi_i8 theValue) +{ + // Copy int8 value to possibly misaligned destination + gsi_u8* dst = (gsi_u8*)theCursor; + + *dst++ = (gsi_u8)theValue; + + return (gsi_u8*)dst; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeInt16(gsi_u8* theCursor, gsi_i16 theValue) +{ + // Convert to network byte order + gsi_i16 netValue = (gsi_i16)htons(theValue); + + // Copy int16 value to possibly misaligned destination + gsi_u8* dst = (gsi_u8*)theCursor; + gsi_u8* src = (gsi_u8*)&netValue; + + *dst++ = *src++; + *dst++ = *src++; + + return (gsi_u8*)dst; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeInt32(gsi_u8* theCursor, gsi_i32 theValue) +{ + // Convert to network byte order + gsi_i32 netValue = (gsi_i32)htonl(theValue); + + // Copy int32 value to possibly misaligned destination + gsi_u8* dst = (gsi_u8*)theCursor; + gsi_u8* src = (gsi_u8*)&netValue; + + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + + return (gsi_u8*)dst; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeInt64(gsi_u8* theCursor, gsi_i64 theValue) +{ + // Convert to network byte order + //gsi_i32 netValue = (gsi_i32)htonl(theValue); + + // Copy int32 value to possibly misaligned destination + gsi_u8* dst = (gsi_u8*)theCursor; + gsi_u8* src = (gsi_u8*)&theValue; + +#ifdef GSI_BIG_ENDIAN + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + + dst[4] = src[4]; + dst[5] = src[5]; + dst[6] = src[6]; + dst[7] = src[7]; +#else + dst[0] = src[7]; + dst[1] = src[6]; + dst[2] = src[5]; + dst[3] = src[4]; + + dst[4] = src[3]; + dst[5] = src[2]; + dst[6] = src[1]; + dst[7] = src[0]; +#endif + + return (gsi_u8*)dst; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeFloat(gsi_u8* theCursor, float theValue) +{ + unsigned char netValue[4]; + gsi_u8 *dst, *src; + +#if defined(GSI_BIG_ENDIAN) + //swap byte ordering for floats to little endian - htonl doesn't work + gsiFloatSwap(netValue, theValue); +#else + memcpy(netValue, &theValue, 4); +#endif + + dst = (gsi_u8*)theCursor; + src = (gsi_u8*)&netValue; + + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + + return (gsi_u8*)dst; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeKey(gsi_u8* theCursor, gsi_u16 theKey) +{ + return sciSerializeInt16(theCursor, (gsi_i16)theKey); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeDataLength(gsi_u8* theCursor, gsi_u32 theDataLength) +{ + return sciSerializeInt32(theCursor, (gsi_i32)theDataLength); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeGUID(gsi_u8* theCursor, const gsi_u8 theGUID[SC_CONNECTION_GUID_SIZE]) +{ + // a GUID is a string comprised of an int, two shorts and eight bytes + // 6B29FC40-CA47-1067-B31D-00DD010662DA + gsi_u32 anInt = 0; + gsi_u32 aShort1 = 0; + gsi_u32 aShort2 = 0; + gsi_u32 aIntArray[8]; + gsi_u8 aByteArray[8]; + int i=0; + + sscanf((char *)theGUID, "%8x", &anInt); + sscanf((char *)theGUID+9, "%4x", &aShort1); + sscanf((char *)theGUID+14, "%4x", &aShort2); + + sscanf((char *)theGUID+19, "%2x", &aIntArray[0]); + sscanf((char *)theGUID+21, "%2x", &aIntArray[1]); + + sscanf((char *)theGUID+24, "%2x%2x%2x%2x%2x%2x", &aIntArray[2], + &aIntArray[3], &aIntArray[4], &aIntArray[5], + &aIntArray[6], &aIntArray[7]); + + for (i=0; i < 8; i++) + aByteArray[i] = (gsi_u8)aIntArray[i]; + + theCursor = sciSerializeInt32(theCursor, (gsi_i32)anInt); + theCursor = sciSerializeInt16(theCursor, (gsi_i16)aShort1); + theCursor = sciSerializeInt16(theCursor, (gsi_i16)aShort2); + memcpy(theCursor, aByteArray, 8); + theCursor += 8; + + return theCursor; +} diff --git a/code/gamespy/sc/sciSerialize.h b/code/gamespy/sc/sciSerialize.h new file mode 100644 index 00000000..32e65d99 --- /dev/null +++ b/code/gamespy/sc/sciSerialize.h @@ -0,0 +1,45 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __SCISERIALIZE_H__ +#define __SCISERIALIZE_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sciReport.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeInt8 (gsi_u8* theCursor, gsi_i8 theValue); + +gsi_u8* sciSerializeInt16 (gsi_u8* theCursor, gsi_i16 theValue); + +gsi_u8* sciSerializeInt32 (gsi_u8* theCursor, gsi_i32 theValue); + +gsi_u8* sciSerializeInt64 (gsi_u8* theCursor, gsi_i64 theValue); + +gsi_u8* sciSerializeFloat (gsi_u8* theCursor, float theValue); + +gsi_u8* sciSerializeKey (gsi_u8* theCursor, gsi_u16 theKey); + +gsi_u8* sciSerializeDataLength(gsi_u8* theCursor, gsi_u32 theDataLength); + +gsi_u8* sciSerializeGUID (gsi_u8* theCursor, const gsi_u8 theGUID[SC_CONNECTION_GUID_SIZE]); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __SCSERIALIZE_H__ diff --git a/code/gamespy/sc/sciWebServices.c b/code/gamespy/sc/sciWebServices.c new file mode 100644 index 00000000..4620f94a --- /dev/null +++ b/code/gamespy/sc/sciWebServices.c @@ -0,0 +1,600 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../common/gsCore.h" + +#include "sci.h" +#include "sciInterface.h" +#include "sciWebServices.h" +#include "sciReport.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define SC_CREATEMATCHLESSSESSION_SOAPACTION "SOAPAction: \"http://gamespy.net/competition/CreateMatchlessSession\"" +#define SC_CREATESESSION_SOAPACTION "SOAPAction: \"http://gamespy.net/competition/CreateSession\"" +#define SC_SUBMITREPORT_SOAPACTION "SOAPAction: \"http://gamespy.net/competition/SubmitReport\"" +#define SC_SETINTENTION_SOAPACTION "SOAPAction: \"http://gamespy.net/competition/SetReportIntention\"" + +#define SC_SERVICE_NAMESPACE_COUNT 1 +const char * SC_SERVICE_NAMESPACES[SC_SERVICE_NAMESPACE_COUNT] = +{ + "gsc=\"http://gamespy.net/competition/\"" +}; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciWsInit(SCWebServices* theWebServices, + SCInterfacePtr theInterface) +{ + + + GS_ASSERT(theWebServices != NULL); + GS_ASSERT(theInterface != NULL); + GS_ASSERT(!theWebServices->mInit); + + // Check gsCore + if (gsCoreIsShutdown()) + { + return SCResult_CORE_NOT_INITIALIZED; + } + + // Initialize SCWebServices struct + theWebServices->mInterface = theInterface; + theWebServices->mCreateSessionCallback = NULL; + theWebServices->mSetReportIntentionCallback = NULL; + theWebServices->mSubmitReportDataCallback = NULL; + theWebServices->mCreateSessionUserData = NULL; + theWebServices->mSetReportIntentionUserData = NULL; + theWebServices->mSubmitReportUserData = NULL; + theWebServices->mCreateSessionPending = gsi_false; + theWebServices->mSetReportIntentionPending = gsi_false; + theWebServices->mSubmitReportPending = gsi_false; + + // Now initialized + theWebServices->mInit = gsi_true; + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void sciWsDestroy(SCWebServices* theWebServices) +{ + GS_ASSERT(theWebServices != NULL); + GS_ASSERT(theWebServices->mInit); + + // No longer initialized + theWebServices->mInit = gsi_false; + + // Destroy SCWebServices struct + theWebServices->mCreateSessionCallback = NULL; + theWebServices->mSetReportIntentionCallback = NULL; + theWebServices->mSubmitReportDataCallback = NULL; + theWebServices->mCreateSessionUserData = NULL; + theWebServices->mSetReportIntentionUserData = NULL; + theWebServices->mSubmitReportUserData = NULL; + theWebServices->mCreateSessionPending = gsi_false; + theWebServices->mSetReportIntentionPending = gsi_false; + theWebServices->mSubmitReportPending = gsi_false; + theWebServices->mInterface = NULL; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void sciWsThink(SCWebServices* theWebServices) +{ + GS_ASSERT(theWebServices != NULL); + GS_ASSERT(theWebServices->mInit); + + gsCoreThink(0); + + GSI_UNUSED(theWebServices); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciWsCreateSession (SCWebServices * theWebServices, + gsi_u32 theGameId, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCCreateSessionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData) +{ + GSXmlStreamWriter aRequest = NULL; + + // Check parameters + GS_ASSERT(theWebServices != NULL); + GS_ASSERT(theWebServices->mInit); + + // Check for pending request + if (theWebServices->mCreateSessionPending) + return SCResult_CALLBACK_PENDING; + + // Create the XML message writer + aRequest = gsXmlCreateStreamWriter(SC_SERVICE_NAMESPACES, SC_SERVICE_NAMESPACE_COUNT); + if (aRequest == NULL) + return SCResult_OUT_OF_MEMORY; + + // Fill in the request data + if (gsi_is_false(gsXmlWriteOpenTag(aRequest, "gsc", "CreateSession")) || + gsi_is_false(gsXmlWriteOpenTag(aRequest, "gsc", "certificate")) || + gsi_is_false(wsLoginCertWriteXML(theCertificate, "gsc", aRequest)) || + gsi_is_false(gsXmlWriteCloseTag(aRequest, "gsc", "certificate")) || + gsi_is_false(gsXmlWriteHexBinaryElement(aRequest, "gsc", "proof", (const gsi_u8*)thePrivateData->mKeyHash, GS_CRYPT_MD5_HASHSIZE)) || + gsi_is_false(gsXmlWriteIntElement(aRequest, "gsc", "gameid", (gsi_u32)theGameId)) || + gsi_is_false(gsXmlWriteCloseTag(aRequest, "gsc", "CreateSession")) || + gsi_is_false(gsXmlCloseWriter(aRequest)) + ) + { + gsXmlFreeWriter(aRequest); + return SCResult_HTTP_ERROR; + } + + // Set callback + theWebServices->mCreateSessionCallback = theCallback; + theWebServices->mCreateSessionUserData = theUserData; + theWebServices->mCreateSessionPending = gsi_true; + + // Execute soap call + gsiExecuteSoap(scServiceURL, SC_CREATESESSION_SOAPACTION, + aRequest, sciWsCreateSessionCallback, theWebServices); + GSI_UNUSED(theTimeoutMs); + return SCResult_NO_ERROR; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciWsCreateMatchlessSession (SCWebServices * theWebServices, + gsi_u32 theGameId, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCCreateSessionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData) +{ + GSXmlStreamWriter aRequest = NULL; + + // Check parameters + GS_ASSERT(theWebServices != NULL); + GS_ASSERT(theWebServices->mInit); + + // Check for pending request + if (theWebServices->mCreateSessionPending) + return SCResult_CALLBACK_PENDING; + + // Create the XML message writer + aRequest = gsXmlCreateStreamWriter(SC_SERVICE_NAMESPACES, SC_SERVICE_NAMESPACE_COUNT); + if (aRequest == NULL) + return SCResult_OUT_OF_MEMORY; + + // Fill in the request data + if (gsi_is_false(gsXmlWriteOpenTag(aRequest, "gsc", "CreateMatchlessSession")) || + gsi_is_false(gsXmlWriteOpenTag(aRequest, "gsc", "certificate")) || + gsi_is_false(wsLoginCertWriteXML(theCertificate, "gsc", aRequest)) || + gsi_is_false(gsXmlWriteCloseTag(aRequest, "gsc", "certificate")) || + gsi_is_false(gsXmlWriteHexBinaryElement(aRequest, "gsc", "proof", (const gsi_u8*)thePrivateData->mKeyHash, GS_CRYPT_MD5_HASHSIZE)) || + gsi_is_false(gsXmlWriteIntElement(aRequest, "gsc", "gameid", (gsi_u32)theGameId)) || + gsi_is_false(gsXmlWriteCloseTag(aRequest, "gsc", "CreateMatchlessSession")) || + gsi_is_false(gsXmlCloseWriter(aRequest)) + ) + { + gsXmlFreeWriter(aRequest); + return SCResult_HTTP_ERROR; + } + + // Set callback + theWebServices->mCreateSessionCallback = theCallback; + theWebServices->mCreateSessionUserData = theUserData; + theWebServices->mCreateSessionPending = gsi_true; + + // Execute soap call + gsiExecuteSoap(scServiceURL, SC_CREATEMATCHLESSSESSION_SOAPACTION, + aRequest, sciWsCreateSessionCallback, theWebServices); + GSI_UNUSED(theTimeoutMs); + return SCResult_NO_ERROR; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void sciWsCreateSessionCallback(GHTTPResult theHttpResult, + GSXmlStreamWriter theRequestData, + GSXmlStreamReader theResponseData, + void* theUserData) +{ + SCResult aTranslatedResult = SCResult_HTTP_ERROR; + SCWebServices* aWebServices = (SCWebServices*)theUserData; + + char csid[255]; + char ccid[255]; + int csidLen = 255; + int ccidLen = 255; + + GS_ASSERT(aWebServices != NULL); + GS_ASSERT(aWebServices->mCreateSessionPending); + + // Check for shutdown + if (!aWebServices->mInit) + return; + + if (theHttpResult == GHTTPSuccess) + { + int createResult = 0; + + // Parse through in a way that will work for either type of CreateSession response. + if (gsi_is_false(gsXmlMoveToStart(theResponseData))) + { + aTranslatedResult = SCResult_RESPONSE_INVALID; + } + else if(gsi_is_false(gsXmlMoveToNext(theResponseData, "CreateSessionResponse"))) + { + if(gsi_is_false(gsXmlMoveToNext(theResponseData, "CreateMatchlessSessionResponse"))) + { + aTranslatedResult = SCResult_RESPONSE_INVALID; + } + } + + if(gsi_is_false(gsXmlMoveToNext(theResponseData, "CreateSessionResult"))) + { + if(gsi_is_false(gsXmlMoveToNext(theResponseData, "CreateMatchlessSessionResult"))) + { + aTranslatedResult = SCResult_RESPONSE_INVALID; + } + } + + if(gsi_is_false(gsXmlReadChildAsInt(theResponseData, "result", &createResult))) + { + aTranslatedResult = SCResult_RESPONSE_INVALID; + } + else if(aTranslatedResult != SCResult_RESPONSE_INVALID) + { + // Parse server reported result + if (createResult == SCWsResult_NO_ERROR) + { + // Read session and connection ID + if(gsi_is_false(gsXmlReadChildAsStringNT(theResponseData, "csid", csid, csidLen)) || + gsi_is_false(gsXmlReadChildAsStringNT(theResponseData, "ccid", ccid, ccidLen)) + ) + { + aTranslatedResult = SCResult_RESPONSE_INVALID; + } + else + { + sciInterfaceSetSessionId((SCInterface*)aWebServices->mInterface, csid); + sciInterfaceSetConnectionId((SCInterface*)aWebServices->mInterface, ccid); + aTranslatedResult = SCResult_NO_ERROR; + } + } + else + { + // Server reported an error, handle it? + + // TODO: + // translate result into developer useable form + // report result string as gsDebugFormat message for easier debugging + aTranslatedResult = SCResult_RESPONSE_INVALID; + } + } + } + else + { + aTranslatedResult = SCResult_HTTP_ERROR; + } + + // Client callback + aWebServices->mCreateSessionPending = gsi_false; + if (aWebServices->mCreateSessionCallback != NULL) + { + aWebServices->mCreateSessionCallback(aWebServices->mInterface, theHttpResult, aTranslatedResult, aWebServices->mCreateSessionUserData); + aWebServices->mCreateSessionUserData = NULL; + aWebServices->mCreateSessionCallback = NULL; + } + GSI_UNUSED(theRequestData); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciWsSetReportIntention(SCWebServices* theWebServices, + gsi_u32 theGameId, + const char * theSessionId, + const char * theConnectionId, + gsi_bool isAuthoritative, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCSetReportIntentionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData) +{ + GSXmlStreamWriter aRequest = NULL; + + // Check parameters + GS_ASSERT(theWebServices != NULL); + GS_ASSERT(theWebServices->mInit); + + // Check for pending request + if (theWebServices->mSetReportIntentionPending) + return SCResult_CALLBACK_PENDING; + + // Create the XML message writer + aRequest = gsXmlCreateStreamWriter(SC_SERVICE_NAMESPACES, SC_SERVICE_NAMESPACE_COUNT); + if (aRequest == NULL) + return SCResult_OUT_OF_MEMORY; + + // Fill in the request data + if (gsi_is_false(gsXmlWriteOpenTag(aRequest, "gsc", "SetReportIntention")) || + gsi_is_false(gsXmlWriteOpenTag(aRequest, "gsc", "certificate")) || + gsi_is_false(wsLoginCertWriteXML(theCertificate, "gsc", aRequest)) || + gsi_is_false(gsXmlWriteCloseTag(aRequest, "gsc", "certificate")) || + gsi_is_false(gsXmlWriteHexBinaryElement(aRequest, "gsc", "proof", (const gsi_u8*)thePrivateData->mKeyHash, GS_CRYPT_MD5_HASHSIZE)) || + gsi_is_false(gsXmlWriteStringElement(aRequest, "gsc", "csid", theSessionId)) || + gsi_is_false(gsXmlWriteStringElement(aRequest, "gsc", "ccid", theConnectionId)) || + gsi_is_false(gsXmlWriteIntElement(aRequest, "gsc", "gameid", (gsi_u32)theGameId)) || + gsi_is_false(gsXmlWriteIntElement(aRequest, "gsc", "authoritative", (gsi_u32)(gsi_is_true(isAuthoritative) ? 1:0))) || + gsi_is_false(gsXmlWriteCloseTag(aRequest, "gsc", "SetReportIntention")) || + gsi_is_false(gsXmlCloseWriter(aRequest)) + ) + { + gsXmlFreeWriter(aRequest); + return SCResult_HTTP_ERROR; + } + + // Set callback + theWebServices->mSetReportIntentionCallback = theCallback; + theWebServices->mSetReportIntentionUserData = theUserData; + theWebServices->mSetReportIntentionPending = gsi_true; + + // Execute soap call + gsiExecuteSoap(scServiceURL, SC_SETINTENTION_SOAPACTION, + aRequest, sciWsSetReportIntentionCallback, theWebServices); + + GSI_UNUSED(theTimeoutMs); + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void sciWsSetReportIntentionCallback(GHTTPResult theHttpResult, + GSXmlStreamWriter theRequestData, + GSXmlStreamReader theResponseData, + void* theUserData) +{ + SCResult aTranslatedResult = SCResult_HTTP_ERROR; + SCWebServices* aWebServices = (SCWebServices*)theUserData; + + char ccid[255]; + int ccidLen = 255; + + + GS_ASSERT(aWebServices != NULL); + GS_ASSERT(aWebServices->mSetReportIntentionPending); + + // Check for shutdown + if (!aWebServices->mInit) + return; + + if (theHttpResult == GHTTPSuccess) + { + int intentionResult = 0; + + if (gsi_is_false(gsXmlMoveToStart(theResponseData)) || + gsi_is_false(gsXmlMoveToNext(theResponseData, "SetReportIntentionResponse")) || + gsi_is_false(gsXmlMoveToNext(theResponseData, "SetReportIntentionResult")) || + gsi_is_false(gsXmlReadChildAsInt(theResponseData, "result", &intentionResult)) || + gsi_is_false(gsXmlReadChildAsStringNT(theResponseData, "ccid", ccid, ccidLen)) + ) + { + aTranslatedResult = SCResult_RESPONSE_INVALID; + } + else + { + if (intentionResult == SCWsResult_NO_ERROR) + { + aTranslatedResult = SCResult_NO_ERROR; + sciInterfaceSetConnectionId((SCInterface*)aWebServices->mInterface, ccid); + } + else + aTranslatedResult = SCResult_UNKNOWN_RESPONSE; + } + } + else + { + aTranslatedResult = SCResult_HTTP_ERROR; + } + + // Client callback + aWebServices->mSetReportIntentionPending = gsi_false; + if (aWebServices->mSetReportIntentionCallback != NULL) + { + aWebServices->mSetReportIntentionCallback(aWebServices->mInterface, + theHttpResult, + aTranslatedResult, + aWebServices->mSetReportIntentionUserData); + aWebServices->mSetReportIntentionUserData = NULL; + aWebServices->mSetReportIntentionCallback = NULL; + } + GSI_UNUSED(theRequestData); +} + + +/////////////////////////////////////////////////////////////////////////////// +// declared here to allow function to get around Unicode calls +extern GHTTPBool ghiPostAddFileFromMemory(GHTTPPost post,const char * name,const char * buffer, + int bufferLen,const char * reportFilename,const char * contentType); +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Private GSSoapCustomFunc used by sciWsSubmitReport +static void sciWsSubmitReportCustom(GHTTPPost thePost, void* theUserData) +{ + SCWebServices* aWebServices = (SCWebServices*)theUserData; + + //Use internal method to get around unicode calls + ghiPostAddFileFromMemory(thePost, "report", (char *)aWebServices->mSubmitReportData, + (gsi_i32)aWebServices->mSubmitReportLength, "report", "application/bin"); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciWsSubmitReport(SCWebServices* theWebServices, + gsi_u32 theGameId, + const char * theSessionId, + const char * theConnectionId, + const SCIReport * theReport, + gsi_bool isAuthoritative, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCSubmitReportCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData) +{ + GSXmlStreamWriter aRequest = NULL; + //SCIReportHeader * aReportHeader = NULL; + //gsi_u32 aTotalSize = 0; + + //SCReportStatus* aStatus = NULL; + + // Check parameters + GS_ASSERT(theWebServices != NULL); + //GS_ASSERT(theReportData != NULL); + + // Check for pending request + if (theWebServices->mSubmitReportPending) + { + return SCResult_CALLBACK_PENDING; + } + + // Get a pointer to the header + //aReportHeader = (SCIReportHeader*)theReport->mBuffer.mData; + + // Check for complete report + if (theReport->mBuffer.mPos < sizeof(SCIReportHeader)) + return SCResult_REPORT_INVALID; + + // Check size (early check for easier debugging) + //aTotalSize = sizeof(SCIReportHeader); + //aTotalSize += htonl(aReportHeader->mPlayerDataLength); + //aTotalSize += htonl(aReportHeader->mTeamDataLength); + //aTotalSize += htonl(aReportHeader->mSessionDataLength); + // aTotalSize += auth info... + //if (theReport->mBuffer.mPos != aTotalSize) + // return SCResult_REPORT_INVALID; + + // Create the XML message writer + aRequest = gsXmlCreateStreamWriter(SC_SERVICE_NAMESPACES, SC_SERVICE_NAMESPACE_COUNT); + if (aRequest == NULL) + return SCResult_OUT_OF_MEMORY; + + // Fill in the request data + if (gsi_is_false(gsXmlWriteOpenTag(aRequest, "gsc", "SubmitReport")) || + gsi_is_false(gsXmlWriteOpenTag(aRequest, "gsc", "certificate")) || + gsi_is_false(wsLoginCertWriteXML(theCertificate, "gsc", aRequest)) || + gsi_is_false(gsXmlWriteCloseTag(aRequest, "gsc", "certificate")) || + gsi_is_false(gsXmlWriteHexBinaryElement(aRequest, "gsc", "proof", (const gsi_u8*)thePrivateData->mKeyHash, GS_CRYPT_MD5_HASHSIZE)) || + gsi_is_false(gsXmlWriteStringElement(aRequest, "gsc", "csid", theSessionId)) || + gsi_is_false(gsXmlWriteStringElement(aRequest, "gsc", "ccid", theConnectionId)) || + gsi_is_false(gsXmlWriteIntElement(aRequest, "gsc", "gameid", (gsi_u32)theGameId)) || + gsi_is_false(gsXmlWriteIntElement(aRequest, "gsc", "authoritative", (gsi_u32)(gsi_is_true(isAuthoritative) ? 1:0))) || + gsi_is_false(gsXmlWriteCloseTag(aRequest, "gsc", "SubmitReport")) || + gsi_is_false(gsXmlCloseWriter(aRequest)) + ) + { + gsXmlFreeWriter(aRequest); + return SCResult_OUT_OF_MEMORY; + } + + // Get submission size + theWebServices->mSubmitReportData = (gsi_u8*)theReport->mBuffer.mData; + theWebServices->mSubmitReportLength = theReport->mBuffer.mPos; + + // Set callback + theWebServices->mSubmitReportDataCallback = theCallback; + theWebServices->mSubmitReportUserData = theUserData; + theWebServices->mSubmitReportPending = gsi_true; + + // Execute soap call + gsiExecuteSoapCustom(scServiceURL, SC_SUBMITREPORT_SOAPACTION, + aRequest, sciWsSubmitReportCallback,sciWsSubmitReportCustom, theWebServices); + + GSI_UNUSED(theTimeoutMs); + return SCResult_NO_ERROR; +} + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void sciWsSubmitReportCallback(GHTTPResult theHttpResult, + GSXmlStreamWriter theRequestData, + GSXmlStreamReader theResponseData, + void* theUserData) +{ + SCResult aTranslatedResult = SCResult_HTTP_ERROR; + SCWebServices* aWebServices = (SCWebServices*)theUserData; + + GS_ASSERT(aWebServices != NULL); + + // Check for shutdown + if (!aWebServices->mInit) + return; + + GS_ASSERT(aWebServices->mSubmitReportPending); + + if (theHttpResult == GHTTPSuccess) + { + int submitResult = 0; + + if (gsi_is_false(gsXmlMoveToStart(theResponseData)) || + gsi_is_false(gsXmlMoveToNext(theResponseData, "SubmitReportResponse")) || + gsi_is_false(gsXmlMoveToNext(theResponseData, "SubmitReportResult")) || + gsi_is_false(gsXmlReadChildAsInt(theResponseData, "result", &submitResult)) + ) + { + aTranslatedResult = SCResult_RESPONSE_INVALID; + } + else + { + switch (submitResult) + { + case SCWsResult_NO_ERROR: + aTranslatedResult = SCResult_NO_ERROR; + break; + case SCWsResult_REPORT_INVALID: + aTranslatedResult = SCResult_REPORT_INVALID; + break; + case SCWsResult_SINGLE_ATTACHMENT_EXPECTED: + aTranslatedResult = SCResult_SUBMISSION_FAILED; + break; + default: + aTranslatedResult = SCResult_UNKNOWN_RESPONSE; + break; + }; + } + } + else + { + aTranslatedResult = SCResult_HTTP_ERROR; + } + + // Client callback + aWebServices->mSubmitReportPending = gsi_false; + if (aWebServices->mSubmitReportDataCallback != NULL) + { + aWebServices->mSubmitReportDataCallback(aWebServices->mInterface, + theHttpResult, + aTranslatedResult, + aWebServices->mSubmitReportUserData); + aWebServices->mSubmitReportUserData = NULL; + aWebServices->mSubmitReportDataCallback = NULL; + } + GSI_UNUSED(theRequestData); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/sc/sciWebServices.h b/code/gamespy/sc/sciWebServices.h new file mode 100644 index 00000000..a6d0fb5c --- /dev/null +++ b/code/gamespy/sc/sciWebServices.h @@ -0,0 +1,126 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __SCWEBSERVICES_H__ +#define __SCWEBSERVICES_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../common/gsSoap.h" +#include "../common/gsXML.h" +#include "../ghttp/ghttpPost.h" + +#include "sci.h" +#include "sciReport.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Web service result codes (must match definitions in StatsReceiver) +typedef enum +{ + SCWsResult_NO_ERROR = 0, + SCWsResult_REPORT_INVALID, + SCWsResult_SINGLE_ATTACHMENT_EXPECTED +} SCWsResult; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct +{ + SCInterfacePtr mInterface; + SCCreateSessionCallback mCreateSessionCallback; + SCSetReportIntentionCallback mSetReportIntentionCallback; + SCSubmitReportCallback mSubmitReportDataCallback; + gsi_bool mSetReportIntentionPending; + gsi_bool mCreateSessionPending; + gsi_bool mSubmitReportPending; + void * mSetReportIntentionUserData; + void * mCreateSessionUserData; + void * mSubmitReportUserData; + gsi_u8* mSubmitReportData; + gsi_u32 mSubmitReportLength; + gsi_bool mInit; +} SCWebServices; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciWsInit (SCWebServices* theWebServices, + SCInterfacePtr theInterface); +void sciWsDestroy(SCWebServices* theWebServices); +void sciWsThink (SCWebServices* theWebServices); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciWsCreateSession (SCWebServices * theWebServices, + gsi_u32 theGameId, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCCreateSessionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData); + +SCResult sciWsCreateMatchlessSession(SCWebServices * theWebServices, + gsi_u32 theGameId, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCCreateSessionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData); + +void sciWsCreateSessionCallback(GHTTPResult theHttpResult, + GSXmlStreamWriter theRequestData, + GSXmlStreamReader theResponseData, + void* theUserData); + +SCResult sciWsSetReportIntention (SCWebServices* theWebServices, + gsi_u32 theGameId, + const char * theSessionId, + const char * theConnectionId, + gsi_bool isAuthoritative, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCSetReportIntentionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData); + +void sciWsSetReportIntentionCallback(GHTTPResult theHttpResult, + GSXmlStreamWriter theRequestData, + GSXmlStreamReader theResponseData, + void* theUserData); + +SCResult sciWsSubmitReport (SCWebServices* theWebServices, + gsi_u32 theGameId, + const char * theSessionId, + const char * theConnectionId, + const SCIReport* theReport, + gsi_bool isAuthoritative, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCSubmitReportCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData); + +void sciWsSubmitReportCallback(GHTTPResult theHttpResult, + GSXmlStreamWriter theRequestData, + GSXmlStreamReader theResponseData, + void* theUserData); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __SCWEBSERVICES_H__ diff --git a/code/gamespy/serverbrowsing/changelog.txt b/code/gamespy/serverbrowsing/changelog.txt new file mode 100644 index 00000000..9877cafb --- /dev/null +++ b/code/gamespy/serverbrowsing/changelog.txt @@ -0,0 +1,184 @@ +Changelog for: GameSpy Server Browsing SDK +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 2.07.00 RMV RELEASE Released to Developer Site +12-11-2007 2.06.05 SAH OTHER Added NatNeg files to projects to remove compiler errors +11-27-2007 2.06.03 SAH CLEANUP Moved extern "c" block below includes to prevent linker errors +11-21-2007 2.06.02 SAH FIX Added NNFreeNegotiateList to ServerBrowserFree function +11-05-2007 2.06.01 DES FEATURE Added ServerBrowserConnectToServer (ACE functionality) +08-06-2007 2.06.00 RMV OTHER Modified sbctest, making it more thorough and developer friendly (and added ReadMe) +08-06-2007 2.06.00 RMV RELEASE Released to Developer Site +07-10-2007 2.05.04 RMV FIX Fixed test project files to get rid of Unicode warnings and fixed other compiler warnings +05-16-2007 2.05.03 DES FIX Changed use of u_char to gsi_u8 + DES FEATURE ICMP support is now included by default, define SB_NO_ICMP_SUPPORT to exclude it +01-25-2007 2.05.02 SAH FIX Fixed a bug where the country/region keys would be queried for by individual servers +01-16-2007 2.05.01 DES FEATURE Added X360 support +12-15-2006 2.05.00 MJW RELEASE Released to Developer Site +11-10-2006 2.04.36 JR RELEASE Limited release +10-23-2006 2.04.36 DES RELEASE Limited release +10-11-2006 2.04.36 SN FIX Fixed case where possible disconnection could corrupt input buffer when there + was still data on the buffer. +10-05-2006 2.04.35 SAH FIX Updated MacOSX Makefile +09-28-2006 2.04.34 SAH FIX Fixed PS3 project to work with PS3 095.00x SDK; changed included libaries in linker input. +08-18-2006 2.04.33 SN FIX Fixed ServerBrowserAuxUpdateIP where servers not pending queries were marked + with duplicate query error +08-02-2006 2.04.32 SAH RELEASE Releasing to developer site +07-31-2006 2.04.32 SAH FIX Fixed PS3 project file - added post-build step to create *.SELF for execution +07-25-2006 2.04.31 SAH FIX Fixed NITRO project, include for crt0.o now above others so it add correct file +07-24-2006 2.04.30 SAH FIX Added newline at EOF to sb_serverbrowsing.c to fix compilation warning + SAH FIX Removed #ifdef _PS3 for socket calls (changed platformSocket.h to typecast calls) +07-06-2006 2.04.29 SAH FIX Changed a break; to a return; for challenge received when calling auxUpdateIP synchronously + SAH FIX Made Querytest.c use test_main like our other SDKs, edited project appropriately +07-06-2006 2.04.28 SAH FIX Fixed PSP project file to not explicitly include the PSP linker file +07-06-2006 2.04.27 SAH FIX Fixed linux makefile to work with pthreads (for http asynch DNS lookup) +06-30-2006 2.04.26 SAH FIX Changed natneg = 1 to SBTrue to get rid of warnings + SAH FIX Fixed NITRO project && linker command file (for Codewarrior 2.0/NitroSDK 3.1) +06-30-2006 2.04.25 MJ FEATURE Added SBServerGetConnectionInfo +05-31-2006 2.04.24 SAH RELEASE Releasing to developer site + SAH FIX Fixed Linux makefile +05-30-2006 2.04.24 SAH FIX Fixed PS3 projects to work with PS3(084_001 SDK) +05-25-2006 2.04.24 SAH FIX Changed PSP project warning levels +05-24-2006 2.04.23 SAH FIX added sbe_duplicateupdateerror case to switch statement +05-23-2006 2.04.22 BED FIX AuxUpdate functions will no longer update a server while an update is already pending +05-23-2006 2.04.21 SAH FIX Added win32common.c and gsTestMain.c to sbctest project +05-23-2006 2.04.20 SAH FIX Added GS_STATIC_CALLBACK to compare functions for __fastcall support +05-19-2006 2.04.19 SAH FIX Added gsTestMain.c for nitro CodeWarrior project +05-15-2006 2.04.18 SAH FIX Added "PS3 Release" configuration to project +04-25-2006 2.04.17 SAH RELEASE Releasing to developer site +04-24-2006 2.04.17 SAH FIX Fixed UNICODE compile errors, mainly typecasts +04-24-2006 2.04.16 SAH FIX Fixed SortInfo creation call, fixed Nitro project files to work on build machine +04-20-2006 2.04.15 SAH FIX Removed unnecessary break; statements, added some (char*) typecasts + SAH FIX Moved GSI_UNUSED calls above return statements +04-20-2006 2.04.14 SAH FIX Added _PS3 wrapper typecast for recvfrom calls +04-19-2006 2.04.13 SAH FIX Added || defined(_PS3) to sbctest.c for PS3 Support +04-13-2006 2.04.12 SAH FIX Added parenthesis to remove compiler warnings before Release +04-10-2006 2.04.11 SAH FEATURE Multiple sorts will retain previous sorting arrangement when values are equal +03-28-2006 2.04.10 SN FIX Added extra checks to account for empty keys +03-28-2006 2.04.10 SN FIX Fixed SBServerGetIntValue to return negative numbers +03-20-2006 2.04.09 SN OTHER Added PSP to the defines before the test_main in sbctest + SN OTHER Moved the GSI_UNUSED calls to before the return statement +01-27-2006 2.04.09 SN RELEASE Releasing to developer site +01-27-2006 2.04.09 SN FIX Added psp prodg project and solution to sgv +12-22-2005 2.04.08 SN OTHER Cleaned up project files and added missing common code. +11-17-2005 2.04.07 DES FIX Compatibility fixes. + DES FIX Updated Nitro Makefile. +11-14-2005 2.04.06 DES FIX Updated OSX support. + DES FEATURE Added GSI_DOMAIN_NAME support. +10-12-2005 2.04.05 BED RELEASE Releasing to developer site. +10-11-2005 2.04.04 BED FEATURE Added query challege/response to prevent ip spoofing. +09-23-2005 2.04.03 DES FEATURE Updated DS support + DES FIX Fixed SBServerGetBoolValue() to return the default is the value is empty +07-28-2005 2.04.02 SN RELEASE Releasing to developer site. +06-28-2005 2.04.02 BED FEATURE Added ICMP support for remaining platforms. +06-03-2005 2.04.01 SN RELEASE Releasing to developer site. +05-05-2005 2.04.01 BED FIX Updated projects to use new common folder. +o5-04-2005 2.04.00 SN OTHER Created Visual Studio .NET projects +04-29-2005 2.04.00 BED FEATURE Added support for multi-packet qr2 responses. (Full keys direct only) +04-28-2005 2.03.03 SN RELEASE Releasing to developer site. +04-27-2005 2.03.03 DES RELEASE Limited release to Nintendo DS developers. +04-27-2005 2.03.03 DES FEATURE Modified sbctest to quit when the update is complete. +04-25-2005 2.03.02 DES FIX Fixed a function call in querytest. +04-21-2005 2.03.01 DES FIX Renamed IPHeader and ICMPHeader to avoid Nitro header conflict. +04-18-2005 2.03.00 DDW FEATURE Added initial Win32 support for ICMP pings of firewalled servers + FEATURE Added new SBServerHasValidPing public function +04-04-2005 2.02.21 SN RELEASE Releasing to developer site. +03-14-2005 2.02.21 DES FEATURE Nintendo DS support +02-17-2005 2.02.20 DDW OTHER Extended gamenames in SB structures to 36 bytes from 32 (dynamic gamename support) +01-27-2005 2.02.19 DES FIX Fixed custom SN sendto and moved it to nonport +11-03-2004 2.02.18 SN FIX Fixed and turned off availability check during LAN browsing, + SN FIX Added a check to a server object when obtaining key values + SN FIX Updated SBMFC sample to have new ServerBrowserNew function +09-27-2004 2.02.17 SN RELEASE Releasing qr2 fix to developer site +09-16-2004 2.02.16 SN RELEASE Releasing to developer site. +09-08.2004 2.02.16 BED FIX Fixed cases where same server could appear in FIFO multiple times +08-27-2004 2.02.15 DES CLEANUP Fixed warnings under OSX + DES CLEANUP Updated Win32 project configurations + DES CLEANUP Removed #pragma comment for linking with winsock (in nonport now) + DES CLEANUP General Unicode cleanup + DES CLEANUP Removed MacOS style includes + DES FEATURE Added OSX Makefile +08-25-2004 2.02.14 DES FEATURE Added OSX makefile +08-04-2004 2.02.13 SN RELEASE Releasing to developer site. +07-12-2004 2.02.13 SN FIX Cleared errors when warnings are treated as errors set, and warning level is highest. +06-18-2004 2.02.12 BED RELEASE Releasing to developer site. +06-16-2004 2.02.12 BED FEATURE PS2 Insock configurations added to samples. +05-24-2004 2.02.11 BED FIX ServerBrowserAuxUpdateIP no longer adds a duplicate SBServer to the list +05-22-2004 2.02.10 BED FEATURE Ps2 Insock support added. +05-21-2004 2.02.09 DDW FEATURE VEngine & Peer compatibility fix - now local to each serverlist +05-14-2004 2.02.08 DDW FEATURE VEngine & Peer compatibility fix +05-06-2004 2.02.07 DDW FEATURE Allow external management of the unique string hash +05-06-2004 2.02.06 BED FIX SBServerGetIntValue now returns instead of 0 for a key that is empty-string. +04-02-2004 2.02.05 BED FIX Capped max recvfrom size to 2048 for PS2 INSOCK compatibility. + BED FEATURE Added ability to specify network adapter when LAN browsing. + BED FIX Changed "unsigned long" to "gsi_time" in a couple places for PS2 compatability. +03-30-2004 2.02.04 BED FIX Fixed timing issue where server might appear twice in the FIFO, causing a crash. +03-19-2004 2.02.03 BED FEATURE Added ability to specify network adapter when LAN browsing. +03-09-2004 2.02.02 BED FIX Fixed case where browsing would not complete when all servers were behind a firewall. +01-12-2004 2.02.01 BED FEATURE Added ServerBrowserGetServerByIP for IP lookup into the list. +01-07-2004 2.02.00 DDW FEATURE Added internal support for player searching +01-07-2004 2.01.01 BED FIX Added prototypes for ascii functions in UNICODE mode. +12-16-2003 2.01.00 DDW FEATURE Added internal support for map loop lookup +11-10-2003 2.00.36 DES RELEASE Releasing to developer site. +11-10-2003 2.00.36 BED FIX Fixed case where LAN browsing would never complete. +11-07-2003 2.00.35 BED FIX Update CodeWarrior project file. +11-07-2003 2.00.35 DES FIX Updated linux and PS2 makefiles. +11-06-2003 2.00.34 BED FIX Fixed case where raw buffer was treated as string (unicode conversion doesn't apply) +11-04-2003 2.00.33 BED FIX Added some const-correctness to the SDK. + BED FIX Removed misc warnings for devstudio level 4. +11-04-2003 2.00.32 DES FEATURE Added availability check code. +10-22-2003 2.00.31 BED RELEASE Releasing to developer site. (UNIQUE NICK AND UNICODE SUPPORT) +10-22-2003 2.00.31 BED FIX Removed some compiler warnings on scrict setting. +10-21-2003 2.00.30 DES FIX Added stringutil files to VC++ sample projects. +10-09-2003 2.00.29 BED FIX Switched to gsi_time type instead of unsinged long for PS2 compatibility +09-09-2003 2.00.28 BED FEATURE Added UTF-8 wrapper -- define GSI_UNICODE + OTHER Added stringutil.c and stringutil.h to sample project files +08-06-2003 2.00.27 BED FIX Increased sb_serverlist.c incoming UDP buffer size from 500 to 1500 bytes. +07-29-2003 2.00.26 BED FIX Fixed calls in sb_server.c where integers (#2) were being returned instead of SBBool. +07-24-2003 2.00.25 DES RELEASE Releasing to developer site. +07-18-2003 2.00.25 BED FEATURE Added CodeWarrior (PS2) sample project file. + BED CLEANUP General cleanup to remove CodeWarrior warnings. +07-17-2003 2.00.24 DES CLEANUP Cleaned up the PS2 Makefile, it now uses Makefile.commmon. +07-16-2003 2.00.23 DES FIX Changed a couple of __mips64 checks to _PS2 checks. + BED FEATURE Added ProDG sample project files. +07-09-2003 2.00.22 BED CLEANUP Made MFC sample's server list not continually redraw while sorting. +06-11-2003 2.00.21 DES RELEASE Releasing to developer site. +05-11-2003 2.00.21 DDW CLEANUP Modularized SBServer type handling +05-11-2003 2.00.20 DDW CLEANUP Modularized server parsing and addition +05-09-2003 2.00.19 DES CLEANUP Removed Dreamcast support. +05-09-2003 2.00.18 DDW CLEANUP Modularized refstrings and serverlist functions +05-07-2003 2.00.17 DES RELEASE Releasing to developer site. +04-07-2003 2.00.17 DES FIX Fixed semicolon after if() in SBServerListFindServer. +03-24-2003 2.00.16 DDW FEATURE Added ServerBrowserLimitUpdate function to limit the number of results returned +03-03-2003 2.00.15 DES CLEANUP General cleanup to remove warnings. +02-28-2003 2.00.14 DES RELEASE Releasing to developer site with updated QR2. +02-05-2003 2.00.14 DES RELEASE Releasing to developer site. +02-05-2003 2.00.14 DES FIX Made sure servers in MFC sample show correct info after an sbc_serverupdated. + CLEANUP Switched select calls to CanReceiveOnSocket and CanSendOnSocket. +02-04-2003 2.00.13 DES RELEASE Relasing to developer site. +02-04-2003 2.00.13 DES CLEANUP Removed assert.h include from querytest.c + FIX Use GOA/QR2 checkbox in the MFC sample. + OTHER Show servers in the MFC sample as soon as they are added (sbc_serveradded). +01-23-2003 2.00.12 DES FIX Replaced a call to free with gsifree. +12-19-2002 2.00.11 DES RELEASE Releasing to developer site. +12-19-2002 2.00.11 DES CLEANUP Removed assert.h includes. +12-13-2002 2.00.10 DES FEATURE Added PS2 eenet stack support. +12-06-2002 2.00.09 DES RELEASE Releasing to developer site with updated QR2. +12-03-2002 2.00.09 DES RELEASE Releasing to developer site. +12-03-2002 2.00.09 DES FEATURE Added a Linux Makefile. +11-22-2002 2.00.08 DES RELEASE Releasing to developer site. +11-20-2002 2.00.08 DES FEATURE Added support for compiling on the PS2. +11-15-2002 2.00.07 DDW FEATURE Abstracted the server->next pointer into SBServer.c +11-07-2002 2.00.06 DDW FEATURE Added "deadlist" support so that servers are not freed on the same pass they are removed +11-07-2002 2.00.06 DDW FIX Fixed AV when disconnecting during mainlist with data pending +10-22-2002 2.00.05 DDW FIX Correctly removes server from query engine when removed message arrives +09-26-2002 2.00.04 DES RELEASE Limited release on developer site +09-26-2002 2.00.04 DDW FIX MFC sample checks whether server has full-rules before requerying +09-26-2002 2.00.03 DES FEATURE Added MFC sample (sbmfcsample) +09-26-2002 2.00.02 DDW OTHER Changelog started +09-26-2002 2.00.02 DES FIX Fixed memory leak when cleaning up server list object +09-24-2002 2.00.01 DDW FIX Fixed protocol parsing error on packet boundary +09-23-2002 2.00.00 DDW RELEASE Release to EAPAC for Generals (Peer) +09-06-2002 2.00.00 DDW RELEASE Release to EAPAC for Generals + diff --git a/code/gamespy/serverbrowsing/sb_ascii.h b/code/gamespy/serverbrowsing/sb_ascii.h new file mode 100644 index 00000000..06d74b7f --- /dev/null +++ b/code/gamespy/serverbrowsing/sb_ascii.h @@ -0,0 +1,167 @@ +/****** +GameSpy Server Browsing SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + + Please see the GameSpy Server Browsing SDK documentation for more + information + +******/ + +// PROTOTYPES FOR ASCII VERSIONS +// This is required to silence CodeWarrior warnings about functions not having a prototype + + +#ifndef _SB_ASCII_H +#define _SB_ASCII_H + +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* +ServerBrowserNew +---------------- +Creates and returns a new (empty) ServerBrowser object. +Returns NULL if an allocation error occurs. + +queryForGamename - The gamename you are querying for +queryFromGamename - The gamename you are querying from - generally the same as queryForGamename +queryFromKey - Secret key that corresponds to the queryFromGamename +queryFromVersion - A game-specific version identifier (pass 0 unless told otherwise) +maxConcUpdates - Max number of concurent updates (10-15 for modem users, 20-30 for high-bandwidth) +queryVersion - Query protocol to use. Use QVERSION_GOA for DeveloperSpec/Query&Reporting1 games, and QVERSION_QR2 for games that use Query & Reporting 2 +callback - The function that will be called with list updates +instance - User-defined instance data (e.g. structure or object pointer) */ +ServerBrowser ServerBrowserNewA(const char *queryForGamename, const char *queryFromGamename, const char *queryFromKey, int queryFromVersion, int maxConcUpdates, int queryVersion, SBBool lanBrowse, ServerBrowserCallback callback, void *instance); + + +/* ServerBrowserUpdate +------------------- +Starts an update by downloading a list of servers from the master server, then querying them. + +sb - The server browser object to update +async - If SBTrue, the update will be initiated, and ServerListThink must be called for processing and querying to occur + If SBFalse, the function will not return until the initial list of servers has been completely updated +disconnectOnComplete - If SBTrue, the connection to the master server will be disconnected immediately after the list is downloaded. + If SBFalse, the connection will be left open for additional data queries, and can be closed via ServerBrowserDisconnect +basicFields - This array of registered QR2 keys is used to determine the fields requested from servers during the initial "basic" update. + Only server keys listed in this array will be returned for servers. +numBasicFields - The number of fields in the basicFields array +serverFilter - SQL Filter string that will be applied on the master server to limit the list of servers returned. + All server keys are available for filtering on the master server, as well as the master-defined "country" and "region" keys. + Standard SQL syntax should be used. + +ServerBrowserLimitUpdate +------------------------ +Identical to ServerBrowserUpdate, except that the number of servers returned can be limited +maxServers - Maximum number of servers to be returned +*/ +SBError ServerBrowserUpdateA(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const char *serverFilter); +SBError ServerBrowserLimitUpdateA(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const char *serverFilter, int maxServers); + + +/* ServerBrowserAuxUpdateIP +------------------- +Manually updates a server given an IP address and query port. Use to manually add servers to the list when you just +have an IP and port for them. + +sb - The server browser object to add the server to +ip - The dotted IP address of the server e.g. "1.2.3.4" +port - The query port of the server +viaMaster - If SBTrue, information about the server will be retrieved from the master server instead of attempting to query the server directly. + If a connection to the master server does not exist, it will be made to kept open afterwards. + If SBFalse, the server will be contacted directly for information. +async - If SBTrue, the update will be initiated, and ServerListThink must be called for processing and querying to occur + If SBFalse, the function will not return until the server has been successfully or unsuccessfully updated +fullUpdate - If SBTrue, all server keys/rules/player/team information will be retrieved + If SBFalse, only the keys specified in the basicFields array of the ServerBrowserUpdate function will be retrieved */ +SBError ServerBrowserAuxUpdateIPA(ServerBrowser sb, const char *ip, unsigned short port, SBBool viaMaster, SBBool async, SBBool fullUpdate); + +/* ServerBrowserRemoveIP +------------------- +Removes a server from the list given an IP and query port */ +void ServerBrowserRemoveIPA(ServerBrowser sb, const char *ip, unsigned short port); + +/* ServerBrowserErrorDesc +------------------- +Returns a human-readable error string for the given error code. */ +const char *ServerBrowserErrorDescA(ServerBrowser sb, SBError error); + +/* ServerBrowserListQueryError +------------------- +When a list query error occurs, as indicated by the sbc_queryerror callback, this function allows you to +obtain the human-readable error string for the error (generally these errors are caused by errors in the +filter string) */ +const char *ServerBrowserListQueryErrorA(ServerBrowser sb); + + +/* ServerBrowserSendNatNegotiateCookieToServer +------------------ +Sends a cookie value to the server for use with NAT Negotiation */ +SBError ServerBrowserSendNatNegotiateCookieToServerA(ServerBrowser sb, const char *ip, unsigned short port, int cookie); + + +/* ServerBrowserSendMessageToServer +------------------ +Sends a game-specific message to a server */ +SBError ServerBrowserSendMessageToServerA(ServerBrowser sb, const char *ip, unsigned short port, const char *data, int len); + + +/* ServerBrowserSort +----------------- +Sort the server list in either ascending or descending order using the +specified comparemode. +sortkey can be a normal server key, or "ping" or "hostaddr" */ +void ServerBrowserSortA(ServerBrowser sb, SBBool ascending, const char *sortkey, SBCompareMode comparemode); + +/******************* +SBServer Object Functions +********************/ + + +/* SBServerGetPublicAddress/SBServerGetPrivateAddress +------------------- +Returns the string, dotted IP address for the specified server +The "private" version is only valid when the server has a private address available */ +char *SBServerGetPublicAddress(SBServer server); +char *SBServerGetPrivateAddress(SBServer server); + + +/* SBServerGet[]Value +------------------ +Returns the value for the specified key. If the key does not exist for the +given server, the default value is returned */ +const char *SBServerGetStringValueA(SBServer server, const char *keyname, const char *def); +int SBServerGetIntValueA(SBServer server, const char *key, int idefault); +double SBServerGetFloatValueA(SBServer server, const char *key, double fdefault); +SBBool SBServerGetBoolValueA(SBServer server, const char *key, SBBool bdefault); + + +/* SBServerGetPlayer[]Value / SBServerGetTeam[]Value +------------------ +Returns the value for the specified key on the specified player or team. If the key does not exist for the +given server, the default value is returned +Player keys take the form keyname_N where N is the player index, and team keys take the form +keyname_tN where N is the team index. You should only specify the keyname for the key in the below functions. +*/ +const char *SBServerGetPlayerStringValueA(SBServer server, int playernum, const char *key, const char *sdefault); +int SBServerGetPlayerIntValueA(SBServer server, int playernum, const char *key, int idefault); +double SBServerGetPlayerFloatValueA(SBServer server, int playernum, const char *key, double fdefault); + +const char *SBServerGetTeamStringValueA(SBServer server, int teamnum, const char *key, const char *sdefault); +int SBServerGetTeamIntValueA(SBServer server, int teamnum, const char *key, int idefault); +double SBServerGetTeamFloatValueA(SBServer server, int teamnum, const char *key, double fdefault); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/serverbrowsing/sb_crypt.c b/code/gamespy/serverbrowsing/sb_crypt.c new file mode 100644 index 00000000..85b5fd56 --- /dev/null +++ b/code/gamespy/serverbrowsing/sb_crypt.c @@ -0,0 +1,212 @@ +#include +#include +#include "sb_crypt.h" + + +static unsigned char keyrand(GOACryptState *state, int limit, + unsigned char *user_key, + unsigned char keysize, + unsigned char *rsum, + unsigned *keypos) +{ + unsigned int u, // Value from 0 to limit to return. + retry_limiter, // No infinite loops allowed. + mask; // Select just enough bits. + + if (!limit) return 0; // Avoid divide by zero error. + retry_limiter = 0; + mask = 1; // Fill mask with enough bits to cover + while (mask < (unsigned)limit) // the desired range. + mask = (mask << 1) + 1; + do + { + *rsum = (unsigned char)(state->cards[*rsum] + user_key[(*keypos)++]); + if (*keypos >= keysize) + { + *keypos = 0; // Recycle the user key. + *rsum = (unsigned char)(*rsum + keysize); // key "aaaa" != key "aaaaaaaa" + } + u = mask & *rsum; + if (++retry_limiter > 11) + u %= (unsigned int)limit; // Prevent very rare long loops. + } + while (u > (unsigned)limit); + return (unsigned char)(u); +} + + +void GOAHashInit(GOACryptState *state) +{ + // This function is used to initialize non-keyed hash + // computation. + + int i, j; + + // Initialize the indices and data dependencies. + + state->rotor = 1; + state->ratchet = 3; + state->avalanche = 5; + state->last_plain = 7; + state->last_cipher = 11; + + // Start with state->cards all in inverse order. + + for (i=0, j=255;i<256;i++,j--) + state->cards[i] = (unsigned char) j; +} + + +void GOACryptInit(GOACryptState *state, unsigned char *key, unsigned char keysize) +{ + // Key size may be up to 256 bytes. + // Pass phrases may be used directly, with longer length + // compensating for the low entropy expected in such keys. + // Alternatively, shorter keys hashed from a pass phrase or + // generated randomly may be used. For random keys, lengths + // of from 4 to 16 bytes are recommended, depending on how + // secure you want this to be. + + int i; + unsigned char toswap, swaptemp, rsum; + unsigned keypos; + + // If we have been given no key, assume the default hash setup. + + if (keysize < 1) + { + GOAHashInit(state); + return; + } + + // Start with state->cards all in order, one of each. + + for (i=0;i<256;i++) + state->cards[i] = (unsigned char)(i); + + // Swap the card at each position with some other card. + + toswap = 0; + keypos = 0; // Start with first byte of user key. + rsum = 0; + for (i=255;i>=0;i--) + { + toswap = keyrand(state, i, key, keysize, &rsum, &keypos); + swaptemp = state->cards[i]; + state->cards[i] = state->cards[toswap]; + state->cards[toswap] = swaptemp; + } + + // Initialize the indices and data dependencies. + // Indices are set to different values instead of all 0 + // to reduce what is known about the state of the state->cards + // when the first byte is emitted. + + state->rotor = state->cards[1]; + state->ratchet = state->cards[3]; + state->avalanche = state->cards[5]; + state->last_plain = state->cards[7]; + state->last_cipher = state->cards[rsum]; + + toswap = swaptemp = rsum = 0; + keypos = 0; +} + + +unsigned char GOAEncryptByte(GOACryptState *state, unsigned char b) +{ + // Picture a single enigma state->rotor with 256 positions, rewired + // on the fly by card-shuffling. + + // This cipher is a variant of one invented and written + // by Michael Paul Johnson in November, 1993. + + unsigned char swaptemp; + + // Shuffle the deck a little more. + + state->ratchet = (unsigned char)(state->ratchet + state->cards[state->rotor++]); + swaptemp = state->cards[state->last_cipher]; + state->cards[state->last_cipher] = state->cards[state->ratchet]; + state->cards[state->ratchet] = state->cards[state->last_plain]; + state->cards[state->last_plain] = state->cards[state->rotor]; + state->cards[state->rotor] = swaptemp; + state->avalanche = (unsigned char)(state->avalanche + state->cards[swaptemp]); + + // Output one byte from the state in such a way as to make it + // very hard to figure out which one you are looking at. + /* + state->last_cipher = b^state->cards[(state->cards[state->ratchet] + state->cards[state->rotor]) & 0xFF] ^ + state->cards[state->cards[(state->cards[state->last_plain] + + state->cards[state->last_cipher] + + state->cards[state->avalanche])&0xFF]]; + */ + state->last_cipher = (unsigned char)(b^state->cards[(state->cards[state->avalanche] + state->cards[state->rotor]) & 0xFF] ^ + state->cards[state->cards[(state->cards[state->last_plain] + + state->cards[state->last_cipher] + + state->cards[state->ratchet])&0xFF]]); + state->last_plain = b; + return state->last_cipher; +} + + +void GOAEncrypt(GOACryptState *state, unsigned char *bp, int len) +{ + int i; + for (i = 0 ; i < len ; i++) + { + bp[i] = GOAEncryptByte(state, bp[i]); + } +} + +unsigned char GOADecryptByte(GOACryptState *state, unsigned char b) +{ + unsigned char swaptemp; + + // Shuffle the deck a little more. + + state->ratchet = (unsigned char)(state->ratchet + state->cards[state->rotor++]); + swaptemp = state->cards[state->last_cipher]; + state->cards[state->last_cipher] = state->cards[state->ratchet]; + state->cards[state->ratchet] = state->cards[state->last_plain]; + state->cards[state->last_plain] = state->cards[state->rotor]; + state->cards[state->rotor] = swaptemp; + state->avalanche = (unsigned char)(state->avalanche + state->cards[swaptemp]); + + // Output one byte from the state in such a way as to make it + // very hard to figure out which one you are looking at. + /* + state->last_plain = b^state->cards[(state->cards[state->ratchet] + state->cards[state->rotor]) & 0xFF] ^ + state->cards[state->cards[(state->cards[state->last_plain] + + state->cards[state->last_cipher] + + state->cards[state->avalanche])&0xFF]]; + */ + //crt - change this around + state->last_plain = (unsigned char)(b^state->cards[(state->cards[state->avalanche] + state->cards[state->rotor]) & 0xFF] ^ + state->cards[state->cards[(state->cards[state->last_plain] + + state->cards[state->last_cipher] + + state->cards[state->ratchet])&0xFF]]); + state->last_cipher = b; + return state->last_plain; +} + +void GOADecrypt(GOACryptState *state, unsigned char *bp, int len) +{ + int i; + for (i = 0 ; i < len ; i++) + { + bp[i] = GOADecryptByte(state, bp[i]); + } +} + +void GOAHashFinal(GOACryptState *state, unsigned char *outputhash, // Destination + unsigned char hashlength) // Size of hash. +{ + int i; + + for (i=255;i>=0;i--) + GOAEncryptByte(state, (unsigned char) i); + for (i=0;istate +#define STATE_BASICKEYS (1 << 0) +#define STATE_FULLKEYS (1 << 1) +#define STATE_PENDINGBASICQUERY (1 << 2) +#define STATE_PENDINGFULLQUERY (1 << 3) +#define STATE_QUERYFAILED (1 << 4) +#define STATE_PENDINGICMPQUERY (1 << 5) +#define STATE_VALIDPING (1 << 6) +#define STATE_PENDINGQUERYCHALLENGE (1 << 7) + +//how long before a server query times out +#define MAX_QUERY_MSEC 2500 + +//game server flags +#define UNSOLICITED_UDP_FLAG 1 +#define PRIVATE_IP_FLAG 2 +#define CONNECT_NEGOTIATE_FLAG 4 +#define ICMP_IP_FLAG 8 +#define NONSTANDARD_PORT_FLAG 16 +#define NONSTANDARD_PRIVATE_PORT_FLAG 32 +#define HAS_KEYS_FLAG 64 +#define HAS_FULL_RULES_FLAG 128 + +//backend query flags (set in hbmaster, don't change) +#define QR2_USE_QUERY_CHALLENGE 128 + +//key types for the key type list +#define KEYTYPE_STRING 0 +#define KEYTYPE_BYTE 1 +#define KEYTYPE_SHORT 2 + +//how long to make the outgoing challenge +#define LIST_CHALLENGE_LEN 8 + +//protocol versions +#define LIST_PROTOCOL_VERSION 1 +#define LIST_ENCODING_VERSION 3 + +//message types for outgoing requests +#define SERVER_LIST_REQUEST 0 +#define SERVER_INFO_REQUEST 1 +#define SEND_MESSAGE_REQUEST 2 +#define KEEPALIVE_REPLY 3 +#define MAPLOOP_REQUEST 4 +#define PLAYERSEARCH_REQUEST 5 + +//message types for incoming requests +#define PUSH_KEYS_MESSAGE 1 +#define PUSH_SERVER_MESSAGE 2 +#define KEEPALIVE_MESSAGE 3 +#define DELETE_SERVER_MESSAGE 4 +#define MAPLOOP_MESSAGE 5 +#define PLAYERSEARCH_MESSAGE 6 + +//server list update options +#define SEND_FIELDS_FOR_ALL 1 +#define NO_SERVER_LIST 2 +#define PUSH_UPDATES 4 +#define SEND_GROUPS 32 +#define NO_LIST_CACHE 64 +#define LIMIT_RESULT_COUNT 128 + +//player search options +#define SEARCH_ALL_GAMES 1 +#define SEARCH_LEFT_SUBSTRING 2 +#define SEARCH_RIGHT_SUBSTRING 4 +#define SEARCH_ANY_SUBSTRING 8 + +//max number of keys for the basic key list +#define MAX_QUERY_KEYS 20 + +//how long to search on the LAN +#define SL_LAN_SEARCH_TIME 2000 + +//MAGIC bytes for the QR2 queries +#define QR2_MAGIC_1 0xFE +#define QR2_MAGIC_2 0xFD + +//magic bytes for nat negotiation message +#define NATNEG_MAGIC_LEN 6 +#define NN_MAGIC_0 0xFD +#define NN_MAGIC_1 0xFC +#define NN_MAGIC_2 0x1E +#define NN_MAGIC_3 0x66 +#define NN_MAGIC_4 0x6A +#define NN_MAGIC_5 0xB2 + + +//query types +#define QTYPE_BASIC 0 +#define QTYPE_FULL 1 +#define QTYPE_ICMP 2 + +//query strings for old-style servers +#define BASIC_GOA_QUERY "\\basic\\\\info\\" +#define BASIC_GOA_QUERY_LEN 13 +#define FULL_GOA_QUERY "\\status\\" +#define FULL_GOA_QUERY_LEN 8 + +//maximum length of a sortkey string +#define SORTKEY_LENGTH 255 + +//include ICMP support by default +#ifndef SB_NO_ICMP_SUPPORT + #undef SB_ICMP_SUPPORT + #define SB_ICMP_SUPPORT +#endif + +//a key/value pair +typedef struct _SBKeyValuePair +{ + const char *key; + const char *value; +} SBKeyValuePair; + + +//a ref-counted string +typedef struct _SBRefString +{ + const char *str; +#ifdef GSI_UNICODE + const unsigned short *str_W; +#endif + int refcount; +} SBRefString; + + +typedef struct _SBServerList *SBServerListPtr; +typedef struct _SBQueryEngine *SBQueryEnginePtr; + +//callback types for server lists +typedef enum {slc_serveradded, slc_serverupdated, slc_serverdeleted, slc_initiallistcomplete, slc_disconnected, slc_queryerror, slc_publicipdetermined, slc_serverchallengereceived} SBListCallbackReason; +//callback types for query engine +typedef enum {qe_updatesuccess, qe_updatefailed, qe_engineidle, qe_challengereceived} SBQueryEngineCallbackReason; + +//callback function prototypes +typedef void (*SBListCallBackFn)(SBServerListPtr serverlist, SBListCallbackReason reason, SBServer server, void *instance); +typedef void (*SBEngineCallbackFn)(SBQueryEnginePtr engine, SBQueryEngineCallbackReason reason, SBServer server, void *instance); +typedef void (*SBMaploopCallbackFn)(SBServerListPtr serverlist, SBServer server, time_t mapChangeTime, int numMaps, char *mapList[], void *instance); +typedef void (*SBPlayerSearchCallbackFn)(SBServerListPtr serverlist, char *nick, goa_uint32 serverIP, unsigned short serverPort, time_t lastSeenTime, char *gamename, void *instance); + +//key information structure +typedef struct _KeyInfo +{ + const char *keyName; + int keyType; +} KeyInfo; + + +typedef struct _SBServerList SBServerList; + +#ifdef VENGINE_SUPPORT + #define FTABLE_TYPES + #include "../../VEngine/ve_gm3ftable.h" +#endif + + +//keeps track of previous and current sorting information +typedef struct _SortInfo +{ + gsi_char sortkey[SORTKEY_LENGTH]; + SBCompareMode comparemode; +} SortInfo; + +struct _SBServerList +{ + SBServerListState state; + DArray servers; + DArray keylist; + char queryforgamename[36]; + char queryfromgamename[36]; + char queryfromkey[32]; + char mychallenge[LIST_CHALLENGE_LEN]; + char *inbuffer; + int inbufferlen; + const char *popularvalues[MAX_POPULAR_VALUES]; + int numpopularvalues; + int expectedelements; + + SBListCallBackFn ListCallback; + SBMaploopCallbackFn MaploopCallback; + SBPlayerSearchCallbackFn PlayerSearchCallback; + void *instance; + + SortInfo currsortinfo; + SortInfo prevsortinfo; + + SBBool sortascending; + goa_uint32 mypublicip; + goa_uint32 srcip; + unsigned short defaultport; + + char *lasterror; +#ifdef GSI_UNICODE + unsigned short *lasterror_W; +#endif + + SOCKET slsocket; + gsi_time lanstarttime; + int fromgamever; + GOACryptState cryptkey; + int queryoptions; + SBListParseState pstate; + gsi_u16 backendgameflags; + + const char* mLanAdapterOverride; + + SBServer deadlist; + +#ifdef VENGINE_SUPPORT + #define FTABLE_IMPLEMENT + #include "../../VEngine/ve_gm3ftable.h" +#endif + +}; + + +//server object + +#ifndef SB_SERVER_DECLARED +#define SB_SERVER_DECLARED + +struct _SBServer +{ + goa_uint32 publicip; + unsigned short publicport; + goa_uint32 privateip; + unsigned short privateport; + goa_uint32 icmpip; + unsigned char state; + unsigned char flags; + HashTable keyvals; + gsi_time updatetime; + gsi_u32 querychallenge; + struct _SBServer *next; + gsi_u8 splitResponseBitmap; +}; + +#endif + +typedef struct _SBServerFIFO +{ + SBServer first; + SBServer last; + int count; +} SBServerFIFO; + +typedef struct _SBQueryEngine +{ + int queryversion; + int maxupdates; + SBServerFIFO querylist; + SBServerFIFO pendinglist; + SOCKET querysock; + #if !defined(SN_SYSTEMS) + SOCKET icmpsock; + #endif + goa_uint32 mypublicip; + unsigned char serverkeys[MAX_QUERY_KEYS]; + int numserverkeys; + SBEngineCallbackFn ListCallback; + void *instance; +} SBQueryEngine; + + +struct _ServerBrowser +{ + SBQueryEngine engine; + SBServerList list; + SBBool disconnectFlag; + SBBool dontUpdate; + goa_uint32 triggerIP; + unsigned short triggerPort; + ServerBrowserCallback BrowserCallback; + SBConnectToServerCallback ConnectCallback; + void *instance; +}; + +#define SB_ICMP_ECHO 8 +#define SB_ICMP_ECHO_REPLY 0 + +typedef struct _IPHeader { + gsi_u8 ip_hl_ver; + gsi_u8 ip_tos; + gsi_i16 ip_len; + gsi_u16 ip_id; + gsi_i16 ip_off; + gsi_u8 ip_ttl; + gsi_u8 ip_p; + gsi_u16 ip_sum; + struct in_addr ip_src,ip_dst; +} SBIPHeader; + + + +typedef struct _ICMPHeader +{ + gsi_u8 type; + gsi_u8 code; + gsi_u16 cksum; + union { + struct { + gsi_u16 id; + gsi_u16 sequence; + } echo; + gsi_u32 idseq; + gsi_u16 gateway; + struct { + gsi_u16 __notused; + gsi_u16 mtu; + } frag; + } un; +} SBICMPHeader; + + +//server list functions +void SBServerListInit(SBServerList *slist, const char *queryForGamename, const char *queryFromGamename, const char *queryFromKey, int queryFromVersion, SBBool lanBrowse, SBListCallBackFn callback, void *instance); +SBError SBServerListConnectAndQuery(SBServerList *slist, const char *fieldList, const char *serverFilter, int options, int maxServers); +SBError SBServerListGetLANList(SBServerList *slist, unsigned short startport, unsigned short endport, int queryversion); +void SBServerListDisconnect(SBServerList *slist); +void SBServerListCleanup(SBServerList *slist); +void SBServerListClear(SBServerList *slist); +SBError SBGetServerRulesFromMaster(SBServerList *slist, goa_uint32 ip, unsigned short port); +SBError SBSendMessageToServer(SBServerList *slist, goa_uint32 ip, unsigned short port, const char *data, int len); +SBError SBSendNatNegotiateCookieToServer(SBServerList *slist, goa_uint32 ip, unsigned short port, int cookie); +SBError SBSendMaploopRequest(SBServerList *slist, SBServer server, SBMaploopCallbackFn callback); +SBError SBSendPlayerSearchRequest(SBServerList *slist, char *searchName, int searchOptions, int maxResults, SBPlayerSearchCallbackFn callback); +int SBServerListFindServerByIP(SBServerList *slist, goa_uint32 ip, unsigned short port); +int SBServerListFindServer(SBServerList *slist, SBServer findserver); +void SBServerListRemoveAt(SBServerList *slist, int index); +void SBServerListAppendServer(SBServerList *slist, SBServer server); +void SBServerListSort(SBServerList *slist, SBBool ascending, SortInfo sortinfo); +int SBServerListCount(SBServerList *slist); +SBServer SBServerListNth(SBServerList *slist, int i); +SBError SBListThink(SBServerList *slist); +const char *SBLastListErrorA(SBServerList *slist); +const unsigned short *SBLastListErrorW(SBServerList *slist); +void SBSetLastListErrorPtr(SBServerList *slist, char* theError); +void SBFreeDeadList(SBServerList *slist); +void SBAllocateServerList(SBServerList *slist); + +//sbserver functions +SBServer SBAllocServer(SBServerList *slist, goa_uint32 publicip, unsigned short publicport); +void SBServerFree(void *elem); +void SBServerAddKeyValue(SBServer server, const char *keyname, const char *value); +void SBServerAddIntKeyValue(SBServer server, const char *keyname, int value); +void SBServerParseKeyVals(SBServer server, char *keyvals); +void SBServerParseQR2FullKeysSingle(SBServer server, char *data, int len); +void SBServerParseQR2FullKeysSplit(SBServer server, char *data, int len); +void SBServerSetFlags(SBServer server, unsigned char flags); +void SBServerSetPublicAddr(SBServer server, goa_uint32 ip, unsigned short port); +void SBServerSetPrivateAddr(SBServer server, goa_uint32 ip, unsigned short port); +void SBServerSetICMPIP(SBServer server, goa_uint32 icmpip); +void SBServerSetState(SBServer server, unsigned char state); +void SBServerSetNext(SBServer server, SBServer next); +SBServer SBServerGetNext(SBServer server); +unsigned char SBServerGetState(SBServer server); +unsigned char SBServerGetFlags(SBServer server); +unsigned short SBServerGetPublicQueryPortNBO(SBServer server); +int SBIsNullServer(SBServer server); +extern SBServer SBNullServer; + + +//ref-str functions +const char *SBRefStr(SBServerList *slist,const char *str); +void SBReleaseStr(SBServerList *slist,const char *str); +HashTable SBRefStrHash(SBServerList *slist); +void SBRefStrHashCleanup(SBServerList *slist); + +extern char *SBOverrideMasterServer; + + +//query engine functions +void SBQueryEngineInit(SBQueryEngine *engine, int maxupdates, int queryversion, SBBool lanBrowse, SBEngineCallbackFn callback, void *instance); +void SBQueryEngineUpdateServer(SBQueryEngine *engine, SBServer server, int addfront, int querytype, SBBool usequerychallenge); +void SBQueryEngineSetPublicIP(SBQueryEngine *engine, goa_uint32 mypublicip); +SBServer SBQueryEngineUpdateServerByIP(SBQueryEngine *engine, const char *ip, unsigned short queryport, int addfront, int querytype, SBBool usequerychallenge); +void SBQueryEngineThink(SBQueryEngine *engine); +void SBQueryEngineAddQueryKey(SBQueryEngine *engine, unsigned char keyid); +void SBEngineCleanup(SBQueryEngine *engine); +void SBQueryEngineRemoveServerFromFIFOs(SBQueryEngine *engine, SBServer server); +int NTSLengthSB(char *buf, int len); +void SBEngineHaltUpdates(SBQueryEngine *engine); + + +//server browser internal function +SBError ServerBrowserBeginUpdate2(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const char *serverFilter, int updateOptions, int maxServers); + + +// Ascii versions of functions that should still be available in GSI_UNICODE mode +#ifdef GSI_UNICODE +const char *SBServerGetStringValueA(SBServer server, const char *keyname, const char *def); // for peer SDK +int SBServerGetIntValueA(SBServer server, const char *key, int idefault); +#endif + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/serverbrowsing/sb_queryengine.c b/code/gamespy/serverbrowsing/sb_queryengine.c new file mode 100644 index 00000000..ae6015c6 --- /dev/null +++ b/code/gamespy/serverbrowsing/sb_queryengine.c @@ -0,0 +1,700 @@ +#include "sb_serverbrowsing.h" +#include "sb_internal.h" + +#ifdef GSI_MANIC_DEBUG +// Make sure the server isn't already in the fifo +void FIFODebugCheckAdd(SBServerFIFO *fifo, SBServer server) +{ + SBServer aServer = fifo->first; + while(aServer != NULL) + { + assert(aServer != server); + aServer = aServer->next; + } +} + + +// Verify the contents of the fifo +void FIFODebugCheck(SBServerFIFO *fifo) +{ + int i=0; + SBServer aServer; + + assert(fifo != NULL); + aServer = fifo->first; + for (i=0; i < fifo->count; i++) + { + assert(aServer != NULL); + aServer = aServer->next; + } +} +#else +#define FIFODebugCheckAdd(a,b) +#define FIFODebugCheck(a) +#endif + +//FIFO Queue management functions +static void FIFOAddRear(SBServerFIFO *fifo, SBServer server) +{ + FIFODebugCheckAdd(fifo, server); + + if (fifo->last != NULL) + fifo->last->next = server; + fifo->last = server; + server->next = NULL; + if (fifo->first == NULL) + fifo->first = server; + fifo->count++; + + FIFODebugCheck(fifo); +} + +static void FIFOAddFront(SBServerFIFO *fifo, SBServer server) +{ + FIFODebugCheckAdd(fifo, server); + + server->next = fifo->first; + fifo->first = server; + if (fifo->last == NULL) + fifo->last = server; + fifo->count++; + + FIFODebugCheck(fifo); +} + +static SBServer FIFOGetFirst(SBServerFIFO *fifo) +{ + SBServer hold; + hold = fifo->first; + if (hold != NULL) + { + fifo->first = hold->next; + if (fifo->first == NULL) + fifo->last = NULL; + fifo->count--; + } + + FIFODebugCheck(fifo); + return hold; +} + +static SBBool FIFORemove(SBServerFIFO *fifo, SBServer server) +{ + SBServer hold, prev; + prev = NULL; + hold = fifo->first; + while (hold != NULL) + { + if (hold == server) //found + { + if (prev != NULL) //there is a previous.. + prev->next = hold->next; + if (fifo->first == hold) + fifo->first = hold->next; + if (fifo->last == hold) + fifo->last = prev; + fifo->count--; + // assert((fifo->count == 0 && fifo->first == NULL && fifo->last == NULL) || fifo->count > 0); + return SBTrue; + } + prev = hold; + hold = hold->next; + } + + FIFODebugCheck(fifo); + return SBFalse; +} + +static void FIFOClear(SBServerFIFO *fifo) +{ + fifo->first = fifo->last = NULL; + fifo->count = 0; + + FIFODebugCheck(fifo); +} + +#ifdef SB_ICMP_SUPPORT +static unsigned short IPChecksum(const unsigned short *buf, int len) +{ + unsigned long cksum = 0; + + //Calculate the checksum + while (len > 1) + { + cksum += *buf++; + len -= sizeof(unsigned short); + } + + //If we have one char left + if (len) { + cksum += *(unsigned char*)buf; + } + + //Complete the calculations + cksum = (cksum >> 16) + (cksum & 0xffff); + cksum += (cksum >> 16); + + //Return the value (inversed) + return (unsigned short)(~cksum); +} +#endif + +static void QEStartQuery(SBQueryEngine *engine, SBServer server) +{ + unsigned char queryBuffer[256]; + int queryLen; + gsi_bool querySuccess = gsi_false; + struct sockaddr_in saddr; + + saddr.sin_family = AF_INET; + server->updatetime = current_time(); + + if (server->state & STATE_PENDINGICMPQUERY) //send an ICMP ping request + { +#ifdef SB_ICMP_SUPPORT + #if !defined(SN_SYSTEMS) + SBICMPHeader *_icmp = (SBICMPHeader *)(queryBuffer); + //todo: alignment issues on PS2 + _icmp->type = SB_ICMP_ECHO; + _icmp->code = 0; + _icmp->un.idseq = server->updatetime; //no need for network byte order since only we read the reply + _icmp->cksum = 0; + queryLen = sizeof(SBICMPHeader) + 6; + memcpy(queryBuffer + sizeof(SBICMPHeader), &server->publicip, 4); //put some data in the echo packet that we can use to verify the reply + memcpy(queryBuffer + sizeof(SBICMPHeader) + 4, &server->publicport, 2); + _icmp->cksum = IPChecksum((unsigned short *)queryBuffer, queryLen); + if (SBServerGetFlags(server) & ICMP_IP_FLAG) //there is a special ICMP address + { + saddr.sin_addr.s_addr = server->icmpip; + } else + { + saddr.sin_addr.s_addr = server->publicip; + } + + sendto(engine->icmpsock, (char *)queryBuffer, queryLen, 0, (struct sockaddr *)&saddr, sizeof(saddr)); + querySuccess = gsi_true; + #else + int result; + sndev_set_ping_ip_type optval; + optval.ip_addr = server->icmpip; + + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Attempting to send ICMP ping to %s\r\n", inet_ntoa(*((struct in_addr*)&server->icmpip))); + + result = sndev_set_options(0, SN_DEV_SET_PING_IP, (void*)&optval, sizeof(optval)); // tell SN to ping this addr + if (result==SN_PING_FAIL) + { + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError, + "ICMP ping attempt failed on SNSystems (unknown error)\r\n"); + querySuccess = gsi_true; // let the SDK process failures as a timed out ping + } + else if (result==SN_PING_FULL) + { + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError, + "ICMP ping attempt failed on SNSystems (SN_PING_FULL)\r\n"); + querySuccess = gsi_false; + } + else + querySuccess = gsi_true; + #endif +#endif + } else //send a UDP query + { + if (engine->queryversion == QVERSION_QR2) + { + if (server->state & STATE_PENDINGQUERYCHALLENGE) + { + // send IP verify request now, but send qr2 query later when IP verify returns + int pos = 0; + queryBuffer[pos++] = QR2_MAGIC_1; + queryBuffer[pos++] = QR2_MAGIC_2; + queryBuffer[pos++] = 0x09; // ip verify prequery + //set the request key + memcpy(&queryBuffer[pos], &server->updatetime, 4); + pos += 4; + queryLen = pos; + } + else + { + gsi_u32 challengeNBO = htonl(server->querychallenge); + int pos = 0; + + //set the header + queryBuffer[pos++] = QR2_MAGIC_1; + queryBuffer[pos++] = QR2_MAGIC_2; + queryBuffer[pos++] = 0; + memcpy(&queryBuffer[pos], &server->updatetime, 4); //set the request key + pos += 4; + if (challengeNBO != 0) + { + memcpy(&queryBuffer[pos], &challengeNBO, 4); // set the challenge + pos += 4; + } + if (server->state & STATE_PENDINGBASICQUERY) + { + int i; + queryBuffer[pos++] = (unsigned char)engine->numserverkeys; + for (i = 0 ; i < engine->numserverkeys ; i++) + queryBuffer[pos++] = engine->serverkeys[i]; + + //don't request any player or team keys + queryBuffer[pos++] = 0x00; + queryBuffer[pos++] = 0x00; + queryLen = pos; + + } else //request all keys for everyone + { + queryBuffer[pos++] = 0xFF; + queryBuffer[pos++] = 0xFF; + queryBuffer[pos++] = 0xFF; + + // Tell the server we support split packets + queryBuffer[pos++] = 0x1; + queryLen = pos; // 11 + } + } + } else //GOA + { + if (server->state & STATE_PENDINGBASICQUERY) //original - do a \basic\info\ query + { + memcpy(queryBuffer, BASIC_GOA_QUERY, BASIC_GOA_QUERY_LEN); + queryLen = BASIC_GOA_QUERY_LEN; + } else //original - do a \status\ query + { + memcpy(queryBuffer, FULL_GOA_QUERY, FULL_GOA_QUERY_LEN); + queryLen = FULL_GOA_QUERY_LEN; + } + } + if (server->publicip == engine->mypublicip && (server->flags & PRIVATE_IP_FLAG)) //try querying the private IP + { + saddr.sin_addr.s_addr = server->privateip; + saddr.sin_port = server->privateport; + } else + { + saddr.sin_addr.s_addr = server->publicip; + saddr.sin_port = server->publicport; + } + sendto(engine->querysock, (char *)queryBuffer, queryLen, 0, (struct sockaddr *)&saddr, sizeof(saddr)); + querySuccess = gsi_true; + } + + //add it to the query list + if (gsi_is_true(querySuccess)) + FIFOAddRear(&engine->querylist, server); + else + server->updatetime = 0; +} + + +void SBQueryEngineInit(SBQueryEngine *engine, int maxupdates, int queryversion, SBBool lanBrowse, SBEngineCallbackFn callback, void *instance) +{ + // 11-03-2004 : Added by Saad Nader + // fix for LANs and unnecessary availability check + /////////////////////////////////////////////////// + if(lanBrowse == SBFalse) + { + if(__GSIACResult != GSIACAvailable) + return; + } + SocketStartUp(); + engine->queryversion = queryversion; + engine->maxupdates = maxupdates; + engine->numserverkeys = 0; + engine->ListCallback = callback; + engine->instance = instance; + engine->mypublicip = 0; + engine->querysock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); +#if defined(SB_ICMP_SUPPORT) + #if defined(SN_SYSTEMS) + { + // reset SNSystems internal ICMP ping structures + sndev_set_ping_reset_type optval; + optval.timeout_ms = MAX_QUERY_MSEC; // this gets rounded up to 3 sec + optval.reserved = 0; + sndev_set_options(0, SN_DEV_SET_PING_RESET, &optval, sizeof(optval)); + } + #else + engine->icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + #endif +#endif + FIFOClear(&engine->pendinglist); + FIFOClear(&engine->querylist); +} + +void SBQueryEngineSetPublicIP(SBQueryEngine *engine, goa_uint32 mypublicip) +{ + engine->mypublicip = mypublicip; +} + +void SBEngineHaltUpdates(SBQueryEngine *engine) +{ + FIFOClear(&engine->pendinglist); + FIFOClear(&engine->querylist); +} + + +void SBEngineCleanup(SBQueryEngine *engine) +{ + closesocket(engine->querysock); +#ifdef SB_ICMP_SUPPORT + #if !defined(SN_SYSTEMS) + closesocket(engine->icmpsock); + #endif +#endif + engine->querysock = INVALID_SOCKET; + FIFOClear(&engine->pendinglist); + FIFOClear(&engine->querylist); +} + + +//NOTE: the server must not be in the pending or update list currently! +void SBQueryEngineUpdateServer(SBQueryEngine *engine, SBServer server, int addfront, int querytype, SBBool usequerychallenge) +{ + // Assert state of FIFOs + FIFODebugCheckAdd(&engine->pendinglist, server); + FIFODebugCheckAdd(&engine->querylist, server); + + server->state &= (unsigned char)~(STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY|STATE_PENDINGICMPQUERY|STATE_PENDINGQUERYCHALLENGE|STATE_QUERYFAILED); //clear out these flags + server->splitResponseBitmap = 0; + server->querychallenge = 0; + +#ifndef SB_ICMP_SUPPORT + if (querytype == QTYPE_ICMP) + return; // ICMP not supported +#endif + + if (querytype == QTYPE_BASIC) + server->state |= STATE_PENDINGBASICQUERY; + else if (querytype == QTYPE_FULL) + server->state |= STATE_PENDINGFULLQUERY; + else if (querytype == QTYPE_ICMP) + server->state |= STATE_PENDINGICMPQUERY; + else + return; // hoterror: unsupported querytype! + + if (usequerychallenge && (querytype == QTYPE_FULL || querytype == QTYPE_BASIC)) + server->state |= STATE_PENDINGQUERYCHALLENGE; + + if (engine->querylist.count < engine->maxupdates) //add it now.. + { + QEStartQuery(engine, server); + return; + } + //else need to queue it + + if (addfront) + FIFOAddFront(&engine->pendinglist, server); + else + FIFOAddRear(&engine->pendinglist, server); +} + +SBServer SBQueryEngineUpdateServerByIP(SBQueryEngine *engine, const char *ip, unsigned short queryport, int addfront, int querytype, SBBool usequerychallenge) +{ + //need to create a new server + SBServer server; + goa_uint32 ipaddr; + ipaddr = inet_addr(ip); + server = SBAllocServer(NULL, ipaddr, htons(queryport)); + server->flags = UNSOLICITED_UDP_FLAG; //we assume we can talk directly to it + SBQueryEngineUpdateServer(engine, server, addfront, querytype, usequerychallenge); + return server; +} + + +static void ParseSingleQR2Reply(SBQueryEngine *engine, SBServer server, char *data, int len) +{ + int i; + int dlen; + + // 0x00 == qr2 query response, 0x09 == qr2 challenge response + if (data[0] != 0x00 && data[0] != 0x09) + return; + + //we could test the request key here for added security, or skip + data += 5; + len -= 5; + if (server->state & STATE_PENDINGQUERYCHALLENGE) + { + server->state &= (unsigned char)~(STATE_PENDINGQUERYCHALLENGE); + + if (len > 0) + { + server->querychallenge = (gsi_u32)atoi(data); + FIFORemove(&engine->querylist, server); // remove it + QEStartQuery(engine, server); // readd it with a keys query + engine->ListCallback(engine, qe_challengereceived, server, engine->instance); + return; + } + } + else if (server->state & STATE_PENDINGBASICQUERY) + { + //need to pick out the keys they selected + for (i = 0 ; i < engine->numserverkeys ; i++) + { + dlen = NTSLengthSB(data, len); + if (dlen < 0) + break; + //add the value if its not a Query-From-Master-Only key + if (!qr2_internal_is_master_only_key(qr2_registered_key_list[engine->serverkeys[i]])) + { + SBServerAddKeyValue(server, qr2_registered_key_list[engine->serverkeys[i]], data); + } + data += dlen; + len -= dlen; + } + server->state |= STATE_BASICKEYS|STATE_VALIDPING; + } + else //need to parse out all the keys + { + // Is this a split packet format? + if (*data && strncmp("splitnum", data, 8)==0) + { + SBServerParseQR2FullKeysSplit(server, data, len); + if (server->splitResponseBitmap != 0xFF) + return; + server->state |= STATE_FULLKEYS|STATE_BASICKEYS|STATE_VALIDPING; + } + else + { + // single packet + SBServerParseQR2FullKeysSingle(server, data, len); + server->state |= STATE_FULLKEYS|STATE_BASICKEYS|STATE_VALIDPING; + } + } + server->state &= (unsigned char)~(STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY); + server->updatetime = current_time() - server->updatetime; + FIFORemove(&engine->querylist, server); + engine->ListCallback(engine, qe_updatesuccess, server, engine->instance); +} + +static void ParseSingleGOAReply(SBQueryEngine *engine, SBServer server, char *data, int len) +{ + int isfinal; + //need to check before parse as it will modify the string + isfinal = (strstr(data,"\\final\\") != NULL); + SBServerParseKeyVals(server, data); + if (isfinal) + { + if (server->state & STATE_PENDINGBASICQUERY) + server->state |= STATE_BASICKEYS|STATE_VALIDPING; + else + server->state |= STATE_FULLKEYS|STATE_VALIDPING; + server->state &= (unsigned char)~(STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY); + server->updatetime = current_time() - server->updatetime; + FIFORemove(&engine->querylist, server); + engine->ListCallback(engine, qe_updatesuccess, server, engine->instance); + } + + GSI_UNUSED(len); +} + +static SBBool ParseSingleICMPReply(SBQueryEngine *engine, SBServer server, char *data, int len) +{ +#ifdef SB_ICMP_SUPPORT + SBIPHeader *ipheader = (SBIPHeader *)data; + SBICMPHeader *icmpheader; + int ipheaderlen; + goa_uint32 packetpublicip; + unsigned short packetpublicport; + //todo: byte alignment on PS2 + ipheaderlen = (gsi_u8)(ipheader->ip_hl_ver & 15); + ipheaderlen *= 4; + icmpheader = (SBICMPHeader *)(data + ipheaderlen); + if (icmpheader->type != SB_ICMP_ECHO_REPLY) + return SBFalse; + if (icmpheader->un.idseq != server->updatetime) + return SBFalse; + if (len < ipheaderlen + (int)sizeof(SBICMPHeader) + 6) + return SBFalse; //not enough data + //check the server IP and port + memcpy(&packetpublicip, data + ipheaderlen + sizeof(SBICMPHeader), 4); + memcpy(&packetpublicport, data + ipheaderlen + sizeof(SBICMPHeader) + 4, 2); + if (packetpublicport != server->publicport || packetpublicip != server->publicip) + return SBFalse; + //else its a valid echo + server->updatetime = current_time() - server->updatetime; + server->state |= STATE_VALIDPING; + server->state &= (unsigned char)~(STATE_PENDINGICMPQUERY); + FIFORemove(&engine->querylist, server); + engine->ListCallback(engine, qe_updatesuccess, server, engine->instance); +#else + GSI_UNUSED(engine); + GSI_UNUSED(server); + GSI_UNUSED(data); + GSI_UNUSED(len); +#endif + return SBTrue; + +} + +#if defined(SN_SYSTEMS) && defined(SB_ICMP_SUPPORT) +static void ProcessIncomingICMPReplies(SBQueryEngine *engine) +{ + SBServer server; + int result = 0; + int found = 0; + int i = 0; + sndev_stat_ping_times_type optval; + gsi_i32 optsize = sizeof(optval); + + // Get the ICMP replies from the SNSystems stack + result = sndev_get_status(0, SN_DEV_STAT_PING_TIMES, (void*)&optval, &optsize); + if (result != 0) + { + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Failed on sndev_get_status (checking ICMP pings): %d\r\n", result); + return; + } + if (optval.num_entries == 0) + return; // no outstanding pings (according to sn_systems) + + // match servers to ping responses + for (server = engine->querylist.first; server != NULL; server = server->next) + { + if ((server->state & STATE_PENDINGICMPQUERY) == 0 || + (server->flags & ICMP_IP_FLAG) == 0) + continue; // server not flagged for ICMP + + // find this server + for (i=0; iicmpip == optval.times[i].ip_addr) + { + if (optval.times[i].status == SN_PING_TIMES_CODE_GOTREPLY) + { + server->updatetime = optval.times[i].time_ms; + server->state |= STATE_VALIDPING; + server->state &= (unsigned char)~(STATE_PENDINGICMPQUERY); + FIFORemove(&engine->querylist, server); + engine->ListCallback(engine, qe_updatesuccess, server, engine->instance); + } + //else + // let query engine timeout queries on its own (for simplicity) + + found++; + if (found == optval.num_entries) + return; // found them all + } + } + } +} +#endif // SN_SYSTEMS && SB_ICMP_SUPPORT + +static void ProcessIncomingReplies(SBQueryEngine *engine, SBBool icmpSocket) +{ + int i; + char indata[MAX_RECVFROM_SIZE]; + struct sockaddr_in saddr; + int saddrlen = sizeof(saddr); + SBServer server; + SOCKET recvSock = 0; + + if (icmpSocket) + { + #ifdef SB_ICMP_SUPPORT + #if defined(SN_SYSTEMS) + ProcessIncomingICMPReplies(engine); + return; + #else + recvSock = engine->icmpsock; + #endif + #endif + } + else + { + recvSock = engine->querysock; + } + + // Process all information in the socket buffer + while(CanReceiveOnSocket(recvSock)) + { + i = (int)recvfrom(recvSock, indata, sizeof(indata) - 1, 0, (struct sockaddr *)&saddr, &saddrlen); + + if (gsiSocketIsError(i)) + break; + indata[i] = 0; + //find the server in our query list + for (server = engine->querylist.first ; server != NULL ; server = server->next) + { + if ((icmpSocket && (server->flags & ICMP_IP_FLAG) && server->icmpip == saddr.sin_addr.s_addr) || //if it's an ICMP query and it matches the ICMP address + (server->publicip == saddr.sin_addr.s_addr && (server->publicport == saddr.sin_port || icmpSocket)) || //if it matches public - port doesnt need to match for ICMP + (server->publicip == engine->mypublicip && (server->flags & PRIVATE_IP_FLAG) && server->privateip == saddr.sin_addr.s_addr && server->privateport == saddr.sin_port)) //or has a private, and matches + { + if (icmpSocket) + { + if (ParseSingleICMPReply(engine, server, indata, i)) + break; //only break if it matches exactly, since we may have multiple outstanding pings to the same ICMPIP for different servers! + } else + { + if (engine->queryversion == QVERSION_QR2) + ParseSingleQR2Reply(engine, server, indata, i); + else + ParseSingleGOAReply(engine, server, indata, i); + break; + } + } + } + } + + +} + +static void TimeoutOldQueries(SBQueryEngine *engine) +{ + gsi_time ctime = current_time(); + while (engine->querylist.first != NULL) + { + if (ctime > engine->querylist.first->updatetime + MAX_QUERY_MSEC) + { + engine->querylist.first->flags |= STATE_QUERYFAILED; + engine->querylist.first->updatetime = MAX_QUERY_MSEC; + engine->querylist.first->flags &= (unsigned char)~(STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY|STATE_PENDINGICMPQUERY); + engine->ListCallback(engine, qe_updatefailed, engine->querylist.first, engine->instance); + FIFOGetFirst(&engine->querylist); + } else + break; //since servers are added in FIFO order, nothing later can have already expired + } +} + +static void QueueNextQueries(SBQueryEngine *engine) +{ + while (engine->querylist.count < engine->maxupdates && engine->pendinglist.count > 0) + { + SBServer server = FIFOGetFirst(&engine->pendinglist); + QEStartQuery(engine, server); + } +} + +void SBQueryEngineThink(SBQueryEngine *engine) +{ + if (engine->querylist.count == 0) //not querying anything - we can go away + return; + ProcessIncomingReplies(engine, SBFalse); +#ifdef SB_ICMP_SUPPORT + ProcessIncomingReplies(engine, SBTrue); +#endif + TimeoutOldQueries(engine); + if (engine->pendinglist.count > 0) + QueueNextQueries(engine); + if (engine->querylist.count == 0) //we are now idle.. + engine->ListCallback(engine, qe_engineidle, NULL, engine->instance); +} + +void SBQueryEngineAddQueryKey(SBQueryEngine *engine, unsigned char keyid) +{ + if (engine->numserverkeys < MAX_QUERY_KEYS) + engine->serverkeys[engine->numserverkeys++] = keyid; +} + + +//remove a server from our update FIFOs +void SBQueryEngineRemoveServerFromFIFOs(SBQueryEngine *engine, SBServer server) +{ + SBBool ret; + + // remove the server from the current query list + ret = FIFORemove(&engine->querylist, server); + if(ret) + return; // -- Caution: assumes that server will not be in pendinglist + FIFORemove(&engine->pendinglist, server); +} diff --git a/code/gamespy/serverbrowsing/sb_server.c b/code/gamespy/serverbrowsing/sb_server.c new file mode 100644 index 00000000..477e850d --- /dev/null +++ b/code/gamespy/serverbrowsing/sb_server.c @@ -0,0 +1,892 @@ +#include "sb_internal.h" +#include "sb_ascii.h" + +//for the unique value list +#if defined(_NITRO) + #define LIST_NUMKEYBUCKETS 100 + #define LIST_NUMKEYCHAINS 2 +#else + #define LIST_NUMKEYBUCKETS 500 + #define LIST_NUMKEYCHAINS 4 +#endif + +// for unicode version of key/value pairs +#define UKEY_LENGTH_MAX 255 +#define UVALUE_LENGTH_MAX 255 + +#ifndef EXTERN_REFSTR_HASH +//global, shared unique value list +#if defined(_WIN32) && !defined(_DLL) && !defined (_USRDLL) && !defined(_MANAGED) && defined(GM_2B) +//for gmaster2b +__declspec( thread ) +#endif +HashTable g_SBRefStrList = NULL; +#endif + + +/*********** + * REF COUNTING STRING HASHTABLE FUNCTIONS + **********/ +static int StringHash(const char *s, int numbuckets); +static int RefStringHash(const void *elem, int numbuckets) +{ + return StringHash(((SBRefString *)elem)->str, numbuckets); +} + + + +/* keyval +* Compares two gkeyvaluepair +*/ + +static int GS_STATIC_CALLBACK RefStringCompare(const void *entry1, const void *entry2) +{ + return strcasecmp(((SBRefString *)entry1)->str,((SBRefString *)entry2)->str); +} + + +static void RefStringFree(void *elem) +{ + gsifree((char *)((SBRefString *)elem)->str); +#ifdef GSI_UNICODE + gsifree((unsigned short *)((SBRefString *)elem)->str_W); +#endif +} + + +#ifndef EXTERN_REFSTR_HASH + +HashTable SBRefStrHash(SBServerList *slist) +{ + if (g_SBRefStrList == NULL) + g_SBRefStrList = TableNew2(sizeof(SBRefString),LIST_NUMKEYBUCKETS,LIST_NUMKEYCHAINS,RefStringHash, RefStringCompare, RefStringFree); + + GSI_UNUSED(slist); + return g_SBRefStrList; +} + +void SBRefStrHashCleanup(SBServerList *slist) +{ + if (g_SBRefStrList != NULL && TableCount(g_SBRefStrList) == 0) + { + TableFree(g_SBRefStrList); + g_SBRefStrList = NULL; + } + + GSI_UNUSED(slist); +} + +#endif + + +void SBServerFree(void *elem) +{ + SBServer server = *(SBServer *)elem; + //free all the keys.. + TableFree(server->keyvals); + server->keyvals = NULL; + gsifree(server); +} + +void SBServerAddKeyValue(SBServer server, const char *keyname, const char *value) +{ + SBKeyValuePair kv; + kv.key = SBRefStr(NULL, keyname); + kv.value = SBRefStr(NULL, value); + TableEnter(server->keyvals, &kv); + + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Misc, GSIDebugLevel_Comment, + "SBServerAddKeyValue added %s\\%s\r\n", keyname, value); +} + +void SBServerAddIntKeyValue(SBServer server, const char *keyname, int value) +{ + char stemp[20]; + sprintf(stemp, "%d", value); + SBServerAddKeyValue(server, keyname, stemp); +} + +typedef struct +{ + SBServerKeyEnumFn EnumFn; + void *instance; +} SBServerEnumData; + +/* ServerEnumKeys +----------------- +Enumerates the keys/values for a given server by calling KeyEnumFn with each +key/value. The user-defined instance data will be passed to the KeyFn callback */ + +static void KeyMapF(void *elem, void *clientData) +{ + SBKeyValuePair *kv = (SBKeyValuePair *)elem; + SBServerEnumData *ped = (SBServerEnumData *)clientData; + +#ifndef GSI_UNICODE + ped->EnumFn((char *)kv->key, (char *)kv->value, ped->instance); +#else + unsigned short key_W[UKEY_LENGTH_MAX]; + unsigned short value_W[UVALUE_LENGTH_MAX]; + UTF8ToUCS2String(kv->key, key_W); + UTF8ToUCS2String(kv->value, value_W); + ped->EnumFn(key_W, value_W, ped->instance); +#endif +} +void SBServerEnumKeys(SBServer server, SBServerKeyEnumFn KeyFn, void *instance) +{ + SBServerEnumData ed; + + ed.EnumFn = KeyFn; + ed.instance = instance; + TableMap(server->keyvals, KeyMapF, &ed); +} + + +const char *SBServerGetStringValueA(SBServer server, const char *keyname, const char *def) +{ + SBKeyValuePair kv, *ptr; + + // 11-03-2004 : Saad Nader + // Check if we are getting a valid server + // before doing anything! + /////////////////////////////////////////// + assert(server); + if (!server) + return NULL; + kv.key = keyname; + ptr = (SBKeyValuePair *)TableLookup(server->keyvals, &kv); + if (ptr == NULL) + return def; + return ptr->value; +} +#ifdef GSI_UNICODE +const unsigned short *SBServerGetStringValueW(SBServer server, const unsigned short *keyname, const unsigned short *def) +{ + char* keyname_A = UCS2ToUTF8StringAlloc(keyname); + const char* value = SBServerGetStringValueA(server, keyname_A, NULL); + if (value == NULL) + return def; + else + { + // Since we need the unicode version, we have to dig down to the + // reference counted string structure + SBRefString ref, *val; + ref.str = value; + val = (SBRefString *)TableLookup(SBRefStrHash(NULL), &ref); + if (val == NULL) + return def; // this shouldn't happen + + return val->str_W; + } +} +#endif + +int SBServerGetIntValueA(SBServer server, const char *key, int idefault) +{ + const char *s, *s2; + // check assumtions during development + GS_ASSERT(key != NULL); + GS_ASSERT(server != NULL); + if (server == NULL) + return idefault; + if (strcmp(key,"ping") == 0) //ooh! they want the ping! + return SBServerGetPing(server); + s = SBServerGetStringValueA(server, key, NULL); + if (s == NULL) + return idefault; + s2 = (*s != '-') ? s : s+1; // check for signed values + if (!isdigit((unsigned char)*s2)) // empty-string/non-numeric should return idefault + return idefault; + else + return atoi(s); +} +#ifdef GSI_UNICODE +int SBServerGetIntValueW(SBServer server, const unsigned short *key, int idefault) +{ + char keyname_A[255]; + UCS2ToUTF8String(key, keyname_A); + return SBServerGetIntValueA(server, keyname_A, idefault); +} +#endif + +double SBServerGetFloatValueA(SBServer server, const char *key, double fdefault) +{ + const char *s; + s = SBServerGetStringValueA(server, key, NULL); + if (s == NULL) + return fdefault; + else + return atof(s); +} +#ifdef GSI_UNICODE +double SBServerGetFloatValueW(SBServer server, const unsigned short *key, double fdefault) +{ + char keyname_A[255]; + UCS2ToUTF8String(key, keyname_A); + return SBServerGetFloatValueA(server, keyname_A, fdefault); +} +#endif + +SBBool SBServerGetBoolValueA(SBServer server, const char *key, SBBool bdefault) +{ + const char *s; + s = SBServerGetStringValueA(server, key, NULL); + if (!s || !s[0]) + return bdefault; + + // check the first char for known "false" values + if('0' == s[0]|| 'F' == s[0] || 'f' == s[0] || 'N' == s[0] || 'n' == s[0]) + return SBFalse; + + // presume that all other non-zero values are "true" + return SBTrue; +} +#ifdef GSI_UNICODE +SBBool SBServerGetBoolValueW(SBServer server, const unsigned short *key, SBBool bdefault) +{ + char keyname_A[255]; + UCS2ToUTF8String(key, keyname_A); + return SBServerGetBoolValueA(server, keyname_A, bdefault); +} +#endif + +const char *SBServerGetPlayerStringValueA(SBServer server, int playernum, const char *key, const char *sdefault) +{ + char keyname[128]; + sprintf(keyname, "%s_%d", key, playernum); + return SBServerGetStringValueA(server, keyname, sdefault); +} +#ifdef GSI_UNICODE +const unsigned short *SBServerGetPlayerStringValueW(SBServer server, int playernum, const unsigned short *key, const unsigned short *sdefault) +{ + char keyname_A[UKEY_LENGTH_MAX]; + char default_A[UKEY_LENGTH_MAX]; + const char* value_A = NULL; + UCS2ToUTF8String(key, keyname_A); + UCS2ToUTF8String(sdefault, default_A); + + value_A = SBServerGetPlayerStringValueA(server, playernum, keyname_A, default_A); + if (value_A == NULL) + return sdefault; + else + { + // Since we need the unicode version, we have to dig down to the SBRefString structure + SBRefString ref, *val; + ref.str = value_A; + val = (SBRefString *)TableLookup(SBRefStrHash(NULL), &ref); + if (val == NULL) + return sdefault; + return val->str_W; + } +} +#endif + +int SBServerGetPlayerIntValueA(SBServer server, int playernum, const char *key, int idefault) +{ + char keyname[128]; + sprintf(keyname, "%s_%d", key, playernum); + return SBServerGetIntValueA(server, keyname, idefault); +} +#ifdef GSI_UNICODE +int SBServerGetPlayerIntValueW(SBServer server, int playernum, const unsigned short *key, int idefault) +{ + char keyname_A[UKEY_LENGTH_MAX]; + UCS2ToUTF8String(key, keyname_A); + return SBServerGetPlayerIntValueA(server, playernum, keyname_A, idefault); +} +#endif + +double SBServerGetPlayerFloatValueA(SBServer server, int playernum, const char *key, double fdefault) +{ + char keyname[128]; + sprintf(keyname, "%s_%d", key, playernum); + return SBServerGetFloatValueA(server, keyname, fdefault); +} +#ifdef GSI_UNICODE +double SBServerGetPlayerFloatValueW(SBServer server, int playernum, const unsigned short *key, double fdefault) +{ + char keyname_A[UKEY_LENGTH_MAX]; + UCS2ToUTF8String(key, keyname_A); + return SBServerGetPlayerFloatValueA(server, playernum, keyname_A, fdefault); +} +#endif + +const char *SBServerGetTeamStringValueA(SBServer server, int teamnum, const char *key, const char *sdefault) +{ + char keyname[128]; + sprintf(keyname, "%s_t%d", key, teamnum); + return SBServerGetStringValueA(server, keyname, sdefault); +} +#ifdef GSI_UNICODE +const unsigned short *SBServerGetTeamStringValueW(SBServer server, int teamnum, const unsigned short *key, const unsigned short *sdefault) +{ + char keyname_A[UKEY_LENGTH_MAX]; + char default_A[UKEY_LENGTH_MAX]; + const char* value_A = NULL; + UCS2ToUTF8String(key, keyname_A); + UCS2ToUTF8String(sdefault, default_A); + value_A = SBServerGetTeamStringValueA(server, teamnum, keyname_A, default_A); + + if (value_A == NULL) + return sdefault; + else + { + // Since we need the unicode version, we have to dig down to the SBRefString structure + SBRefString ref, *val; + ref.str = value_A; + val = (SBRefString *)TableLookup(SBRefStrHash(NULL), &ref); + if (val == NULL) + return sdefault; + return val->str_W; + } +} +#endif + +int SBServerGetTeamIntValueA(SBServer server, int teamnum, const char *key, int idefault) +{ + char keyname[128]; + sprintf(keyname, "%s_t%d", key, teamnum); + return SBServerGetIntValueA(server, keyname, idefault); +} +#ifdef GSI_UNICODE +int SBServerGetTeamIntValueW(SBServer server, int teamnum, const unsigned short *key, int idefault) +{ + char keyname_A[UKEY_LENGTH_MAX]; + UCS2ToUTF8String(key, keyname_A); + return SBServerGetTeamIntValueA(server, teamnum, keyname_A, idefault); +} +#endif + +double SBServerGetTeamFloatValueA(SBServer server, int teamnum, const char *key, double fdefault) +{ + char keyname[128]; + sprintf(keyname, "%s_t%d", key, teamnum); + return SBServerGetFloatValueA(server, keyname, fdefault); +} +#ifdef GSI_UNICODE +double SBServerGetTeamFloatValueW(SBServer server, int teamnum, const unsigned short *key, double fdefault) +{ + char keyname_A[UKEY_LENGTH_MAX]; + UCS2ToUTF8String(key, keyname_A); + return SBServerGetTeamFloatValueA(server, teamnum, keyname_A, fdefault); +} +#endif + +SBBool SBServerHasBasicKeys(SBServer server) +{ + return (((server->state & STATE_BASICKEYS) == STATE_BASICKEYS) ? SBTrue : SBFalse); +} + +SBBool SBServerHasFullKeys(SBServer server) +{ + return (((server->state & STATE_FULLKEYS) == STATE_FULLKEYS) ? SBTrue : SBFalse); +} + +SBBool SBServerHasValidPing(SBServer server) +{ + return (((server->state & STATE_VALIDPING) == STATE_VALIDPING) ? SBTrue : SBFalse); +} + + +char *SBServerGetPublicAddress(SBServer server) +{ + return (char *)inet_ntoa(*(struct in_addr *)&server->publicip); +} + +unsigned int SBServerGetPublicInetAddress(SBServer server) +{ + return server->publicip; +} + + +unsigned short SBServerGetPublicQueryPort(SBServer server) +{ + return ntohs(server->publicport); +} + +unsigned short SBServerGetPublicQueryPortNBO(SBServer server) +{ + return server->publicport; +} + +SBBool SBServerHasPrivateAddress(SBServer server) +{ + return (((server->flags & PRIVATE_IP_FLAG) == PRIVATE_IP_FLAG) ? SBTrue : SBFalse); +} + + + +SBBool SBServerDirectConnect(SBServer server) +{ + return (((server->flags & UNSOLICITED_UDP_FLAG) == UNSOLICITED_UDP_FLAG) ? SBTrue : SBFalse); +} + +char *SBServerGetPrivateAddress(SBServer server) +{ + return (char *)inet_ntoa(*(struct in_addr *)&server->privateip); +} + +unsigned int SBServerGetPrivateInetAddress(SBServer server) +{ + return server->privateip; +} + + +unsigned short SBServerGetPrivateQueryPort(SBServer server) +{ + return ntohs(server->privateport); +} + +void SBServerSetNext(SBServer server, SBServer next) +{ + server->next = next; +} + +SBServer SBServerGetNext(SBServer server) +{ + return server->next; +} + +static int CheckValidKey(char *key) +{ + const char *InvalidKeys[]={"queryid","final"}; + int i; + for (i = 0; i < sizeof(InvalidKeys)/sizeof(InvalidKeys[0]); i++) + { + if (strcmp(key,InvalidKeys[i]) == 0) + return 0; + } + return 1; +} + +static char *mytok(char *instr, char delim) +{ + char *result; + static char *thestr; + + if (instr) + thestr = instr; + result=thestr; + while (*thestr && *thestr != delim) + { + thestr++; + } + if (thestr == result) + result = NULL; + if (*thestr) //not the null term + *thestr++ = '\0'; + return result; +} + + +void SBServerParseKeyVals(SBServer server, char *keyvals) +{ + char *k, *v; + + k = mytok(++keyvals,'\\'); //skip over starting backslash + while (k != NULL) + { + v = mytok(NULL,'\\'); + if (v == NULL) + v = ""; + if (CheckValidKey(k)) + { + //add the value if its not a Query-From-Master-Only key + if (!qr2_internal_is_master_only_key(k)) + { + SBServerAddKeyValue(server, k, v); + } + } + k = mytok(NULL,'\\'); + } +} + + +/* +Query Response Format +Packet Type: 1 Byte +Request Key: 4 Bytes (copied from request packet) + +Server Keys (if number of keys requested > 0) +If server key list specified: +Server Values: NTS, one per key specified +If server key list not specified: +Server Keys / Server Values: NTS Pairs, terminated with NUL + +Player Keys (if number of keys requested > 0) +Number of Players: 2 Bytes +If player key list NOT specified + Player Keys: NTS, one per key, terminated with NUL +Player Values: NTS, one per key specified, per player + +Team Keys (if number of keys requested > 0) +Number of Teams: 2 Bytes +If team key list NOT specified + Team Keys: NTS, one per key, terminated with NUL +Team Values: NTS, one per key specified, per team + +*/ +void SBServerParseQR2FullKeysSingle(SBServer server, char *data, int len) +{ + int dlen; + char *k; + char *v; + char *keys; + int nkeys; + unsigned short nunits; + int pflag; + int i,j; + char tempkey[128]; + //first pull out all the server keys/values + while (*data) + { + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; //not a full NTS + k = data; + data += dlen; + len -= dlen; + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; //not a full NTS + v = data; + data += dlen; + len -= dlen; + + //add the value if its not a Query-From-Master-Only key + if (!qr2_internal_is_master_only_key(k)) + { + SBServerAddKeyValue(server, k, v); + } + } + //skip the NUL + data++; + len--; + //now get out the # of players (or teams) .. we do this whole thing 2X, once for players once for teams + for (pflag = 0 ; pflag < 2 ; pflag++) + { + if (len < 2) + return; + memcpy(&nunits, data, 2); + nunits = ntohs(nunits); + data += 2; + len -= 2; + keys = data; + nkeys = 0; + //count up the number of keys.. + while (*data) + { + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; //not all there + if (dlen > 100) //key is too long, may cause buffer overrun + return; + nkeys++; + data += dlen; + len -= dlen; + } + //skip the NUL + data++; + len--; + //now for each player/team + for (i = 0 ; i < nunits ; i++) + { + k = keys; + //for each key.. + for (j = 0 ; j < nkeys ; j++) + { + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; //not all there + sprintf(tempkey, "%s%d", k, i); + SBServerAddKeyValue(server, tempkey, data); + data += dlen; + len -= dlen; + k += strlen(k) + 1; //skip to the next key + } + } + } + +} + + +// FullKeys with split response support +// Goes something like: +// [qr2header]["splitnum"][num (byte)][keytype] +// if keytype is server, read KV's until a NULL +// otherwise read a keyname, then a number of values until NULL +#define QR2_SPLITPACKET_NUMSTRING "splitnum" +#define QR2_SPLITPACKET_MAX 7 // -xxxxxxx +#define QR2_SPLITPACKET_FINAL (1<<7) // x------- +void SBServerParseQR2FullKeysSplit(SBServer server, char *data, int len) +{ + int dlen; + char *k; + char *v; + unsigned int packetNum = 0; + gsi_bool isFinal = gsi_false; + + // make sure it's valid + if (*data == '\0') + return; + + // data should have "splitnum" followed by BINARY key + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; + k = data; + data += dlen; + len -= dlen; + + if (strncasecmp(QR2_SPLITPACKET_NUMSTRING,k,strlen(QR2_SPLITPACKET_NUMSTRING))!=0) + return; + if (len < 1) + return; + + packetNum = (unsigned int)((unsigned char)*data); + data++; + len--; + + // check final flag + if ((packetNum & QR2_SPLITPACKET_FINAL) == QR2_SPLITPACKET_FINAL) + { + isFinal = gsi_true; + packetNum ^= QR2_SPLITPACKET_FINAL; + } + + // sanity check the packet num + if (packetNum > QR2_SPLITPACKET_MAX) + return; + + // update the received flags + if (isFinal == SBTrue) + // mark all bits higher than the final packet + server->splitResponseBitmap |= (char)(0xFF<splitResponseBitmap |= (1< 0) + { + int keyType = 0; + int nindex = 0; + + // Read the key type + keyType = *data; + data++; + len--; + + if (keyType < 0 || keyType > 2) + { + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Split packet parse error, invalid key type! (%d)\r\n", keyType); + return; // invalid key type! + } + + // read keys until section terminator + while(*data) + { + // Read key name + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; + k = data; + data += dlen; + len -= dlen; + + if (keyType == 0) + { + // read the server key value + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; + v = data; + data += dlen; + len -= dlen; + + //add the value if its not a Query-From-Master-Only key + if (!qr2_internal_is_master_only_key(k)) + { + SBServerAddKeyValue(server, k, v); + } + } + else + { + char tempkey[128]; + + // Read first player/team number + if (len < 1) + return; + + nindex = *data; + data++; + len--; + + // read values until + while(*data) + { + // read the value + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; + v = data; + data += dlen; + len -= dlen; + // append team or player index before adding + sprintf(tempkey, "%s%d", k, nindex); + SBServerAddKeyValue(server, tempkey, v); + nindex++; // index increments from start + } + + // skip the null (key terminator) + if (len > 0) + { + data++; + len--; + } + } + } + + // skip the null (section terminator) + if (len > 0) + { + if (*data != '\0') + { + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Split packet parse error, NULL byte expected!\r\n"); + return; // ERROR! + } + data++; + len--; + } + } +} + +/*********** + * UTILITY FUNCTIONS + **********/ +#define MULTIPLIER -1664117991 +static int StringHash(const char *s, int numbuckets) +{ + goa_uint32 hashcode = 0; + while (*s != 0) + { + hashcode = (goa_uint32)((int)hashcode * MULTIPLIER + tolower(*s)); + s++; + } + return (int)(hashcode % numbuckets); + +} + +static void KeyValFree(void *elem) +{ + SBKeyValuePair *kv = (SBKeyValuePair *)elem; + SBReleaseStr(NULL, kv->key); + SBReleaseStr(NULL, kv->value); +} + +static int KeyValHashKey(const void *elem, int numbuckets) +{ + return StringHash(((SBKeyValuePair *)elem)->key, numbuckets); +} + + +static int GS_STATIC_CALLBACK KeyValCompareKey(const void *entry1, const void *entry2) +{ + GS_ASSERT(entry1) + GS_ASSERT(entry2) + if ( + (((SBKeyValuePair *)entry1)->key == NULL) || + (((SBKeyValuePair *)entry2)->key == NULL) + ) + return 1;// treat NULL as not the same + + return strcasecmp(((SBKeyValuePair *)entry1)->key, ((SBKeyValuePair *)entry2)->key); +} + + +int SBServerGetPing(SBServer server) +{ + return (int) server->updatetime; +} + +#define NUM_BUCKETS 8 +#define NUM_CHAINS 4 + +//todo: benchmark sorted darray vs. hashtable - memory + speed +SBServer SBAllocServer(SBServerList *slist, goa_uint32 publicip, unsigned short publicport) +{ + SBServer server; + server = (SBServer)gsimalloc(sizeof(struct _SBServer)); + if (server == NULL) + return NULL; + server->keyvals = TableNew2(sizeof(SBKeyValuePair),NUM_BUCKETS,NUM_CHAINS, KeyValHashKey, KeyValCompareKey, KeyValFree); + if (server->keyvals == NULL) + { + gsifree(server); + return NULL; + } + server->state = 0; + server->flags = 0; + server->next = NULL; + server->updatetime = 0; + server->icmpip = 0; + server->publicip = publicip; + server->publicport = publicport; + server->privateip = 0; + server->privateport = 0; + + GSI_UNUSED(slist); + return server; +} + + + +void SBServerSetFlags(SBServer server, unsigned char flags) +{ + server->flags = flags; +} +void SBServerSetPublicAddr(SBServer server, goa_uint32 ip, unsigned short port) +{ + server->publicip = ip; + server->publicport = port; + +} +void SBServerSetPrivateAddr(SBServer server, goa_uint32 ip, unsigned short port) +{ + server->privateip = ip; + server->privateport = port; + +} +void SBServerSetICMPIP(SBServer server, goa_uint32 icmpip) +{ + server->icmpip = icmpip; + +} +void SBServerSetState(SBServer server, unsigned char state) +{ + server->state = state; + +} +unsigned char SBServerGetState(SBServer server) +{ + return server->state; + +} +unsigned char SBServerGetFlags(SBServer server) +{ + return server->flags; + +} + +int SBIsNullServer(SBServer server) +{ + return (server == SBNullServer) ? 1 : 0; +} + +SBServer SBNullServer = NULL; diff --git a/code/gamespy/serverbrowsing/sb_serverbrowsing.c b/code/gamespy/serverbrowsing/sb_serverbrowsing.c new file mode 100644 index 00000000..a8a64f03 --- /dev/null +++ b/code/gamespy/serverbrowsing/sb_serverbrowsing.c @@ -0,0 +1,681 @@ +#include "sb_internal.h" +#include "sb_ascii.h" +#include "../natneg/natneg.h" + +//Future Versions: +//ICMP Ping support (icmp engine) + + +//internal callback proxy for server list +static void ListCallback(SBServerList *serverlist, SBListCallbackReason reason, SBServer server, void *instance) +{ + ServerBrowser sb = (ServerBrowser)instance; + switch (reason) + { + case slc_serveradded: + sb->BrowserCallback(sb, sbc_serveradded, server, sb->instance); + if ((server->state & (STATE_BASICKEYS|STATE_FULLKEYS)) == 0 || (server->state & STATE_VALIDPING) == 0) //we need to do an update + { + // Don't update if an update is already pending + if (server->state & (STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY|STATE_PENDINGICMPQUERY)) + break; + + if (!sb->dontUpdate) //if this flag is set, we don't want to trigger updates + { + int qtype; + if (server->flags & UNSOLICITED_UDP_FLAG) + { + if (sb->list.state == sl_lanbrowse || sb->engine.numserverkeys == 0) + qtype = QTYPE_FULL; + else + qtype = QTYPE_BASIC; + } else + qtype = QTYPE_ICMP; //we can only do an ICMP query + + if (serverlist->backendgameflags & QR2_USE_QUERY_CHALLENGE) + SBQueryEngineUpdateServer(&sb->engine, server, 0, qtype, SBTrue); + else + SBQueryEngineUpdateServer(&sb->engine, server, 0, qtype, SBFalse); + + } + } + break; + case slc_serverupdated: + if ((server->state & (STATE_BASICKEYS|STATE_FULLKEYS|STATE_VALIDPING)) == 0) //if it was updated, but with no data, then the update failed! + sb->BrowserCallback(sb, sbc_serverupdatefailed, server, sb->instance); + else + sb->BrowserCallback(sb, sbc_serverupdated, server, sb->instance); + break; + case slc_serverdeleted: + if ((server->state & (STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY|STATE_PENDINGICMPQUERY)) != 0) + SBQueryEngineRemoveServerFromFIFOs(&sb->engine, server); + sb->BrowserCallback(sb, sbc_serverdeleted, server, sb->instance); + break; + case slc_initiallistcomplete: + if (sb->disconnectFlag) + SBServerListDisconnect(serverlist); + // If there aren't any servers to query, call the completed callback + if (ArrayLength(serverlist->servers)==0 || sb->engine.querylist.count==0) + sb->BrowserCallback(sb, sbc_updatecomplete, NULL, sb->instance); + break; + case slc_disconnected: + break; + case slc_queryerror: + sb->BrowserCallback(sb, sbc_queryerror, NULL, sb->instance); + break; + case slc_publicipdetermined: + SBQueryEngineSetPublicIP(&sb->engine, sb->list.mypublicip); + break; + case slc_serverchallengereceived: + break; + } + if (server != NULL && server->publicip == sb->triggerIP && server->publicport == sb->triggerPort) + sb->triggerIP = 0; //clear the trigger +} + +//internal callback proxy for query engine +static void EngineCallback(SBQueryEnginePtr engine, SBQueryEngineCallbackReason reason, SBServer server, void *instance) +{ + ServerBrowser sb = (ServerBrowser)instance; + switch (reason) + { + case qe_updatefailed: + sb->BrowserCallback(sb, sbc_serverupdatefailed, server, sb->instance); + break; + case qe_updatesuccess: + sb->BrowserCallback(sb, sbc_serverupdated, server, sb->instance); + break; + case qe_engineidle: + sb->BrowserCallback(sb, sbc_updatecomplete, server, sb->instance); + break; + case qe_challengereceived: + sb->BrowserCallback(sb, sbc_serverchallengereceived, server, sb->instance); + //challenge received will return instead of break - since the game has not yet been updated + return; + } + if (server != NULL && server->publicip == sb->triggerIP && server->publicport == sb->triggerPort) + sb->triggerIP = 0; //clear the trigger + + GSI_UNUSED(engine); +} + + + +ServerBrowser ServerBrowserNewA(const char *queryForGamename, const char *queryFromGamename, const char *queryFromKey, int queryFromVersion, int maxConcUpdates, int queryVersion, SBBool lanBrowse, ServerBrowserCallback callback, void *instance) +{ + ServerBrowser sb; + if(lanBrowse == SBFalse) + { + if(__GSIACResult != GSIACAvailable) + return NULL; + } + + sb = (ServerBrowser)gsimalloc(sizeof(struct _ServerBrowser)); + if (sb == NULL) + return NULL; + sb->BrowserCallback = callback; + sb->ConnectCallback = NULL; + sb->instance = instance; + sb->dontUpdate = SBFalse; + SBServerListInit(&sb->list, queryForGamename, queryFromGamename, queryFromKey, queryFromVersion, lanBrowse, ListCallback, sb); + SBQueryEngineInit(&sb->engine, maxConcUpdates, queryVersion, lanBrowse, EngineCallback, sb); + return sb; +} +#ifdef GSI_UNICODE +ServerBrowser ServerBrowserNewW(const unsigned short *queryForGamename, const unsigned short *queryFromGamename, const unsigned short *queryFromKey, int queryFromVersion, int maxConcUpdates, int queryVersion, SBBool lanBrowse, ServerBrowserCallback callback, void *instance) +{ + char forGameName_A[255]; + char fromGameName_A[255]; + char fromGameKey_A[255]; + + assert(queryForGamename != NULL); + assert(queryFromGamename != NULL); + assert(queryFromKey != NULL); + + UCS2ToAsciiString(queryForGamename, forGameName_A); + UCS2ToAsciiString(queryFromGamename, fromGameName_A); + UCS2ToAsciiString(queryFromKey, fromGameKey_A); + return ServerBrowserNewA(forGameName_A, fromGameName_A, fromGameKey_A, queryFromVersion, maxConcUpdates, queryVersion, lanBrowse, callback, instance); +} +#endif + +void ServerBrowserFree(ServerBrowser sb) +{ + SBServerListCleanup(&sb->list); + SBEngineCleanup(&sb->engine); + NNFreeNegotiateList(); + gsifree(sb); +} + +//internal version that allows passing of additional options +SBError ServerBrowserBeginUpdate2(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const char *serverFilter, int updateOptions, int maxServers) +{ + char keyList[MAX_FIELD_LIST_LEN] = ""; + int listLen = 0; + int i; + int keylen; + SBError err; + + sb->disconnectFlag = disconnectOnComplete; + //clear this out in case it was already set + sb->engine.numserverkeys = 0; + //build the key list... + for (i = 0 ; i < numBasicFields ; i++) + { + keylen = (int)strlen(qr2_registered_key_list[basicFields[i]]); + if (listLen + keylen + 1 >= MAX_FIELD_LIST_LEN) + break; //can't add this field, too long + listLen += sprintf(keyList + listLen, "\\%s", qr2_registered_key_list[basicFields[i]]); + //add to the engine query list + SBQueryEngineAddQueryKey(&sb->engine, basicFields[i]); + } + +#if defined(SN_SYSTEMS) && defined(SB_ICMP_SUPPORT) + { + // reset SNSystems internal ICMP ping structures + sndev_set_ping_reset_type optval; + optval.timeout_ms = MAX_QUERY_MSEC; // this gets rounded up to 3 sec + optval.reserved = 0; + sndev_set_options(0, SN_DEV_SET_PING_RESET, &optval, sizeof(optval)); + } +#endif + + err = SBServerListConnectAndQuery(&sb->list, keyList, serverFilter, updateOptions, maxServers); + if (err != sbe_noerror) + return err; + + if (!async) //loop while we are still getting the main list and the engine is updating... + { + while ((sb->list.state == sl_mainlist) || ((sb->engine.querylist.count > 0) && (err == sbe_noerror))) + { + msleep(10); + err = ServerBrowserThink(sb); + } + } + return err; +} + + +SBError ServerBrowserUpdateA(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const char *serverFilter) +{ + return ServerBrowserBeginUpdate2(sb, async, disconnectOnComplete, basicFields, numBasicFields, serverFilter, 0, 0); +} +#ifdef GSI_UNICODE +SBError ServerBrowserUpdateW(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const unsigned short *serverFilter) +{ + char serverFilter_A[1024]; + if (serverFilter != NULL) + UCS2ToUTF8String(serverFilter, serverFilter_A); + return ServerBrowserUpdateA(sb, async, disconnectOnComplete, basicFields, numBasicFields, (serverFilter != NULL) ? serverFilter_A:NULL); +} +#endif + +SBError ServerBrowserLimitUpdateA(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const char *serverFilter, int maxServers) +{ + return ServerBrowserBeginUpdate2(sb, async, disconnectOnComplete, basicFields, numBasicFields, serverFilter, LIMIT_RESULT_COUNT, maxServers); +} +#ifdef GSI_UNICODE +SBError ServerBrowserLimitUpdateW(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const unsigned short *serverFilter, int maxServers) +{ + char serverFilter_A[1024]; + if (serverFilter != NULL) + UCS2ToUTF8String(serverFilter, serverFilter_A); + return ServerBrowserLimitUpdateA(sb, async, disconnectOnComplete, basicFields, numBasicFields, (serverFilter != NULL) ? serverFilter_A:NULL, maxServers); +} +#endif + +SBError ServerBrowserLANUpdate(ServerBrowser sb, SBBool async, unsigned short startSearchPort, unsigned short endSearchPort) +{ + SBError err = sbe_noerror; + ServerBrowserHalt(sb); + SBServerListGetLANList(&sb->list, startSearchPort, endSearchPort, sb->engine.queryversion); + if (!async) + { + while ((sb->list.state == sl_lanbrowse) || ((sb->engine.querylist.count > 0) && (err == sbe_noerror))) + { + msleep(10); + err = ServerBrowserThink(sb); + } + } + return err; +} + +static SBError WaitForTriggerUpdate(ServerBrowser sb, SBBool viaMaster) +{ + SBError err = sbe_noerror; + //wait until info is received for the triggerIP + while (sb->triggerIP != 0 && err == sbe_noerror) + { + msleep(10); + err = ServerBrowserThink(sb); + if (viaMaster && sb->list.state == sb_disconnected) //we were supposed to get from master, and it's disconnected + break; + } + return err; + +} + + +SBError ServerBrowserSendMessageToServerA(ServerBrowser sb, const char *ip, unsigned short port, const char *data, int len) +{ + return SBSendMessageToServer(&sb->list, inet_addr(ip), htons(port), data, len); +} +#ifdef GSI_UNICODE +SBError ServerBrowserSendMessageToServerW(ServerBrowser sb, const unsigned short *ip, unsigned short port, const char *data, int len) +{ + char ip_A[128]; + UCS2ToAsciiString(ip, ip_A); + return ServerBrowserSendMessageToServerA(sb, ip_A, port, data, len); +} +#endif + +SBError ServerBrowserSendNatNegotiateCookieToServerA(ServerBrowser sb, const char *ip, unsigned short port, int cookie) +{ + return SBSendNatNegotiateCookieToServer(&sb->list, inet_addr(ip), htons(port), cookie); +} +#ifdef GSI_UNICODE +SBError ServerBrowserSendNatNegotiateCookieToServerW(ServerBrowser sb, const unsigned short *ip, unsigned short port, int cookie) +{ + char ip_A[128]; + UCS2ToAsciiString(ip, ip_A); + return ServerBrowserSendNatNegotiateCookieToServerA(sb, ip_A, port, cookie); +} +#endif + +static void NatNegProgressCallback(NegotiateState state, void *userdata) +{ + // we don't do anything here + GSI_UNUSED(state); + GSI_UNUSED(userdata); +} + +static void NatNegCompletedCallback(NegotiateResult result, SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *userdata) +{ + ServerBrowser sb = (ServerBrowser)userdata; + + if(result == nr_success) + { + sb->ConnectCallback(sb, sbcs_succeeded, gamesocket, remoteaddr, sb->instance); + } + else + { + sb->ConnectCallback(sb, sbcs_failed, INVALID_SOCKET, NULL, sb->instance); + } + + sb->ConnectCallback = NULL; + + GSI_UNUSED(remoteaddr); +} + +SBError ServerBrowserConnectToServer(ServerBrowser sb, SBServer server, SBConnectToServerCallback callback) +{ + SBError sbError; + NegotiateError nnError; + int cookie; + + if((sb == NULL) || (server == NULL) || (callback == NULL)) + return sbe_paramerror; + + if(sb->ConnectCallback != NULL) + return sbe_connecterror; + + // for now, always do natneg + + // send a cookie to the server + Util_RandSeed((unsigned long)current_time()); + cookie = Util_RandInt(GSI_MIN_I32, GSI_MAX_I32); + sbError = ServerBrowserSendNatNegotiateCookieToServerA(sb, SBServerGetPublicAddress(server), SBServerGetPublicQueryPort(server), cookie); + if(sbError != sbe_noerror) + return sbError; + + // start the nn + nnError = NNBeginNegotiation(cookie, 0, NatNegProgressCallback, NatNegCompletedCallback, sb); + if(nnError != ne_noerror) + return sbe_connecterror; + + sb->ConnectCallback = callback; + + return sbe_noerror; +} + +SBError ServerBrowserAuxUpdateIPA(ServerBrowser sb, const char *ip, unsigned short port, SBBool viaMaster, SBBool async, SBBool fullUpdate) +{ + + SBError err = sbe_noerror; + sb->dontUpdate = SBTrue; + + if (!viaMaster) //do an engine query + { + SBServer server; + int i; + SBBool usequerychallenge = (sb->list.backendgameflags & QR2_USE_QUERY_CHALLENGE) > 0 ? SBTrue:SBFalse; + + //need to see if the server exists... + i = SBServerListFindServerByIP(&sb->list, inet_addr(ip), htons(port)); + if (i == -1) + { + server = SBQueryEngineUpdateServerByIP(&sb->engine, ip, port, 1, (fullUpdate) ? QTYPE_FULL : QTYPE_BASIC, usequerychallenge); + SBServerListAppendServer(&sb->list, server); + } + else + server = SBServerListNth(&sb->list, i); + + // Don't overwrite the existing update, otherwise the response for the first update + // will be mistaken as the response for the second update. (Which is bad if they have different update parameters.) + if (server->state & (STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY|STATE_PENDINGICMPQUERY)) + return sbe_duplicateupdateerror; + else + SBQueryEngineUpdateServer(&sb->engine, server, 1, (fullUpdate) ? QTYPE_FULL : QTYPE_BASIC, usequerychallenge); + + } else //do a master update + { + err = SBGetServerRulesFromMaster(&sb->list, inet_addr(ip), htons(port)); + //this will add the server itself.. + } + if (!async && err == sbe_noerror) + { + sb->triggerIP = inet_addr(ip); + sb->triggerPort = htons(port); + err = WaitForTriggerUpdate(sb, viaMaster); + } + sb->dontUpdate = SBFalse; + return err; +} +#ifdef GSI_UNICODE +SBError ServerBrowserAuxUpdateIPW(ServerBrowser sb, const unsigned short *ip, unsigned short port, SBBool viaMaster, SBBool async, SBBool fullUpdate) +{ + char ip_A[128]; + UCS2ToAsciiString(ip, ip_A); + return ServerBrowserAuxUpdateIPA(sb, ip_A, port, viaMaster, async, fullUpdate); +} +#endif + +SBError ServerBrowserAuxUpdateServer(ServerBrowser sb, SBServer server, SBBool async, SBBool fullUpdate) +{ + SBBool viaMaster; + SBError err = sbe_noerror; + SBBool usequerychallenge = (sb->list.backendgameflags & QR2_USE_QUERY_CHALLENGE) > 0 ? SBTrue:SBFalse; + + sb->dontUpdate = SBTrue; + + if (server->flags & UNSOLICITED_UDP_FLAG) //do an engine query + { + //remove from the existing update lists if present + SBQueryEngineRemoveServerFromFIFOs(&sb->engine, server); + SBQueryEngineUpdateServer(&sb->engine, server, 1, (fullUpdate) ? QTYPE_FULL : QTYPE_BASIC, usequerychallenge); + viaMaster = SBFalse; + } else //do a master update + { + err = SBGetServerRulesFromMaster(&sb->list, server->publicip, server->publicport); + viaMaster = SBTrue; + } + if (!async && err == sbe_noerror) + { + sb->triggerIP = server->publicip; + sb->triggerPort = server->publicport; + err = WaitForTriggerUpdate(sb, viaMaster); + } + sb->dontUpdate = SBFalse; + return err; +} + +void ServerBrowserRemoveIPA(ServerBrowser sb, const char *ip, unsigned short port) +{ + int i = SBServerListFindServerByIP(&sb->list, inet_addr(ip), htons(port)); + if (i != -1) + SBServerListRemoveAt(&sb->list, i); +} +#ifdef GSI_UNICODE +void ServerBrowserRemoveIPW(ServerBrowser sb, const unsigned short *ip, unsigned short port) +{ + char ip_A[128]; + UCS2ToAsciiString(ip, ip_A); + ServerBrowserRemoveIPA(sb, ip_A, port); +} +#endif + +void ServerBrowserRemoveServer(ServerBrowser sb, SBServer server) +{ + int i = SBServerListFindServer(&sb->list, server); + if (i != -1) + SBServerListRemoveAt(&sb->list, i); +} + +SBError ServerBrowserThink(ServerBrowser sb) +{ + NNThink(); + SBQueryEngineThink(&sb->engine); + return SBListThink(&sb->list); +} + +void ServerBrowserHalt(ServerBrowser sb) +{ + //stop the list + SBServerListDisconnect(&sb->list); + //stop the query engine... + SBEngineHaltUpdates(&sb->engine); + +} + +void ServerBrowserClear(ServerBrowser sb) +{ + ServerBrowserHalt(sb); + SBServerListClear(&sb->list); +} + +const char *ServerBrowserErrorDescA(ServerBrowser sb, SBError error) +{ + switch (error) + { + case sbe_noerror: + return "None"; + //break; + case sbe_socketerror: + return "Socket creation error"; + //break; + case sbe_dnserror: + return "DNS lookup error"; + //break; + case sbe_connecterror: + return "Connection failed"; + //break; + case sbe_dataerror: + return "Data stream error"; + //break; + case sbe_allocerror: + return "Memory allocation error"; + //break; + case sbe_paramerror: + return "Function parameter error"; + //break; + case sbe_duplicateupdateerror: + return "Duplicate update request error"; + //break; + } + + GSI_UNUSED(sb); + return ""; +} +#ifdef GSI_UNICODE +const unsigned short *ServerBrowserErrorDescW(ServerBrowser sb, SBError error) +{ + switch (error) + { + case sbe_noerror: + return L"None"; + break; + case sbe_socketerror: + return L"Socket creation error"; + break; + case sbe_dnserror: + return L"DNS lookup error"; + break; + case sbe_connecterror: + return L"Connection failed"; + break; + case sbe_dataerror: + return L"Data stream error"; + break; + case sbe_allocerror: + return L"Memory allocation error"; + break; + case sbe_paramerror: + return L"Function parameter error"; + break; + case sbe_duplicateupdateerror: + return L"Duplicate update request error"; + break; + } + return L""; + + GSI_UNUSED(sb); +} +#endif + +const char *ServerBrowserListQueryErrorA(ServerBrowser sb) +{ + return SBLastListErrorA(&sb->list); +} +#ifdef GSI_UNICODE +const unsigned short *ServerBrowserListQueryErrorW(ServerBrowser sb) +{ + return SBLastListErrorW(&sb->list); +} +#endif + +SBState ServerBrowserState(ServerBrowser sb) +{ + if (sb->engine.querylist.count > 0) + return sb_querying; + if (sb->list.state == sl_mainlist || sb->list.state == sl_lanbrowse) + return sb_listxfer; + if (sb->list.state == sl_disconnected) + return sb_disconnected; + return sb_connected; +} + +int ServerBrowserPendingQueryCount(ServerBrowser sb) +{ + return sb->engine.querylist.count + sb->engine.pendinglist.count; +} + +SBServer ServerBrowserGetServer(ServerBrowser sb, int index) +{ + return SBServerListNth(&sb->list, index); +} + +SBServer ServerBrowserGetServerByIPA(ServerBrowser sb, const char* ip, unsigned short port) +{ + int anIndex = -1; + goa_uint32 anIP = 0; + unsigned short aPortNBO = htons(port); + + anIP = inet_addr(ip); + anIndex = SBServerListFindServerByIP(&sb->list, anIP, aPortNBO); + if (anIndex != -1) + return SBServerListNth(&sb->list, anIndex); + return NULL; +} +#ifdef GSI_UNICODE +SBServer ServerBrowserGetServerByIPW(ServerBrowser sb, const unsigned short* ip, unsigned short port) +{ + char ip_A[20]; + + if(ip == NULL || wcslen(ip) > 16) + return NULL; + + UCS2ToAsciiString(ip, ip_A); + return ServerBrowserGetServerByIPA(sb, ip_A, port); +} +#endif + +int ServerBrowserCount(ServerBrowser sb) +{ + return SBServerListCount(&sb->list); +} + +void ServerBrowserSortA(ServerBrowser sb, SBBool ascending, const char *sortkey, SBCompareMode comparemode) +{ + SortInfo info; + info.comparemode = comparemode; +#ifdef GSI_UNICODE + GS_ASSERT(sortkey != NULL && _tcslen((const unsigned short *)sortkey) <= SORTKEY_LENGTH); + _tcscpy(info.sortkey, (const unsigned short *)sortkey); +#else + GS_ASSERT(sortkey != NULL && _tcslen(sortkey) <= SORTKEY_LENGTH); + _tcscpy(info.sortkey, sortkey); +#endif + SBServerListSort(&sb->list, ascending, info); +} +#ifdef GSI_UNICODE +void ServerBrowserSortW(ServerBrowser sb, SBBool ascending, const unsigned short *sortkey, SBCompareMode comparemode) +{ + char sortkey_A[255]; + UCS2ToUTF8String(sortkey, sortkey_A); + //struct SortInfo info; + //info.comparemode = comparemode; + //GS_ASSERT(sortkey != NULL && _tcslen((const unsigned short *)sortkey) <= SORTKEY_LENGTH); + //_tcscpy(info.sortkey, (const unsigned short *)sortkey_A); + //SBServerListSort(&sb->list, ascending, info); + ServerBrowserSortA(sb, ascending, sortkey_A, comparemode); +} +#endif + +char *ServerBrowserGetMyPublicIP(ServerBrowser sb) +{ + return (char *)inet_ntoa(*(struct in_addr *)&sb->list.mypublicip); +} + +unsigned int ServerBrowserGetMyPublicIPAddr(ServerBrowser sb) +{ + return sb->list.mypublicip; +} + +void ServerBrowserDisconnect(ServerBrowser sb) +{ + SBServerListDisconnect(&sb->list); +} + +// Allows the user to specify a broadcast address for LAN hosting +void ServerBrowserLANSetLocalAddr(ServerBrowser sb, const char* theAddr) +{ + sb->list.mLanAdapterOverride = theAddr; +} + + +/* SBServerGetConnectionInfo +---------------- +Check if Nat Negotiation is requires, based off whether it is a lan game, public ip present and several other facts. +Returns an IP string to use for NatNeg, or direct connect if possible +Work for subsequent connection to this server, One of three results will occur +i) Lan game, connect using ipstring +2) Internet game, connect using ipstring +3) nat traversal required, perform nat negotiation using Nat SDK and this ipstring before connecting. + +return sb_true if further processing is required... i.e. NAT. sb_false if not. +fills an IP string +*/ +SBBool SBServerGetConnectionInfo(ServerBrowser gSB, SBServer server, gsi_u16 PortToConnectTo, char *ipstring) +{ + SBBool natneg = SBFalse; + if (SBServerHasPrivateAddress(server) == SBTrue && (SBServerGetPublicInetAddress(server) == ServerBrowserGetMyPublicIPAddr(gSB))) + { + + //directly connect to private IP (LAN) + sprintf(ipstring,"%s:%d", SBServerGetPrivateAddress(server),PortToConnectTo ); + + } + else + if ((SBServerDirectConnect(server) == SBTrue )&& (SBServerHasPrivateAddress(server) == SBFalse)) + { + //can directly connect to public IP, no negotiation required + sprintf(ipstring,"%s:%d", SBServerGetPrivateAddress(server), PortToConnectTo ); + } + else + { + //Nat Negotiation required + natneg = SBTrue; + sprintf(ipstring,"%s:%d", SBServerGetPublicAddress(server), SBServerGetPublicQueryPort (server) ); + } + return natneg; +} diff --git a/code/gamespy/serverbrowsing/sb_serverbrowsing.h b/code/gamespy/serverbrowsing/sb_serverbrowsing.h new file mode 100644 index 00000000..81a65202 --- /dev/null +++ b/code/gamespy/serverbrowsing/sb_serverbrowsing.h @@ -0,0 +1,480 @@ +/****** +GameSpy Server Browsing SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + + Please see the GameSpy Server Browsing SDK documentation for more + information + +******/ + + +#ifndef _SB_SERVERBROWSING_H +#define _SB_SERVERBROWSING_H + +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + + + +/******************* +ServerBrowser Typedefs +********************/ +//ServerBrowser is an abstract data type used to represent the server list and query engine objects. +typedef struct _ServerBrowser *ServerBrowser; + +//SBServer is an abstract data type representing a single server. +#ifndef SBServer +typedef struct _SBServer *SBServer; +#endif +//Simple boolean type used for some functions +typedef enum {SBFalse, SBTrue} SBBool; + +//Error codes that can be returned from functions +typedef enum +{ + sbe_noerror, //no error has occured + sbe_socketerror, //a socket function has returned an unexpected error + sbe_dnserror, //DNS lookup of master address failed + sbe_connecterror, //connection to master server failed + sbe_dataerror, //invalid data was returned from master server + sbe_allocerror, //memory allocation failed + sbe_paramerror, //an invalid parameter was passed to a function + sbe_duplicateupdateerror //server update requested on a server that was already being updated +} SBError; + +//States the ServerBrowser object can be in +typedef enum +{ + sb_disconnected, //idle and not connected to the master server + sb_listxfer, //downloading list of servers from the master server + sb_querying, //querying servers + sb_connected //idle but still connected to the master server +} SBState; + +//Callbacks that can occur during server browsing operations +typedef enum +{ + sbc_serveradded, //a server was added to the list, may just have an IP & port at this point + sbc_serverupdated, //server information has been updated - either basic or full information is now available about this server + sbc_serverupdatefailed, //an attempt to retrieve information about this server, either directly or from the master, failed + sbc_serverdeleted, //a server was removed from the list + sbc_updatecomplete, //the server query engine is now idle + sbc_queryerror, //the master returned an error string for the provided query + sbc_serverchallengereceived // received ip verification challenge from server +} SBCallbackReason; + +//Passed to callback to indicate state of attempt to connect to server +typedef enum +{ + sbcs_succeeded, + sbcs_failed +} SBConnectToServerState; + +//Prototype for the callback function you need to provide +typedef void (*ServerBrowserCallback)(ServerBrowser sb, SBCallbackReason reason, SBServer server, void *instance); + +//Prototype for the callback function used when connecting to a server +typedef void (*SBConnectToServerCallback)(ServerBrowser sb, SBConnectToServerState state, SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *instance); + +//Maximum length for the SQL filter string +#define MAX_FILTER_LEN 256 + +//Version defines for query protocol +#define QVERSION_GOA 0 +#define QVERSION_QR2 1 + +/******************* +ServerBrowser Object Functions +********************/ + +#ifndef GSI_UNICODE +#define ServerBrowserNew ServerBrowserNewA +#define ServerBrowserUpdate ServerBrowserUpdateA +#define ServerBrowserLimitUpdate ServerBrowserLimitUpdateA +#define ServerBrowserAuxUpdateIP ServerBrowserAuxUpdateIPA +#define ServerBrowserRemoveIP ServerBrowserRemoveIPA +#define ServerBrowserSendNatNegotiateCookieToServer ServerBrowserSendNatNegotiateCookieToServerA +#define ServerBrowserSendMessageToServer ServerBrowserSendMessageToServerA +#define ServerBrowserSort ServerBrowserSortA +#define SBServerGetStringValue SBServerGetStringValueA +#define SBServerGetIntValue SBServerGetIntValueA +#define SBServerGetFloatValue SBServerGetFloatValueA +#define SBServerGetBoolValue SBServerGetBoolValueA +#define SBServerGetPlayerStringValue SBServerGetPlayerStringValueA +#define SBServerGetPlayerIntValue SBServerGetPlayerIntValueA +#define SBServerGetPlayerFloatValue SBServerGetPlayerFloatValueA +#define SBServerGetTeamStringValue SBServerGetTeamStringValueA +#define SBServerGetTeamIntValue SBServerGetTeamIntValueA +#define SBServerGetTeamFloatValue SBServerGetTeamFloatValueA +#define ServerBrowserListQueryError ServerBrowserListQueryErrorA +#define ServerBrowserErrorDesc ServerBrowserErrorDescA +#define ServerBrowserGetServerByIP ServerBrowserGetServerByIPA +#else +#define ServerBrowserNew ServerBrowserNewW +#define ServerBrowserUpdate ServerBrowserUpdateW +#define ServerBrowserLimitUpdate ServerBrowserLimitUpdateW +#define ServerBrowserAuxUpdateIP ServerBrowserAuxUpdateIPW +#define ServerBrowserRemoveIP ServerBrowserRemoveIPW +#define ServerBrowserSendNatNegotiateCookieToServer ServerBrowserSendNatNegotiateCookieToServerW +#define ServerBrowserSendMessageToServer ServerBrowserSendMessageToServerW +#define ServerBrowserSort ServerBrowserSortW +#define SBServerGetStringValue SBServerGetStringValueW +#define SBServerGetIntValue SBServerGetIntValueW +#define SBServerGetFloatValue SBServerGetFloatValueW +#define SBServerGetBoolValue SBServerGetBoolValueW +#define SBServerGetPlayerStringValue SBServerGetPlayerStringValueW +#define SBServerGetPlayerIntValue SBServerGetPlayerIntValueW +#define SBServerGetPlayerFloatValue SBServerGetPlayerFloatValueW +#define SBServerGetTeamStringValue SBServerGetTeamStringValueW +#define SBServerGetTeamIntValue SBServerGetTeamIntValueW +#define SBServerGetTeamFloatValue SBServerGetTeamFloatValueW +#define ServerBrowserListQueryError ServerBrowserListQueryErrorW +#define ServerBrowserErrorDesc ServerBrowserErrorDescW +#define ServerBrowserGetServerByIP ServerBrowserGetServerByIPW +#endif +/* +ServerBrowserNew +---------------- +Creates and returns a new (empty) ServerBrowser object. +Returns NULL if an allocation error occurs. + +queryForGamename - The gamename you are querying for +queryFromGamename - The gamename you are querying from - generally the same as queryForGamename +queryFromKey - Secret key that corresponds to the queryFromGamename +queryFromVersion - A game-specific version identifier (pass 0 unless told otherwise) +maxConcUpdates - Max number of concurent updates (10-15 for modem users, 20-30 for high-bandwidth) +queryVersion - Query protocol to use. Use QVERSION_GOA for DeveloperSpec/Query&Reporting1 games, and QVERSION_QR2 for games that use Query & Reporting 2 +callback - The function that will be called with list updates +instance - User-defined instance data (e.g. structure or object pointer) */ +ServerBrowser ServerBrowserNew(const gsi_char *queryForGamename, const gsi_char *queryFromGamename, const gsi_char *queryFromKey, int queryFromVersion, int maxConcUpdates, int queryVersion, SBBool lanBrowse, ServerBrowserCallback callback, void *instance); + +/* +ServerBrowserFree +----------------- +Free a ServerBrowser and all internal sturctures and servers */ +void ServerBrowserFree(ServerBrowser sb); + + +/* ServerBrowserUpdate +------------------- +Starts an update by downloading a list of servers from the master server, then querying them. + +sb - The server browser object to update +async - If SBTrue, the update will be initiated, and ServerListThink must be called for processing and querying to occur + If SBFalse, the function will not return until the initial list of servers has been completely updated +disconnectOnComplete - If SBTrue, the connection to the master server will be disconnected immediately after the list is downloaded. + If SBFalse, the connection will be left open for additional data queries, and can be closed via ServerBrowserDisconnect +basicFields - This array of registered QR2 keys is used to determine the fields requested from servers during the initial "basic" update. + Only server keys listed in this array will be returned for servers. +numBasicFields - The number of fields in the basicFields array +serverFilter - SQL Filter string that will be applied on the master server to limit the list of servers returned. + All server keys are available for filtering on the master server, as well as the master-defined "country" and "region" keys. + Standard SQL syntax should be used. + +ServerBrowserLimitUpdate +------------------------ +Identical to ServerBrowserUpdate, except that the number of servers returned can be limited +maxServers - Maximum number of servers to be returned +*/ +SBError ServerBrowserUpdate(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const gsi_char *serverFilter); +SBError ServerBrowserLimitUpdate(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const gsi_char *serverFilter, int maxServers); + + +/* ServerBrowserThink +------------------- +Processes incoming data from the master server and game servers that are being queried. Should be called +as often as possible while a server list update is in progress (~10ms is ideal). */ +SBError ServerBrowserThink(ServerBrowser sb); + +/* ServerBrowserLANUpdate +------------------- +Starts an update by searching for servers on the LAN, then querying them. You can specifiy a range of ports to search +for servers. Generally this should start with your standard query port, and range above it, since the QR and QR2 SDKs will +automatically allocate higher port numbers when running multiple servers on the same machine. You should limit your search +to 100 ports or less in most cases to limit flooding of the LAN with broadcast packets. + +sb - The server browser object to update +async - If SBTrue, the update will be initiated, and ServerListThink must be called for processing and querying to occur + If SBFalse, the function will not return until the initial list of servers has been completely updated +startSearchPort - The initial port to begin searching for servers on +endSearchPort - The final port to search. All ports between start and end will be queried. */ +SBError ServerBrowserLANUpdate(ServerBrowser sb, SBBool async, unsigned short startSearchPort, unsigned short endSearchPort); + +/* ServerBrowserAuxUpdateIP +------------------- +Manually updates a server given an IP address and query port. Use to manually add servers to the list when you just +have an IP and port for them. + +sb - The server browser object to add the server to +ip - The dotted IP address of the server e.g. "1.2.3.4" +port - The query port of the server +viaMaster - If SBTrue, information about the server will be retrieved from the master server instead of attempting to query the server directly. + If a connection to the master server does not exist, it will be made to kept open afterwards. + If SBFalse, the server will be contacted directly for information. +async - If SBTrue, the update will be initiated, and ServerListThink must be called for processing and querying to occur + If SBFalse, the function will not return until the server has been successfully or unsuccessfully updated +fullUpdate - If SBTrue, all server keys/rules/player/team information will be retrieved + If SBFalse, only the keys specified in the basicFields array of the ServerBrowserUpdate function will be retrieved */ +SBError ServerBrowserAuxUpdateIP(ServerBrowser sb, const gsi_char *ip, unsigned short port, SBBool viaMaster, SBBool async, SBBool fullUpdate); + +/* ServerBrowserAuxUpdateServer +------------------- +Manually updates a server object. Generally used to get additional information about a server (for example, to get full rules and +player information from a server that only has basic information so far), but can also be used to "refresh" the information +about a given server. Data will automatically be retrieved from the master server directly or from the game server as appropriate. +When called asynchronously, multiple server update requests can be queued and will be executed by the query engine in turn. + +sb - The server browser object to add the server to +server - Server object to update +async - If SBTrue, the update will be initiated, and ServerListThink must be called for processing and querying to occur + If SBFalse, the function will not return until the server has been successfully or unsuccessfully updated +fullUpdate - If SBTrue, all server keys/rules/player/team information will be retrieved + If SBFalse, only the keys specified in the basicFields array of the ServerBrowserUpdate function will be retrieved */ +SBError ServerBrowserAuxUpdateServer(ServerBrowser sb, SBServer server, SBBool async, SBBool fullUpdate); + + +/* ServerBrowserDisconnects +------------------- +Disconnects an idle connection to the master server when it is no longer needed. Note that if you disconnect +and then request an operation that requires a connection to the master, such as an AuxServerUpdate via the master, +the connection will be automatically re-established. */ +void ServerBrowserDisconnect(ServerBrowser sb); + + +/* ServerBrowserState +------------------- +Returns the current state of the Server Browser object */ +SBState ServerBrowserState(ServerBrowser sb); + +/* ServerBrowserRemoveIP +------------------- +Removes a server from the list given an IP and query port */ +void ServerBrowserRemoveIP(ServerBrowser sb, const gsi_char *ip, unsigned short port); + +/* ServerBrowserRemoveServer +------------------- +Removes a server from the list and releases all resources associated with it */ +void ServerBrowserRemoveServer(ServerBrowser sb, SBServer server); + +/* ServerBrowserHalt +------------------- +Stops a server list update in progress, clears any servers queued to be queried, and disconneects +from the master server. */ +void ServerBrowserHalt(ServerBrowser sb); + +/* ServerBrowserClear +------------------- +Removes all the servers from the list and frees all resources associated with them. */ +void ServerBrowserClear(ServerBrowser sb); + +/* ServerBrowserErrorDesc +------------------- +Returns a human-readable error string for the given error code. */ +const gsi_char *ServerBrowserErrorDesc(ServerBrowser sb, SBError error); + +/* ServerBrowserListQueryError +------------------- +When a list query error occurs, as indicated by the sbc_queryerror callback, this function allows you to +obtain the human-readable error string for the error (generally these errors are caused by errors in the +filter string) */ +const gsi_char *ServerBrowserListQueryError(ServerBrowser sb); + +/* ServerBrowserGetServer +---------------------- +Returns the server at the specified index, or NULL if the index is out of bounds */ +SBServer ServerBrowserGetServer(ServerBrowser sb, int index); + +/* ServerBrowserGetServerByIP +---------------------- +Returns the SBServer with the specified IP, or NULL if the server is not in the list */ +SBServer ServerBrowserGetServerByIP(ServerBrowser sb, const gsi_char* ip, unsigned short port); + +/* ServerBrowserCount +------------------ +Returns the number of servers on the specified list. Indexing is 0 based, so +the actual server indexes are 0 <= valid index < Count */ +int ServerBrowserCount(ServerBrowser sb); + +/* ServerBrowserPendingQueryCount +------------------ +Returns the number of servers currently being queried or queued to be queried. When this number is 0, the query +engine is idle */ +int ServerBrowserPendingQueryCount(ServerBrowser sb); + +/* ServerBrowserGetMyPublicIP +------------------ +Returns the public IP address for this computer, as seen by an outside machine (the master server). Use to determine +if a server you want to connect to is on the same private network or not. Only valid after a call to ServerListUpdate has +connected to the master server */ +char *ServerBrowserGetMyPublicIP(ServerBrowser sb); + +/* ServerBrowserGetMyPublicIPAddr +------------------ +Same as ServerBrowserGetMyPublicIP except that the address is returned in standard network-byte-order form */ +unsigned int ServerBrowserGetMyPublicIPAddr(ServerBrowser sb); + + +/* ServerBrowserSendNatNegotiateCookieToServer +------------------ +Sends a cookie value to the server for use with NAT Negotiation */ +SBError ServerBrowserSendNatNegotiateCookieToServer(ServerBrowser sb, const gsi_char *ip, unsigned short port, int cookie); + + +/* ServerBrowserSendMessageToServer +------------------ +Sends a game-specific message to a server */ +SBError ServerBrowserSendMessageToServer(ServerBrowser sb, const gsi_char *ip, unsigned short port, const char *data, int len); + +/* ServerBrowserConnectToServer +------------------ +Attempts to connect to the server, using natneg if necessary */ +SBError ServerBrowserConnectToServer(ServerBrowser sb, SBServer server, SBConnectToServerCallback callback); + + +/* Comparision types for the ServerBrowserSort function +int - assume the values are int and do an integer compare +float - assume the values are float and do a flot compare +strcase - assume the values are strings and do a case sensitive compare +stricase - assume the values are strings and do a case insensitive compare */ +typedef enum {sbcm_int, sbcm_float, sbcm_strcase, sbcm_stricase} SBCompareMode; + + +/* ServerBrowserSort +----------------- +Sort the server list in either ascending or descending order using the +specified comparemode. +sortkey can be a normal server key, or "ping" or "hostaddr" */ +void ServerBrowserSort(ServerBrowser sb, SBBool ascending, const gsi_char *sortkey, SBCompareMode comparemode); + +/* ServerBrowserLANSetLocalAddr +------------------- +Sets the network adapter to use for LAN broadcasts (optional) */ +void ServerBrowserLANSetLocalAddr(ServerBrowser sb, const char* theAddr); + + +/******************* +SBServer Object Functions +********************/ + + +// Callback function used for enumerating the keys/values for a server +typedef void (*SBServerKeyEnumFn)(gsi_char *key, gsi_char *value, void *instance); + + +/* SBServerGetConnectionInfo +---------------- +Check if Nat Negotiation is requires, based off whether it is a lan game, public ip present and several other facts. +Returns an IP string to use for NatNeg, or direct connect if possible +Work for subsequent connection to this server, One of three results will occur +i) Lan game, connect using ipstring +2) Internet game, connect using ipstring +3) nat traversal required, perform nat negotiation using Nat SDK and this ipstring before connecting. + +return sb_true if further processing is required... i.e. NAT. sb_false if not. +fills an IP string +*/ +SBBool SBServerGetConnectionInfo(ServerBrowser gSB, SBServer server, gsi_u16 PortToConnectTo, char *ipstring_out); + + +/* SBServerHasPrivateAddress +---------------- +Indicates whether the master server has provided a private address for this server */ +SBBool SBServerHasPrivateAddress(SBServer server); + +/* SBServerDirectConnect +---------------- +Indicates whether the server supports direct UDP connections (if false, NAT Negotiation is required) */ +SBBool SBServerDirectConnect(SBServer server); + +/* SBServerGetPing +---------------- +Returns the ping for the specified server. Ping is measured as the latency from the time a +query is sent to the server until the data is returned for that query */ +int SBServerGetPing(SBServer server); + + +/* SBServerGetPublicAddress/SBServerGetPrivateAddress +------------------- +Returns the string, dotted IP address for the specified server +The "private" version is only valid when the server has a private address available */ +char *SBServerGetPublicAddress(SBServer server); +char *SBServerGetPrivateAddress(SBServer server); + +/* SBServerGetPublicInetAddress/SBServerGetPrivateInetAddress +------------------- +Returns the network-ordered IP address for the specified server */ +unsigned int SBServerGetPublicInetAddress(SBServer server); +unsigned int SBServerGetPrivateInetAddress(SBServer server); + +/* SBServerGetPublicQueryPort/SBServerGetPrivateQueryPort +---------------- +Returns the "query" port for the specified server. If the game uses a seperate +"game" port, it can be retrieved via: SBServerGetIntValue(server,"hostport",0) */ +unsigned short SBServerGetPublicQueryPort(SBServer server); +unsigned short SBServerGetPrivateQueryPort(SBServer server); + + +/* SBServerHasBasicKeys +---------------- +Returns SBTrue if a server has at least basic keys available for it. "Basic" keys +are those indicated in the ServerBrowserUpdate function. */ +SBBool SBServerHasBasicKeys(SBServer server); + +/* SBServerHasFullKeys +---------------- +Returns SBTrue if a server has full keys available for it. This includes all server +rules and player/team keys. */ +SBBool SBServerHasFullKeys(SBServer server); + +/* SBServerHasValidPing +---------------- +Returns SBTrue if a server has a valid ping value for it (otherwise the ping will be 0) */ +SBBool SBServerHasValidPing(SBServer server); + + +/* SBServerGet[]Value +------------------ +Returns the value for the specified key. If the key does not exist for the +given server, the default value is returned */ +const gsi_char *SBServerGetStringValue(SBServer server, const gsi_char *keyname, const gsi_char *def); +int SBServerGetIntValue(SBServer server, const gsi_char *key, int idefault); +double SBServerGetFloatValue(SBServer server, const gsi_char *key, double fdefault); +SBBool SBServerGetBoolValue(SBServer server, const gsi_char *key, SBBool bdefault); + + +/* SBServerGetPlayer[]Value / SBServerGetTeam[]Value +------------------ +Returns the value for the specified key on the specified player or team. If the key does not exist for the +given server, the default value is returned +Player keys take the form keyname_N where N is the player index, and team keys take the form +keyname_tN where N is the team index. You should only specify the keyname for the key in the below functions. +*/ +const gsi_char *SBServerGetPlayerStringValue(SBServer server, int playernum, const gsi_char *key, const gsi_char *sdefault); +int SBServerGetPlayerIntValue(SBServer server, int playernum, const gsi_char *key, int idefault); +double SBServerGetPlayerFloatValue(SBServer server, int playernum, const gsi_char *key, double fdefault); + +const gsi_char *SBServerGetTeamStringValue(SBServer server, int teamnum, const gsi_char *key, const gsi_char *sdefault); +int SBServerGetTeamIntValue(SBServer server, int teamnum, const gsi_char *key, int idefault); +double SBServerGetTeamFloatValue(SBServer server, int teamnum, const gsi_char *key, double fdefault); + + +/* SBServerEnumKeys +----------------- +Enumerates the keys/values for a given server by calling KeyEnumFn with each +key/value. The user-defined instance data will be passed to the KeyFn callback */ +void SBServerEnumKeys(SBServer server, SBServerKeyEnumFn KeyFn, void *instance); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/serverbrowsing/sb_serverlist.c b/code/gamespy/serverbrowsing/sb_serverlist.c new file mode 100644 index 00000000..40a1465f --- /dev/null +++ b/code/gamespy/serverbrowsing/sb_serverlist.c @@ -0,0 +1,1816 @@ +#include "sb_serverbrowsing.h" +#include "sb_internal.h" +#include "sb_ascii.h" + + +#define SERVER_GROWBY 100 + +//for the master server info +#define INCOMING_BUFFER_SIZE 4096 + +#define MAX_OUTGOING_REQUEST_SIZE (MAX_FIELD_LIST_LEN + MAX_FILTER_LEN + 255) + + + + + + +static SBServerList *g_sortserverlist; //global serverlist for sorting info!! + + +//private function used to compare the key values based on a previously defined sortkey +static int prevKeyCompare(SBServer server1, SBServer server2) +{ + const char *prevsortkey = (const char *)g_sortserverlist->prevsortinfo.sortkey; + int diff; + double f; + //test which type of sort + switch(g_sortserverlist->prevsortinfo.comparemode) + { + case sbcm_int: + diff = SBServerGetIntValueA(server1, prevsortkey, 0) - + SBServerGetIntValueA(server2, prevsortkey, 0); + break; + case sbcm_float: + f = SBServerGetFloatValueA(server1, prevsortkey, 0) - + SBServerGetFloatValueA(server2, prevsortkey, 0); + if (!g_sortserverlist->sortascending) + f = -f; + if ((float)f > (float)0.0) + return 1; + else if ((float)f < (float)0.0) + return -1; + else + return 0; + //break; + case sbcm_strcase: + diff = strcmp(SBServerGetStringValueA(server1, prevsortkey, ""), + SBServerGetStringValueA(server2, prevsortkey, "")); + break; + case sbcm_stricase: + diff = strcasecmp(SBServerGetStringValueA(server1, prevsortkey, ""), + SBServerGetStringValueA(server2, prevsortkey, "")); + break; + default: + return 0; + } + if (!g_sortserverlist->sortascending) + diff = -diff; + return diff; +} + + +/**** +Comparision Functions +***/ +static int GS_STATIC_CALLBACK IntKeyCompare(const void *entry1, const void *entry2) +{ + SBServer server1 = *(SBServer *)entry1, server2 = *(SBServer *)entry2; + int diff; + const char *currsortkey = (const char *)g_sortserverlist->currsortinfo.sortkey; + + diff = SBServerGetIntValueA(server1, currsortkey, 0) - + SBServerGetIntValueA(server2, currsortkey, 0); + + if (diff == 0) //if equal, sort by previous sort value to retain earlier sort + return prevKeyCompare(server1, server2); + + if (!g_sortserverlist->sortascending) + diff = -diff; + return diff; + +} + +static int GS_STATIC_CALLBACK FloatKeyCompare(const void *entry1, const void *entry2) +{ + SBServer server1 = *(SBServer *)entry1, server2 = *(SBServer *)entry2; + double f; + const char *currsortkey = (const char *)g_sortserverlist->currsortinfo.sortkey; + + f = SBServerGetFloatValueA(server1, currsortkey, 0) - + SBServerGetFloatValueA(server2, currsortkey, 0); + + //if equal, sort by previous sort value to retain earlier sort + if ( !((float)f > (float)0.0) && !((float)f < (float)0.0) ) + return prevKeyCompare(server1, server2); + + if (!g_sortserverlist->sortascending) + f = -f; + if ((float)f > (float)0.0) + return 1; + else if ((float)f < (float)0.0) + return -1; + else + return 0; +} + +static int GS_STATIC_CALLBACK StrCaseKeyCompare(const void *entry1, const void *entry2) +{ + SBServer server1 = *(SBServer *)entry1, server2 = *(SBServer *)entry2; + int diff; + const char *currsortkey = (const char *)g_sortserverlist->currsortinfo.sortkey; + + diff = strcmp(SBServerGetStringValueA(server1, currsortkey, ""), + SBServerGetStringValueA(server2, currsortkey, "")); + + if (diff == 0) //if equal, sort by previous sort value to retain earlier sort + return prevKeyCompare(server1, server2); + + if (!g_sortserverlist->sortascending) + diff = -diff; + return diff; +} + +static int GS_STATIC_CALLBACK StrNoCaseKeyCompare(const void *entry1, const void *entry2) +{ + SBServer server1 = *(SBServer *)entry1, server2 = *(SBServer *)entry2; + int diff; + const char *currsortkey = (const char *)g_sortserverlist->currsortinfo.sortkey; + + diff = strcasecmp(SBServerGetStringValueA(server1, currsortkey, ""), + SBServerGetStringValueA(server2, currsortkey, "")); + + if (diff == 0) //if equal, sort by previous sort value to retain earlier sort + return prevKeyCompare(server1, server2); + + if (!g_sortserverlist->sortascending) + diff = -diff; + return diff; +} + + +/* ServerListSort +----------------- +Sort the server list in either ascending or descending order using the +specified comparemode. +sortkey can be a normal server key, or "ping" or "hostaddr" */ +void SBServerListSort(SBServerList *slist, SBBool ascending, SortInfo sortinfo) +{ + ArrayCompareFn comparator; + switch (sortinfo.comparemode) + { + case sbcm_int: comparator = IntKeyCompare; + break; + case sbcm_float: comparator = FloatKeyCompare; + break; + case sbcm_strcase: comparator = StrCaseKeyCompare; + break; + case sbcm_stricase: comparator = StrNoCaseKeyCompare; + break; + default: + comparator = StrNoCaseKeyCompare; + } + + //set last used sortkey + if (_tcslen(slist->prevsortinfo.sortkey) == 0) //set prev to current if not initialized + slist->prevsortinfo = sortinfo; + else if (strcmp((const char *)sortinfo.sortkey, (const char *)slist->currsortinfo.sortkey) != 0) + slist->prevsortinfo = slist->currsortinfo; //only set new sort if different than prev + + slist->currsortinfo = sortinfo; + slist->sortascending = ascending; + g_sortserverlist = slist; + ArraySort(slist->servers,comparator); +} + + + + +void SBServerListAppendServer(SBServerList *slist, SBServer server) +{ + ArrayAppend(slist->servers, &server); + slist->ListCallback(slist, slc_serveradded, server, slist->instance); +} + + + +int SBServerListFindServer(SBServerList *slist, SBServer findserver) +{ + int numservers; + int i; + numservers = ArrayLength(slist->servers); + for (i = 0 ; i < numservers ; i++) + { + if (findserver == *(SBServer *)ArrayNth(slist->servers, i)) + { + return i; + } + } + return -1; +} + +int SBServerListFindServerByIP(SBServerList *slist, goa_uint32 ip, unsigned short port) +{ + int numservers; + SBServer server; + int i; + numservers = ArrayLength(slist->servers); + for (i = 0 ; i < numservers ; i++) + { + server = *(SBServer *)ArrayNth(slist->servers, i); + if (SBServerGetPublicInetAddress(server) == ip && SBServerGetPublicQueryPortNBO(server) == port) + { + return i; + } + } + return -1; +} + +//add to the singly linked list of dead servers +static void AddServerToDeadlist(SBServerList *slist, SBServer server) +{ + if (slist->deadlist == NULL) + { + SBServerSetNext(server, NULL); + } else + SBServerSetNext(server, slist->deadlist); + slist->deadlist = server; +} + + +void SBServerListRemoveAt(SBServerList *slist, int index) +{ + SBServer server = *(SBServer *)ArrayNth(slist->servers, index); + slist->ListCallback(slist, slc_serverdeleted, server, slist->instance); + //need to remove it... + ArrayDeleteAt(slist->servers, index); + //now add it to the dead list + AddServerToDeadlist(slist, server); +} + +int SBServerListCount(SBServerList *slist) +{ + return ArrayLength(slist->servers); +} + +SBServer SBServerListNth(SBServerList *slist, int i) +{ + return *(SBServer *)ArrayNth(slist->servers, i); +} + + +void SBFreeDeadList(SBServerList *slist) +{ + SBServer server, next; + if (slist->deadlist == NULL) + return; + server = slist->deadlist; + while (server != NULL) + { + next = SBServerGetNext(server); + SBServerFree(&server); + server = next; + } + slist->deadlist = NULL; +} + + +void SBServerListClear(SBServerList *slist) +{ + //we need to add each server to the dead list so it can be freed after the clear + int i; + int nservers = ArrayLength(slist->servers); + for (i = 0 ; i < nservers ; i++) + { + AddServerToDeadlist(slist, *(SBServer *)ArrayNth(slist->servers, i)); + } + ArrayClear(slist->servers); + //now free the dead list + SBFreeDeadList(slist); +} + +void SBAllocateServerList(SBServerList *slist) +{ + slist->servers = ArrayNew(sizeof(SBServer ), SERVER_GROWBY, NULL); //don't free the server automatically - it goes into the dead list + slist->deadlist = NULL; +} + + + +const char *SBRefStr(SBServerList *slist, const char *str) +{ + SBRefString ref, *val; + ref.str = str; + val = (SBRefString *)TableLookup(SBRefStrHash(slist), &ref); + if (val != NULL) + { + val->refcount++; + return val->str; + } + + //else we need to add. + ref.str = goastrdup(str); + ref.refcount = 1; +#ifdef GSI_UNICODE + ref.str_W = UTF8ToUCS2StringAlloc(str); +#endif + TableEnter(SBRefStrHash(slist), &ref); + return ref.str; + + +} + +void SBReleaseStr(SBServerList *slist, const char *str) +{ + SBRefString ref, *val; + ref.str = str; + val = (SBRefString *)TableLookup(SBRefStrHash(slist), &ref); + assert(val != NULL); + if (val == NULL) + return; //not found! + val->refcount--; + if (val->refcount == 0) + TableRemove(SBRefStrHash(slist), &ref); +} + +#ifdef VENGINE_SUPPORT + #define FTABLE_ASSIGN + #include "../../VEngine/ve_gm3ftable.h" +#endif + + +#ifdef VENGINE_SUPPORT +#define FTABLE_DEFINES +#include "../../VEngine/ve_gm3ftable.h" +#endif + + + +//global pointer to alternate master server name/IP +char *SBOverrideMasterServer = NULL; + + +int NTSLengthSB(char *buf, int len) +{ + int i; + for (i = 0 ; i < len ; i++) + { + if (buf[i] == '\0') //found a full NTS + return i + 1; //return the length including the null + } + return -1; +} + + + +void SBServerListInit(SBServerList *slist, const char *queryForGamename, const char *queryFromGamename, const char *queryFromKey, int queryFromVersion, SBBool lanBrowse, SBListCallBackFn callback, void *instance) +{ + assert(slist != NULL); + // 11-03-2004 : Added by Saad Nader + // fix for LANs and unnecessary availability check + /////////////////////////////////////////////////// + if (lanBrowse == SBFalse) + { + if(__GSIACResult != GSIACAvailable) + return; + } + +#ifdef VENGINE_SUPPORT + SBServerListSetPointers(slist); +#endif + + slist->state = sl_disconnected; + SBAllocateServerList(slist); + SBRefStrHash(slist); //make sure it's initialized + strcpy(slist->queryforgamename, queryForGamename); + strcpy(slist->queryfromgamename, queryFromGamename); + strcpy(slist->queryfromkey, queryFromKey); + slist->ListCallback = callback; + slist->MaploopCallback = NULL; //populate when requested + assert(callback != NULL); + slist->instance = instance; + slist->mypublicip = 0; + slist->slsocket = INVALID_SOCKET; + slist->inbuffer = NULL; + slist->inbufferlen = 0; + slist->keylist = NULL; + slist->expectedelements = -1; + slist->numpopularvalues = 0; + slist->srcip = 0; + slist->fromgamever = queryFromVersion; +#ifdef GSI_UNICODE + slist->lasterror = NULL; + slist->lasterror_W = NULL; + _tcscpy(slist->currsortinfo.sortkey, (const unsigned short *)""); + _tcscpy(slist->prevsortinfo.sortkey, (const unsigned short *)""); +#else + _tcscpy(slist->currsortinfo.sortkey, ""); + _tcscpy(slist->prevsortinfo.sortkey, ""); +#endif + SBSetLastListErrorPtr(slist, ""); + slist->mLanAdapterOverride = NULL; + slist->backendgameflags = QR2_USE_QUERY_CHALLENGE; + + srand((unsigned int)current_time()); + SocketStartUp(); + +} + + +static void ErrorDisconnect(SBServerList *slist) +{ + static char* QUERY_ERROR = "Query Error: "; + + // check to see if there is a Query Error string in the inbuffer + if ((slist->inbufferlen > 0) && ((unsigned int)slist->inbufferlen > strlen(QUERY_ERROR)) && + (0 == strncmp(slist->inbuffer, QUERY_ERROR, strlen(QUERY_ERROR)))) + { + // call the callback with queryerror first + SBSetLastListErrorPtr(slist, slist->inbuffer + strlen(QUERY_ERROR)); + slist->ListCallback(slist, slc_queryerror, SBNullServer, slist->instance); + } + + //call the callback.. + slist->ListCallback(slist, slc_disconnected, SBNullServer, slist->instance); + SBServerListDisconnect(slist); +} + +#define MULTIPLIER -1664117991 +static int StringHash(const char *s, int numbuckets) +{ + goa_uint32 hashcode = 0; + while (*s != 0) + { + hashcode = (goa_uint32)((int)hashcode * MULTIPLIER + tolower(*s)); + s++; + } + return (int)(hashcode % numbuckets); + +} + + + +static SBError ServerListConnect(SBServerList *slist) +{ + struct sockaddr_in saddr; + struct hostent *hent; + char masterHostname[128]; + int masterIndex; + + + masterIndex = StringHash(slist->queryforgamename, NUM_MASTER_SERVERS); + if (SBOverrideMasterServer != NULL) + strcpy(masterHostname, SBOverrideMasterServer); + else //use the default format... + sprintf(masterHostname,"%s.ms%d." GSI_DOMAIN_NAME, slist->queryforgamename, masterIndex); + saddr.sin_family = AF_INET; + saddr.sin_port = htons(MSPORT2); + saddr.sin_addr.s_addr = inet_addr(masterHostname); + if (saddr.sin_addr.s_addr == INADDR_NONE) + { + hent = gethostbyname(masterHostname); + if (!hent) + return sbe_dnserror; + memcpy(&saddr.sin_addr.s_addr,hent->h_addr_list[0], sizeof(saddr.sin_addr.s_addr)); + } + + if (slist->slsocket == INVALID_SOCKET) + { + slist->slsocket = socket ( AF_INET, SOCK_STREAM, IPPROTO_TCP ); + if (slist->slsocket == INVALID_SOCKET) + return sbe_socketerror; + } + if (connect ( slist->slsocket, (struct sockaddr *) &saddr, sizeof saddr ) != 0) + { + closesocket(slist->slsocket); + slist->slsocket = INVALID_SOCKET; + return sbe_connecterror; + } + + //else we are connected + return sbe_noerror; + +} + +static void BufferAddNTS(char **buffer, const char *str, int *len) +{ + int slen; + if (str == NULL) + str = ""; + slen = (int)strlen(str) + 1; + memcpy(*buffer, str, (size_t)slen); + (*len) += slen; + (*buffer) += slen; +} + +static void BufferAddByte(char **buffer, unsigned char bval, int *len) +{ + *(*buffer) = (char)bval; + (*len) += 1; + (*buffer) += 1; +} + +static void BufferAddInt(char **buffer, int ival, int *len) +{ + memcpy(*buffer, &ival, 4); + (*len) += 4; + (*buffer) += 4; +} + +// This a utility to write ival as a little-endian (PC) byte order number +static void BufferAddIntLE(char **buffer, int ival, int *len) +{ + unsigned int ivalNBO = htonl(ival); + unsigned int ivalBE = 0; + ivalBE |= ((0x000000FF&ivalNBO)<<24); + ivalBE |= ((0x0000FF00&ivalNBO)<< 8); + ivalBE |= ((0x00FF0000&ivalNBO)>> 8); + ivalBE |= ((0xFF000000&ivalNBO)>>24); + BufferAddInt(buffer, (int)ivalBE, len); +} + + +static void BufferAddData(char **buffer, char *data, int dlen, int *len) +{ + memcpy(*buffer, data, (size_t)dlen); + (*len) += dlen; + (*buffer) += dlen; +} + + + +#define CALCULATEODDMODE(buffer, i, oddmode) ((buffer[i-1] & 1) ^ (i & 1) ^ oddmode ^ (buffer[0] & 1) ^ ((buffer[0] < 79) ? 1 : 0) ^ ((buffer[i-1] < buffer[0]) ? 1 : 0)); +static void SetupListChallenge(SBServerList *slist) +{ + int i; + int oddmode; + + slist->mychallenge[0] = (char)(33 + rand() % 93); //use chars in the range 33 - 125 + oddmode = 0; + for (i = 1; i < LIST_CHALLENGE_LEN ; i++) + { + oddmode = CALCULATEODDMODE(slist->mychallenge,i, oddmode); + slist->mychallenge[i] = (char)(33 + rand() % 93); //use chars in the range 33 - 125 + //if oddmode make sure the char is odd, otherwise make sure it's even + if ((oddmode && (slist->mychallenge[i] & 1) == 0) || (!oddmode && ((slist->mychallenge[i] & 1) == 1))) + slist->mychallenge[i]++; + + } +} + +static SBError SendWithRetry(SBServerList *slist, char *data, int len) +{ + SBError err; + int ret = 0; + + int retryCount = 1; + + while (retryCount >= 0) + { + retryCount--; + ret = send(slist->slsocket, data, len, 0); + if (ret <= 0 && retryCount >= 0) //error! try to reconnect + { + if (slist->inbufferlen > 0) + break; + else + SBServerListDisconnect(slist); + err = SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0); + if (err != sbe_noerror) + { + ErrorDisconnect(slist); + return err; //couldn't reconnect + } + } else + break; //send ok + } + +#ifdef INSOCK + GSI_UNUSED(data); + GSI_UNUSED(len); +#endif + + if (ret <= 0) + return sbe_connecterror; + else + return sbe_noerror; +} + + + +/* +1 bytes - message type (0) +2 bytes - message length +Protocol Version - Byte +Requested Encoding - Byte +Query For Game - NTS +Query From Game - NTS +Server Challenge - 8 bytes +Filter - NTS +Field List - NTS +Options - 4 bytes +Send Fields for non-NAT servers too (TurboQuery!) +No-server-slist-requested (just initiate connection and/or receive push updates) +Push Live-Updates +Log Alternate Source IP +*/ +#define ALTERNATE_SOURCE_IP 8 + +SBError SBServerListConnectAndQuery(SBServerList *slist, const char *fieldList, const char *serverFilter, int options, int maxServers) +{ + SBError err; + int ret; + int requestLen; + unsigned short netLen; + char *requestBuf; + char outgoingRequest[MAX_OUTGOING_REQUEST_SIZE + 1]; + assert(slist->state == sl_disconnected); + + + if (fieldList == NULL) + fieldList = ""; + if (serverFilter == NULL) + serverFilter = ""; + + if (strlen(fieldList) > MAX_FIELD_LIST_LEN) + return sbe_paramerror; + + if (strlen(serverFilter) > MAX_FILTER_LEN) + return sbe_paramerror; + + + err = ServerListConnect(slist); + if (err != sbe_noerror) + return err; + + slist->queryoptions = options; + + //setup our challenge value + SetupListChallenge(slist); + + //skip the length field for now + requestLen = 2; + requestBuf = outgoingRequest + 2; + + BufferAddByte(&requestBuf, SERVER_LIST_REQUEST, &requestLen); + BufferAddByte(&requestBuf, LIST_PROTOCOL_VERSION, &requestLen); + BufferAddByte(&requestBuf, LIST_ENCODING_VERSION, &requestLen); + BufferAddIntLE(&requestBuf, slist->fromgamever, &requestLen); + BufferAddNTS(&requestBuf, slist->queryforgamename, &requestLen); + BufferAddNTS(&requestBuf, slist->queryfromgamename, &requestLen); + BufferAddData(&requestBuf, slist->mychallenge, LIST_CHALLENGE_LEN, &requestLen); + BufferAddNTS(&requestBuf, serverFilter, &requestLen); + BufferAddNTS(&requestBuf, fieldList, &requestLen); + options = (int)htonl((unsigned int)options); + BufferAddInt(&requestBuf, options, &requestLen); + if (slist->queryoptions & ALTERNATE_SOURCE_IP) + { + BufferAddInt(&requestBuf, (int)slist->srcip, &requestLen); + } + if (slist->queryoptions & LIMIT_RESULT_COUNT) + { + BufferAddIntLE(&requestBuf, maxServers, &requestLen); + } + netLen = htons((unsigned short)requestLen); + memcpy(outgoingRequest, &netLen, 2); //set the length... + + //now send! + ret = send(slist->slsocket, outgoingRequest, requestLen, 0); + if (ret <= 0) + { + SBServerListDisconnect(slist); + return sbe_connecterror; + } + slist->state = sl_mainlist; + slist->pstate = pi_cryptheader; + //allocate an incoming buffer + if (slist->inbuffer == NULL) + { + slist->inbuffer = (char *)gsimalloc(INCOMING_BUFFER_SIZE); + if (slist->inbuffer == NULL) + return sbe_allocerror; + slist->inbufferlen = 0; + } + + + return sbe_noerror; +} + +SBError SBServerListGetLANList(SBServerList *slist, unsigned short startport, unsigned short endport, int queryversion) +{ + struct sockaddr_in saddr; + unsigned short i; + unsigned char qr2_echo_request[] = {QR2_MAGIC_1, QR2_MAGIC_2, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}; + int qr2requestlen = sizeof(qr2_echo_request) / sizeof(qr2_echo_request[0]); + if (slist->state != sl_disconnected) + SBServerListDisconnect(slist); + + slist->slsocket = socket ( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); + if (slist->slsocket == INVALID_SOCKET) + return sbe_socketerror; + +// enable broadcasting where needed +#if !defined(INSOCK) && !defined(_NITRO) && !defined(_REVOLUTION) + { + int optval = 1; + if (setsockopt(slist->slsocket, SOL_SOCKET, SO_BROADCAST, (char *)&optval, sizeof(optval)) != 0) + return sbe_socketerror; + } +#endif + + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = gsiGetBroadcastIP(); //broadcast + + // Added for multihomed machines. Allows the user to specify which adapter to use + if (slist->mLanAdapterOverride != NULL) + { + struct sockaddr_in aClassCAddr; + aClassCAddr.sin_family = AF_INET; + aClassCAddr.sin_addr.s_addr = inet_addr(slist->mLanAdapterOverride); + aClassCAddr.sin_port = 0; // bind to any port + if (0 != bind(slist->slsocket, (struct sockaddr*)&aClassCAddr, sizeof(aClassCAddr))) + return sbe_socketerror; + } + + if (endport - startport > 500) //the max we will search + endport = (unsigned short)(startport + 500); + for (i = startport ; i <= endport ; i += 1) + { + saddr.sin_port = htons(i); + if (queryversion == QVERSION_QR2) //send a QR2 echo request + sendto(slist->slsocket, (char*)qr2_echo_request,qr2requestlen,0,(struct sockaddr *)&saddr,sizeof(saddr)); + else //send a GOA echo request + sendto(slist->slsocket, "\\echo\\test",10,0,(struct sockaddr *)&saddr,sizeof(saddr)); + } + slist->state = sl_lanbrowse; + slist->lanstarttime = current_time(); + return sbe_noerror; +} + +static void FreePopularValues(SBServerList *slist) +{ + int i; + for (i = 0 ; i < slist->numpopularvalues ; i++) + SBReleaseStr(slist, slist->popularvalues[i]); + slist->numpopularvalues = 0; +} + +static void FreeKeyList(SBServerList *slist) +{ + int i; + if (slist->keylist == NULL) + return; + for (i = 0 ; i < ArrayLength(slist->keylist) ; i++) + SBReleaseStr(slist, ((KeyInfo *)ArrayNth(slist->keylist,i))->keyName); + + ArrayFree(slist->keylist); + slist->keylist = NULL; + +} + +void SBServerListDisconnect(SBServerList *slist) +{ + if (slist->inbuffer != NULL) + gsifree(slist->inbuffer); + slist->inbuffer = NULL; + slist->inbufferlen = 0; + if (slist->slsocket != INVALID_SOCKET) + closesocket(slist->slsocket); + slist->slsocket = INVALID_SOCKET; + slist->state = sl_disconnected; + + FreeKeyList(slist); + slist->expectedelements = -1; + FreePopularValues(slist); +} + +void SBServerListCleanup(SBServerList *slist) +{ + SBServerListDisconnect(slist); + SBServerListClear(slist); + SBRefStrHashCleanup(slist); + if(slist->servers) + ArrayFree(slist->servers); + slist->servers = NULL; + +#ifdef GSI_UNICODE + if (slist->lasterror_W != NULL) + { + gsifree(slist->lasterror_W); + slist->lasterror_W = NULL; + } +#endif +} + + + +static void InitCryptKey(SBServerList *slist, char *key, int keylen) +{ + //combine our secret key, our challenge, and the server's challenge into a crypt key + int i; + int seckeylen = (int)strlen(slist->queryfromkey); + char *seckey = slist->queryfromkey; + for (i = 0 ; i < keylen ; i++) + { + slist->mychallenge[(i * seckey[i % seckeylen]) % LIST_CHALLENGE_LEN] ^= (char)((slist->mychallenge[i % LIST_CHALLENGE_LEN] ^ key[i]) & 0xFF); + } + GOACryptInit(&(slist->cryptkey), (unsigned char *)(slist->mychallenge), LIST_CHALLENGE_LEN); + +} + + +static int ServerSizeForFlags(int flags) +{ + int size = 5; //all servers are at least 5 .. + if (flags & PRIVATE_IP_FLAG) + size += 4; + if (flags & ICMP_IP_FLAG) + size += 4; + if (flags & NONSTANDARD_PORT_FLAG) + size += 2; + if (flags & NONSTANDARD_PRIVATE_PORT_FLAG) + size += 2; + return size; + +} + + +static int FullRulesPresent(char *buf, int len) +{ + int i; + while (len > 0 && *buf) + { + i = NTSLengthSB(buf, len); + if (i < 0) + return 0; //no full key + buf += i; + len -= i; + i = NTSLengthSB(buf, len); + if (i < 0) + return 0; //no full value + buf += i; + len -= i; + } + if (len == 0) + return 0; //not even enough space for the null term + if (*buf == 0) + return 1; //all there + return 0; +} + +//checks to see if all the keys for the given servers are there +static int AllKeysPresent(SBServerList *slist, char *buf, int len) +{ + int numkeys; + int i; + int strindex; + numkeys = ArrayLength(slist->keylist); + for (i = 0 ; i < numkeys ; i++) + { + switch (((KeyInfo *)ArrayNth(slist->keylist, i))->keyType) + { + case KEYTYPE_BYTE: + buf++; + len--; + break; + case KEYTYPE_SHORT: + buf += 2; + len -= 2; + break; + case KEYTYPE_STRING: + if (len < 1) + return 0; //not enough + strindex = (unsigned char)(buf[0]); + buf++; + len--; + if (strindex == 0xFF) //a NTS string + { + strindex = NTSLengthSB(buf, len); + if (strindex == -1) //not all there.. + return 0; + buf += strindex; + len -= strindex; + } //else it's a popular string - just the index is present + break; + default: + assert(0); + return 0; //error - unknown key type + } + if (len < 0) + return 0; //not enough.. + } + return 1; +} + +#define LAST_SERVER_MARKER "\xFF\xFF\xFF\xFF" +#define SERVER_MARKER_LEN 4 + +//parse only the IP/port from the server buffer +static void ParseServerIPPort(SBServerList *slist, char *buf, int len, goa_uint32 *ip, unsigned short *port) +{ + unsigned char flags; + if (len < 5) + return; //invalid buffer + flags = (unsigned char)buf[0]; + buf++; + len--; + memcpy(ip, buf, 4); + + buf += 4; + len -= 4; + + if (flags & NONSTANDARD_PORT_FLAG) + { + if (len < 2) + return; //invalid buffer + memcpy(port, buf, 2); + } else + *port = slist->defaultport; +} + + + + +//parse a server buffer +static int ParseServer(SBServerList *slist, SBServer server, char *buf, int len, int usepopularlist) +{ + int numkeys; + int i; + short sval; + int strindex; + int holdlen = len; + goa_uint32 ip; + unsigned short port; + unsigned char flags; + + + flags = (unsigned char)buf[0]; + SBServerSetFlags(server, flags); + buf++; + len--; + + //skip the IP, it's already set + buf += 4; + len -= 4; + + //skip the port, if needed + if (flags & NONSTANDARD_PORT_FLAG) + { + buf += 2; + len -= 2; + } + + if (flags & PRIVATE_IP_FLAG) + { + memcpy(&ip, buf, 4); + buf += 4; + len -= 4; + } else + ip = 0; + if (flags & NONSTANDARD_PRIVATE_PORT_FLAG) + { + memcpy(&port, buf, 2); + buf += 2; + len -= 2; + } else + port = slist->defaultport; + SBServerSetPrivateAddr(server, ip, port); + if (flags & ICMP_IP_FLAG) + { + memcpy(&ip, buf, 4); + buf += 4; + len -= 4; + SBServerSetICMPIP(server, ip); + } + + if (flags & HAS_KEYS_FLAG) //parse the keys + { + numkeys = ArrayLength(slist->keylist); + for (i = 0 ; i < numkeys ; i++) + { + KeyInfo *ki = (KeyInfo *)ArrayNth(slist->keylist, i); + switch (ki->keyType) + { + case KEYTYPE_BYTE: + SBServerAddIntKeyValue(server, ki->keyName, (unsigned char)buf[0]); + buf++; + len--; + break; + case KEYTYPE_SHORT: + memcpy(&sval, buf, 2); + SBServerAddIntKeyValue(server, ki->keyName, ntohs((unsigned short)sval)); + buf += 2; + len -= 2; + break; + case KEYTYPE_STRING: + if (usepopularlist) + { + strindex = (unsigned char)(buf[0]); + buf++; + len--; + } else + strindex = 0xFF; + if (strindex == 0xFF) //a NTS string + { + SBServerAddKeyValue(server, ki->keyName, buf); + strindex = (int)strlen(buf) + 1; + buf += strindex; + len -= strindex; + } else //else it's a popular string - just the index is present + { + SBServerAddKeyValue(server, ki->keyName, slist->popularvalues[strindex]); + } + break; + } + + } + SBServerSetState(server, (unsigned char)(SBServerGetState(server) | STATE_BASICKEYS)); + } + if (flags & HAS_FULL_RULES_FLAG) //got the full rules in there + { + while (*buf && len > 0) + { + char *k = buf; + i = (int)strlen(k) + 1; + buf += i; + len -= i; + SBServerAddKeyValue(server, k, buf); + i = (int)strlen(buf) + 1; + buf += i; + len -= i; + } + buf++; + len--; //get the last null out + SBServerSetState(server, (unsigned char)(SBServerGetState(server) | STATE_FULLKEYS)); + } + + // the server was an empty server message, thus it should be cleared of + // basic key and full key states + { + unsigned char state = SBServerGetState(server); + if ((flags & (HAS_FULL_RULES_FLAG|HAS_KEYS_FLAG)) == 0 && ((state & STATE_BASICKEYS) | (state & STATE_FULLKEYS))) + { + state &= (unsigned char)~(STATE_BASICKEYS|STATE_FULLKEYS); + SBServerSetState(server, state); + } + } + return holdlen - len; +} + + +static int IncomingListParseServer(SBServerList *slist, char *buf, int len) +{ + int i; + goa_uint32 ip; + unsigned short port; + SBServer server; + unsigned char flags; + //fields depends on the flags.. + if (len < 1) + return 0; + flags = (unsigned char)(buf[0]); + i = ServerSizeForFlags(flags); + if (len < i) + return 0; + if (flags & HAS_KEYS_FLAG) + { + if (!AllKeysPresent(slist, buf + i, len - i)) + return 0; + } + if (flags & HAS_FULL_RULES_FLAG) + { + if (!FullRulesPresent(buf + i, len - i)) + return 0; + } + //else we have a whole server! + //see if it's the "last" server (0xffffffff) + if (memcmp(buf + 1, LAST_SERVER_MARKER, SERVER_MARKER_LEN) == 0) + return -1; + ParseServerIPPort(slist, buf, len, &ip, &port); + server = SBAllocServer(slist, ip, port); + if (SBIsNullServer(server)) + return -2; + i = ParseServer(slist, server, buf, len, 1); + SBServerListAppendServer(slist, server); + return i; +} + +const char *SBLastListErrorA(SBServerList *slist) +{ + return slist->lasterror; +} +#ifdef GSI_UNICODE +const unsigned short *SBLastListErrorW(SBServerList *slist) +{ + return slist->lasterror_W; +} +#endif +void SBSetLastListErrorPtr(SBServerList *slist, char* theError) +{ +#ifdef GSI_UNICODE + if (slist->lasterror != theError) + { + // set the ascii error + slist->lasterror = theError; + + // free the current unicode error and allocate a new one + if (slist->lasterror_W != NULL) + gsifree(slist->lasterror_W); + slist->lasterror_W = UTF8ToUCS2StringAlloc(slist->lasterror); + } +#else + slist->lasterror = theError; +#endif +} + +#define FIXED_HEADER_LEN 6 + +/* +--challenge header +1 byte - number of random bytes to follow xor 0xEC +random bytes +1 byte - length xor 0xEA +keylen bytes - server challenge +--fixed header +YOUR public IP - 4 bytes +Standard query port - 2 bytes - or 0xFFFF if the master does not know about this gamename yet +[rest only follows if they requested a server slist] +--key slist +Number of Keys - 1 byte +List of Keys - with key type +Key Type - 1 Byte (0 = string, 1 = byte, 2 = short) +KeyName - NTS +--unique value slist +number of unique string values - 1 byte +Table of Unique String Values (255 most popular string keys) +Key NTS +--servers +Server Table (for each server - terminated with IP of 0xFFFFFFFF, flags 0) +1 byte flags +-Connect negotiate support? +-Has private IP +-Unsolicited UDP support? +-Has ICMP IP +-Nonstandard Public Port +-Nonstandard Private Port +-Has Keys +4 bytes public ip +[optional] +4 bytes port +[optional] +4 bytes private IP +[optional] +4 bytes private port +[optional] +4 bytes ICMP IP +[optional - keys] +String values +1-bytes - value ID +or +FF + valuestring + 00 +Byte Values + 1 byte - value + Short values? + 2 bytes - value +*/ + +static SBError ProcessMainListData(SBServerList *slist) +{ + int reqlen; + int keyoffset; + int keylen; + char *inbuf = slist->inbuffer; + int inlen = slist->inbufferlen; + switch (slist->pstate) + { + case pi_cryptheader: + if (inlen < 1) + break; + reqlen = (((unsigned char)(inbuf[0])) ^ 0xEC) + 2; + if (inlen < reqlen) + break; //not there yet + keyoffset = reqlen; + keylen = ((unsigned char)(inbuf[reqlen - 1])) ^ 0xEA; + reqlen += keylen; + if (inlen < reqlen) + break; //not there yet + //otherwise we have the whole crypt header and can init our crypt key + InitCryptKey(slist, inbuf + keyoffset, keylen); + slist->pstate = pi_fixedheader; + + // the first byte of the "random" data is actually + // the game options flag + memcpy(&slist->backendgameflags, &inbuf[1], 2); + slist->backendgameflags = ntohs(slist->backendgameflags); + + inbuf += reqlen; + inlen -= reqlen; + //decrypt any remaining data! + GOADecrypt(&(slist->cryptkey), (unsigned char *)inbuf, inlen); + //and fall through + case pi_fixedheader: + if (inlen < FIXED_HEADER_LEN) + break; + memcpy(&slist->mypublicip, inbuf, 4); + slist->ListCallback(slist, slc_publicipdetermined, SBNullServer, slist->instance); + memcpy(&slist->defaultport, inbuf + 4, 2); + if (slist->defaultport == 0xFFFF) //there was an error- grab the error string if present.. + { + if (NTSLengthSB(inbuf + FIXED_HEADER_LEN, inlen - FIXED_HEADER_LEN) == -1) //not all there + break; + SBSetLastListErrorPtr(slist, inbuf + FIXED_HEADER_LEN); + //make the error callback + slist->ListCallback(slist, slc_queryerror, SBNullServer, slist->instance); + if (slist->inbuffer == NULL) + break; + } + inbuf += FIXED_HEADER_LEN; + inlen -= FIXED_HEADER_LEN; + if ((slist->queryoptions & NO_SERVER_LIST) || slist->defaultport == 0xFFFF) + { + slist->pstate = pi_finished; + slist->state = sl_connected; + break; //no more data expected (if we didn't request any more, or the server doesn't know about this game) + } + slist->pstate = pi_keylist; + slist->expectedelements = -1; + + //and fall through + case pi_keylist: + if (slist->expectedelements == -1) //we haven't read the initial count of keys yet.. + { + if (inlen < 1) + break; + slist->expectedelements = (unsigned char)(inbuf[0]); + slist->keylist = ArrayNew(sizeof(KeyInfo), slist->expectedelements, NULL); + if (slist->keylist == NULL) //error + return sbe_allocerror; + inbuf++; + inlen--; + } + //try to read elements, up to the expected amount... + while (slist->expectedelements > ArrayLength(slist->keylist)) + { + KeyInfo ki; + int keylen; + if (inlen < 2) + break; //can't possibly be a full key (keytype + string) + keylen = NTSLengthSB(inbuf + 1, inlen - 1); + if (keylen == -1) + break; //no full NTS string there + ki.keyType = (unsigned char)(inbuf[0]); + ki.keyName = SBRefStr(slist, inbuf + 1); + ArrayAppend(slist->keylist, &ki); + inbuf += keylen + 1; + inlen -= keylen + 1; + } + if (slist->expectedelements > ArrayLength(slist->keylist)) + break; //didn't read them all... + //else we have read all the keys, fall through.. + slist->pstate = pi_uniquevaluelist; + slist->expectedelements = -1; + case pi_uniquevaluelist: + if (slist->expectedelements == -1) //we haven't read the # of unique values yet.. + { + if (inlen < 1) + break; + slist->expectedelements = (unsigned char)(inbuf[0]); + slist->numpopularvalues = 0; + inbuf++; + inlen--; + } + while (slist->expectedelements > slist->numpopularvalues) + { + int keylen = NTSLengthSB(inbuf, inlen); + if (keylen == -1) + break; //no full NTS string + slist->popularvalues[slist->numpopularvalues++] = SBRefStr(slist, inbuf); + inbuf += keylen; + inlen -= keylen; + } + if (slist->expectedelements > slist->numpopularvalues) + break; //didn't read all the popular values + //else we've got them all - move on to servers! + slist->pstate = pi_servers; + case pi_servers : + if (inlen < 5) //not enough for a full servers + break; + do + { + reqlen = IncomingListParseServer(slist, inbuf, inlen); + if (reqlen == -2) + return sbe_allocerror; + else if (reqlen == -1) //that was the last server! + { + inlen -= 5; + inbuf += 5; + slist->pstate = pi_finished; + slist->state = sl_connected; + slist->ListCallback(slist, slc_initiallistcomplete, SBNullServer, slist->instance); + break; + } + inbuf += reqlen; + inlen -= reqlen; + if (slist->inbuffer == NULL) + reqlen = 0; //break out - they disconnected + } while (reqlen != 0); + break; + default: + break; + } + assert(inlen >= 0); + if (slist->inbuffer == NULL) + return sbe_noerror; //don't keep processing - they disconnected + if (inlen != 0) //need to shift it over.. + { + memmove(slist->inbuffer, inbuf, (size_t)inlen); + } + slist->inbufferlen = inlen; + //we could clear the key list here if we wanted + + return sbe_noerror; +} + +/* + Number of Keys - Byte + List of Keys - with key type + Key Type - 1 Byte (0 = string, 1 = byte, 2 = short) + KeyName - NTS +*/ +static SBError ProcessPushKeyList(SBServerList *slist, char *buf, int len) +{ + int i; + int numkeys; + //first byte is the # of keys + numkeys = (unsigned char)buf[0]; + buf++; + len--; + //set up our key list... + if (slist->keylist != NULL) //free the existing key list + { + FreeKeyList(slist); + } + slist->keylist = ArrayNew(sizeof(KeyInfo), numkeys, NULL); + if (slist->keylist == NULL) + return sbe_allocerror; + for (i = 0 ; i < numkeys ; i++) + { + int keylen; + KeyInfo ki; + if (len < 2) + return sbe_dataerror; //can't possibly be a full key (keytype + string) + keylen = NTSLengthSB(buf + 1, len - 1); + if (keylen == -1) + return sbe_dataerror; //no full NTS string there + ki.keyType = (unsigned char)(buf[0]); + ki.keyName = SBRefStr(slist, buf + 1); + ArrayAppend(slist->keylist, &ki); + buf += keylen + 1; + len -= keylen + 1; + } + return sbe_noerror; +} + + +/* +Is Final Packet - Byte +Result Count - 1 bytes (up to 255 per packet) +Results + Player Name - NTS + 6 bytes - server addr + 4 bytes - last seen time (UTC Unix time) + gamename - NTS (empty if they are searching for only a single game) +*/ + +static SBError ProcessPlayerSearch(SBServerList *slist, char *buf, int len) +{ + unsigned char isFinal; + unsigned char resultCount; + unsigned int ip; + unsigned short port; + char *nick; + time_t lastSeen; + int i; + + if (len < 2) + return sbe_dataerror; //not a full packet + isFinal = (unsigned char)buf[0]; + resultCount = (unsigned char)buf[1]; + buf += 2; + len -= 2; + for (i = 0 ; i < resultCount ; i++) + { + int slen; + nick = buf; + slen = NTSLengthSB(buf, len); + if (slen == -1) + return sbe_dataerror; + buf += slen; + len -= slen; + if (len < 11) + return sbe_dataerror; //not a full entry + memcpy(&ip, buf, 4); + memcpy(&port, buf + 4, 2); + memcpy(&lastSeen, buf + 6, 4); + lastSeen = (time_t)ntohl((unsigned long)lastSeen); + buf += 10; + len -= 10; + slen = NTSLengthSB(buf, len); + if (slen == -1) + return sbe_dataerror; //not a valid gamename + slist->PlayerSearchCallback(slist, nick, ip, port, lastSeen, buf, slist->instance); + //now skip the gamename + buf += slen; + len -= slen; + } + if (isFinal) //send a final callback + slist->PlayerSearchCallback(slist, NULL, 0, 0, 0, NULL, slist->instance); + + + return sbe_noerror; + +} +/* + Server IP - 4 bytes + Server Port - 2 bytes + Map change time - 4 bytes + Number of maps - 1 byte + Maps + Mapname - NTS +*/ + + +static SBError ProcessMaploop(SBServerList *slist, char *buf, int len) +{ + time_t changeTime; + unsigned int ip; + unsigned short port; + SBServer server; + unsigned char mapCount; + int i; + char *mapList[MAX_MAPLOOP_LENGTH]; + if (len < 11) + return sbe_dataerror; //not a valid packet + memcpy(&ip, buf, 4); + memcpy(&port, buf + 4, 2); + i = SBServerListFindServerByIP(slist, ip, port); + if (i == -1) + return sbe_noerror; //not present any more + server = SBServerListNth(slist, i); + memcpy(&changeTime, buf + 6, 4); + changeTime = (time_t)ntohl((unsigned long)changeTime); + mapCount = (unsigned char)buf[10]; + buf += 11; + len -= 11; + for (i = 0 ; i < mapCount && i < MAX_MAPLOOP_LENGTH; i++) + { + int maplen; + if (len < 1) + break; + maplen = NTSLengthSB(buf, len); + if (maplen == -1) + return sbe_dataerror; //not a full NTS string + mapList[i] = buf; + buf += maplen; + len -= maplen; + } + if (slist->MaploopCallback == NULL) + return sbe_noerror; + slist->MaploopCallback(slist, server, changeTime, i, mapList, slist->instance); + return sbe_noerror; +} + +static SBError ProcessDeleteServer(SBServerList *slist, char *buf, int len) +{ + goa_uint32 ip; + unsigned short port; + int i; + + if (len < 6) + return sbe_dataerror; + //need to grab out the ip & port to find it in our list + memcpy(&ip, buf, 4); + memcpy(&port, buf + 4, 2); + //see if we can find the server in the list - if not, then add it + i = SBServerListFindServerByIP(slist, ip, port); + if (i == -1) + return sbe_noerror; + SBServerListRemoveAt(slist, i); + return sbe_noerror; +} + +/* +same as server list server format, except without using popular value lists +*/ +static SBError ProcessPushServer(SBServerList *slist, char *buf, int len) +{ + + SBServer server; + goa_uint32 ip; + unsigned short port; + int foundexisting; + int i; + + if (len < 5) + return sbe_dataerror; + //need to grab out the ip & port to find it in our list + ParseServerIPPort(slist, buf, len, &ip, &port); + + foundexisting = SBServerListFindServerByIP(slist, ip, port); + if (foundexisting == -1) //need to add it + { + server = SBAllocServer(slist, ip, port); + if (SBIsNullServer(server)) + return sbe_allocerror; + } else + server = SBServerListNth(slist, foundexisting); + i = ParseServer(slist, server, buf, len, 0); + if (i < 0) + return sbe_dataerror; //whole thing wasn't there + if (foundexisting == -1) + SBServerListAppendServer(slist, server); + slist->ListCallback(slist, slc_serverupdated, server, slist->instance); + return sbe_noerror; + +} + + + +static SBError ProcessAdHocData(SBServerList *slist) +{ + unsigned short msglen; + int i; + SBError ret = sbe_noerror; + while (slist->inbufferlen >= 3) //while there is at least 1 message in there.. (2 bytes for the size + 1 for the type) + { + //first two bytes are the length - see if we have the whole message.. + memcpy(&msglen, slist->inbuffer, 2); + msglen = ntohs(msglen); + if (msglen > INCOMING_BUFFER_SIZE) //ack! won't fit.. + { + ret = sbe_dataerror; + break; + } + if (slist->inbufferlen < msglen) + return sbe_noerror; //whole message not yet here + //else process the message + switch (slist->inbuffer[2]) + { + case PUSH_KEYS_MESSAGE: + ret = ProcessPushKeyList(slist, slist->inbuffer + 3, msglen - 3); + break; + case PUSH_SERVER_MESSAGE: + ret = ProcessPushServer(slist, slist->inbuffer + 3, msglen - 3); + break; + case KEEPALIVE_MESSAGE: //just need to send it back.. + i = (int)send(slist->slsocket, slist->inbuffer, msglen, 0); + if (i <= 0) + return sbe_connecterror; + break; + case DELETE_SERVER_MESSAGE: + ret = ProcessDeleteServer(slist, slist->inbuffer + 3, msglen - 3); + break; + case MAPLOOP_MESSAGE: + ret = ProcessMaploop(slist, slist->inbuffer + 3, msglen - 3); + break; + case PLAYERSEARCH_MESSAGE: + ret = ProcessPlayerSearch(slist, slist->inbuffer + 3, msglen - 3); + break; + } + + slist->inbufferlen -= msglen; + assert(slist->inbufferlen >= 0); + if (slist->inbufferlen != 0 && slist->inbuffer != NULL) //if anything is left, shift it over.. + { + memmove(slist->inbuffer, slist->inbuffer + msglen, (size_t)slist->inbufferlen); + } + if (ret != sbe_noerror) + break; + } + if (ret != sbe_noerror) + ErrorDisconnect(slist); + return ret; +} + + +static SBError ProcessIncomingData(SBServerList *slist) +{ + SBError err; + int len; + int oldlen; + + if(!CanReceiveOnSocket(slist->slsocket)) + return sbe_noerror; + + //append to data + oldlen = slist->inbufferlen; + len = recv(slist->slsocket, slist->inbuffer + slist->inbufferlen, INCOMING_BUFFER_SIZE - slist->inbufferlen, 0); + if (gsiSocketIsError(len)|| len == 0) + { + ErrorDisconnect(slist); + return sbe_connecterror; + } + slist->inbufferlen += len; + err = sbe_noerror; + if (slist->state == sl_connected || slist->pstate > pi_cryptheader) //decrypt any new data.. + { + GOADecrypt(&(slist->cryptkey), (unsigned char *)(slist->inbuffer + oldlen), slist->inbufferlen - oldlen); + } + + if (slist->state == sl_mainlist) + err = ProcessMainListData(slist); + if (err != sbe_noerror) + return err; + //always need to check this after mainlistdata, in case some extra data has some in (e.g. key list for push) + if (slist->state == sl_connected && slist->inbufferlen > 0) + return ProcessAdHocData(slist); + return sbe_noerror; + +} + +SBError SBGetServerRulesFromMaster(SBServerList *slist, goa_uint32 ip, unsigned short port) +{ + char requestBuffer[16]; + unsigned short tmp; + unsigned short msgLen = 9; + + if (slist->state == sl_disconnected) //try to connect + SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0); + + if (slist->state == sl_disconnected) + return sbe_connecterror; + + //the length + tmp = htons(msgLen); + memcpy(requestBuffer, &tmp, 2); + //the message type + requestBuffer[2] = SERVER_INFO_REQUEST; + memcpy(requestBuffer + 3, &ip, sizeof(ip)); + memcpy(requestBuffer + 7, &port, sizeof(port)); + return SendWithRetry(slist, requestBuffer, msgLen); +} + +SBError SBSendMessageToServer(SBServerList *slist, goa_uint32 ip, unsigned short port, const char *data, int len) +{ + char requestBuffer[16]; + unsigned short tmp; + unsigned short msgLen = 9; + SBError ret; + int i; + + if (slist->state == sl_disconnected) //try to connect + SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0); + + if (slist->state == sl_disconnected) + return sbe_connecterror; + + //the length + tmp = htons((unsigned short)(msgLen + len)); + memcpy(requestBuffer, &tmp, 2); + //the message type + requestBuffer[2] = SEND_MESSAGE_REQUEST; + memcpy(requestBuffer + 3, &ip, sizeof(ip)); + memcpy(requestBuffer + 7, &port, sizeof(port)); + ret = SendWithRetry(slist, requestBuffer, msgLen); + if (ret != sbe_noerror) + return ret; + i = send(slist->slsocket, data, len, 0); //send the actual push data + if (i < 0) + return sbe_connecterror; + return sbe_noerror; + +} + +SBError SBSendPlayerSearchRequest(SBServerList *slist, char *searchName, int searchOptions, int maxResults, SBPlayerSearchCallbackFn callback) +{ + char requestBuffer[256]; + unsigned short msgLen = 11; + int namelen; + SBError ret; + + if (slist->state == sl_disconnected) //try to connect + SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0); + if (slist->state == sl_disconnected) + return sbe_connecterror; + + slist->PlayerSearchCallback = callback; + + + requestBuffer[2] = PLAYERSEARCH_REQUEST; + searchOptions = (int)htonl((unsigned int)searchOptions); + memcpy(requestBuffer + 3, &searchOptions, 4); + maxResults = (int)htonl((unsigned int)maxResults); + memcpy(requestBuffer + 7, &maxResults, 4); + namelen = (int)strlen(searchName); + msgLen = (unsigned short)(msgLen + (namelen + 1)); + if (msgLen > sizeof(requestBuffer)) + return sbe_paramerror; //search string too long + memcpy(requestBuffer + 11, searchName, (size_t)namelen + 1); + msgLen = htons((unsigned short)(msgLen)); + memcpy(requestBuffer, &msgLen, 2); + ret = SendWithRetry(slist, requestBuffer, ntohs(msgLen)); + if (ret != sbe_noerror) + return ret; + return sbe_noerror; +} + + +SBError SBSendMaploopRequest(SBServerList *slist, SBServer server, SBMaploopCallbackFn callback) +{ + char requestBuffer[16]; + unsigned short tmp; + unsigned short msgLen = 9; + unsigned short port; + unsigned int ip; + SBError ret; + + + if (slist->state == sl_disconnected) //try to connect + SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0); + if (slist->state == sl_disconnected) + return sbe_connecterror; + + slist->MaploopCallback = callback; + //the length + tmp = htons((unsigned short)(msgLen)); + memcpy(requestBuffer, &tmp, 2); + //the message type + requestBuffer[2] = MAPLOOP_REQUEST; + ip = SBServerGetPublicInetAddress(server); + port = SBServerGetPublicQueryPortNBO(server); + memcpy(requestBuffer + 3, &ip, sizeof(ip)); + memcpy(requestBuffer + 7, &port, sizeof(port)); + ret = SendWithRetry(slist, requestBuffer, msgLen); + if (ret != sbe_noerror) + return ret; + return sbe_noerror; + +} + +SBError SBSendNatNegotiateCookieToServer(SBServerList *slist, goa_uint32 ip, unsigned short port, int cookie) +{ + unsigned char negotiateBuffer[NATNEG_MAGIC_LEN + 4]; + negotiateBuffer[0] = NN_MAGIC_0; + negotiateBuffer[1] = NN_MAGIC_1; + negotiateBuffer[2] = NN_MAGIC_2; + negotiateBuffer[3] = NN_MAGIC_3; + negotiateBuffer[4] = NN_MAGIC_4; + negotiateBuffer[5] = NN_MAGIC_5; + cookie = (int)htonl((unsigned int)cookie); + memcpy(negotiateBuffer + NATNEG_MAGIC_LEN, &cookie, 4); + return SBSendMessageToServer(slist, ip, port, (char*)negotiateBuffer, NATNEG_MAGIC_LEN + 4); +} + +static SBError ProcessLanData(SBServerList *slist) +{ + char indata[1500]; // make sure we have enough room for a large UDP packet + struct sockaddr_in saddr; + int saddrlen = sizeof(saddr); + int error; + int foundexisting; + SBServer server; + + while (CanReceiveOnSocket(slist->slsocket)) //we break if the select fails + { +#if defined(_PS3) + error = (int)recvfrom(slist->slsocket, indata, sizeof(indata) - 1, 0, (struct sockaddr *)&saddr, (socklen_t*)&saddrlen ); +#else + error = (int)recvfrom(slist->slsocket, indata, sizeof(indata) - 1, 0, (struct sockaddr *)&saddr, &saddrlen ); +#endif + + if (gsiSocketIsError(error)) + continue; + //if we got data, then add it to the list... + foundexisting = SBServerListFindServerByIP(slist, saddr.sin_addr.s_addr, saddr.sin_port); + if (foundexisting != -1) //already exists + continue; + server = SBAllocServer(slist, saddr.sin_addr.s_addr, saddr.sin_port); + if (SBIsNullServer(server)) + return sbe_allocerror; + SBServerSetFlags(server, UNSOLICITED_UDP_FLAG|NONSTANDARD_PORT_FLAG); + SBServerListAppendServer(slist, server); + } + if (current_time() - slist->lanstarttime > SL_LAN_SEARCH_TIME) //done waiting for replies + { + closesocket(slist->slsocket); + slist->slsocket = INVALID_SOCKET; + slist->state = sl_disconnected; + slist->ListCallback(slist, slc_initiallistcomplete, SBNullServer, slist->instance); + } + return sbe_noerror; +} + +SBError SBListThink(SBServerList *slist) +{ + SBFreeDeadList(slist); //free any pending deleted servers + switch (slist->state) + { + case sl_disconnected: + break; + case sl_connected: + case sl_mainlist: + return ProcessIncomingData(slist); + //break; + case sl_lanbrowse: + return ProcessLanData(slist); + //break; + } + + return sbe_noerror; + //see if any data is available... + +} + + + + diff --git a/code/gamespy/serverbrowsing/sbmfcsample/StdAfx.cpp b/code/gamespy/serverbrowsing/sbmfcsample/StdAfx.cpp new file mode 100644 index 00000000..749a2aaa --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// sbmfcsample.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/code/gamespy/serverbrowsing/sbmfcsample/StdAfx.h b/code/gamespy/serverbrowsing/sbmfcsample/StdAfx.h new file mode 100644 index 00000000..3f7783ea --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/StdAfx.h @@ -0,0 +1,27 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__BFD40F2B_3C12_4712_8A50_65B3BCFA0A5F__INCLUDED_) +#define AFX_STDAFX_H__BFD40F2B_3C12_4712_8A50_65B3BCFA0A5F__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__BFD40F2B_3C12_4712_8A50_65B3BCFA0A5F__INCLUDED_) diff --git a/code/gamespy/serverbrowsing/sbmfcsample/res/sbmfcsample.ico b/code/gamespy/serverbrowsing/sbmfcsample/res/sbmfcsample.ico new file mode 100644 index 0000000000000000000000000000000000000000..7eef0bcbe6580a6f464d688906172c2d9de44262 GIT binary patch literal 1078 zcmc&zF>b>!3}jLb9s)T}@Kod(893@u8ajANzT`op9^o+)S?=nU(FD@%0s)Sg^oyC8{H z9myetc;MEP)59v(LMa~xK8Yu^jIR*H22uCFiq5%C{s7(PJi>o15i^bmX4(vPxWAio z9ryY#AU_jfnd047-@`)XzL?%iS$gQyFP{44kS9X)fN{{QoL~hO-&=q&20Zr*cxFAt PkaNE{wR~2C$NfnjhSXWT literal 0 HcmV?d00001 diff --git a/code/gamespy/serverbrowsing/sbmfcsample/res/sbmfcsample.rc2 b/code/gamespy/serverbrowsing/sbmfcsample/res/sbmfcsample.rc2 new file mode 100644 index 00000000..8289015e --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/res/sbmfcsample.rc2 @@ -0,0 +1,13 @@ +// +// SBMFCSAMPLE.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/serverbrowsing/sbmfcsample/resource.h b/code/gamespy/serverbrowsing/sbmfcsample/resource.h new file mode 100644 index 00000000..10c3d57c --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/resource.h @@ -0,0 +1,31 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by sbmfcsample.rc +// +#define IDD_SBMFCSAMPLE_DIALOG 102 +#define IDR_MAINFRAME 128 +#define IDC_SERVERLIST 1001 +#define IDC_REFRESH 1002 +#define IDC_PLAYERLIST 1003 +#define IDC_GAMENAME 1004 +#define IDC_FILTER 1005 +#define IDC_INTERNET 1006 +#define IDC_LAN 1007 +#define IDC_STARTP 1008 +#define IDC_ENDP 1009 +#define IDC_GOA 1010 +#define IDC_QR2 1011 +#define IDC_PROGRESS 1012 +#define IDC_SERVERS 1013 +#define IDC_PLAYERS 1014 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 130 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1016 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.cpp b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.cpp new file mode 100644 index 00000000..4277c7f8 --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.cpp @@ -0,0 +1,63 @@ +// sbmfcsample.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "sbmfcsample.h" +#include "sbmfcsampleDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CSbmfcsampleApp + +BEGIN_MESSAGE_MAP(CSbmfcsampleApp, CWinApp) + //{{AFX_MSG_MAP(CSbmfcsampleApp) + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CSbmfcsampleApp construction + +CSbmfcsampleApp::CSbmfcsampleApp() +{ +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CSbmfcsampleApp object + +CSbmfcsampleApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CSbmfcsampleApp initialization + +BOOL CSbmfcsampleApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization +/* +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif +*/ + CSbmfcsampleDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + } + else if (nResponse == IDCANCEL) + { + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.h b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.h new file mode 100644 index 00000000..60e34cbe --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.h @@ -0,0 +1,47 @@ +// sbmfcsample.h : main header file for the SBMFCSAMPLE application +// + +#if !defined(AFX_SBMFCSAMPLE_H__F2EE1BD5_6089_4F00_9E83_2D0A7C9AA592__INCLUDED_) +#define AFX_SBMFCSAMPLE_H__F2EE1BD5_6089_4F00_9E83_2D0A7C9AA592__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CSbmfcsampleApp: +// See sbmfcsample.cpp for the implementation of this class +// + +class CSbmfcsampleApp : public CWinApp +{ +public: + CSbmfcsampleApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CSbmfcsampleApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CSbmfcsampleApp) + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_SBMFCSAMPLE_H__F2EE1BD5_6089_4F00_9E83_2D0A7C9AA592__INCLUDED_) diff --git a/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.rc b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.rc new file mode 100644 index 00000000..f05e98b1 --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.rc @@ -0,0 +1,197 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\sbmfcsample.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\sbmfcsample.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_SBMFCSAMPLE_DIALOG DIALOGEX 0, 0, 320, 251 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "sbmfcsample" +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "Refresh",IDC_REFRESH,7,127,50,14 + PUSHBUTTON "Close",IDOK,60,127,50,14 + EDITTEXT IDC_GAMENAME,49,145,56,12,ES_AUTOHSCROLL + EDITTEXT IDC_FILTER,49,160,56,12,ES_AUTOHSCROLL + CONTROL "Internet",IDC_INTERNET,"Button",BS_AUTORADIOBUTTON | + WS_GROUP,7,176,40,10 + CONTROL "Lan",IDC_LAN,"Button",BS_AUTORADIOBUTTON,60,176,28,10 + CONTROL "GOA",IDC_GOA,"Button",BS_AUTORADIOBUTTON | WS_GROUP,7, + 188,31,10 + CONTROL "QR2",IDC_QR2,"Button",BS_AUTORADIOBUTTON,60,188,31,10 + CONTROL "List2",IDC_SERVERLIST,"SysListView32",LVS_REPORT | + WS_BORDER | WS_GROUP | WS_TABSTOP,7,7,306,117 + CONTROL "List3",IDC_PLAYERLIST,"SysListView32",LVS_REPORT | + WS_BORDER | WS_TABSTOP,112,146,201,98 + LTEXT "Gamename:",IDC_STATIC,7,147,39,8 + LTEXT "Filter:",IDC_STATIC,7,162,18,8 + CONTROL "Progress2",IDC_PROGRESS,"msctls_progress32",PBS_SMOOTH | + WS_BORDER,112,126,144,10 + LTEXT "Servers:",IDC_STATIC,260,126,27,8 + LTEXT "222222",IDC_SERVERS,288,126,25,8 + EDITTEXT IDC_STARTP,49,214,56,12,ES_AUTOHSCROLL | ES_NUMBER + EDITTEXT IDC_ENDP,49,229,56,12,ES_AUTOHSCROLL | ES_NUMBER + CTEXT "For LAN Only",IDC_STATIC,16,201,72,11 + LTEXT "Start Port",IDC_STATIC,7,215,35,11 + LTEXT "End Port",IDC_STATIC,8,230,28,11 +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "sbmfcsample MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "sbmfcsample\0" + VALUE "LegalCopyright", "Copyright (C) 2002\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "sbmfcsample.EXE\0" + VALUE "ProductName", "sbmfcsample Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_SBMFCSAMPLE_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 313 + TOPMARGIN, 7 + BOTTOMMARGIN, 244 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\sbmfcsample.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsampleDlg.cpp b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsampleDlg.cpp new file mode 100644 index 00000000..8d033aeb --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsampleDlg.cpp @@ -0,0 +1,497 @@ +// sbmfcsampleDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "sbmfcsample.h" +#include "sbmfcsampleDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +//SB - timer ID and frequency +#define TIMER_ID 100 +#define TIMER_FREQUENCY 10 + +//SB - server list columns +#define COL_SERVERNAME 0 +#define COL_PING 1 +#define COL_PLAYERS 2 +#define COL_MAPNAME 3 +#define COL_GAMETYPE 4 + +//SB - player list columns +#define COL_PNAME 0 +#define COL_PPING 1 +#define COL_PSCORE 2 + +// 11-02-2004 : Saad Nader +// replaced with a GUI-based way of doing it. +//SB - starting and ending port for LAN game searches +//#define START_PORT 7778 +//#define END_PORT (START_PORT + 100) + +//SB - maximum number of concurrent updates +#define MAX_UPDATES 30 + +///////////////////////////////////////////////////////////////////////////// +// CSbmfcsampleDlg dialog + +CSbmfcsampleDlg::CSbmfcsampleDlg(CWnd* pParent /*=NULL*/) + : CDialog(CSbmfcsampleDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CSbmfcsampleDlg) + m_filter = _T(""); + m_gamename = _T(""); + m_startPort = 0; + m_endPort = 0; + //}}AFX_DATA_INIT + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); +} + +void CSbmfcsampleDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CSbmfcsampleDlg) + DDX_Control(pDX, IDC_SERVERS, m_servers); + DDX_Control(pDX, IDC_PROGRESS, m_progress); + DDX_Control(pDX, IDC_SERVERLIST, m_serverList); + DDX_Control(pDX, IDC_PLAYERLIST, m_playerList); + DDX_Text(pDX, IDC_FILTER, m_filter); + DDX_Text(pDX, IDC_GAMENAME, m_gamename); + DDX_Text(pDX, IDC_STARTP, m_startPort); + DDX_Text(pDX, IDC_ENDP, m_endPort); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CSbmfcsampleDlg, CDialog) + //{{AFX_MSG_MAP(CSbmfcsampleDlg) + ON_BN_CLICKED(IDC_REFRESH, OnRefresh) + ON_NOTIFY(NM_CLICK, IDC_SERVERLIST, OnClickServerlist) + ON_NOTIFY(NM_DBLCLK, IDC_SERVERLIST, OnDblclkServerlist) + ON_WM_TIMER() + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_NOTIFY(LVN_COLUMNCLICK, IDC_SERVERLIST, OnColumnclickServerlist) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CSbmfcsampleDlg message handlers + +BOOL CSbmfcsampleDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + SetIcon(m_hIcon, TRUE); + SetIcon(m_hIcon, FALSE); + + //SB - setup list columns + m_serverList.InsertColumn(COL_SERVERNAME, "Server Name", LVCFMT_LEFT, 150, -1); + m_serverList.InsertColumn(COL_PING, "Ping", LVCFMT_LEFT, 50, 0); + m_serverList.InsertColumn(COL_PLAYERS, "Players", LVCFMT_LEFT, 75, 1); + m_serverList.InsertColumn(COL_MAPNAME, "Map", LVCFMT_LEFT, 75, 2); + m_serverList.InsertColumn(COL_GAMETYPE, "GameType", LVCFMT_LEFT, 100, 3); + m_playerList.InsertColumn(COL_PNAME, "Player Name", LVCFMT_LEFT, 150, -1); + m_playerList.InsertColumn(COL_PPING, "Ping", LVCFMT_LEFT, 50, 0); + m_playerList.InsertColumn(COL_PSCORE, "Score", LVCFMT_LEFT, 50, 1); + ListView_SetExtendedListViewStyle(m_serverList.m_hWnd, LVS_EX_FULLROWSELECT); + ListView_SetExtendedListViewStyle(m_playerList.m_hWnd, LVS_EX_FULLROWSELECT); + + //SB - default to Internet + CheckRadioButton(IDC_INTERNET, IDC_LAN, IDC_INTERNET); + + //SB - default to QR2 + CheckRadioButton(IDC_GOA, IDC_QR2, IDC_QR2); + + //SB - no server browser yet + m_serverBrowser = NULL; + + //SB - no timer yet + m_timerID = 0; + + //SB - no servers yet + m_servers.SetWindowText(""); + + //SB - check that the game's backend is available + GSIACResult result; + GSIStartAvailableCheck("gmtest"); + while((result = GSIAvailableCheckThink()) == GSIACWaiting) + msleep(5); + if(result != GSIACAvailable) + { + MessageBox("The backend is not available\n"); + return TRUE; + } + + return TRUE; +} + +BOOL CSbmfcsampleDlg::DestroyWindow() +{ + // free the browser + if(m_serverBrowser) + { + ServerBrowserFree(m_serverBrowser); + m_serverBrowser = NULL; + } + + return CDialog::DestroyWindow(); +} + +void CSbmfcsampleDlg::OnTimer(UINT nIDEvent) +{ + // think if our timer was called + if(nIDEvent == m_timerID) + { + ServerBrowserThink(m_serverBrowser); + } + + CDialog::OnTimer(nIDEvent); +} + +void CSbmfcsampleDlg::OnRefresh() +{ + UpdateData(); + + // create the server list if we need to + if(!CreateServerList()) + return; + + // if we're doing an update, cancel it + SBState state = ServerBrowserState(m_serverBrowser); + if((state != sb_connected) && (state != sb_disconnected)) + { + ServerBrowserHalt(m_serverBrowser); + return; + } + + // clear the server browser and list + ServerBrowserClear(m_serverBrowser); + m_serverList.DeleteAllItems(); + + // clear the progress bar + m_progress.SetPos(0); + + // clear the server count + m_serverCount = 0; + m_servers.SetWindowText("0"); + + // set a timer + if(!m_timerID) + m_timerID = SetTimer(TIMER_ID, TIMER_FREQUENCY, NULL); + + // fields we're interested in + unsigned char fields[] = { HOSTNAME_KEY, NUMPLAYERS_KEY, MAXPLAYERS_KEY, MAPNAME_KEY, GAMETYPE_KEY }; + int numFields = sizeof(fields) / sizeof(fields[0]); + + // check for internet/lan + bool internet = (IsDlgButtonChecked(IDC_INTERNET) == BST_CHECKED); + + // do an update + SBError error; + if(internet) + error = ServerBrowserUpdate(m_serverBrowser, SBTrue, SBFalse, fields, numFields, (char *)(const char *)m_filter); + else + error = ServerBrowserLANUpdate(m_serverBrowser, SBTrue, (unsigned short)m_startPort, (unsigned short)m_endPort); +} + +void CSbmfcsampleDlg::OnClickServerlist(NMHDR* pNMHDR, LRESULT* pResult) +{ + // clear the player box + m_playerList.DeleteAllItems(); + + // find the selected server + POSITION pos = m_serverList.GetFirstSelectedItemPosition(); + if(pos == NULL) + return; + int index = m_serverList.GetNextSelectedItem(pos); + + // get the server + LVITEM item; + item.mask = LVIF_PARAM; + item.iItem = index; + item.iSubItem = 0; + m_serverList.GetItem(&item); + SBServer server = (SBServer)item.lParam; + + if (!SBServerHasFullKeys(server)) //we need to query for the full rules + { + // turn on the hour glass + HCURSOR cursor = SetCursor(LoadCursor(NULL, IDC_WAIT)); + + // do a server update (this is blocking!) + ServerBrowserAuxUpdateServer(m_serverBrowser, server, SBFalse, SBTrue); + + // turn off the hour glass + SetCursor(cursor); + } + + // get the player count + int count = SBServerGetIntValue(server, "numplayers", 0); + + // add the players to the list + for(int i = 0 ; i < count ; i++) + { + m_playerList.InsertItem(0, SBServerGetPlayerStringValue(server, i, "player", "(NO NAME)")); + m_playerList.SetItem(0, COL_PPING, LVIF_TEXT, SBServerGetPlayerStringValue(server, i, "ping", "0"), -1, 0, 0, 0); + m_playerList.SetItem(0, COL_PSCORE, LVIF_TEXT, SBServerGetPlayerStringValue(server, i, "score", "0"), -1, 0, 0, 0); + } + + // MFC + *pResult = 0; + + GSI_UNUSED(pNMHDR); +} + +void CSbmfcsampleDlg::OnDblclkServerlist(NMHDR* pNMHDR, LRESULT* pResult) +{ + // launch the game here + //MessageBox("If this were a real server browser, you would be launched now!","Go!"); + + // find the selected server + POSITION pos = m_serverList.GetFirstSelectedItemPosition(); + if(pos == NULL) + return; + int index = m_serverList.GetNextSelectedItem(pos); + + // get the server + LVITEM item; + item.mask = LVIF_PARAM; + item.iItem = index; + item.iSubItem = 0; + m_serverList.GetItem(&item); + SBServer server = (SBServer)item.lParam; + SBError error = ServerBrowserConnectToServer(m_serverBrowser, server, SBConnectCallback); + if(error != sbe_noerror) + MessageBox("Error starting connect to server"); + + // MFC + *pResult = 0; + + GSI_UNUSED(pNMHDR); +} + +void CSbmfcsampleDlg::OnColumnclickServerlist(NMHDR* pNMHDR, LRESULT* pResult) +{ + NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; + + // track ascending/descending + static bool ascending = false; + ascending = !ascending; + + //figure out which column they clicked + switch(pNMListView->iSubItem) + { + case COL_SERVERNAME: + ServerBrowserSort(m_serverBrowser, (SBBool)ascending, "hostname", sbcm_stricase); + break; + case COL_PING: + ServerBrowserSort(m_serverBrowser, (SBBool)ascending, "ping", sbcm_int); + break; + case COL_PLAYERS: + ServerBrowserSort(m_serverBrowser, (SBBool)ascending, "numplayers", sbcm_int); + break; + case COL_MAPNAME: + ServerBrowserSort(m_serverBrowser, (SBBool)ascending, "mapname", sbcm_stricase); + break; + case COL_GAMETYPE: + ServerBrowserSort(m_serverBrowser, (SBBool)ascending, "gametype", sbcm_stricase); + break; + } + + // we don't want the server list to redraw every time we insert a server! + m_serverList.SetRedraw(false); + + // clear the server list + m_serverList.DeleteAllItems(); + + // clear the server count + m_serverCount = 0; + m_servers.SetWindowText("0"); + + // go through the list of servers + for(int i = 0; i < ServerBrowserCount(m_serverBrowser) ; i++) + { + // if we got basic info for it, put it back in the list + SBServer server = ServerBrowserGetServer(m_serverBrowser, i); + if(SBServerHasBasicKeys(server)) + AddServer(server, FALSE); + } + + // let the server list redraw itself now that we're done updating + m_serverList.SetRedraw(true); + + *pResult = 0; +} + +void CSbmfcsampleDlg::SBCallback(ServerBrowser serverBrowser, SBCallbackReason reason, SBServer server, void *instance) +{ + CString str; + CSbmfcsampleDlg * dlg = (CSbmfcsampleDlg *)instance; + + CString address; + if(server) + address.Format("%s:%d", SBServerGetPublicAddress(server), SBServerGetPublicQueryPort(server)); + + switch(reason) + { + case sbc_serveradded: + dlg->AddServer(server, TRUE); + break; + case sbc_serverupdated: + dlg->AddServer(server, TRUE); + break; + case sbc_serverupdatefailed: + break; + case sbc_serverdeleted: + dlg->RemoveServer(server); + break; + case sbc_updatecomplete: + dlg->m_progress.SetPos(100); + break; + case sbc_queryerror: + str.Format("Query Error: %s\n", ServerBrowserListQueryError(dlg->m_serverBrowser)); + dlg->MessageBox(str); + break; + } + + GSI_UNUSED(serverBrowser); +} + +void CSbmfcsampleDlg::SBConnectCallback(ServerBrowser serverBrowser, SBConnectToServerState state, SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *instance) +{ + CSbmfcsampleDlg * dlg = (CSbmfcsampleDlg *)instance; + + switch(state) + { + case sbcs_succeeded: + dlg->MessageBox("Connected to server"); + closesocket(gamesocket); + break; + case sbcs_failed: + dlg->MessageBox("Failed to connect to server"); + break; + } + + GSI_UNUSED(serverBrowser); + GSI_UNUSED(remoteaddr); +} + +BOOL CSbmfcsampleDlg::CreateServerList() +{ + // only create the object once + if(!m_serverBrowser) + { + // check for an empty gamename + if(m_gamename.IsEmpty()) + { + MessageBox("No game specified"); + GetDlgItem(IDC_GAMENAME)->SetFocus(); + return FALSE; + } + SBBool lanBrowsing = (SBBool)(IsDlgButtonChecked(IDC_LAN) == BST_CHECKED); + if (m_startPort == 0 && lanBrowsing == SBTrue) + { + AfxMessageBox("Invalid start port!"); + GetDlgItem(IDC_STARTP)->SetFocus(); + return FALSE; + } + if (m_endPort == 0 && lanBrowsing == SBTrue) + { + AfxMessageBox("Invalid end port!"); + GetDlgItem(IDC_ENDP)->SetFocus(); + return FALSE; + } + + // create it + m_serverBrowser = ServerBrowserNew(m_gamename, "gmtest", "HA6zkS", 0, MAX_UPDATES, (IsDlgButtonChecked(IDC_GOA) == BST_CHECKED)?QVERSION_GOA:QVERSION_QR2, lanBrowsing, SBCallback, this); + if(!m_serverBrowser) + { + MessageBox("Unable to create the server browser object"); + return FALSE; + } + + // don't let them change the gamename + GetDlgItem(IDC_GAMENAME)->EnableWindow(FALSE); + } + + return TRUE; +} + +void CSbmfcsampleDlg::AddServer(SBServer server, BOOL checkForReplace) +{ + // set the progress + if(ServerBrowserCount(m_serverBrowser) > 0) + m_progress.SetPos((ServerBrowserCount(m_serverBrowser) - ServerBrowserPendingQueryCount(m_serverBrowser)) * 100 / ServerBrowserCount(m_serverBrowser)); + + // check for the server in the list + int index = FindServer(server); + bool replace = (index != -1); + + // if we didn't find a server to replace, append + if(!replace) + index = m_serverList.GetItemCount(); + + // set or insert the hostname + const char * hostname = SBServerGetStringValue(server, "hostname","(NO NAME)"); + if(replace) + { + m_serverList.SetItem(index, COL_SERVERNAME, LVIF_TEXT, hostname, -1, 0, 0, NULL); + } + else + { + m_serverList.InsertItem(index, hostname); + m_serverList.SetItem(index, COL_SERVERNAME, LVIF_PARAM, NULL, -1, 0, 0, (LPARAM)server); + } + + // set the rest of the columns + int numplayers = SBServerGetIntValue(server, "numplayers", 0); + CString ping, players; + if (SBServerHasValidPing(server)) + ping.Format("%d%s", SBServerGetPing(server), SBServerDirectConnect(server) ? "" : "i"); + else + ping = "Unknown"; + players.Format("%d/%d", numplayers, SBServerGetIntValue(server, "maxplayers", 0)); + m_serverList.SetItem(index, COL_PING, LVIF_TEXT, ping, -1, 0, 0, 0); + m_serverList.SetItem(index, COL_PLAYERS, LVIF_TEXT, players, -1, 0, 0, 0); + m_serverList.SetItem(index, COL_MAPNAME, LVIF_TEXT, SBServerGetStringValue(server, "mapname", "(NO MAP)"), -1, 0, 0, 0); + m_serverList.SetItem(index, COL_GAMETYPE, LVIF_TEXT, SBServerGetStringValue(server, "gametype", ""), -1, 0, 0, 0); + + // update server count + if(!replace) + { + CString str; + str.Format("%d", ++m_serverCount); + m_servers.SetWindowText(str); + } + + GSI_UNUSED(checkForReplace); +} + +void CSbmfcsampleDlg::RemoveServer(SBServer server) +{ + // find the server + int index = FindServer(server); + ASSERT(index != -1); + if(index == -1) + return; + + // remove it + m_serverList.DeleteItem(index); + + // update server count + CString str; + str.Format("%d", --m_serverCount); + m_servers.SetWindowText(str); +} + +int CSbmfcsampleDlg::FindServer(SBServer server) +{ + LVFINDINFO info; + info.flags = LVFI_PARAM; + info.lParam = (LPARAM)server; + return m_serverList.FindItem(&info); +} diff --git a/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsampleDlg.h b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsampleDlg.h new file mode 100644 index 00000000..10498e0c --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsampleDlg.h @@ -0,0 +1,74 @@ +// sbmfcsampleDlg.h : header file +// + +#if !defined(AFX_SBMFCSAMPLEDLG_H__8B726D03_AF99_4938_A63C_18525912E55D__INCLUDED_) +#define AFX_SBMFCSAMPLEDLG_H__8B726D03_AF99_4938_A63C_18525912E55D__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +///////////////////////////////////////////////////////////////////////////// +// CSbmfcsampleDlg dialog + +//SB - header +#include "..\sb_serverbrowsing.h" +#include "..\..\qr2\qr2regkeys.h" +#include "..\..\common\gsAvailable.h" + +class CSbmfcsampleDlg : public CDialog +{ +// Construction +public: + CSbmfcsampleDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CSbmfcsampleDlg) + enum { IDD = IDD_SBMFCSAMPLE_DIALOG }; + CStatic m_servers; + CProgressCtrl m_progress; + CListCtrl m_serverList; + CListCtrl m_playerList; + CString m_filter; + CString m_gamename; + int m_startPort; + int m_endPort; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CSbmfcsampleDlg) + public: + virtual BOOL DestroyWindow(); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + static void SBCallback(ServerBrowser serverBrowser, SBCallbackReason reason, SBServer server, void * instance); + static void SBConnectCallback(ServerBrowser serverBrowser, SBConnectToServerState state, SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *instance); + void AddServer(SBServer server, BOOL checkForReplace); + void RemoveServer(SBServer server); + int FindServer(SBServer server); + BOOL CreateServerList(); + HICON m_hIcon; + ServerBrowser m_serverBrowser; + UINT m_timerID; + int m_serverCount; + + // Generated message map functions + //{{AFX_MSG(CSbmfcsampleDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnRefresh(); + afx_msg void OnClickServerlist(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnDblclkServerlist(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnColumnclickServerlist(NMHDR* pNMHDR, LRESULT* pResult); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_SBMFCSAMPLEDLG_H__8B726D03_AF99_4938_A63C_18525912E55D__INCLUDED_) diff --git a/code/gamespy/webservices/AuthService.c b/code/gamespy/webservices/AuthService.c new file mode 100644 index 00000000..0ad0ac51 --- /dev/null +++ b/code/gamespy/webservices/AuthService.c @@ -0,0 +1,1093 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "AuthService.h" +#include "../common/gsXML.h" +#include "../common/gsAvailable.h" +#include "../md5.h" + +#pragma warning(disable: 4267) + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define WS_AUTHSERVICE_LOGINPROFILE "LoginProfile" +#define WS_AUTHSERVICE_LOGINUNIQUE "LoginUniqueNick" +#define WS_AUTHSERVICE_LOGINREMOTEAUTH "LoginRemoteAuth" +#define WS_AUTHSERVICE_LOGINPS3CERT "LoginPs3Cert" +#define WS_AUTHSERVICE_PROTOVERSION 1 + +#define WS_AUTHSERVICE_NAMESPACE "ns1" +#define WS_AUTHSERVICE_LOGINPROFILE_SOAP "SOAPAction: \"http://gamespy.net/AuthService/LoginProfile\"" +#define WS_AUTHSERVICE_LOGINUNIQUE_SOAP "SOAPAction: \"http://gamespy.net/AuthService/LoginUniqueNick\"" +#define WS_AUTHSERVICE_LOGINREMOTEAUTH_SOAP "SOAPAction: \"http://gamespy.net/AuthService/LoginRemoteAuth\"" +#define WS_AUTHSERVICE_LOGINPS3CERT_SOAP "SOAPAction: \"http://gamespy.net/AuthService/LoginPs3Cert\"" + +#define WS_AUTHSERVICE_NAMESPACE_COUNT 1 +const char * WS_AUTHSERVICE_NAMESPACES[WS_AUTHSERVICE_NAMESPACE_COUNT] = +{ + WS_AUTHSERVICE_NAMESPACE "=\"http://gamespy.net/AuthService/\"" +}; + +const char WS_AUTHSERVICE_SIGNATURE_KEY[] = + "BF05D63E93751AD4A59A4A7389CF0BE8" + "A22CCDEEA1E7F12C062D6E194472EFDA" + "5184CCECEB4FBADF5EB1D7ABFE911814" + "53972AA971F624AF9BA8F0F82E2869FB" + "7D44BDE8D56EE50977898F3FEE758696" + "22C4981F07506248BD3D092E8EA05C12" + "B2FA37881176084C8F8B8756C4722CDC" + "57D2AD28ACD3AD85934FB48D6B2D2027"; + +/* "D589F4433FAB2855F85A4EB40E6311F0" + "284C7882A72380938FD0C55CC1D65F7C" + "6EE79EDEF06C1AE5EDE139BDBBAB4219" + "B7D2A3F0AC3D3B23F59F580E0E89B9EC" + "787F2DD5A49788C633D5D3CE79438934" + "861EA68AE545D5BBCAAAD917CE9F5C7C" + "7D1452D9214F989861A7511097C35E60" + "A7273DECEA71CB5F8251653B26ACE781";*/ + + + +const char WS_AUTHSERVICE_SIGNATURE_EXP[] = + "010001"; + +// This is declared as an extern so it can be overriden when testing +#define WS_LOGIN_SERVICE_URL_FORMAT "https://%s.auth.pubsvs." GSI_DOMAIN_NAME "/AuthService/AuthService.asmx" +char wsAuthServiceURL[WS_LOGIN_MAX_URL_LEN] = ""; + +typedef struct WSIRequestData +{ + union + { + WSLoginCallback mLoginCallback; + WSLoginPs3CertCallback mLoginPs3CertCallback; + } mUserCallback; + //void * mUserCallback; + void * mUserData; +} WSIRequestData; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void wsiLoginEncryptPassword(const gsi_char * password, gsi_u8 ciphertext[GS_CRYPT_RSA_BYTE_SIZE]); +static gsi_bool wsiServiceAvailable(); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Checks to make sure the availability check has been performed prior +// to using any AuthService Login, and sets the service URL if it has +static gsi_bool wsiServiceAvailable() +{ + if (__GSIACResult == GSIACAvailable) + { + if (wsAuthServiceURL[0] == '\0') + snprintf(wsAuthServiceURL, WS_LOGIN_MAX_URL_LEN, WS_LOGIN_SERVICE_URL_FORMAT, __GSIACGamename); + return gsi_true; + } + else + return gsi_false; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void wsiLoginProfileCallback(GHTTPResult theResult, + GSXmlStreamWriter theRequestXml, + GSXmlStreamReader theResponseXml, + void * theRequestData) +{ + GHTTPResult translatedResult = GHTTPSuccess; + WSIRequestData * requestData = (WSIRequestData*)theRequestData; + + WSLoginResponse response; + GSLoginCertificate * cert = &response.mCertificate; // for convenience + + // initialize local variables + cert->mIsValid = gsi_false; + memset(&response, 0, sizeof(response)); + + if (theResult == GHTTPSuccess) + { + // try to parse the soap + if (gsi_is_false(gsXmlMoveToStart(theResponseXml)) || + gsi_is_false(gsXmlMoveToNext(theResponseXml, "LoginProfileResult"))) + { + response.mLoginResult = WSLogin_ParseError; + } + else + { + // prepare response structure + if (gsi_is_false(gsXmlReadChildAsInt(theResponseXml, "responseCode", (int*)&response.mResponseCode))) + { + // could not parse login response code + response.mLoginResult = WSLogin_ParseError; + } + else if (response.mResponseCode != WSLogin_Success) + { + // server reported an error into reponseCode + response.mLoginResult = WSLogin_ServerError; + } + else if (gsi_is_false(gsXmlMoveToChild(theResponseXml, "certificate")) || + gsi_is_false(wsLoginCertReadXML(cert, theResponseXml)) || + gsi_is_false(gsXmlMoveToParent(theResponseXml)) || + gsi_is_false(gsXmlReadChildAsLargeInt(theResponseXml, "peerkeyprivate", + &response.mPrivateData.mPeerPrivateKey.exponent)) + ) + { + response.mLoginResult = WSLogin_ParseError; + } + else + { + MD5_CTX md5; + + // peer privatekey modulus is same as peer public key modulus + memcpy(&response.mPrivateData.mPeerPrivateKey.modulus, &cert->mPeerPublicKey.modulus, sizeof(cert->mPeerPublicKey.modulus)); + + // hash the private key + MD5Init(&md5); + //gsLargeIntAddToMD5(&response.mPrivateData.mPeerPrivateKey.modulus, &md5); + gsLargeIntAddToMD5(&response.mPrivateData.mPeerPrivateKey.exponent, &md5); + MD5Final((unsigned char*)response.mPrivateData.mKeyHash, &md5); + + // verify certificate + cert->mIsValid = wsLoginCertIsValid(cert); + if (gsi_is_false(cert->mIsValid)) + { + response.mLoginResult = WSLogin_InvalidCertificate; + } + } + } + } + else + { + response.mLoginResult = WSLogin_HttpError; + } + + // trigger the user callback + if (requestData->mUserCallback.mLoginCallback != NULL) + { + WSLoginCallback userCallback = (WSLoginCallback)(requestData->mUserCallback.mLoginCallback); + (userCallback)(translatedResult, &response, requestData->mUserData); + } + gsifree(requestData); + GSI_UNUSED(theRequestXml); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u32 wsLoginProfile(int partnerCode, + int namespaceId, + const gsi_char * profileNick, + const gsi_char * email, + const gsi_char * password, + const gsi_char * cdkey, + WSLoginCallback callback, + void * userData) +{ + GSXmlStreamWriter writer; + WSIRequestData * requestData = NULL; + gsi_u8 encryptedPassword[GS_CRYPT_RSA_BYTE_SIZE]; + + if (!wsiServiceAvailable()) + return WSLogin_NoAvailabilityCheck; + + GS_ASSERT(partnerCode >= 0); + GS_ASSERT(profileNick != NULL); + GS_ASSERT(email != NULL); + GS_ASSERT(password != NULL); + + if (_tcslen(profileNick) >= WS_LOGIN_NICK_LEN) + return WSLogin_InvalidParameters; + if (_tcslen(email) >= WS_LOGIN_EMAIL_LEN) + return WSLogin_InvalidParameters; + if (_tcslen(password) >= WS_LOGIN_PASSWORD_LEN) + return WSLogin_InvalidParameters; + if (cdkey != NULL && (_tcslen(cdkey) > WS_LOGIN_CDKEY_LEN)) + return WSLogin_InvalidParameters; + + // make a copy of the request callback and user param + requestData = (WSIRequestData*)gsimalloc(sizeof(WSIRequestData)); + if (requestData == NULL) + return WSLogin_OutOfMemory; + requestData->mUserCallback.mLoginCallback = callback; + requestData->mUserData = userData; + + // encrypt the password (includes safety padding and hash) + wsiLoginEncryptPassword(password, encryptedPassword); + + writer = gsXmlCreateStreamWriter(WS_AUTHSERVICE_NAMESPACES, WS_AUTHSERVICE_NAMESPACE_COUNT); + if (writer != NULL) + { + GSSoapTask * aTask = NULL; + + if (gsi_is_false(gsXmlWriteOpenTag (writer, WS_AUTHSERVICE_NAMESPACE, WS_AUTHSERVICE_LOGINPROFILE)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "version", WS_AUTHSERVICE_PROTOVERSION)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "partnercode", (gsi_u32)partnerCode)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "namespaceid", (gsi_u32)namespaceId)) || + gsi_is_false(gsXmlWriteAsciiStringElement(writer, WS_AUTHSERVICE_NAMESPACE, "email", email)) || + gsi_is_false(gsXmlWriteAsciiStringElement(writer, WS_AUTHSERVICE_NAMESPACE, "profilenick", profileNick)) || + gsi_is_false(gsXmlWriteOpenTag (writer, WS_AUTHSERVICE_NAMESPACE, "password")) || + gsi_is_false(gsXmlWriteHexBinaryElement(writer, WS_AUTHSERVICE_NAMESPACE, "Value", encryptedPassword, GS_CRYPT_RSA_BYTE_SIZE)) || + gsi_is_false(gsXmlWriteCloseTag (writer, WS_AUTHSERVICE_NAMESPACE, "password")) || + gsi_is_false(gsXmlWriteCloseTag (writer, WS_AUTHSERVICE_NAMESPACE, WS_AUTHSERVICE_LOGINPROFILE)) || + gsi_is_false(gsXmlCloseWriter (writer)) + ) + { + gsXmlFreeWriter(writer); + return WSLogin_OutOfMemory; + } + + aTask = gsiExecuteSoap(wsAuthServiceURL, WS_AUTHSERVICE_LOGINPROFILE_SOAP, + writer, wsiLoginProfileCallback, (void*)requestData); + if (aTask == NULL) + { + gsXmlFreeWriter(writer); + gsifree(requestData); + return WSLogin_OutOfMemory; + } + } + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void wsLoginUniqueCallback(GHTTPResult theResult, + GSXmlStreamWriter theRequestXml, + GSXmlStreamReader theResponseXml, + void * theRequestData) +{ + GHTTPResult translatedResult = theResult; + WSIRequestData * requestData = (WSIRequestData*)theRequestData; + + WSLoginResponse response; + GSLoginCertificate * cert = &response.mCertificate; // for convenience + cert->mIsValid = gsi_false; + + memset(&response, 0, sizeof(response)); + + if (theResult == GHTTPSuccess) + { + // try to parse the soap + if (gsi_is_false(gsXmlMoveToStart(theResponseXml)) || + gsi_is_false(gsXmlMoveToNext(theResponseXml, "LoginUniqueNickResult"))) + { + response.mLoginResult = WSLogin_ParseError; + } + else + { + // prepare response structure + if (gsi_is_false(gsXmlReadChildAsInt(theResponseXml, "responseCode", (int*)&response.mResponseCode))) + { + // could not parse login response code + response.mLoginResult = WSLogin_ParseError; + } + else if (response.mResponseCode != WSLogin_Success) + { + // server reported an error into reponseCode + response.mLoginResult = WSLogin_ServerError; + } + else if (gsi_is_false(gsXmlMoveToChild(theResponseXml, "certificate")) || + gsi_is_false(wsLoginCertReadXML(cert, theResponseXml)) || + gsi_is_false(gsXmlMoveToParent(theResponseXml)) || + gsi_is_false(gsXmlReadChildAsLargeInt(theResponseXml, "peerkeyprivate", + &response.mPrivateData.mPeerPrivateKey.exponent)) + ) + { + response.mLoginResult = WSLogin_ParseError; + } + else + { + MD5_CTX md5; + + // peer privatekey modulus is same as peer public key modulus + memcpy(&response.mPrivateData.mPeerPrivateKey.modulus, &cert->mPeerPublicKey.modulus, sizeof(cert->mPeerPublicKey.modulus)); + + // hash the private key + // -- we use the has like a password for simple authentication + MD5Init(&md5); + //gsLargeIntAddToMD5(&response.mPrivateData.mPeerPrivateKey.modulus, &md5); + gsLargeIntAddToMD5(&response.mPrivateData.mPeerPrivateKey.exponent, &md5); + MD5Final((unsigned char*)response.mPrivateData.mKeyHash, &md5); + + // verify certificate + cert->mIsValid = wsLoginCertIsValid(cert); + if (gsi_is_false(cert->mIsValid)) + { + response.mLoginResult = WSLogin_InvalidCertificate; + } + } + } + } + else if (theResult == GHTTPRequestCancelled) + { + response.mLoginResult = WSLogin_Cancelled; + } + else + { + response.mLoginResult = WSLogin_HttpError; + } + + // trigger the user callback + if (requestData->mUserCallback.mLoginCallback != NULL) + { + WSLoginCallback userCallback = (WSLoginCallback)(requestData->mUserCallback.mLoginCallback); + (userCallback)(translatedResult, &response, requestData->mUserData); + } + gsifree(requestData); + GSI_UNUSED(theRequestXml); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//gsi_u32 wsLoginUnique(WSLoginUniqueRequest * request, WSLoginCallback callback) +gsi_u32 wsLoginUnique(int partnerCode, + int namespaceId, + const gsi_char * uniqueNick, + const gsi_char * password, + const gsi_char * cdkey, + WSLoginCallback userCallback, + void * userData) +{ + GSXmlStreamWriter writer; + WSIRequestData * requestData = NULL; + gsi_u8 encryptedPassword[GS_CRYPT_RSA_BYTE_SIZE]; + + if (!wsiServiceAvailable()) + return WSLogin_NoAvailabilityCheck; + + GS_ASSERT(partnerCode >= 0); + GS_ASSERT(uniqueNick != NULL); + GS_ASSERT(password != NULL); + + if (_tcslen(uniqueNick) >= WS_LOGIN_UNIQUENICK_LEN) + return WSLogin_InvalidParameters; + if (_tcslen(password) >= WS_LOGIN_PASSWORD_LEN) + return WSLogin_InvalidParameters; + if (cdkey != NULL && (_tcslen(cdkey) >= WS_LOGIN_CDKEY_LEN)) + return WSLogin_InvalidParameters; + + // allocate the request values + requestData = (WSIRequestData*)gsimalloc(sizeof(WSIRequestData)); + if (requestData == NULL) + return WSLogin_OutOfMemory; + requestData->mUserCallback.mLoginCallback = userCallback; + requestData->mUserData = userData; + + // encrypt the password (includes safety padding and hash) + wsiLoginEncryptPassword(password, encryptedPassword); + + // create the xml request + writer = gsXmlCreateStreamWriter(WS_AUTHSERVICE_NAMESPACES, WS_AUTHSERVICE_NAMESPACE_COUNT); + if (writer != NULL) + { + GSSoapTask * aTask = NULL; + + if (gsi_is_false(gsXmlWriteOpenTag (writer, WS_AUTHSERVICE_NAMESPACE, WS_AUTHSERVICE_LOGINUNIQUE)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "version", WS_AUTHSERVICE_PROTOVERSION)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "partnercode", (gsi_u32)partnerCode)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "namespaceid", (gsi_u32)namespaceId)) || + gsi_is_false(gsXmlWriteAsciiStringElement(writer, WS_AUTHSERVICE_NAMESPACE, "uniquenick", uniqueNick)) || + gsi_is_false(gsXmlWriteOpenTag (writer, WS_AUTHSERVICE_NAMESPACE, "password")) || + gsi_is_false(gsXmlWriteHexBinaryElement(writer, WS_AUTHSERVICE_NAMESPACE, "Value", encryptedPassword, GS_CRYPT_RSA_BYTE_SIZE)) || + gsi_is_false(gsXmlWriteCloseTag (writer, WS_AUTHSERVICE_NAMESPACE, "password")) || + gsi_is_false(gsXmlWriteCloseTag (writer, WS_AUTHSERVICE_NAMESPACE, WS_AUTHSERVICE_LOGINUNIQUE)) || + gsi_is_false(gsXmlCloseWriter (writer)) + ) + { + gsXmlFreeWriter(writer); + return WSLogin_OutOfMemory; + } + + aTask = gsiExecuteSoap(wsAuthServiceURL, WS_AUTHSERVICE_LOGINUNIQUE_SOAP, + writer, wsLoginUniqueCallback, (void*)requestData); + if (aTask == NULL) + { + gsXmlFreeWriter(writer); + gsifree(requestData); + return WSLogin_OutOfMemory; + } + } + return 0; +} + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void wsLoginRemoteAuthCallback(GHTTPResult theResult, + GSXmlStreamWriter theRequestXml, + GSXmlStreamReader theResponseXml, + void * theRequestData) +{ + GHTTPResult translatedResult = theResult; + WSIRequestData * requestData = (WSIRequestData*)theRequestData; + + WSLoginResponse response; + GSLoginCertificate * cert = &response.mCertificate; // for convenience + cert->mIsValid = gsi_false; + + memset(&response, 0, sizeof(response)); + + if (theResult == GHTTPSuccess) + { + // try to parse the soap + if (gsi_is_false(gsXmlMoveToStart(theResponseXml)) || + gsi_is_false(gsXmlMoveToNext(theResponseXml, "LoginRemoteAuthResult"))) + { + response.mLoginResult = WSLogin_ParseError; + } + else + { + // prepare response structure + if (gsi_is_false(gsXmlReadChildAsInt(theResponseXml, "responseCode", (int*)&response.mResponseCode))) + { + // could not parse login response code + response.mLoginResult = WSLogin_ParseError; + } + else if (response.mResponseCode != WSLogin_Success) + { + // server reported an error into reponseCode + response.mLoginResult = WSLogin_ServerError; + } + else if (gsi_is_false(gsXmlMoveToChild(theResponseXml, "certificate")) || + gsi_is_false(wsLoginCertReadXML(cert, theResponseXml)) || + gsi_is_false(gsXmlMoveToParent(theResponseXml)) || + gsi_is_false(gsXmlReadChildAsLargeInt(theResponseXml, "peerkeyprivate", + &response.mPrivateData.mPeerPrivateKey.exponent)) + ) + { + response.mLoginResult = WSLogin_ParseError; + } + else + { + MD5_CTX md5; + + // peer privatekey modulus is same as peer public key modulus + memcpy(&response.mPrivateData.mPeerPrivateKey.modulus, &cert->mPeerPublicKey.modulus, sizeof(cert->mPeerPublicKey.modulus)); + + // hash the private key + // -- we use the has like a password for simple authentication + MD5Init(&md5); + //gsLargeIntAddToMD5(&response.mPrivateData.mPeerPrivateKey.modulus, &md5); + gsLargeIntAddToMD5(&response.mPrivateData.mPeerPrivateKey.exponent, &md5); + MD5Final((unsigned char*)response.mPrivateData.mKeyHash, &md5); + + // verify certificate + cert->mIsValid = wsLoginCertIsValid(cert); + if (gsi_is_false(cert->mIsValid)) + { + response.mLoginResult = WSLogin_InvalidCertificate; + } + } + } + } + else if (theResult == GHTTPRequestCancelled) + { + response.mLoginResult = WSLogin_Cancelled; + } + else + { + response.mLoginResult = WSLogin_HttpError; + } + + // trigger the user callback + if (requestData->mUserCallback.mLoginCallback != NULL) + { + WSLoginCallback userCallback = (WSLoginCallback)(requestData->mUserCallback.mLoginCallback); + (userCallback)(translatedResult, &response, requestData->mUserData); + } + gsifree(requestData); + GSI_UNUSED(theRequestXml); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u32 wsLoginRemoteAuth(int partnerCode, + int namespaceId, + const gsi_char authtoken[WS_LOGIN_AUTHTOKEN_LEN], + const gsi_char partnerChallenge[WS_LOGIN_PARTNERCHALLENGE_LEN], + WSLoginCallback userCallback, + void * userData) +{ + GSXmlStreamWriter writer; + WSIRequestData * requestData = NULL; + //gsi_u8 encryptedChallenge[GS_CRYPT_RSA_BYTE_SIZE]; + + if (!wsiServiceAvailable()) + return WSLogin_NoAvailabilityCheck; + + GS_ASSERT(partnerCode >= 0); + + // allocate the request values + requestData = (WSIRequestData*)gsimalloc(sizeof(WSIRequestData)); + if (requestData == NULL) + return WSLogin_OutOfMemory; + requestData->mUserCallback.mLoginCallback = userCallback; + requestData->mUserData = userData; + + // encrypt the password (includes safety padding and hash) + //wsiLoginEncryptPassword(partnerChallenge, encryptedChallenge); + + // create the xml request + writer = gsXmlCreateStreamWriter(WS_AUTHSERVICE_NAMESPACES, WS_AUTHSERVICE_NAMESPACE_COUNT); + if (writer != NULL) + { + GSSoapTask * aTask = NULL; + + if (gsi_is_false(gsXmlWriteOpenTag (writer, WS_AUTHSERVICE_NAMESPACE, WS_AUTHSERVICE_LOGINREMOTEAUTH)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "version", WS_AUTHSERVICE_PROTOVERSION)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "partnercode", (gsi_u32)partnerCode)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "namespaceid", (gsi_u32)namespaceId)) || + gsi_is_false(gsXmlWriteTStringElement(writer, WS_AUTHSERVICE_NAMESPACE, "authtoken", authtoken)) || + gsi_is_false(gsXmlWriteTStringElement(writer, WS_AUTHSERVICE_NAMESPACE, "challenge", partnerChallenge)) || + //gsi_is_false(gsXmlWriteOpenTag (writer, WS_AUTHSERVICE_NAMESPACE, "challenge")) || + //gsi_is_false(gsXmlWriteHexBinaryElement(writer, WS_AUTHSERVICE_NAMESPACE, "Value", encryptedChallenge, GS_CRYPT_RSA_BYTE_SIZE)) || + //gsi_is_false(gsXmlWriteCloseTag (writer, WS_AUTHSERVICE_NAMESPACE, "challenge")) || + gsi_is_false(gsXmlWriteCloseTag (writer, WS_AUTHSERVICE_NAMESPACE, WS_AUTHSERVICE_LOGINREMOTEAUTH)) || + gsi_is_false(gsXmlCloseWriter (writer)) + ) + { + gsXmlFreeWriter(writer); + return WSLogin_OutOfMemory; + } + + aTask = gsiExecuteSoap(wsAuthServiceURL, WS_AUTHSERVICE_LOGINREMOTEAUTH_SOAP, + writer, wsLoginRemoteAuthCallback, (void*)requestData); + if (aTask == NULL) + { + gsXmlFreeWriter(writer); + gsifree(requestData); + return WSLogin_OutOfMemory; + } + } + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void wsLoginPs3CertCallback(GHTTPResult theResult, + GSXmlStreamWriter theRequestXml, + GSXmlStreamReader theResponseXml, + void * theRequestData) +{ + + GHTTPResult translatedResult = theResult; + WSIRequestData * requestData = (WSIRequestData*)theRequestData; + + WSLoginPs3CertResponse response; + memset(&response, 0, sizeof(response)); + + if (theResult == GHTTPSuccess) + { + // try to parse the soap + if (gsi_is_false(gsXmlMoveToStart(theResponseXml)) || + gsi_is_false(gsXmlMoveToNext(theResponseXml, "LoginPs3CertResult"))) + { + response.mLoginResult = WSLogin_ParseError; + } + else + { + // prepare response structure + if (gsi_is_false(gsXmlReadChildAsInt(theResponseXml, "responseCode", (int*)&response.mResponseCode))) + { + // could not parse login response code + response.mLoginResult = WSLogin_ParseError; + } + else if (response.mResponseCode != WSLogin_Success) + { + // server reported an error into reponseCode + response.mLoginResult = WSLogin_ServerError; + } + else + { + const char * tokenStr = NULL; + const char * challengeStr = NULL; + int tokenLen = 0; + int challengeLen = 0; + + // Check length of token+challenge, then read into memory + if ( //gsi_is_false(gsXmlReadChildAsBase64Binary(theResponseXml, "authToken", NULL, &tokenLen)) || + //gsi_is_false(gsXmlReadChildAsBase64Binary(theResponseXml, "partnerChallenge", NULL, &challengeLen)) || + //(tokenLen > WS_LOGIN_AUTHTOKEN_LEN || challengeLen > WS_LOGIN_PARTNERCHALLENGE_LEN) || + gsi_is_false(gsXmlReadChildAsString(theResponseXml, "authToken", &tokenStr, &tokenLen)) || + gsi_is_false(gsXmlReadChildAsString(theResponseXml, "partnerChallenge", &challengeStr, &challengeLen)) || + (tokenLen >= WS_LOGIN_AUTHTOKEN_LEN || challengeLen >= WS_LOGIN_PARTNERCHALLENGE_LEN) ) + { + response.mLoginResult = WSLogin_ParseError; + } + else + { + memcpy(response.mRemoteAuthToken, tokenStr, (gsi_u32)tokenLen); + memcpy(response.mPartnerChallenge, challengeStr, (gsi_u32)challengeLen); + response.mRemoteAuthToken[tokenLen] = '\0'; + response.mPartnerChallenge[challengeLen] = '\0'; + } + } + } + } + else if (theResult == GHTTPRequestCancelled) + { + response.mLoginResult = WSLogin_Cancelled; + } + else + { + response.mLoginResult = WSLogin_HttpError; + } + + // trigger the user callback + if (requestData->mUserCallback.mLoginPs3CertCallback != NULL) + { + WSLoginPs3CertCallback userCallback = (WSLoginPs3CertCallback)(requestData->mUserCallback.mLoginPs3CertCallback); + (userCallback)(translatedResult, &response, requestData->mUserData); + } + gsifree(requestData); + GSI_UNUSED(theRequestXml); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//gsi_u32 wsLoginUnique(WSLoginUniqueRequest * request, WSLoginCallback callback) +gsi_u32 wsLoginPs3Cert(int gameId, + int partnerCode, + int namespaceId, + const gsi_u8 * ps3cert, + int certLen, + WSLoginPs3CertCallback userCallback, + void * userData) +{ + GSXmlStreamWriter writer; + WSIRequestData * requestData = NULL; + + if (!wsiServiceAvailable()) + return WSLogin_NoAvailabilityCheck; + + GS_ASSERT(partnerCode >= 0); + GS_ASSERT(ps3cert != NULL); + + + // Version check, todo: use something better than length + //if (certLen != 248) + // return WSLogin_InvalidParameters; + + // allocate the request values + requestData = (WSIRequestData*)gsimalloc(sizeof(WSIRequestData)); + if (requestData == NULL) + return WSLogin_OutOfMemory; + + requestData->mUserCallback.mLoginPs3CertCallback = userCallback; + requestData->mUserData = userData; + + // create the xml request + writer = gsXmlCreateStreamWriter(WS_AUTHSERVICE_NAMESPACES, WS_AUTHSERVICE_NAMESPACE_COUNT); + if (writer != NULL) + { + GSSoapTask * aTask = NULL; + + if (gsi_is_false(gsXmlWriteOpenTag (writer, WS_AUTHSERVICE_NAMESPACE, WS_AUTHSERVICE_LOGINPS3CERT)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "version", WS_AUTHSERVICE_PROTOVERSION)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "gameid", (gsi_u32)gameId)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "partnercode", (gsi_u32)partnerCode)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "namespaceid", (gsi_u32)namespaceId)) || + gsi_is_false(gsXmlWriteOpenTag (writer, WS_AUTHSERVICE_NAMESPACE, "npticket")) || + gsi_is_false(gsXmlWriteBase64BinaryElement(writer, WS_AUTHSERVICE_NAMESPACE, "Value", ps3cert, certLen)) || + gsi_is_false(gsXmlWriteCloseTag (writer, WS_AUTHSERVICE_NAMESPACE, "npticket")) || + gsi_is_false(gsXmlWriteCloseTag (writer, WS_AUTHSERVICE_NAMESPACE, WS_AUTHSERVICE_LOGINPS3CERT)) || + gsi_is_false(gsXmlCloseWriter (writer)) + ) + { + gsXmlFreeWriter(writer); + return WSLogin_OutOfMemory; + } + + aTask = gsiExecuteSoap(wsAuthServiceURL, WS_AUTHSERVICE_LOGINPS3CERT_SOAP, + writer, wsLoginPs3CertCallback, (void*)requestData); + if (aTask == NULL) + { + gsXmlFreeWriter(writer); + gsifree(requestData); + return WSLogin_OutOfMemory; + } + } + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void wsiLoginEncryptPassword(const gsi_char * password, gsi_u8 ciphertext[GS_CRYPT_RSA_BYTE_SIZE]) +{ + gsCryptRSAKey sigkeypub; + + +#ifdef GSI_UNICODE + char password_A[WS_LOGIN_PASSWORD_LEN]; + // strip password into ascii to encrypt + UCS2ToAsciiString(password, password_A); +#endif + + gsLargeIntSetFromHexString(&sigkeypub.modulus, WS_AUTHSERVICE_SIGNATURE_KEY); + gsLargeIntSetFromHexString(&sigkeypub.exponent, WS_AUTHSERVICE_SIGNATURE_EXP); + +#ifdef GSI_UNICODE + gsCryptRSAEncryptBuffer(&sigkeypub, (const gsi_u8*)password_A, _tcslen(password), ciphertext); +#else + gsCryptRSAEncryptBuffer(&sigkeypub, (const gsi_u8*)password, strlen(password), ciphertext); +#endif +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_u32 wsiMakeLittleEndian32(gsi_u32 num) +{ +#if defined(GSI_BIG_ENDIAN) + num = gsiByteOrderSwap32(num); +#endif + return num; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool wsLoginCertIsValid(const GSLoginCertificate * cert) +{ + // Verify the signature + gsCryptRSAKey sigkeypub; + MD5_CTX md5; + gsi_u8 hash[16]; + gsi_i32 cryptResult = 0; + gsi_u32 temp; + + // hash certificate data + MD5Init(&md5); + temp = wsiMakeLittleEndian32(cert->mLength); + MD5Update(&md5, (unsigned char*)&temp, 4); + temp = wsiMakeLittleEndian32(cert->mVersion); + MD5Update(&md5, (unsigned char*)&temp, 4); + temp = wsiMakeLittleEndian32(cert->mPartnerCode); + MD5Update(&md5, (unsigned char*)&temp, 4); + temp = wsiMakeLittleEndian32(cert->mNamespaceId); + MD5Update(&md5, (unsigned char*)&temp, 4); + temp = wsiMakeLittleEndian32(cert->mUserId); + MD5Update(&md5, (unsigned char*)&temp, 4); + temp = wsiMakeLittleEndian32(cert->mProfileId); + MD5Update(&md5, (unsigned char*)&temp, 4); + temp = wsiMakeLittleEndian32(cert->mExpireTime); + MD5Update(&md5, (unsigned char*)&temp, 4); + +#if defined(GSI_UNICODE) + { + char profile_A[WS_LOGIN_NICK_LEN]; + char uniquenick_A[WS_LOGIN_UNIQUENICK_LEN]; + char keyhash_A[WS_LOGIN_KEYHASH_LEN]; + + UCS2ToAsciiString(cert->mProfileNick, profile_A); + UCS2ToAsciiString(cert->mUniqueNick, uniquenick_A); + UCS2ToAsciiString(cert->mCdKeyHash, keyhash_A); + + MD5Update(&md5, (unsigned char*)profile_A, strlen(profile_A)); //FIX for unicode + MD5Update(&md5, (unsigned char*)uniquenick_A, strlen(uniquenick_A)); //FIX for unicode + MD5Update(&md5, (unsigned char*)keyhash_A, strlen(keyhash_A)); //FIX for unicode + } +#else + MD5Update(&md5, (unsigned char*)&cert->mProfileNick, strlen(cert->mProfileNick)); + MD5Update(&md5, (unsigned char*)&cert->mUniqueNick, strlen(cert->mUniqueNick)); + MD5Update(&md5, (unsigned char*)&cert->mCdKeyHash, strlen(cert->mCdKeyHash)); +#endif + + // must be hashed in big endian byte order + // skip leading zeroes + gsLargeIntAddToMD5(&cert->mPeerPublicKey.modulus, &md5); + gsLargeIntAddToMD5(&cert->mPeerPublicKey.exponent, &md5); + + MD5Update(&md5, (unsigned char*)&cert->mServerData, WS_LOGIN_SERVERDATA_LEN); + MD5Final(hash, &md5); + + gsLargeIntSetFromHexString(&sigkeypub.modulus, WS_AUTHSERVICE_SIGNATURE_KEY); + gsLargeIntSetFromHexString(&sigkeypub.exponent, WS_AUTHSERVICE_SIGNATURE_EXP); + cryptResult = gsCryptRSAVerifySignedHash(&sigkeypub, hash, 16, cert->mSignature, WS_LOGIN_SIGNATURE_LEN); + if (cryptResult == 0) + return gsi_true; + else + return gsi_false; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Utility to write a certificate in binary form, does it really belong in this file? +#define WRITE_NETWORK_INT(a) { \ + intNB = (gsi_i32)htonl(a); \ + if(lenoutSoFar + sizeof(intNB) > maxlen) \ + return gsi_false; \ + memcpy(bufout+lenoutSoFar, &intNB, sizeof(intNB)); \ + lenoutSoFar += sizeof(intNB); } +#define WRITE_NTS(a) { \ + if(lenoutSoFar + _tcslen(a) > maxlen) \ + return gsi_false; \ + strcpy(bufout+lenoutSoFar, a); \ + lenoutSoFar += _tcslen(a) + 1; } + +#define WRITE_BINARY(a,l) { \ + if(lenoutSoFar + l > maxlen) \ + return gsi_false; \ + memcpy(bufout+lenoutSoFar, a, l); \ + lenoutSoFar += l; } + +#define WRITE_REV_BINARY(a,l) { \ + int i=(gsi_i32)l; \ + const char * readPos = ((char*)a)+i-1; \ + if (lenoutSoFar + l > maxlen) \ + return gsi_false; \ + while(i > 0) \ + { \ + *(bufout+lenoutSoFar) = *readPos; \ + readPos--; lenoutSoFar++; i--; \ + }; \ + } + +gsi_bool wsLoginCertWriteBinary(const GSLoginCertificate * cert, char * bufout, unsigned int maxlen, unsigned int * lenout) +{ + gsi_i32 intNB; // network byte order int + gsi_i32 lenoutSoFar = 0; // tracks bytes written to bufout + gsi_i32 lenTemp = 0; // for temp length of large ints + + + WRITE_NETWORK_INT(cert->mLength); + WRITE_NETWORK_INT(cert->mVersion); + WRITE_NETWORK_INT(cert->mPartnerCode); + WRITE_NETWORK_INT(cert->mNamespaceId); + WRITE_NETWORK_INT(cert->mUserId); + WRITE_NETWORK_INT(cert->mProfileId); + WRITE_NETWORK_INT(cert->mExpireTime); + +#ifndef GSI_UNICODE + WRITE_NTS(cert->mProfileNick); + WRITE_NTS(cert->mUniqueNick); + WRITE_NTS(cert->mCdKeyHash); +#else + if((lenoutSoFar + _tcslen(cert->mProfileNick) + _tcslen(cert->mUniqueNick) + _tcslen(cert->mCdKeyHash)) > maxlen) + return gsi_false; + // strip unicode to Ascii before writing this to the buffer + lenoutSoFar += UCS2ToAsciiString(cert->mProfileNick, bufout+lenoutSoFar)+1; + lenoutSoFar += UCS2ToAsciiString(cert->mUniqueNick, bufout+lenoutSoFar)+1; + lenoutSoFar += UCS2ToAsciiString(cert->mCdKeyHash, bufout+lenoutSoFar)+1; +#endif + + lenTemp = (gsi_i32)gsLargeIntGetByteLength(&cert->mPeerPublicKey.modulus); // size in bytes, no leading zeroes + WRITE_NETWORK_INT(lenTemp); + WRITE_REV_BINARY(cert->mPeerPublicKey.modulus.mData, (unsigned int)lenTemp); + + lenTemp = (gsi_i32)gsLargeIntGetByteLength(&cert->mPeerPublicKey.exponent); // size in bytes, no leading zeroes + WRITE_NETWORK_INT(lenTemp); + WRITE_REV_BINARY(cert->mPeerPublicKey.exponent.mData, (unsigned int)lenTemp); + + WRITE_NETWORK_INT(WS_LOGIN_SERVERDATA_LEN); + WRITE_BINARY(cert->mServerData, (unsigned int)WS_LOGIN_SERVERDATA_LEN); + + WRITE_NETWORK_INT(GS_CRYPT_RSA_BYTE_SIZE); + WRITE_BINARY(cert->mSignature, (unsigned int)WS_LOGIN_SERVERDATA_LEN); + + *lenout = (gsi_u32)lenoutSoFar; + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Utility to read the binary certificate + +// each read macro first clears the buffer (a) before writing +#define READ_NTOH_INT(a) { \ + gsi_i32 temp; \ + memset(&a, 0, sizeof(a)); \ + memcpy(&temp, bufin, sizeof(gsi_i32)); \ + intHB = (gsi_u32)ntohl(temp); \ + memcpy(&a, &intHB, sizeof(intHB)); \ + if(lenoutSoFar + sizeof(intHB) > maxlen) \ + return gsi_false; \ + bufin += sizeof(gsi_u32); \ + lenoutSoFar += sizeof(intHB); } +#define READ_NTS(a,l) { \ + memset(&a, 0, sizeof(a)); \ + _tcsncpy(a, bufin, l); \ + if(lenoutSoFar + _tcslen(a)+1 > maxlen) \ + return gsi_false; \ + bufin += _tcslen(a)+1; \ + lenoutSoFar += _tcslen(a)+1; } + +#define READ_ASCII(a,l) { \ + char temp[l]; \ + memset(&a, 0, sizeof(a)); \ + strncpy(temp, bufin, l); \ + AsciiToUCS2String(temp, a); \ + if(lenoutSoFar + _tcslen(a)+1 > maxlen) \ + return gsi_false; \ + bufin += _tcslen(a)+1; \ + lenoutSoFar += _tcslen(a)+1; } + + +#define READ_BINARY(a,l) { \ + int index = 0; \ + memset(&a, 0, sizeof(a)); \ + if(lenoutSoFar + l > maxlen) \ + return gsi_false; \ + memcpy(a+index, bufin, l); \ + lenoutSoFar += l; \ + bufin += l; } + +#define READ_REV_BINARY_TO_INT(a,l) { \ + int i=(gsi_i32)l; \ + int index = 0; \ + char temp[4]; \ + const char * readPos = bufin+i-1; \ + memset(&a, 0, sizeof(a)); \ + if (lenoutSoFar + l > maxlen) \ + return gsi_false; \ + while(i > 0) \ + { \ + temp[index%4] = *readPos; \ + if (index%4 == 3) \ + memcpy(a+(index/4), temp, 4); \ + else if (index == (gsi_i32)l-1) \ + memcpy(a+(index/4), temp, l); \ + readPos--; index++; lenoutSoFar++; i--; \ + }; \ + bufin += l; } + + + + +gsi_bool wsLoginCertReadBinary(GSLoginCertificate * certOut, char * bufin, unsigned int maxlen) +{ + gsi_u32 intHB; // host byte order int + gsi_u32 lenoutSoFar = 0; // tracks bufin index to make sure we dont exceed the maxlen + gsi_u32 lenTemp = 0; // for temp length of large ints + + + READ_NTOH_INT(certOut->mLength); + READ_NTOH_INT(certOut->mVersion); + READ_NTOH_INT(certOut->mPartnerCode); + READ_NTOH_INT(certOut->mNamespaceId); + READ_NTOH_INT(certOut->mUserId); + READ_NTOH_INT(certOut->mProfileId); + READ_NTOH_INT(certOut->mExpireTime); + +#ifndef GSI_UNICODE + + READ_NTS(certOut->mProfileNick, WS_LOGIN_NICK_LEN); + READ_NTS(certOut->mUniqueNick, WS_LOGIN_UNIQUENICK_LEN); + READ_NTS(certOut->mCdKeyHash, WS_LOGIN_CDKEY_LEN); +#else + + // parses ascii to unicode before writing into the buffer + READ_ASCII(certOut->mProfileNick, WS_LOGIN_NICK_LEN); + READ_ASCII(certOut->mUniqueNick, WS_LOGIN_UNIQUENICK_LEN); + READ_ASCII(certOut->mCdKeyHash, WS_LOGIN_CDKEY_LEN); +#endif + + READ_NTOH_INT(lenTemp); //size of the modulus data in bytes + // now calculate the length in int's + certOut->mPeerPublicKey.modulus.mLength = (lenTemp / GS_LARGEINT_DIGIT_SIZE_BYTES); + if (lenTemp % GS_LARGEINT_DIGIT_SIZE_BYTES != 0) + certOut->mPeerPublicKey.modulus.mLength++; + + READ_REV_BINARY_TO_INT(certOut->mPeerPublicKey.modulus.mData, lenTemp); + + READ_NTOH_INT(lenTemp); //size of the exponent data in bytes + // now calculate the length in int's + certOut->mPeerPublicKey.exponent.mLength = (lenTemp / GS_LARGEINT_DIGIT_SIZE_BYTES); + if (lenTemp % GS_LARGEINT_DIGIT_SIZE_BYTES != 0) + certOut->mPeerPublicKey.exponent.mLength++; + + READ_REV_BINARY_TO_INT(certOut->mPeerPublicKey.exponent.mData, lenTemp); + + bufin += sizeof(gsi_u32); //skip the prepended length + READ_BINARY(certOut->mServerData, (unsigned int)WS_LOGIN_SERVERDATA_LEN); + + bufin += sizeof(gsi_u32); //skip the prepended length + READ_BINARY(certOut->mSignature, (unsigned int)WS_LOGIN_SERVERDATA_LEN); + + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool wsLoginCertWriteXML(const GSLoginCertificate * cert, const char * aNamespace, GSXmlStreamWriter writer) +{ + GS_ASSERT(cert != NULL); + GS_ASSERT(writer != NULL); + + if (gsi_is_false(gsXmlWriteIntElement(writer, aNamespace, "length", cert->mLength)) || + gsi_is_false(gsXmlWriteIntElement(writer, aNamespace, "version", cert->mVersion)) || + gsi_is_false(gsXmlWriteIntElement(writer, aNamespace, "partnercode", cert->mPartnerCode)) || + gsi_is_false(gsXmlWriteIntElement(writer, aNamespace, "namespaceid", cert->mNamespaceId)) || + gsi_is_false(gsXmlWriteIntElement(writer, aNamespace, "userid", cert->mUserId)) || + gsi_is_false(gsXmlWriteIntElement(writer, aNamespace, "profileid", cert->mProfileId)) || + gsi_is_false(gsXmlWriteIntElement(writer, aNamespace, "expiretime", cert->mExpireTime)) || + gsi_is_false(gsXmlWriteAsciiStringElement(writer, aNamespace, "profilenick", cert->mProfileNick))|| + gsi_is_false(gsXmlWriteAsciiStringElement(writer, aNamespace, "uniquenick", cert->mUniqueNick)) || + gsi_is_false(gsXmlWriteAsciiStringElement(writer, aNamespace, "cdkeyhash", cert->mCdKeyHash)) || + gsi_is_false(gsXmlWriteLargeIntElement(writer, aNamespace, "peerkeymodulus", &cert->mPeerPublicKey.modulus)) || + gsi_is_false(gsXmlWriteLargeIntElement(writer, aNamespace, "peerkeyexponent", &cert->mPeerPublicKey.exponent)) || + gsi_is_false(gsXmlWriteHexBinaryElement(writer, aNamespace, "serverdata", cert->mServerData, WS_LOGIN_SERVERDATA_LEN)) || + gsi_is_false(gsXmlWriteHexBinaryElement(writer, aNamespace, "signature", cert->mSignature, WS_LOGIN_SIGNATURE_LEN)) + ) + { + //gsLargeIntReverseBytes(&cert->mPeerPublicKey.modulus); + //gsLargeIntReverseBytes(&cert->mPeerPublicKey.exponent); + return gsi_false; + } + + //gsLargeIntReverseBytes(&cert->mPeerPublicKey.modulus); + //gsLargeIntReverseBytes(&cert->mPeerPublicKey.exponent); + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool wsLoginCertReadXML(GSLoginCertificate * cert, GSXmlStreamReader reader) +{ + char hexstr[GS_CRYPT_RSA_BYTE_SIZE*2 +1]; // temp storage for key hex strings + int hexlen; + + GS_ASSERT(cert != NULL); + GS_ASSERT(reader != NULL); + + if (gsi_is_false(gsXmlReadChildAsInt (reader, "length", (int*)&cert->mLength)) || + gsi_is_false(gsXmlReadChildAsInt (reader, "version", (int*)&cert->mVersion)) || + gsi_is_false(gsXmlReadChildAsInt (reader, "partnercode",(int*)&cert->mPartnerCode)) || + gsi_is_false(gsXmlReadChildAsInt (reader, "namespaceid",(int*)&cert->mNamespaceId)) || + gsi_is_false(gsXmlReadChildAsInt (reader, "userid", (int*)&cert->mUserId)) || + gsi_is_false(gsXmlReadChildAsInt (reader, "profileid", (int*)&cert->mProfileId)) || + gsi_is_false(gsXmlReadChildAsInt (reader, "expiretime", (int*)&cert->mExpireTime)) || + gsi_is_false(gsXmlReadChildAsTStringNT (reader, "profilenick", cert->mProfileNick, WS_LOGIN_NICK_LEN)) || + gsi_is_false(gsXmlReadChildAsTStringNT (reader, "uniquenick", cert->mUniqueNick, WS_LOGIN_UNIQUENICK_LEN)) || + gsi_is_false(gsXmlReadChildAsTStringNT (reader, "cdkeyhash", cert->mCdKeyHash, WS_LOGIN_KEYHASH_LEN)) || + + gsi_is_false(gsXmlReadChildAsStringNT (reader, "peerkeymodulus", hexstr, GS_CRYPT_RSA_BYTE_SIZE*2 +1)) || + gsi_is_false(gsLargeIntSetFromHexString(&cert->mPeerPublicKey.modulus, hexstr)) || + + gsi_is_false(gsXmlReadChildAsStringNT (reader, "peerkeyexponent", hexstr, GS_CRYPT_RSA_BYTE_SIZE*2 +1)) || + gsi_is_false(gsLargeIntSetFromHexString(&cert->mPeerPublicKey.exponent, hexstr)) || + + gsi_is_false(gsXmlReadChildAsHexBinary(reader, "serverdata", cert->mServerData, WS_LOGIN_SERVERDATA_LEN, &hexlen)) || + + gsi_is_false(gsXmlReadChildAsHexBinary(reader, "signature", cert->mSignature, WS_LOGIN_SIGNATURE_LEN, &hexlen)) + ) + { + return gsi_false; + } + return gsi_true; +} + +#pragma warning(default: 4267) \ No newline at end of file diff --git a/code/gamespy/webservices/AuthService.h b/code/gamespy/webservices/AuthService.h new file mode 100644 index 00000000..dc99d619 --- /dev/null +++ b/code/gamespy/webservices/AuthService.h @@ -0,0 +1,199 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __AUTHSERVICE_H__ +#define __AUTHSERVICE_H__ + + +// ***** Authentication web services. +// +// ***** PUBLIC INTERFACE AT THE BOTTOM OF THE FILE + +#include "../common/gsSoap.h" +#include "../common/gsCrypt.h" +#include "../common/gsLargeInt.h" + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +// URL for sc services. +#define WS_LOGIN_MAX_URL_LEN (128) +extern char wsAuthServiceURL[WS_LOGIN_MAX_URL_LEN]; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define WSLogin_PARTNERCODE_GAMESPY 0 +#define WSLogin_NAMESPACE_SHARED_NONUNIQUE 0 +#define WSLogin_NAMESPACE_SHARED_UNIQUE 1 + +typedef enum WSLoginValue +{ + // Login response code (mResponseCode) + // -- GameSpy Devs: Must match server + WSLogin_Success = 0, + WSLogin_ServerInitFailed, + + WSLogin_UserNotFound, + WSLogin_InvalidPassword, + WSLogin_InvalidProfile, + WSLogin_UniqueNickExpired, + + WSLogin_DBError, + WSLogin_ServerError, + WSLogin_FailureMax, // must be the last failure + + // Login result (mLoginResult) + WSLogin_HttpError = 100, // ghttp reported an error, response ignored + WSLogin_ParseError, // couldn't parse http response + WSLogin_InvalidCertificate, // login success but certificate was invalid! + WSLogin_LoginFailed, // failed login or other error condition + WSLogin_OutOfMemory, // could not process due to insufficient memory + WSLogin_InvalidParameters, // check the function arguments + WSLogin_NoAvailabilityCheck,// No availability check was performed + WSLogin_Cancelled, // login request was cancelled + WSLogin_UnknownError // error occured, but detailed information not available + +} WSLoginValue; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define WS_LOGIN_SIGKEY_LEN_BITS (GS_CRYPT_RSA_BINARY_SIZE) +#define WS_LOGIN_PEERKEY_LEN_BITS (GS_CRYPT_RSA_BINARY_SIZE) + +#define WS_LOGIN_NICK_LEN (30+1) +#define WS_LOGIN_EMAIL_LEN (50+1) +#define WS_LOGIN_PASSWORD_LEN (30+1) +#define WS_LOGIN_UNIQUENICK_LEN (20+1) +#define WS_LOGIN_CDKEY_LEN (64+1) +#define WS_LOGIN_PEERKEYMOD_LEN (WS_LOGIN_PEERKEY_LEN_BITS/8) +#define WS_LOGIN_PEERKEYEXP_LEN (WS_LOGIN_PEERKEY_LEN_BITS/8) +#define WS_LOGIN_PEERKEYPRV_LEN (WS_LOGIN_PEERKEY_LEN_BITS/8) +#define WS_LOGIN_KEYHASH_LEN (33) // 16 byte hash in hexstr +1 for NULL +#define WS_LOGIN_SIGNATURE_LEN (WS_LOGIN_SIGKEY_LEN_BITS/8) +#define WS_LOGIN_SERVERDATA_LEN (WS_LOGIN_PEERKEY_LEN_BITS/8) +#define WS_LOGIN_AUTHTOKEN_LEN (256) +#define WS_LOGIN_PARTNERCHALLENGE_LEN (256) + +// A user's login certificate, signed by the GameSpy AuthService +// The certificate is public and may be freely passed around +// Avoid use of pointer members so that structure may be easily copied +typedef struct GSLoginCertificate +{ + gsi_bool mIsValid; + + gsi_u32 mLength; + gsi_u32 mVersion; + gsi_u32 mPartnerCode; // aka Account space + gsi_u32 mNamespaceId; + gsi_u32 mUserId; + gsi_u32 mProfileId; + gsi_u32 mExpireTime; + gsi_char mProfileNick[WS_LOGIN_NICK_LEN]; + gsi_char mUniqueNick[WS_LOGIN_UNIQUENICK_LEN]; + gsi_char mCdKeyHash[WS_LOGIN_KEYHASH_LEN]; // hexstr - bigendian + gsCryptRSAKey mPeerPublicKey; + gsi_u8 mSignature[GS_CRYPT_RSA_BYTE_SIZE]; // binary - bigendian + gsi_u8 mServerData[WS_LOGIN_SERVERDATA_LEN]; // binary - bigendian +} GSLoginCertificate; + +// Private information for the owner of the certificate only +// -- careful! private key information must be kept secret -- +typedef struct GSLoginCertificatePrivate +{ + gsCryptRSAKey mPeerPrivateKey; + char mKeyHash[GS_CRYPT_MD5_HASHSIZE]; +} GSLoginPrivateData; + +//typedef char GSLoginCertificateKeyHash[GS_CRYPT_MD5_HASHSIZE]; // Hash of private key, for simple auth + +typedef enum +{ + wsLoginType_INVALID, + wsLoginType_PROFILE, + wsLoginType_UNIQUENICK, + wsLoginType_GPTICKET, + wsLoginType_REMOTEAUTH +} WSLoginType; + +/* +typedef struct WSLoginProfileRequest +{ + int mPartnerCode; + char mProfileName[WS_LOGIN_NICK_LEN]; + char mEmailAddress[WS_LOGIN_EMAIL_LEN]; + char mPassword[WS_LOGIN_PASSWORD_LEN]; + char mCdKeyHash[WS_LOGIN_KEYHASH_LEN]; + void * mUserData; +} WSLoginProfileRequest; + +typedef struct WSLoginUniqueRequest +{ + int mPartnerCode; + char mUniqueNick[WS_LOGIN_NICK_LEN]; + char mPassword[WS_LOGIN_PASSWORD_LEN]; + char mCdKeyHash[WS_LOGIN_KEYHASH_LEN]; + void * mUserData; +} WSLoginUniqueRequest;*/ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// CERTIFICATE login callback format +typedef struct WSLoginResponse +{ + WSLoginValue mLoginResult; // SDK high level result, e.g. LoginFailed + WSLoginValue mResponseCode; // server's result code, e.g. BadPassword + GSLoginCertificate mCertificate; // Show this to others (prooves: "Bill is a valid user") + GSLoginPrivateData mPrivateData; // Keep this secret! (prooves: "I am Bill") + void * mUserData; +} WSLoginResponse; + +typedef void (*WSLoginCallback)(GHTTPResult httpResult, WSLoginResponse * response, void * userData); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// PS3 login callback format +typedef struct WSLoginPs3CertResponse +{ + WSLoginValue mLoginResult; // SDK high level result, e.g. LoginFailed + WSLoginValue mResponseCode; // server's result code, e.g. BadPassword + char mRemoteAuthToken[WS_LOGIN_AUTHTOKEN_LEN]; // Show this to others + char mPartnerChallenge[WS_LOGIN_PARTNERCHALLENGE_LEN]; // keep this secret! (It's a "password" for the token.) + void * mUserData; +} WSLoginPs3CertResponse; + +typedef void (*WSLoginPs3CertCallback)(GHTTPResult httpResult, WSLoginPs3CertResponse * response, void * userData); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Services to obtain a certificate +gsi_u32 wsLoginProfile(int partnerCode, int namespaceId, const gsi_char * profileNick, const gsi_char * email, const gsi_char * password, const gsi_char * cdkeyhash, WSLoginCallback callback, void * userData); +gsi_u32 wsLoginUnique (int partnerCode, int namespaceId, const gsi_char * uniqueNick, const gsi_char * password, const gsi_char * cdkeyhash, WSLoginCallback callback, void * userData); +gsi_u32 wsLoginRemoteAuth(int partnerCode, int namespaceId, const gsi_char authtoken[WS_LOGIN_AUTHTOKEN_LEN], const gsi_char partnerChallenge[WS_LOGIN_PARTNERCHALLENGE_LEN], WSLoginCallback callback, void * userData); + +// Services to obtain a remote auth token +gsi_u32 wsLoginPs3Cert(int gameId, int partnerCode, int namespaceId, const gsi_u8 * ps3cert, int certLen, WSLoginPs3CertCallback callback, void * userData); + +// Certificate Utilities, for use after obtaining a certificate +gsi_bool wsLoginCertIsValid (const GSLoginCertificate * cert); +gsi_bool wsLoginCertWriteXML (const GSLoginCertificate * cert, const char * anamespace, GSXmlStreamWriter writer); +gsi_bool wsLoginCertWriteBinary(const GSLoginCertificate * cert, char * bufout, unsigned int maxlen, unsigned int * lenout); +gsi_bool wsLoginCertReadBinary (GSLoginCertificate * certOut, char * bufin, unsigned int maxlen); +gsi_bool wsLoginCertReadXML (GSLoginCertificate * cert, GSXmlStreamReader reader); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif //__AUTHSERVICE_H__