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 zcmZ?ry~fD^24xHk3>pj!3=WJ84EziX42%pcVDSW~JeY$*G%zqUFfcGQG(aeZ1`w0s zz<~n{4GkcP{|pTO85kJ;|A$Zv|3OTK|Ns9pln;Kuz`$N!US7b!fa+KV_Hu^y^73*} zK!7{}qCpDF3&83?n!qfOI9MJmSzcb=z|aBWcQ9~(sPYO14v;ZmHq^3Xh~K~f zaUeuLSQ$fkd4Vw~9GKb5%iT-LT|nx~VeVr2|NsA&0+3JvnB?97GNv39PT=q>FJNE? z1tWWT1t{Pc*bBe`0J9fHqlI4qdwDqsgUkV$UCzJ(VOF$*oDVV!WLyEWv9Yl+JBs^C z%E3Wc0(O^$g~=BemoFgSFfg!#Tn2F;*ijJol`}9v+z0kK1A7BV6~r=dc!I4*gkJ*( z$N`WrM#zI41N90>8tfa8zdwN7XMojx9~c2Z>HlqJX-u0Tliqb3yLg4+%hU{4#*zx5Nb$ zegzB+kaz`$-xnA5^74v~a!B}ry$;e3asY^ix)0)TP&xrgKrMs#2_yhg334t&1BeAS z1VliSX8}VwG@W*Ub%NEfmlyno1YiLu7(o$TUS3k-QeIy0|Nj?6_H{MD z{{R2t4w5J@0A~`c92ubLGcUnMLF2D5PyT>_xJDLzdw|h z7Z`vfLGfE&4$=h%R9jCDz9h&*K08MK{Z3%2ey$N zlmH9<|K|tkE@*Dpz|GvuUS7`6zyJ~{*v$Y5KZr)MNx1t6n@|ACnBe>YF5h781IJD| x0|WO4a72O=V0n2t_ijk~BWy0!RDhBdD49VDAc*@QY>*XT*MTxTB*F^7G5|yhb0q)( literal 0 HcmV?d00001 diff --git a/code/gamespy/Chat/chatty/res/chatty.ico b/code/gamespy/Chat/chatty/res/chatty.ico new file mode 100644 index 0000000000000000000000000000000000000000..7eef0bcbe6580a6f464d688906172c2d9de44262 GIT binary patch literal 1078 zcmZQzU}RuoP*4zH0D%`w3=C=v3=9GS5WWT@0|Os31A_(w1A_ts1A_wtNIeTkDFXu& zgu)~n7#JED7#JEFAQVFbh{0$u!!4mJlOc22VH4mcU9Yfy(kji)O-t~iwdiU;K2gqsf-u3_g-@w4Y z_pSr%RuC5~0QSTWuowRS|K9=T{{R2u49HLa8{R?u^yADKkU#^-dJd568IV99$a)3_ z4v;KJ0O}@|GiQz*IpcH2=N;bzkXu=Nj`;W-IpcB!#y{e7U8JBkuPa@>s@qyL5 zxcKgn1xqKX5c4z0A2<;9fh`5`K?cDvE%;FX z;k57P%>O^<|D5@M=KTNvLH7UrKY#wupa1{={7)nwrXQ*v<`)nRG7m(WUZV1{DG{|pQa>1i8ze Sfq_Aufq_Ai#L#49U;qI24%F}f 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 zcmZQzU}RuoP*4zH0D%`w3=C=v3=9GS5WWT@0|Os31A_(w1A_ts1A_wtNIeTkDFXu& zgu)~n7#JED7#JEFAQVFbh{0$u!!4mJlOc22VH4mcU9Yfy(kji)O-t~iwdiU;K2gqsf-u3_g-@w4Y z_pSr%RuC5~0QSTWuowRS|K9=T{{R2u49HLa8{R?u^yADKkU#^-dJd568IV99$a)3_ z4v;KJ0O}@|GiQz*IpcH2=N;bzkXu=Nj`;W-IpcB!#y{e7U8JBkuPa@>s@qyL5 zxcKk8c%pQN0^Rjc?vtXi;Y6&HwR;99@{qZfemty;yk Y0AbF5kUo(4Fnd7k{~%Xk^AOQu0KBMYKL7v# literal 0 HcmV?d00001 diff --git a/code/gamespy/Peer/PeerLobby/res/icon1.ico b/code/gamespy/Peer/PeerLobby/res/icon1.ico new file mode 100644 index 0000000000000000000000000000000000000000..d07fb145d3956c1a15a84270bdd5ef2c453223b8 GIT binary patch literal 318 zcmZQzU}Ruq5D*Yx009j~1_m((1_liV1_qFb0s}~%1ti6A08C;d8W}J@#n_ 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 zcmZQzU}RuoP*4zH0D%`w3=C=v3=9GS5WWT@0|Os31A_(w1A_ts1A_wtNIeTkDFXu& zgu)~n7#JED7#JEFAQVFbh{0$u!!4mJlOc22VH4mcU9Yfy(kji)O-t~iwdiU;K2gqsf-u3_g-@w4Y z_pSr%RuC5~0QSTWuowRS|K9=T{{R2u49HLa8{R?u^yADKkU#^-dJd568IV99$a)3_ z4v;KJ0O}@|GiQz*IpcH2=N;bzkXu=Nj`;W-IpcB!#y{e7U8JBkuPa@>s@qyL5 zxcKM!iiv^Gpb8MxS(gY+cv`+8KC z$Plnn6k=dtU}0fl!ws03K~d(cDvHa@AU#P21_mZ3CR}DfOaR9oh+t)Av``SFyv(Cs zUMX`@_0$v>}EU|=AaR}vBuaGCMp!-p3yUR=0vVfpgqAt52Gte|3Bh>JDTnxaHS zjl5FnqUNq4#={OO4nUcNiGe|%fuWdzVJ!p0DF%i!M4){P3_T1C-V6-fAOjc}7?>Fu z4W#+=?39Sl8JH>&dF2ohZoul!(+mt{ASZFKFcmr|V_GpPLQ)7o^2*eyQ~%=zEiEm? zJqe!0_PrLV2Ea50JrQoS(ze@WC*$nO##*N%3)Big_D)3617MwcUCQNRKZnF zp*d!hTjQoqMqa6K0R=L0$V2+}#J9oV4YK9SmoqRhsEBYQ$3>R4qKT}4mzKD@hFGSx zqO+Q)t&(tvz7(<=2pg7HAUq<;WD9v-b{0@ZVP#;*XJ9xOc)rkw+ zOpMHojI7LzLR_rc61=8z0*XRhgAhd+N*CHIduWPjOYlnYu?ujr@~|=EL452i!dz_f zf}EzZ0^x?z$mz7uUfEMqTt|Xeil0M}iS8?n z94xHN&_EXFWw%lg%C=F2-+WV7d%^FW&HT@Ls(dtg^97)5iyEv zr6^S5sA8oc1c^Z{3EpZ~H4|9@ND@LONzW^ZW^x>mnGzuehFN%%7(B^S$k+iMECA(A z1{XD)Ly5@Vg0P`^1*{Q4urM%ife|>jL&fFsdgL?%WMgTPxx9=3CsYd)0|O^`FalP+ zLd6FkB`d(m#tdyNgIveNz(5dz)IqTj7i+AEY>k_`tC}dXIgE^q%!B|VBf?%0ZZ;)h zE(Qh$V*2(FyP24nxVgD`d3m|HxgkceFfqyr!lD482u9AFITLFZK^9C+O+{;lnac}8 zDm)o}4h|Nk2qWnlH+4$|AxIKJCP~aI$rkd^j3UFpupV#vqA-7;X+OfiU=Ixw4^5o2 zRLDMqu;b0-R7AjoYfKCb?hFjm7#Q|}yX&XHj5!PpX$%Zf3=Cp;{c@Ut0W_q~0QI{D zcsO|*tU^5s4vK0925qnl!x2on=iU|`^qB}*<`xbWo3lNW@* zlP6EkoH;Xh?p$|wcbHQM=M`2~)~Kkcl`B_1eE9I^&!7MQ|Nr&t*X`T4`}_N4Wn~!{ z7}!~ue6%HEO=Ka+R8D|_fuX3V2sv9|WpCNCg@J)VU5p2kJZjw3&E*BFUDd1)tP zg7u{M*jcJv)y(8T83?Wh!XPEDPS^XAQ1(;z{? zfB*h1TC@l|8{nE3=9m>#?Yan zYF9N21wlU@iE?MvN*A>l6Ip#Jz7nJk4ZJWWx?VAs1yysP3HEuEr8{CG2alTF3=Gns z94pDkUg1JuHH@bwc-Yw*+~~q%CP67kUSVfo*g~=2>7Q4WDfa#u28Id-R#w&*FJ2In z81bmNa^(smBP0HL1&?;P)S*L%7#SJ)IasP()GAz5S(zEx+1cN|eG6BBhf!Nw%fP^3 zqa+N^1l6vfEMl!FWThw+s3(a$mH}5&dLPC`1 zm4E;KX={VJhe_u0@ur|2jH#(9F%J0u|NrjYyBQc5f;}0o38d(-Nz8Rf7&}@1i)dK=MinUUN=^d&|f0su_^H5{=hMr$KcF zgEx4|9G*zo#Ng`Yia#+PK72SLBI5Aj!}yiy>grOSSN{M1pPru1z`)?IE9syD>Z}wO z7vr}VF8lH0M@~*o7A8hgm$nm;SDaNvL6r&z1H%Tqp^hh{sV;Sl0n}eb9ngSRClE%t zvnmrK0|O6ivZylTLxByv2tY371%3-f}CJp z0Yxxz^DFrC3am%R(2h5thI?Mo*Vl)nM_eSqyaHKg%)r2~ZQC|nhW!8kf9cYt3=9my z+@S3oCMG5%^`&;~*ulWSAWveK1#e!da#0h6Y}rc08~=DxG)<-YK$(G&ks;q6%W8RI z^NJwI5TfQH@#hsyusIA2!!@rMn;37w3wU{XLB4Nq$CJSS|Nl?jyaEaIl9CdvmO|z+ zF{R$Wf6va&&dSV4a3~eI^u?Q3q9May%oNOI(KN4|WMEJL1(p-G)$-KOD|luz!K11S zkmVdhv|e$-yBBT^xHkq0Z0wksnc3FXcIC>I_&XW@|Nqz4*2dpoA*o)0xCU2e<@oXA zm>K2&|NsB~{j;#JU|?W~GX<^PLCz3(*m(1bfiyqJE#}}#Wd(TZ`vB>K^;;Milo=S5 zaG;}L!->yQr@;$K%E7Bl&M+`!fE*~s!;ZP_fckj_e<%4gc&De-kgZoh0Z9O}GBavR z@Nz=7Wy92}si`$LH{ZE)2WzTC6a4k-7at!Vb@K`{Gc&HvN@r&$ngih6($Z1}1_lQe z680qF&MQ@}YS0xe)!^g|x|;$#W?~3Vq4RMs^*~Gb)v!>-O78(1j-~~ZyPtuf8+<`ct%`UOPx?TPQNfEK8NGbnQAAeBAX^2%v&nRkK^xe?I@v$M0?ym>ROGzc%aF&Y2< z{R;{TqIzDzGqa+nr-!S;d;k7D=!gtvCitQ0}!MJ7cKzn4FE~&O7fw3jwX49fq}uC zfnfzWOi0Zo5cPvCuRvTyklX^wD~ybcy}i8zdnA~-D<3|5U}R*RGiMIO8JJ{oaWQD} z6x+ZMT3Ui|apx6z$c~y!aPm)pc#;s=4_1tvSEBH0NyShPnIs2oi^i)FA}Iqd-=~2% z94zP?d})zaa%@4XwGoE`DlssWGcX*)<_qLNKxPl+ymIj1LCh3~OXT0be@~x2?d$8) z(a{0L5CemP5SNYwXk3|!dBw=c2$ut{T)6_e;<2U%mx2u&K+Pp()Gfu3Y=M=;omZfT z;WdL3f-}Uocu7$P2DH^sC!uSCv1=2;-Bpgnt^-|^gMr~Nc=Hmdfxv)vydW*|ik=jo zkqp1K1TQP{HbY+Uxd22i02;D+g_(u<{rmU0(jBt+hYueXELh;?=ElN;=w|YBuo%kl zi-WgyP%*C*78W8~jm+M*4YVd9CME`15Sjhv%^PNBW)2o6B4=4}=M`vjm}qU1@YZr z%PVuh@0BA5)w8n#4pqPi@&^9s~wkjZLAC=VaC93z>y;8V_!hoK%GOMAf^ z!tv^cO0HyJ0F6R|c&U~s3&V)bE8u;Tpe@31+c6oCVZdMphFaVR5=4v)3`=qQiSAPMWMgBSJ$v?C z+(1D=0e@a;Z*K>CjA7@_otUW`F0yp#QZ_cW*RNm0MbH>kRaKz1_i}Q01|#zG^O5(A zVQqTB1qtSrMc}9a?GS)?05^$tPQ@Mu2JkjYoEn(HQzpm3=EIW!gmIdIK^><)6bVSe z1Ph@pqAYa+uVxehk1Ior#Y*~tH9KN+23$QPui&4Mw*;HRKxh*p!FuI5>?lF}TL4yo zPiST)WL+4cyfP2wY(i@%3FZ|`OHdH-@$von^9L>2B6A6@aXEbWFyyS_Cr_RrYeZwu zn>P+B_p zHsD`KPfT8c>}+RYVAzf~+Y!twXTWC}OeA6=XEC9Sa)yDynNVKY3qFblv_c)Pv$rr1 z%qtZY6`+}L2M7FPWd!rezkmOZ9zA;T;zf9h#>!}HY{Y*U1!S)<0|UeD+qbcr@c;k+ z=g*(Bu&`jB{DziGAY9ye1*N~D3r&wCPy*lsOTb52pzG2F&!8*?pRe7(0NV2bQNhc= zU@5ZI<|NsBGx}e0JY=P2%hhz>k5_et^fUGcv zjDtCXQzP+&C~TkIb_ND7By9=|43oeEy~uICn}Gqm@E@wq9UQ|@4ql2KJYKs3q=pH6 z&>foRpi@LycvAtgOK`Cvc_p4=bIyQ$O1HeS3EXeDqlUA=!!&pbXgrA#S?bG|FB}{k zl;@Q%U%rToi@$vN64`KM_Sdgpd3bn0DT9Gw_UzfnQfTa~tSklw23I1^ox`11Q07-+ zA+{1t1|r4JX$FQIutqy@(#Dz|6Tu2dB_zPv4!km-i;Wrci0~K_a6d~Nx4l>$gDD8f zE9!*0KA0NOL_m#WLT4VKX~D`Rawj(=)`SSPLa|zbCJ2t8xpU{DB{WjGn>KACxWNP{^e(?z{pmlp*u>jc|*JV+0`O zmE+(hd>HtoB;+0ma%_Osd=h6evMO7!Eud3$KWHRg9@4Egkkzkf3W(?K zW?%rXnlmskz&{j*CrLt3JUNjgDlbn2RB1>bTl57 zPoF+zW@hGOWx`yl4oO9D5_eufSq}zV&I_@eFe$^pkO;m{#esprmVv>QfguunkbVFI zLlF1?#B}hY`UT)ME+@hLPr`;`sDL!C!1JWWGFT3vt#MP&v{vMRoG9x9whJ?X90R*V ziGcynaWC+^!Ua}L#?qDp3=Huw_v6ni72xwRA&!2Vc)`^LUF)%RTnOXV&|3BoIZz1rRD%RH4 zxLgI<%c7|X+Ng&%HU!Tia0c$Yl8Uy@7%~q5@hn!73EWHfWMC*|U|0Yyrg5FmcnU1L z0G#g9!J}v9pu@GHd!?`%j#Y3zI9-58+x&DS;C@15#G1%LSEX=)$K2Au*AJC}ZyR%F zV1Vo)294l=lXE-{65RHMY~});>FUeCkO?-Y5}XWiktN_m8||Sfml<-nuP6>HAXb~h zys8MUhqA!utdxMY=z{wn;06QKC=Lb&3kHTb@L{Tz;B(l65NjojWd*8T)$EjEJ``hM z@BpvdF2(C+h+py;7{bBLMetdKVm$207V>=H1K-)$*c==jii?YTdU~c40=>PxWo2b< zZf=kXN=N{@y1JI*29lDX^Ke;MSj^4Mb8~aMy1J%MpWf5cla-aFp#eTI792v^+1a?w zD0g-S6`_XU4OjvK0(exGmzP6~#dAX?Bt_xQE9m=IBET-hL#Tq6(8G%)G8ntTnXHMp z$rUm1GV;YB2Qe}*aI4?5}my7bSA>WD+W*{xWhkvpQWGPn6!NO#z zAOyL@v(R4IP==qC8It0$D#t0v$;xD{C}avbkrAgVgaob-;D($Mj8KS=<)tM~ylXX( z4mpNo4m1*XUa4_YHvnf#&?@vZ;Cz73SztNHQVGgWa6Jlcu;Gsq$gx-8MV%6S=!=Ze zdzMC5|e@*y5!no@g$YZKD`%EaR;uZmT40ttjNCE*57hi+0ybiK9w{ zk+iG2DCo>Y1tA+HVQ($*G)o1nP48?Q#Xwz2Vw`QIAY`i~?5QapXDSQvG9&;YWSNs{ zw6To4hM0|#Fn;e_ft}>80djdI`0AQOb2%bIAkR)I-c;686Xbn}d;D}Ha%`27%;kwR z!Ana#$5tsoSCUBe1aHvAn^&O6O+xnDpJHGja``Vixc@=nxi65mIjs2r&WT6_BY1A+ zIP~~>to!XD5j#qbh5)rf0B>GFKgXpBDYX)03WJA=i^1K3ECz-W28Md@j{Ujd4fFfK z1DOP`mD~!R(8RRM7Cczg4U%ADWXQL}a*hkNLI`3`rHfjgol>#|=tR(XQ`uw-`Fwk2 zNLdL{FiH;l5Wt&PYTVSFk?~LjF%wS^GYjpNUDQP7 z1v#OEX~@oCVBlnBRubm&&=i9-u^?I?WSD_8R;&3qSRf}fnaB!YRUjwG328y$Qh-$# zLBSN*ytJk`FG0oBQXt65D#pXEEW&LeFBoPZP5hats-oOPx!|;+UA2 zh&gbu%0bKzsIeC97Q3_}E!AH8nw^NZ7>%v@{!%uC*n2`S|#dBndOs)j=yx zLqkK6l|syfEIUs!mt$aHaCdh{R*;yOsHX?obfKoEh9-`m%g@ix&CQKRO>AtezrVkc zkr8ANjFXkgURju!93#cYAuTP9$7HxvR8*9Mg99HQALO73JI@%;*aal7OqntTd2IdLw{L80Yy|U4g|n(M_%^Wc@bIfwuVN1L!_EBn z@89Xur#(D87#J9ICHWw02TGk(SeY3!Gc)1JAdEkM{s;>T>q+rJq7ifL#q;OS+1S{u z6hIf{XiM;_sHi|R5=o||r7@nhGnU4emtjEsz8JnT7yZk3ke<8X6x!)h8IhyOP?A>+rTLkdm}bqIg(sIF$H3pee_L8w7#SHM*IWeXf@TN~ z9Xf=p2Eq;r3E}5paa9*(WMss8#E84QyC5fPwW}H}@`{tH2qPoo$B!Q&j)9Qy~Px)_hUhFAgkTx3IOenCM&%=PD+Hf_?<(qds@VP$1CG&ID# zXyozZ$L#FvCb9y|OpIvzz~R~A)~#ER`*f3&lObsbQsBbbZ{ED&9}nR;NjuHY=f;{y}A$#ii(Q5 zySuTrVB_NASeY41oe&pi!*dCQfiftZ*WvQAyU5p1= z{bdTvPY~;fB#G#+Kup4NUt}3{$p!wb0vakt&J$SK8(}++aG7NR?$aD)0Nqp%zAqB< zph<{3;Uw0)^7H4li<-XY+9jWPT-7koPhznjfRntAp&PhkWfG=GpDX;wa@x#Hv zfrpKmn~fQ>UU~fZF{W(?4<2M-VDQzJ(39fBnpe1C`y&eMm1PAu<>cgk|Nf2a!MAVU zGBY#dnL);ySLV*0TMGkiZEdSpul|dAnbGm%#~BzHJTyR8x*N+1u&}V4IdcZtUI_cm zn>Uh@l2UvekbxL@f+5HtC9l-DsiWU-f0}_|2Ga6FXxc`lguo}yX@a*o34+f7*I;0P zpcwEa^ChV2k$Ftu^~xu~c?EXXE9P;mSba%SUV((-zkmNA=c{3E)Cdm`7vy3^wup@x zy`lC0|NrLZW(Ed^s;Vl8fe`ZCxpSgl@RjNr%#Z5 zN|<>Cq5zYWm6atguRsnD){)@lWoN<6E0`Aj|NsBaojXM4l^Qp77d6l*#)l6dkd6BG z?HeyIFJ!n6IY{Abta%051Wfk%^XHY7m3i4&N}W^^&E*&w8S?V-FqJ_>Aa}PATr3H9 z4}?KtUa4_YPc)O`fUI&CVqlmJ?&@y`=LY07PCQ$ffq?^uYB2_eiQvxsPUy8nj7Zls zL!6I?#F|(B{Q0wG%a%=Guy5bK=g*%*0uN3?Hr!2{HVrNUVI(Cb@xY498aMSaCsk%9 zM$An_kUMVC_KNrQfwrT;8(q(zKS#ca-PhNbm6i3)n>P@PAY?=YXsIey^2&=BFTmaf z5&QS=N83yI@87?&vN8q+21Z6K{S}B~&`6TJt#gI%o*34>)?p?orJ?MN9jQN#w=gw`w21G(?(?S=pwhCP-{R$lt$zi->@>*vQMvqnY{t|NkRLpr_EF zHQRpt_`%Q5k6Ewa>D$A@8Gl|85)#6!OOfSSY$RyUh^5e%3XJ=JpBLv|M&0T zn>KB#tE+2lY}~zjH(J2M`{Xxn+<jUMIgld@jg2+0tXQ$48wUFN`Y>-^ zojMhC(mV$T2U<9P{rVNMe{9E&9ms+F9KNBN?%jdA>6@unNPGqUAtwpQ9A%o&9OpH!W zPH0m5_wQ$70&Ql4q#67q#d)R1O})}Z&0Rx`haI-ukqLYXRtW>cT5vxGK2CfRe4grI z@SxUlSmuDp?gJla=MBD&9vtJ$jEn}-{CRkHJL2~l8F}U4!Gq%B;(}bPu_m&t%#7;l z>c@{CM+;rd82k6{->zM|q@_Vqq+J+;%>e-cf}E^XE@}dttU*CR$lBk(f6vOwYON>~ ztS`mDz<_qS;m@Bx@oyAIHtPTX|K#PBU%!5(r>8S8Ffb9_w};qEYF_#K_wT`j2aSx3 z7#SEmG{x}89T|D$(xpp~Rx4V?`SxyNbYP=4qm=nBibyGL;oTJIEVi z1xX|%uauXU!x|ts>3#e5O_?&q-QAsuiAj))71EB5HHa%9=EW##4N`uh5ejEubOETINc1mjVPkHgZ^62Hk1 z*<;6!EnBuMEiDZ)%HG!21`&sokU<%5E%6dZ6%H0AUteFi6oj#3#}2ebm$;lqy}Sak zq1sg~-b~I^PCx*16dxq|h#=XR8P!C2{BYyTmYq}L+szJO-BfD9c807>xJv2e1h{Qyn6d!EZ zh&Ts9)=J<~%gx3N>5)K2FL5dJA~ecM?YsgHiE3B1ENewS9SJizK_y`>VJ4W;8uWz#GbkQe(#8p%*@GNuWUMl$JE3ZzDEtcfh9QCLMF=BHUIV3makCc&%`2lJKwJon z<`v??X{42*qyEJV0a#vv4-H}}85J1~fsqyhweA|2UWVnBI(LmxG#Ub~i%>#rb-jj~5WU^E0ULx6z+ E083;6OaK4? literal 0 HcmV?d00001 diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/res/logo270x83.bmp b/code/gamespy/Voice2/Voice2BuddyMFC/res/logo270x83.bmp new file mode 100644 index 0000000000000000000000000000000000000000..01142a32b949be9f8199ca9e796c5e6c0a0a6549 GIT binary patch literal 67450 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 E083;6OaK4? literal 0 HcmV?d00001 diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/res/microsoft-microphone.bmp b/code/gamespy/Voice2/Voice2BuddyMFC/res/microsoft-microphone.bmp new file mode 100644 index 0000000000000000000000000000000000000000..71d275831911a56037edf1405826a9087bcff67c GIT binary patch literal 3126 zcmZ?rHRE9b12YB&1`P%V1_dZ)WRL)hGeG4b90+;h!W9Mvh7%XAkct`_8vcVp0~p{j z1dkLRO9zkn2792Z;hA&4+{%Nwz?O1wx`(jg{Nb z&~V_u0V1t{20e@-UKhj){OTbgbKt-MNZ>FqFhCesosA|4@dZRZE?eN{|Ns9V;vR^4 zG^4O`A?Aaf!~jkjSaLVA`Tzg_hXfKX$6(sh(7*uJ1u`G26$}g@1qYA-@$Nry;R@J% zkO>V9Sge2~7>E_%5(q>zG&Eqk6ORa*6>u}b#xnf>4;6rf45@}g>_xT$9vv`2kogd0 zl#npfK+c7P)&KvX5Fh%ysp5Eu-$AB>F~k>CbUDO)2nn_UK|loIBmld2PcQ%g 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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 Q*?dek*~XHpk(d$*0Q@TB@Bjb+ literal 0 HcmV?d00001 diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/resource.h b/code/gamespy/Voice2/Voice2BuddyMFC/resource.h new file mode 100644 index 00000000..a4a8a2c5 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/resource.h @@ -0,0 +1,45 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by Voice2BuddyMFC.rc +// +#define ID_SETUP 3 +#define ID_VOICECHAT 4 +#define IDM_ABOUTBOX 0x0010 +#define IDD_ABOUTBOX 100 +#define IDS_ABOUTBOX 101 +#define IDD_VOICE2BUDDYMFC_DIALOG 102 +#define IDR_MAINFRAME 128 +#define IDD_LOGIN 129 +#define IDD_SESSION 130 +#define IDD_SETUPDIALOG 132 +#define IDB_SPEAKING2 134 +#define IDB_MICROPHONE 135 +#define IDB_SPEAKER 136 +#define IDB_GAMESPYLOGO 142 +#define IDB_SPEAKING 146 +#define IDB_GAMESPYLOGO1 147 +#define IDC_BUDDYLIST 1000 +#define IDC_EMAIL 1001 +#define IDC_NICKNAME 1002 +#define IDC_DISPLAYTEXT 1002 +#define IDC_PASSWORD 1003 +#define IDC_SPEAKING1 1003 +#define IDC_CAPTURECOMBO 1004 +#define IDC_SPEAKING2 1004 +#define IDC_PLAYBACKCOMBO 1005 +#define IDC_SPEAKERBITMAP 1006 +#define IDC_MICROPHONEBITMAP 1007 +#define IDC_VOICELEVEL 1028 +#define IDC_ACTIVATELEVEL 1029 +#define IDC_ISSPEAKING 1031 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 135 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1005 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/gamespy/Voice2/changelog.txt b/code/gamespy/Voice2/changelog.txt new file mode 100644 index 00000000..4c58820b --- /dev/null +++ b/code/gamespy/Voice2/changelog.txt @@ -0,0 +1,105 @@ +Changelog for: GameSpy Voice SDK 2 +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 2.08.00 RMV RELEASE Released to Developer Site +12-12-2007 2.07.04 SAH FIX Fixed OSX to use mute bool in playing packets +12-11-2007 2.07.03 SN FIX Fixed floating point precision for sample app + SN FIX Cleaned up some compiler errors for PS2 support +12-07-2007 2.07.02 SAH OTHER Fixed all VC6 projects to use Speex 1.0.5 +12-04-2007 2.07.01 SN FIX Removed some test code from PS3 headset listing function + SN FIX Commented out SPURS define from Voice2Test for PS3 PPU testing +11-27-2007 2.07.01 SAH CLEANUP Moved extern "c" block below includes to prevent linker errors +11-08-2007 2.07.00 SN FEATURE Added initial SPU speex support for the PS3 +08-22-2007 2.06.01 SN FIX Fixed support for 16Khz audio on the PS3 + SN FIX Cleaned up some code for VS2005 +08-20-2007 2.06.00 SN FEATURE Added support for 16Khz audio on the PC +08-06-2007 2.05.00 RMV RELEASE Released to Developer Site +06-20-2007 2.04.00 SN FEATURE Added support for PS3 USB Headsets. + SN FEATURE Used speex as a codec for PS3 USB Headsets for now + SN FEATURE Using speex 1.2 beta1 + SN FEATURE Usung GSM 1.0.12 for platforms using GSM codec +06-20-2007 2.03.01 DES FEATURE Speex is now more compatible with other sample rates +12-15-2006 2.03.00 MJW RELEASE Released to Developer Site +12-13-2006 2.02.14 MJW FIX Fixed VC7 projects +10-05-2006 2.02.13 SAH FIX Updated MacOSX Makefile +08-02-2006 2.02.12 SAH RELEASE Releasing to developer site +07-24-2006 2.02.12 SAH FIX Added new NatNeg files to Voice2 MFC app to fix compilation error +07-06-2006 2.02.11 SAH FIX Fixed PSP project file to not explicitly include the PSP linker file + SAH FIX Added PSP solution along with project file +05-31-2006 2.02.10 SAH RELEASE Releasing to developer site +05-20-2006 2.02.09 SAH FIX Added GSI_UNUSED calls to Voice2Test, fixed PSP warning levels +05-15-2006 2.02.08 SAH FIX Added "PS3 Release" configuration to project +04-25-2006 2.02.07 SAH RELEASE Releasing to developer site +04-24-2006 2.02.07 SAH FIX Got rid of unnecessary source files in Voice2Test project +04-20-2006 2.02.06 SAH FIX Added a missing ')' in ps2Headset +04-18-2006 2.02.05 SAH FIX Added || defined(_PS3) in voice2Test.c for PS3 support +04-13-2006 2.02.04 DES FEATURE PSP and GSM support + DES RELEASE Limited developer release +03-21-2006 2.01.03 DES FIX Unicode support + DES RELEASE Limited developer release +03-16-2006 2.01.02 DES FIX Fixed OSX when using a device with more than one channel. + DES FEATURE Added channel API. + DES RELEASE Limited developer release +03-15-2006 2.01.01 DES FEATURE OSX volume is now controlled in software by default. + DES FIX No longer hangs when trying to stop a closed iSight + DES FIX Detects when an OSX USB device is unplugged +03-14-2006 2.01.00 DES RELEASE Limited developer release +03-14-2006 2.01.00 DES FIX Moved OSX encoding and decoding out of the IOProc functions. + DES FEATURE Added raw codec for getting unencoded audio + DES OTHER Changed decodeCallback to decodeAddCallback and decodeSetCallback. +01-27-2006 2.00.25 SN RELEASE Releasing to developer site +12-22-2005 2.00.25 SN OTHER Cleaned up projects and added missing common code if needed +11-14-2005 2.00.24 DES FIX Updated OSX support. +11-11-2005 2.00.23 DES CLEANUP Updated dsp projects to use Speex 1.0.5 +09-21-2005 2.00.22 DES FEATURE Changed DirectSound include from mmreg.h to mmsystem.h + DES FEATURE Voice2Test is now setup to work with Speex 1.0.5 +07-28-2005 2.00.21 SN RELEASE Releasing to developer site. +07-18-2005 2.00.21 DES FIX gvGetCustomPlaybackAudio no longer always returns true. + DES FIX Custom playback devices were trying to write too much audio data. +06-03-2005 2.00.20 SN RELEASE Releasing to developer site. +06-03-2005 2.00.20 SN FIX Fix support for old and new eyetoy library. +05-04-2005 2.00.19 SN OTHER Created Visual Studio .NET project +04-28-2005 2.00.19 SN RELEASE Releasing to developer site. +04-25-2005 2.00.18 DES CLEANUP Disable Win32 linker warning. + DES FEATURE Updated to use Speex 1.0.4. +04-04-2005 2.00.17 SN RELEASE Releasing to developer site. +03-18-2005 2.00.17 DES FIX Added coded to ensure that DirectSound always pairs calls + to init and cleanup COM. +01-27-2005 2.00.16 DES FIX Removed GV_CUSTOM_SOURCE_TYPE define from Voice2Test.c +01-19-2005 2.00.15 DES FIX Fixed bug when checking GVI_DYNAMICALLY_ALLOCATE_FRAMES. +10-04-2004 2.00.14 DES FEATURE Completed OSX hardware support. + DES FIX Packet capturing code no longer assumes voice pointer is valid. + DES FIX Fixed voice2test endian issues. + DES FIX Voice2test no longer assume a capture device was found. +09-16-2004 2.00.13 SN RELEASE Releasing to developer site. +09-02-2004 2.00.13 DES FEATURE Added initial OSX hardware support (not complete yet) + DES CLEANUP Moved code for managing device lists into gvDevice.c/h +08-27-2004 2.00.12 DES CLEANUP Fixed warnings under OSX (no harware support yet) + DES FIX voice2bench will now use Speex on all non-ps2 systems + DES CLEANUP Updated Win32 project configurations + DES FEATURE Added OSX Makefiles + DES FIX Typo in the CORE_INPUT_L CORE0_INPUT_L define. +08-10-2004 2.00.11 DES FIX An lgAud device can now only be used once at a time. +08-05-2004 2.00.10 SN RELEASE Releasing to developer site. +08-05-2004 2.00.10 SN FIX Fixed missing prototype warning for codwarrior +08-04-2004 2.00.09 DES FIX gvGetDeviceVolume was using the wrong formula for getting + the volume from DirectSound devices. +08-03-2004 2.00.08 DES FEATURE Now differentiates between lgAud device types. + Possible types are headset, microphone, and speakers. +07-23-2004 2.00.07 SN FIX Updated code with explicit casts to remove implicit cast error + when compiling at highest level and warnings treated as errors. +07-22-2004 2.00.06 DES FIX Fixed eyetoy device bit + FEATURE Voice2Test now shows what types are being + used for created devices. +07-14-2004 2.00.05 DES FEATURE Added GVHardwareType for checking a device's hardware type + FEATURE SPU2 playback support for PS2. + CLEANUP Split PS2 devices into seperate files. +06-29-2004 2.00.04 SN FIX Changed the ouput path for the object files +06-29-2004 2.00.03 BED RELEASE Releasing to developer site (Beta) +06-28-2004 2.00.03 DES FEATURE Setup lgVid to use the GSI memory functions. +06-23-2004 2.00.02 BED FIX Removed gvSpeex files from PS2 builds +06-22-2004 2.00.01 DES FEATURE Added support for the PS2 EyeToy as an audio capture device. +06-08-2004 2.00.00 DES RELEASE Limited developer release +06-04-2004 2.00.00 DES OTHER Changelog started diff --git a/code/gamespy/Voice2/gv.h b/code/gamespy/Voice2/gv.h new file mode 100644 index 00000000..ca970462 --- /dev/null +++ b/code/gamespy/Voice2/gv.h @@ -0,0 +1,234 @@ +/* +GameSpy Voice2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2004 GameSpy Industries, Inc + +devsupport@gamespy.com +http://gamespy.net +*/ + +/************************ +** GameSpy Voice 2 SDK ** +************************/ + +#ifndef _GV_H_ +#define _GV_H_ + +#include "../common/gsCommon.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +//DEFINES +///////// +#define GV_BYTES_PER_SAMPLE (sizeof(GVSample) / sizeof(GVByte)) +#define GV_BITS_PER_SAMPLE (GV_BYTES_PER_SAMPLE * 8) + +#define GV_DEVICE_NAME_LEN 64 +#define GV_CHANNEL_NAME_LEN 64 + +#define GV_CAPTURE 1 +#define GV_PLAYBACK 2 +#define GV_CAPTURE_AND_PLAYBACK (GV_CAPTURE|GV_PLAYBACK) + +#define GVRate_8KHz 8000 +#define GVRate_16KHz 16000 +//Used by PSP +#define GVRate_11KHz 11025 + +//For backwards compatability +#define GV_SAMPLES_PER_SECOND (gvGetSampleRate()) +#define GV_BYTES_PER_SECOND (gvGetSampleRate() * GV_BYTES_PER_SAMPLE) + +//TYPES +/////// +typedef enum +{ + GVCodecRaw, + GVCodecSuperHighQuality, + GVCodecHighQuality, + GVCodecAverage, + GVCodecLowBandwidth, + GVCodecSuperLowBandwidth +} GVCodec; + +typedef enum +{ + GVHardwareDirectSound, // Win32 + GVHardwarePS2Spu2, // PS2 (System output) + GVHardwarePS2Headset, // PS2 (USB) + GVHardwarePS2Microphone, // PS2 (USB) + GVHardwarePS2Speakers, // PS2 (USB) + GVHardwarePS2Eyetoy, // PS2 (USB) + GVHardwarePS3Headset, // PS3 (USB) + GVHardwarePSPHeadset, // PSP + GVHardwareMacOSX, // MacOSX + GVHardwareCustom // Any +} GVHardwareType; + +typedef enum +{ + GVCaptureModeThreshold, + GVCaptureModePushToTalk +} GVCaptureMode; + +typedef int GVBool; +#define GVFalse 0 +#define GVTrue 1 + +typedef gsi_u8 GVByte; +typedef gsi_i16 GVSample; + +typedef int GVRate; + +#if defined(_PSP) || defined(_PS2) || defined(_PS3) + typedef float GVScalar; // range 0-1 +#else + typedef double GVScalar; // range 0-1 +#endif +typedef gsi_u16 GVFrameStamp; +typedef void * GVDecoderData; +typedef int GVDeviceType; +typedef struct GVIDevice * GVDevice; + +#if defined(GV_CUSTOM_SOURCE_TYPE) +typedef GV_CUSTOM_SOURCE_TYPE GVSource; +#else +typedef int GVSource; +#endif + +#if defined(_WIN32) +typedef GUID GVDeviceID; +#else +typedef int GVDeviceID; +#endif + +typedef struct +{ + GVDeviceID m_id; + gsi_char m_name[GV_DEVICE_NAME_LEN]; + GVDeviceType m_deviceType; + GVDeviceType m_defaultDevice; // not supported on PS2 + GVHardwareType m_hardwareType; +} GVDeviceInfo; + +typedef struct +{ + int m_samplesPerFrame; // number of samples in an unencoded frame + int m_encodedFrameSize; // number of bytes in an encoded frame + + GVBool (* m_newDecoderCallback)(GVDecoderData * data); + void (* m_freeDecoderCallback)(GVDecoderData data); + + void (* m_encodeCallback)(GVByte * out, const GVSample * in); + void (* m_decodeAddCallback)(GVSample * out, const GVByte * in, GVDecoderData data); // adds to output (required) + void (* m_decodeSetCallback)(GVSample * out, const GVByte * in, GVDecoderData data); // sets output (optional) +} GVCustomCodecInfo; + +//GLOBALS +///////// +#if defined(_WIN32) +extern const GVDeviceID GVDefaultCaptureDeviceID; +extern const GVDeviceID GVDefaultPlaybackDeviceID; +#elif defined(_PS2) +extern const GVDeviceID GVPS2Spu2DeviceID; +#endif + +//GENERAL +///////// +#if defined(_WIN32) +GVBool gvStartup(HWND hWnd); +#else +GVBool gvStartup(void); +#endif +void gvCleanup(void); + +void gvThink(void); + +//CODEC +/////// +GVBool gvSetCodec(GVCodec codec); +void gvSetCustomCodec(GVCustomCodecInfo * info); + +void gvGetCodecInfo(int * samplesPerFrame, int * encodedFrameSize, int * bitsPerSecond); + +//SAMPLE RATE +///////////// +void gvSetSampleRate(GVRate sampleRate); +GVRate gvGetSampleRate(void); + +//DEVICES +///////// +int gvListDevices(GVDeviceInfo devices[], int maxDevices, GVDeviceType types); + +#if defined(_WIN32) +GVBool gvRunSetupWizard(GVDeviceID captureDeviceID, GVDeviceID playbackDeviceID); +GVBool gvAreDevicesSetup(GVDeviceID captureDeviceID, GVDeviceID playbackDeviceID); +#endif + +GVDevice gvNewDevice(GVDeviceID deviceID, GVDeviceType type); +void gvFreeDevice(GVDevice device); + +GVBool gvStartDevice(GVDevice device, GVDeviceType type); +void gvStopDevice(GVDevice device, GVDeviceType type); +GVBool gvIsDeviceStarted(GVDevice device, GVDeviceType type); + +void gvSetDeviceVolume(GVDevice device, GVDeviceType type, GVScalar volume); +GVScalar gvGetDeviceVolume(GVDevice device, GVDeviceType type); + +typedef void (* gvUnpluggedCallback)(GVDevice device); +void gvSetUnpluggedCallback(GVDevice device, gvUnpluggedCallback unpluggedCallback); + +typedef void (* gvFilterCallback)(GVDevice device, GVSample * audio, GVFrameStamp frameStamp); +void gvSetFilter(GVDevice device, GVDeviceType type, gvFilterCallback callback); + +//CAPTURE +///////// +// len is both an input and output parameter +GVBool gvCapturePacket(GVDevice device, GVByte * packet, int * len, GVFrameStamp * frameStamp, GVScalar * volume); +int gvGetAvailableCaptureBytes(GVDevice device); + +void gvSetCaptureThreshold(GVDevice device, GVScalar threshold); +GVScalar gvGetCaptureThreshold(GVDevice device); + +void gvSetCaptureMode(GVDevice device, GVCaptureMode captureMode); +GVCaptureMode gvGetCaptureMode(GVDevice device); + +void gvSetPushToTalk(GVDevice device, GVBool talkOn); +GVBool gvGetPushToTalk(GVDevice device); + +//PLAYBACK +////////// +void gvPlayPacket(GVDevice device, const GVByte * packet, int len, GVSource source, GVFrameStamp frameStamp, GVBool mute); + +GVBool gvIsSourceTalking(GVDevice device, GVSource source); +int gvListTalkingSources(GVDevice device, GVSource sources[], int maxSources); + +void gvSetGlobalMute(GVBool mute); +GVBool gvGetGlobalMute(void); + +//CUSTOM DEVICE +/////////////// +GVDevice gvNewCustomDevice(GVDeviceType type); + +// for both of these, numSamples must be a multiple of the codec's samplesPerFrame +// this ensures that no data needs to be buffered by the SDK +GVBool gvGetCustomPlaybackAudio(GVDevice device, GVSample * audio, int numSamples); +GVBool gvSetCustomCaptureAudio(GVDevice device, const GVSample * audio, int numSamples, + GVByte * packet, int * packetLen, GVFrameStamp * frameStamp, GVScalar * volume); + +//CHANNELS +////////// +int gvGetNumChannels(GVDevice device, GVDeviceType type); +void gvGetChannelName(GVDevice device, GVDeviceType type, int channel, gsi_char name[GV_CHANNEL_NAME_LEN]); +void gvSetChannel(GVDevice device, GVDeviceType type, int channel); +int gvGetChannel(GVDevice device, GVDeviceType type); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/code/gamespy/Voice2/gvCodec.c b/code/gamespy/Voice2/gvCodec.c new file mode 100644 index 00000000..f7933b20 --- /dev/null +++ b/code/gamespy/Voice2/gvCodec.c @@ -0,0 +1,354 @@ +#include "gvCodec.h" +#include "gvFrame.h" + +#if !defined(GV_NO_DEFAULT_CODEC) + #if defined(_PS2) + #include "gvLogitechPS2Codecs.h" + #elif defined(_PSP) + #include "gvGSM.h" + #else + #include "gvSpeex.h" + #endif +#endif + +/************ +** GLOBALS ** +************/ +#define GVI_RAW_BASE_SAMPLES_PER_FRAME 160 + +/************ +** GLOBALS ** +************/ +int GVISamplesPerFrame; +int GVIBytesPerFrame; +int GVIEncodedFrameSize; +int GVISampleRate; +int GVIBytesPerSecond; +static GVCustomCodecInfo GVICodecInfo; +#if !defined(GV_NO_DEFAULT_CODEC) +static GVBool GVICleanupInternalCodec; +#endif +#ifndef _PSP +static GVBool GVIRawCodec; +#endif + +/************** +** FUNCTIONS ** +**************/ +#if !defined(GV_NO_DEFAULT_CODEC) +static void gviCleanupCodec(void) +{ +#if defined(_PS2) + gviLGCodecCleanup(); +#elif defined(_PSP) + gviGSMCleanup(); +#else + gviSpeexCleanup(); +#endif +} +#endif + +void gviCodecsInitialize(void) +{ + //Set a default sample rate. +#if defined(_PSP) + gviSetSampleRate(GVRate_11KHz); +#else + gviSetSampleRate(GVRate_8KHz); +#endif + +#if !defined(GV_NO_DEFAULT_CODEC) + GVICleanupInternalCodec = GVFalse; +#endif +} + +void gviCodecsCleanup(void) +{ +#if !defined(GV_NO_DEFAULT_CODEC) + if(GVICleanupInternalCodec) + { + gviCleanupCodec(); + GVICleanupInternalCodec = GVFalse; + } +#endif +} + +#if !defined(GV_NO_DEFAULT_CODEC) + +#if defined(_PS2) +static GVBool gviSetInternalCodec(GVCodec codec) +{ + GVCustomCodecInfo info; + const char * name; + + // figure out the name of the codec to use + // goto gvLogitechPS2Codecs.h to see what the quality values mean + 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; + + // init lgCodec + if(!gviLGCodecInitialize(name)) + return GVFalse; + + // setup the info + info.m_samplesPerFrame = gviLGCodecGetSamplesPerFrame(); + info.m_encodedFrameSize = gviLGCodecGetEncodedFrameSize(); + info.m_newDecoderCallback = NULL; + info.m_freeDecoderCallback = NULL; + info.m_encodeCallback = gviLGCodecEncode; + info.m_decodeAddCallback = gviLGCodecDecodeAdd; + info.m_decodeSetCallback = gviLGCodecDecodeSet; + + // set it + gviSetCustomCodec(&info); + + return GVTrue; +} +#elif defined(_PSP) +static GVBool gviSetInternalCodec(GVCodec codec) +{ + GVCustomCodecInfo info; + + // init gsm + if(!gviGSMInitialize()) + return GVFalse; + + // setup the info + info.m_samplesPerFrame = gviGSMGetSamplesPerFrame(); + info.m_encodedFrameSize = gviGSMGetEncodedFrameSize(); + info.m_newDecoderCallback = gviGSMNewDecoder; + info.m_freeDecoderCallback = gviGSMFreeDecoder; + info.m_encodeCallback = gviGSMEncode; + info.m_decodeAddCallback = gviGSMDecodeAdd; + info.m_decodeSetCallback = gviGSMDecodeSet; + + // set it + gviSetCustomCodec(&info); + + GSI_UNUSED(codec); + + return GVTrue; +} +#else +static GVBool gviSetInternalCodec(GVCodec codec) +{ + GVCustomCodecInfo info; + int quality; + + // figure out the quality + // goto gvSpeex.h to see what the quality values mean + 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; + + // init speex + if(!gviSpeexInitialize(quality, GVISampleRate)) + return GVFalse; + + // setup the info + info.m_samplesPerFrame = gviSpeexGetSamplesPerFrame(); + info.m_encodedFrameSize = gviSpeexGetEncodedFrameSize(); + info.m_newDecoderCallback = gviSpeexNewDecoder; + info.m_freeDecoderCallback = gviSpeexFreeDecoder; + info.m_encodeCallback = gviSpeexEncode; + info.m_decodeAddCallback = gviSpeexDecodeAdd; + info.m_decodeSetCallback = gviSpeexDecodeSet; + + // set it + gviSetCustomCodec(&info); + + return GVTrue; +} +#endif + +#endif + +static void gviRawEncode(GVByte * out, const GVSample * in) +{ + GVSample * sampleOut = (GVSample *)out; + int i; + + for(i = 0 ; i < GVISamplesPerFrame; i++) + sampleOut[i] = htons(in[i]); +} + +static void gviRawDecodeAdd(GVSample * out, const GVByte * in, GVDecoderData data) +{ + const GVSample * sampleIn = (const GVSample *)in; + int i; + + for(i = 0 ; i < GVISamplesPerFrame; i++) + // Expanded to remove warnings in VS2K5 + out[i] = out[i] + ntohs(sampleIn[i]); + + GSI_UNUSED(data); +} + +static void gviRawDecodeSet(GVSample * out, const GVByte * in, GVDecoderData data) +{ + const GVSample * sampleIn = (const GVSample *)in; + int i; + + for(i = 0 ; i < GVISamplesPerFrame; i++) + out[i] = ntohs(sampleIn[i]); + + GSI_UNUSED(data); +} + +static void gviSetRawCodec(void) +{ + GVCustomCodecInfo info; + memset(&info, 0, sizeof(info)); + // setup the info + if (GVISampleRate == GVRate_8KHz) + info.m_samplesPerFrame = GVI_RAW_BASE_SAMPLES_PER_FRAME; + // need to double the frame size since data has shorter wavelength + else if (GVISampleRate == GVRate_16KHz) + info.m_samplesPerFrame = GVI_RAW_BASE_SAMPLES_PER_FRAME * 2; + + // the samples per frame should be set above + info.m_encodedFrameSize = (info.m_samplesPerFrame * GV_BYTES_PER_SAMPLE); + info.m_newDecoderCallback = NULL; + info.m_freeDecoderCallback = NULL; + info.m_encodeCallback = gviRawEncode; + info.m_decodeAddCallback = gviRawDecodeAdd; + info.m_decodeSetCallback = gviRawDecodeSet; + + // set it + gviSetCustomCodec(&info); +} + +GVBool gviSetCodec(GVCodec codec) +{ +#if !defined(GV_NO_DEFAULT_CODEC) + // cleanup if we need to + if(GVICleanupInternalCodec) + { + gviCleanupCodec(); + GVICleanupInternalCodec = GVFalse; + } +#endif + + // raw codec is handled specially + if(codec == GVCodecRaw) + { + gviSetRawCodec(); + #ifndef _PSP + GVIRawCodec = GVTrue; + #endif + return GVTrue; + } + else + #ifndef _PSP + GVIRawCodec = GVFalse; + #endif + +#if !defined(GV_NO_DEFAULT_CODEC) + // do the actual set (based on which internal codec we are using) + if(!gviSetInternalCodec(codec)) + return GVFalse; + + // clean this up at some point + GVICleanupInternalCodec = GVTrue; + + return GVTrue; +#else + return GVFalse; +#endif +} + +void gviSetCustomCodec(GVCustomCodecInfo * info) +{ + // store the info + memcpy(&GVICodecInfo, info, sizeof(GVCustomCodecInfo)); + GVISamplesPerFrame = info->m_samplesPerFrame; + 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 zcmZQzU}RuoP*4zH0D%`w3=C=v3=9GS5WWT@0|Os31A_(w1A_ts1A_wtNIeTkDFXu& zgu)~n7#JED7#JEFAQVFbh{0$u!!4mJlOc22VH4mcU9Yfy(kji)O-t~iwdiU;K2gqsf-u3_g-@w4Y z_pSr%RuC5~0QSTWuowRS|K9=T{{R2u49HLa8{R?u^yADKkU#^-dJd568IV99$a)3_ z4v;KJ0O}@|GiQz*IpcH2=N;bzkXu=Nj`;W-IpcB!#y{e7U8JBkuPa@>s@qyL5 zxcK + +#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 zcmZQzU}RuoP*4zH0D%`w3=C=v3=9GS5WWT@0|Os31A_(w1A_ts1A_wtNIeTkDFXu& zgu)~n7#JED7#JEFAQVFbh{0$u!!4mJlOc22VH4mcU9Yfy(kji)O-t~iwdiU;K2gqsf-u3_g-@w4Y z_pSr%RuC5~0QSTWuowRS|K9=T{{R2u49HLa8{R?u^yADKkU#^-dJd568IV99$a)3_ z4v;KJ0O}@|GiQz*IpcH2=N;bzkXu=Nj`;W-IpcB!#y{e7U8JBkuPa@>s@qyL5 zxcKbuckets, 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 zcmZQzU}RuoP*4zH0D%`w3=C=v3=9GS5WWT@0|Os31A_(w1A_ts1A_wtNIeTkDFXu& zgu)~n7#JED7#JEFAQVFbh{0$u!!4mJlOc22VH4mcU9Yfy(kji)O-t~iwdiU;K2gqsf-u3_g-@w4Y z_pSr%RuC5~0QSTWuowRS|K9=T{{R2u49HLa8{R?u^yADKkU#^-dJd568IV99$a)3_ z4v;KJ0O}@|GiQz*IpcH2=N;bzkXu=Nj`;W-IpcB!#y{e7U8JBkuPa@>s@qyL5 zxcK 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 zcmZQzU}RuoP*4zH0D%`w3=C=v3=9GS5WWT@0|Os31A_(w1A_ts1A_wtNIeTkDFXu& zgu)~n7#JED7#JEFAQVFbh{0$u!!4mJlOc22VH4mcU9Yfy(kji)O-t~iwdiU;K2gqsf-u3_g-@w4Y z_pSr%RuC5~0QSTWuowRS|K9=T{{R2u49HLa8{R?u^yADKkU#^-dJd568IV99$a)3_ z4v;KJ0O}@|GiQz*IpcH2=N;bzkXu=Nj`;W-IpcB!#y{e7U8JBkuPa@>s@qyL5 zxcKresult; + 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 zcmZQzU}RuoP*4zH0D%`w3=C=v3=9GS5WWT@0|Os31A_(w1A_ts1A_wtNIeTkDFXu& zgu)~n7#JED7#JEFAQVFbh{0$u!!4mJlOc22VH4mcU9Yfy(kji)O-t~iwdiU;K2gqsf-u3_g-@w4Y z_pSr%RuC5~0QSTWuowRS|K9=T{{R2u49HLa8{R?u^yADKkU#^-dJd568IV99$a)3_ z4v;KJ0O}@|GiQz*IpcH2=N;bzkXu=Nj`;W-IpcB!#y{e7U8JBkuPa@>s@qyL5 zxcK 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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 ThIs~i`upidxOw`x>M;NS3-HZ? literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/images/asteroid1.tga b/code/gamespy/gt2/gt2action/images/asteroid1.tga new file mode 100644 index 0000000000000000000000000000000000000000..f9a3dee83cf4cf69f387b948052dfa7642f2ad54 GIT binary patch literal 65580 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 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 zcmZQzU}As+Mg~R&4j7M#iHQM(85tQFn3UFgcih5FZ4DL#tgK)+g4BYH863I7z~5tVB#Pegh6sJy)YW24kQl3=xmrCkT{5q4TJO{V~`q< zKS1V#*dRF=8^i~(kugXfCJti5Fh~q!4u}u39~pz>K;keOWFCwUQwQQBV~{vZJ%|SJ zLGm!YFgCI{hz(K;k^|8o3}eIOKw{W1NF5A=?105TNG*sDk^`9mVk2XaI0(bUVCq3? zL1ut3h>eaxdO#SY9z=umgV->2AT~%GhCzIg9E=ZQgTz35kX{%K5(8n77>EX8kQ|7H z=|N|M_~;m<2V@3}hQ%#N9ZU>l9teZPKx#pL196agD}iokU1beNFGLm#9(|78>AK_2f`pe2qVkG z_#hgDLF&;lNE|slK=L3l5QdorG9QLPVjv8Y2hku5(+5%q;)5_q9f%L2K^R#K#0KdB z$%EJ+d1MR{2VsyJkT^&Uh!10f_#h19gXBPJL1G{_j0VZW_#iQ4H6S?<9~pzhK;Z$> z0}}^{f%JkfNE{i1uf3u1#XvN~ioNF9g|QUg*CVuLV9936w)1d{{NFbq-$(hs6RVjvoXL2M9)u|YH; zJ3wmCF~|&%`5+o3hs=hFfy9w9%pMSpEDmCW#6V_%Fo+HE7l;iq3&aP}APg$oKyn~4 zkUo$Y2!q%lF_0b*8>9v%4iW>gK^P>5j6q@`3=#v;APh1GM1wF$oEQvJ3$q8L9>#~U zK{PrB*#i?pX2ZllW`O(wk^`9yVuNT9A0!9DATbaI=|L8U@j>cAY!D5?Fg8dp2!r^@ z7$gS5ATbaP!XR@&e2{(+AH*hvLFz#2K^UeM#s5T{N5>$&AoD>O#736~@j-GR zj7=US2Erh_K^VjaVVHUl8-zjTfM|4am>5VLqy}UL$S)u^2!rH7VlWKigTz4?CI(`| zFh~rh2E+!5BbyE46T%?%APh4HT|G#gkUYq45C*9Q=?AF?VGtjLL1G{pSq#L7VVF3` z41D1MGY6y�JqI3}S=K0@27ABnL7ZBnD!GXhIld4oDA(#-;{oA0!8&L41%nhz-LaJ~D=>0qH>& z2dT%!2k8OHgJ^U$AaRgcAQ~nQ3KI|?gkkED+2~>*{U8hyhp7XZ0WuH92g!jjh!4Ub z^&kvlgD^-ArXC~?Q$e0*$ka-|=Fnu64GKPtRXpkFV>OpLf7|1Um8l(qA zgD^51#0O!J7zl&(gD|of$PADi2*bodGzi1Qu&IH$0c0Lb9f(H8ATy9LNDi3~VuQp$ z7{msd1)^aXB#w+>;vgDkKa34h2XZIK3=kWHL2@t}#D-yz7zktIgTzU}ATwZgg4_hd zAaM`|nFXRj7{mr)kQxvlgh6bO7zl&-$QUFB!XPn_9Uu&1gTz4kKp4aZiGwgq4n)H+ zNFEmk=>cJwS{Mye58{K^Fnd63LKvh6W+q4<41@H6xHmF-Sd#529iAfY^jE zNDs(N5C-W(#vnNm2AKh)L2MYtCI&JGgh6s33{wjd17VOD2&3BrlLM&-$s=Qs7&Z)2 z1JVb=AT}}vsfY1FY*H}DJP<~9BZ!S`9*B)@CQJ;Z7A6N0L&nJBAUi-9Bo7h?=>uU9 zA0`f>L2@7r5(m-5U}UwRatE0Y(huUJV~{v92Fb(R0}_X^VdjD4LGFWL5FexlghA>- z7$gRgM`nY>&@o6Jqz{BaY!C+N17VO_n4K^_NFF2(qLKB0_}DN^El3Vz7RVeB8-_vh z*f2~UM1$mEb|JGtW`QtB3`8Sika`#fnE}Eed5~IUHi(ao(e;7kVR~VHfQf<30cf_JYgmVGx@b3{nd+A4J2M zvIA^BGZWYyAU+7g)WFyv$HK&L(J(b2Jzz6I{$gTeU_!zmy&yRd2B~9WW=8Q3%nXn^ z7zV3D3KM2#$lNtZ9Hs_FgT!EFgTz4b0p=s=0jUAWVZ$Ifka`dXv0)gb7UVW$K1hrh z4AKKK4}`I)hsncem>LitWG9Rdqe0^67-Tj`45kJ~gTz2`APf=%@j)2bTo50GLGmyR z;)5`Z529guK;j?_+W@KbwVq|1sWnyMvWd!AY zM(~0QW@bhPW(EcbW@KbwVFInOU}Ruq2B~FXU}0opU(9`M?ZP%M5ZW!~x7q%nU4yEDX#HOkn?j$|KOi5k@8k25>nAS|b9&Abn8(vw*^Y zk(q%3gc+C^m>EH4Ff%YSg4_vm0}BHqC`>>F76U5-0|N^KC~!d9SwP_fVlXl>urM=& z-NXR$EC_>q4mKO)HUx;aSh@zu!7xY<2!rH6G)x_c z4^jgX1JT$pNDZ<)h>eV4dO%{xY!DwAgXBPFfiTE^5C+ME#6dI&!`Ltyq!z|TRu2;c z$-}|{BnIMx%mHC+e2^GO4rCUH4O0)|gV->$Kx#np*f1#HF|#m%*CBz@6ew@8fcQ+1 zJkG?*2u|kAEQ}0nj7$uiOsouS42)pR3R1(&$iTwD#=yeJ#=y$N!T_$vKv@WsR6$yq zL3Jfa08~COGH@_4Ft9N*FtCC#BLh1F3j+rOD+9=0CKgbA4N?zE|BMVQEQ}1S%!~}2 zAiF{Nixt$a0B1P{22fsL1(jcn3{1?RJjKYs%nC9WWF{j67Xu@M03$mC9|JoB8v_#q z3n=}wfE0rCK=@pYObqOx@($!iW<~}sW+n!9W)`qNKx?1!lV@j+@pGzf##fcPL9gpt`Gdq83!bs+sPIS?O& zL3|JmQUhWm(;zlT56lb@AEXAP9z?^$VKhh{oec^PkQp#Jm^>#FGXp0pD+32B3j-?) z69XGN69Y3lBLfpBBLgERBLf2`0|Ns)0|Nsn?=dnla4@nn@G@~T@G@{Ra5J!j^DZ+J zF9QPuH%Jk8x%)lVQ z$;`mR#>~LV!T?qWYGi=2HV+d6g8%~qgD4{d13xI+g0e5DC}Cz`VCG@Gx5F9Mvwh99y8v`Q~Cj&DRI|DNl2Lm$` z7Xu3;Hv=mZ4+ASR4+9GmCj&bxHv=0BF9Q=i@ImbisK-Hp!@$PCz{tS>D(PVPpM{x) z0VV%4g8Cq!h8C!71eGzMaurg}gVF$Ux&g64X#;bVs7^EMh7Q{x@3u1%hK;j_vAPiDR2*cFF)PeXg z3=)Hx0~150LGmCzhz6+xxdDV>Vjyu48(9pb4kQMW17T!2kT?i~#6jvnG>8vV17d^3 zK^VjaVUQY-JctHikQj&$QUi)d5FaEC3LB6Zhz%11u|XKrX69jJVc=nAW8h?FW?*As z0#z%Je9r~V(mX5-ph}yOgO!01)W&CEW#D1tX5eGsVGv~GWe{NGWngCHV_;(7VPIn7 zU|KRlva4~Q(@G@{Pa51oh^FBL+Bm)bBFask4FB1a;I}50d0_yO9@-a696N4ZF8>0wV z1uKIn11FOt0}rbV11B@c)glb+Okxa7415fb!k3eQ5md*63p-W@W+rY1CPt7MoD3|C zT+ns{D6fOu1?mGZLD~bL{15GSfZ`t%_n`CvqCx2cCI+HG7?d7Bav&NThRGqT1&P5h zNF0P=d~_OQCP)lKgD}V~APi!|Fo+MrFm*8VK=L3lWI31^hz4PhJV-r=4Zxc$gJF<55C+M?Xc!yD2hku5GYiB<)(=t-;)BFtYCwFDI0%EpKyn~92!q%l z3=&7ipnMAw2dM#J5E~>9qCtER2Jt~ONDPKSd=LilK{QMa$PF+t5DmgCOw0@%pmsZ` zZUnXBnIU~rMo>Ax&dk8f&C0;Y!NR}*YL|o3JQFhm8#5~d2NNp;H!~XpFEa-NFEcv> z4+AFy7b7PFCld#_&&|XPYM+AIv1|+sOq>i1ESwBXEF276%-jqDOrUaui$R!?lR=mf zl;4;bWSAKkBpI0*_(6SQP#c$(iGiJ+jX{{1jX|23gF%#$nL(I|je(zugMky&)&`X? zpe8jN3j-qyGXo>2|I5P8z{SMMzzcF4sP1LtVBlqBhqO_cKzW6Ofti_ufr*usft8hs zfsYx|t`Gs`ZBRc0qz_y!urUZSFf#}+Ff)iSvonC|axPFGg_(_knVF4&iJ2YLNC9=i z85lr&Ojy|%SlKukI9NFt*qK?u<0b-(tPH}8tPFf0ec&+!P}#}Bz{tqWz{tqYz{n`b zz{JSMz|6?cz{V^D!7M@yEX;xo?5x5JY|MfT3?Q{ETnr3MAp1c5dypBRJ_0AW-OtJd zDt9>162NinjnyN04QyM(f}-dfcWTX1SAHN2dM?| zK^UYCgh6633=#*iK{N=1*dUC|Mm7tkA0!XLFg{2PNFNA;#6f%zhVemSFg8dWgh6^i z7$yf2gRwzs(J@FZNDQAEkUR*3)F5M!7)U<|gX{rekQfMq)PeXQ3}VADNDdu?WT%bNWsBUHCW?*CB2eob(SV3$? zZU%Nn9x!HQ0JYJ1!1XZ`GY>d#g8Dn4{LH}2&cMXU%D}?I!obSS%)r74DkDK-0if{? z4hB{h0R|>UP%j3Q@A()Q8F?8PLG2_^e+tx};bdlD=3oNr1JTT&a+i&nfr*ucfsu_B zT=p`tF*7i;F*7hSF@wiT85vj^SQ$X&06PN{cq9hWhlk8bfZ`OC$6;d7ITkDl1e6Y7 z=>n8SKzwW%rWK?Xgt4hX76Yk4#vnPE8K5){vkRmK#D-xQA4G%nAY+hzm^zR+NDT;s z%meX37{teiVQN6;fY=~2K{P&$%{-8P5C-Xk(I7TREr<`IK^R#KWETv>#6dJnJu(|v zFGw7OVPYUNKzw2_Ob>|0jzJAt4oDr%z|O$P2uf2N46KZt3_MJH3>*yH41D1JGds9v z3mOUpWi?R#VFZl}fV8qPFo?1+Fi5a6G6*s;Fz_;g#?(OL<{$;2v@XiP#3%%A&oVOb zGcYiK$^&5r1|~iRCT7s+94CVy0~doBGYh!?4IV3CV`E@oWn%#4El_`%0aR}@axs8v zb7oKm2F*u;^9HC<4pPSjE?*cJKzWmgfrX71Tu+1M6Tst)12Z2p10y%6{{kv!7#JAYSU`gm42(>C42(=d3`{J-42;aY42&SZuz>P4WVnQZ zgMopKkC}m)hmnDWhmnDigOP!WgNcEWi-m!K9m)p{On|!lAn!6VgL6M<6c*H<0gV@c z=37De05q-$o|*u)`xzklo=`f0r3+X(gz;fCNI!@Tk_V+d5C)0i!yxtO7^EL224aKs zgZLme2*cPQaTp(D7Yu{ML25u4BoE_**dQ?w28n_AAR5F+#~^W#dC1}*F?<+VEsPCQ z4>AjcVSJE0$bJ|NVuR#Bd}Is~2k}92FgA=2qCsLHjNVVfrXC~)!yvuL7$god2Sg)_ zBeNMmgLEvc42;Zd3@j|5bk51Z%D}Xg#I2f4NL45*F z1|D{P20j*E1|eo)23}@i22K_c238hv24)sX1}0`H21aHX21aIC1{Own1}-Kg2395+ z21W)E1}0`v21Z5^1|~*v23AH{22Lhr1{Ma8oCE_ilQ;tln>Yg_s2vR|a~K5}m{>%> z>X}(&!DccrN;5Dq$}%vq$TBc634`l)&>RL6BdG7f$^e>7U}OQc`PmtmK=W{*e8->@)nv#^2t8$9fs46GcS42Fvq{ZwC!4ure{Qvam65u<$VOFe)+#FljRIF{v}KGpI3evluckGwLxg zF&HqgGn+GVGFdQiFc>p1F@VH$7&urA8MxVv8Mv8EnRpm2Sa}#MnfaK_n7Nqs85kH; z!I+U*oq>T-m4SgtlYxOzn}Lx*n}M6fn1P+uoRN`1pMjOhkb#+9hk=1ZmVuF7fPs^Z zoq>x3G{y#576IzxfciV2X?+$Z1_llm&{!V>1E{T(C9*ga!|f@G>zlb2Bn9f!Yro(0&c5i~yCh zpe71voDrJ0SwLe743ILK3Djm|0IjP4tu!}NgM z1yTbN2hkwEz}O%fgh6USav%)igD^}Cq#ncuiGeUo4TwfI8>R*%4pRqWgD{AXj6rHa z=7PjP@*oTn1JNKeL2PskQwyR&av+)*3^Eg>4x|U!PLLQ#4kQMW1F=D3APnMzXi!@b zw5EiGnTdg)n~Q-HGzZSY#K6bS$H2-Y$iU1b%)rG0s<%OHWk{QsiIJUwot2q^3p7T@ z2pT`+U|r%YlMPo2RjS-6c?By)u@M?{wZ zQ`BSumY7LG!pXCE+Gx7vBu<(X7@rcB<@QN00W?@fR$jX{DhnXvJF|$zITo!@ICPr2p31(J9W=19z zW=0leW=3WuW=3XZW@Z)@CPqdH1{Nj}1{Nj(1{P+11~wLM1|AmB_$LNrtRt{DM zHg+}!7B)8U*ej_0&%g@G*PwA(&>S8!c+C@N&Jh%Epg9n*9pI%Es`K^P>)!oD@Me+E`g(C{1+11keJ0~dn~13RM*0~50?A2X|eE;noHdJe{t z<818BmsnZ*uduUByv4;a`vJGqn*V~fC;khC-1yHEa`Qiz@3sG2HYfkH%WeJ7#=r4D zkMyqp;+CiW3wvGqFW__gzmUs={}Q$j{|h?Z`p+A1?LTkAt^YiUxBhd5o&V1pbml*| z(}Dl2THF3}=Z*k&3pTUj)Y;q_6vx=Pj&n0&5KQG_${{oz+{|j;+xXaJE;R+}F z{3|RheOFjm+OM#4v|i!ls6NBalD&zYF?KE+gHI?Eqd5Z$qZtDmlO6**qXGkHq@M>g z7sANGz|Y9YAi=~0Ub7&~1e!+xttDV*5Mg3x;0Mh|FtIbRGO>ZnM^K-O5j?)n!NADG z#lXnS10FL4kCn26=1oBPn2muIG^Pn^OEG}v5I}_rs9(Xzzyuol0F4X4@;_+(5eUP^ zJ3(_K&^Z85e1qa1ghBBR;)5`V4ZCX0C|!f}fXv6I7NiCw57Gl-gD{8<5(BY87{rHRkT}Rp5FaFmj6vcc3=#vW!-hd} z$QYX%kQpFxWIJGd5RI%K#0IGYVVE3e72G!GVq<1t0gXZNF)*-z@-b*mpMi&gg+-i!g;{`s4K(t_!oVQH#l*k| z8ma@0OM}*uf#%~tZA zES>*(1Q-4nGuiiF-2BpiA)`zGMJ#Xr7x#VkUoQ8@f9d-F|HYdA{}-tH`=2-C+kX~^ zr~jFa?)?{VeEwf8_4|LF+W-Hx>i_>&$o~IdCg$INvGl+Hg(`pkm+1ffUvm2Y|H9q> z{|i<9{x6yL^}j^ir~gvXpZ@D){{F8X@&CWD)1UwB20#Cc8~^#Qr2Fl^y7tfiIyNBu z>A#ls>;H1bxBe?yT=}nVec``?=Fb1pGE4sRa(4aaVkvvg!Wwg&nJKiJjoHVHmBomG z5wseDiGe|kiGe|effc;QL5P8YL6V7;L4tvmL4t{sfuDhmftQgJ+;?YV;$UE9Vu!4m zWaeUEVgjw50*#k~#v&nOm>@CGdJ0xXF7P-kXzY^>G!Fr7^Me*3F@cH!(0B}T{s-lK z5QetLU}FHFxCg~I2!qlF2*bocY!JpK2NDBekUR*3*r4l*gMpJlj)9j^nt_i& zltF+|kbw;}djeV;0h(K7U}4~8;$RSE;b7ngu|Z`iBP)1(g_Dt$fs+xm#(|xIgOQ7Y zi@(Kc~H6r$${7~aS$IL zhN%Uq2dM+`L6{I5BoD(N^~m}`;vhGHXb=X;!)Op2gh70e9*{Z^AH)WU!7xY+#Dz{(oB3z42*m%49r3t3@m)C3|w50d3w-@xfpbP9Tzh@ z11GBh0}GQp0~4b$0}E3?B0F=|NlvEP{~QeM|JfKO{Ac1_{$JSW`hS_gxBvOW9{%S| zdj6jy@7;fv&d>iDmVf!ry!!QjmZdNMbFTjKUvS&M|AKq|{b%3y_dm3`nTFaJ4PKmF%g z{O3Q<_J9A`_x$FgTz5ypX!XWcOd=LilK^SBnh=yU197qhL7DR*iAUPNx#0H7O z_#k-@8zc_GFgchQhz}D7sRi*tav&Ne4x(XVAaNK5i6LW{ILI6j=3r)F;A7!q;A7!u z;9}xr;9v%gXM^f_4)B^2CQfDs2GCeEHxmOBA0q=BJ0k-(3uyhU2!kMlB7*>fHUkHf zwlO=4nFlkAYYa1AdiQ{o8-GW&i#&&inhHvHSaf=9+i^Stoq^&%NUBf8MQs|8wv9 z_n&3$pZ^?d{{81&`tLv2#P9z(n!f*M$@%=B!T!d7M!6&Zh4df&mrwlhpR41`f2L)> z{CG0w*UFhHRtz#)~4_OnTx*s7p(vLU%d72f3eE{ z|E2Q(|Cdbs@?Y5R(ti$%)&H60JO48YRexiY$XUT89@oam<{HevpwGd~sK&s?Aj!bT zAj}}iAjlxbzy(?X%plCj!63%M%^<|c&LGId&LG0d$-vLV%plAHT3gA=z{$kKz{kqM zz{?EU2>_aVU}oR|?WF|Gy@1vgg4Q{K_5nfn`GD3sfc6T4;uW-38x+@|H36{r2c;2^ z7&3;*A=5B%D+$Q%$2!!UV}T96!w56kx;F%Slc!)TB^ zNH0tcOb#Ru!yqw`IuMO42I9joNFF8!VuLU-=EC%YXpnx8J`fG!qhpX9$V?E1v60n) z^bv!R^?=yudO%{x7+oI32iXr&1FHK$d{%A-1`ci}1`cjk1~yJ61~v|623BU!8X(Ym zc@D@vQ#K|BCeVB|XiW|yKLab13IjW%jXEb|a5N`l#&j0O313(_=lx?5U-6$+cl&={ z-%J06lOO&UFMIo6xaz}y*3_H-xw3En7w-J_Uv$R5|GZOv{O6tV=Rf<5KmXZgfB!GE z`p18PRX_jpEcx}HZO*U%9CQEv=bZERKkMWl|5>`f{b%p`{a>i>&wuImpZ^7NKK^Hk zdh(wo|NoK= zzy1s4{`}AE`|dxZ%$5I4tf&7A@jU%6Vf5v{Q2x*VoYVgN=iK=3Klg!O|GCcm{m*jY z=YN*-KmPNa{P&-G-Ov9l9WVYfRX+PKH0jTOsRe)ki_ZT0pJ&3i|2z{u{+FKl`M*@} z^Z(p=m;bXzpZ(7raPB{^%klrr%1eGTayG1EXUXhlXY>x@VA5ygWmN#x=nSl&xkPpr z1|BXp22M6622M6c23}5P@VF{B8zTc~T$Pudi2;PU!E2Nm7+9GY!F!S!LF*O4`I6RGj#u1SA4}#=C7$gtV z17d^3KxrG67h!V9G)N5$!^A;)L2Q^fIt?-pCJ!96~1EdF}4unDCAU;Sv2!rH7;vg{)jVuon1JN*XkQxvlhC$-UVlX~T z48(`2g^4jTA@*`1%fr;cXi%L1(gVVv{XQTXBnNUgs1E?zpvKC_!N9@`8mogWZD(fY zVBll|wL(GZiv_$cfDtqvz{ti2)n+;{&uGd}!hOL_mFBmez> zfrgL&<$HepS8DqAUo7MIeo8v(0|{Uv%}) z|8g7u|5se||G)UWKmWyN{Qj@f_xHbA-M|0hv2XwL7@zsi%suBn6Jz;*CMN$_MrH#B z24*1!(4I`)V^Y8X5e9GWZ>n1?oDE0VBleAVqgQEX97BN z05o361nN`pfcF4__7Z~P6%?PK{10M-_5g#@1uV`%Y(~(@ECljDNG}M((g=t~$Dp(W zG7qE<#s=|`F(^-f^nuiXFh~x>2dM{P(0CPy55pieAaM`|iGlba4ATdrL2@7r;)CR2 z>Oo>44B~^tLH2`akQfMq*dPpIgD{8tD47BGQbS4J~gVv!yma#H1fX!nD(V#hB zh*=;rAQ%+cp#7g9aY$JKY70Wzg`oXn5HZmHJQg-i1}4xNK+wKlE+z(0+hkUo!2_e|Fcm{~2^| z{bw+F@Sn}=+kdX8zyCQC{{Cl9`u(3h{^x&|z_<54S=i2%EKlhTK|Jk~~{O4%-^Ixd*-+zHNP+#KRf6>XG|0^u}`(JMP-~Tep z|Noa+`TxJ-y1)OmHvRptw&K@+rP)9J%htX7&*8l9Kda1)|13OJ-&nXar?T)xWiWHt zMze9+MRRbPnlZDsb91Ps-EDRj%j0|iX;H^gBJ^oBgTnx<2 zf($I6HN}jeJ;I6p0;p5~Eh_+} zBhVfSP@Y;9J~}BwC#); zwDy%nZ4iq=k)vbpT+U#e;C(nxAw<>=HOTVS(GmQ=j1*4pNW0Le+I^d|Cw2~{pZua^Ix|3 z(|@fw-~a1v|NUQX=U=dUxaa-+&)oRzKWo^1KR(?%)rIK4BnprS(gbqYXNjd0cd+9sDA-E7X{oN0PTGOEn)->4uAxp z`vX8}4wT+N7&+adr&n}7NIeLH#6fumq!)x?VjvoZVd9`P4-*6FgNcL0U~G^$NF9hq z$1rh_T96z_AB+aEVHo5dkT^^oh>eV4=77|L#6f&ywIDVK!{k9Uj1OYN^uX9K8YB*5 zgUkeBm_862BoE_*+z3*Oj6rgsasVU-ix=?uQJ{DOMJlKt2BJZ0aG5~q0F>uJ>%Bl{ z$AI!U=%g#qx^K|_FHkuEq8S;$=R$zTi$HTlpgJE?M}X4@$ZXI)Qw9MBCRSkv22M@} z20l&(25w#k7Iqn4R!(gdCQkos4%z;n+&X*y^IJanuMqkBziR3K|B7Az{wqxS_g`h^ z|Noo|LGAe;|5;c4{?EMa=YRIYfB&-`{QsYA*Z=?A+yDG$nDOL4Q}2uaY_0GAv*f+} z&zSP|KUdMu|2$b=|1-tE{?FF%;y>%uZ~wXG|NGCo;{SjCmH+<>&HD!)YvT$1_@7b! z*ndX36aQIFAOGj{{PJHY{O5m}$p8Pj9KQc&kh}e#QQ^ja4%2u4IUK+IXEA&6pULR{ ze`d=&|2f?s{Fli6_MgA`?|r_n&w5Z}7N*$hQCgMOXa$FFNVZ zf4=;;|HYHv{#S~9|6j}F^?z~YBmdbsdcHF<1cfs*nQO8z=u5IOs4=iHiZZY;i7_yM z&H!Ly2cMn53hE<(_9?S4z}HxV&P@WJ$HdIQzz$kl2wDpSUJn2|a|kp)2udT6^;e** z4@!fe^abi0z%le3Ah0-=Gk`#87ldJYK{N=1(ko0H#0FuI86Y_j4da8@ATf|y7#k!9 z5(Du;7{mtU2@nmE1Brpu!}uT?#0QDPFi0H;gZLnM5DmgGbs#yAI58Nc52OxRFH8(X zgUkY9m>LipBo1mPgT!DMBnD!G%mL9bK8Ov%$l@S55C)kAk^^B7ABG`$A9~gm=v)ji z29@)mwjSi*8<<)!4Uz}9??8LJL3tnA_6IKzg{%>0Wn=-L;l&OYzy5RdeE%=e`scrL z*5CiK_HX~Q@*exozKNs`j z|NKmg{&O+y{m;yM`9GK8gZ~2I@Ba&xefTff_w_&Dl;8ikJAeLX&U*8oC;aPwUYDQ$ z+4SE3XOesRpF#7*e^$4z|G9iW{%24<^Pf@q%zrl5r~gGue*YJl`0qda)X)F~ zzW48c(f$Aa^RNH=pKsca|I(em{wp{A{jXH=|G!w^`~Unp7yh$y&i~KG-0_N)x#AWx zOME#4i-R%~i!K8blL7-9t0?$P1x^+w22OTn@Hhcz_z$%H7j$wO=E7WAbHT?R-m)xKx|N)f!5rzF*AeD*<=Rw4H#J%K|CRM${1{Jq5IW;_Q{|B zOD+8LUwFow|Dy9g{})~H?LXg&AOBhB|NhT7;rD;W_V53Bru_adG2{1tr78da^A&yj z&k*|UKU2uJ|1AFR|1)|#`p=wn_dj#R%l|B`KmIdTfBw&!|LMPA{Kx;owvYbvOJDlW z!*TpS3&ZjM?5y|xOUnQJFRt+CKM(K6{~~-}|4Rw|`Y$2!{lB32_y2MNAO8z5-~Z3V zaQQzo!@2(q4Ez3bsNVQ5ocQ-YZ`Pmxq6we=%LF|8&!uzbKabF^|6;6b{);iL`7gkH z`ac`Xga1rixBfFLT>Q`Fa```h+N1vhW#9jE7ybUv7XSS}r~lvo9CrWyb6WoW&tmxd zKa=6N{~Y#z{&N_A`p>|5`9A~0rT;7nPyfpV{r)dr_WM6;_3Qt<)8G9U+y3J}-~R9a z*|vQD&$00Pf3EhA|0UbL|CjIn{a?21?|+=JEwGl2R7tl$$0nLuSdXi*@jvIo@# zpfM&;ngDIrWP+UE$O<|?0klS#5tK$iZ9!055z!ZgrBN6SN{=u$jE0GUXi$Ct(V#pB zqCsjvGzf#(AbAi*=7ZQE49XiIH6S)fKZuPi2I9joNDU0b)PndRHi!mcm|A2SBn~nY zM8m|9`56AL#31EU-R1A`VjJA<}7C!t5Py6tnbHT^|Of%m7XXttJpRM!#f8ja*{!1i+*Pp#ST?Am6S3?2M=X z^9eur&nI;6KNHi*|BSrn{_|LW{m)_h^*@)+{r{|DNB%Rg@A%KnbKt)y>*oK0OsoF0 zGw=A%#(LsE8~dLBtQ0n>^QQm#&sO^7KWo?9|J>`o{b$|t z^FPCe@BbO*|N76?_4~hI$M64=lYab{>ihj)w))S1`M5v-m4kl&m+^Z2U)c8ae_qX1 z|JlVSe`8=RJA02B`t*LC-TF^&oMOJcx}C!_+|X48(o-)Pv+eW`i(HFH8(ZgTz4? zqy{7gVuNUW7^DWI9)@9j5Dmll^n>I;>OeHaFQBnjP)!Uv3l6kLi;)4;x8nfqwE>+` z!3a8QnhkuW9y%71JndW`}&p7qhe}?`a{~0E{`_C}*`G1C4PyaJbd-I>E^X-53sXzV;Ecp9h zdd83cyyYMNGq}C^&u07izl8no|KjGq|0}wE{;%fy<-eN8r~i8HKmME8{`xO3`}99I z%c=h?j0gWSFdqKT#B%IEkHE?Qy!_|?GccU_&%|)%KQqIT|IC~h{_`4r`_Jq6=RbqU zh5rm3$Nq~Nef}?R_V+(0|Goc=3>W@03q1bMCiLt-2j|`Y%q&;`GccU~&%|)zKR4r^ z|GdoW{xdPG_|L+!=D(2OivMC9JOA^t?fB2ke&9a?`_=!9ypR5K$$$ROB=r721M`dj zECTQUi>m+rud4U|zmDFo|2l@>|I6uq`_HTM>pzFtpZ}chfB!Q_|NYNW^8Y_e&$s`q z8@~T%JND;4!|tE|nb!aP&ouMrf2NL4|JkSh{LjDa?|+d6|Ne8;{P@q6`};q8{_p?1 z*J7#9!0`1vk0-aOF!T>s_ zjRolrdQch#rBzT`2eDx^NDPEQVlW!S#)Uy@KzczK#0Fsy8>Ak@24N5zABL%cw0lYGU0d{UH zsBI3~Uc=7G%)kQL>&e8zzy>-m4|Hw}xDMxJ;9+8A5MX9z5MW_p-~q`qvN13-fX?mW zWncv5ebD)kAU(|73`|V?3@l8d42%ro44~66L1)b}@PZE91s$Tx%)rdY&A`Aa#lXbk zW6#ao^P6Az@PARONB>1rzWkTz{r6vF;lKYJ8~*=i+VSr{<37+D+OPka_kH-!vFpo! z?(JXxb8i0dpL6A_{~WVl{%4;4=|B5||Nprb{QS>8>ED0m*1!K5n&16rnEd2F^StN( zdFKE4&olAQfA*fg|2evT{ugR~|6jQ5%YV**AOD4He*RZ5`}Lnk^4Wh@{?q^21<(KI z5xekTQ1tA70shnfxwsDfXJlUcpOJIJe+JfV|5;de|L5i0^`Dn(_kU)#{r{PmcKv5( z-u|C~{m_3N-H-o;9sd94(0KNrL;mi65#ulaB~5<*=McE{pN;YCe|FXj|5+JN|L0;l z|DTEJ?0;sK!~eP24*ut4TJ@icW#(^Yrpf=98E5|IVVn7%mucaDZpLN*Sr|6`XJfqZ zUqJBLe+|<=|HYKQ{AXf+{-2fq-G5QF-~VM5KK&Qvzx-dA|JZ+S;iLcAC9nVI()#?L z$MoNS7LVWmnUa6}XKw!TpL5NR|AI&V{O8>J?LX(%AOD%=z5mb9^Ws1I#+o|>Qk8Pk6JXUzTcpQG~se}SC8|D~e;{8tS5{a@ba%YSLRXa6~scK&DLY5LE} z;FBZIsK}tl%FiId$;lwi!pxw`%Fdv`%)%hV1UkDMwC|c5Tz@dLa4@iQu!8G;ZYE|1 zAvR71eik+c9v09ZM9|#=ka=TRItJ08Gz?0o$TUbCghApU8YG7gBdYn&#mEonOz$zzRA~k{NWS3usRmXzwRz9XJyM4-*skEIba- z{!#E*dZ2w`pf(=p+!z)HM$j5v(7Bx~?BM#Hkx7Jsmw}%_5LDKK&XHgR?I)FGU|`l~ zVCD+1Wf7YBhF|r{e}2D5|HUf5{g;~a_rLtc-~Xj|eg7|f;Maeiy}$ko?D+GabNAo> z-1~n2m)!aLzwFkZ|7ExR{x7@c*MEr_U;cAc9BU?E36Kd(pf90&~Crm)!E_zs$D3|3yLTnfu@TXY6?MpMT1a|AGrX z{}-9`@xMsnxBrYG@BTByzx~fz{^P$``M>`{W#9ixH2(ar-0|f-%n6iML1`LBgT!HMkT^aJQVWtt zPWK=_NIyspgh70K7+o*AT96qa^FSD+7DOY9f!HuTFgAz=sRLn<9*{hU4HAd3L46hw z8zhE|LFz!_AQ~hNV#CY;@j)0QhEBu8K>9#tgXBRN)L#ePw*fj+6EtK8TCV|`-v`Mt zvx3g!Vg}zM1G-lSv@Z;F`W+V|Xty4yY~W;IW`>+I13LSXk&7L)5uX9HmXDDgv}d1- zfeUnY4iHz(-aEoBAp$PlpZ_`5fBnzC?%RLKv>s;mC~S6}$&zvATo z|M}~F|L3dv^15c`ssh6*+2gC&HDSFcf!B_Y?XiiGbVlh z&k%m+KUeg#|Ki@i{_`t8`7fdP`adhj{r@a1XZ|y@9Q)7Avh_a`%hdl2%(?%WIHLcv za>o5`d>uu z>wkX9m;Z(2p8Xe;x&5D8;OKv5rj`F087BQ_V(IwL#@Xq;=Sc>I+uQ$)+3)^y)&2R;QTy*d zYuWGrJRQIPi!A>2Uu5OC|6((L{+B3w^IyXK)PF9aY5zGG@-A^OI;gNS>4NtRu!7d= zvN1A%>IYs%UItD^0R~=XX$BrfDF${%0q_~gYw zf#n$x8-!uvAT}}viG%ckFo=yT2V#T7Ko}$s;)5`V528UBBo4wLF=Px917VOmVB#S4 zAdJihu|evPX%HJ-9mpJXF%Tc77bFkDFfnG(xGi{m7PLkOG)51)Sq9W@V`X4xVFTZB z1zN`m8pdS@o%;$J>jRGfb24x+g3jdvjqQQXga)no2A#9R#K6YI#J~(bg9CI259o|s zPVgE1+@O;+85J1Vne^FNn4GhiSu-~>3(WY(rg`E&r~kA6yhUIC3w8eaFAQ3vvirw> zu1%md@PGevuKx3%12ld=h|HbNl{pZU3 z^`9^0*ME)NzyDiYj*wB^r#fvW%iS(1PKXA67( zUnt_se=(=8|5^FZ{pS_A@SmOQ?0;6Keg9dx_WbAM+y0-OYu0}T#@zpm%(4GD*%JOU zGlu_VV)C8I!W=Q3hpptlAZPV|KDNgH0$gqX+1VQYv#|92=U`p)p9xgYv+elL!oKxC z3;VkNEbL4FbFk0(&(7NQpN+ZnKL<<34>p#h4=k)PTbLPb1lX8#8CcmQ85mh57?{~@ z1-SUaSMrI({}tj&|H;Rk_nVKs>OUt}^M7W}{{PIJEB`ZbAO6q8clkd9@8SPUyr8vw zW%n659P}8OOjMYd{3kNAq<>*yp7WoHY2|-rmi7Nx8X{~C3l{&U;z`p+cV_n(!u=sFWqbR9RN76TKb0s{jx zCj&bZXn!^v_>6N7Mqvhi1_cIo&^QpN3;>4C{3i-Fk47^DWI9)w|K5XdYLAA~__k@+Ao5C(~XFpLdShm1ky zg5*IMrUpcV)PTf5Gzi1QKx~jW2*bocG>8w917Q#wW-cWEL+16t?JP!C26iTH25wdX z@VPLc`?y%xSwP26f{v4ftoa0;{lWz5({Y3Ehyk4d37XGi#P5aLQnoPus#0IDs$(*u-~WuVnu)d^OpbkFIe&C zKVQPH|D5hW|BHJ3{V#3t=RZ65_5Tbk+y65#Fa6KRFy%iJ*Zlw7!Ylss2+aG>$W;HI zm8s-EJ9FlLX2y{3Ow10t42(v6OiX6ZoNSKnoGiZ4f}BYoxLGs)3-UDn=Vj^r&&1I2 zpMk0NKLbPAe+Gv9|BNj8|Cv~_{xdK}{by$K{m;(k5zorzqQ}JIsLjBj#=yh?I`@;4 zfq|8qfssXxfsNZ*j9=6{T!`DlU4YfUlA9&`Aq#W-KL&>M|4fYS{}~yV{AXa;`k$F~ z&3|r@&i_2331=92A~{|srj{xig0`_GVZ_di$n$Ny5x|Nobo{rf+E$>aYV-lzX_svP*w%)j_I4`cQ` z4u*gjMn-W~&>al?OrUd|7#ReZ1Q>)FR|d{e2^GA2B`sIP?-Uf1E~jL5FaK7qCprY4x&MP zkQxvMu|aMC(I7dH7ziVq3zG+lgVey}K=b_IHWsMO#sE5BUx@46jIe3^DA@|pT z#(vrO7?@Zz1UOk!7f1_k{x2u@7~F=HOZ@d;s^iyx>L&Gtc|;pJCqr{|qbs{bybD z=RfD9zyCR#|Ndvsc>kX@_~C!{q(}cbnm_&LU;O_+>%4FO8MpR?iXf4;o` z|Ji+i{%5m!{a@Jm!GA8p8~>TO?*C_Ixc#4*_0)e3-P`}Ug5LaRPx|zqC+YovUY}S0 zS@mB3=QsHIUr6!)e|A>Tx}ClM85rjLXJT#p&%{3QKd;b&|3ab*|8sEm{AXvW`_IOh z^Pht`;vYAgM;HsU5d#C08UrJfHUkT@B?Bj$Zzcy*)MXLwvi~AnjsH1Ws{XSu#V%xG z3ZBW#68xB*GvYM|SJXRZ=77g6jPBPt*j>yxIqeuYIgJ?DSmha57)2OZ+1MGFIY4bG z5e61cRR#`jV+IaZGX_>hOGb7U_Yf8qzyA!3!T*_<3;#1Q_5EjIUiP1vdH#QHj++1M z9KQ1zSa}#&IJp?u*$f#t*!}ATSSI{uXPEwue0L@1;fZF0;v-{_7)TC;LEX6kVs{@IHFiZ@@2B}BKAbFS^XbcdvjSGB#3nMm`2MR(1wZ`O5+- zr#V4uzCr6WKx;X9!DRqw%@;c(=)OMC`O*yFbNaZMA$Q#IFoX7nh%qp-7|AemhSzdQ z^dIHZ*z#Z8<;s8Ml#l;)T7UmnoB8Lz)Z#z?`Bwe^&$QzAf3{Ws|FbUn{hwpX-~a4Y zfB$o2|M<`9_V_=8#<~A2uJ`|Q7k&9JIQ`#$j@iHeGfw{cpRM=%e|FHm{oH^5nO)!g z=dpSDU&`gve<_D||3wtv|L0)7@Sl-k?SFQ`?f(UIulyG@dHA1Q_x68Am5cuw1dsn` z7Qgk^7Q`aV5$Aj#gzA-pE>@& zIA{1ZJ{FHq4i*Pl(D@<^3>pj!Ou7tAY!z?}c=%ya(pFjf6$XY!fN$RxnP#0C7W!H7TqSzSN>XVic3pUv*mfBvN3|9OkQ z{g-b3^=05}XuKyf-kN(Rk{{PR#|KY!|#GC&D0{j1Suq^z~ zz|i-flfCD^s6fwuezwa0>j7% zmrnWqU)bQze^#;n9}Jv9QB3Tb3~Zcypz4JIl&={;bH$9H`vkce7@5HbvVzYN;0N7x z%mBH&9dw^NsLE#mWqZ(aKu{V5trLdsHvy$vPT9Y6DMYjF>FTUshf4+4;{xi?{{GV<1&;Pvh|NiG${`)^$*N6X1Ngw`m zhy3|3W}`jbMN`jz%>0oEBnm&(3(@KP$`j|7>i_{xfoI z{?E>L_rJ8_xBna>_y0>Pz5FjHe&IhC%j*A(4E_H(*qZ)J3OD^1$L`|F z$!wv<%WQ4J&tPrK!(=AP&Z5D<$Rx|az#s{(*O)-(iG%Lf0Nv{YTKCGz%D}?O$H2%2 zx(!s4ftd-^-j@USf!P?O7+6^(8CaOb!S`x{@8AX1|165koJ_8!ObpJV987Kr!kqpO zxtMJDnHlsMxY=DC#d))Dh_E;P7i4YzFU*?vpO4vh77Mck12c;-0~?1aBe!70HW8jV z|3$gC{AXd^`kzPs+<%>@fB!X8{{I(u|M{Ou{Lz0_r4RoF-2VO-jrsFmB>c~R7OS8C z8F;S$XJy*(pP6;Ve@>xG|7FxZ{O1$D^`DV(&wo~~)Bh!PUjCPN`}|)q^3Q*fsK5VN zV!r)nD0%arXX%&!{71h17ufdazk1F8|B_yx{MjSGYHz|?@$ zgVe!jkT^&l{*qK3N_n`BhA!GVXY>+!*KzscKSQ#0_SQr^ZL2Ede*ciAOI2bq>c^TN5L>bsY z^E(WB3@i*m9&GHrr`Q#D{}+gS`d_U1%YTKf|NaZ_{r#W&z@Pt|$N&E4JofWH=dQQ^ zSr^^=&(w7PKTGka|C~*~{`1cJ`k$rg-hal>2mhID-u-9M`}kkn`saVmpg;d56aM_? z$oTf3tLVpnrsU868G=6kXY~K|pTqh8e}0`G|JgW?{AXsa`p+rS{$Emd>whWHt^c{0 z`~R~tw*BX1p7>vYW&VF2rnUchm^S|x=2-img9EhZ_V9mZt`q+m#kT$D5Zd!!jOEaO zHik|AnVDw%XJ@Va&&d+^mYu~tmX%eXfrV8OoX?p+_W&_TFfcO8F)%XAF)%R6Fff4b z`(YAgU;^!t1D#6D!~)&#&cFcP3&YIDzz9BjQ-Fb)kq3O=CKChbI6{;BdBMXOCVbz?J)7oGtsm z2y^gqUREszR%UqyCT24RCLZq$UheL%LVQd9vv95d&nbQIzmoIa|CUML{+mSn`7f&f z>?NbYrB8^Z$L{CQ!IBGchppLhc!2XJuhvXJTOh-O~sfb7BFFFN5w+ z1fLlUPLvFw{0|C4(3ugSF>6qo1*LsZdIr&;JOQF%7$nZf2x>S$#tJ~{L3|JmQUju4 z7{mvuN5&vA5C-W1iG$cM^&lF=2g$)Ohz}A6VUQSz28p5Tg^7XGfY=}mVuLVD4@?Zi z2VsyIAPiCi!XPn_8W;@{17VmLh>Z<{9kk&^R9xgD@*2_;CyKAaMFW3rqNaR*sPWf;@HqrFr`P3ow@c=VU1PFTy##lgW zVL)jcbcQzQ76UfWJ%0=W3=E*M0Mupx-8an2#=y+R&A`Q@!XUsFw33y<|352}-+L}r z4|xtweMSa$5e5bpK?Vk9H3nu@TXr7Kpjr`@m|X&lfo;4@7R>BSS`3T~x(qDr4jh~u zg%`QGTmCaK&-l;CzVJV*$b$dk+S~t2=p6sgCwu8X3-6Wxd{Vdniz+|;&nxx*KP&J1 z{~Szr{fM1iaRnh3)KrC4-m$bpn6>m&*Xn ztN!~hvFz7>uKjQRvuu6!pSd1%*2(MtOff(Hb4C6CuNwRRzq0F-|NL?b|Ff{B&1GXV z7h&e$2j_oQUS{xmb5NdU0q-y2VgR)QKxaX-urV<(gYP8;m5YqvG88l(2%2kxrDOCw z087^lPykW~!Z39(8YBk7;4}{1a{%JQFi0H42GJm+K>9!!#D`&!JURx+!NfppkX{fC z!XP#XgXBSc5C)kE!pLGUHb@+#1|5U^2*Mz7m>Q5C5QgzVa>y8FFGvkY9Ht+{2VwBN zd!XWynSlY+)@K4;t_cbk(7Eu;JPgci9N@dHxmlPQgqR^~|3Q1eSXn^hO)?DZOlph_ zj7944W@i0vLFU-85Cx%Lcx? zkAqj5ft%INh?BuHnVZQgk%!%0jG04`fq{#Qfq?^b(Wx*4E3*;<2a7HPCzCBF50kqA zJA*9;E0ZPzE0Y2PC$lL72b2FCX2$sctPC~(S(#h@Gcs5GXJD-T&&bpLpOtIge`cn2 z{~1|#{paDl_Fr7&?teDU%l}!JPW)$PTJWENx$8d@)71YgOf&y8v#$Ej%(nMGGxvr6 z%)EF1GjiPf&nEWhKfmeo|H4UM{`0i``_DV=&wu6_Z~rrOJpa#7`Tjp+{>%T&xu5@w zX8!$eT>k&RZP}my>h3rHbMemp&%~JA#KdMM2D&d1bZ-$87bE0uQAW@mrl32-L1+Ik zF@UxYg6n-o&>TN#Edc1u2++OakTF0|x&@_W5QedFrGHR*hsnWcLV7@X24*%$56BFV z9&8w<4kQO-gY+X~kQ_1wnF+!mF&GWf3$h2q2kC{$f%q^sNDYh)5{L0&Vjw;`2Fbz9 z1F=CEq!)xiZ1DO&(0X0a*_y1Nd!WFVIDi_i3`~ri3~Wr?;9DMfLAT3*=5s;nKnJ^k z@1>PwU}iRAVc|$z%qKPR8^6`@|AMI>|4aA(|1YuT|9|#vzyC9A{QRF`<>&v*>%aYH z+4BBB_qI>q{rRGx^E#@2{%216_@6WD%YUKTpZ{69KmX_M`uSfh=huG;|G)ovRet_w zVtVqQnf>yA8QU-aW#az*=Lr1zpGouWe2EpNbh#Kn zbt|JV0|S#d0|T=J0|T>DEicQA|DsGM|BJ9*`Y*`6{XYv^;eRF$zg3J(UU{6XkuUgI zBmQ%7`fp(7^h#l1@oi&d@#|q{^KE5ian@vH)C0HsLHiq+*_pt1<1&Em$m3;YU;y1S z0-hWN^=Em(_wF$VqoHBfgHlc47$%-hk=E` zn1Pkih=GGum4OvFa-nvJd|m3f}%_DEaoEwf5V8uG$~}`3wI3 zm&yM7->~5Sf1{}X|HYMW|7T;J^OuDwxfOK(Asee81G^w610xTp{mH-pT89q0L!5yD zHr5ZC?+4{~P+yb*+@C^D+n}@yO2eQ$0}=17Vmvhz4Ph7zl&ZfcPL9#s{&H zF~}?s28qGcg6sjQ2l26CkXmFtAT=O92!r&2FiZ?YgZLmlAPnMzXpk5PgZRi8BnGk{ zBnC1Mq!xxjVjv7s0~3SM$ZA1sm^zqR5Fa$h3R(vMT4%|`%FMvV$}+59{V91L8bHQj8W@f%(S24>K>mj(j^qk{^!eESEDfRq1K%YOdnY5DhGVD`WN zoXdawX9A7yZT|nCedX`}oD1InXPR{DKj(~l|K(PG`meU??|-Q&KmLn1fB7#}_w~PU z^)K+b*!;D>{|m-``7h)B=f8;hpZ_c@&;GOVU;VG<@#Vi}!r%YG?jQa$3f%tB$avvD z6T{B`9E{8VOR!G@3Ou85jcoGcv{e=VEF6&&@ddzY^cs|JKS+{woTd{4c_|;lBXm z$ZT8;pz;sY_6O}H1C;}epfiP;I2f2g>wTF)bAX_}JTn6<-Gb6LDD8r1m>7%?qCprW z4#FTd2!qtZFpLk9hl#;xP(DFsgVci5fiOrM#7D=Vya|&B=?BS!_%JrIJ`fv(LGmCB zQVZgPFo+GpAoDkRFg4nAspPkUG#_3lJMr27u1t z1E~RtgZf%5%#bsFSU}^l;5k}u1~yhP1}0_|1_lN*1_oxYN-pWr6GEPc{_EC#{I9a$ z|9|G$pZ+t=dHtVl$;bbkoB#gj-uCZ5$D)7#*}H!IXUKW+pQY&4f6<-~|5cWJ|F6FI z=YNgKfB(x={QA$8_~$=w+MoYCA>aRVntlE+uK(x1wEF-5%mT0e^XWYKZ<74?zj5B* z|I&UR{xb<&|If&H>^~#Jvj0qst-sh96VCB4xo3)Si!<33O#LvRO$j!vS0P3@X@-GYM z@GE8p7FIR}P&vQ|x-=WSeg(8Io{xcvk(Ys)i3d_1FoM=og2cEOKxd$W8o!Xffg}SX zqc{Tt=&WPVm^c>$11ldB0}JThUN$BMCN_4kTbP(d7(hGk85#H(SV8x6F!M36g7#W7 zNHOp*xz|gwW&amuD)`UMnEIcQ!SO#MgZqCzw$lH+OjG_#u^#xZ!hia|2=BW8V%&58 ziwX7o=M-%C&%jvspOLZsKZoFk|3Y?;|8s?X`_JR^@jr{i#s3Tp$NqEh-TBY&{QEyg z`oI6&1z-NN6uXa92w&Hm5HUVNU3*~5W_SqFT_GZXmk0A}!<0H_>bVg!X9BListIWrSzA37^M z|AW#iIE^xaCJ8}h1L!8wwATe|`ATgLa5F3O+d=Lh)L3%-Y zKp2@15=X`$IT!|s!`Lu=Fg`P=ug}QH02+q@VX%KeVoad5*-Q*fEX?5c0BA2T8xv?9 zFDUO@aWZnpO=nl>{4eUUa8IV`a>` z3>?f-41%CNtDw8XnRpl&!Tkpg21d}{C-9xQpz$Zr+D9%1Rwi)A%@idY)sbwnVHP~^Rg!X7hrAuFU+{^KM&KU|D0Tt z{|gJX{TCFg`p?9h_n(Qi@;?hp_kS*t-Ty_L-~8u_{PCaD>*Ie$p)>zkn9lwem3aMM z)b0O&&eZ?^dGde$XD|QopSke!f2M*T|Cuv?{pX1O^`G1A%YO#(bN?AQcK&BpzWiS% z2BaQDqw_&xq+pPK5C-Xi*#V!!g`!C=74}7*ZWB=R!9XP)@-Khyj#|GC!u`!BTh*MHHKKmW5-zWL7( z_2fTq&iDV~wSWE#l>Ge9ne^*Fqy6ju%;G2hGqA4z&&F}&KQq()|Evtp{xdLK|Ifg7 z=0B6E}#K_1Z%)rc~%EZE~!NA0#%)rE~#K6uh&%nl{%D}{=!NANY$-vFX&cMeA zn$HvfuYCZWRRKN&frSCQZy9^ z79IvhQ2UDov=)wqfdzc`E+lR^*&#F=8!LEh7IJ?t7Xu3m2Lm@JCj%QR4+9gk5CbQR zOA$Xq&VNakrvIX>W&c^2T>mpNS^Z~XjQP*RSoNQSVcCB!#!dg(*?a!;a8>^2;wb#j z$XxiJo4Ni!CqwIh7PgiD*_1E-=XU<`U&Q|3e>T3m|GAlN{Fjk`{a@Pq?|+_*|Nr?4 z|NdtN_4`5l1Z)5P=gj>6pF8l|e_^}t|CxkAXPj*M&mgt$zhKal|7s1N{>xQ;{x9bJ z=|7MB+5b$O)Bkg@W^Wc@GGXOnl4f9IG}qz-~Wu0{{Cm3`SU;H+)w`*mVNopw(-w@ z=>vcNtM2{tUu5!||BR`3|FcBD`_CEu_diq6@BfSezyC8hz5LH4yYD|M$Fl$I>|6e` zaGw9q!E*UOJJZ?!tbC{bbI9!a&nmR)KQqVt{|qck{xgEk;NiaXUxatpe?H#+?~JUz zd5p}Uy=S5fp!0@!n2i|tL2GC^c^R0vnHbo4xEPo@*%?^*m>JmknHiY4nHd;CcLTG7 z&Ku@t0G)fp&dR{R!NSY{TJFHk%mhkt3~Y>``-eCgKy!A?AQ8|yR#sL9Zf1}=PNAInsq()V+tmL;?Ct*bru{FbspzHYlBgFhnJU1o1&?Kr~1M#0IGau|Z~!$DDA42`Buv2KNMa!UFtb23$Zik@u|XIl&jMOQ$-umdV#>@_y ztpcYl7SIqE=)6AA!9WZm42*2n>Kqcqchp=D{MXO;_+O#o|9|evzyBG^fBxsF`T3ut z>+gS#8UOwZEdKXj0JJ82_0RvJ8-D*+-0|nX`i|fKWoEto&zgPbKZEy^|Eykr{xf?1 z`_B>a`#*p3kN>>RFaL8(9{$h5zUw~|XdMpIk^d|V$NsbNU;NJ@v;RL6$KwAC4AcKJ zFwFnY09wb#ar(cAz~29Sd=vgNa|bVG<Zz#j=6g`s|G0b#a^wA`Co?5)8ad zLJZtYyx{x~x(baAw0ww{0emkS6Q~da4c&s)%Q6Wuu(A417h$dYr@%GmzZ7TRe-5^o z|6H6g|GC(z{His+vj4NN)clv`?)tC9-~XSBXW4%a zwtfFO*w6mwW4rxdhWY7#F{TIqSs70MXOz0|pV|B6e}>4H|C#dM{pYXy^Ixpx$AA9x zZ~s}XfBa{W|M{PR@BM!P&5!@(;&1*}Zn^hgWy+ub;`RUjb2|O_&%l4{KLf+j|Due0 z{_C-I{gG!4&*EZI0-rkrS~CG^^Mm%Ff`(2&?EugW0VsWg(k(g-5(8n7I5kyW0bx+S2Z@8&APi!o zV(2^_*b)W?P(B8otIWv)x|?Pk;GeWA)Gf zhHHNRSDp3kKY!J`|7_7u{&V|0{?G68@xNsB|NmMU|Noms{{OFT{_Vf0!qfk(Vtf8G zaxVGL%)H^h5Z{siVw}hS^RcY`&%jvzpMfFsKO0-ie__74|0Vg?{pVww@}G%4{xLI~ zojnT+H+UY2nTeNy1+=*gbUrbtuLE9>3#varcaeblG@yBA&>07844}L1L1&kO>S@p# zGtjsvGZSb}DrhenXm2`b9t3ng0%&ZCi2>AJ0-bTe0$P6#x*G|!UXm5GFA_9X3Q`NY z{{}Q~4cf;BIm-eRMWA_B@Oml+P+JN#Rt3uZU>WdP3!t%AQ2JqE0-e7AYFjWdfcnDB zEQ}1GF&a>L0cs1df$Rfe78V9pQ2h^@4rBxMlOgltptJ&Ne}Kl%IN0UH1-UIv6~)3L zR3zfNg}Hq*IGEi7xmh9)@i1lomlf#zFDudcpHHClKMPCDe+H(2{~Y|;|Ai$d{pS~& z`JbI@;(sOv(44^J|2*tH{{`7+{O4la{-1^E=6@#95C7RLzW?V6`SPEo=G%Yn_J9Ay zQ$GFY)VuwkMda;&X0}iNSwtTF7jymmU%d9qf8Nf||Aia={pZd4`=8VP!+%De^Zywb z_y1?**!!QKZOeaQ_D$c|nI;|=U@zFu&7>_S#2~~V!Ndl>+npP9IV5Pl1hhwrg_(h! ziIG8+k&!`~k&!{1k(B{-9t|TCsQ&|sFKF0<;vL$@h2gV-QB zP&mTaAU+I(#6WV$7$gT02hq4NNIggnM1$0#V~`v+F_3ytnE|3fYC!G<(I7rJO@aFG zps_m8*~*~SB{S&Wb}j~P(5YKY91NiIx_G&m8Q8fQ8CZFl7??OfD+$_);<3j8B6~&FeLovWGVhH!qM|zoNdW}UdE~a85z_5GcyPDFf+45 z_L_jkjX<3k=vW7+`3(v;P?&+j7!rP>X|Ljb${}~y*93$?)>Lcc=BID=IehU;b;E^IamDW zVX#+`W)fx4<>q8iWnp9xVrFAtVP^xc6=Gs%WZ-25oj}aQpvu6+pvb_+zz@tq)8b`dW&oYz!^ptH!pOkQ2s)Qlkb#3$h=G}z z3w(AjH={lmD}%i&J4f+Oag$yD4XVEVS6cM{zv$8b|JhFc|IfJR-+#7cpZ*KfeE-j0 z@aI2U)xZDDH9!9|l>Ye7Q26;jW5JXEEcti;b5_6jFWB_?KWEXq|J>Q1|MU4i`_Ck@ z>pug_`u`k)d;g0{9{iRFhvE;v`;GF;L z9ElqlSv|v9S^Xlp+2hLi*b5(Vv1ROJV77N+VAN9w&9i~WDxu??pfE(nps)mCQ2cF@j)1w4dTNvNF7W+NDRgXiGwhx?FWi` zkQ@wy#9`)v*dTGx5CRLR?*L8bptcJWDF1`j(1Ye1nLzCbUIu1HDFzk>Wd=58O$N|> z8xx}u0}F$V2p>m4DnD1u9X96h{jAJBp-fD+3e4;tj*J{>w;5RK{xdVS{AXpZ`p?La z{-2RC@jo+j`G0ZoUH_$&-v8%O|M#EM;@^MXpdbHvsz3dg=>Gp-EAz{LZp91#8JHgZ zXJY#LpG*Dqf2HJK|3y0g|7WTF@t-~U!+(Lqum7c@|NR&B{rg`y^51{%sGtA&!+!pk zi~sXq)Z^2CR^e5DnHfUzgqVz3<=EsHq*w$QcsO|(m^ne~tr);}!E>{*GKjG-Gsv+p zGe|SBFz_+3F|dG676hGH%?v3EL16^L;IIM}@(hqN7sdz4BeOw#P#A;a1I7l?=oq92 z8KcXi^FeAsVxTw$(I7rL28n|($bOJIkQ+d1U}DH@kQhiFL?dI+y_}%-DF+K111o4h zAE=)S>OZo9&h}H~-2UfdW@g}LVqxHA1g-1mVc=rc zVc=p6&R}85|Ie?p>c4W{%l}Ht{{QDY^z%RawXgr#F8%t?y!Fq2u9@HeOJ{xm&k^wr zeD@_|&Y%Ab8GrvXr2qZTkoNgMTh^!le5F7C^W=T~&zbu9zi8I)|4Lz>{&NWK1o!1x znCAZHWt;z>k-hyt14H9~21d~QbJl+bmWcZdjJ}zSEZ&(sqREGaB@*Vbb6Ke}vMVw$ zamX{U@~AN|vT3U@vZ>25u!7dbgX(_J+A)wfA(cNUY+&I83qOz;hz$y35F3O+e2_X2 z4dN5RAa$U40?{B0(+d&9 z;9-+vU~CR7IrQM$iguY4eBR@#+Nz4b4;LfKG->#7`Rv%82Fh%Z3amO zW(GT1CdT~j?9#LU%ZFe4uQB=ef648C{_~vq@t^hTtN-lh-~DIX{NumC^xyxLQ-1#! z4*mU~Gxpbi){I~O8Pop!XGr@0pCRt&f7bZl|JjoM{AZ2%`JXlX+kf%YzyDQ&zx?MC z+Wnu2VcLHVrmp|I%x(WUSPK3#F=qZ}Wd@ByC;Vq%3OvuiXv@INX3oIMV8s{EX}|Ox;KEGpMe3i1{pk01}f7)?OjOQ7?eN2VE}EXgTe(AMj#p#KOi}f8ju(a zgTz7dAPi!I)POKd9!7)YL2Q^Dj7Ap+@j>Q*)PgXG55gcZ5JuMrk^^CoT2TEDqCtEZ zhVel(!tJ2BGLWA^XX8P}A3*D8LHiFtCpv)E7=q%Qk%5*=zm_`yZ@Ou@BC*Ld-$Kv`00QCkURf{!=C*YH^26uh3EKxMy9*}nV25`XAryd zpV|KPfA-AJ|C!U?{%7)i{-4$T*?)e|xBodjU;gJS`SPE0+Ry*|i~s&tUiIt0>h#b5 zB{OdS7qmF^pIdDDe+H(oYz9VE239sM@K`Gg1L%xE9`Js1W=7B$4P@UMc&{20Xe#iL3KV`I8iYY?5C(-mh!2Vb5Fds?VlXyH4#tPkAblWt7!8XDkQlle5Fca) zNDW9HM1#aY7^WX24#OaEWDL>^!XPnRYCw4$Bo9^xZ399&zzhtGY)lNSpt(DCCI-m8 zJfM6J+H(e)tAeaI1Mf@Y0*xIrFtBlf`VI^XTued?986}y42+5M*hCio7j!%GU#{-M ze+AIFu}A;@=REc8Kij33|G6){{?ESU`+vcyzy52a{r)c*`TM_E+OPkD1z-QOq<#L+ z5dZBzL(JFzj1k}eGlYKq&lL6hKWp5d|3Ybh{tLT4{LjX}?mr_#=YI~S%Ksv)pgnq7 z|2Y|w|MRlv{byw=_|MFo`h|(v!xprTm5D)yftg8`fssLhfdy25F@W+rI|CCJI|B%nj(CW>)aM4xs%TkVVSe z;QJVuKzBKS);BSL_8ki_FfoD30Z|4fW?2RfW*G)SCJ_ccMnMJ+W@QE@MmrNu#;C2_ zoTdL+*&F^dGWY!F;9T{ei}S#LHs<5tyHHrUPyQEBJpZ3rdE0+R*^U1>B$oeYVq5l~ ziRt)%Ve#kxd5quxXLkGepFQ*Af0pDI|2ab5{^#*~^PkuG`F}=}i~l)N?*8YR`R+gO z>hJ%B*Zlb}wdnVM$)4~3CG$W0=dd~WpFyzm0|Qfd4mXP-A1|{Ic>OsqBMXB76Xe!m z(D`#rjI7{uszCDups)mCP#9qgH<%cRMpg$>4~i3zI1GctKx#mI5F12;)PTfbd>9+X z2dM$EabcKxkUDgXOAkyOSuaQo#0Q;~0va0z@fkperhwWqte|zMpsJpc0kmI*6*Tw7 z%+0{a2#Rme84Lp8HEt}-9N@EAxwse^KwH{C^I9AX91Ps7S`1v=@iUqDd;fE5o%+w8 z{_elvoPYm$cKrU&eCE%8hKoP`Gu`<3pYy_}|7@#%{O9lb^ItLX+kf$}um2UY|NNIM z`23$U<=ua#*iZi%VnF!Ef2Q~!|2fh?<3E4?3uJx!FXDUWKZp48|BMVx|5+Jx{!8&? z{g>fM{V&K7^IuY=@V~TR`+ot@dR-QGPd3oL7FKTX932Cb2m>=CXrDbB0~0$F1G_M2 zFF6APXsnwRT>e4Y{^0O{_Vv)i8N>(4gTfC)!}y@M1@S@RAPi!Iu1+)hdH0}%9hX~ql2b!w{try^7;9}upkYM3s5M|G2!E2{E!S~g$gN_pdje{YT|Ddo0g%c<&K{N=1*dTEbhQ$d;3>|~ig4BS- zL2M8WQV$XXVPr878-zh>Ks2_x9;6p04$==&i;O|~kTFaSBnDCs!XP#bgT|X#LF-gN z{spZq0SyL$=9xjuO+ir&s_xks!SmcqERej<1UjdRQ515KJ4hchXbT$)0~a&s>@R)> z4klFwHV$8B9@R-7IqeSp=L~)PpS$tff8iDX|8s5n`k(#q_y1hy|NZB>{QE!G(O>`B z=KuN6S^xdNQ0%w=0-?YDOQ!t$FOmM^KTq`6|J+f(|FeXB{m&5m`9Dka&;J7HzyHgX z{{JtL`Rl)k=cE5@BCG#1F!cUsVXpZv!dv)Xf+zRCAXn0VNuh%O>Y@|>EAqGe=jHHp z0ku+xefBW&MA4mR0|mnV0@&V3_xxjdAgRHs*Q%nHfs{voKZu=V71sUqbrG ze{Q|||Cy~n{AUdK@SolP&42l@-~aUz{{PpF`Tbwm_0fOM=tuuqDqjBQ>HPkmee%!$ z939{PGqrsD&(`_-zi7{||H7Fc{&QI#|If_R{F$94B!h`jlbe%4kb#$xk%5V43hKQlNTfYzHp z`huXf63kqnHExUypt%wz&>D2mTsWf$0~3Qd0~4s^1kE3_GlACxfcBg)GVwC7F~~D; za{BwQi8nsy^Ev!qvg*TsuD0+0`DXq3FTDK!fBtnp|BLPU{a=l1^jUnuzZe>V3o{~4@b|7Udl0Nz6)o%#R2 zMB@MdBChZMvxy)2&&as+KPw~X4vCikJdBP1IT+Lb^Dw3Smu7ALFUwy0Uyv;zlATG1 zfsq-sH;0XZnOT&9l~D$|{+=Ctk06@>sNcuWz{U(Zvy=@ymyR9}ps)mCm^g?H3R4gU zv0)e#z92SC9K?rVkQj&!l82cC;)5_u3?>fagJ=*2sR3aS8-!tO5DmgGvtewI8W0BY zK{N=1*f0#|gXaf9;~t=Keo(y+X$LSdvNJF-u`w{Rurq-67=!1DLGxiOp!spo+8AaA zHa2z!7SKLV(0U;j1_n-M1_oIsMg~O&CI%r!HU@T9VFng<1qNm&V~)8G8N;v8?>h&U@rP zv-q9=OeQb?3%Gv!ui^afzmDDS|LUgS{>zwr2A}c38u0BuyZ^WUO!43TvsL{0&(!t# zKj)%f|K+y+`meP3*MFtjPyYq1j{j%n>HN;b80gNzsLTL5GmHbYc9I3OKpj-Zu`+
DvF-G4%Q`r)>J4j}MgpS^u+haQz33 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 $WX_j%uvdr$dJRN#E`+P#1PG_ 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%0Dw7|>LPlSP6-;gndzf4p_ORPBoa8WLxWH!0 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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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 T8s-`7>F=i-;pXY%s>c8TfL{fT 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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

Ca z%2rU=g2E9LZ=iAxWCtj&Vd)7JPawB|%t6K=F_1l={0p)Z6t^G@D&s+M0df;4--E^g zKuVqKZ(U4^&kw=55k1(8Yp>? z+d%FF`3sZ|Ko}G^p!5L3pu7a4L1M^iK*wGHUKETL2(93 z!=U&B#W^V5fa()ac@HWBKy3g}Jqs%HL1hCdZG!R!C@+B07swot+i=+rk^`v$r4vwH z0!rhcdKXj%fX3oMWAdPJIZ#^|wB8JqjzM_`WIo89=zat7L1_%+UXVLL^)D#zfcE-= z_WOg%6;OK~WIm`|0r>+o{szk5ptKC~E6Cp<3<__M+dvo;4j?l@c^%|VkQ+eh8zcrw z>o7Y(c@Bg@>OtWN@;4~&faF1aCD5F|x3~8eP`L!M3*>*08e|O82NDOF4=S@j^MRnX zgrIN%g*~Xgg@rT7ZJ>CD=?Ccrxf>MrpfCdY2^0>XG5}QGf!af$u?Wz32Pi&4Wd%q* zC@evCg7m}8g3+Kb1<8Tz0+n+hvqA9%!XP_9Wf3S|Kw$}TFQ`rcmA#;}0`db$4X6wN z=>^#b3J+}d!0f|CgWLhqhYf?m02!mJ0qF&q3!_2o!G=L@1GyXI4^Tb?VNh6s;s=C5 zX$VAv#6WVOIDugpADsr71u~Zyzk<|()Pu|b*#(L#P(A>qWl&lL#UUtdgXVxi<7=S! z2K6UFZF5k%1f>B`830Oapfm@ngFtZ(N?RbavAF>x4)PNyO@Y!Xs4W397c>q48oLAK zS_xLLHP)ju0U=C`5)a}keMJmL3V@O0V-2K>-<4wNos29EEopmMNmH< zR4;?t_@J@^lx9Kh0Qnc>E|3`@yFhh6sEh#l5tR2}YCz!uYPW#YgYpN+ZJ_cHlt)2+ z0=Wf-LHPw_m%Y9H0Z=&vQVS|SK=BDugX|AbIsk<`C|p5d09rc?T0aQt$AiX1LE!{) z2PjUE!vJIsh!2W4Q2qwF6I5n_Xpns%{h)pTNIys%>Gi zcY@S`{0j0L$Zn7wAag)%6;N6LwJ|{LD^QsVY8QaQ6I5P;$^e*MAbUXR3}h}T7~}>R z2DuT$2GKAKG82YjVjwjzOp09tp%&y$kh?*C0OdhYc!Rp^WkP<{oC zn}OPZpgaUB13>j7C|!Wk3CIkPJFvL{WGBczkR71>0ZP-Lv;?XjL3ssK27tx_KznsT zWdLYB3&RM z1vEYf8m9-9YaqXY@&~9*1Iq6p|AE>bAisj`o69?<-WkB`r0(EL6qzkte0kX}%{f%JngNG*s4sRz~5pt2C;Mo>Ed zRE~kd02JP!G6v)pP+Wu5gYAvAHZgt#sROA8=>=g>Is(}TvKth4 zp!5QYOHla^if7Q64+w+GeNf#35(Bj}LHPod-avU2lvY4)0-24?4In>*t2eny2WdLYRAE*ofjR%7A7N|}Jr74gbL1w_r2H69`Aismc3zYsr?kT zUc^Tv{kROrtf!H9kK=y*d6jTO-!T?kbg4_x! zS3!9d#0QCk+zwI)Du+O3f$W930Y-z&1f>g5Jb~1}(gR2zC>%ibJSfkD;tVtf02*Hb zm5ZP-2lWL&eI!uW!0Z5-0}3yYxiCA3r9pboF~}?s2DuSL!!WWsV$B~Ybs)Eb+zs*r zDE>fU1j_%g@CKE;pl}CaP~3vjJxCo4gVdvAP+Wq{1DQ>%zd&k1dO&(XW`oQGxdUWB zC@q2Nbx@pw(hw-`gYpHaz5~%9K4^Rilz%~S4pIwBBOv#J{D#d9Ap1aef$|k7y@S#v zs6GLi0ZK=p{sm|r0F+-q`4E&hLFpLO4gr-#AoD?P1i2aHUXVR74B~^r59AI|Jp{^+ zpmYiAW7XCQMy<`d%=kUEfhkUKznK^SBf$Q__G z0!k~O_C2V42en~A^)aYz4XRT?G-#X()Q$$pgVF^keSq8z@(ajJY;FMA334~6jsVq# zpt>E@4gl4cpmYUlH-pL+P~8QppF#NnWIjk8s0;wrL7=n~JZps_-by&!jh{0<5iko!UAfyy9I`v6ow zgZv2c1IR9r*`V=7P#y=BSs=H8{?fC)i2?mWtfWiz^9)a8fayv*J$PN$< z@)O8ypn4TFRskxTKxF{v9zu{CL474qn1TEW(gVXFy|8!#g*(W-Aa{Vu3s^jX{0p)Z zBo1;vC|*JKf#LzAA7&Pe28n^}K{ppvCxFZWm93!hU{L)Jssljn6HpldDi1;72MT+T zT2R^rxd~(rC_F*tf-uY;QfQDq7zUYzj$v{jniO*fLM_ORAort(F(}`G!VDCLpt2l< zLGc6<1Em*GeBr{N`~XUmATvScgD|mv1E~eM2ZTX!1&T9}y`VS*rDIT;4r;%G#^gZt zKPWwd(iVsYAXojzD=6 zl(#?_lqW#x3e?U3l}8{wpfn6h%ODJ5gTf4CCkTVW3d9GAgYq)SE>O6E#6fIOxPsgX zvJ+G{g4))gyazHLqz)E_ps)bdWuU$Us4N1Ne<1Tf{sQ>{pK0#py@;^u|NFTCUFgDCg zkbNL?L170n7i0#gyau%iL4E;Y5F6A#0*zsU+D@SO1ce1C4nS!MWCq9{kbglKWH!tm zQfZK05C)k8!yrB~Ce_S=P!Do1C`>?M1IoLgbO#D!Q2GRg8z_y!Fo+Kl2g!rN6B&c_ z!7#`iP+Wn`1%(aB4iF~Re;~CWy&!WyX#-?GC>}v+2UKr?;u=&3g65S#=?awpLF?W? z`4iOs1LY-98U>{jkQ+f}fy_k4Ah&?{Aa{cN1xkmY{0yq!L2U?79t4>Ks=q+?fZErf zy>p6Ub%Bvvvfa-EkIs>T((V(^jC~tzo0u-hoyOA--PLSOoF_1jSk0AR&?gU{_ z*n;Fh;Rd2XVj#DH@)4+g0P2r}+yDv_kXaygAbC)EXlZGA3{*CP!VF{wNFEelpuQ6* z%s^!asE-jJAHM*!Mgde;g32b488G!A`#|+3D4aoUDp1}9l>wkNK_CnYcaS{De;_^R z7-T2NJdixdU7#=m*$pbEL1hQ1%mLNipl}1_VUYbGH-hw_n+4;85777|hz6Bgpn3pQc7wtUqzyE2eCo=L2dw< z2eKbDRsb3=1dSbn#6aeP%mcX}!55y&o3*u(sT%RG>oAh&_y3KT{leV}p(G%gDovjOFQ zkUby_N&}#J02Du?x0DNx!5<$q9p1)6^c)z6^%1ylxr z+zxUZ%uOIOLGcfB1IS$f3|lL1x120);gL3tk(AE0s&BnPqs0ty>YUI1ZGIs?TGC_jMO zw4ihWqCprW2E!meNF6MULE#M24>AjcLFR(Y2Vsz1APlmXkiS7{Kx#p81}a}b_JGO& zP<;lfb3u6oghA~AP~RAozCdjOP`(6}{h;&#%EO?v0&*+JeW1Jsb05e(FgJkg0l5|A zZje04@1S%8Y7>C!VNjX}qQg(GtKf%KwdP%50FCptchz zen4>p3VV><=w=}ELGmEKf$RpQMUdGb_kr3JpfV6t_JaHb8V3NC1E8@=P~3sS7~~dE zoPxpv6mB5XF3<@7mIDzs3DBM744itu< zFo$6fACxyh7?i(2d{9~fg)vAU2!qT4g)<0)%mrbPJs=DUcVheuavMk=$UKlcK=#4X zCa8`Dwf{hMGpHN@)xV&6282Olk10`<+Eot^i%ySwiI&DVj<2GwUEe}cjp6m}rDf@qMx zK<#nRdVSEGA*kE~`4i-JkQx{U`3DqkAiF?$57bToommPBBT%@))PZPF7=XlJ?gQm@ zP#XtSzJdG)Dq}%y6j1nq)WX6Jq!$^3#6kW9g+IvupmGD0|3TvuAa{b=>!ADzDwja< z0&+7*53(89*dTv{+y{z7Q22qu71SOCl>wk|1eF1x{s1V{eqCpsBC&-;3401oH z>;ahpvI|t7f$DcqpB%KF542|v)cywLL(updsGSUAgZj&$^>ZLMf$Ahs9Rf$ZU%)psBHskcYxvqhC$^PD9?i0FCeo(@c`3{ zj|SNTax+K|sH_8(X`t`~#XqR;0}5kM+aFZUfx-tAU!b%Katp{$ptK3H3*>fCngWF- z$gTM7#3u)G4?4!Dez3}c`~V6EPSa*=2IX^5dIsee zP#ptmmxJn5kQgY9fYg96NIj_R0MQ^bK=l~NUAWu<5{HEgC=G$^1epiwe}L*4(Ec{i z+yJPa2F(wE)(nE?+CcR`sJ{+s4}i)DkQk`%4VtF}rF~Fc2x_N*$|_LX9#n3C*8YIX z5|BDjI|JliPQ zpfCoF`GfK!XzT#wW>6S_$^p>WA}B0DWje?m$Zo*J2AK;|3vwGM4T9VU3JXx%3{=j5 z+DV{(639MK*$NT|xd9ZS`=B@nrAJVBg7N_<--E&*l$Ssl#0H6j)Pd3lEWBZ50LUyD28BJye2{%0dkKXx zNFB&cATvR25tutbZh_@zP+9`j1E4-2D2;*AI;foqN*AEI7F2J6@&G8^gV><_1=0^n zOCWa1{7u>`$2vJl|7(xBOoAPJ?Q)(kUo%~ zK=Fm{7f^VE+zVRQ4B8I>YLkHC3nY$gK8Ov{3&J43g2EP-|3U5pjTwT%666L@o&wng ziWiW2kX{%Dr6G`hQ22t%3s9Z}wE;k7GH84RghAm3k^|*`P#Qru17;R74N?ON6Oi4o zGyqC_pfUi|4gj^UK;6_mGObu*}44QdB~>RV9W0Hs?{e-T83`VAlqax+LjD1Ct3 zL&z;4d5}LqWgI9BKZLGcFa?}N%9P`d=AA7lq8JVEj>wJ;i_9~4(0KZ3#z)cyyJae(?gpfCjGIZzmY z;uusWfz-qFf@qMLApM~D0fiB$UIw+xLH!3%`3P$FfXWh(Ss=H8)PnSY^b*3Lcml;2 zNFS&S0L48h4T8c2l;1)96;Rm=@*{{3@&iacs15_A9gtlh4Dvh3?I3&6-A~93!&M#> z4j>E)6HtBwr9n`5gVGcz{6KLIic?USg7ODC|LL4}?MDpm+g=IdXV|!WqK)3uw*~G^PkDYe05^!VBaFboC&;AiF_v1&T{hz6F(8 zptVP!b`8j%Fn5B=d64-a^&ovP400<-KgchjIsxQ=P&)z?2eAAPiW5-T0E#=1T5Rrs znTJe+;t1q!P+A0q2gnXkyo1~d@+T;KLE{`CyFlR$vLED5kRDLDg6slekh?)sh#d{CYM#Ve@H z0EIaW!{PwM2bl*l7i2!j9+2IH!WyIolukfp2gofT42oM&S^&8VlomjCgWLlervvpl zVPya)pMlz-pnf}OtPDhh@(d_ng4_+#2Qm-j9+2Phxdr47kQz|g2+{-cD@Z>GgWLeZ zptK3{FU+kVH6U?NS_Op>C?A7pP~HI955k}_2xKP6Y>*wuZUFfkBoE3np!O|j4gxes z2pVGm^>;vX>Yy+M>4oV7u|eSjsuMxyn}GHKfXV?-+XQ4cDBM8y!PFzupg01R@1S-% zNH1u90u*kbvJ>Q1kQ~U5ApfDe6%?N!ahQKWc@q@Qpz#?{dkj=2gUS_9`T@lo$lW0S zp__y39uONO4~id9x&rwNWIsqhC~QID3JOz@-$3OnsQw4p2}-9RH6T5p_yG9}gpt!8 z$m~)4p8*uUFbqmNAPkBZP?&?_6BLG^FbBmeC{96X0F;hE;SNd@ptK3XATdyUfYK{0 z%t16dhQ$NO4v<~M!~w{CAoZa51L+5u333-KJ%HQ>%7>sb0F>T9eQ?nD6=+-y)b0eW z5dxJ9pt)#Jc>t=@KjtH9Wc?s> zKzSP!FQ700^%FpIf1tiUsE-7SCs3Gy>;|btwiD!DP?&<+G9bG^ZGX_Z1W=s~vI|sZ zf#Mt#)*%04(+?5{r4>+J0SZ@8_=5UEpmsl~3<8zEpmYLqHz?eY-Hz-QWH!hikUEfh zklR7#gUT=v4KfGhCQ$wd<$Vwa`4eOZsC)pa2l)vU?jZf3H~@tYC|*G3BHJ@k*`RQM zVNej6v}WN>iXV7zo44c2N9)^uWR%6#gI# zyFQ`urO8=m}b)Y#rP+0(4 zX9Mcfg3hJ^jYWaV08slBq#mRXWgn<*1d1b2o&fn1WEad{5DoGNC>%lRKy!?sF-zFqLr@+Dm6xEn2DuBQAEXBv zgY<*)KPVhQWduka$c>=92^tFljdOzPe^7n}#T7^`$e$p6AdGAlIvW%wuy_W!A7lpz zgUSGqd7$~P`U(#14tf}4nb)Tq#tG{D4fv!k8bBk;e)~fhC%TF!=Nw% zg*zyXfx-_I?x1)B#VaVkfZ`TZ27u}sP`U!;BM=QL8<4{u6#k%e1;QXUtPCJ0E4WtZoDO z2b5l5?gH5Z!XSG<7{mr)P`H5Hft)@-;fKt}#|EhdVVD^pGeP+P6gHqR2Du+JrT}Wk zfcnj#F+b28A*e47D$hV=7RarjIDwe~vJYe?$P7?D4Jt1{V+5eS0%)!hGzJgKhoJa@ z`4OZChCzBkegw&Z+8Utn0hJq|ISi0lpuPtvUxDHn6viO6AU!bsAR6QrP}qU|08#@= z1E6#Qs*geKe^5INwmq1`2af+5@pcaSW0N)dQe70_g+MpganS3s@NdG8be%$R3dW z#Dq1n`#|Xeq#u-yKxTo=2Bl$8ngG>Tp!xQhkO0Hqm_evo@XW`X<+N;e?)!Z1i2lwU!1gWN)jyFva0xfPTSL1u%(17s&Cyg=ay zax-Wg3si1_`ud^90#B{0%4GOAa{Z0 zEMWN`RDOcm1)%Z=6kZ@dBijcu59DVMMz&|9vO(bh!=N+`!=P{iVNiMj#T6)SL17Mx zTM!22aZntC!Wxu+K=W*%G62;60;NTeJShBO7^DZJ9~2*;xCF%sC_jMA2iXI%7v>Ip zaRE{TQVYW%y`Xdib0esX0;LO3Isl~!&>9rbSO93fJg6)HVbHpLP&*gY#|52#2TJ## zu_KV%LFoqMc2GJ6m0O^44CGG`4Z|QiK=y&`1-T!TCqV8(cQ;53NGg4)&~H-g$Cp!EcxJO}FMgX&*U-3`irpmr@tEyzxgIUqBU?E!@$D2!qCAgIj) zTAv6i13>eRps_+w{s)!Opl}Dd2U$Oe5Ar9-4p3ViRQ7=Mf!qQLGf;U1%3mNigW?aQ z9$7z#4e~$8e<1TgYC!%3g(Ij;1C2j`+7X~R2vEF$!WER3K;Z{+J1G3HnF%r%lhLg%ijQ5Fg}LP&mWv1Gx*N9~6F|^a3&$gh6(UV#GKg zC>~%K6iy%v3V%?zf#Mew?x3^+ibGHwgVG%+Z-B}GQ2GL;Ay7F0>KA~-U~vGWLGc07 z2Qmi~Cm=IHaSO5oWEaS8P`n^xV&Vs+50s8z^#mxdfb0U50ibjN8iN9*Lr_@&qCstI zP?-QaqZ?G;f$|KfoeIj2pfU_pAA{TvsuMx}1C@ zEI{^v>;}0J)DH%Y*Mr8|K=n6hj{qnygXTp+^I{-Bf!qkvgKQ4STu?lM@+znt0=11m zc^@2J+SZxxd}A>0BS>k`XHbD$X$zzuWDYt8xfK+KAT=PhAUz;Afb0N;DJ=hk+9#ks04Q8RZ4OXc1i2TK?m+1c zl-5A{L2&^x599}s*&vK==Sbv(!U2XsVFSXTIDuhM8UckLDEwhz4~j<+2BjTP`UB+& zP~HdSIZ!zOO6wpD%15BI2cki70?Id_IDxeTKxqaPzo2*l*$1;5M1wHMUBtu*NG-?= zP#lBO2q+DL+yzPtpmYct69CO^f%*lYv;xXspt=n-CJ3tMKy@vst_0P`AU}ZeJIGHU zKY+{t)hnR<2+F@8^FSEnUy#2*=?s*%LH+`zOHf#W+yM$J5C-`fWDdv-P+bL@n*`Yl zs^dZPd7!>Jhz8BOgYp}wz6GfTg&RmeD16Yv02D4DKZD8!P`HBJ1!~8D+S;IX$)GU+ zP~QPmCV=t;$Zeo_1nGmB1!9Bp224LF|AXdFLH#9A-vKlS2r>_3HpmYkcYyT3^n+*+ z9~5sOIgr0WVGl|dps)wc&4bEX(0WObe?Vyk6fdy22c;*Fn?U{mnT3vFX2aqYWIjj_ z$jzW|2IYTHxdjSS(6}ck|AX8LN`Ig@1NjToW&^1Q=>@qFWERN1AhSUj-OiE72ZaMN z2E_*qgTe|FW*`ho)1dqciepgtgD@!lfx;M+2S9lS6!xIF2IV189Dwo&41?-cP(A>~ z3n)E;(l*E}keMK}LGc4h4P}Ez2cM771BxpVYG^P(~r-Syv z$Ucz!K=XZ|umG(u0?i?S)*OM_Odx-O@-!%H(cJ+u6Xq6BxP$BgsRh{y8e0Li{Xy%U zKw%EbuOK;4ngNvopg01>Imirjb3uHNILJ;=7=YA)Fepqw`5EMIka?hV2FmxKG7~h$ z0E!1t`U9moP}vMhn;`WdeIWfHKZDE!xfx_W2!rez#fW|YC`>>Y6h@%10%1@bfG{YI zL1if@?m%%3%EO?z0Od7MIs~P4Q22x58&o!fXb=XmK^P0qxBLwfRBq08qXI9)93kn-h{s-v?wT(b!04V>1&MF4=O+aFx@)Hz4 zxZDB~2c->=dq80hN(-Q{2I&R0sX*l=$b4A+4{|do&4AQ`^nl!g>^2x1WG+Y!mi|C$ zL2d@67m$8X83{^Dp#BM{90uio5E~SqAp1aZ3L$?mH7IUC zc>xqZpgILq27u}VQ2GR!0ZI>`bOtgLhC%TSiZ_tmAh!@3KOnUrJs|TyX$6!fL1_V0 zCnIA}+6JY6P+9@G50r;M^VOid4NCu@aY9i32IYIu*dZuif$CRK{}|NI2KCoLG$_A- z+S{PK4AKYcQ-I16(D)*#P6lDn+yh80sI3p`^Ml6OKz(dbxdN)cK;Z?-H=u9?m8&54 zgW>|+&mb`n289c#eglE&+=k6;kb6PqgVG&HA1F>?`5)v)P`eIP#)9Gy=1BE3>4JduV(h&Z83HmJG>!-=t3YOg+B~3m0)+#5n1S*DC~QID0P;7e3<1R#XnYjZ{|B}GL2d)Z z5hz|j;fQVqG9Tn7Q2c=M8YoOb@eb-of!qaZ2Y}jsp!^St1Cakf@d2`*khvhYgUklG z7o-;EZ;*aa{DJC!kh?)?3Y5M;WdSH{f$~4dPariQ_k;9-!UE(*kU1bTVHjiwvR_8o z{~17G016ja7=dU|xPdSz3_)QD!k}~t!l3X4rAJWQg3>T3U4q(Rptu0l`JlJ~rBhHj z4Qs1`;tEtwfYK>QA1F>i=?8>CW`WWNhz-IZHYgv1>;$<50M&({egUW~0Oeg!-3}US2d!-ZrEyTZ8B}+I z>N?OEBB<>TYVU*E;GljzXuJ_LUH}>k2Z@35C&+wIz5s<4s7(U0ALKreA3^CIo8Lfw z0fjdxd_a0Y^*$({g4!dXdLPtQ0oe;`*MQ;#TR4HlKye5PQ;?mYJOXk9$UUHb56CQ# ze?ex0$_J2rAbYX72P6hE4ErBqo+yP-w{s+l{$|6u$f%Jjg2l6+_9FV;r zGeH>SewbaOG$ilC!T}UEpfCbqP`JS`C@eu36rP~41z}MB1eLX*d;%Oh%#Fm*Ab)_| z4$2cScY@3Wr6mv_gh6bOU7&gjl(sNk^{LP6iy)h zAUA>f6`-}jpneEwTmqEuKysjP0;$Jk7AWpOYGCCa$bF#l3)F7`%^QNoJwWaT*#!!3 zkp0-qf{B6b0=W%j9>|ZN@(oljg33P7SO_Rjg32UN*n<2F3TIH5gY?17z(s@X24PVA zfx-ynZjgRZxdn1JDBMBy49E@`4H5&%fz*KF6{HX3XOLe&=72EBZV(2U4Z^tWAL-(t zumOb=2!rw+DC|HO6qcYc1z}M5f-opfLE#OGV^IABO7oyP4iq<_JOau~urdIYenDjb zs9XT0HCTQC<$Vwa#W9Emr74hkAT}tkfb0O-4RR02ZKT8x%>5uYgVHOA2KfbqL1u#Z zAaRgAAbUaeBg`M5vH+C6KxG-o51_mT@)Ib}gT@a)`5L4aghAy82!rY$P#yxg9TYC0 zv{vh=ryFhUZier#}Kp12mDBMA2gD|pvqijSw02DSL3<@hyn1R9$gh62m z!=SJQg)u08Ko}I}pm+x50Z=@E@(3u7K4GLGcI5|DZes%CDe23Ysqjt&s+e0f77l z$~z#tL2iJ#9oY0fZ`t%b|5!`!Uv=W*$jMa zkbgjK2Bj@foPhE_s4W3%Yk=Gj!k}^hWH%`PgX{vOJCGWXU7#=l`2%Dp$Q%#`g)hus zAoKCrKhotu;RC{;FoR)G7=kb;OhI7_!=SJRrF~GigYqpX&w%m^D2_no0H{3x!k~H- zREL7{KPX*;$^eiWP#OW{BT)JSrB6^Cg3JJkf!Lrj090>*(gDa$kQ-p}h#pTMH-j*` zIEW7thhY#O9fRaReg&BiN_U|A0?H4d^b2zvhz7Y0M1#aZZUU(Tg$YP6$V`wqAetBq z@&`yS$P8G10@a(KumSZoKx1>DvqnL42%x?`sGbG22SE7%6jmVjf!s^TE|47{wV<#D z)%75=K;xI7@eG)GApd~k5ERBBcj58}NDdS}AoZX&KFF=0u?|rG2viP$#zsJ60H87o z)HeaeAIMx{+zIms$Zn8&kUgL<2IYTH+5*`JDt|#36i%Rg52|B8=?f$VQUkIJlqNy` z0Qnna4hVzd2jq5;*&qzFYm`RR{m5Yf3L_8(g&7Eg!VeUVFboP?P#A+SD7;~D1&RYu z9s$KYC{93S0Vu9OWdW$X2DJr1WdNv-0HqyJ9D>w<;uch=f$9`cItB4T=77>U$V^xn z04i%hc7xmlau>*rAPjOlh(;F!iIa*!=72EB4v_yq`4g0`Kr|>lf@qL`K^POk=VieFH9 z0?PBCG5~}@Y>>I2b|1)GP`rWc2kFCR9yT$MpFnX8(g(5&R5pU#2TB{DbOFL3cf;BM zAR1OCg7kpm1cX6mz|t$oevrAi{DsXOBV7y>R-iD0VNe)?!V^Y=*q|^5r5R9O0EIm$ zu0Z(}l%_%P4@#?`xB;baP}&8R*PyupP#XY*LHz zH&9*ynGec~AUi?!!`uR*K^P_max=*Nl*S>*43JqMvym}O3`7%a2godt8KATY@&hPM zgUULPpF!;-(AXKMZx3plgYrHIgXSSXbt@>WKw$(5M^Ih{=?8^9KKnuTf$Rd=18U=g z+WnxpOVE5DD6fLd1La{*Jb~1J%ml?F$c?!C1PXUh*#pXpp!fleLx9%&fa-n_28~sK z<^VxvgW?PnUm!jB+=*@%NG-^3pz;xv|3PgRP~3z34YC)M9zgB}g&U~u2e|`OCVJPOJ`pt2d1pFnX1%KxBn!(}GO{h&Ao#Q{hUDDQ#X4a)zZzCS4cgD|KZ z1C`-0GePdaWhOSagVciD0ZL0CH-Y>LigS>8p!5W?8cy`1k~pT zjroDb>OtWPDi=WVATvSn0tzpXK5XWJ>;cJv;uPd|P&|Oz-Jo_psJ{=Y_d#t6Q2qzS zDJXw}@+`=mAb*n*_8@Q5ikX;};b6<)h8hLfcy#yGY}2Jp!f!bAIROH`X7}4 zLE#8WPoQ!Uqz0r9q#u+XLH+?@keMJifXv5b_oz7T{0|EoQ23!^P`H9HD2zd24hnlv zyo2HaR0e?J1Qv&&bP2+scm$;*P@M)!FR(HIwC)DfPX(nT5C)|yP@Mv58-vD{KxG3+ zEhycA;vAF?LFE7_Ux4BtWG;vXr4bkg$$`W{=@H}(klR4+1i2UF7nt9WX^=R~PoVSz z!XP~`4AKh{17Q#wgh6a{46+O4e~_O+?g8ZsP}&8hSx{L9T89J5pP;%Plt)49dO>>y zKx^_q{eDp10;O|M-UsOig(t`^klT>m3u1%Z400dHpCEfcO<2!s3$qCw>k$Zk-0fXYOWA3$RPpfU^;?;txt zYC&Z!$PAEKpzs1=P?&+tN4Im7PdMMBhY=_okufN2K^PR?ptJ%Cdr%yJ;sO+JFbqmV zAPh>QptJ-k13>i%C>}xme^B2Ml>b5F-Jtvqs;fYCBPcI`=BiJx*?4^TM( zG6#e~X%j?)(ljVdAY)LT0Lg*U3CMns+d%mfgWL_u{~!#@|DZ4j#S2IsNH56Ep!fsX55gdSfXoMBbU%*r zNiPGC!weLrpzsA@P#Oh=J17o8aR7=75C+8~C~jahC_RBNC@q1~I4CYbePK|Y2N*ex@j+@p z=?Ii&KyCuL4^&=(+zfL&hz}A6$%E1rEbW8L1(^>r8&r0I(jSNh*#WBiKyCqHQ2qkt z4^VywUL0^fx-afKajsb?go`NAa{exSddzf9#D9K%m$eOawodk=zNeLL4H9` zH?TAVax2JuPI~2DL*$X#$iUKw}CK7 zKd9^lmDQj&1*q-^m4Tq}0)-tYu0eW1=>U`-u$hl6266*P9Y{S$AGS0DaswzHKw%Hc z`=Gc4*$FDgKyCv02c!m+zHsG#WIIRMl;?YFVTc@;p!5lf3s8KbV^A7FPFtXQ1(d%) z`2bW7fZ79~_9bXt3@CqrFsP3J!l1eoR0e?doq^h(pz;Bf|3K*|lovqb3$XeZ zRGxtHC#cQ>nGH(6pgIebZ$S2f@(?JGgX$9y2Bj+yA4G#ND6N6&O_2LQc^+gJC_jP9 zC6F6Hc@8v(2g=`|J~t@8gX(w?4eE!3`t_jv4stultswV-><5_(vJaHMKxr40{_*8| zP?&?{LFzzd6v%C`JOOeSs67rUcR=lb&>lvRdq8YZ`w7(d2dM>xH7Gtn=77w@hC%re z6n7vuf$|l|eV}{>(hD*JRAzzf0);&&KZ43pkb6MtL1u&82QmYML1_Y;9WXJFJV+fV z|AXuVVUQUh3`!>;GeK?ug%ikbP~Hc%w?Jhv$p0WW!SX*SO@Z`-%mSGS@*~K67>3z7 zO4BC)gTfUQ)}XKlVNe+WN(&$iiWeA$#VshlKy?NvenIsID2_pS5;V>S%DAYjevp|Ue}c@1VUT^Jn09#|7Pg@928BH+4nT1M ziW6iEienH4#WyIufZ`ZAoq^&NRK|nSHHZdbQ2GU7kQk``2IVPGeGcj)fXV?-UIXQA zP+0(~??8DJ#0TY5P~8jadxP5Dpm8=({s7gBpn4l52T~6jR{-TzP@4mkmq29=sLX+t zMWAvAWEZHM0gXd|%meME1@+-U7?$Tj`3+RIfbt^99FTsH`$6RisGR{yub{LF@-rw5 zKw$(5D_Gd!qCsjvVF}7(p!^2P|DbRJg$bza0)+>tyaUz$pgsnu{)e^uLFE_7UXWgp zIk@bA#T&>TSpEZ*&!GGTs%JrY9aNTp{09mPP?-iQ6F_AHNFB(0kUmhng3JKf1u_$t zeIRj=8c-O3)PvFpC`>>YWDY28fXoEh5ArK0|AWF3M1$M{5(C)}3J>%!1(^vl8)QES zgX|i`w9osjLZhbHwc5$3n)K;(h#g10Hrrj+=9w|7zV{RDBeM3 zKPcUU+T)*G>SvIfp!5K;8)OD3|AWj0g&Qc|L4F0<2?|pXALM^fIDp&$ z3KNiekbaPPAag+&urvpXPf)oJ zN_(LA1z}J+0AWy^gD|KL1f>H|{RS%sKy@G}AA<5fsQv@ZwSn?9Xbv2dcR^yHwgL!) z@-_&A)Pl+kP+0*g13+W)pfxif3~Jwl#_&KGbp8gYO%1}Jd=KJ-)Pl?gl@XwJF{ms8 zl}n&>3UV7P--G-D${(P#4e~F@|DbRolomm1Kp2#+L3s<5A3(&w<8HKzSe3w*vKr zKxTv70*Wh;I*|WBZUn_8NIx<5fz-h41i2A}L16?k2WCIW9#CEfxdAk00xJJOVG9xi zr2|kJ0l5LB2c#e5evp|U3^E^v3Hg0go;KwIEc`(M%5C)|&5C+9F2!qlihz4O$c>qcup!5JL2S8~a)D{M%QBYeQRELA|CMdsy+U+1Q zP#Ol+W1u!Vs1FOmpnMC$AaPLM0p%x9{{f^ARBnLsIH-;Xu|Z`Ds2l;686b6_G6z)e zgX{&BL!f#JlukkU6O>OuZU@zoAb)}U2=Xt;@5H1xkUCJOp!z<`82aC~iRZf#Lxa2OzhD$}o_>KTe?j2| z8ovbj4dgG79U!-X%5IP!LH2?CNXR~rn_w8E9%L^ld_Z=C@-oOQP`Lu~FDS3W@;%7! zps)qG3nT|h4u8y$+&5V@#kr93%&7TZ8IX(D*uN3;=>^sKpgIbaK0)af zlr}+b1*J_82BmF~A3=Tx`I#6DQVUAcpu7O`Kgdjw`5-rf+z*-;1+`H?{Si=k1ac>+ zTmY#9sR!u;*-4CDAT^+P0NDo$dr-K8!W85;kl$hDCa9bP`4wCK2gMsG{y^q{%p-(B z{sUo<8$lRkH%LFo9FQA8egL@vl-EId9)v+*3&J2Vm_I>kLHa=YL3V>M$XpmEX&6)vfbtMBMiv95RZw07)mN~x0F*vK=@gV#KzSLI zk3snrghBZlBo4|ept>4VW`Ob)EM0-p7BU8y7l3rde5H-Iq6 ztsu99>LrkWK>h;x5nmV}%YoE`(lf{mkpDpDgUT0>UqJnDP=6oP{{Zz9||L4F0<4blrT0~GI|_yy&EP<(>?4zdpvryv@HL2OXj0P-uyZjfI= zW`N8BVUW2njLZH}@qy3x=y3pwGf@12;u4e|Kb9zf|C6we?UhCzH}IS?O~Za`@h zl!ih10hC@q`4p6AL1`M4k3lqu4@yU%^aaA8^aM(8AblVjl(s+^WEKpA_@H!&j6rsS z>;}0DCK*<3xAhSRi zWG)C3vVT;5@Rb3eI03~S48zI+P+0)Npfm!)AT}}vr9TiKBnL_ppn3_EK0xUil#W0c zl#W1U0tkcB5Qqk0kT^&lq#l%}LFo^a4q+H(F32q)3`(yc8e}IZy@B{38s=t@A4tI< zeIPSH_JG_D%EzF552|ZHeH2hX1~i5NYVU*QF+k%QpuRq6juDjiL3sk?evqF*{)f2> zWG*3hfYgBO2Kg1_cTl*1>;<*;LF;@#7-T=FEda{@pmqo-%s_4gsYfnv2-$@$53(B+ zZXo+XZU(slghAmCiVKjRK=B94>!3IS*$eV3hz5y+{0mYKiVKiAAPh1ShSBXCl%GK936xerX$q8XL1_n;wm^K4 zI4CWF)PdB4^nvt)Fvx6>xgZR44~WL)W>UmKW`fKI*$1*4Jn1IR?P=5(D z<_EGHRL+3<9H4OkSpEmu2MRZk9#D9J$}Nz6r1%}A7GysNgWL;pHz+TIFsMucg$F2J zKw$!M7YKvw1z}Km0J0yX2BZ(9ALI{^S;!b<$0#0Jc^+4Mf#MaE#$YrkzCjo!1`>zq z1+hWmAPiCi%EzEI3c{fD3QAuf3`##B3}S=CK^Qqbf%JpSf|*MV8e|^KO`x<6%0r;^ z3`*Cab~R{h0yLHfnpXhz^FibLpu7*7%LMU3;-E1~P~HdiYe4!yc>$DHK>h%^A7nnr z9zyN~`41E(AblV+Ky@pqy#X5Q1m$}W2G#wbaZ^zL1C;+k_JZnPWcxt*j~M&V-2>7C zaw{l)K<);G2MB}Q1Hzy(4P+0f3;q$EOZt76^mv1f_FOIRi?+ptKCC??L$< z)aC|_uYu+*Kx^zl^NgVRd{CVans)@{eNaCfqz2SJ0Ofs9eg)-!kXfK|1eA9`{sGyA z&pjYHkQ$I$P#yy52blxv+k?s;kQ+dCJ}B>lXi#|o5(kZ)g7QD8E(X~L@(0Kaa>EIv z4-_UKGeF@2!ytcv%m$UsAbUV%6Da?K>;;7xhz7}l)PeLO#}~*<5C-{i6c7FUPOCTs zr4eKd$`2q66Qk5#kU1dpVCfr_RzYbNl-EFM7?kfpc^H)ML3J*uO%KZ7pmhqMxqQ%? z9?;rE5F3>DL1TTOc}-Bi0Hg<$_d#ZW@&(9DP=0~cg&@1}xgR7C$~UmQ56VxVegtR^ z6f~Crato-=hhbR$2iXm)`$2vI*$46?KD%(qf!qMn3kn~QIUo$m|1cU9ULf~@>;bg{ zKxF_Z+(7;Xu|aYm^&o$M%mA4M!XR^T**PjcS{~3N4S>=hC@q50B`7_E(kd(sgX&pO zUISrJJq~KigT~fC?RwBW8)!`;sI3pm{~#Ka_d#oKK=m+a{t{G2gXSJV`ayXSlpjEK zB*-3+oglmM`4J=!$}^zy0;CU=mq7UnR2G5O4uRJD!16z+9RR|hzCUQZ0n|p2AKxq+{u0iP(lwLvU7F5rI@+_$C2IVDCTN>1+2KB8$eQi*?AGFpGwB`_m zL2OWa05lENF69Ig7gz>CocDZ+yZhl$UPvlkTFaQRK|e(2eK0sejp5L>wx$mIavM& zg*C`5Q2xhd->5is@;4~n&@nM#3(`Z3x}mHF;u`2 zFYG|_pnL+#H=z6j%1bZ|avLcBgT@{~`5#o)fZPPi|De1NDtAC`2KfbKC&+Igvp{|Y znTyX}Tyh}2ATvPj2VszzAQ~Bi+zPS}WG^TTK^Vk_wLd_5KxTk2F=2qqEu-SZmkpr! z#)Z+t5u^?z2BOh1NPIY9PA2i+vDhojU0nj`=Xg&^>_d#uSP(A?V3sAZT*-I!KL17P43#u$^el2K;;UkJOcGGK;;c6|AX57pz;9JRspF4`3GbM$SjawL3V@8CS*Ul zJV+l1gUkVi7Yw8GVfi2CUQpf#u|a+Z$sy|lnMaOW(A_r5$6pqJ!kAnPiX&u?es^D`(MK<4AJ8<#jp z9|(iY0bx+sq0=BSkb6OS9pq<_dqEiH4p2D+QUk&uy&&^&**7YVKTi`IKA^b9hCyKu z!i3~NYCvfb8H2=N`UtrLrVgDZqz{)o$Q+Oyhz4PhTR<4(K2Vwhr7ai+rF&4CgJDo! zfMHOY1f@+-ega`oeG00lLG?WdgYrJ8-wqn*2jzRvyc(!o4_e0uI;#NG&j-<<{d}Od z0I2;9YIB47+#vlR^FVn3WFN>qAUA^CO$dYh1o8_=4=68z^n=_AG7E%3ZU)uspm_w) z*dJ(&50vjg^*^W_0Er>n4e~q4ERdNX3^E^t3AqDV9;6=Revp0;28A;S!`LA6K^PR? zAUA{ZKgi7>w}NPpA3)+Dd5}7gUXXbpbCK;CWm6~5qlY6XoM9Lg?)We^b)a}g#-R9v z@j+(6^nhrP-5?qngVZ3a2l0_HvRV)uWCkt_k_YL9nFn$UC|!ZlIVg>R(jEwd(j6%M z!7wO~gD@y>fG{YpgX&OFJ_6-+P+bbD=RtKhs67p;M?v`tv=0%K$3bm-(4IX|y$_;6 z<9?v=e^8zSl@Xvi7i1R5T#%ihG!JqY$bBI9;_?r=ILLn>zk%`z$WI`1Kz;$`eGmqj z4YC8&9|5iH0hK$TaX%0ZDt|y_4@ey-3_#%la|6gskUK!;gD|?i`1m08AiW?n&@sq= zFbuK_WGBdOn43XtkQm4>AbF6#K`u2E`!= z!}uU|gvCWjJ$j&dJkZ=8sGbM0LFE9bJOHT$ z^|3+odZ2OxlC~egwG{{z&`2&POc>t78K=~3>--Gff zsE$U?_n`54P}?1}Uj|g4gX(oqzaEs|LHl((&RCa;teURHg{eRFn z1<22!Z~&cj9Z>!U)$<@esICX~`$27Mka|$L0Wt$rj)2Ss<$aJnAUA;A4)O=cub^-O zg&!zvL73EZ3ep3L50Dw4Faw1t$b67Ju)Gg)3&>rdIvkys5rjc;3d5jy24PToBNl_wC=7$rEGX@Q(m4!+@-ryS zBV$mW1M!h*5F3==kufNbg6c_7dmmKqgYpNc%?_$3L2YJGdmmJ{!`l0xx*o&^l?R~u z9<+`YRKJ4e`#|k%P@NAdM?mI+Fvw1j`#^4n`4pl||(B`93Ui5HMQP`HE40);0i zTtMLgvJYfGDDQ*HBar(*Wf;gmAoqd#10er{+ye_!kh?%;fZ_;b9?WgzxC^8QN8N9 z2VqdX3(CtN8kE*y7?dwT`5Pt^f~=8p2|pYK8O0K%}i z1H~CA9fL3^-GDGC9f2??O@c6p4HAd3VKgWYgVHM~4T9=OP<{dBVNksZ!l3jC;)BFN z7^DWI7NiH1k3eh?2FZccgYq0GU4!yJC?A6IHVA|AB`EEK>OW9EfaPsaz6D{>cphjx z97KaKsGbMa=^za1(}VIlX#EbTt_Sf!;-E4C)Sd>J1!^OJ$`eo-1F|3F7LdC^`5uHp zZUng*E^Rw_)=O%)KBtgD}Y7AR2~2d~^(x1L*}}Z03%N(Kr8t!W$G9 zAPkBhP@KWiASfMz(hdlN(h(?4fzl*443h(;Jy0DF$|In(2+B90dJEJ}2jwGB8yZA| z_%Lx04N?cnU!d{Xc!-)57fp7nFT6GK=mr9E(Mh{ptdx~evmsr{s8$0l;=Ty1Njkz zL16+a13}>h3O7*rfx;0Krleqy9*|y;IUw_3VF@Z6&El3S0--GfaD4oOdKB&G2war2KA5>q1@-c`8 zl?5O%P<{r(K2IYB> z-$3~a6b7L10EG)EoIv3P3O7&~g2Izj4AKWO0~FSvFb3HH3R4gU*$Z+D$Zeo-0F{3r zzrZlaParXnKS1FNDyu+xK<0oj$ekc_LFQB9Msz=c+zi4XGtn_f9EOqQU}7*D-ON$` zz~p~WxWnQEM1$fClwLq-8{0%1@d1LbK@{zlH%ptK9ZpmYqX$3XcK#0FsyACy;N7*ywh@-Qg>!}2&NpMx+c zufys^P`&__7a;v0yFhk=>;}03lt)1M98{-*+z4_j$juGr`5hDv zpzr{N4JeF27!+Qh@FNF<^n%O)g)_)(kR2fVK=y(#D2zew0=XBI|6ySSYX2i+kRL(v zAU}cP0b~X!zCak+y&(I@aVNSSkU206az8cWfIxO|Ts_Q-JsO5-35NgRUIx{7pu7#r`=EX~2!qN15C*Y9av*ge^`LSCWG*PbgUTIH z83b|%$Za6Eg4_(lpgab`Apd~;1;QXdg8T~dGsyoS49cUR@BpQCSU4ev9jR=PIUw^u zW`n{PWFH8F><3|xdqD02g)1ohKw$<7BT)E&{0Isg5C-uPNp<$q9~1YuBmM#rE!8dfHN+Pbjv09F=&@;9g) zfMHmk2Z@971V|l7AIw~kJs|r*?gF_Hl%GHtbjcY$b-+dyswg(E2ZKw$&Q=b*9;lmUpg17T1(0m2~lpfnFs4`ahhlzaZxskbgmbr)D|@nFlf#miIyS zf$RlgkQ+d50b!84Ko}H;AU}Zo1uD}(bw4Q2gX(-xc?YWZL1iQ;Y(V}3nE^5jWG<*| z0oetz6Nahn7nr%YXlmLwYSzG%7szo7N>9jX7nIIG7?kEfX%B>9Y*;!5^3 zj&D$!0;Mld`bEc}^asPB^ax7RAPh>QAPmZzpnM9#pgar1pmYpkgTz5IQ5Qec~G)N4DL1_|%LFp5eRzYbNghA;Xl+R%p#0O!J97r7q zgY=M#VftZqgUkW3K^WvNkUNnv$Uh+ef&5BM3^EghLFR+(1KAC71IRrf400#P&mau) zKPc~m>U~h#9#rpx+WjC58v6kC13-BnR8E4_gY<(i$UKm_APlktgh6&9V`}ThAU2E!v5_&zJY)>A z2ZTXlAQ~Mby9>ld_6sRtK&)Dj`5=2h7~~e1+d=*T`4^OrK^RmHg8E3H{0++Ipf)}z z--F5k5F1n;fci_I_7O-uNI%RhkY8aKWDg93>;_?C-8!mnWan#g;~$j9Kp2$nKr{%0 z*w`>A4wafsY9k=<{{e$asxUB`41)rV$&MK>;kzDfRGrePSA)?sWI4buZN7iK4nhPi1VXqX#7ZUea!VA+ppu7(<7luK0!7#{fkUKzl zAj4wR?vY&%fYKka7?fry!7zIUBF(|!5lcl-FVTAH)V>P?-Sg z?}Nq!K4a(o3d=AR*pmG3&L3|Jf$-(M=P`e+b z7nJ`&W`fKI*#*NO`#~7w77(U)IE>miS~iTNGyuwDp!^2HusjILlQ0a*qaX~*_b?2~ z|DgN}%FCeo9F+e-`5V;62jzDV4a3NJAEXwR_d#ZX?10$`qCsu|VUU|ZcqGNcs9#6R z9eSq$P#%L}P`(3UP(FlVQ2vBrP#FNjp!^TY$Dn>ZsO=BS|De2%jzRqbkUS{w!`l5I zGhudsXpp@i408*;!(r6E(Y!a((f};4f$|+P2IWT(2IWy?49okVdK;GiVf8<#3;^YG zSl$P*K^T_x@{5e}nvj^@9SmIjdX z9Ec6di!cnzn=p)?|3Uc|l#fCA8I<=y?SD|a9+cOSF{n%c$$`{?>U@wskXbNuL3V-c zMaCm74o3YuTJ8)~8bHo>pu7mfu>6TkgYqp1gYrEnFN4PYKy^PTe}nQqD6fO^K8OZk zkT^&lqz9%XLFRz$0AXZ%k=gVPi&6VV z^WR8G1IYOd#s=j@d>EE*LHQnrL3tXKw?TOvRKFu*kQhh~qy|(@fbu>p-^1+0MURwt z81?69nKKY+09U@lmH$Ec7KA}%04N`W@;@j~gYq^gkE3IdI7l9(4x}EW52PPtJ}$dq z;sX&Lqjrww!;zE*V0jIl#+5%oc^8z2K^T;uLHQb#zd`vNhCzH}3{nG955ge*xXcEH zJIEa%dK8c5!_hQAzcc{Kcf?>&-Ua1hP+kUMSQ!AyEt#ghA>-`e0^bbHk|EXxbc20|TE1V0jco zgW>~3gYqs6gYq&cPs1>B{s)PH_#hgj4uoO)K<0oj$jnhZ@bNqv2cvO7N*Vy=T^NSt zX%G#{FjqiJ9?4UDFN(KIlc21e7sXc`zz1EXnRG!2ZV zfzdQDng&MGz-Ss6O#`E8U^ESkrh(BkFq#HN)4*sN7)=ACX<#%BjHZFnG%%V5M$^D( z8W>FjqiJ9?4UDFN(KIlc21e7sXc`zz1EXnRG!2ZVfzdQDng&MGz-Ss6O#`E8U^ESk lrh(BkFq#HN(*Pt5FhD^_P^fE|XRxQgpKgSkr;n>10|4JLiYWj9 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 zcmWIYbaP8Nz`zjh80MOmTcRMqz`(%Bz{sE!#=zjRhJk^JL4YA8u_W=^eg=kF|04eB z{Lf>^VLZmz$9RCjg;9jbiz$Ukj;V`j6H^E?AJZZR9R|PuX@5EXru^IY{|Un(#wkpX z7;i9gF&QwKFkN7p$GncUj`a$w81o^@6PX2f2aLF#L&QWkD=%PDyB1xml$_2=dpA#zWTrD-xbDp z%tlNS{}ujz_#^Xo#($Z=cYa>`74xg(R|rGd|0TcOe&;dNu}ok%!*Gu23hN*C8kTu~ zr~T&pyY1h*|1JME{4@JI?~l!Ik-rMRPJD6svg#+-FTFqS{@wVui1h(W%>Rg=bN)#D ztNMG7MUG_+ixgWK8yCY5Mw5RBejNK*@%PQ&2qqn-J%2-f^Za!C>GEgZ&w1bE-v9b) z@%t3x3+6ivOTN7MX~4+9xQcTM>z@BoOm$pdJli;#{(bl^_TPyyhItNy((gMTOulve z?_#R>^XvVcFG?Sd{BvPl!rb$3*59_jUs#{;Br)dv4frYaPma@p+l`s+z0Lb6Uw1G` zFv zn8#f3TjX=X=NErXSUUc1_Ev6Wz6My@DJY;TRljpg?8OZVDhu23|Mm_F!p{4AL{$_ul z!SH}-1=kfO@z2MfzWboh%EWZ!$J2MKKW6;+&z#HI!0_wIjOPm&zw?BO*9&s8-v4`v zHA5^zPC{zaKmU)yKc)#uiDa>#|DE*y{KxBml(?k+Tzb6bR{is!ZxfieGS_`va?ALW@G_|(Ld1W$6Qv0W2ArY~%{N>!Un zh^d>yOT0s9{r}Oin+RKRa`M;-&Vx>#nKX zZar6b#pnRr+5zroldyob!Ooa`R)G4zTTYnV(DX!kFvkNa!(iaQ24GdXl7*;uCc&g%;T(u zifXY$k6fLQ;LpV8j8B8^^W9i@M*e`{sjDZaopn5T>1yZ|>z6Ly*M1k}-6Ubt7W)k`ZaEEu*JkzB+dLO}x7kfKVKC>mOw&BX^_B7Kcl|fl z&+a~V>geBne-AQldA57sMVE8mPrkU>f6w$ok6@r&sJNzti;=Y3U581w<>vpiz8Rm@ zH`6kZW&d0L>DA+PxAtCJblPyg#n$Cpoe%QuwLLF>LHwk^<7Yo6{0|n+R5+-@qOjfg zi`{aEV{S9;UmKsdZZs`a-y_lT``PFDpWi$=cm3hv|HpS94%!=Y@bpf(J(kyxU7B>8 z^ZkM!988n`dy8FDdZt@rW~KL3pUsWWoWZnI>%Q1`sXReWR>to|ulXNLKKJv2)aBy` zGY-5v-f+bDe#|}pdruyxzy8m3l_yYCO76MjQSEa^3HlFgzv!e$Emzf(zaV>rNB5J=k!X`#kGO=F{01e%%lI^ptt;kGIba zx!RNx)#fWMQwmjOwmfHiRZ~!5xyVu>L*dgLcYkTWx&I{Y-l;3M?y6r|c69goN4MTR zEPm$nEa%nFce_~@^4}1a7j=~ER<_XjraD7KL$y;bRdlN0RG}2!d+di^H(jZ_b>^+k zi&NJwo}6=j=R<*4(_cy6`+jx7j+}sg|PnbEfYMf>L~fE56-&bn)J%yH9U@eQ0&{&Y6x2({HlgTK(kHo3z)$ zuM0lM^O=h8kp3+gEFml#sF1JxMm|5w4y@2%w?%vVo73EN^4Y%WG{>UV*UE7n!|#R<^SRr+pfl( zop!nEx$y0tEACg4uFQQX`?28nhEFXoQ(i7(`5@6Q`9SC>XCCh*!9-aTDG%OqhRrPE zKgz%S{buw@^!vv5Jx?~=>3H+|qtVle4_`gH{dyH+Jm>Wv-k-OIUv z{oDU{|BQKVGEM$b{q5h+!(a10y?-nG<=@v?Z};47dU)ZL-?xY#$zK@X#rzQG>G

BrLl-t6<)k{K<(vwcwhuKe-i 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 literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/sounds/explosion.wav b/code/gamespy/gt2/gt2action/sounds/explosion.wav new file mode 100644 index 0000000000000000000000000000000000000000..d36b4a8d2d513f6859d3432b8f12fe4e910ea1b1 GIT binary patch literal 47720 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 literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/sounds/mine.wav b/code/gamespy/gt2/gt2action/sounds/mine.wav new file mode 100644 index 0000000000000000000000000000000000000000..ea0f6645ca77f2808a11c7f17a246070de3f7b10 GIT binary patch literal 3812 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 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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 G0|NkdBfHlC 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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 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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 HP!9nBT?f^t literal 0 HcmV?d00001 diff --git a/code/gamespy/pt/pt.h b/code/gamespy/pt/pt.h new file mode 100644 index 00000000..1c11fc7f --- /dev/null +++ b/code/gamespy/pt/pt.h @@ -0,0 +1,156 @@ +/* +GameSpy PT SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +/************************************** +** GameSpy Patching and Tracking SDK ** +**************************************/ + +#ifndef _PT_H_ +#define _PT_H_ + +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/********** +** TYPES ** +**********/ + +// Boolean. +/////////// +typedef int PTBool; +#define PTFalse 0 +#define PTTrue 1 + +/************** +** FUNCTIONS ** +**************/ +#ifndef GSI_UNICODE +#define ptCheckForPatch ptCheckForPatchA +#define ptTrackUsage ptTrackUsageA +#define ptCheckForPatchAndTrackUsage ptCheckForPatchAndTrackUsageA +#define ptCreateCheckPatchTrackUsageReq ptCreateCheckPatchTrackUsageReqA +#else +#define ptCheckForPatch ptCheckForPatchW +#define ptTrackUsage ptTrackUsageW +#define ptCheckForPatchAndTrackUsage ptCheckForPatchAndTrackUsageW +#define ptCreateCheckPatchTrackUsageReq ptCreateCheckPatchTrackUsageReqW +#endif + +// This callback gets called when a patch is being checked for +// with either ptCheckForPatch or ptCheckForPatchAndTrackUsage. +// If a patch is available, and the fileID is not 0, then +// ptLookupFilePlanetInfo can be used to find download sites. +/////////////////////////////////////////////////////////////// +typedef void (* ptPatchCallback) +( + PTBool available, // If PTTrue, a patch is available. + PTBool mandatory, // If PTTrue, this patch is flagged as being mandatory. + const gsi_char * versionName, // The display name for the new version. + int fileID, // The FilePlanet fileID for the patch, or 0. + const gsi_char * downloadURL, // A URL to download the patch from, or NULL. + void * param // User-data passed originally passed to the function. +); + +// Check for a patch for the current version and particular +// distribution of the product. If this function does not return +// PTFalse, then the callback will be called with info on a +// possible patch to a newer version. +/////////////////////////////////////////////////////////////// +PTBool ptCheckForPatch +( + int productID, // The ID of this product. + const gsi_char * versionUniqueID, // A string uniquely identifying this version. + int distributionID, // The distribution ID for this version. Can be 0. + ptPatchCallback callback, // The callback to call with the patch info. + PTBool blocking, // If PTTrue, don't return until after the callback is called. + void * param // User-data to pass to the callback. +); + +// Used to track usage of a product, based on version and distribution. +// If PTFalse is returned, there was an error tracking usage. +/////////////////////////////////////////////////////////////////////// +PTBool ptTrackUsage +( + int userID, // The GP userID of the user who is using the product. Can be 0. + int productID, // The ID of this product. + const gsi_char * versionUniqueID, // A string uniquely identifying this version. + int distributionID, // The distribution ID for this version. Can be 0. + PTBool blocking +); + +// This does the same thing as both ptCheckForPatch and +// ptTrackUsage in one call. +/////////////////////////////////////////////////////// +PTBool ptCheckForPatchAndTrackUsage +( + int userID, // The GP userID of the user who is using the product. Can be 0. + int productID, // The ID of this product. + const gsi_char * versionUniqueID, // A string uniquely identifying this version. + int distributionID, // The distribution ID for this version. Can be 0. + ptPatchCallback callback, // The callback to call with the patch info. + PTBool blocking, // If PTTrue, don't return until after the callback is called. + void * param // User-data to pass to the callback. +); + +// This function is similar to the function ptCheckForPatchAndTrackUsage +// except that it returns a handle that can be used to call ghttpRequestThink +// or a failure of -1 +////////////////////////////////////////////////////// +PTBool ptCreateCheckPatchTrackUsageReq +( + int userID, // The GP userID of the user who is using the product. Can be 0. + int productID, // The ID of this product. + const gsi_char * versionUniqueID, // A string uniquely identifying this version. + int distributionID, // The distribution ID for this version. Can be 0. + ptPatchCallback callback, // The callback to call with the patch info. + PTBool blocking, // If PTTrue, don't return until after the callback is called. + int *request, // The request that can be used for ghttpRequestThink + void * param // User-data to pass to the callback. + ); + +// This callback gets called when looking up info on a +// FilePlanet file. If the file is found, it provides a +// text description, size, and a list of download sites. +//////////////////////////////////////////////////////// +typedef void (* ptFilePlanetInfoCallback) +( + int fileID, // The ID of the file for which info was looked up. + PTBool found, // PTTrue if the file was found. + const gsi_char * description, // A user-readable description of the file. + const gsi_char * size, // A user-readable size of the file. + int numMirrors, // The number of mirrors found for this file. + const gsi_char ** mirrorNames, // The names of the mirrors. + const gsi_char ** mirrorURLs, // The URLS for downloading the files. + void * param // User-data passed originally passed to the function. +); + +// 9/7/2004 (xgd) ptLookupFilePlanetInfo() deprecated; per case 2724. +// +// Use this function to lookup info on a fileplanet file, by ID. +// This can be used to find a list of download sites for a patch +// based on the fileID passed to ptPatchCallback. If PTFalse is +// returned, then there was an error and the callback will not +// be called. +//////////////////////////////////////////////////////////////// +PTBool ptLookupFilePlanetInfo +( + int fileID, // The ID of the file to find info on. + ptFilePlanetInfoCallback callback, // The callback to call with the patch info. + PTBool blocking, // If PTTrue, don't return until after the callback is called. + void * param // User-data to pass to the callback. +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/pt/ptMain.c b/code/gamespy/pt/ptMain.c new file mode 100644 index 00000000..c94cfbf0 --- /dev/null +++ b/code/gamespy/pt/ptMain.c @@ -0,0 +1,681 @@ +/* +GameSpy PT SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include +#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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 Pz-S1JhQMeDkQD*|g$k;a 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 zcmZQzU}RuoP*4zH0D%`w3=C=v3=9GS5WWT@0|Os31A_(w1A_ts1A_wtNIeTkDFXu& zgu)~n7#JED7#JEFAQVFbh{0$u!!4mJlOc22VH4mcU9Yfy(kji)O-t~iwdiU;K2gqsf-u3_g-@w4Y z_pSr%RuC5~0QSTWuowRS|K9=T{{R2u49HLa8{R?u^yADKkU#^-dJd568IV99$a)3_ z4v;KJ0O}@|GiQz*IpcH2=N;bzkXu=Nj`;W-IpcB!#y{e7U8JBkuPa@>s@qyL5 zxcKmWebServices, 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 zcmZQzU}RuoP*4zH0D%`w3=C=v3=9GS5WWT@0|Os31A_(w1A_ts1A_wtNIeTkDFXu& zgu)~n7#JED7#JEFAQVFbh{0$u!!4mJlOc22VH4mcU9Yfy(kji)O-t~iwdiU;K2gqsf-u3_g-@w4Y z_pSr%RuC5~0QSTWuowRS|K9=T{{R2u49HLa8{R?u^yADKkU#^-dJd568IV99$a)3_ z4v;KJ0O}@|GiQz*IpcH2=N;bzkXu=Nj`;W-IpcB!#y{e7U8JBkuPa@>s@qyL5 zxcK 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__

>iaQ)*6xGWr@%0%9008cVP;_GU}RtcE#hYc&G!j0uri7< za5C{Q@NhFQaDtAUX9A5If%aAjGjK7eF|e~_A7oeD_+Px>$$#Tz|Nl!Z`Tw6~{=ff> z^Z)&4UHOWi5=l_fWpa1jNeE!dE^y@#H z!h8wFqQ}6@4C=Fh_O(efaIi=)Ff(w0*C}&x zursi-urn|-2{N#=N;5D)#{gjA1_~==3<^t-983(x2hku56DO4h=>=hAvq5PC*&LWW zhz5y+Xpnjk4da8@Fbood$%Di|d>96q4HAQ4kT^&!2!q(5dXS9`bk7p#>2X6BC0b6B7eyYcn$!D+6ec6C%m>;E${&ic>5TJ)cpG5NnVbNhce z#-;zc88-cAVL$z!%i_a-sepg~#r1#v=M((!pO@qHe{O--|3ysx{pU3M@t;}#;eRHz zTmLyTzWryJ@aI3@>c9UL_Wk;=vG>P+gGImpD`34ySL=GPi8x%gsG$^dlF-#3CjA49`K6JIX_#iznIgmaO zA4H>LkT^&khz7~Q>;#D+^FeG7hKYmP8lbh$p!q!ToChN#c>Nz})(bTM$IQaaz{JYJ zzyi95n*lV=4?4R_n1P8=5WL5NotuGyosS8;|AY~=r;o*qjg=v|iivglFCNod|K&S> z{@2_5_rK`6FaO!*zWL8S@xy=aNnif6b-(}5*8TZE-}ImV`KJE*&sg{AKSSn|{|w=v zJGFlOXK;D{pULg#f41n~|Hbou{}+k-`Jd75<$oT#kN;&n|NrN;`}v<)>*arT)u;bi z#IOBlWZV0nfo;ow7PdA2S(v*%F)+HSF)->dFf-Z7v$9$XFfl4Ju(OCUfW|Pumym$= z&x6j(Vg!|Wj126c{V$-oUnX(zJ|0k5fYLrJtU%!j!mw}yiGwhV4+?h>8$^RJvK)vF z(+g4q!yqw`90*g84x}C=2f`pWNDLXHi-XjF#6TFN4n)H+ zj1Qt==0nm5XzviHK41Wkk%87mfYJyn6C(o~GZO=7p92RI69X$V0|OsuE}eygL7a(& zfr}l~b_DgSKzj@M8JOAim>HS9A{ZD`Ub8S}|L0)J{m;!_{-2wx{XYli?El zt@_W(o%5fA-T%KZbMk*_rl$V_pgRiq4*ln|eE(nE=l_3J@yGue7;gS&WPS=>Bg8KL z`ah5I=l`s1*Z(sLocqrc{_#I&=imS0Yybb3-~I2u%D#X9)mQ)jFW>a%zaVJ5fTjIE zGh=802csf`Ad?^i4=XDJI~OAZ8y6b`BdAKK$pb`u(4) z<=cOb_Rs&>n}7c2sQCJyE&k7c#)SX>8DhWwXH5L{pQYgUf4-9M|9K)m{b#Uv{GZS2 z<$q1@fBz*czW-0p7#Klk zZGz6#6k=dv;0DiYGlMqffY!=`#yG(9-VEUNvEa44OrZ7wXx$PE187_X6h5GE0)-7I zOhGgVgTz4?BnP5F7{mt2fiQ>4@5&SB>#h!ih=HRWCyREWn$o9Vgw&u0_r`3PA%eO zU}9ir0PS+%Vqj_w<^Sj7^ZYNy9Qa>^A?`mvOUr)&;Vu99ji3GJvHAU;RwN#mvadzy#Vm%F4{Z!N$zM z$%jy3&S8jNF0PgY;+7#2NDC(*f2IZm{~9yqz@cN;B~&BGzprg zhPVr)ABK_D!`Lu6kQvBgAU;SAM1#x*u|XI%E(v18Fh~sugZ9sW>j?PTP-X@$CT38H z3dW!nV4!`^p!IX0y-uKWL_qflFtM{Suz=QyvoSHSuyHf6FpDy?e^#B3|AkC`{TDF)@n6K~$A3As_y5J&F8r5b+yfpHV)efBU!?Wtf1#P* z{_`#S_Mdt2r~eEKzW(QH`~6=$_S1h3tquPfS)y85SrwQB__-LkI3aTy42+;X)rhir|Ne`v|NmcL%kTeeo8SLu zSn~8gL;t(~Om*M>vsL~1&sy{SKU3SQ|BQ_f|1+1r{?DHL>OY_3*Z({rU;cCT{`$|m z;%by_#g}t1My)PBnD!`Xk<3X9+-L<8^mV>wTD6V z18Bc86Egz`Gicu;3j;SJ6ZlRZ5Y5TN&cF-W-^c(uk4S_8bbcZu3o`=?7c&C`XrDA2 z8v{EtXna)!JbuK+B*nnY2)e)7N{opq`VcEy(|>NBUH?Ue5C3Q9t@_W-5%Qmh(d$1i zquYNL#`yn?j2-`3RImOQ^7;Q?$oubq7W0??MO?r9m-GMoU&!p~e?h5p|3z7M{g-6` z%_pn{-&ZV>_xZng-}nC|I8xIk62h;oq0G^ z8911Q85o(^!RuW>?Ho|JfWiPogD`Smg|4a7&{?D`O&wuufzy34t`uLx5{m1_dQ@{Ra>iF@WtK;u~uAZO& zd8fVq&(r|BNeN| zTj}h7CB3KrrDd*x_m6QiF8|NP)c>D_vG_kTQ__DH*3gYC9M&?7ptuAbn+Y1y(;WM*UloqYp32Zxc3k%193cFo8NK8F{w&IPpg7gE>4!VpG-!ViQ&VTep4 zvq5s8umy=B>jR17!r0U!>j%k$Fh~r9LFRyHkoh1shz9XNY#0qRGlTAUWnlo-3|ye}0$Mu*KKFzJykCri zS%QIySpYmQ019UY(EKqA69WeeEBHQdE(XwkGHwPACeU1xIs-GSyE_|4)@x?AdH>nC zSN&&XEBnvL7W1Et#rHouvpaZS78BEi{~X$P{|f~C|Ih9E@jrw5>Hn-o=l_d3KKRe2 zcH%!5&({A!3@iSNFwFeV3Q8BMXaCEkeEl!c_~k!K+w=bny)XZ>&i(ygWck1U3NwHH zS1fq_pU+^^eAvgk@+At2!qT4(V#d5=>xIRF-Q#v!{k9`fiOrN zNDNfAgXcUz=gom6O8X>)t&m6E9Aj{`MUrAWqN-77n%0$KkKrO{~6YQ{?EAg^MCd|zy7l<`}d!H z#^3(}v;Y6+p8o4U`?L@L`Fr30XHS0opF!``e@3=P|AjO^{O76u@n2xp_y6)U{`?oJ z`ShR7{KS6-!R7xMxYquckU9Hbgn$2k9)?x_`55N?=V$KwFUVQ(pPwV{F$;r(E(3!K z0}F!?_#6Vz`J;?%Y~VZ&+Mmk=I$HyD9y#b(J@EPDp!=ZN85o#A>lpc=bK1-dpmG!z zrpRFj3Nu_76s91xAPfpu7#~D~Fh~p;!_I-147U zV%vXq)g%9z__zFLW?cK9n_=aD9;TW9IayZx=aM?`UpfBce~IeP|2f(}{AZr{^*_tZ zU;jDgeE%;x>-&Gr-ar2p!XN%;mze*bi7|Z_7psFQ53djd=o|!4n1L`XOhDlY!l3Yh ziGjie#0RMZVGtW1hUo{X1!0g{kXaxMG7Cf_V~{*d4@exu2GJl4VuLV94G6=;Kx~j% zAoD>O#0H6h*dQ8&L26+b%m=Mq1^0hJ`*uO&xg6kno(;4Q3Ut0K11|#$8w&$F2Ll5q z2Ll5)J0k-(3+Vh=Q3fU!T}DQhfHXd}{yzfg7yk2i{QWOE@9%%n`CtBvFa7eLW%cX- z3|l|{XFTxxKg<5V|5?}n|IfVO_kWf-zy34N`S+i3#`pi6-QWK6Wq9nf7OpnZ#=^T(JtK!So^ zgc;ZvctK-IAQqbz12eOODkD?ze@2$h|I93N{&TU+{?E(Y@Sl&V;LR?-u#!I@#DX8-rxVCX7~Sd3M~20%o;h133N9L z=v-9L*f4a@Hz=$?euQBVAA~{S0uzI=k!g?`5C(~XXqZ|U4H5@okQ|7HiGyfRT!3g~ zaS$7XLE9|o2Z#;ApuOLqb1Oh&-k`VzjrTKy&W>aNt@B_7 zpQ{Wy+X^(-1G-y{iHVbeiAj!umBmesS*YTxp!<&hdYzyCYpneLUt;aA|H7+({1;mP z>p%0J@BbN&eE-jU^v{3xqyPRhZ~Fb8ec_+~%oBe6=bZlkKi7;u|9QH;{b$d7|DVn2 z^?z3O=l>a*@BHUdyYgSi^2&cv+q?fcG>`vh;9U8ifnn}{W|p=8Sy|Tp=VYDxpPi}u zKQ~jue<9ZVzhYcb`$f52RJfRQ7}(i_!R;6Dox)743~WrS46LAgbeKSW8qi1;XpJ8y zcuW&?ehz5OKPxln99__UFpxOt4h_iuPEa_3!v}me0%R^2BnHDUIb=R8oIzrsyanTf z)PmR`aah=c_#h0D1Brt$j1Lk6VUQT8Jp!UZ`jBamJV-rA41_^!kQj&!!yqvj8%Cq5 z0j>XotoLDN1oiizc^I??f)T=I0JZ77NLFd7N+7u$-^Ap)P?WLJGqE3U(3}t3p_MeSo z&VNDnvj5VodH;nOL34n!{&TbL|If&<|34ec^8ais6aO;L(i-v5^> z`~P1k_S1hZorC|`SgVe)FsOp>3j&o>pmh$Qa~Hwu;6Q7h7(nY{n4xzuF))DULBMAX zgZhr3O#{$00vbC2%R%|5VGS9-1;q(S9E3q>1VqCyNDdUwAPkBxkT^&TM1%O~7$k>` zLGmEAAdHO-QVWv<$%D?Q0nK%T&qxE!eS-Z7n#lp3n+{ql$Hc(I&ceXN1zI1+#sHcJ zWM+|NU}3ga;$$jW!7jJ?zj(^4|GKmP{a4!Y|3BC6@Bg{>{rJy*=*NG?Q{VqHocsQt z*S=zdHl(3x4#eJ-Fqpe)Sn450lV z;IrdF=S3sM9Vi}PVFqF|F+uVGDD03iNDT;s#6W65G$?LCG>8wwFg~&zNFPidod($n zk_TasUJxG{gTz2`AblV{d8#IOjIzNz^fq{#aiGiPmnL&Vwg+YK3G#kLqz{vzU8-|+!l>b3r91u`sin%P=tdr!liP{O1rT`!6csl`PHS7%ajO)FHu_|4)Ep z@_#PIDgU_{ivEkUru`RTPW{irQ1G9fvFSf6bLW3Xrk4K<3=RLe7&`uQ@vQjIX7lnt zN7�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 AFaQ7m 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/mine1.tga b/code/gamespy/gt2/gt2action/images/mine1.tga new file mode 100644 index 0000000000000000000000000000000000000000..ecde8f6b07c0ee8f17ffd010df6613a7f5c458d4 GIT binary patch literal 65580 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 RGuYGLPdCEN)5leh0RV4!&BOoz 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 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 WBq-E1%rn^2-%mHf&C|zKj{yLwn4xa~ 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 zcmZQzU}As)2L=ZQ1&;s!|1*r@(J&ZI1EXnRG!2ZVfzdQTzcj$Wu=_s)!=nEqHOv_p zPW}gl!ASH!1H;Du3=IFFc+ZGS0}Ko+{xdM#hx&i_i1I%J!-oG13~&C=pa1`VT^%S5 zT>Q_#uy4eK0RuzNe+GtA|EEp+4-NxJInXmA{IBgE{ogn^{Xd9Sw)X!I!_vmi|GAZg zhjkc${EW@-!#xZ@eg4pKi{ z;veKkkbgil$j`9!58{KwL4Jl~W#RuIF_7GF@ju8PApd|cvj0K;1Yu;q3u_yL>qd~C z;fB%u58{K$I(}nuaNQ5`J19Jco4;ZH0r?j>{z3dv|NH+RjsKCC{y}LUgkk9)l=ne+ zq~w2)-$5AUe{p^L{~*7E@JRJPEDT2d?>ubUy|8)@MuXBjto{SBL46=t`X6cW5Ar)G z3_$(|rTLNOe~{lncqIEDlmG403{Xf#}I(CL18e`{11wM@Az_X7=Yq`q~(85{DZ;(6!)Nf zFw){5Az|A)mrs0}a@{STV|0{I<;VdWoa3}D!g z|7rLCS61%+&%gk}v;Q+NEdS5W9{-=6J^DWbga3ba_Fym@HeNK`Xzda1e^7ja(mtpR z0AW!22c>&;d-MMw3}S=S3}+0=?;yW7H#h$W(I7v={0@>E&VGma4VDH#G|2xTJW~7) z3ImW|L2(blAisk!NDL%5QZPS%_J3qNQv40`Cn&yQ=^w;~iH{^2RQ``tzmKGN9`!5z r_SB4q^=Md+rh(BkFq#I4O9Qae078O7UBf(sJ^lT3BiuZFT=f_L&snIt literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/images/rocket2.tga b/code/gamespy/gt2/gt2action/images/rocket2.tga new file mode 100644 index 0000000000000000000000000000000000000000..835dc2ca2b87788b6c364df58f1980c24fe06afa GIT binary patch literal 16428 zcmZQzU}As)2L=ZQ1&;s!|1*r@(J&ZI1EXnRG!2ZVfzdQTzcj$WaPB_?!@d6_HOv_p zPX1?LI6ET!&%p5bKLf*mI38(f0Oa?yZ-4)TFo->p{13_Z|NkF6`2YXbt*|ukXvE|L z28Nmc85lnP-@N(%{}U%*{$Dd9{IBgE{ogn^{Xd9Sw)X!I!_vmi|GAZghjkc${EW@- z!#xZ@eg4pKi{;veKkkbgil z$j`9!58{KwL4Jl~W#RuIF_7GF@ju8PApd|cvj0K;1Yu;q3u_yL>qd~C;fB%u58{K$ zI(}nuaNQ5`J19Jco4;ZH0r?j>{z3dv|NH+RjsKCC{y}LUgkk9)l=ne+q~w2)-$5AU ze{p^L{~*7E@JRJPEDT2d?>ubUy|8)@MuXBjto{SBL46=t`X6cW5Ar)G3_$(|rTLNO ze~{lncqIEDlmG403{Xf#}I(CL18e`{11wM@Az_X7=Yq`q~(85{DZ;(6!)NfFw){5Az|A)mrs0}a@{STV|0{I<;VdWoa3}D!g|7rLCS61%+ z&%gk}v;Q+NEdS5W9{-=6J^DWbga3ba_Fym@HeNK`Xzda1e^7ja(mtpR0AW!22c>&; zd-MMw3}S=S3}+0=?;yW7H#h$W(I7v={0@>E&VGma4VDH#G|2xTJW~7)3ImW|L2(bl zAisk!NDL%5QZPS%_J3qNQv40`Cn&yQ=^w;~iH{^2RQ``tzmKGN9`!5z_SB4q^=Md+ jrh(BkFq#I4O9Qae078O7UBf(sJ^lT3BiuZFT=f_L&N#11 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 zcmZQzU}As)2L=ZQ1&;s!|1*r@(J&ZI1EXnRG!2ZVfzdQTzck>p{r~@j=l?-qMApH! zQ~v+=+Y9nO{la{(q;I3DfB)T9|Nn0@?f-wvu799F7*T1!VfO$3#sB~RZ}|WJzh*MX z`y(d}Ffjc855izygAyc|{~N>|5ov&d;SAXSmoEMP|KI^6EEpJejtKv2yGQ>w4o?3M zqLr=v|HH7fvGad!W#M5R1|UCU^ZRfQ1CXCV803HZ_;PUkn|sHDX%HV2_aMwIF8?2d zLFzylR5T0+42xfopFsWvg#|wU!_*H4zk~b$idzuI=YLS%2gN-IgVgdXYY&^U0OTJK zM)yA`Er7(3{SNX!$nPNaps*Vb801Hge?TMwq_gM5Fs3#0Qmi{Kn$ox*z0sP-whNXW{-Us24lK(+|2Vs!^#r5s~gZvJ{Bh~+~Fc|f}^RQ|6BG-K& zHYmNr>OT-0)CYp4|B)8|Aismc0OWsAnjdNY2l*X@N3#DxX<(%KALRFu9{(fR|06yA zN4oz(X&)2@Bf0z=iT?LbuKw>Fp8wxIIQ>7!|HjVY;PJkZ(*6n0Zif3G6b2*B|DgEy zjxPs?0Vw`QTK)&cKPU`9aSzG|BQ5?x{s;LRgh632()6Zx%oec2KgE0 zcaYq0_B+gPurvUoLH-Bfk>Yny7=Zi=ihB?S`5lBoVj#JZg8BKg|0Cm(;%}HgLGcYs z{~$I@d?eAJ@_(fIeI&*6s9#6U+WyhF9*yhKG%%V5hDsWModysR6zUr08SLrrryJqs J>Eo)$005$vyD$I% literal 0 HcmV?d00001 diff --git a/code/gamespy/gt2/gt2action/images/ship0.tga b/code/gamespy/gt2/gt2action/images/ship0.tga new file mode 100644 index 0000000000000000000000000000000000000000..f8948dcf6c3ca8d5e971a57858570bc2319a193f GIT binary patch literal 262188 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