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 00000000..2d40a91f Binary files /dev/null and b/code/gamespy/Chat/chatty/res/Toolbar.bmp differ diff --git a/code/gamespy/Chat/chatty/res/chatty.ico b/code/gamespy/Chat/chatty/res/chatty.ico new file mode 100644 index 00000000..7eef0bcb Binary files /dev/null and b/code/gamespy/Chat/chatty/res/chatty.ico differ diff --git a/code/gamespy/Chat/chatty/res/chatty.rc2 b/code/gamespy/Chat/chatty/res/chatty.rc2 new file mode 100644 index 00000000..399a219f --- /dev/null +++ b/code/gamespy/Chat/chatty/res/chatty.rc2 @@ -0,0 +1,13 @@ +// +// CHATTY.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/Chat/chatty/res/chattyDoc.ico b/code/gamespy/Chat/chatty/res/chattyDoc.ico new file mode 100644 index 00000000..2a1f1ae6 Binary files /dev/null and b/code/gamespy/Chat/chatty/res/chattyDoc.ico differ 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 00000000..7eef0bcb Binary files /dev/null and b/code/gamespy/Peer/PeerLobby/res/PeerLobby.ico differ diff --git a/code/gamespy/Peer/PeerLobby/res/PeerLobby.rc2 b/code/gamespy/Peer/PeerLobby/res/PeerLobby.rc2 new file mode 100644 index 00000000..9fb0ea68 --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/res/PeerLobby.rc2 @@ -0,0 +1,13 @@ +// +// PEERLOBBY.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/Peer/PeerLobby/res/ico00001.ico b/code/gamespy/Peer/PeerLobby/res/ico00001.ico new file mode 100644 index 00000000..4d3d729a Binary files /dev/null and b/code/gamespy/Peer/PeerLobby/res/ico00001.ico differ diff --git a/code/gamespy/Peer/PeerLobby/res/ico00002.ico b/code/gamespy/Peer/PeerLobby/res/ico00002.ico new file mode 100644 index 00000000..f988b937 Binary files /dev/null and b/code/gamespy/Peer/PeerLobby/res/ico00002.ico differ diff --git a/code/gamespy/Peer/PeerLobby/res/ico00003.ico b/code/gamespy/Peer/PeerLobby/res/ico00003.ico new file mode 100644 index 00000000..dc5c8127 Binary files /dev/null and b/code/gamespy/Peer/PeerLobby/res/ico00003.ico differ diff --git a/code/gamespy/Peer/PeerLobby/res/ico00004.ico b/code/gamespy/Peer/PeerLobby/res/ico00004.ico new file mode 100644 index 00000000..771d4e14 Binary files /dev/null and b/code/gamespy/Peer/PeerLobby/res/ico00004.ico differ diff --git a/code/gamespy/Peer/PeerLobby/res/icon1.ico b/code/gamespy/Peer/PeerLobby/res/icon1.ico new file mode 100644 index 00000000..d07fb145 Binary files /dev/null and b/code/gamespy/Peer/PeerLobby/res/icon1.ico differ diff --git a/code/gamespy/Peer/PeerLobby/resource.h b/code/gamespy/Peer/PeerLobby/resource.h new file mode 100644 index 00000000..3bfc1876 --- /dev/null +++ b/code/gamespy/Peer/PeerLobby/resource.h @@ -0,0 +1,45 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by PeerLobby.rc +// +#define IDD_PEERLOBBY_DIALOG 102 +#define IDP_SOCKETS_INIT_FAILED 103 +#define IDD_TITLE_PAGE 106 +#define IDD_CONNECT_PAGE 107 +#define IDD_GROUP_PAGE 108 +#define IDD_CREATE_PAGE 109 +#define IDD_STAGING_PAGE 110 +#define IDR_MAINFRAME 128 +#define IDI_YELLOW_SMILEY 130 +#define IDI_RED_SMILEY 131 +#define IDI_RUNNING_GAME 132 +#define IDI_STAGING_ROOM 133 +#define IDI_GREEN_SMILEY 134 +#define IDC_NICK 1000 +#define IDC_NAME 1001 +#define IDC_MAX_PLAYERS 1002 +#define IDC_CREATE_GAME 1004 +#define IDC_JOIN_GAME 1005 +#define IDC_GAMES 1006 +#define IDC_CHAT_WINDOW 1007 +#define IDC_PLAYERS 1008 +#define IDC_MESSAGE 1009 +#define IDC_NOT_READY 1017 +#define IDC_READY 1018 +#define IDC_CREATE 1020 +#define IDC_TITLE 1021 +#define IDC_KEY 1022 +#define IDC_GROUPS 1029 +#define IDC_GROUP_ROOMS 1030 +#define IDC_PROGRESS 10126 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 130 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1031 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/gamespy/Peer/changelog.txt b/code/gamespy/Peer/changelog.txt new file mode 100644 index 00000000..34fce00d --- /dev/null +++ b/code/gamespy/Peer/changelog.txt @@ -0,0 +1,255 @@ +Changelog for: GameSpy Peer SDK +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 2.07.00 RMV RELEASE Released to Developer Site +12-11-2007 2.06.03 SAH OTHER Added NatNeg to projects to remove compiler errors +11-27-2007 2.06.01 SAH CLEANUP Moved extern "c" block below includes to prevent linker errors +08-06-2007 2.06.00 RMV RELEASE Released to Developer Site +07-10-2007 2.05.02 RMV FIX Fixed peerc Project files to get rid of Unicode warnings and fixed other compiler warnings +02-16-2006 2.05.01 SAH FIX Fixed bug where room name for UNICODE version was not being set +12-15-2006 2.05.00 MJW RELEASE Released to Developer Site +12-07-2006 2.04.66 SAH FIX Fixed bug where pending IMCP queries were not removed from gameEngine in piSBGamesListCallback +10-10-2006 2.04.65 SN FIX Stop automatch now automatically has the player leave the room. +10-05-2006 2.04.65 SAH FIX Updated MacOSX Makefile +09-28-2006 2.04.64 SAH FIX Fixed PS3 project to work with PS3 095.00x SDK; changed included libaries in linker input. +08-02-2006 2.04.63 SAH RELEASE Releasing to developer site +07-31-2006 2.04.63 SAH FIX Fixed PS3 project file - added post-build step to create *.SELF for execution +07-25-2006 2.04.62 SAH FIX Fixed NITRO project, include for crt0.o now above others so it add correct file +07-06-2006 2.04.61 SAH FIX Fixed PSP project file to not explicitly include the PSP linker file +07-06-2006 2.04.60 SAH FIX Fixed Linux makefile to work with pthreads (for http asynch DNS lookup) + SAH FIX Fixed NITRO project & linker command file (to work with CW 2.0/NitroSDK 3.1) +06-05-2006 2.04.59 SN FIX Modified the start listing groups to use correct function for stopping the listing of groups +06-02-2006 2.04.58 SAH FIX Added GS_STATIC_CALLBACK on comparator functions for __fastcall support +05-31-2006 2.04.57 SAH RELEASE Releasing to developer site +05-30-2006 2.04.56 SAH FIX Fixed PS3 projects to work with PS3(084_001 SDK) +05-25-2006 2.04.55 SAH FIX Added GSI_UNUSED calls to get rid of PSP warnings + SAH FIX Changed PSP project warning levels +05-23-2006 2.04.54 SAH FIX added GSI_UNUSED calls to get rid of PS3 warnings +05-19-2006 2.04.53 SAH FIX Added gsTestMain.c to nitro CodeWarrior project +05-15-2006 2.04.52 SAH FIX Added "PS3 Release" configuration to project +04-25-2006 2.04.51 SAH RELEASE Releasing to developer site +04-24-2006 2.04.51 SAH FIX Fixed some compile errors with UNICODE build, mainly typecasts +04-24-2006 2.04.50 SAH FIX Fixed Nitro project files to work on build machine +04-20-2006 2.04.49 SAH FIX Commented out unused variables, Moved GSI_UNUSED above return calls + SAH FIX Removed uncessary check (if unsigned int > 0) + SAH FIX Removed uncessary break; statements +04-18-2006 2.04.48 SAH FIX Added || defined(_PS3) to peerc.c for PS3 support +04-14-2006 2.04.47 SAH FIX Got rid of GsCore in the project files, added compile errors +04-10-2006 2.04.46 SAH FEATURE Multiple sorts will retain previous sorting arrangement when values are equal +02-10-2006 2.02.45 SN FIX peerParseQueryW was redundant since qr2_parse_query simply needs an char stream + As encountered by a developer. +02-07-2006 2.02.44 SN FIX peerSetStagingRoomMaxPlayers sets the channel limit in addition to the state change +01-27-2006 2.02.43 SN RELEASE Releasing to developer site +01-26-2006 2.02.43 SN OTHER Added psp prodg project and solution to sgv +12-22-2005 2.02.42 SN OTHER Cleaned up vc6 projects and added latest common code if missing +12-16-2005 2.02.41 SN OTHER Added a solution for the SDK to vault and fixed bindings on vcproj files +12-13-2005 2.02.40 SN FIX Cleaned up .vcproj files to have latest common code + Fixed a compiler issue that would not get caught on VC6 +11-17-2005 2.02.39 DES FEATURE Added ability to send client messages to servers. + DES FIX Updated Nitro Makefile. + DES FIX Made peerc printf Unicode compatible. +11-14-2005 2.02.38 DES FIX Updated the OSX Makefile. + DES FEATURE Added GSI_DOMAIN_NAME support. +10-13-2005 2.02.37 BED FEATURE peerSB now supports QR2 query challenge. + BED FIX Function pointers now cast to int to prevent strict ANSI warnings +09-21-2005 2.02.36 DES FEATURE Updated DS support +08-16-2005 2.02.35 SN FIX Added support for NatNeg in automatch. +07-28-2005 2.02.34 SN RELEASE Releasing to developer site. +07-28-2005 2.02.34 SN FIX Fixed code causing warning in peerc test app +06-03-2004 2.02.33 SN RELEASE Releasing to developer site. +06-03-2005 2.02.33 SN FIX Fixed issue where people behind same firewall as host could join full staging rooms. +05-13-2005 2.02.32 SN FIX Fixed issue where group room internal data was being incompletely cleared, causing crashes. +05-05-2005 2.02.31 BED FIX Update header files for new common code. +05-05-2005 2.02.30 SN OTHER Created Visual Studio .NET project +04-28-2005 2.02.30 SN RELEASE Releasing to developer site. +04-27-2005 2.02.30 DES RELEASE Limited release to Nintendo DS developers. +04-27-2005 2.02.30 DES FEATURE Updates to make peerc more friendly. + DES FIX On Nitro only, Ping the chat server ever 20 seconds to stay connected. +04-27-2005 2.02.29 SN FEATURE Made changes to allow developers to override default chat ping time. +04-25-2005 2.02.28 DES CLEANUP Disable Win32 linker warning. +04-04-2005 2.02.27 SN RELEASE Releasing to developer site. +04-01-2005 2.02.27 SN FIX Fixed and handled case where getting automatch channel modes failed +03-14-2005 2.02.26 DES FEATURE Nintendo DS support +01-27-2005 2.02.25 DES FIX Fixed custom SN sendto and moved it to nonport +11-06-2004 2.02.24 SN FIX Fixed hashing algorithm to minimize collisions +11-04-2004 2.02.23 SN FIX Fixed calls to SB Interal calls +10-15-2004 2.02.22 SN FEATURE Added SDK side nickname checking +09-27-2004 2.02.21 SN RELEASE Releasing with Qr2 fix to developer site +09-17-2004 2.02.21 JED FIX Added robustness to piConnectFillInUserCallback & PEER_CONNECTION_OP, Arcade reported some null pointer crashes +09-16-2004 2.02.20 SN RELEASE Releasing to developer site. +08-27-2004 2.02.20 DES CLEANUP Fixed warnings under OSX + DES CLEANUP Removed peerc.dsp's dependency on peer.dsp + DES CLEANUP General Unicode cleanup + DES CLEANUP Removed MacOS style includes + DES CLEANUP Updated Win32 project configurations + DES CLEANUP Removed headers already included in nonport.h + DES CLEANUP Updated OSX Makefile +08-26-2004 2.02.19 SN FEATURE Added peerUpdateServerByMaster function to update a server through the master server +08-26-2004 2.02.18 DES CLEANUP Removed MacOS stlye includes +08-16-2004 2.02.18 BED FIX Fixed case where players would become stuck in automatch searching phase +08-05-2004 2.02.17 SN RELEASE Releasing to developer site. +08-05-2004 2.02.17 SN FIX Added a parameter missing in a prototype (BAD, compiler could not catch it!!!). +07-19-2004 2.02.17 SN FIX Updated code with explicit casts to remove implicit cast error + when compiling at highest level and warnings treated as errors. +06-21-2004 2.02.16 BED FIX Fixed bug with peerc sample's handling of NickErrorCallback +06-18-2004 2.02.15 BED RELEASE Releasing to developer site. +06-17-2004 2.02.15 BED FIX Fixed bug where piListingGamesCall was prematurely showing 100% progress. + BED FEATURE Added PS2 Insock support +05-21-2004 2.02.14 DDW FEATURE Minor change to accomodate Peer/VEngine compatibility +03-04-2004 2.02.13 BED FIX Fixed bug in Unicode wrapper when retrieving keys for user "*" +12-03-2003 2.02.12 BED FIX Fixed bug where duplicate key change callbacks were called for broadcast keys in staging rooms +12-02-2003 2.02.11 BED FIX Fixed parameter mismatch when using cached player info. +11-10-2003 2.02.10 DES RELEASE Releasing to developer site. +11-07-2003 2.02.10 BED FIX Updated CodeWarrior project files. +11-07-2003 2.02.09 DES FIX Updated linux and PS2 makefiles. + FIX Fixed a case mismatch for a chat include. + FIX Took some debug code out of peerc.c. + FIX Wrapped some internal unicode functions in GSI_UNICODE ifdefs. + FIX Commented out functions in peerc.c that were no longer being used. +11-04-2003 2.02.08 DES FEATURE Added availability check code. +10-29-2003 2.02.07 DES FEATURE Added code to PeerTest for doing the available check. + DES FIX Removed SN compiler options from PeerTest's project. +10-21-2003 2.02.06 BED RELEASE Releasing to developer site. (UNIQUE NICK AND UNICODE SUPPORT) + BED FIX Removed misc compiler warnings on strict settings. +10-21-2003 2.02.05 DES FEATURE Updated PeerTest to handle new connect methods. +10-16-2003 2.02.04 BED FIX Changed from UCS2ToUTF8String to UCS2ToAsciiString when dealing with nicknames. + FEATURE Added peerSetStagingRoomMaxPlayers. Developer request to allow host to update this. + DES FIX peerPingPlayer no longer returns PEERFalse if there is an outstanding ping for the player. + FIX Correctly handle peerConnectLogin with a 0 namespaceID. +10-09-2003 2.02.03 BED FIX Switched to gsi_time type instead of unsinged long for PS2 compatibility +10-08-2003 2.02.02 DES FEATURE Added peerPingPlayer for doing one-time pings in non-ping rooms. +10-01-2003 2.02.01 DES FEATURE Added a reason code for failed connect attempts. + FEATURE Added suggested nicks for a CHAT_INVALID_UNIQUENICK nick errors. +09-30-2003 2.02.00 DES FEATURE Uniquenick support. +09-08-2003 2.01.27 BED FEATURE Added UTF-8 wrapper for UNICODE support. +08-29-2003 2.01.26 DES RELEASE Released to Stainless Steel for Empires: DotMW. + FIX Compatible AM's that start at the same time will now get matched. + FIX No longer queries an AM server if a query is already pending. +08-18-2003 2.01.25 DES FEATURE Added a callback for getting your public reporting address. +07-24-2003 2.01.24 DES RELEASE Releasing to developer site. +07-24-2003 2.01.24 DES CLEANUP Removed unused roomType parameter from piPingPlayerLeftRoom. +07-23-2003 2.01.23 BED FEATURE Added Linux sample Makefile. +07-18-2003 2.01.22 BED FEATURE Added CodeWarrior (PS2) sample project file. + CLEANUP General cleanup to remove CodeWarrior warnings. +07-17-2003 2.01.21 DES CLEANUP Cleaned up the PS2 Makefile, it now uses Makefile.commmon. +07-16-2003 2.01.20 DES FIX Changed a __mips64 check to a _PS2 check. + BED FEATURE Added ProDG sample project files. +07-10-2003 2.01.19 DES FIX Fixed unused value warning in pinger. +06-26-2003 2.01.18 DES FEATURE Added peerGetRoomHost to get the SBServer for the staging room host. +06-26-2003 2.01.17 DES RELEASE Releasing to developer site. + FEATURE peerJoinStagingRoomByChannel added for joining a staging room based on channel name. + FIX Fixed peerShutdown to disconnect before clearing the title. + FIX PeerTest now uses PEER_FLAG_HOST instead of PEER_FLAG_OP to determine the room host. + FIX Fixed PeerTest to refresh a room's players when a player's host flag changes. +06-24-2003 2.01.16 DES FIX Fixed peerStopAutoMatch comment to say it will not leave a staging room. +06-11-2003 2.01.15 DES RELEASE Releasing to developer site. +06-04-2003 2.01.15 DDW FIX Fixed binding to private IP when both private and public are present +05-11-2003 2.01.14 DDW CLEANUP Compatibility with SB internal API changes +05-09-2003 2.01.13 DES CLEANUP Removed Dreamcast support. + FIX Metrowerks for Win32 is no longer falsely identified as MacOS. +05-07-2003 2.01.12 DES RELEASE Releasing to developer site. + FIX Updated PS2 Makefile for AutoMatch. +05-07-2003 2.01.12 JED CLEANUP Replaced refrences in comments to the dyslexic peerGameStart() with the proper peerStartGame() +05-03-2003 2.01.11 DES FIX Was unable to join an AutoMatch room if the host was behind a firewall. +04-25-2003 2.01.10 DES FIX Flipped peerGetPlayerInfo IP and profileID paramters to match the NoWait version. + FIX peerParseQuery wasn't handling queries correctly during an AutoMatch attempt. + CLEANUP peerStartAutoMatch[WithSocket] now returns void. If it fails to start the + attempt, it calls the statusCallback with PEERFailed. + FIX peerStartAutoMatch[WithSocket] wasn't doing blocking correctly. +04-24-2003 2.01.09 DES FEATURE Added peerGetPlayerInfo to replace peerGetPlayerProfileID and peerGetPlayerIP. + Both old functions are now deprecated and will be removed in a future version. +04-23-2003 2.01.08 DES FEATURE peerGameStartedCallback is now passed the host's SBServer instead of IP. + FEATURE peerGetLocalIP has been replaced by peerGetPublicIP and peerGetPrivateIP. + FIX AutoMatch now correctly checks if a listed match is the local player or not. +04-03-2003 2.01.07 DES FIX If maxPlayers was set to 0 in peerCreateStagingRoom[WithSocket], + the qrServerKey callback was not being called for MAXPLAYERS_KEY. +03-27-2003 2.01.06 DES FIX piGetPrivateIP now returns the IP instead of true/false. +03-26-2003 2.01.05 DES FEATURE Added peerSetGroupID and peerGetGroupID, which allows for getting a list + of games in a group room and reporting a game as being in a group, + without having to actually be in that group room. + CLEANUP Removed piListingGamesGroupID method of setting group ID. + OTHER Updated to pass the new extra parameter to SBServerListConnectAndQuery(). +03-24-2003 2.01.04 DES FIX piStartHosting and piStartReportingWithSocket will now correctly return + PEERFalse if piStartReporting fails. + FIX Updated the PS2 Makefile to compile files recently added to Peer. + OTHER Changed interal IP code to use the common code's IsPrivateIP. +03-21-2003 2.01.03 DES FEATURE Added the ability to join a passworded title room. + FEATURE Added a global (piListingGamesGroupID) to control what group ID to use + when listing games. Use -1 for games that aren't in a group. + OTHER Doesn't initialize pinger if there are no ping rooms. + OTHER During initial server listing, don't query server that already have keys. +03-19-2003 2.01.02 DES FEATURE Added a player flag PEER_FLAG_HOST to identify a staging room host. + FIX Switched to a new set of crypt characters for encoding IPs, the old set + had both 'F' and 'f', which could cause conflicting staging room names. + FIX No longer need to pass a list of fields to peerStartAutoMatch[WithSocket](). + FIX General AutoMatch fixes. +03-13-2003 2.01.01 DES OTHER Shortened the length of the new staging room names. + OTHER If peerIsPlayerHost() is called for the local player, return Peer's hosting flag. + CLEANUP Changed code that used internal members of in_addr (not always fully supported). +03-12-2003 2.01.00 DES FEATURE Full AutoMatch support. + FEATURE Added peerAlwaysGetPlayerInfo(), which is used to tell Peer to always + get players' IPs and Profile IDs whether rooms are ping rooms or not. + FEATURE Added the ability to host multiple staging rooms behind the same public IP. + The staging room name is now based on public IP, private IP, and private queryport. + FEATURE Peer now sets limits on staging rooms to allow no more than the maxPlayers. + This behavior can be turned off by defining PI_NO_STAGING_ROOM_LIMIT. + If maxPlayers is 0, no limit is set, and maxPlayers is not reported. + FEATURE If 0 keys are requested on a room or a player, all of the keys will be returned. + FEATURE Added piSBQueryVersion global flag to set the type of server queries to send. + FIX If a server is removed, clear any pending add/update calls for the same server. + CLEANUP Changed _strdup() calls to goastrdup(). + CLEANUP Added some explicit casts to remove compiler warnings. + CLEANUP Removed peerJoinStagingRoomByIP(). + CLEANUP Peer no longer stores passwords, it only stores whether or not one has been set. + CLEANUP Improved the process of cancelling a join that is already in progress. +02-10-2003 2.00.19 DES CLEANUP Better seperation between hosting (hosting a staging room) + and reporting (reporting a staging room or running game). + FEATURE First steps for AutoMatch support. +02-28-2003 2.00.18 DES RELEASE Releasing to developer site with updated QR2. +02-05-2003 2.00.18 DES RELEASE Releasing to developer site. +02-05-2003 2.00.18 DES CLEANUP Switched to use CanReceiveOnSocket instead of select. +02-04-2003 2.00.17 DES RELEASE Releasing to developer site. +01-23-2003 2.00.17 DES FIX Replaced several calls to free with gsifree. +01-08-2003 2.00.16 DES FIX Fixed passing the incorrect param value to the peerListGroupRooms callback. +12-19-2002 2.00.15 DES RELEASE Releasing to developer site. +12-19-2002 2.00.15 DES CLEANUP Removed assert.h includes. +12-16-2002 2.00.14 DES CLEANUP Removed call to GOAClearSocketError in pinger. +12-13-2002 2.00.13 DES FEATURE Added PS2 eenet stack support. +12-06-2002 2.00.12 DES RELEASE Releasing to developer site with updated QR2. +12-05-2002 2.00.12 DES CLEANUP Removed an extraneous assert. + CLEANUP Changes to remove warnings. +12-03-2002 2.00.11 DES RELEASE Releasing to developer site. +12-03-2002 2.00.11 DES FIX Fixed PeerTest to correctly show server state flags. + FEATURE Added Update button to PeerTest, uses peerUpdateGame to get a server's full keys. +11-25-2002 2.00.10 DES FIX Fixed PeerTest to handle e_qrnochallengeerror. +11-22-2002 2.00.09 DES RELEASE Releasing to developer site. +11-20-2002 2.00.09 DES CLEANUP Cleaned up to remove compiler warings on the PS2. +11-20-2002 2.00.08 DES FIX Fixed bug which caused blocking functions to lockup if disconnected while blocking. +11-14-2002 2.00.07 DES FEATURE Added peerKickPlayer function to SDK and to PeerTest +11-07-2002 2.00.06 DES RELEASE Limited release on developer site and to EAPAC for Generals + Incorporates serverbrowsing change to fix invalid SBServer in listing games callback. +11-07-2002 2.00.06 DES FIX Fixed negative hash due to high-ascii characters in hashed string. +10-28-2002 2.00.05 DES RELEASE Limited release on developer site and to EAPAC for Generals +10-28-2002 2.00.05 DES FIX Correctly passing request fields to SB when starting listing. + Changed peerStartListingGames API to pass in keys by id instead of name. + Changed tests/samples to use new API. + SB callbacks debugging code now also shows the server address or group id. +10-22-2002 2.00.04 DES RELEASE Limited release on developer site and to EAPAC for Generals +10-22-2002 2.00.04 DES FIX Updated SB querying to use QR2 style instead of old GOA style + Update PeerTest to always check for no currently selected server +10-22-2002 2.00.03 DDW FIX Clears query engine when stopping listing of games + Correctly removes servers from query engine when deleted message arrives +10-17-2002 2.00.02 DES RELEASE Limited release on developer site and to EAPAC for Generals + Incorporates fix to Chat SDK +10-10-2002 2.00.02 DES RELEASE Limited release on developer site and to EAPAC for Generals +10-10-2002 2.00.02 DES FIX SBQueryEngineSetPublicIP is now called when the serverlist + determines the public IP (slc_publicipdetermined). +09-26-2002 2.00.01 DES RELEASE Limited release on developer site +09-25-2002 2.00.01 DDW OTHER Changelog started +09-23-2002 2.00.01 DES RELEASE Release to EAPAC for Generals +09-23-2002 2.00.01 DES FEATURE Added peerUpdateGame function +09-06-2002 2.00.00 DDW RELEASE Release to EAPAC for Generals +09-06-2002 2.00.00 DES FEATURE Replaces CEngine/QR with ServerBrowsing/QR2 +06-25-2002 1.20.00 DES RELEASE Release on deveoper site \ No newline at end of file diff --git a/code/gamespy/Peer/peer.h b/code/gamespy/Peer/peer.h new file mode 100644 index 00000000..1b0978ac --- /dev/null +++ b/code/gamespy/Peer/peer.h @@ -0,0 +1,1866 @@ + /* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEER_H_ +#define _PEER_H_ + +/************* +** INCLUDES ** +*************/ +#include "../common/gsCommon.h" +#include "../Chat/chat.h" +#include "../qr2/qr2.h" +#include "../serverbrowsing/sb_internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/************ +** DEFINES ** +************/ +// Defines for the msg param that's passed into peerListingGamesCallback(). +// PANTS-04.20.00-changed from PI_* to PEER_* +// PANTS-09.26.02-added PEER_COMPLETE +/////////////////////////////////////////////////////////////////////////// +#define PEER_ADD 0 // a server is being added +#define PEER_UPDATE 1 // a server has been updated +#define PEER_REMOVE 2 // a server has been removed +#define PEER_CLEAR 3 // all the servers have been cleared +#define PEER_COMPLETE 4 // the initial listing of servers is complete + +// Nick errors, for peerNickErrorCallback. +////////////////////////////////////////// +#define PEER_NICK_OK 0 +#define PEER_IN_USE 1 // the nick is already being used +#define PEER_INVALID 2 // the nick contains invalid characters +#define PEER_UNIQUENICK_EXPIRED 3 // the uniquenick for this account has expired +#define PEER_NO_UNIQUENICK 4 // there is no uniquenick for this account +#define PEER_INVALID_UNIQUENICK 5 // the uniquenick to associate with the account is invalid or in use +#define PEER_NICK_TOO_LONG 6 // the nick was too long + +// Possible values for the failureReason passed to the peerConnectCallback. +/////////////////////////////////////////////////////////////////////////// +#define PEER_DISCONNECTED 0 // disconnected from, or unable to connect to, the server +#define PEER_NICK_ERROR 1 // a nick error was either ignored or not handled +#define PEER_LOGIN_FAILED 2 // the login info passed to peerConnectLogin was not valid + +// Maximum length of a room password, including the terminating NUL. +//////////////////////////////////////////////////////////////////// +#define PEER_PASSWORD_LEN 24 + +// Each player can have various flags set for each room they are in. +//////////////////////////////////////////////////////////////////// +#define PEER_FLAG_STAGING 0x01 // s +#define PEER_FLAG_READY 0x02 // r +#define PEER_FLAG_PLAYING 0x04 // g +#define PEER_FLAG_AWAY 0x08 // a +#define PEER_FLAG_HOST 0x10 // h +#define PEER_FLAG_OP 0x20 +#define PEER_FLAG_VOICE 0x40 + +// Bitfield reporting options for peerStartGame. +//////////////////////////////////////////////// +#define PEER_KEEP_REPORTING 0 // Continue reporting. +#define PEER_STOP_REPORTING 1 // Stop reporting. Cannot be used with other options. +#define PEER_REPORT_INFO 2 // Continue reporting server keys (as if it were not playing). +#define PEER_REPORT_PLAYERS 4 // Continue reporting player keys (as if it were not playing). + +/********** +** TYPES ** +**********/ +// The peer object. +/////////////////// +typedef void * PEER; + +// Boolean. +/////////// +typedef enum +{ + PEERFalse, + PEERTrue +} PEERBool; + +// Types of rooms. +////////////////// +typedef enum +{ + TitleRoom, // The main room for a game. + GroupRoom, // A room which is, in general, for a particular type of gameplay (team, dm, etc.). + StagingRoom, // A room where players meet before starting a game. + NumRooms +} RoomType; + +// Types of messages. These have the same +// values as their CHAT SDK counterparts. +// PANTS-01.08.01 +///////////////////////////////////////// +typedef enum +{ + NormalMessage, + ActionMessage, + NoticeMessage +} MessageType; + +// Possible results when attempting to join a room. +// Passed into peerJoinRoomCallback(). +/////////////////////////////////////////////////// +typedef enum +{ + PEERJoinSuccess, // The room was joined. + + PEERFullRoom, // The room is full. + PEERInviteOnlyRoom, // The room is invite only. + PEERBannedFromRoom, // The local user is banned from the room. + PEERBadPassword, // An incorrect password (or none) was given for a passworded room. + + PEERAlreadyInRoom, // The local user is already in or entering a room of the same type. + PEERNoTitleSet, // Can't join a room if no title is set. + PEERNoConnection, // Can't join a room if there's no chat connection. + PEERAutoMatching, // The user can't join a staging room during an auto match attempt. + + PEERJoinFailed // Generic failure. +} PEERJoinResult; + +// Possible status values passed to the peerAutoMatchStatusCallback. +// If PEERFailed, the match failed, otherwise this is the current status +// of the automatch attempt. +//////////////////////////////////////////////////////////////////////// +typedef enum +{ + PEERFailed, // The automatch attempt failed. + + PEERSearching, // Searching for a match (active). + PEERWaiting, // Waiting for a match (passive). + PEERStaging, // In a staging room with at least one other player, possibly waiting for more. + PEERReady, // All players are in the staging room, the game is ready to be launched. + PEERComplete // The game is launching, the automatch attempt is now complete. + // The player is still in the staging room. +} PEERAutoMatchStatus; + +/************** +** CALLBACKS ** +**************/ +// Called when the connection to the server gets disconnected. +////////////////////////////////////////////////////////////// +typedef void (* peerDisconnectedCallback) +( + PEER peer, // The peer object. + const gsi_char * reason, // The reason for the disconnection. + void * param // User-data. +); + +// Called when a message is sent to a room the local player is in. +////////////////////////////////////////////////////////////////// +typedef void (* peerRoomMessageCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of room that the message was in. + const gsi_char * nick, // The nick of the player who sent the message. + const gsi_char * message, // The text of the message. + MessageType messageType, // The type of message. + void * param // User-data. +); + +// Called when a UTM is sent to a room the local player is in. +////////////////////////////////////////////////////////////// +typedef void (* peerRoomUTMCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of room that the UTM was in. + const gsi_char * nick, // The nick of the player who sent the UTM. + const gsi_char * command, // The UTM command for this message. + const gsi_char * parameters, // Any parameters for this UTM. + PEERBool authenticated, // True if this has been authenticated by the server. + void * param // User-data. +); + +// Called when the name of a room the player is in changes. +// The new name can be checked with peerGetRoomName. +// PANTS|09.11.00 +/////////////////////////////////////////////////////////// +typedef void (* peerRoomNameChangedCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of room that the name changed in. + void * param // User-data +); + +// Called when a room's mode changes. +// PANTS|04.17.00 +/////////////////////////////////////////////////////////// +typedef void (* peerRoomModeChangedCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of room that the name changed in. + CHATChannelMode * mode, // The current mode for this room. + void * param // User-data +); + +// Called when a private message is received from another player. +///////////////////////////////////////////////////////////////// +typedef void (* peerPlayerMessageCallback) +( + PEER peer, // The peer object. + const gsi_char * nick, // The nick of the player who sent the message. + const gsi_char * message, // The text of the message. + MessageType messageType, // The type of message. + void * param // User-data +); + +// Called when a private UTM is received from another player. +///////////////////////////////////////////////////////////// +typedef void (* peerPlayerUTMCallback) +( + PEER peer, // The peer object. + const gsi_char * nick, // The nick of the player who sent the UTM. + const gsi_char * command, // The UTM command for this message. + const gsi_char * parameters, // Any parameters for this UTM. + PEERBool authenticated, // True if this has been authenticated by the server. + void * param // User-data +); + +// Called when a player's ready state changes, +// from a call to peerSetReady(). +////////////////////////////////////////////// +typedef void (* peerReadyChangedCallback) +( + PEER peer, // The peer object. + const gsi_char * nick, // The nick of the player who's ready state changed. + PEERBool ready, // The player's new ready state. + void * param // User-data. +); + +// Called when the host of a staging room launches the game, +// with a call to peerStartGame(). +// The public and private IPs and ports of the server can +// be obtained from the server object. +//////////////////////////////////////////////////////////// +typedef void (* peerGameStartedCallback) +( + PEER peer, // The peer object. + SBServer server, // A server object representing this host. + const gsi_char * message, // A message that was passed into peerStartGame(). + void * param // User-data. +); + +// A player joined a room. +////////////////////////// +typedef void (* peerPlayerJoinedCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of room that the player joined. + const gsi_char * nick, // The nick of the player that joined. + void * param // User-data. +); + +// A player left a room. +//////////////////////// +typedef void (* peerPlayerLeftCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of room that the player left. + const gsi_char * nick, // The nick of the player that left. + const gsi_char * reason, // The reason the player left. + void * param // User-data. +); + +// The local player was kicked from a room. +/////////////////////////////////////////// +typedef void (* peerKickedCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of room that the player was kicked from. + const gsi_char * nick, // The nick of the player that did the kicking. + const gsi_char * reason, // An optional reason for the kick. + void * param // User-data. +); + +// The entire player list for this room has been updated. +///////////////////////////////////////////////////////// +typedef void (* peerNewPlayerListCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of room. + void * param // User-data +); + +// A player in one of the rooms changed his nick. +///////////////////////////////////////////////// +typedef void (* peerPlayerChangedNickCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The type of the room the nick changed was in. + const gsi_char * oldNick, // The player's old nick. + const gsi_char * newNick, // The player's new nick. + void * param // User-data. +); + +// The IP and ProfileID for this player has just been received. +// PANTS|01.08.01 +// This gets called for all players (who are using peer) in a room +// shortly after joining. It will be called with nick==NULL after +// getting info for all the players. +////////////////////////////////////////////////////////////////// +typedef void (* peerPlayerInfoCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The room the info was gotten in. + const gsi_char * nick, // The nick of the player the info is for. + unsigned int IP, // The player's IP. + int profileID, // The player's profile ID. + void * param // User-data. +); + +// This gets called when a player's flags have changed. +/////////////////////////////////////////////////////// +typedef void (* peerPlayerFlagsChangedCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The room the flags were changed in. + const gsi_char * nick, // The player whose flags have changed. + int oldFlags, // The player's old flags. + int newFlags, // The player's new flags. + void * param // User-data +); + +// An updated ping for a player, who may be in any room(s). +/////////////////////////////////////////////////////////// +typedef void (* peerPingCallback) +( + PEER peer, // The peer object. + const gsi_char * nick, // The other player's nick. + int ping, // The ping. + void * param // User-data. +); + +// An updated cross-ping between two players in the staging room. +///////////////////////////////////////////////////////////////// +typedef void (* peerCrossPingCallback) +( + PEER peer, // The peer object. + const gsi_char * nick1, // The first player's nick. + const gsi_char * nick2, // The second player's nick. + int crossPing, // The cross-ping. + void * param // User-data. +); + +// This is called for watch keys when a room is joined, for +// watch keys when another player joins, and for any newly +// set watch keys. +/////////////////////////////////////////////////////////// +typedef void (* peerGlobalKeyChangedCallback) +( + PEER peer, // The peer object. + const gsi_char * nick, // The player whose key changed. + const gsi_char * key, // The key. + const gsi_char * value, // The value. + void * param // User-data. +); + +// This is called for watch keys when a room is joined, for +// watch keys when another player joins, for any newly +// set watch keys, and when a broadcast key is changed. +/////////////////////////////////////////////////////////// +typedef void (* peerRoomKeyChangedCallback) +( + PEER peer, // The peer object. + RoomType roomType, // The room the player is in. + const gsi_char * nick, // The player whose key changed. + const gsi_char * key, // The key. + const gsi_char * value, // The value. + void * param // User-data. +); + +// Called to report QR server keys. +// Use qr2_buffer_add or qr2_buffer_add_int to +// add this key's information to the buffer. +////////////////////////////////////////////// +typedef void (* peerQRServerKeyCallback) +( + PEER peer, // The peer object. + int key, // The key for which to report information. + qr2_buffer_t buffer, // Fill in the information using this buffer. + void * param // User-data. +); + +// Called to report QR player keys. +// Use qr2_buffer_add or qr2_buffer_add_int to +// add this key's information to the buffer. +////////////////////////////////////////////// +typedef void (* peerQRPlayerKeyCallback) +( + PEER peer, // The peer object. + int key, // The key for which to report information. + int index, // The index of the player for which to report info. + qr2_buffer_t buffer, // Fill in the information using this buffer. + void * param // User-data. +); + +// Called to report QR team keys. +// Use qr2_buffer_add or qr2_buffer_add_int to +// add this key's information to the buffer. +////////////////////////////////////////////// +typedef void (* peerQRTeamKeyCallback) +( + PEER peer, // The peer object. + int key, // The key for which to report information. + int index, // The index of the team for which to report info. + qr2_buffer_t buffer, // Fill in the information using this buffer. + void * param // User-data. +); + +// Called to get a list of keys to be reported. +// Use qr2_keybuffer_add() to add keys. +// The following keys do not need to be added, as peer already adds them: +// GAMENAME_KEY, HOSTNAME_KEY, NUMPLAYERS_KEY, MAXPLAYERS_KEY, +// GAMEMODE_KEY, PASSWORD_KEY, GROUPID_KEY, PLAYER__KEY, PING__KEY. +///////////////////////////////////////////////////////////////////////// +typedef void (* peerQRKeyListCallback) +( + PEER peer, // The peer object. + qr2_key_type type, // The type of keys being asked for (key_server, key_player, or key_team). + qr2_keybuffer_t keyBuffer, // Fill in the keys using this buffer. + void * param // User-data. +); + +// Called to get a count of the number of players or teams. +/////////////////////////////////////////////////////////// +typedef int (* peerQRCountCallback) +( + PEER peer, // The peer object. + qr2_key_type type, // The type of count to return (key_player or key_team). + void * param // User-data. +); + +// Called when there is an error reporting the server. +////////////////////////////////////////////////////// +typedef void (* peerQRAddErrorCallback) +( + PEER peer, // The peer object. + qr2_error_t error, // The type of error. + gsi_char * errorString, // A text string containing the error. + void * param // User-data. +); + +// Called when hosting a server and a nat-negotiate cookie is received. +/////////////////////////////////////////////////////////////////////// +typedef void (* peerQRNatNegotiateCallback) +( + PEER peer, // The peer object. + int cookie, // A cookie sent from a potential client. + void * param // User-data. +); + +// Called when hosting a server with the server's public reporting address. +/////////////////////////////////////////////////////////////////////////// +typedef void (* peerQRPublicAddressCallback) +( + PEER peer, // The peer object. + unsigned int ip, // The public reporting IP + unsigned short port, // The public reporting port + void * param // User-data. +); + +// This struct gets passed into peerInitialize(). +// param will be passed as the last parameter to each of the callbacks. +/////////////////////////////////////////////////////////////////////// +typedef struct PEERCallbacks +{ + peerDisconnectedCallback disconnected; + peerRoomMessageCallback roomMessage; + peerRoomUTMCallback roomUTM; + peerRoomNameChangedCallback roomNameChanged; // PANTS|09.11.00 + peerRoomModeChangedCallback roomModeChanged; // PANTS|04.17.01 + peerPlayerMessageCallback playerMessage; + peerPlayerUTMCallback playerUTM; + peerReadyChangedCallback readyChanged; + peerGameStartedCallback gameStarted; + peerPlayerJoinedCallback playerJoined; + peerPlayerLeftCallback playerLeft; + peerKickedCallback kicked; + peerNewPlayerListCallback newPlayerList; + peerPlayerChangedNickCallback playerChangedNick; + peerPlayerInfoCallback playerInfo; // PANTS|01.08.01 + peerPlayerFlagsChangedCallback playerFlagsChanged; // PANTS|03.12.01 + peerPingCallback ping; + peerCrossPingCallback crossPing; + peerGlobalKeyChangedCallback globalKeyChanged; + peerRoomKeyChangedCallback roomKeyChanged; + peerQRServerKeyCallback qrServerKey; + peerQRPlayerKeyCallback qrPlayerKey; + peerQRTeamKeyCallback qrTeamKey; + peerQRKeyListCallback qrKeyList; + peerQRCountCallback qrCount; + peerQRAddErrorCallback qrAddError; + peerQRNatNegotiateCallback qrNatNegotiateCallback; + peerQRPublicAddressCallback qrPublicAddressCallback; + void * param; +} PEERCallbacks; + +/************ +** UNICODE ** +************/ +#ifndef GSI_UNICODE +#define peerConnect peerConnectA +#define peerConnectLogin peerConnectLoginA +#define peerConnectPreAuth peerConnectPreAuthA +#define peerRetryWithNick peerRetryWithNickA +#define peerRegisterUniqueNick peerRegisterUniqueNickA +#define peerSetTitle peerSetTitleA +#define peerGetTitle peerGetTitleA +#define peerGetNick peerGetNickA +#define peerFixNick peerFixNickA +#define peerTranslateNick peerTranslateNickA +#define peerChangeNick peerChangeNickA +#define peerSetAwayMode peerSetAwayModeA +#define peerParseQuery peerParseQueryA +#define peerAuthenticateCDKey peerAuthenticateCDKeyA +#define peerJoinTitleRoom peerJoinTitleRoomA +#define peerJoinStagingRoom peerJoinStagingRoomA +#define peerJoinStagingRoomByChannel peerJoinStagingRoomByChannelA +#define peerCreateStagingRoom peerCreateStagingRoomA +#define peerCreateStagingRoomWithSocket peerCreateStagingRoomWithSocketA +#define peerLeaveRoom peerLeaveRoomA +#define peerListGroupRooms peerListGroupRoomsA +#define peerStartListingGames peerStartListingGamesA +#define peerMessageRoom peerMessageRoomA +#define peerUTMRoom peerUTMRoomA +#define peerSetPassword peerSetPasswordA +#define peerSetRoomName peerSetRoomNameA +#define peerGetRoomName peerGetRoomNameA +#define peerGetRoomChannel peerGetRoomChannelA +#define peerSetTitleRoomChannel peerSetTitleRoomChannelA +#define peerMessagePlayer peerMessagePlayerA +#define peerUTMPlayer peerUTMPlayerA +#define peerKickPlayer peerKickPlayerA +#define peerGetPlayerPing peerGetPlayerPingA +#define peerGetPlayersCrossPing peerGetPlayersCrossPingA +#define peerPingPlayer peerPingPlayerA +#define peerGetPlayerInfoNoWait peerGetPlayerInfoNoWaitA +#define peerGetPlayerInfo peerGetPlayerInfoA +#define peerGetPlayerProfileID peerGetPlayerProfileIDA +#define peerGetPlayerIP peerGetPlayerIPA +#define peerIsPlayerHost peerIsPlayerHostA +#define peerGetPlayerFlags peerGetPlayerFlagsA +#define peerGetReady peerGetReadyA +#define peerStartGame peerStartGameA +#define peerSetGlobalKeys peerSetGlobalKeysA +#define peerGetPlayerGlobalKeys peerGetPlayerGlobalKeysA +#define peerGetRoomGlobalKeys peerGetRoomGlobalKeysA +#define peerSetRoomKeys peerSetRoomKeysA +#define peerGetRoomKeys peerGetRoomKeysA +#define peerSetGlobalWatchKeys peerSetGlobalWatchKeysA +#define peerSetRoomWatchKeys peerSetRoomWatchKeysA +#define peerGetGlobalWatchKey peerGetGlobalWatchKeyA +#define peerGetRoomWatchKey peerGetRoomWatchKeyA +#define peerStartAutoMatch peerStartAutoMatchA +#define peerStartAutoMatchWithSocket peerStartAutoMatchWithSocketA +#else +#define peerConnect peerConnectW +#define peerConnectLogin peerConnectLoginW +#define peerConnectPreAuth peerConnectPreAuthW +#define peerRetryWithNick peerRetryWithNickW +#define peerRegisterUniqueNick peerRegisterUniqueNickW +#define peerSetTitle peerSetTitleW +#define peerGetTitle peerGetTitleW +#define peerGetNick peerGetNickW +#define peerFixNick peerFixNickW +#define peerTranslateNick peerTranslateNickW +#define peerChangeNick peerChangeNickW +#define peerSetAwayMode peerSetAwayModeW +#define peerParseQuery peerParseQueryA +#define peerAuthenticateCDKey peerAuthenticateCDKeyW +#define peerJoinTitleRoom peerJoinTitleRoomW +#define peerJoinStagingRoom peerJoinStagingRoomW +#define peerJoinStagingRoomByChannel peerJoinStagingRoomByChannelW +#define peerCreateStagingRoom peerCreateStagingRoomW +#define peerCreateStagingRoomWithSocket peerCreateStagingRoomWithSocketW +#define peerLeaveRoom peerLeaveRoomW +#define peerListGroupRooms peerListGroupRoomsW +#define peerStartListingGames peerStartListingGamesW +#define peerMessageRoom peerMessageRoomW +#define peerUTMRoom peerUTMRoomW +#define peerSetPassword peerSetPasswordW +#define peerSetRoomName peerSetRoomNameW +#define peerGetRoomName peerGetRoomNameW +#define peerGetRoomChannel peerGetRoomChannelW +#define peerSetTitleRoomChannel peerSetTitleRoomChannelW +#define peerMessagePlayer peerMessagePlayerW +#define peerUTMPlayer peerUTMPlayerW +#define peerKickPlayer peerKickPlayerW +#define peerGetPlayerPing peerGetPlayerPingW +#define peerGetPlayersCrossPing peerGetPlayersCrossPingW +#define peerPingPlayer peerPingPlayerW +#define peerGetPlayerInfoNoWait peerGetPlayerInfoNoWaitW +#define peerGetPlayerInfo peerGetPlayerInfoW +#define peerGetPlayerProfileID peerGetPlayerProfileIDW +#define peerGetPlayerIP peerGetPlayerIPW +#define peerIsPlayerHost peerIsPlayerHostW +#define peerGetPlayerFlags peerGetPlayerFlagsW +#define peerGetReady peerGetReadyW +#define peerStartGame peerStartGameW +#define peerSetGlobalKeys peerSetGlobalKeysW +#define peerGetPlayerGlobalKeys peerGetPlayerGlobalKeysW +#define peerGetRoomGlobalKeys peerGetRoomGlobalKeysW +#define peerSetRoomKeys peerSetRoomKeysW +#define peerGetRoomKeys peerGetRoomKeysW +#define peerSetGlobalWatchKeys peerSetGlobalWatchKeysW +#define peerSetRoomWatchKeys peerSetRoomWatchKeysW +#define peerGetGlobalWatchKey peerGetGlobalWatchKeyW +#define peerGetRoomWatchKey peerGetRoomWatchKeyW +#define peerStartAutoMatch peerStartAutoMatchW +#define peerStartAutoMatchWithSocket peerStartAutoMatchWithSocketW +#endif + +/************ +** GENERAL ** +************/ +// This creates and intializes a peer object. +// NULL is returned in case of an error, otherwise a peer +// object is returned. +// If a peer object is returned, peerShutdown() must be called +// to cleanup the object +/////////////////////////////////////////////////////////////// +PEER peerInitialize +( + PEERCallbacks * callbacks // Global callbacks. +); + +// This gets called when the connection attempt finishes. +///////////////////////////////////////////////////////// +typedef void (* peerConnectCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + int failureReason, // If failure, the reason for it (PEER_DISCONNECTED, etc.) + void * param // User-data. +); + +// This gets called if there is an error with +// the nickname while connecting. +// Call peerRetryWithNick() to try another nick. If it +// is called with a NULL nick, then the connect will be +// stopped, and peerConnectCallback will be called with +// failure. +// The suggested nicks are only provided if type +// is PEER_INVALID_UNIQUENICK. +///////////////////////////////////////////////////////// +typedef void (* peerNickErrorCallback) +( + PEER peer, // The peer object. + int type, // The type of nick error (PEER_IN_USE, PEER_INVALID, etc.) + const gsi_char * nick, // The bad nick. + int numSuggestedNicks, // The number of suggested nicks. + const gsi_char ** suggestedNicks, // The array of nicks. + void * param // User-data. +); + +// This connects a peer object to a chat server. +// Call peerDisconnect() to close the connection. +// A title must be set with peerSetTitle before connecting. +/////////////////////////////////////////////////////////// +void peerConnect +( + PEER peer, // The peer object. + const gsi_char * nick, // The nick to connect with. + int profileID, // The profileID, or 0 if no profileID. + peerNickErrorCallback nickErrorCallback, // Called if nick error. + peerConnectCallback connectCallback, // Called on complete. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Same as peerConnect, but also authenticates using a uniquenick +// and password or an email, profilenick, and password. +///////////////////////////////////////////////////////////////// +void peerConnectLogin +( + PEER peer, // The peer object. + int namespaceID, // The namespace in which to login. + const gsi_char * email, // The email to login with. + const gsi_char * profilenick, // The profile to login with. + const gsi_char * uniquenick, // The uniquenick to login with. + const gsi_char * password, // The password for the login account. + peerNickErrorCallback nickErrorCallback, // Called if nick error. + peerConnectCallback connectCallback, // Called on complete. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Same as peerConnect, but also authenticates using an authtoken +// and partnerchallenge from a partner account system. +///////////////////////////////////////////////////////////////// +void peerConnectPreAuth +( + PEER peer, // The peer object. + const gsi_char * authtoken, // The authtoken for this login. + const gsi_char * partnerchallenge, // The partner challenge for this login. + peerNickErrorCallback nickErrorCallback, // Called if nick error. + peerConnectCallback connectCallback, // Called on complete. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// If peerNickErrorCallback is called, call this to +// try and continue the connection with a new nickname. +// If there is an error with this nick, the +// peerNickErrCallback will be called again. +/////////////////////////////////////////////////////// +void peerRetryWithNick +( + PEER peer, + const gsi_char * nick +); + +// Register a uniquenick. +// Should be called in response to the peerNickErrorCallback being called +// with a type of PEER_UNIQUENICK_EXPIRED or PEER_NO_UNIQUENICK. +// If the uniquenick cannot be registered, the peerNickErrorCallback will +// be called again with a type of PEER_IN_USE or PEER_INVALID. +///////////////////////////////////////////////////////////////////////// +void peerRegisterUniqueNick +( + PEER peer, + int namespaceID, + const gsi_char * uniquenick, + const gsi_char * cdkey +); + +// Returns true if peer is connected. +// PANTS|09.11.00 +///////////////////////////////////// +PEERBool peerIsConnected(PEER peer); + +// Sets the current title. +// A title must be set before connecting. +// Returns PEERFalse if an error, or PEERTrue if success. +// For most games the title/qrSecretKey and +// sbName/sbSecretKey pairs will be the same. +///////////////////////////////////////////////////////// +PEERBool peerSetTitle +( + PEER peer, // The peer object. + const gsi_char * title, // The title to make current (ie., ut, gmtest). + const gsi_char * qrSecretKey, // The queryreporting secret key. + const gsi_char * sbName, // The serverbrowsing name. + const gsi_char * sbSecretKey, // The serverbrowsing secret key. + int sbGameVersion, // The version of the game doing the browsing. + int sbMaxUpdates, // The maximum number of concurent updates (10-15 for modem users, 20-30 for high-bandwidth). + PEERBool natNegotiate, // PEERTrue if the title supports GameSpy's NAT-negotiation technology (or another similar technology). + PEERBool pingRooms[NumRooms], // To do pings int a room, set it to PEERTrue. + PEERBool crossPingRooms[NumRooms] // To do cross-pings in a room, set it to PEERTrue. +); + +// Resets peer to no title. +/////////////////////////// +void peerClearTitle(PEER peer); + +// Gets the currently set title. +// Returns NULL if no title is set. +/////////////////////////////////// +const gsi_char * peerGetTitle(PEER peer); + +// Disconnect peer from the chat server. +//////////////////////////////////////// +void peerDisconnect(PEER peer); + +// Shutdown peer. +// The peer object should not be used again after this call. +//////////////////////////////////////////////////////////// +void peerShutdown(PEER peer); + +// Let's peer think. +// This should be called at least every ~10ms, +// typically, within the program's main loop. +////////////////////////////////////////////// +void peerThink(PEER peer); + +// Get the chat object associated with this peer object. +// This returns NULL if peer isn't connected. +//////////////////////////////////////////////////////// +CHAT peerGetChat(PEER peer); + +// Get the local user's nickname. +///////////////////////////////// +const gsi_char * peerGetNick(PEER peer); + +// Replaces any invalid characters in nick with underscores. +//////////////////////////////////////////////////////////// +void peerFixNick +( + gsi_char * newNick, + const gsi_char * oldNick +); + +// Removes the namespace extension from a chat unique nick. +// Returns the nick if it ended with the extension. +// Otherwise returns NULL. +/////////////////////////////////////////////////////////// +const gsi_char * peerTranslateNick +( + gsi_char * nick, // The nick to translate + const gsi_char * extension // The extension to be removed. +); + +// Gets the local IP address. If called before +// the peer object is connected, will return 0. +// DEPRECATED - Use peerGetPublicIP. +/////////////////////////////////////////////// +#define peerGetLocalIP peerGetPublicIP + +// Gets the local public IP address. If called before +// the peer object is connected, will return 0. +////////////////////////////////////////////////////// +unsigned int peerGetPublicIP(PEER peer); + +// Gets the local private IP address. +///////////////////////////////////// +unsigned int peerGetPrivateIP(PEER peer); + +// Gets the local userID. +// Only valid if connected with peerConnectLogin or peerConnectPreAuth. +/////////////////////////////////////////////////////////////////////// +int peerGetUserID(PEER peer); + +// Gets the local profileID. +// Only valid if connected with peerConnectLogin or peerConnectPreAuth. +/////////////////////////////////////////////////////////////////////// +int peerGetProfileID(PEER peer); + +// This gets called when an attempt to change nicks is finished. +//////////////////////////////////////////////////////////////// +typedef void (* peerChangeNickCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + const gsi_char * oldNick, // The old nickname. + const gsi_char * newNick, // The new nickname. + void * param // User-data. +); + +// Changes the user's nickname. +/////////////////////////////// +void peerChangeNick +( + PEER peer, // The peer object. + const gsi_char * newNick, // The nickname to which to change. + peerChangeNickCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Causes Peer to not automatically leave the +// room the next time peerSetTitle or peerClearTitle +// is called. No effect if a title isn't set. When +// the next title is set, the flag is cleared. +// Only TitleRoom is currently supported. +// This function is normally not needed. +//////////////////////////////////////////////////// +void peerStayInRoom +( + PEER peer, // The peer object. + RoomType roomType // Only TitleRoom is currently supproted. +); + +// Turns quiet mode on/off. +/////////////////////////// +void peerSetQuietMode +( + PEER peer, // The peer object. + PEERBool quiet // If PEERTrue, enable quiet mode. +); + +// Sets the away mode. +// If an empty string or NULL, away mode is off. +// If a valid string, away mode is on. +//////////////////////////////////////////////// +void peerSetAwayMode +( + PEER peer, // The peer object. + const gsi_char * reason // The away reason. If NULL or "", not away. +); + +// When using peerStartReportingWithSocket or peerCreateStagingRoomWithSocket, +// any qureries received on the sockets need to be passed to the SDK. Pass +// the data using this function. +////////////////////////////////////////////////////////////////////////////// +void peerParseQuery +( + PEER peer, // The peer object. + char * query, // String of query data. + int len, // The length of the string, not including the NUL. + struct sockaddr * sender // The address the query was received from. +); + +// This gets called when an attempt to authenticate a CD key is finished. +// If the result is 1, the CD key was authenticated. Otherwise, the CD +// key was not authenticated. +///////////////////////////////////////////////////////////////////////// +typedef void (* peerAuthenticateCDKeyCallback) +( + PEER peer, // The peer object. + int result, // 1 if authenticated, otherwise not authenticated. + const gsi_char * message, // A string representing the result. + void * param // User-data. +); + +// Sends a CD Key to the chat server for authentication. +// The callback gets called when the chat server responds. +// This call is required by some games to enter chat rooms. +// It must be called after connecting to the chat server, but +// before entering a room. +///////////////////////////////////////////////////////////// +void peerAuthenticateCDKey +( + PEER peer, // The peer object. + const gsi_char * cdkey, // The CD key to authenticate. + peerAuthenticateCDKeyCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Sends a nat-negotiate cookie to a server. +//////////////////////////////////////////// +void peerSendNatNegotiateCookie +( + PEER peer, // The peer object. + unsigned int ip, // IP (network byte order) of the server to send the cookie to. + unsigned short port, // Port (host byte order) of the server to send the cookie to. + int cookie // The cookie to send. +); + +// Sends a client message to a QR2 server. +////////////////////////////////////////// +void peerSendMessageToServer +( + PEER peer, + unsigned int ip, + unsigned short port, + const char * data, + int len +); + +// If always is PEERTrue, then players' IP and profile ID will +// always be requested when joining a room. This info is normally +// only requested if pings are set in a room. Use this function +// to always request the information. +////////////////////////////////////////////////////////////////// +void peerAlwaysGetPlayerInfo +( + PEER peer, // The peer object. + PEERBool always // If true, always get player info. +); + +/********** +** ROOMS ** +**********/ +// This gets called when an attempt to join or create a room has finished. +////////////////////////////////////////////////////////////////////////// +typedef void (* peerJoinRoomCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + PEERJoinResult result, // The result of the attempt. + RoomType roomType, // The type of room joined/created. + void * param // User-data. +); + +// Joins the currently selected title's title room. +/////////////////////////////////////////////////// +void peerJoinTitleRoom +( + PEER peer, // The peer object. + const gsi_char password[PEER_PASSWORD_LEN], // An optional password, normally NULL. + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Joins a group room. +// The groupID comes from the peerListGroupRoomsCallback. +///////////////////////////////////////////////////////// +void peerJoinGroupRoom +( + PEER peer, // The peer object. + int groupID, // The ID for the group to join. + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Sets the group ID. +// Used to get a list of games in a group room and/or report or create a game +// in a particular group, without actually being in that group room. This is +// not needed if already using peerJoinGroupRoom. +// Setting a group ID of 0 is equivalent to leaving the group room. +///////////////////////////////////////////////////////////////////////////// +void peerSetGroupID +( + PEER peer, // The peer object. + int groupID // The group ID to set +); + +// Returns the current group ID. +// The group ID is set with either peerJoinGroupRoom or peerSetGroupID. +/////////////////////////////////////////////////////////////////////// +int peerGetGroupID +( + PEER peer // The peer object. +); + +// Joins a staging room. +// server is one of the server objects passed to peerListingGamesCallback(). +// This call will only work if staging==PEERTrue for the server. +// PANTS|09.11.00 - The password is only needed for passworded rooms. +// PANTS|03.15.01 - No longer requires you to be actively listing games. +//////////////////////////////////////////////////////////////////////////// +void peerJoinStagingRoom +( + PEER peer, // The peer object. + SBServer server, // The server passed into peerlistingGamesCallback(). + const gsi_char password[PEER_PASSWORD_LEN], // The password of the room being joined. Can be NULL or "". + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Joins a staging room by its channel name. +// Same as peerJoinStagingRoom, except it takes the staging room's +// channel name instead of an SBServer object. Also, when the +// peerGameStartedCallback is called, the server paramter passed to +// it will be NULL. +/////////////////////////////////////////////////////////////////// +void peerJoinStagingRoomByChannel +( + PEER peer, // The peer object. + const gsi_char * channel, // The channel to join. + const gsi_char password[PEER_PASSWORD_LEN], // The password of the room being joined. Can be NULL or "". + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Creates a new staging room, with the local player hosting. +// PANTS|09.11.00 - If the password parameter is not NULL +// or "", this will create a passworded room. The same +// case-sensitive password needs to be passed into +// peerJoinStagingRoom() for other player's to join the room. +// PANTS|09.11.00 - The staging room will be reported as part +// of whatever group room the local player was in when the +// room was created. Leaving the group room will not affect +// what group the staging room is reported as part of. +///////////////////////////////////////////////////////////// +void peerCreateStagingRoom +( + PEER peer, // The peer object. + const gsi_char * name, // The name of the room. + int maxPlayers, // The max number of players allowed in the room. + const gsi_char password[PEER_PASSWORD_LEN], // An optional password for the staging room + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Same as peerCreateStagingRoom, but uses the provided socket for +// sending heartbeats and query replies. This allows the game +// to share a socket with the peer SDK, which can make hosting +// games behind a NAT proxy possible. +////////////////////////////////////////////////////////////////// +void peerCreateStagingRoomWithSocket +( + PEER peer, // The peer object. + const gsi_char * name, // The name of the room. + int maxPlayers, // The max number of players allowed in the room. + const gsi_char password[PEER_PASSWORD_LEN], // An optional password for the staging room + SOCKET socket, // The socket to be used for reporting. + unsigned short port, // The local port to which the socket is bound. + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Leave a room. +// PANTS|09.11.00 - You can now leave a group room +// without being forcibly removed from a staging room. +////////////////////////////////////////////////////// +void peerLeaveRoom +( + PEER peer, // The peer object. + RoomType roomType, // The room you want to leave (TitleRoom, GroupRoom, or StagingRoom). + const gsi_char * reason // The reason the player is leaving (can be NULL). PANTS|03.13.01 +); + +// Gets called once for each group room when listing group rooms. +// After this has been called for each group room, it will be +// called one more time with groupID==0 and name==NULL. +///////////////////////////////////////////////////////////////// +typedef void (* peerListGroupRoomsCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + int groupID, // A unique ID for this group. + SBServer server, // The server object for this group room. + const gsi_char * name, // The group room's name. + int numWaiting, // The number of players in the room. + int maxWaiting, // The maximum number of players allowed in the room. + int numGames, // The number of games either staging or running in the group. + int numPlaying, // The total number of players in games in the group. + void * param // User-data. +); + +// List all the groups rooms for the currently set title. +// The fields parameter allows you to request extra info +// on each group room. +///////////////////////////////////////////////////////// +void peerListGroupRooms +( + PEER peer, // The peer object. + const gsi_char * fields, // A backslash delimited list of fields. + peerListGroupRoomsCallback callback, // Called for each group room. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Called with info on games being listed. +// Used to maintain a list of running games and staging rooms. +// The server object is a unique way of identifying each game. +// It can also be used with the calls in the "SBServer Object Functions" section +// of sb_serverbrowsing.h to find out more info about the server. +// If staging==PEERTrue, the game hasn't started yet, it's still in the staging room +// use peerJoinStagingRoom() to join the staging room, or if staging==peerfalse +// use the server object to get the game's IP and port to join with. +// During the _intial_ listing of games, progress is the percentage (0-100) of the +// games that have been added. Once the initial listing is completed, +// progress will always be 100. +// PANTS|09.11.00 - The "password" key will be set to 1 for games that are +// passworded. This can be checked with ServerGetIntValue(server, "password", 0). +///////////////////////////////////////////////////////////////////////////////////// +typedef void (* peerListingGamesCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + const gsi_char * name, // The name of the game being listed. + SBServer server, // The server object for this game. + PEERBool staging, // If PEERTrue, this is a staging room and not a running game. + int msg, // The type of message this is. + // PEER_CLEAR: + // Clear the list. This has the same effect as if this was called + // with PEER_REMOVE for every server listed. + // PEER_ADD: + // This is a new server. Add it to the list. + // PEER_UPDATE: + // This server is already on the list, and its been updated. + // PEER_REMOVE: + // Remove this server from the list. The server object for this server + // should not be used again after this callback returns. + int progress, // The percent of servers that have been added. + void * param // User-data. +); + +// Start listing the currently running games and staging rooms. +// This is used to maintain a list that can presented to the user, +// so they can pick a game (or staging room) to join. +// Games and staging rooms are filtered based on what group the local +// user is in. If the local user isn't in a group, then only games +// and staging rooms that aren't part of any group are listed. +// The fields determine which info to request from the server. These +// must be registered QR2 keys (see qr2regkeys.h). HOSTNAME_KEY and +// GAMEMODE_KEY are automatically requested by Peer, so do not need +// to included. +///////////////////////////////////////////////////////////////////// +void peerStartListingGames +( + PEER peer, // The peer object. + const unsigned char * fields, // An array of registered QR2 keys to request from servers. + int numFields, // The number of keys in the fields array. + const gsi_char * filter, // A SQL-like rule filter. + peerListingGamesCallback callback, // Called when finished. + void * param // Passed to the callback. +); + +// Stop games from being listed. This does NOT clear the games list. +// So all games listed are still considered valid. They stay valid +// until either another call to peerStartListingGames or until the +// title is cleared (or a new one set). +//////////////////////////////////////////////////////////////////// +void peerStopListingGames +( + PEER peer // The peer object. +); + +// Queries the game server for the latest information. +// If fullUpdate is PEERTrue, all of the keys will be requested. +// If fullUpdate is PEERFalse, only the keys passed to +// peerStartListingGames as the fields parameter will be requested. +/////////////////////////////////////////////////////////////////// +void peerUpdateGame +( + PEER peer, // The peer object. + SBServer server, // The server object for the game to update. + PEERBool fullUpdate // If true, get all of the game's keys. +); + +// 08-26-2004 Added by Saad Nader +// Queries the game server for the latest information via the master +// server. +// If fullUpdate is PEERTrue, all of the keys will be requested. +// If fullUpdate is PEERFalse, only the keys passed to +// peerStartListingGames as the fields parameter will be requested. +/////////////////////////////////////////////////////////////////// +void peerUpdateGameByMaster +( + PEER peer, // The peer object. + SBServer server, // The server object for the game to update. + PEERBool fullUpdate // If true, get all of the game's keys. +); + +// Send a ping to the server +// ICMP echo reply will be sent as response. The firewall +// or NAT between server and internet does not have to accept +// ICMP echo requests. +/////////////////////////////////////////////////////////////////// +void peerUpdateGamePing(PEER peer, SBServer server); + +// Send a message to a room. +//////////////////////////// +void peerMessageRoom +( + PEER peer, // The peer object. + RoomType roomType, // The room to send the message to. + const gsi_char * message, // The message. + MessageType messageType // The type of message. +); + +// Send a UTM to a room. +//////////////////////// +void peerUTMRoom +( + PEER peer, // The peer object. + RoomType roomType, // The room to send the UTM to. + const gsi_char * command, // The command. + const gsi_char * parameters, // The UTM's parameters. + PEERBool authenticate // If true, the server will authenticate this UTM (should normally be false). +); + +// Set a password in a room you're hosting. +// The only roomType currently supported is StagingRoom. +// This will only work if the player is hosting the room. +// If password is NULL or "", the password will be cleared. +/////////////////////////////////////////////////////////// +void peerSetPassword +( + PEER peer, // The peer object. + RoomType roomType, // The room in which to set the password. + const gsi_char password[PEER_PASSWORD_LEN] // The password to set. +); + +// Set the name of a room you're hosting. +// The only roomType currently supported is StagingRoom. +// PANTS|09.11.00 +//////////////////////////////////////////////////////// +void peerSetRoomName +( + PEER peer, // The peer object. + RoomType roomType, // The room in which to set the name. + const gsi_char * name // The new name +); + +// Get a room's name (the channel's title). +// Returns NULL if not in the room. +/////////////////////////////////////////// +const gsi_char * peerGetRoomName +( + PEER peer, // The peer object. + RoomType roomType // The room to get the name for. +); + +// Get's the chat channel associated with the room. +// Returns NULL if not in the room. +/////////////////////////////////////////////////// +const gsi_char * peerGetRoomChannel +( + PEER peer, // The peer object. + RoomType roomType // The room to get the channel for. +); + +// Returns PEERTrue if in the room. +/////////////////////////////////// +PEERBool peerInRoom +( + PEER peer, // The peer object. + RoomType roomType // The room to check for. +); + +// Use this channel for the title room for the currently set title. +// This function is normally not needed. +/////////////////////////////////////////////////////////////////// +void peerSetTitleRoomChannel +( + PEER peer, // The peer object. + const gsi_char * channel // The channel to use for the title room. +); + +// Use this to get the SBServer object for the staging room host. +// This will return NULL if the local player is not in a staging room, +// is the staging room host, or joined using peerJoinStagingRoomByChannel. +////////////////////////////////////////////////////////////////////////// +SBServer peerGetHostServer(PEER peer); + +// Update the maximum number of players for a staging room +///////////////////////////////////////////////////////// +void peerSetStagingRoomMaxPlayers +( + PEER peer, + int maxPlayers +); + +/************ +** PLAYERS ** +************/ +// Called for each player in a room being enumerated, and once +// when finished, with index==-1 and nick==NULL. +////////////////////////////////////////////////////////////// +typedef void (* peerEnumPlayersCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + RoomType roomType, // The room whose players are being enumerated. + int index, // The index of the player (0 to (N - 1)). -1 when finished. + const gsi_char * nick, // The nick of the player. + int flags, // This player's flags (see #define's above). PANTS|03.12.01 + void * param // User-data. +); + +// Enumerates through the players in a room. +//////////////////////////////////////////// +void peerEnumPlayers +( + PEER peer, // The peer object. + RoomType roomType, // The room to enum the players in. + peerEnumPlayersCallback callback, // Called when finished. + void * param // Passed to callback. +); + +// Send a message to a player. +////////////////////////////// +void peerMessagePlayer +( + PEER peer, // The peer object. + const gsi_char * nick, // The nick of the player to send the message to. + const gsi_char * message, // The message to send. + MessageType messageType // The type of message. +); + +// Send a UTM to a player. +////////////////////////// +void peerUTMPlayer +( + PEER peer, // The peer object. + const gsi_char * nick, // The nick of the player to send the UTM to. + const gsi_char * command, // The command. + const gsi_char * parameters, // The UTM's parameters. + PEERBool authenticate // If true, the server will authenticate this UTM (should normally be false). +); + +// Kick a player from a room. +// Can only be called by a player with host privileges. +/////////////////////////////////////////////////////// +void peerKickPlayer +( + PEER peer, // The peer object. + RoomType roomType, // The room to kick the player from. + const gsi_char * nick, // The nick of the player to kick. + const gsi_char * reason // An optional reason for kicking the player +); + +// Gets a player's ping (between the local machine and the player's machine). +// Returns PEERFalse if we don't have or can't get the player's ping. +///////////////////////////////////////////////////////////////////////////// +PEERBool peerGetPlayerPing +( + PEER peer, // The peer object. + const gsi_char * nick, // The player to get the ping for. + int * ping // The player's ping is stored here, if we have it. +); + +// Gets the cross-ping between 2 players. +// Returns PEERFalse if we don't have or can't get the player's cross-ping. +/////////////////////////////////////////////////////////////////////////// +PEERBool peerGetPlayersCrossPing +( + PEER peer, // The peer object. + const gsi_char * nick1, // The first player. + const gsi_char * nick2, // The second player. + int * crossPing // The cross-ping is stored here, if we have it. +); + +// Peer already automatically pings all players that are in ping rooms. +// This function does a one-time ping of a remote player in a non-ping room. +// However pings must be enabled in at least one room for this to work, +// otherwise Peer will not open the UDP ping socket. +// Also, peerAlwaysGetPlayerInfo() must be enabled so that Peer has IPs for +// players that are only in non-ping rooms. +//////////////////////////////////////////////////////////////////////////// +PEERBool peerPingPlayer +( + PEER peer, // The peer object. + const gsi_char * nick // The player to ping. +); + +// Gets a player's info immediately. +// Returns PEERFalse if the info is no available. +///////////////////////////////////////////////// +PEERBool peerGetPlayerInfoNoWait +( + PEER peer, + const gsi_char * nick, + unsigned int * IP, + int * profileID +); + +// Called as a result of a call to peerGetPlayerInfo(). +/////////////////////////////////////////////////////// +typedef void (* peerGetPlayerInfoCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + const gsi_char * nick, // The player's nick. + unsigned int IP, // The player's IP, in network byte order. + int profileID, // The player's profile ID. + void * param // User-data. +); + +// Called to get a player's IP and profile ID. +////////////////////////////////////////////// +void peerGetPlayerInfo +( + PEER peer, // The peer object. + const gsi_char * nick, // The player's nick. + peerGetPlayerInfoCallback callback, // Called when finished. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Called as a result of a call to peerGetPlayerProfileID(). +//////////////////////////////////////////////////////////// +typedef void (* peerGetPlayerProfileIDCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + const gsi_char * nick, // The player's nick. + int profileID, // The player's profile ID. + void * param // User-data. +); + +// Called to get a player's profile ID. +// DEPRECATED - Use peerGetPlayerInfo. +/////////////////////////////////////// +void peerGetPlayerProfileID +( + PEER peer, // The peer object. + const gsi_char * nick, // The player's nick. + peerGetPlayerProfileIDCallback callback, // Called when finished. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Called as a result of a call to peerGetPlayerIP(). +///////////////////////////////////////////////////// +typedef void (* peerGetPlayerIPCallback) +( + PEER peer, // The peer object. + PEERBool success, // PEERTrue if success, PEERFalse if failure. + const gsi_char * nick, // The player's nick. + unsigned int IP, // The player's IP, in network byte order. PANTS|09.11.00 - was unsigned long + void * param // User-data. +); + +// Called to get a player's IP. +// DEPRECATED - Use peerGetPlayerInfo. +////////////////////////////////////// +void peerGetPlayerIP +( + PEER peer, // The peer object. + const gsi_char * nick, // The player's nick. + peerGetPlayerIPCallback callback, // Called when finished. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Checks if a player is a host (has ops). +// Returns PEERTrue if yes, PEERFalse if no. +//////////////////////////////////////////// +PEERBool peerIsPlayerHost +( + PEER peer, // The peer object. + const gsi_char * nick, // The player's nick. + RoomType roomType // The room to check in. +); + +// Gets a player's flags in a room. Returns PEERFalse if +// the local player or this player is not in the room. +///////////////////////////////////////////////////////// +PEERBool peerGetPlayerFlags +( + PEER peer, + const gsi_char * nick, + RoomType roomType, + int * flags +); + +/********* +** GAME ** +*********/ +/**************** Disabled *************************/ +// Returns the qr2 record used to report +// Primarily used for CD Key integration to prevent +// game intrusion and and post staging hacks +//////////////////////////////////////////////////// +//qr2_t peerGetReportingRecord(PEER peer); + +// Sets the local player's ready state. +// PEERTrue if ready, PEERFalse if not ready. +///////////////////////////////////////////// +void peerSetReady +( + PEER peer, // The peer object. + PEERBool ready // Ready or not. +); + +// Gets a player's ready state. +/////////////////////////////// +PEERBool peerGetReady +( + PEER peer, // The peer object. + const gsi_char * nick, // The player's nick. + PEERBool * ready // The player's ready state gets stored in here. +); + +// Checks if all the player's in the staging room are ready. +//////////////////////////////////////////////////////////// +PEERBool peerAreAllReady +( + PEER peer // The peer object. +); + +// Called only by a staging room host to start the game. +// All the other people in the staging room will have their +// peerGameStartedCallback() called. +// The message gets passed to everyone in the peerGameStartedCallback(), and +// can be used to pass information such as a special port or password. +// If (reportingOptions & PEER_STOP_REPORTING), Peer will stop game reporting, +// so the program is responsible for any server reporting. +// If !(reportingOptions & PEER_STOP_REPORTING), Peer will continue doing +// game reporting, and calling the program-supplied callbacks. If +// (reportingOptions & PEER_REPORT_INFO), all server keys will still be +// reported. If (reportingOptions & PEER_REPORT_PLAYERS), all player keys +// will still be reported. +///////////////////////////////////////////////////////////////////////////// +void peerStartGame +( + PEER peer, // The peer object. + const gsi_char * message, // A message to send to everyone. + int reportingOptions // Bitfield flags used to set reporting options. +); + +// Starts server reporting, without creating a staging room. +// Call peerStopGame() to stop reporting. +//////////////////////////////////////////////////////////////// +PEERBool peerStartReporting +( + PEER peer // The peer object. +); + +// Same as peerStartReporting, but uses the provided socket for +// sending heartbeats and query replies. This allows the game +// to share a socket with the peer SDK, , which can make hosting +// games behind a NAT proxy possible. +//////////////////////////////////////////////////////////////// +PEERBool peerStartReportingWithSocket +( + PEER peer, // The peer object. + SOCKET socket, // The socket to be used for reporting. + unsigned short port // The local port to which the socket is bound. +); + +// Mark the local player as playing. +// Use this if the player is in a game not +// started by peer, but he should be marked as playing. +/////////////////////////////////////////////////////// +void peerStartPlaying +( + PEER peer // The peer object. +); + +// Check to see if the local player is playing. +/////////////////////////////////////////////// +PEERBool peerIsPlaying +( + PEER peer // The peer object. +); + +// Needs to be called by the host when the game has stopped. +//////////////////////////////////////////////////////////// +void peerStopGame +( + PEER peer // The peer object. +); + +// Call this when hosting a staging room or a game to force peer +// to report the game again. An example of when to call this is +// when a player joins or leaves a game. +// PANTS|09.11.00 +/////////////////////////////////////////////////////////////////// +void peerStateChanged +( + PEER peer // The peer object. +); + +/********* +** KEYS ** +*********/ +// Set global keys on the local player. +/////////////////////////////////////// +void peerSetGlobalKeys +( + PEER peer, // The peer object. + int num, // The number of keys to set. + const gsi_char ** keys, // The keys to set. + const gsi_char ** values // The values for the keys. +); + +// Called with a player's global keys. +////////////////////////////////////// +typedef void (* peerGetGlobalKeysCallback) +( + PEER peer, // The peer object. + PEERBool success, // If PEERFalse, unable to get the keys. + const gsi_char * nick, // The player the keys are for. + int num, // The number of keys. + const gsi_char ** keys, // The keys got. + const gsi_char ** values, // The values for the keys. + void * param // User-data. +); + +// Get a player's global keys. +////////////////////////////// +void peerGetPlayerGlobalKeys +( + PEER peer, // The peer object. + const gsi_char * nick, // The player to get the keys for. + int num, // The number of keys. + const gsi_char ** keys, // The keys to get. + peerGetGlobalKeysCallback callback, // Called with the keys. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Get the global keys for all players in a room we're in. +////////////////////////////////////////////////////////// +void peerGetRoomGlobalKeys +( + PEER peer, // The peer object. + RoomType roomType, // The room to get the keys in. + int num, // The number of keys. + const gsi_char ** keys, // The keys to get. + peerGetGlobalKeysCallback callback, // Called with the keys. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Set the room keys for a player. +// Use NULL or "" to set keys on the room itself. +///////////////////////////////////////////////// +void peerSetRoomKeys +( + PEER peer, // The peer object. + RoomType roomType, // The room to set the keys in. + const gsi_char * nick, // The player to set the keys on (NULL or "" for the room). + int num, // The number of keys. + const gsi_char ** keys, // The keys to set. + const gsi_char ** values // The values to set. +); + +// Called with a player's room keys. +//////////////////////////////////// +typedef void (* peerGetRoomKeysCallback) +( + PEER peer, // The peer object. + PEERBool success, // If PEERFalse, unable to get the keys. + RoomType roomType, // The room the keys are in. + const gsi_char * nick, // The player the keys are for, or NULL for the room. + int num, // The number of keys. + gsi_char ** keys, // The keys. + gsi_char ** values, // The values for the keys. + void * param // User-data. +); + +// Get the room keys for either a single player of an entire room. +// Use "*" for the player to get the keys for the entire room. +// Use NULL or "" for the player to get keys on the room itself. +////////////////////////////////////////////////////////////////// +void peerGetRoomKeys +( + PEER peer, // The peer object. + RoomType roomType, // The room to get the keys in. + const gsi_char * nick, // The player to get the keys for. + int num, // The number of keys. + const gsi_char ** keys, // The keys to get. + peerGetRoomKeysCallback callback, // Called with the keys. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Set the global watch keys for a room type. +// If addKeys is set to PEERTrue, the keys will be +// added to the current global watch keys for this room. +// If addKeys is PEERFalse, these will replace any existing +// global watch keys for this room. +// When entering a room of the given type, peer will get and +// cache these keys for all players in the room. +//////////////////////////////////////////////////////////// +void peerSetGlobalWatchKeys +( + PEER peer, // The peer object. + RoomType roomType, // The type of room to set the watch keys for. + int num, // The number of keys. + const gsi_char ** keys, // The keys to watch for. + PEERBool addKeys // If PEERTrue, add these keys to the existing global watch keys for this room. +); + +// Set the room watch keys for a room type. +// If addKeys is set to PEERTrue, the keys will be +// added to the current room watch keys for this room. +// If addKeys is PEERFalse, these will replace any existing +// room watch keys for this room. +// When entering a room of the given type, peer will get and +// cache these keys for all players in the room. +//////////////////////////////////////////////////////////// +void peerSetRoomWatchKeys +( + PEER peer, // The peer object. + RoomType roomType, // The type of room to set the watch keys for. + int num, // The number of keys. + const gsi_char ** keys, // The keys to watch for. + PEERBool addKeys // If PEERTrue, add these keys to the existing room watch keys for this room. +); + +// Get the global watch key for a particular player. +// If the key isn't cached locally (either because it isn't +// a watch key or just isn't yet known), NULL will be returned. +// If the key is empty, "" will be returned. +/////////////////////////////////////////////////////////////// +const gsi_char * peerGetGlobalWatchKey +( + PEER peer, // The peer object. + const gsi_char * nick, // The player to get the key for. + const gsi_char * key // The key to get. +); + +// Get the room watch key for a particular player in a room. +// If the key isn't cached locally (either because it isn't +// a watch key or just isn't yet known), NULL will be returned. +// If the key is empty, "" will be returned. +/////////////////////////////////////////////////////////////// +const gsi_char * peerGetRoomWatchKey +( + PEER peer, // The peer object. + RoomType roomType, // The room to get the key in. + const gsi_char * nick, // The player to get the key for. + const gsi_char * key // The key to get. +); + +/************** +** AUTOMATCH ** +**************/ + +// Called during a automatch attempt to inform the app of the status. +///////////////////////////////////////////////////////////////////// +typedef void (* peerAutoMatchStatusCallback) +( + PEER peer, // The peer object. + PEERAutoMatchStatus status, // The current status. + void * param // User-data. +); + +// This callback may be called during the automatch attempt. +// Peer uses the callback to ask the application how well a particular +// match fits what the user is looking for. The application checks the match +// and then returns a rating for it. If the rating is <=0 the match is +// considered unsuitable and Peer will not attempt that match. If the rating +// is >0 this match is considered suitable. The higher the rating, up to a +// maximum of INT_MAX, the more suitable the match. The exact scheme used +// to generate a rating is entirely up to the application. The match ratings +// are used by Peer both to check if a particular match meets the user's wishes +// and to compare matches to each other. Peer will first try to match with the +// highest rated possible match, then the second highest, etc. +/////////////////////////////////////////////////////////////////////////////// +typedef int (* peerAutoMatchRateCallback) +( + PEER peer, // The peer object. + SBServer match, // Possible match to rate. + void * param // User-data. +); + +// Used to start a automatch attempt. +// The filter contains any hard criteria. This is used to narrow down the list +// of possible matches to those the user might consider. +// The status callback will be called as the attempt progresses, and the rate +// callback will be used to sort possible matches. +///////////////////////////////////////////////////////////////////////////////// +void peerStartAutoMatch +( + PEER peer, // The peer object. + int maxPlayers, // The total number of players to match (including the local player). + const gsi_char * filter, // Hard criteria - filters out servers. + peerAutoMatchStatusCallback statusCallback, // Called as the attempt status changes. + peerAutoMatchRateCallback rateCallback, // Used to rate possible matches. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Same as peerStartAutoMatch, but uses the provided socket for +// sending heartbeats and query replies. This allows the game +// to share a socket with the peer SDK, which can make hosting +// games behind a NAT proxy possible. +//////////////////////////////////////////////////////////////// +void peerStartAutoMatchWithSocket +( + PEER peer, // The peer object. + int maxPlayers, // The total number of players to match (including the local player). + const gsi_char * filter, // Hard criteria - filters out servers. + SOCKET socket, // The socket to be used for reporting. + unsigned short port, // The local port to which the socket is bound. + peerAutoMatchStatusCallback statusCallback, // Called as the attempt status changes. + peerAutoMatchRateCallback rateCallback, // Used to rate possible matches. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Stops an automatch attempt in progress. +// The user will not automatically be removed from any staging room he is in. +///////////////////////////////////////////////////////////////////////////// +void peerStopAutoMatch(PEER peer); + +// Checks if a automatch attempt is in progress. +//////////////////////////////////////////////// +PEERBool peerIsAutoMatching(PEER peer); + +// Gets the current status of the automatch attempt. +// If no attempt is in progress, PEERFailed will be returned. +///////////////////////////////////////////////////////////// +PEERAutoMatchStatus peerGetAutoMatchStatus(PEER peer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerAscii.h b/code/gamespy/Peer/peerAscii.h new file mode 100644 index 00000000..8ed2352e --- /dev/null +++ b/code/gamespy/Peer/peerAscii.h @@ -0,0 +1,734 @@ + /* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERASCII_H_ +#define _PEERASCII_H_ + +// PROTOTYPES OF ASCII FUNCTIONS +// The only purpose of this file is to silence the CodeWarrior +// "function not prototyped" warnings when using GSI_UNICODE mode + +#ifdef __cplusplus +extern "C" { +#endif + + +/************ +** GENERAL ** +************/ +// This connects a peer object to a chat server. +// Call peerDisconnect() to close the connection. +// A title must be set with peerSetTitle before connecting. +/////////////////////////////////////////////////////////// +void peerConnectA +( + PEER peer, // The peer object. + const char * nick, // The nick to connect with. + int profileID, // The profileID, or 0 if no profileID. + peerNickErrorCallback nickErrorCallback, // Called if nick error. + peerConnectCallback connectCallback, // Called on complete. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Same as peerConnect, but also authenticates using a uniquenick +// and password or an email, profilenick, and password. +///////////////////////////////////////////////////////////////// +void peerConnectLoginA +( + PEER peer, // The peer object. + int namespaceID, // The namespace in which to login. + const char * email, // The email to login with. + const char * profilenick, // The profile to login with. + const char * uniquenick, // The uniquenick to login with. + const char * password, // The password for the login account. + peerNickErrorCallback nickErrorCallback, // Called if nick error. + peerConnectCallback connectCallback, // Called on complete. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Same as peerConnect, but also authenticates using an authtoken +// and partnerchallenge from a partner account system. +///////////////////////////////////////////////////////////////// +void peerConnectPreAuthA +( + PEER peer, // The peer object. + const char * authtoken, // The authtoken for this login. + const char * partnerchallenge, // The partner challenge for this login. + peerNickErrorCallback nickErrorCallback, // Called if nick error. + peerConnectCallback connectCallback, // Called on complete. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// If peerNickErrorCallback is called, call this to +// try and continue the connection with a new nickname. +// If there is an error with this nick, the +// peerNickErrCallback will be called again. +/////////////////////////////////////////////////////// +void peerRetryWithNickA +( + PEER peer, + const char * nick +); + +// Register a uniquenick. +// Should be called in response to the peerNickErrorCallback being called +// with a type of PEER_UNIQUENICK_EXPIRED or PEER_NO_UNIQUENICK. +// If the uniquenick cannot be registered, the peerNickErrorCallback will +// be called again with a type of PEER_IN_USE or PEER_INVALID. +///////////////////////////////////////////////////////////////////////// +void peerRegisterUniqueNickA +( + PEER peer, + int namespaceID, + const char * uniquenick, + const char * cdkey +); + +// Sets the current title. +// A title must be set before connecting. +// Returns PEERFalse if an error, or PEERTrue if success. +// For most games the title/qrSecretKey and +// sbName/sbSecretKey pairs will be the same. +///////////////////////////////////////////////////////// +PEERBool peerSetTitleA +( + PEER peer, // The peer object. + const char * title, // The title to make current (ie., ut, gmtest). + const char * qrSecretKey, // The queryreporting secret key. + const char * sbName, // The serverbrowsing name. + const char * sbSecretKey, // The serverbrowsing secret key. + int sbGameVersion, // The version of the game doing the browsing. + int sbMaxUpdates, // The maximum number of concurent updates (10-15 for modem users, 20-30 for high-bandwidth). + PEERBool natNegotiate, // PEERTrue if the title supports GameSpy's NAT-negotiation technology (or another similar technology). + PEERBool pingRooms[NumRooms], // To do pings int a room, set it to PEERTrue. + PEERBool crossPingRooms[NumRooms] // To do cross-pings in a room, set it to PEERTrue. +); + +// Gets the currently set title. +// Returns NULL if no title is set. +/////////////////////////////////// +const char * peerGetTitleA(PEER peer); + +// Get the local user's nickname. +///////////////////////////////// +const char * peerGetNickA(PEER peer); + +// Replaces any invalid characters in nick with underscores. +//////////////////////////////////////////////////////////// +void peerFixNickA +( + char * newNick, + const char * oldNick +); + +// Removes the namespace extension from a chat unique nick. +// Returns the nick if it ended with the extension. +// Otherwise returns NULL. +/////////////////////////////////////////////////////////// +const char * peerTranslateNickA +( + char * nick, // The nick to translate + const char * extension // The extension to be removed. +); + +// Changes the user's nickname. +/////////////////////////////// +void peerChangeNickA +( + PEER peer, // The peer object. + const char * newNick, // The nickname to which to change. + peerChangeNickCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Sets the away mode. +// If an empty string or NULL, away mode is off. +// If a valid string, away mode is on. +//////////////////////////////////////////////// +void peerSetAwayModeA +( + PEER peer, // The peer object. + const char * reason // The away reason. If NULL or "", not away. +); + +// When using peerStartReportingWithSocket or peerCreateStagingRoomWithSocket, +// any qureries received on the sockets need to be passed to the SDK. Pass +// the data using this function. +////////////////////////////////////////////////////////////////////////////// +void peerParseQueryA +( + PEER peer, // The peer object. + char * query, // String of query data. + int len, // The length of the string, not including the NUL. + struct sockaddr * sender // The address the query was received from. +); + +// Sends a CD Key to the chat server for authentication. +// The callback gets called when the chat server responds. +// This call is required by some games to enter chat rooms. +// It must be called after connecting to the chat server, but +// before entering a room. +///////////////////////////////////////////////////////////// +void peerAuthenticateCDKeyA +( + PEER peer, // The peer object. + const char * cdkey, // The CD key to authenticate. + peerAuthenticateCDKeyCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +/********** +** ROOMS ** +**********/ +// Joins the currently selected title's title room. +/////////////////////////////////////////////////// +void peerJoinTitleRoomA +( + PEER peer, // The peer object. + const char password[PEER_PASSWORD_LEN], // An optional password, normally NULL. + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Joins a staging room. +// server is one of the server objects passed to peerListingGamesCallback(). +// This call will only work if staging==PEERTrue for the server. +// PANTS|09.11.00 - The password is only needed for passworded rooms. +// PANTS|03.15.01 - No longer requires you to be actively listing games. +//////////////////////////////////////////////////////////////////////////// +void peerJoinStagingRoomA +( + PEER peer, // The peer object. + SBServer server, // The server passed into peerlistingGamesCallback(). + const char password[PEER_PASSWORD_LEN], // The password of the room being joined. Can be NULL or "". + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Joins a staging room by its channel name. +// Same as peerJoinStagingRoom, except it takes the staging room's +// channel name instead of an SBServer object. Also, when the +// peerGameStartedCallback is called, the server paramter passed to +// it will be NULL. +/////////////////////////////////////////////////////////////////// +void peerJoinStagingRoomByChannelA +( + PEER peer, // The peer object. + const char * channel, // The channel to join. + const char password[PEER_PASSWORD_LEN], // The password of the room being joined. Can be NULL or "". + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Creates a new staging room, with the local player hosting. +// PANTS|09.11.00 - If the password parameter is not NULL +// or "", this will create a passworded room. The same +// case-sensitive password needs to be passed into +// peerJoinStagingRoom() for other player's to join the room. +// PANTS|09.11.00 - The staging room will be reported as part +// of whatever group room the local player was in when the +// room was created. Leaving the group room will not affect +// what group the staging room is reported as part of. +///////////////////////////////////////////////////////////// +void peerCreateStagingRoomA +( + PEER peer, // The peer object. + const char * name, // The name of the room. + int maxPlayers, // The max number of players allowed in the room. + const char password[PEER_PASSWORD_LEN], // An optional password for the staging room + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Same as peerCreateStagingRoom, but uses the provided socket for +// sending heartbeats and query replies. This allows the game +// to share a socket with the peer SDK, which can make hosting +// games behind a NAT proxy possible. +////////////////////////////////////////////////////////////////// +void peerCreateStagingRoomWithSocketA +( + PEER peer, // The peer object. + const char * name, // The name of the room. + int maxPlayers, // The max number of players allowed in the room. + const char password[PEER_PASSWORD_LEN], // An optional password for the staging room + SOCKET socket, // The socket to be used for reporting. + unsigned short port, // The local port to which the socket is bound. + peerJoinRoomCallback callback, // Called when finished. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Leave a room. +// PANTS|09.11.00 - You can now leave a group room +// without being forcibly removed from a staging room. +////////////////////////////////////////////////////// +void peerLeaveRoomA +( + PEER peer, // The peer object. + RoomType roomType, // The room you want to leave (TitleRoom, GroupRoom, or StagingRoom). + const char * reason // The reason the player is leaving (can be NULL). PANTS|03.13.01 +); + +// List all the groups rooms for the currently set title. +// The fields parameter allows you to request extra info +// on each group room. +///////////////////////////////////////////////////////// +void peerListGroupRoomsA +( + PEER peer, // The peer object. + const char * fields, // A backslash delimited list of fields. + peerListGroupRoomsCallback callback, // Called for each group room. + void * param, // Passed to the callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Start listing the currently running games and staging rooms. +// This is used to maintain a list that can presented to the user, +// so they can pick a game (or staging room) to join. +// Games and staging rooms are filtered based on what group the local +// user is in. If the local user isn't in a group, then only games +// and staging rooms that aren't part of any group are listed. +// The fields determine which info to request from the server. These +// must be registered QR2 keys (see qr2regkeys.h). HOSTNAME_KEY and +// GAMEMODE_KEY are automatically requested by Peer, so do not need +// to included. +///////////////////////////////////////////////////////////////////// +void peerStartListingGamesA +( + PEER peer, // The peer object. + const unsigned char * fields, // An array of registered QR2 keys to request from servers. + int numFields, // The number of keys in the fields array. + const char * filter, // A SQL-like rule filter. + peerListingGamesCallback callback, // Called when finished. + void * param // Passed to the callback. +); + +// Send a message to a room. +//////////////////////////// +void peerMessageRoomA +( + PEER peer, // The peer object. + RoomType roomType, // The room to send the message to. + const char * message, // The message. + MessageType messageType // The type of message. +); + +// Send a UTM to a room. +//////////////////////// +void peerUTMRoomA +( + PEER peer, // The peer object. + RoomType roomType, // The room to send the UTM to. + const char * command, // The command. + const char * parameters, // The UTM's parameters. + PEERBool authenticate // If true, the server will authenticate this UTM (should normally be false). +); + +// Set a password in a room you're hosting. +// The only roomType currently supported is StagingRoom. +// This will only work if the player is hosting the room. +// If password is NULL or "", the password will be cleared. +/////////////////////////////////////////////////////////// +void peerSetPasswordA +( + PEER peer, // The peer object. + RoomType roomType, // The room in which to set the password. + const char password[PEER_PASSWORD_LEN] // The password to set. +); + +// Set the name of a room you're hosting. +// The only roomType currently supported is StagingRoom. +// PANTS|09.11.00 +//////////////////////////////////////////////////////// +void peerSetRoomNameA +( + PEER peer, // The peer object. + RoomType roomType, // The room in which to set the name. + const char * name // The new name +); + +// Get a room's name (the channel's title). +// Returns NULL if not in the room. +/////////////////////////////////////////// +const char * peerGetRoomNameA +( + PEER peer, // The peer object. + RoomType roomType // The room to get the name for. +); + +// Get's the chat channel associated with the room. +// Returns NULL if not in the room. +/////////////////////////////////////////////////// +const char * peerGetRoomChannelA +( + PEER peer, // The peer object. + RoomType roomType // The room to get the channel for. +); + +// Use this channel for the title room for the currently set title. +// This function is normally not needed. +/////////////////////////////////////////////////////////////////// +void peerSetTitleRoomChannelA +( + PEER peer, // The peer object. + const char * channel // The channel to use for the title room. +); + + +/************ +** PLAYERS ** +************/ +// Send a message to a player. +////////////////////////////// +void peerMessagePlayerA +( + PEER peer, // The peer object. + const char * nick, // The nick of the player to send the message to. + const char * message, // The message to send. + MessageType messageType // The type of message. +); + +// Send a UTM to a player. +////////////////////////// +void peerUTMPlayerA +( + PEER peer, // The peer object. + const char * nick, // The nick of the player to send the UTM to. + const char * command, // The command. + const char * parameters, // The UTM's parameters. + PEERBool authenticate // If true, the server will authenticate this UTM (should normally be false). +); + +// Kick a player from a room. +// Can only be called by a player with host privileges. +/////////////////////////////////////////////////////// +void peerKickPlayerA +( + PEER peer, // The peer object. + RoomType roomType, // The room to kick the player from. + const char * nick, // The nick of the player to kick. + const char * reason // An optional reason for kicking the player +); + +// Gets a player's ping (between the local machine and the player's machine). +// Returns PEERFalse if we don't have or can't get the player's ping. +///////////////////////////////////////////////////////////////////////////// +PEERBool peerGetPlayerPingA +( + PEER peer, // The peer object. + const char * nick, // The player to get the ping for. + int * ping // The player's ping is stored here, if we have it. +); + +// Gets the cross-ping between 2 players. +// Returns PEERFalse if we don't have or can't get the player's cross-ping. +/////////////////////////////////////////////////////////////////////////// +PEERBool peerGetPlayersCrossPingA +( + PEER peer, // The peer object. + const char * nick1, // The first player. + const char * nick2, // The second player. + int * crossPing // The cross-ping is stored here, if we have it. +); + +// Peer already automatically pings all players that are in ping rooms. +// This function does a one-time ping of a remote player in a non-ping room. +// However pings must be enabled in at least one room for this to work, +// otherwise Peer will not open the UDP ping socket. +// Also, peerAlwaysGetPlayerInfo() must be enabled so that Peer has IPs for +// players that are only in non-ping rooms. +//////////////////////////////////////////////////////////////////////////// +PEERBool peerPingPlayerA +( + PEER peer, // The peer object. + const char * nick // The player to ping. +); + +// Gets a player's info immediately. +// Returns PEERFalse if the info is no available. +///////////////////////////////////////////////// +PEERBool peerGetPlayerInfoNoWaitA +( + PEER peer, + const char * nick, + unsigned int * IP, + int * profileID +); + +// Called to get a player's IP and profile ID. +////////////////////////////////////////////// +void peerGetPlayerInfoA +( + PEER peer, // The peer object. + const char * nick, // The player's nick. + peerGetPlayerInfoCallback callback, // Called when finished. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Called to get a player's profile ID. +// DEPRECATED - Use peerGetPlayerInfo. +/////////////////////////////////////// +void peerGetPlayerProfileIDA +( + PEER peer, // The peer object. + const char * nick, // The player's nick. + peerGetPlayerProfileIDCallback callback, // Called when finished. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Called to get a player's IP. +// DEPRECATED - Use peerGetPlayerInfo. +////////////////////////////////////// +void peerGetPlayerIPA +( + PEER peer, // The peer object. + const char * nick, // The player's nick. + peerGetPlayerIPCallback callback, // Called when finished. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Checks if a player is a host (has ops). +// Returns PEERTrue if yes, PEERFalse if no. +//////////////////////////////////////////// +PEERBool peerIsPlayerHostA +( + PEER peer, // The peer object. + const char * nick, // The player's nick. + RoomType roomType // The room to check in. +); + +// Gets a player's flags in a room. Returns PEERFalse if +// the local player or this player is not in the room. +///////////////////////////////////////////////////////// +PEERBool peerGetPlayerFlagsA +( + PEER peer, + const char * nick, + RoomType roomType, + int * flags +); + +/********* +** GAME ** +*********/ +// Gets a player's ready state. +/////////////////////////////// +PEERBool peerGetReadyA +( + PEER peer, // The peer object. + const char * nick, // The player's nick. + PEERBool * ready // The player's ready state gets stored in here. +); + +// Called only by a staging room host to start the game. +// All the other people in the staging room will have their +// peerGameStartedCallback() called. +// The message gets passed to everyone in the peerGameStartedCallback(), and +// can be used to pass information such as a special port or password. +// If (reportingOptions & PEER_STOP_REPORTING), Peer will stop game reporting, +// so the program is responsible for any server reporting. +// If !(reportingOptions & PEER_STOP_REPORTING), Peer will continue doing +// game reporting, and calling the program-supplied callbacks. If +// (reportingOptions & PEER_REPORT_INFO), all server keys will still be +// reported. If (reportingOptions & PEER_REPORT_PLAYERS), all player keys +// will still be reported. +///////////////////////////////////////////////////////////////////////////// +void peerStartGameA +( + PEER peer, // The peer object. + const char * message, // A message to send to everyone. + int reportingOptions // Bitfield flags used to set reporting options. +); + +/********* +** KEYS ** +*********/ +// Set global keys on the local player. +/////////////////////////////////////// +void peerSetGlobalKeysA +( + PEER peer, // The peer object. + int num, // The number of keys to set. + const char ** keys, // The keys to set. + const char ** values // The values for the keys. +); + + +// Get a player's global keys. +////////////////////////////// +void peerGetPlayerGlobalKeysA +( + PEER peer, // The peer object. + const char * nick, // The player to get the keys for. + int num, // The number of keys. + const char ** keys, // The keys to get. + peerGetGlobalKeysCallback callback, // Called with the keys. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Get the global keys for all players in a room we're in. +////////////////////////////////////////////////////////// +void peerGetRoomGlobalKeysA +( + PEER peer, // The peer object. + RoomType roomType, // The room to get the keys in. + int num, // The number of keys. + const char ** keys, // The keys to get. + peerGetGlobalKeysCallback callback, // Called with the keys. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Set the room keys for a player. +// Use NULL or "" to set keys on the room itself. +///////////////////////////////////////////////// +void peerSetRoomKeysA +( + PEER peer, // The peer object. + RoomType roomType, // The room to set the keys in. + const char * nick, // The player to set the keys on (NULL or "" for the room). + int num, // The number of keys. + const char ** keys, // The keys to set. + const char ** values // The values to set. +); + +// Get the room keys for either a single player of an entire room. +// Use "*" for the player to get the keys for the entire room. +// Use NULL or "" for the player to get keys on the room itself. +////////////////////////////////////////////////////////////////// +void peerGetRoomKeysA +( + PEER peer, // The peer object. + RoomType roomType, // The room to get the keys in. + const char * nick, // The player to get the keys for. + int num, // The number of keys. + const char ** keys, // The keys to get. + peerGetRoomKeysCallback callback, // Called with the keys. + void * param, // Passed to callback. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Set the global watch keys for a room type. +// If addKeys is set to PEERTrue, the keys will be +// added to the current global watch keys for this room. +// If addKeys is PEERFalse, these will replace any existing +// global watch keys for this room. +// When entering a room of the given type, peer will get and +// cache these keys for all players in the room. +//////////////////////////////////////////////////////////// +void peerSetGlobalWatchKeysA +( + PEER peer, // The peer object. + RoomType roomType, // The type of room to set the watch keys for. + int num, // The number of keys. + const char ** keys, // The keys to watch for. + PEERBool addKeys // If PEERTrue, add these keys to the existing global watch keys for this room. +); + +// Set the room watch keys for a room type. +// If addKeys is set to PEERTrue, the keys will be +// added to the current room watch keys for this room. +// If addKeys is PEERFalse, these will replace any existing +// room watch keys for this room. +// When entering a room of the given type, peer will get and +// cache these keys for all players in the room. +//////////////////////////////////////////////////////////// +void peerSetRoomWatchKeysA +( + PEER peer, // The peer object. + RoomType roomType, // The type of room to set the watch keys for. + int num, // The number of keys. + const char ** keys, // The keys to watch for. + PEERBool addKeys // If PEERTrue, add these keys to the existing room watch keys for this room. +); + +// Get the global watch key for a particular player. +// If the key isn't cached locally (either because it isn't +// a watch key or just isn't yet known), NULL will be returned. +// If the key is empty, "" will be returned. +/////////////////////////////////////////////////////////////// +const char * peerGetGlobalWatchKeyA +( + PEER peer, // The peer object. + const char * nick, // The player to get the key for. + const char * key // The key to get. +); + +// Get the room watch key for a particular player in a room. +// If the key isn't cached locally (either because it isn't +// a watch key or just isn't yet known), NULL will be returned. +// If the key is empty, "" will be returned. +/////////////////////////////////////////////////////////////// +const char * peerGetRoomWatchKeyA +( + PEER peer, // The peer object. + RoomType roomType, // The room to get the key in. + const char * nick, // The player to get the key for. + const char * key // The key to get. +); + +/************** +** AUTOMATCH ** +**************/ + +// Used to start a automatch attempt. +// The filter contains any hard criteria. This is used to narrow down the list +// of possible matches to those the user might consider. +// The status callback will be called as the attempt progresses, and the rate +// callback will be used to sort possible matches. +///////////////////////////////////////////////////////////////////////////////// +void peerStartAutoMatchA +( + PEER peer, // The peer object. + int maxPlayers, // The total number of players to match (including the local player). + const char * filter, // Hard criteria - filters out servers. + peerAutoMatchStatusCallback statusCallback, // Called as the attempt status changes. + peerAutoMatchRateCallback rateCallback, // Used to rate possible matches. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + +// Same as peerStartAutoMatch, but uses the provided socket for +// sending heartbeats and query replies. This allows the game +// to share a socket with the peer SDK, which can make hosting +// games behind a NAT proxy possible. +//////////////////////////////////////////////////////////////// +void peerStartAutoMatchWithSocketA +( + PEER peer, // The peer object. + int maxPlayers, // The total number of players to match (including the local player). + const char * filter, // Hard criteria - filters out servers. + SOCKET socket, // The socket to be used for reporting. + unsigned short port, // The local port to which the socket is bound. + peerAutoMatchStatusCallback statusCallback, // Called as the attempt status changes. + peerAutoMatchRateCallback rateCallback, // Used to rate possible matches. + void * param, // User-data. + PEERBool blocking // If PEERTrue, don't return until finished. +); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerAutoMatch.c b/code/gamespy/Peer/peerAutoMatch.c new file mode 100644 index 00000000..9a8c8fd9 --- /dev/null +++ b/code/gamespy/Peer/peerAutoMatch.c @@ -0,0 +1,334 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include "peerAutoMatch.h" +#include "peerMangle.h" +#include "peerOperations.h" +#include "peerSB.h" +#include "peerQR.h" +#include "peerCallbacks.h" +#include "peerPlayers.h" +#include "peerRooms.h" + +/************** +** FUNCTIONS ** +**************/ +static PEERBool piCreateAutoMatchRoom(PEER peer); + +static void piCleanAutoMatch(PEER peer) +{ + PEER_CONNECTION; + + // free memory + gsifree(connection->autoMatchFilter); + + // remove the operation + piRemoveOperation(peer, connection->autoMatchOperation); + + connection->autoMatchOperation = NULL; +} + +void piSetAutoMatchStatus(PEER peer, PEERAutoMatchStatus status) +{ + piOperation * operation; + PEER_CONNECTION; + + // make sure there is an operation + operation = connection->autoMatchOperation; + assert(operation); + if(!operation) + return; + + // we need to create a room before we can really switch to PEERWaiting + if((status == PEERWaiting) && !connection->inRoom[StagingRoom]) + { + if(!piCreateAutoMatchRoom(peer)) + piSetAutoMatchStatus(peer, connection->autoMatchSBFailed?PEERFailed:PEERSearching); + return; + } + + // check if this is already the current status + if(connection->autoMatchStatus != status) + { + // set the new status + connection->autoMatchStatus = status; + + // add the callback + piAddAutoMatchStatusCallback(peer); + } + + // handle the status + switch(status) + { + case PEERFailed: + // stop + piSBStopListingAutoMatches(peer); + piStopAutoMatchReporting(peer); + piLeaveRoom(peer, StagingRoom, ""); + + // clean + piCleanAutoMatch(peer); + break; + + case PEERSearching: + // stop + piStopAutoMatchReporting(peer); + piLeaveRoom(peer, StagingRoom, ""); + + // start + if(!connection->autoMatchBrowsing) + { + if(!piSBStartListingAutoMatches(peer)) + { + piSetAutoMatchStatus(peer, connection->autoMatchQRFailed?PEERFailed:PEERWaiting); + return; + } + } + break; + + case PEERWaiting: + // start + assert(connection->inRoom[StagingRoom]); + if(!connection->autoMatchBrowsing && !connection->autoMatchSBFailed) + piSBStartListingAutoMatches(peer); + + if(!connection->autoMatchReporting) + { + if(!piStartAutoMatchReporting(peer)) + { + piSetAutoMatchStatus(peer, connection->autoMatchSBFailed?PEERFailed:PEERSearching); + return; + } + } + break; + + case PEERStaging: + // stop + if(!connection->hosting) + { + piStopAutoMatchReporting(peer); + } + piSBStopListingAutoMatches(peer); + + // start + if(connection->hosting && !connection->autoMatchReporting) + { + if(!piStartAutoMatchReporting(peer)) + { + piSetAutoMatchStatus(peer, PEERSearching); + return; + } + } + break; + + case PEERReady: + // start + if (connection->hosting && !connection->autoMatchReporting) + piStartAutoMatchReporting(peer); + + break; + case PEERComplete: + // stop + piSBStopListingAutoMatches(peer); + piStopAutoMatchReporting(peer); + + // clean + piCleanAutoMatch(peer); + break; + } +} + +void piStopAutoMatch(PEER peer) +{ + //PEERBool inRoom; + + PEER_CONNECTION; + + // make sure we're AutoMatching + if(peerIsAutoMatching(peer)) + { + // we don't want the status callback called + if(connection->autoMatchOperation) + connection->autoMatchOperation->callback = (PEERCBType)NULL; + + // trick it into thinking it's not in a staging room (if it is) + //inRoom = connection->inRoom[StagingRoom]; + //connection->inRoom[StagingRoom] = PEERFalse; + + // cleanup + piSetAutoMatchStatus(peer, PEERFailed); + + // reset the inRoom flag + //connection->inRoom[StagingRoom] = inRoom; + } +} + +static void piJoinAutoMatchRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + void * param +) +{ + PEER_CONNECTION; + + // if we're the only one, or if there's no host, leave + if(success && ((connection->numPlayers[StagingRoom] <= 1) || !piCountRoomOps(peer, StagingRoom, connection->nick))) + { + piLeaveRoom(peer, StagingRoom, ""); + success = PEERFalse; + } + + // check if it succeeded + if(success) + { + // we're now staging + piSetAutoMatchStatus(peer, PEERStaging); + + // if we've got maxplayers, we're now Ready + if((connection->autoMatchStatus == PEERStaging) && (connection->numPlayers[StagingRoom] >= connection->maxPlayers)) + piSetAutoMatchStatus(peer, PEERReady); + } + + // if we were waiting, and this failed, restart waiting + if(!success && (connection->autoMatchStatus == PEERWaiting)) + piSetAutoMatchStatus(peer, PEERWaiting); + + GSI_UNUSED(result); + GSI_UNUSED(roomType); + GSI_UNUSED(param); +} + +PEERBool piJoinAutoMatchRoom(PEER peer, SBServer server) +{ + unsigned int publicIP; + unsigned int privateIP; + unsigned short privatePort; + char room[PI_ROOM_MAX_LEN]; + + PEER_CONNECTION; + + // Get the public and private IPs and ports. + publicIP = SBServerGetPublicInetAddress(server); + privateIP = SBServerGetPrivateInetAddress(server); + if(SBServerHasPrivateAddress(server)) + privatePort = SBServerGetPrivateQueryPort(server); + else + privatePort = SBServerGetPublicQueryPort(server); + + if(!publicIP) + return PEERFalse; + + // get the staging room. + piMangleStagingRoom(room, connection->title, publicIP, privateIP, privatePort); + + // start the operation. + if(!piNewJoinRoomOperation(peer, StagingRoom, room, NULL, piJoinAutoMatchRoomCallback, NULL, piGetNextID(peer))) + return PEERFalse; + + // clone the server + connection->hostServer = piSBCloneServer(server); + + return PEERTrue; +} + +// GetChannelModeCallback used to get the channel modes +// and fix the modes: Limit and OpsObeyChannelLimit +static void piAutoMatchGetChannelCallback(CHAT chat, CHATBool success, const gsi_char *channel, CHATChannelMode *mode, void *param) +{ + piConnection *connection = (piConnection *)param; + if (success) + { + + mode->Limit = connection->maxPlayers; + mode->OpsObeyChannelLimit = CHATTrue; + + // Don't let ops bypass channel limit on the number of players in room + chatSetChannelMode(chat, channel, mode); + } + else + { + // handle the failure + connection->autoMatchQRFailed = PEERTrue; + piSetAutoMatchStatus((PEER *)&connection, connection->autoMatchSBFailed?PEERFailed:PEERSearching); + } +} + +static void piCreateAutoMatchRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + void * param +) +{ + PEERAutoMatchStatus status; + + PEER_CONNECTION; + + if(success) + { + // set the status based on the number of people in the room + if(connection->numPlayers[StagingRoom] <= 1) + { + status = PEERWaiting; + + // get the channel modes so we can set the limits on our staging channel + chatGetChannelMode(connection->chat, peerGetRoomChannel(peer, StagingRoom), piAutoMatchGetChannelCallback, + (void *)connection, CHATFalse); + } + else if(connection->numPlayers[StagingRoom] >= connection->maxPlayers) + { + status = PEERReady; + } + else + { + status = PEERStaging; + } + + // set the Waiting status + piSetAutoMatchStatus(peer, status); + } + else + { + // handle the failure + connection->autoMatchQRFailed = PEERTrue; + piSetAutoMatchStatus(peer, connection->autoMatchSBFailed?PEERFailed:PEERSearching); + } + + GSI_UNUSED(result); + GSI_UNUSED(roomType); + GSI_UNUSED(param); +} + +static PEERBool piCreateAutoMatchRoom(PEER peer) +{ + piOperation * operation; + + PEER_CONNECTION; + + // get the AutoMatch operation + operation = connection->autoMatchOperation; + + // start the operations + if(!piNewCreateStagingRoomOperation(peer, connection->nick, "", connection->maxPlayers, operation->socket, operation->port, piCreateAutoMatchRoomCallback, NULL, piGetNextID(peer))) + { + connection->autoMatchQRFailed = PEERTrue; + return PEERFalse; + } + + return PEERTrue; +} diff --git a/code/gamespy/Peer/peerAutoMatch.h b/code/gamespy/Peer/peerAutoMatch.h new file mode 100644 index 00000000..73234a49 --- /dev/null +++ b/code/gamespy/Peer/peerAutoMatch.h @@ -0,0 +1,39 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERAUTOMATCH_H_ +#define _PEERAUTOMATCH_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/************ +** DEFINES ** +************/ +#define PI_AUTOMATCH_RATING_KEY "gsi_am_rating" + +/************** +** FUNCTIONS ** +**************/ +void piSetAutoMatchStatus(PEER peer, PEERAutoMatchStatus status); +void piStopAutoMatch(PEER peer); +PEERBool piJoinAutoMatchRoom(PEER peer, SBServer server); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerCallbacks.c b/code/gamespy/Peer/peerCallbacks.c new file mode 100644 index 00000000..87d1997a --- /dev/null +++ b/code/gamespy/Peer/peerCallbacks.c @@ -0,0 +1,3695 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include +#include "peer.h" +#include "peerMain.h" +#include "peerCallbacks.h" +#include "peerOperations.h" + +/************ +** DEFINES ** +************/ +#define ASSERT_DATA(data) assert(data->type >= 0);\ + assert(data->callback);\ + assert(data->params); + +/********** +** TYPES ** +**********/ +typedef struct piCallbackData +{ + piCallbackType type; // PI__CALLBACK + PEERBool success; // operation success + PEERCBType callback; // the function callback -- int type for ANSI compatability + void * callbackParam; // user-data for the callback + void * params; // extra callback params + int ID; // unique ID for this callback + PEERBool inCall; // set to true immediately before callback is called, then to false +} piCallbackData; + +typedef struct piCallbackFuncs +{ + piCallbackType type; + PEERBool (* copy) + ( + void * paramsOut, + void * paramsIn + ); + void (* gsifree) + ( + void * params + ); + void (* call) + ( + PEER peer, + piCallbackData * data + ); +} piCallbackFuncs; + +/************** +** CALLBACKS ** +**************/ +static int piAddCallback +( + PEER peer, + PEERBool success, + PEERCBType callback, + void * param, + piCallbackType type, + void * paramsIn, + size_t paramsSize, + int opID +); + +/* Connect. +**********/ +typedef struct piConnectParams +{ + int failureReason; +} piConnectParams; +static PEERBool piConnectCopy(void * paramsOut_, void * paramsIn_) +{ + piConnectParams * paramsOut = (piConnectParams *)paramsOut_; + piConnectParams * paramsIn = (piConnectParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->failureReason = paramsIn->failureReason; + + return PEERTrue; +} +static void piConnectFree(void * params_) +{ + piConnectParams * params = (piConnectParams *)params_; + + assert(params); + GSI_UNUSED(params); +} +static void piConnectCall(PEER peer, piCallbackData * data) +{ + piConnectParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_CONNECT_CALLBACK); + + params = data->params; + ((peerConnectCallback)data->callback)(peer, data->success, params->failureReason, data->callbackParam); +} +void piAddConnectCallback +( + PEER peer, + PEERBool success, + int failureReason, + peerConnectCallback callback, + void * param, + int opID +) +{ + piConnectParams params; + + params.failureReason = failureReason; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_CONNECT_CALLBACK, ¶ms, sizeof(piConnectParams), opID); +} + +/* JoinRoom. +***********/ +typedef struct piJoinRoomParams +{ + PEERJoinResult result; + RoomType roomType; +} piJoinRoomParams; +static PEERBool piJoinRoomCopy(void * paramsOut_, void * paramsIn_) +{ + piJoinRoomParams * paramsOut = (piJoinRoomParams *)paramsOut_; + piJoinRoomParams * paramsIn = (piJoinRoomParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->result = paramsIn->result; + paramsOut->roomType = paramsIn->roomType; + + return PEERTrue; +} +static void piJoinRoomFree(void * params_) +{ + piJoinRoomParams * params = (piJoinRoomParams *)params_; + + assert(params); + GSI_UNUSED(params); +} +static void piJoinRoomCall(PEER peer, piCallbackData * data) +{ + piJoinRoomParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_JOIN_ROOM_CALLBACK); + + params = data->params; + ((peerJoinRoomCallback)(PEERCBType)data->callback)(peer, data->success, params->result, params->roomType, data->callbackParam); +} +void piAddJoinRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + peerJoinRoomCallback callback, + void * param, + int opID +) +{ + piJoinRoomParams params; + + // if this was called as part of an AutoMatch attempt, call the callback directly + // this is safe because we're only calling into Peer code + if(peerIsAutoMatching(peer) && (roomType == StagingRoom)) + { + callback(peer, success, result, roomType, param); + return; + } + + params.result = result; + params.roomType = roomType; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_JOIN_ROOM_CALLBACK, ¶ms, sizeof(piJoinRoomParams), opID); +} + +/* ListGroupsRooms. +******************/ +typedef struct piListGroupRoomsParams +{ + int groupID; + SBServer server; + char * name; + int numWaiting; + int maxWaiting; + int numGames; + int numPlaying; +} piListGroupRoomsParams; +static PEERBool piListGroupRoomsCopy(void * paramsOut_, void * paramsIn_) +{ + piListGroupRoomsParams * paramsOut = (piListGroupRoomsParams *)paramsOut_; + piListGroupRoomsParams * paramsIn = (piListGroupRoomsParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->groupID = paramsIn->groupID; + paramsOut->server = paramsIn->server; + paramsOut->numWaiting = paramsIn->numWaiting; + paramsOut->maxWaiting = paramsIn->maxWaiting; + paramsOut->numGames = paramsIn->numGames; + paramsOut->numPlaying = paramsIn->numPlaying; + if(paramsIn->name) + { + paramsOut->name = goastrdup(paramsIn->name); + if(paramsIn->name && !paramsOut->name) + return PEERFalse; + } + else + paramsOut->name = NULL; + + return PEERTrue; +} +static void piListGroupRoomsFree(void * params_) +{ + piListGroupRoomsParams * params = (piListGroupRoomsParams *)params_; + + assert(params); + + gsifree(params->name); +} +static void piListGroupRoomsCall(PEER peer, piCallbackData * data) +{ + piListGroupRoomsParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_LIST_GROUP_ROOMS_CALLBACK); + + params = data->params; + +#ifndef GSI_UNICODE + ((peerListGroupRoomsCallback)(PEERCBType)data->callback)(peer, data->success, params->groupID, params->server, params->name, params->numWaiting, params->maxWaiting, params->numGames, params->numPlaying, data->callbackParam); +#else + { + unsigned short* name_W = NULL; + if (params->name != NULL) + name_W = UTF8ToUCS2StringAlloc(params->name); + ((peerListGroupRoomsCallback)(int)data->callback)(peer, data->success, params->groupID, params->server, name_W, params->numWaiting, params->maxWaiting, params->numGames, params->numPlaying, data->callbackParam); + gsifree(name_W); + } +#endif +} +void piAddListGroupRoomsCallback +( + PEER peer, + PEERBool success, + int groupID, + SBServer server, + const char * name, + int numWaiting, + int maxWaiting, + int numGames, + int numPlaying, + peerListGroupRoomsCallback callback, + void * param, + int opID +) +{ + piListGroupRoomsParams params; + params.groupID = groupID; + params.server = server; + params.name = (char *)name; + params.numWaiting = numWaiting; + params.maxWaiting = maxWaiting; + params.numGames = numGames; + params.numPlaying = numPlaying; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_LIST_GROUP_ROOMS_CALLBACK, ¶ms, sizeof(piListGroupRoomsParams), opID); +} + +/* ListingGames. +***************/ +typedef struct piListingGamesParams +{ + char * name; + SBServer server; + PEERBool staging; + int msg; + int progress; +} piListingGamesParams; +static PEERBool piListingGamesCopy(void * paramsOut_, void * paramsIn_) +{ + piListingGamesParams * paramsOut = (piListingGamesParams *)paramsOut_; + piListingGamesParams * paramsIn = (piListingGamesParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + if(paramsIn->name) + { + paramsOut->name = goastrdup(paramsIn->name); + if(paramsIn->name && !paramsOut->name) + return PEERFalse; + } + else + paramsOut->name = NULL; + paramsOut->staging = paramsIn->staging; + paramsOut->server = paramsIn->server; + paramsOut->msg = paramsIn->msg; + paramsOut->progress = paramsIn->progress; + + return PEERTrue; +} +static void piListingGamesFree(void * params_) +{ + piListingGamesParams * params = (piListingGamesParams *)params_; + + assert(params); + + gsifree(params->name); +} +static void piListingGamesCall(PEER peer, piCallbackData * data) +{ + piListingGamesParams * params; + int len; + int i; + + PEER_CONNECTION; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_LISTING_GAMES_CALLBACK); + + params = data->params; + + // This is a bit of a hack. We don't want the server browser progess to show + // 100% until the last callback is being called. + if (params->progress == 100) + { + len = ArrayLength(connection->callbackList); + for(i = 0; i < len; i++) + { + // look for a listing games callback + piCallbackData* anotherData = (piCallbackData *)ArrayNth(connection->callbackList, i); + if(anotherData != data && anotherData->type == PI_LISTING_GAMES_CALLBACK) + { + piListingGamesParams* anotherParams = (piListingGamesParams *)anotherData->params; + if(anotherParams->msg == PEER_UPDATE) + { + // Another update callback will follow this one, + // limit the current callback's progress to 99% + params->progress = 99; + break; + } + } + } + } + +#ifndef GSI_UNICODE + ((peerListingGamesCallback)(PEERCBType)data->callback)(peer, data->success, params->name, params->server, params->staging, params->msg, params->progress, data->callbackParam); +#else + { + unsigned short* name_W = NULL; + if (params->name != NULL) + name_W = UTF8ToUCS2StringAlloc(params->name); + ((peerListingGamesCallback)(int)data->callback)(peer, data->success, name_W, params->server, params->staging, params->msg, params->progress, data->callbackParam); + gsifree(name_W); + } +#endif +} + +void piAddListingGamesCallback +( + PEER peer, + PEERBool success, + SBServer server, + int msg +) +{ + piListingGamesParams params; + const char * name; + const char * gamemode; + PEERBool staging; + int count; + int pendingCount; + int progress; + PEER_CONNECTION; + + // if this is a remove, remove any queued adds or updates + if(msg == PEER_REMOVE) + piClearListingGameServerCallbacks(peer, server); + + // get info from the server object + if(server) + { + name = SBServerGetStringValueA(server, "hostname", "(No Name)"); + gamemode = SBServerGetStringValueA(server, "gamemode", ""); + staging = (PEERBool)(strcasecmp(gamemode, "openstaging") == 0); + } + else + { + name = NULL; + staging = PEERFalse; + } + + // set the progress + if(connection->initialGameList) + { + count = SBServerListCount(&connection->gameList); + if(count) + { + pendingCount = (connection->gameEngine.querylist.count + connection->gameEngine.pendinglist.count); + progress = (((count - pendingCount) * 100) / count); + } + else + { + progress = 0; + } + } + else + { + progress = 100; + } + + params.name = (char *)name; + params.server = server; + params.staging = staging; + params.msg = msg; + params.progress = progress; + piAddCallback(peer, success, (PEERCBType)connection->gameListCallback, connection->gameListParam, PI_LISTING_GAMES_CALLBACK, ¶ms, sizeof(piListingGamesParams), -1); +} + +/* NickError. +*************/ +typedef struct piNickErrorParams +{ + int type; + char * nick; + int numSuggestedNicks; + char ** suggestedNicks; +} piNickErrorParams; +static PEERBool piNickErrorCopy(void * paramsOut_, void * paramsIn_) +{ + int i; + int num; + PEERBool success = PEERTrue; + piNickErrorParams * paramsOut = (piNickErrorParams *)paramsOut_; + piNickErrorParams * paramsIn = (piNickErrorParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + memset(paramsOut, 0, sizeof(piNickErrorParams)); + num = paramsOut->numSuggestedNicks = paramsIn->numSuggestedNicks; + + paramsOut->type = paramsIn->type; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + success = PEERFalse; + } + else + paramsOut->nick = NULL; + + if(success && num && paramsIn->suggestedNicks) + { + paramsOut->suggestedNicks = (char **)gsimalloc(sizeof(char *) * num); + if(!paramsOut->suggestedNicks) + success = PEERFalse; + else + memset(paramsOut->suggestedNicks, 0, sizeof(char *) * num); + + for(i = 0 ; success && (i < num) ; i++) + { + paramsOut->suggestedNicks[i] = goastrdup(paramsIn->suggestedNicks[i]); + if(!paramsOut->suggestedNicks[i]) + success = PEERFalse; + } + } + + if(!success) + { + gsifree(paramsOut->nick); + for(i = 0 ; i < num ; i++) + { + if(paramsOut->suggestedNicks) + gsifree(paramsOut->suggestedNicks[i]); + } + gsifree(paramsOut->suggestedNicks); + } + + return success; +} +static void piNickErrorFree(void * params_) +{ + int i; + piNickErrorParams * params = (piNickErrorParams *)params_; + + assert(params); + + gsifree(params->nick); + for(i = 0 ; i < params->numSuggestedNicks ; i++) + { + if(params->suggestedNicks) + gsifree(params->suggestedNicks[i]); + } + gsifree(params->suggestedNicks); +} +static void piNickErrorCall(PEER peer, piCallbackData * data) +{ + piNickErrorParams * params; + + //PEER_CONNECTION; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_NICK_ERROR_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerNickErrorCallback)(PEERCBType)data->callback)(peer, params->type, params->nick, params->numSuggestedNicks, (const char **)params->suggestedNicks, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short** suggestedNicks_W = UTF8ToUCS2StringArrayAlloc((const UTF8String *)params->suggestedNicks, params->numSuggestedNicks); + int i; + ((peerNickErrorCallback)data->callback)(peer, params->type, nick_W, params->numSuggestedNicks, (const unsigned short**)suggestedNicks_W, data->callbackParam); + gsifree(nick_W); + for (i=0; i < params->numSuggestedNicks; i++) + { + gsifree(suggestedNicks_W[i]); + } + gsifree(suggestedNicks_W); + } +#endif +} + +void piAddNickErrorCallback +( + PEER peer, + int type, + const char * nick, + int numSuggestedNicks, + const char ** suggestedNicks, + void * param, + int opID +) +{ + piNickErrorParams params; + + PEER_CONNECTION; + + params.type = type; + params.nick = (char *)nick; + params.numSuggestedNicks = numSuggestedNicks; + params.suggestedNicks = (char **)suggestedNicks; + piAddCallback(peer, PEERFalse, (PEERCBType)connection->nickErrorCallback, param, PI_NICK_ERROR_CALLBACK, ¶ms, sizeof(piNickErrorParams), opID); +} + +/* EnumPlayers. +**************/ +typedef struct piEnumPlayersParams +{ + RoomType roomType; + int index; + char * nick; + int flags; +} piEnumPlayersParams; +static PEERBool piEnumPlayersCopy(void * paramsOut_, void * paramsIn_) +{ + piEnumPlayersParams * paramsOut = (piEnumPlayersParams *)paramsOut_; + piEnumPlayersParams * paramsIn = (piEnumPlayersParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + paramsOut->index = paramsIn->index; + paramsOut->flags = paramsIn->flags; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(paramsIn->nick && !paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + + return PEERTrue; +} +static void piEnumPlayersFree(void * params_) +{ + piEnumPlayersParams * params = (piEnumPlayersParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piEnumPlayersCall(PEER peer, piCallbackData * data) +{ + piEnumPlayersParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_ENUM_PLAYERS_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerEnumPlayersCallback)data->callback)(peer, data->success, params->roomType, params->index, params->nick, params->flags, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerEnumPlayersCallback)data->callback)(peer, data->success, params->roomType, params->index, nick_W, params->flags, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddEnumPlayersCallback +( + PEER peer, + PEERBool success, + RoomType roomType, + int index, + const char * nick, + int flags, + peerEnumPlayersCallback callback, + void * param, + int opID +) +{ + piEnumPlayersParams params; + params.roomType = roomType; + params.index = index; + params.nick = (char *)nick; + params.flags = flags; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_ENUM_PLAYERS_CALLBACK, ¶ms, sizeof(piEnumPlayersParams), opID); +} + +/* GetPlayerInfo. +****************/ +typedef struct piGetPlayerInfoParams +{ + char * nick; + unsigned int IP; + int profileID; +} piGetPlayerInfoParams; +static PEERBool piGetPlayerInfoCopy(void * paramsOut_, void * paramsIn_) +{ + piGetPlayerInfoParams * paramsOut = (piGetPlayerInfoParams *)paramsOut_; + piGetPlayerInfoParams * paramsIn = (piGetPlayerInfoParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + paramsOut->IP = paramsIn->IP; + paramsOut->profileID = paramsIn->profileID; + + return PEERTrue; +} +static void piGetPlayerInfoFree(void * params_) +{ + piGetPlayerInfoParams * params = (piGetPlayerInfoParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piGetPlayerInfoCall(PEER peer, piCallbackData * data) +{ + piGetPlayerInfoParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_GET_PLAYER_INFO_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerGetPlayerInfoCallback)data->callback)(peer, data->success, params->nick, params->IP, params->profileID, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerGetPlayerInfoCallback)data->callback)(peer, data->success, nick_W, params->IP, params->profileID, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddGetPlayerInfoCallback +( + PEER peer, + PEERBool success, + const char * nick, + unsigned int IP, + int profileID, + peerGetPlayerInfoCallback callback, + void * param, + int opID +) +{ + piGetPlayerInfoParams params; + params.nick = (char *)nick; + params.IP = IP; + params.profileID = profileID; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_GET_PLAYER_INFO_CALLBACK, ¶ms, sizeof(piGetPlayerInfoParams), opID); +} + +/* GetPlayerProfileID. +*********************/ +typedef struct piGetPlayerProfileIDParams +{ + char * nick; + int profileID; +} piGetPlayerProfileIDParams; +static PEERBool piGetPlayerProfileIDCopy(void * paramsOut_, void * paramsIn_) +{ + piGetPlayerProfileIDParams * paramsOut = (piGetPlayerProfileIDParams *)paramsOut_; + piGetPlayerProfileIDParams * paramsIn = (piGetPlayerProfileIDParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + paramsOut->profileID = paramsIn->profileID; + + return PEERTrue; +} +static void piGetPlayerProfileIDFree(void * params_) +{ + piGetPlayerProfileIDParams * params = (piGetPlayerProfileIDParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piGetPlayerProfileIDCall(PEER peer, piCallbackData * data) +{ + piGetPlayerProfileIDParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_GET_PLAYER_PROFILE_ID_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerGetPlayerProfileIDCallback)data->callback)(peer, data->success, params->nick, params->profileID, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerGetPlayerProfileIDCallback)data->callback)(peer, data->success, nick_W, params->profileID, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddGetPlayerProfileIDCallback +( + PEER peer, + PEERBool success, + const char * nick, + int profileID, + peerGetPlayerProfileIDCallback callback, + void * param, + int opID +) +{ + piGetPlayerProfileIDParams params; + params.nick = (char *)nick; + params.profileID = profileID; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_GET_PLAYER_PROFILE_ID_CALLBACK, ¶ms, sizeof(piGetPlayerProfileIDParams), opID); +} + +/* GetPlayerIP. +**************/ +typedef struct piGetPlayerIPParams +{ + char * nick; + unsigned int IP; +} piGetPlayerIPParams; +static PEERBool piGetPlayerIPCopy(void * paramsOut_, void * paramsIn_) +{ + piGetPlayerIPParams * paramsOut = (piGetPlayerIPParams *)paramsOut_; + piGetPlayerIPParams * paramsIn = (piGetPlayerIPParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->IP = paramsIn->IP; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + + return PEERTrue; +} +static void piGetPlayerIPFree(void * params_) +{ + piGetPlayerIPParams * params = (piGetPlayerIPParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piGetPlayerIPCall(PEER peer, piCallbackData * data) +{ + piGetPlayerIPParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_GET_PLAYER_IP_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerGetPlayerIPCallback)data->callback)(peer, data->success, params->nick, params->IP, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerGetPlayerIPCallback)data->callback)(peer, data->success, nick_W, params->IP, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddGetPlayerIPCallback +( + PEER peer, + PEERBool success, + const char * nick, + unsigned int IP, + peerGetPlayerIPCallback callback, + void * param, + int opID +) +{ + piGetPlayerIPParams params; + params.nick = (char *)nick; + params.IP = IP; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_GET_PLAYER_IP_CALLBACK, ¶ms, sizeof(piGetPlayerIPParams), opID); +} + +/* Room Message. +***************/ +typedef struct piRoomMessageParams +{ + RoomType roomType; + char * nick; + char * message; + MessageType messageType; +} piRoomMessageParams; +static PEERBool piRoomMessageCopy(void * paramsOut_, void * paramsIn_) +{ + piRoomMessageParams * paramsOut = (piRoomMessageParams *)paramsOut_; + piRoomMessageParams * paramsIn = (piRoomMessageParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + paramsOut->messageType = paramsIn->messageType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + if(paramsIn->message) + { + paramsOut->message = goastrdup(paramsIn->message); + if(!paramsOut->message) + { + gsifree(paramsOut->nick); + return PEERFalse; + } + } + else + paramsOut->message = NULL; + + return PEERTrue; +} +static void piRoomMessageFree(void * params_) +{ + piRoomMessageParams * params = (piRoomMessageParams *)params_; + + assert(params); + + gsifree(params->nick); + gsifree(params->message); +} +static void piRoomMessageCall(PEER peer, piCallbackData * data) +{ + piRoomMessageParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_ROOM_MESSAGE_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerRoomMessageCallback)data->callback)(peer, params->roomType, params->nick, params->message, params->messageType, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short* message_W = UTF8ToUCS2StringAlloc(params->message); + + ((peerRoomMessageCallback)data->callback)(peer, params->roomType, nick_W, message_W, params->messageType, data->callbackParam); + gsifree(nick_W); + gsifree(message_W); + } +#endif +} +void piAddRoomMessageCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * message, + MessageType messageType +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->roomMessage) + { + piRoomMessageParams params; + params.roomType = roomType; + params.nick = (char *)nick; + params.message = (char *)message; + params.messageType = messageType; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->roomMessage, callbacks->param, PI_ROOM_MESSAGE_CALLBACK, ¶ms, sizeof(piRoomMessageParams), -1); + } +} + +/* Room UTM. +***********/ +typedef struct piRoomUTMParams +{ + RoomType roomType; + char * nick; + char * command; + char * parameters; + PEERBool authenticated; +} piRoomUTMParams; +static PEERBool piRoomUTMCopy(void * paramsOut_, void * paramsIn_) +{ + piRoomUTMParams * paramsOut = (piRoomUTMParams *)paramsOut_; + piRoomUTMParams * paramsIn = (piRoomUTMParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->authenticated = paramsIn->authenticated; + paramsOut->roomType = paramsIn->roomType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + if(paramsIn->command) + { + paramsOut->command = goastrdup(paramsIn->command); + if(!paramsOut->command) + { + gsifree(paramsOut->nick); + return PEERFalse; + } + } + else + paramsOut->command = NULL; + if(paramsIn->parameters) + { + paramsOut->parameters = goastrdup(paramsIn->parameters); + if(!paramsOut->parameters) + { + gsifree(paramsOut->nick); + gsifree(paramsOut->command); + return PEERFalse; + } + } + else + paramsOut->parameters = NULL; + + return PEERTrue; +} +static void piRoomUTMFree(void * params_) +{ + piRoomUTMParams * params = (piRoomUTMParams *)params_; + + assert(params); + + gsifree(params->nick); + gsifree(params->command); + gsifree(params->parameters); +} +static void piRoomUTMCall(PEER peer, piCallbackData * data) +{ + piRoomUTMParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_ROOM_UTM_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerRoomUTMCallback)data->callback)(peer, params->roomType, params->nick, params->command, params->parameters, params->authenticated, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short* command_W = UTF8ToUCS2StringAlloc(params->command); + unsigned short* parameters_W = UTF8ToUCS2StringAlloc(params->parameters); + ((peerRoomUTMCallback)data->callback)(peer, params->roomType, nick_W, command_W, parameters_W, params->authenticated, data->callbackParam); + gsifree(nick_W); + gsifree(command_W); + gsifree(parameters_W); + } +#endif +} +void piAddRoomUTMCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * command, + const char * parameters, + PEERBool authenticated +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->roomUTM) + { + piRoomUTMParams params; + params.roomType = roomType; + params.nick = (char *)nick; + params.command = (char *)command; + params.parameters = (char *)parameters; + params.authenticated = authenticated; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->roomUTM, callbacks->param, PI_ROOM_UTM_CALLBACK, ¶ms, sizeof(piRoomUTMParams), -1); + } +} + +/* Room Name Changed. +********************/ +typedef struct piRoomNameChangedParams +{ + RoomType roomType; +} piRoomNameChangedParams; +static PEERBool piRoomNameChangedCopy(void * paramsOut_, void * paramsIn_) +{ + piRoomNameChangedParams * paramsOut = (piRoomNameChangedParams *)paramsOut_; + piRoomNameChangedParams * paramsIn = (piRoomNameChangedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + + return PEERTrue; +} +static void piRoomNameChangedFree(void * params_) +{ + piRoomNameChangedParams * params = (piRoomNameChangedParams *)params_; + + assert(params); + GSI_UNUSED(params); +} +static void piRoomNameChangedCall(PEER peer, piCallbackData * data) +{ + piRoomNameChangedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_ROOM_NAME_CHANGED_CALLBACK); + + params = data->params; + ((peerRoomNameChangedCallback)data->callback)(peer, params->roomType, data->callbackParam); +} +void piAddRoomNameChangedCallback +( + PEER peer, + RoomType roomType +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->roomNameChanged) + { + piRoomNameChangedParams params; + params.roomType = roomType; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->roomNameChanged, callbacks->param, PI_ROOM_NAME_CHANGED_CALLBACK, ¶ms, sizeof(piRoomNameChangedParams), -1); + } +} + +/* Room Mode Changed. +********************/ +typedef struct piRoomModeChangedParams +{ + RoomType roomType; + CHATChannelMode mode; +} piRoomModeChangedParams; +static PEERBool piRoomModeChangedCopy(void * paramsOut_, void * paramsIn_) +{ + piRoomModeChangedParams * paramsOut = (piRoomModeChangedParams *)paramsOut_; + piRoomModeChangedParams * paramsIn = (piRoomModeChangedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + paramsOut->mode = paramsIn->mode; + + return PEERTrue; +} +static void piRoomModeChangedFree(void * params_) +{ + piRoomModeChangedParams * params = (piRoomModeChangedParams *)params_; + + assert(params); + GSI_UNUSED(params); +} +static void piRoomModeChangedCall(PEER peer, piCallbackData * data) +{ + piRoomModeChangedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_ROOM_MODE_CHANGED_CALLBACK); + + params = data->params; + ((peerRoomModeChangedCallback)data->callback)(peer, params->roomType, ¶ms->mode, data->callbackParam); +} +void piAddRoomModeChangedCallback +( + PEER peer, + RoomType roomType, + CHATChannelMode * mode +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->roomModeChanged) + { + piRoomModeChangedParams params; + params.roomType = roomType; + params.mode = *mode; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->roomModeChanged, callbacks->param, PI_ROOM_MODE_CHANGED_CALLBACK, ¶ms, sizeof(piRoomModeChangedParams), -1); + } +} + +/* Player Message. +*****************/ +typedef struct piPlayerMessageParams +{ + char * nick; + char * message; + MessageType messageType; +} piPlayerMessageParams; +static PEERBool piPlayerMessageCopy(void * paramsOut_, void * paramsIn_) +{ + piPlayerMessageParams * paramsOut = (piPlayerMessageParams *)paramsOut_; + piPlayerMessageParams * paramsIn = (piPlayerMessageParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->messageType = paramsIn->messageType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + if(paramsIn->message) + { + paramsOut->message = goastrdup(paramsIn->message); + if(!paramsOut->message) + { + gsifree(paramsOut->nick); + return PEERFalse; + } + } + else + paramsOut->message = NULL; + + return PEERTrue; +} +static void piPlayerMessageFree(void * params_) +{ + piPlayerMessageParams * params = (piPlayerMessageParams *)params_; + + assert(params); + + gsifree(params->nick); + gsifree(params->message); +} +static void piPlayerMessageCall(PEER peer, piCallbackData * data) +{ + piPlayerMessageParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_PLAYER_MESSAGE_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerPlayerMessageCallback)data->callback)(peer, params->nick, params->message, params->messageType, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short* message_W = UTF8ToUCS2StringAlloc(params->message); + ((peerPlayerMessageCallback)data->callback)(peer, nick_W, message_W, params->messageType, data->callbackParam); + gsifree(nick_W); + gsifree(message_W); + } +#endif +} +void piAddPlayerMessageCallback +( + PEER peer, + const char * nick, + const char * message, + MessageType messageType +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->playerMessage) + { + piPlayerMessageParams params; + params.nick = (char *)nick; + params.message = (char *)message; + params.messageType = messageType; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->playerMessage, callbacks->param, PI_PLAYER_MESSAGE_CALLBACK, ¶ms, sizeof(piPlayerMessageParams), -1); + } +} + +/* Player UTM. +*************/ +typedef struct piPlayerUTMParams +{ + char * nick; + char * command; + char * parameters; + PEERBool authenticated; +} piPlayerUTMParams; +static PEERBool piPlayerUTMCopy(void * paramsOut_, void * paramsIn_) +{ + piPlayerUTMParams * paramsOut = (piPlayerUTMParams *)paramsOut_; + piPlayerUTMParams * paramsIn = (piPlayerUTMParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->authenticated = paramsIn->authenticated; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + if(paramsIn->command) + { + paramsOut->command = goastrdup(paramsIn->command); + if(!paramsOut->command) + { + gsifree(paramsOut->nick); + return PEERFalse; + } + } + else + paramsOut->command = NULL; + if(paramsIn->parameters) + { + paramsOut->parameters = goastrdup(paramsIn->parameters); + if(!paramsOut->parameters) + { + gsifree(paramsOut->nick); + gsifree(paramsOut->command); + return PEERFalse; + } + } + else + paramsOut->parameters = NULL; + + return PEERTrue; +} +static void piPlayerUTMFree(void * params_) +{ + piPlayerUTMParams * params = (piPlayerUTMParams *)params_; + + assert(params); + + gsifree(params->nick); + gsifree(params->command); + gsifree(params->parameters); +} +static void piPlayerUTMCall(PEER peer, piCallbackData * data) +{ + piPlayerUTMParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_PLAYER_UTM_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerPlayerUTMCallback)data->callback)(peer, params->nick, params->command, params->parameters, params->authenticated, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short* command_W = UTF8ToUCS2StringAlloc(params->command); + unsigned short* parameters_W = UTF8ToUCS2StringAlloc(params->parameters); + ((peerPlayerUTMCallback)data->callback)(peer, nick_W, command_W, parameters_W, params->authenticated, data->callbackParam); + gsifree(nick_W); + gsifree(command_W); + gsifree(parameters_W); + } +#endif +} +void piAddPlayerUTMCallback +( + PEER peer, + const char * nick, + const char * command, + const char * parameters, + PEERBool authenticated +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->playerUTM) + { + piPlayerUTMParams params; + params.nick = (char *)nick; + params.command = (char *)command; + params.parameters = (char *)parameters; + params.authenticated = authenticated; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->playerUTM, callbacks->param, PI_PLAYER_UTM_CALLBACK, ¶ms, sizeof(piPlayerUTMParams), -1); + } +} + +/* Ready Changed. +****************/ +typedef struct piReadyChangedParams +{ + char * nick; + PEERBool ready; +} piReadyChangedParams; +static PEERBool piReadyChangedCopy(void * paramsOut_, void * paramsIn_) +{ + piReadyChangedParams * paramsOut = (piReadyChangedParams *)paramsOut_; + piReadyChangedParams * paramsIn = (piReadyChangedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + paramsOut->ready = paramsIn->ready; + + return PEERTrue; +} +static void piReadyChangedFree(void * params_) +{ + piReadyChangedParams * params = (piReadyChangedParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piReadyChangedCall(PEER peer, piCallbackData * data) +{ + piReadyChangedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_READY_CHANGED_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerReadyChangedCallback)data->callback)(peer, params->nick, params->ready, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerReadyChangedCallback)data->callback)(peer, nick_W, params->ready, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddReadyChangedCallback +( + PEER peer, + const char * nick, + PEERBool ready +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->readyChanged) + { + piReadyChangedParams params; + params.nick = (char *)nick; + params.ready = ready; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->readyChanged, callbacks->param, PI_READY_CHANGED_CALLBACK, ¶ms, sizeof(piReadyChangedParams), -1); + } +} + +/* GameStarted. +**************/ +typedef struct piGameStartedParams +{ + SBServer server; + char * message; +} piGameStartedParams; +static PEERBool piGameStartedCopy(void * paramsOut_, void * paramsIn_) +{ + piGameStartedParams * paramsOut = (piGameStartedParams *)paramsOut_; + piGameStartedParams * paramsIn = (piGameStartedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->server = paramsIn->server; + if(paramsIn->message) + { + paramsOut->message = goastrdup(paramsIn->message); + if(!paramsOut->message) + return PEERFalse; + } + else + paramsOut->message = NULL; + + return PEERTrue; +} +static void piGameStartedFree(void * params_) +{ + piGameStartedParams * params = (piGameStartedParams *)params_; + + assert(params); + + gsifree(params->message); +} +static void piGameStartedCall(PEER peer, piCallbackData * data) +{ + piGameStartedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_GAME_STARTED_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerGameStartedCallback)data->callback)(peer, params->server, params->message, data->callbackParam); +#else + { + unsigned short* message_W = UTF8ToUCS2StringAlloc(params->message); + ((peerGameStartedCallback)data->callback)(peer, params->server, message_W, data->callbackParam); + gsifree(message_W); + } +#endif +} +void piAddGameStartedCallback +( + PEER peer, + SBServer server, + const char * message +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->gameStarted) + { + piGameStartedParams params; + params.server = server; + params.message = (char *)message; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->gameStarted, callbacks->param, PI_GAME_STARTED_CALLBACK, ¶ms, sizeof(piGameStartedParams), -1); + } +} + +/* Player Joined. +****************/ +typedef struct piPlayerJoinedParams +{ + RoomType roomType; + char * nick; +} piPlayerJoinedParams; +static PEERBool piPlayerJoinedCopy(void * paramsOut_, void * paramsIn_) +{ + piPlayerJoinedParams * paramsOut = (piPlayerJoinedParams *)paramsOut_; + piPlayerJoinedParams * paramsIn = (piPlayerJoinedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + + return PEERTrue; +} +static void piPlayerJoinedFree(void * params_) +{ + piPlayerJoinedParams * params = (piPlayerJoinedParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piPlayerJoinedCall(PEER peer, piCallbackData * data) +{ + piPlayerJoinedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_PLAYER_JOINED_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerPlayerJoinedCallback)data->callback)(peer, params->roomType, params->nick, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerPlayerJoinedCallback)data->callback)(peer, params->roomType, nick_W, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddPlayerJoinedCallback +( + PEER peer, + RoomType roomType, + const char * nick +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->playerJoined) + { + piPlayerJoinedParams params; + params.roomType = roomType; + params.nick = (char *)nick; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->playerJoined, callbacks->param, PI_PLAYER_JOINED_CALLBACK, ¶ms, sizeof(piPlayerJoinedParams), -1); + } +} + +/* Player Left. +**************/ +typedef struct piPlayerLeftParams +{ + RoomType roomType; + char * nick; + char * reason; +} piPlayerLeftParams; +static PEERBool piPlayerLeftCopy(void * paramsOut_, void * paramsIn_) +{ + piPlayerLeftParams * paramsOut = (piPlayerLeftParams *)paramsOut_; + piPlayerLeftParams * paramsIn = (piPlayerLeftParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + if(paramsIn->reason) + { + paramsOut->reason = goastrdup(paramsIn->reason); + if(!paramsOut->reason) + { + gsifree(paramsOut->nick); + return PEERFalse; + } + } + else + paramsOut->reason = NULL; + + return PEERTrue; +} +static void piPlayerLeftFree(void * params_) +{ + piPlayerLeftParams * params = (piPlayerLeftParams *)params_; + + assert(params); + + gsifree(params->nick); + gsifree(params->reason); +} +static void piPlayerLeftCall(PEER peer, piCallbackData * data) +{ + piPlayerLeftParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_PLAYER_LEFT_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerPlayerLeftCallback)data->callback)(peer, params->roomType, params->nick, params->reason, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short* reason_W = UTF8ToUCS2StringAlloc(params->reason); + ((peerPlayerLeftCallback)data->callback)(peer, params->roomType, nick_W, reason_W, data->callbackParam); + gsifree(nick_W); + gsifree(reason_W); + } +#endif +} +void piAddPlayerLeftCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * reason +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->playerLeft) + { + piPlayerLeftParams params; + params.roomType = roomType; + params.nick = (char *)nick; + params.reason = (char *)reason; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->playerLeft, callbacks->param, PI_PLAYER_LEFT_CALLBACK, ¶ms, sizeof(piPlayerLeftParams), -1); + } +} + +/* Kicked. +*********/ +typedef struct piKickedParams +{ + RoomType roomType; + char * nick; + char * reason; +} piKickedParams; +static PEERBool piKickedCopy(void * paramsOut_, void * paramsIn_) +{ + piKickedParams * paramsOut = (piKickedParams *)paramsOut_; + piKickedParams * paramsIn = (piKickedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + if(paramsIn->reason) + { + paramsOut->reason = goastrdup(paramsIn->reason); + if(!paramsOut->reason) + { + gsifree(paramsOut->nick); + return PEERFalse; + } + } + else + paramsOut->reason = NULL; + + return PEERTrue; +} +static void piKickedFree(void * params_) +{ + piKickedParams * params = (piKickedParams *)params_; + + assert(params); + + gsifree(params->nick); + gsifree(params->reason); +} +static void piKickedCall(PEER peer, piCallbackData * data) +{ + piKickedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_KICKED_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerKickedCallback)data->callback)(peer, params->roomType, params->nick, params->reason, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short* reason_W = UTF8ToUCS2StringAlloc(params->reason); + ((peerKickedCallback)data->callback)(peer, params->roomType, nick_W, reason_W, data->callbackParam); + gsifree(nick_W); + gsifree(reason_W); + } +#endif +} +void piAddKickedCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * reason +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->kicked) + { + piKickedParams params; + params.roomType = roomType; + params.nick = (char *)nick; + params.reason = (char *)reason; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->kicked, callbacks->param, PI_KICKED_CALLBACK, ¶ms, sizeof(piKickedParams), -1); + } +} + +/* New Player List. +******************/ +typedef struct piNewPlayerListParams +{ + RoomType roomType; +} piNewPlayerListParams; +static PEERBool piNewPlayerListCopy(void * paramsOut_, void * paramsIn_) +{ + piNewPlayerListParams * paramsOut = (piNewPlayerListParams *)paramsOut_; + piNewPlayerListParams * paramsIn = (piNewPlayerListParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + + return PEERTrue; +} +static void piNewPlayerListFree(void * params_) +{ + piNewPlayerListParams * params = (piNewPlayerListParams *)params_; + + assert(params); + GSI_UNUSED(params); +} +static void piNewPlayerListCall(PEER peer, piCallbackData * data) +{ + piNewPlayerListParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_NEW_PLAYER_LIST_CALLBACK); + + params = data->params; + ((peerNewPlayerListCallback)data->callback)(peer, params->roomType, data->callbackParam); +} +void piAddNewPlayerListCallback +( + PEER peer, + RoomType roomType +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->newPlayerList) + { + piNewPlayerListParams params; + params.roomType = roomType; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->newPlayerList, callbacks->param, PI_NEW_PLAYER_LIST_CALLBACK, ¶ms, sizeof(piNewPlayerListParams), -1); + } +} + +/* Player Changed Nick. +**********************/ +typedef struct piPlayerChangedNickParams +{ + RoomType roomType; + char * oldNick; + char * newNick; +} piPlayerChangedNickParams; +static PEERBool piPlayerChangedNickCopy(void * paramsOut_, void * paramsIn_) +{ + piPlayerChangedNickParams * paramsOut = (piPlayerChangedNickParams *)paramsOut_; + piPlayerChangedNickParams * paramsIn = (piPlayerChangedNickParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + if(paramsIn->oldNick) + { + paramsOut->oldNick = goastrdup(paramsIn->oldNick); + if(!paramsOut->oldNick) + return PEERFalse; + } + else + paramsOut->oldNick = NULL; + if(paramsIn->newNick) + { + paramsOut->newNick = goastrdup(paramsIn->newNick); + if(!paramsOut->newNick) + { + gsifree(paramsOut->oldNick); + return PEERFalse; + } + } + else + paramsOut->newNick = NULL; + + return PEERTrue; +} +static void piPlayerChangedNickFree(void * params_) +{ + piPlayerChangedNickParams * params = (piPlayerChangedNickParams *)params_; + + assert(params); + + gsifree(params->oldNick); + gsifree(params->newNick); +} +static void piPlayerChangedNickCall(PEER peer, piCallbackData * data) +{ + piPlayerChangedNickParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_PLAYER_CHANGED_NICK_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerPlayerChangedNickCallback)data->callback)(peer, params->roomType, params->oldNick, params->newNick, data->callbackParam); +#else + { + unsigned short* oldNick_W = UTF8ToUCS2StringAlloc(params->oldNick); + unsigned short* newNick_W = UTF8ToUCS2StringAlloc(params->newNick); + ((peerPlayerChangedNickCallback)data->callback)(peer, params->roomType, oldNick_W, newNick_W, data->callbackParam); + gsifree(oldNick_W); + gsifree(newNick_W); + } +#endif +} +void piAddPlayerChangedNickCallback +( + PEER peer, + RoomType roomType, + const char * oldNick, + const char * newNick +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->playerChangedNick) + { + piPlayerChangedNickParams params; + params.roomType = roomType; + params.oldNick = (char *)oldNick; + params.newNick = (char *)newNick; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->playerChangedNick, callbacks->param, PI_PLAYER_CHANGED_NICK_CALLBACK, ¶ms, sizeof(piPlayerChangedNickParams), -1); + } +} + +/* Player Info. +**************/ +typedef struct piPlayerInfoParams +{ + RoomType roomType; + char * nick; + unsigned int IP; + int profileID; +} piPlayerInfoParams; +static PEERBool piPlayerInfoCopy(void * paramsOut_, void * paramsIn_) +{ + piPlayerInfoParams * paramsOut = (piPlayerInfoParams *)paramsOut_; + piPlayerInfoParams * paramsIn = (piPlayerInfoParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + paramsOut->IP = paramsIn->IP; + paramsOut->profileID = paramsIn->profileID; + + return PEERTrue; +} +static void piPlayerInfoFree(void * params_) +{ + piPlayerInfoParams * params = (piPlayerInfoParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piPlayerInfoCall(PEER peer, piCallbackData * data) +{ + piPlayerInfoParams * params; + PEER_CONNECTION; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_PLAYER_INFO_CALLBACK); + + params = data->params; + + // Don't call this if we're not in the room anymore. + //////////////////////////////////////////////////// + if(!connection->inRoom[params->roomType]) + return; + +#ifndef GSI_UNICODE + ((peerPlayerInfoCallback)data->callback)(peer, params->roomType, params->nick, params->IP, params->profileID, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerPlayerInfoCallback)data->callback)(peer, params->roomType, nick_W, params->IP, params->profileID, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddPlayerInfoCallback +( + PEER peer, + RoomType roomType, + const char * nick, + unsigned int IP, + int profileID +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->playerInfo) + { + piPlayerInfoParams params; + params.roomType = roomType; + params.nick = (char *)nick; + params.IP = IP; + params.profileID = profileID; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->playerInfo, callbacks->param, PI_PLAYER_INFO_CALLBACK, ¶ms, sizeof(piPlayerInfoParams), -1); + } +} + +/* Disconnected. +***************/ +typedef struct piDisconnectedParams +{ + char * reason; +} piDisconnectedParams; +static PEERBool piDisconnectedCopy(void * paramsOut_, void * paramsIn_) +{ + piDisconnectedParams * paramsOut = (piDisconnectedParams *)paramsOut_; + piDisconnectedParams * paramsIn = (piDisconnectedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + if(paramsIn->reason) + { + paramsOut->reason = goastrdup(paramsIn->reason); + if(!paramsOut->reason) + return PEERFalse; + } + else + paramsOut->reason = NULL; + + return PEERTrue; +} +static void piDisconnectedFree(void * params_) +{ + piDisconnectedParams * params = (piDisconnectedParams *)params_; + + assert(params); + + gsifree(params->reason); +} +static void piDisconnectedCall(PEER peer, piCallbackData * data) +{ + piDisconnectedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_DISCONNECTED_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerDisconnectedCallback)data->callback)(peer, params->reason, data->callbackParam); +#else + { + unsigned short* reason_W = UTF8ToUCS2StringAlloc(params->reason); + ((peerDisconnectedCallback)data->callback)(peer, reason_W, data->callbackParam); + gsifree(reason_W); + } +#endif +} +void piAddDisconnectedCallback +( + PEER peer, + const char * reason +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->disconnected) + { + piDisconnectedParams params; + params.reason = (char *)reason; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->disconnected, callbacks->param, PI_DISCONNECTED_CALLBACK, ¶ms, sizeof(piDisconnectedParams), -1); + } +} + +/* Ping. +*******/ +typedef struct piPingParams +{ + char * nick; + int ping; +} piPingParams; +static PEERBool piPingCopy(void * paramsOut_, void * paramsIn_) +{ + piPingParams * paramsOut = (piPingParams *)paramsOut_; + piPingParams * paramsIn = (piPingParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->ping = paramsIn->ping; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + + return PEERTrue; +} +static void piPingFree(void * params_) +{ + piPingParams * params = (piPingParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piPingCall(PEER peer, piCallbackData * data) +{ + piPingParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_PING_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerPingCallback)data->callback)(peer, params->nick, params->ping, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerPingCallback)data->callback)(peer, nick_W, params->ping, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddPingCallback +( + PEER peer, + const char * nick, + int ping +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->ping) + { + piPingParams params; + params.nick = (char *)nick; + params.ping = ping; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->ping, callbacks->param, PI_PING_CALLBACK, ¶ms, sizeof(piPingParams), -1); + } +} + +/* CrossPing. +************/ +typedef struct piCrossPingParams +{ + char * nick1; + char * nick2; + int crossPing; +} piCrossPingParams; +static PEERBool piCrossPingCopy(void * paramsOut_, void * paramsIn_) +{ + piCrossPingParams * paramsOut = (piCrossPingParams *)paramsOut_; + piCrossPingParams * paramsIn = (piCrossPingParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->crossPing = paramsIn->crossPing; + if(paramsIn->nick1) + { + paramsOut->nick1 = goastrdup(paramsIn->nick1); + if(!paramsOut->nick1) + return PEERFalse; + } + else + paramsOut->nick1 = NULL; + if(paramsIn->nick2) + { + paramsOut->nick2 = goastrdup(paramsIn->nick2); + if(!paramsOut->nick2) + { + gsifree(paramsOut->nick1); + return PEERFalse; + } + } + else + paramsOut->nick2 = NULL; + + return PEERTrue; +} +static void piCrossPingFree(void * params_) +{ + piCrossPingParams * params = (piCrossPingParams *)params_; + + assert(params); + + gsifree(params->nick1); + gsifree(params->nick2); +} +static void piCrossPingCall(PEER peer, piCallbackData * data) +{ + piCrossPingParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_CROSS_PING_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerCrossPingCallback)data->callback)(peer, params->nick1, params->nick2, params->crossPing, data->callbackParam); +#else + { + unsigned short* nick1_W = UTF8ToUCS2StringAlloc(params->nick1); + unsigned short* nick2_W = UTF8ToUCS2StringAlloc(params->nick2); + ((peerCrossPingCallback)data->callback)(peer, nick1_W, nick2_W, params->crossPing, data->callbackParam); + gsifree(nick1_W); + gsifree(nick2_W); + } +#endif +} +void piAddCrossPingCallback +( + PEER peer, + const char * nick1, + const char * nick2, + int crossPing +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->crossPing) + { + piCrossPingParams params; + params.nick1 = (char *)nick1; + params.nick2 = (char *)nick2; + params.crossPing = crossPing; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->crossPing, callbacks->param, PI_CROSS_PING_CALLBACK, ¶ms, sizeof(piCrossPingParams), -1); + } +} + +/* Change Nick. +**************/ +typedef struct piChangeNickParams +{ + char * oldNick; + char * newNick; +} piChangeNickParams; +static PEERBool piChangeNickCopy(void * paramsOut_, void * paramsIn_) +{ + piChangeNickParams * paramsOut = (piChangeNickParams *)paramsOut_; + piChangeNickParams * paramsIn = (piChangeNickParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + if(paramsIn->newNick) + { + paramsOut->newNick = goastrdup(paramsIn->newNick); + if(!paramsOut->newNick) + return PEERFalse; + } + else + paramsOut->newNick = NULL; + if(paramsIn->oldNick) + { + paramsOut->oldNick = goastrdup(paramsIn->oldNick); + if(!paramsOut->oldNick) + { + gsifree(paramsOut->newNick); + return PEERFalse; + } + } + else + paramsOut->oldNick = NULL; + + return PEERTrue; +} +static void piChangeNickFree(void * params_) +{ + piChangeNickParams * params = (piChangeNickParams *)params_; + + assert(params); + + gsifree(params->newNick); + gsifree(params->oldNick); +} +static void piChangeNickCall(PEER peer, piCallbackData * data) +{ + piChangeNickParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_CHANGE_NICK_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerChangeNickCallback)data->callback)(peer, data->success, params->oldNick, params->newNick, data->callbackParam); +#else + { + unsigned short* oldNick_W = UTF8ToUCS2StringAlloc(params->oldNick); + unsigned short* newNick_W = UTF8ToUCS2StringAlloc(params->newNick); + ((peerChangeNickCallback)data->callback)(peer, data->success, oldNick_W, newNick_W, data->callbackParam); + gsifree(oldNick_W); + gsifree(newNick_W); + } +#endif +} +void piAddChangeNickCallback +( + PEER peer, + PEERBool success, + const char * oldNick, + const char * newNick, + peerChangeNickCallback callback, + void * param, + int opID +) +{ + piChangeNickParams params; + params.newNick = (char *)newNick; + params.oldNick = (char *)oldNick; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_CHANGE_NICK_CALLBACK, ¶ms, sizeof(piChangeNickParams), opID); +} + +/* GlobalKeyChanged. +*******************/ +typedef struct piGlobalGlobalKeyChangedParams +{ + RoomType roomType; + char * nick; + char * key; + char * value; +} piGlobalKeyChangedParams; +static PEERBool piGlobalKeyChangedCopy(void * paramsOut_, void * paramsIn_) +{ + piGlobalKeyChangedParams * paramsOut = (piGlobalKeyChangedParams *)paramsOut_; + piGlobalKeyChangedParams * paramsIn = (piGlobalKeyChangedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + if(paramsIn->key) + { + paramsOut->key = goastrdup(paramsIn->key); + if(!paramsOut->key) + { + gsifree(paramsOut->nick); + return PEERFalse; + } + } + else + paramsOut->key = NULL; + if(paramsIn->value) + { + paramsOut->value = goastrdup(paramsIn->value); + if(!paramsOut->value) + { + gsifree(paramsOut->nick); + gsifree(paramsOut->key); + return PEERFalse; + } + } + else + paramsOut->value = NULL; + + return PEERTrue; +} +static void piGlobalKeyChangedFree(void * params_) +{ + piGlobalKeyChangedParams * params = (piGlobalKeyChangedParams *)params_; + + assert(params); + + gsifree(params->nick); + gsifree(params->key); + gsifree(params->value); +} +static void piGlobalKeyChangedCall(PEER peer, piCallbackData * data) +{ + piGlobalKeyChangedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_GLOBAL_KEY_CHANGED_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerGlobalKeyChangedCallback)data->callback)(peer, params->nick, params->key, params->value, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short* key_W = UTF8ToUCS2StringAlloc(params->key); + unsigned short* value_W = UTF8ToUCS2StringAlloc(params->value); + ((peerGlobalKeyChangedCallback)data->callback)(peer, nick_W, key_W, value_W, data->callbackParam); + gsifree(nick_W); + gsifree(key_W); + gsifree(value_W); + } +#endif +} +void piAddGlobalKeyChangedCallback +( + PEER peer, + const char * nick, + const char * key, + const char * value +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->globalKeyChanged) + { + piGlobalKeyChangedParams params; + params.nick = (char *)nick; + params.key = (char *)key; + params.value = (char *)value; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->globalKeyChanged, callbacks->param, PI_GLOBAL_KEY_CHANGED_CALLBACK, ¶ms, sizeof(piGlobalKeyChangedParams), -1); + } +} + +/* RoomKeyChanged. +*****************/ +typedef struct piRoomKeyChangedParams +{ + RoomType roomType; + char * nick; + char * key; + char * value; +} piRoomKeyChangedParams; +static PEERBool piRoomKeyChangedCopy(void * paramsOut_, void * paramsIn_) +{ + piRoomKeyChangedParams * paramsOut = (piRoomKeyChangedParams *)paramsOut_; + piRoomKeyChangedParams * paramsIn = (piRoomKeyChangedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + if(paramsIn->key) + { + paramsOut->key = goastrdup(paramsIn->key); + if(!paramsOut->key) + { + gsifree(paramsOut->nick); + return PEERFalse; + } + } + else + paramsOut->key = NULL; + if(paramsIn->value) + { + paramsOut->value = goastrdup(paramsIn->value); + if(!paramsOut->value) + { + gsifree(paramsOut->nick); + gsifree(paramsOut->key); + return PEERFalse; + } + } + else + paramsOut->value = NULL; + + return PEERTrue; +} +static void piRoomKeyChangedFree(void * params_) +{ + piRoomKeyChangedParams * params = (piRoomKeyChangedParams *)params_; + + assert(params); + + gsifree(params->nick); + gsifree(params->key); + gsifree(params->value); +} +static void piRoomKeyChangedCall(PEER peer, piCallbackData * data) +{ + piRoomKeyChangedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_ROOM_KEY_CHANGED_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerRoomKeyChangedCallback)data->callback)(peer, params->roomType, params->nick, params->key, params->value, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short* key_W = UTF8ToUCS2StringAlloc(params->key); + unsigned short* value_W = UTF8ToUCS2StringAlloc(params->value); + ((peerRoomKeyChangedCallback)data->callback)(peer, params->roomType, nick_W, key_W, value_W, data->callbackParam); + gsifree(nick_W); + gsifree(key_W); + gsifree(value_W); + } +#endif +} +void piAddRoomKeyChangedCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * key, + const char * value +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->roomKeyChanged) + { + piRoomKeyChangedParams params; + params.roomType = roomType; + params.nick = (char *)nick; + params.key = (char *)key; + params.value = (char *)value; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->roomKeyChanged, callbacks->param, PI_ROOM_KEY_CHANGED_CALLBACK, ¶ms, sizeof(piRoomKeyChangedParams), -1); + } +} + +/* GetGlobalKeys. +****************/ +typedef struct piGetGlobalKeysParams +{ + char * nick; + int num; + char ** keys; + char ** values; +} piGetGlobalKeysParams; +static PEERBool piGetGlobalKeysCopy(void * paramsOut_, void * paramsIn_) +{ + int i; + int num; + PEERBool success = PEERTrue; + piGetGlobalKeysParams * paramsOut = (piGetGlobalKeysParams *)paramsOut_; + piGetGlobalKeysParams * paramsIn = (piGetGlobalKeysParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + memset(paramsOut, 0, sizeof(piGetGlobalKeysParams)); + num = paramsOut->num = paramsIn->num; + + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + success = PEERFalse; + } + else + paramsOut->nick = NULL; + + if(success && num) + { + paramsOut->keys = (char **)gsimalloc(sizeof(char *) * num); + if(!paramsOut->keys) + success = PEERFalse; + else + memset(paramsOut->keys, 0, sizeof(char *) * num); + } + + if(success && num && paramsIn->values) + { + paramsOut->values = (char **)gsimalloc(sizeof(char *) * num); + if(!paramsOut->values) + success = PEERFalse; + else + memset(paramsOut->values, 0, sizeof(char *) * num); + } + + if(success && num && paramsIn->values) + { + for(i = 0 ; success && (i < num) ; i++) + { + paramsOut->keys[i] = goastrdup(paramsIn->keys[i]); + if(!paramsOut->keys[i]) + success = PEERFalse; + else + { + paramsOut->values[i] = goastrdup(paramsIn->values[i]); + if(!paramsOut->values[i]) + success = PEERFalse; + } + } + } + + if(!success) + { + gsifree(paramsOut->nick); + for(i = 0 ; i < num ; i++) + { + if(paramsOut->keys) + gsifree(paramsOut->keys[i]); + if(paramsOut->values) + gsifree(paramsOut->values[i]); + } + gsifree(paramsOut->keys); + gsifree(paramsOut->values); + } + + return success; +} +static void piGetGlobalKeysFree(void * params_) +{ + int i; + piGetGlobalKeysParams * params = (piGetGlobalKeysParams *)params_; + + assert(params); + + gsifree(params->nick); + for(i = 0 ; i < params->num ; i++) + { + gsifree(params->keys[i]); + if(params->values) + gsifree(params->values[i]); + } + gsifree(params->keys); + gsifree(params->values); +} +static void piGetGlobalKeysCall(PEER peer, piCallbackData * data) +{ + piGetGlobalKeysParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_GET_GLOBAL_KEYS_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerGetGlobalKeysCallback)data->callback)(peer, data->success, params->nick, params->num, (const char **)params->keys, (const char **)params->values, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short** keys_W = UTF8ToUCS2StringArrayAlloc((const UTF8String *)params->keys, params->num); + unsigned short** values_W = UTF8ToUCS2StringArrayAlloc((const UTF8String *)params->values, params->num); + int i; + ((peerGetGlobalKeysCallback)data->callback)(peer, data->success, nick_W, params->num, (const unsigned short**)keys_W, (const unsigned short**)values_W, data->callbackParam); + gsifree(nick_W); + for (i=0; i < params->num; i++) + { + gsifree(keys_W[i]); + if (values_W != NULL) // may be a NULL when getting keys for "*" + gsifree(values_W[i]); + } + gsifree(keys_W); + gsifree(values_W); + } +#endif +} +void piAddGetGlobalKeysCallback +( + PEER peer, + PEERBool success, + const char * nick, + int num, + const char ** keys, + const char ** values, + peerGetGlobalKeysCallback callback, + void * param, + int opID +) +{ + piGetGlobalKeysParams params; + params.nick = (char *)nick; + params.num = num; + params.keys = (char **)keys; + params.values = (char **)values; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_GET_GLOBAL_KEYS_CALLBACK, ¶ms, sizeof(piGetGlobalKeysParams), opID); +} + +/* GetRoomKeys. +****************/ +typedef struct piGetRoomKeysParams +{ + RoomType roomType; + char * nick; + int num; + char ** keys; + char ** values; +} piGetRoomKeysParams; +static PEERBool piGetRoomKeysCopy(void * paramsOut_, void * paramsIn_) +{ + int i; + int num; + PEERBool success = PEERTrue; + piGetRoomKeysParams * paramsOut = (piGetRoomKeysParams *)paramsOut_; + piGetRoomKeysParams * paramsIn = (piGetRoomKeysParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + memset(paramsOut, 0, sizeof(piGetRoomKeysParams)); + num = paramsOut->num = paramsIn->num; + + paramsOut->roomType = paramsIn->roomType; + + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + success = PEERFalse; + } + else + paramsOut->nick = NULL; + + if(success && num) + { + paramsOut->keys = (char **)gsimalloc(sizeof(char *) * num); + if(!paramsOut->keys) + success = PEERFalse; + else + memset(paramsOut->keys, 0, sizeof(char *) * num); + } + + if(success && num && paramsIn->values) + { + paramsOut->values = (char **)gsimalloc(sizeof(char *) * num); + if(!paramsOut->values) + success = PEERFalse; + else + memset(paramsOut->values, 0, sizeof(char *) * num); + } + + if(success && num && paramsIn->values) + { + for(i = 0 ; success && (i < num) ; i++) + { + paramsOut->keys[i] = goastrdup(paramsIn->keys[i]); + if(!paramsOut->keys[i]) + success = PEERFalse; + else if(paramsOut->values) + { + paramsOut->values[i] = goastrdup(paramsIn->values[i]); + if(!paramsOut->values[i]) + success = PEERFalse; + } + } + } + + if(!success) + { + gsifree(paramsOut->nick); + for(i = 0 ; i < num ; i++) + { + if(paramsOut->keys) + gsifree(paramsOut->keys[i]); + if(paramsOut->values) + gsifree(paramsOut->values[i]); + } + gsifree(paramsOut->keys); + gsifree(paramsOut->values); + } + + return success; +} +static void piGetRoomKeysFree(void * params_) +{ + int i; + piGetRoomKeysParams * params = (piGetRoomKeysParams *)params_; + + assert(params); + + gsifree(params->nick); + for(i = 0 ; i < params->num ; i++) + { + gsifree(params->keys[i]); + if(params->values) + gsifree(params->values[i]); + } + gsifree(params->keys); + gsifree(params->values); +} +static void piGetRoomKeysCall(PEER peer, piCallbackData * data) +{ + piGetRoomKeysParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_GET_ROOM_KEYS_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerGetRoomKeysCallback)data->callback)(peer, data->success, params->roomType, params->nick, params->num, params->keys, params->values, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + unsigned short** keys_W = UTF8ToUCS2StringArrayAlloc((const UTF8String *)params->keys, params->num); + unsigned short** values_W = UTF8ToUCS2StringArrayAlloc((const UTF8String *)params->values, params->num); + int i; + ((peerGetRoomKeysCallback)data->callback)(peer, data->success, params->roomType, nick_W, params->num, keys_W, values_W, data->callbackParam); + gsifree(nick_W); + for (i=0; i < params->num; i++) + { + gsifree(keys_W[i]); + if (values_W != NULL) // may be a NULL when getting keys for "*" + gsifree(values_W[i]); + } + gsifree(keys_W); + gsifree(values_W); + } +#endif +} +void piAddGetRoomKeysCallback +( + PEER peer, + PEERBool success, + RoomType roomType, + const char * nick, + int num, + const char ** keys, + const char ** values, + peerGetRoomKeysCallback callback, + void * param, + int opID +) +{ + piGetRoomKeysParams params; + params.roomType = roomType; + params.nick = (char *)nick; + params.num = num; + params.keys = (char **)keys; + params.values = (char **)values; + + piAddCallback(peer, success, (PEERCBType)callback, param, PI_GET_ROOM_KEYS_CALLBACK, ¶ms, sizeof(piGetRoomKeysParams), opID); +} + +/* PlayerFlagsChanged. +*********************/ +typedef struct piPlayerFlagsChangedParams +{ + RoomType roomType; + char * nick; + int oldFlags; + int newFlags; +} piPlayerFlagsChangedParams; +static PEERBool piPlayerFlagsChangedCopy(void * paramsOut_, void * paramsIn_) +{ + piPlayerFlagsChangedParams * paramsOut = (piPlayerFlagsChangedParams *)paramsOut_; + piPlayerFlagsChangedParams * paramsIn = (piPlayerFlagsChangedParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->roomType = paramsIn->roomType; + paramsOut->newFlags = paramsIn->newFlags; + paramsOut->oldFlags = paramsIn->oldFlags; + if(paramsIn->nick) + { + paramsOut->nick = goastrdup(paramsIn->nick); + if(!paramsOut->nick) + return PEERFalse; + } + else + paramsOut->nick = NULL; + + return PEERTrue; +} +static void piPlayerFlagsChangedFree(void * params_) +{ + piPlayerFlagsChangedParams * params = (piPlayerFlagsChangedParams *)params_; + + assert(params); + + gsifree(params->nick); +} +static void piPlayerFlagsChangedCall(PEER peer, piCallbackData * data) +{ + piPlayerFlagsChangedParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_PLAYER_FLAGS_CHANGED_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerPlayerFlagsChangedCallback)data->callback)(peer, params->roomType, params->nick, params->oldFlags, params->newFlags, data->callbackParam); +#else + { + unsigned short* nick_W = UTF8ToUCS2StringAlloc(params->nick); + ((peerPlayerFlagsChangedCallback)data->callback)(peer, params->roomType, nick_W, params->oldFlags, params->newFlags, data->callbackParam); + gsifree(nick_W); + } +#endif +} +void piAddPlayerFlagsChangedCallback +( + PEER peer, + RoomType roomType, + const char * nick, + int oldFlags, + int newFlags +) +{ + PEERCallbacks * callbacks; + + PEER_CONNECTION; + + callbacks = &connection->callbacks; + if(callbacks->playerFlagsChanged) + { + piPlayerFlagsChangedParams params; + params.roomType = roomType; + params.nick = (char *)nick; + params.oldFlags = oldFlags; + params.newFlags = newFlags; + + piAddCallback(peer, PEERTrue, (PEERCBType)callbacks->playerFlagsChanged, callbacks->param, PI_PLAYER_FLAGS_CHANGED_CALLBACK, ¶ms, sizeof(piPlayerFlagsChangedParams), -1); + } +} + +/* Authenticate CD Key. +**********************/ +typedef struct piAuthenticateCDKeyParams +{ + int result; + char * message; +} piAuthenticateCDKeyParams; +static PEERBool piAuthenticateCDKeyCopy(void * paramsOut_, void * paramsIn_) +{ + piAuthenticateCDKeyParams * paramsOut = (piAuthenticateCDKeyParams *)paramsOut_; + piAuthenticateCDKeyParams * paramsIn = (piAuthenticateCDKeyParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->result = paramsIn->result; + if(paramsIn->message) + { + paramsOut->message = goastrdup(paramsIn->message); + if(!paramsOut->message) + return PEERFalse; + } + else + paramsOut->message = NULL; + + return PEERTrue; +} +static void piAuthenticateCDKeyFree(void * params_) +{ + piAuthenticateCDKeyParams * params = (piAuthenticateCDKeyParams *)params_; + + assert(params); + + gsifree(params->message); +} +static void piAuthenticateCDKeyCall(PEER peer, piCallbackData * data) +{ + piAuthenticateCDKeyParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_AUTHENTICATE_CDKEY_CALLBACK); + + params = data->params; +#ifndef GSI_UNICODE + ((peerAuthenticateCDKeyCallback)data->callback)(peer, params->result, params->message, data->callbackParam); +#else + { + unsigned short* message_W = UTF8ToUCS2StringAlloc(params->message); + ((peerAuthenticateCDKeyCallback)data->callback)(peer, params->result, message_W, data->callbackParam); + gsifree(message_W); + } +#endif +} +void piAddAuthenticateCDKeyCallback +( + PEER peer, + int result, + const char * message, + peerAuthenticateCDKeyCallback callback, + void * param, + int opID +) +{ + piAuthenticateCDKeyParams params; + params.result = result; + params.message = (char *)message; + + piAddCallback(peer, PEERTrue, (PEERCBType)callback, param, PI_AUTHENTICATE_CDKEY_CALLBACK, ¶ms, sizeof(piAuthenticateCDKeyParams), opID); +} + +/* AutoMatch Status. +*******************/ +typedef struct piAutoMatchStatusParams +{ + PEERAutoMatchStatus status; +} piAutoMatchStatusParams; +static PEERBool piAutoMatchStatusCopy(void * paramsOut_, void * paramsIn_) +{ + piAutoMatchStatusParams * paramsOut = (piAutoMatchStatusParams *)paramsOut_; + piAutoMatchStatusParams * paramsIn = (piAutoMatchStatusParams *)paramsIn_; + + assert(paramsOut); + assert(paramsIn); + + paramsOut->status = paramsIn->status; + + return PEERTrue; +} +static void piAutoMatchStatusFree(void * params_) +{ + piAutoMatchStatusParams * params = (piAutoMatchStatusParams *)params_; + + assert(params); + GSI_UNUSED(params); +} +static void piAutoMatchStatusCall(PEER peer, piCallbackData * data) +{ + piAutoMatchStatusParams * params; + + assert(data); + assert(data->callback); + assert(data->params); + assert(data->type == PI_AUTO_MATCH_STATUS_CALLBACK); + + params = data->params; + ((peerAutoMatchStatusCallback)data->callback)(peer, params->status, data->callbackParam); +} +void piAddAutoMatchStatusCallback +( + PEER peer +) +{ + piAutoMatchStatusParams params; + piOperation * operation; + + PEER_CONNECTION; + + operation = connection->autoMatchOperation; + if(!operation || !operation->callback) + return; + + params.status = connection->autoMatchStatus; + + piAddCallback(peer, PEERTrue, operation->callback, operation->callbackParam, PI_AUTO_MATCH_STATUS_CALLBACK, ¶ms, sizeof(piAutoMatchStatusParams), operation->ID); +} + +/* AutoMatch Rate. +*****************/ +int piCallAutoMatchRateCallback +( + PEER peer, + SBServer server +) +{ + piOperation * operation; + + PEER_CONNECTION; + + operation = connection->autoMatchOperation; + if(!operation || !operation->callback2) + return 0; + + return ((peerAutoMatchRateCallback)connection->autoMatchOperation->callback2)(peer, server, operation->callbackParam); +} + +/************ +** GLOBALS ** +************/ +static const piCallbackFuncs callbackFuncs[] = +{ + { + PI_CONNECT_CALLBACK, + piConnectCopy, + piConnectFree, + piConnectCall + }, + { + PI_JOIN_ROOM_CALLBACK, + piJoinRoomCopy, + piJoinRoomFree, + piJoinRoomCall + }, + { + PI_LIST_GROUP_ROOMS_CALLBACK, + piListGroupRoomsCopy, + piListGroupRoomsFree, + piListGroupRoomsCall + }, + { + PI_LISTING_GAMES_CALLBACK, + piListingGamesCopy, + piListingGamesFree, + piListingGamesCall + }, + { + PI_NICK_ERROR_CALLBACK, + piNickErrorCopy, + piNickErrorFree, + piNickErrorCall + }, + { + PI_ENUM_PLAYERS_CALLBACK, + piEnumPlayersCopy, + piEnumPlayersFree, + piEnumPlayersCall + }, + { + PI_GET_PLAYER_INFO_CALLBACK, + piGetPlayerInfoCopy, + piGetPlayerInfoFree, + piGetPlayerInfoCall + }, + { + PI_GET_PLAYER_PROFILE_ID_CALLBACK, + piGetPlayerProfileIDCopy, + piGetPlayerProfileIDFree, + piGetPlayerProfileIDCall + }, + { + PI_GET_PLAYER_IP_CALLBACK, + piGetPlayerIPCopy, + piGetPlayerIPFree, + piGetPlayerIPCall + }, + { + PI_ROOM_MESSAGE_CALLBACK, + piRoomMessageCopy, + piRoomMessageFree, + piRoomMessageCall + }, + { + PI_ROOM_UTM_CALLBACK, + piRoomUTMCopy, + piRoomUTMFree, + piRoomUTMCall + }, + { + PI_ROOM_NAME_CHANGED_CALLBACK, + piRoomNameChangedCopy, + piRoomNameChangedFree, + piRoomNameChangedCall + }, + { + PI_ROOM_MODE_CHANGED_CALLBACK, + piRoomModeChangedCopy, + piRoomModeChangedFree, + piRoomModeChangedCall + }, + { + PI_PLAYER_MESSAGE_CALLBACK, + piPlayerMessageCopy, + piPlayerMessageFree, + piPlayerMessageCall + }, + { + PI_PLAYER_UTM_CALLBACK, + piPlayerUTMCopy, + piPlayerUTMFree, + piPlayerUTMCall + }, + { + PI_READY_CHANGED_CALLBACK, + piReadyChangedCopy, + piReadyChangedFree, + piReadyChangedCall + }, + { + PI_GAME_STARTED_CALLBACK, + piGameStartedCopy, + piGameStartedFree, + piGameStartedCall + }, + { + PI_PLAYER_JOINED_CALLBACK, + piPlayerJoinedCopy, + piPlayerJoinedFree, + piPlayerJoinedCall + }, + { + PI_PLAYER_LEFT_CALLBACK, + piPlayerLeftCopy, + piPlayerLeftFree, + piPlayerLeftCall + }, + { + PI_KICKED_CALLBACK, + piKickedCopy, + piKickedFree, + piKickedCall + }, + { + PI_NEW_PLAYER_LIST_CALLBACK, + piNewPlayerListCopy, + piNewPlayerListFree, + piNewPlayerListCall + }, + { + PI_PLAYER_CHANGED_NICK_CALLBACK, + piPlayerChangedNickCopy, + piPlayerChangedNickFree, + piPlayerChangedNickCall + }, + { + PI_PLAYER_INFO_CALLBACK, + piPlayerInfoCopy, + piPlayerInfoFree, + piPlayerInfoCall + }, + { + PI_DISCONNECTED_CALLBACK, + piDisconnectedCopy, + piDisconnectedFree, + piDisconnectedCall + }, + { + PI_PING_CALLBACK, + piPingCopy, + piPingFree, + piPingCall + }, + { + PI_CROSS_PING_CALLBACK, + piCrossPingCopy, + piCrossPingFree, + piCrossPingCall + }, + { + PI_CHANGE_NICK_CALLBACK, + piChangeNickCopy, + piChangeNickFree, + piChangeNickCall + }, + { + PI_GLOBAL_KEY_CHANGED_CALLBACK, + piGlobalKeyChangedCopy, + piGlobalKeyChangedFree, + piGlobalKeyChangedCall + }, + { + PI_ROOM_KEY_CHANGED_CALLBACK, + piRoomKeyChangedCopy, + piRoomKeyChangedFree, + piRoomKeyChangedCall + }, + { + PI_GET_GLOBAL_KEYS_CALLBACK, + piGetGlobalKeysCopy, + piGetGlobalKeysFree, + piGetGlobalKeysCall + }, + { + PI_GET_ROOM_KEYS_CALLBACK, + piGetRoomKeysCopy, + piGetRoomKeysFree, + piGetRoomKeysCall + }, + { + PI_PLAYER_FLAGS_CHANGED_CALLBACK, + piPlayerFlagsChangedCopy, + piPlayerFlagsChangedFree, + piPlayerFlagsChangedCall + }, + { + PI_AUTHENTICATE_CDKEY_CALLBACK, + piAuthenticateCDKeyCopy, + piAuthenticateCDKeyFree, + piAuthenticateCDKeyCall + }, + { + PI_AUTO_MATCH_STATUS_CALLBACK, + piAutoMatchStatusCopy, + piAutoMatchStatusFree, + piAutoMatchStatusCall + }, + { + PI_NUM_CALLBACK_TYPES, + NULL, + NULL, + NULL + } +}; + +/************** +** FUNCTIONS ** +**************/ +static void piCallbackListFree(void *elem1) +{ + piCallbackData * data = (piCallbackData *)elem1; + //ASSERT_DATA(data); + + // Call the gsifree func. + ////////////////////// + callbackFuncs[data->type].gsifree(data->params); + + // Cleanup the callback data. + ///////////////////////////// +#ifdef GSI_MANIC_DEBUG + // Set the data to a fill value so we catch the overwrite + memset(data->params, 0xea, sizeof(data->params)); +#endif + gsifree(data->params); + +} + +PEERBool piCallbacksInit +( + PEER peer +) +{ + PEER_CONNECTION; + +#ifdef _DEBUG +{ + // Consistency check. + ///////////////////// + int i; + for(i = 0 ; i <= PI_NUM_CALLBACK_TYPES ; i++) + assert(callbackFuncs[i].type == i); +} +#endif + + // No callbacks yet. + //////////////////// + connection->callbacksQueued = 0; + connection->callbacksCalled = 0; + connection->callbackDepth = 0; + + // Init the list. + ///////////////// + connection->callbackList = ArrayNew(sizeof(piCallbackData), 0, piCallbackListFree); + if(!connection->callbackList) + return PEERFalse; + + return PEERTrue; +} + +void piCallbacksCleanup +( + PEER peer +) +{ + PEER_CONNECTION; + + // gsifree the callback list. + ////////////////////////// + if(connection->callbackList) + ArrayFree(connection->callbackList); +} + +#ifdef GSI_MANIC_DEBUG +#include "stdio.h" +#endif +static void piCallCallback(PEER peer, piCallbackData * data, int index) +{ + PEER_CONNECTION; + + // In the call. + /////////////// + data->inCall = PEERTrue; + connection->callbackDepth++; + + // Call it. + /////////// + callbackFuncs[data->type].call(peer, data); + + // Out of the call. + /////////////////// + data->inCall = PEERFalse; + connection->callbackDepth--; + + // One more called. + /////////////////// + connection->callbacksCalled++; + + // gsifree it. + /////////// + ArrayDeleteAt(connection->callbackList, index); +} + +void piCallbacksThink +( + PEER peer, + int blockingID +) +{ + int index; + int len; + piCallbackData * data; + + PEER_CONNECTION; + + assert(blockingID >= -1); + + // Blocking call? + ///////////////// + if(blockingID != -1) + { + // How many? + //////////// + len = ArrayLength(connection->callbackList); + assert(len >= 0); + + // Check if this callback is finished. + ////////////////////////////////////// + for(index = 0 ; index < len ; index++) + { + // Get the nth element. + /////////////////////// + data = (piCallbackData *)ArrayNth(connection->callbackList, index); + assert(data); + + // Check the ID and specifically for disconnect. + //////////////////////////////////////////////// + if((data->ID == blockingID) || (data->type == PI_DISCONNECTED_CALLBACK)) + { + // Call it. + /////////// + piCallCallback(peer, data, index); + + break; + } + } + } + else + { + int numInCalls = 0; + while(ArrayLength(connection->callbackList) > numInCalls) + { + // Get the callback data. + ///////////////////////// + data = (piCallbackData *)ArrayNth(connection->callbackList, numInCalls); + assert(data); + + // Are we already in this call? (how philosophical) + /////////////////////////////////////////////////// + if(data->inCall) + { + numInCalls++; + } + else + { + // Call it. + /////////// + piCallCallback(peer, data, numInCalls); + } + } + } +} + +static int piAddCallback +( + PEER peer, + PEERBool success, + PEERCBType callback, + void * param, + piCallbackType type, + void * paramsIn, + size_t paramsSize, + int opID +) +{ + piCallbackData data; + void * paramsOut; + + PEER_CONNECTION; + + assert(callback); + //assert(type >= 0); + assert(type < PI_NUM_CALLBACK_TYPES); + assert(paramsIn); + assert(paramsSize > 0); + + // If no callback, nothing to do. + ///////////////////////////////// + if(!callback) + return -1; + + // Allocate the output struct. + ////////////////////////////// + paramsOut = gsimalloc(paramsSize); + if(!paramsOut) + return -1; + + // Zero it. + /////////// + memset(paramsOut, 0, paramsSize); + + // Copy the input to the output. + //////////////////////////////// + if(!callbackFuncs[type].copy(paramsOut, paramsIn)) + { + assert(0); + gsifree(paramsOut); + return -1; + } + + // Fill in the data. + //////////////////// + data.type = type; + data.success = success; + data.callback = callback; + data.callbackParam = param; + data.params = paramsOut; + data.ID = opID; + data.inCall = PEERFalse; + + // Add it to the list. + ////////////////////// + ArrayAppend(connection->callbackList, &data); + connection->callbacksQueued++; + + return data.ID; +} + +static int GS_STATIC_CALLBACK piIsCallbackFinishedCompareCallback +( + const void *elem1, + const void *elem2 +) +{ + piCallbackData * data1 = (piCallbackData *)elem1; + piCallbackData * data2 = (piCallbackData *)elem2; + assert(data1); + assert(data2); + + return (data1->ID - data2->ID); +} + +PEERBool piIsCallbackFinished +( + PEER peer, + int opID +) +{ + int index; + piCallbackData data; + + PEER_CONNECTION; + + // Search for it. + ///////////////// + data.ID = opID; + index = ArraySearch(connection->callbackList, &data, piIsCallbackFinishedCompareCallback, 0, 0); + + return (index == NOT_FOUND)?PEERTrue:PEERFalse; +} + +void piClearCallbacks +( + PEER peer, + piCallbackType type +) +{ + piCallbackData * data; + int len; + int i; + + PEER_CONNECTION; + + len = ArrayLength(connection->callbackList); + for(i = (len - 1) ; i >= 0 ; i--) + { + data = (piCallbackData *)ArrayNth(connection->callbackList, i); + if(data->type == type) + ArrayDeleteAt(connection->callbackList, i); + } +} + +void piClearListingGameServerCallbacks(PEER peer, SBServer server) +{ + piCallbackData * data; + piListingGamesParams * params; + int len; + int i; + + PEER_CONNECTION; + + len = ArrayLength(connection->callbackList); + for(i = (len - 1) ; i >= 0 ; i--) + { + data = (piCallbackData *)ArrayNth(connection->callbackList, i); + if(data->type == PI_LISTING_GAMES_CALLBACK) + { + params = (piListingGamesParams *)data->params; + if(params->server == server) + ArrayDeleteAt(connection->callbackList, i); + } + } +} diff --git a/code/gamespy/Peer/peerCallbacks.h b/code/gamespy/Peer/peerCallbacks.h new file mode 100644 index 00000000..db8475d9 --- /dev/null +++ b/code/gamespy/Peer/peerCallbacks.h @@ -0,0 +1,401 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERCALLBACKS_H_ +#define _PEERCALLBACKS_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/********** +** TYPES ** +**********/ +typedef enum piCallbackType +{ + PI_CONNECT_CALLBACK, + PI_JOIN_ROOM_CALLBACK, + PI_LIST_GROUP_ROOMS_CALLBACK, + PI_LISTING_GAMES_CALLBACK, + PI_NICK_ERROR_CALLBACK, + PI_ENUM_PLAYERS_CALLBACK, + PI_GET_PLAYER_INFO_CALLBACK, + PI_GET_PLAYER_PROFILE_ID_CALLBACK, + PI_GET_PLAYER_IP_CALLBACK, + PI_ROOM_MESSAGE_CALLBACK, + PI_ROOM_UTM_CALLBACK, + PI_ROOM_NAME_CHANGED_CALLBACK, + PI_ROOM_MODE_CHANGED_CALLBACK, + PI_PLAYER_MESSAGE_CALLBACK, + PI_PLAYER_UTM_CALLBACK, + PI_READY_CHANGED_CALLBACK, + PI_GAME_STARTED_CALLBACK, + PI_PLAYER_JOINED_CALLBACK, + PI_PLAYER_LEFT_CALLBACK, + PI_KICKED_CALLBACK, + PI_NEW_PLAYER_LIST_CALLBACK, + PI_PLAYER_CHANGED_NICK_CALLBACK, + PI_PLAYER_INFO_CALLBACK, + PI_DISCONNECTED_CALLBACK, + PI_PING_CALLBACK, + PI_CROSS_PING_CALLBACK, + PI_CHANGE_NICK_CALLBACK, + PI_GLOBAL_KEY_CHANGED_CALLBACK, + PI_ROOM_KEY_CHANGED_CALLBACK, + PI_GET_GLOBAL_KEYS_CALLBACK, + PI_GET_ROOM_KEYS_CALLBACK, + PI_PLAYER_FLAGS_CHANGED_CALLBACK, + PI_AUTHENTICATE_CDKEY_CALLBACK, + PI_AUTO_MATCH_STATUS_CALLBACK, + PI_NUM_CALLBACK_TYPES +} piCallbackType; + +/************** +** FUNCTIONS ** +**************/ +PEERBool piCallbacksInit(PEER peer); +void piCallbacksCleanup(PEER peer); +void piCallbacksThink(PEER peer, int blockingID); +PEERBool piIsCallbackFinished(PEER peer, int opID); +void piClearCallbacks(PEER peer, piCallbackType type); +void piClearListingGameServerCallbacks(PEER peer, SBServer server); + +/************** +** CALLBACKS ** +**************/ + +void piAddConnectCallback +( + PEER peer, + PEERBool success, + int failureReason, + peerConnectCallback callback, + void * param, + int opID +); + +void piAddJoinRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + peerJoinRoomCallback callback, + void * param, + int opID +); + +void piAddListGroupRoomsCallback +( + PEER peer, + PEERBool success, + int groupID, + SBServer server, + const char * name, + int numWaiting, + int maxWaiting, + int numGames, + int numPlaying, + peerListGroupRoomsCallback callback, + void * param, + int opID +); + +void piAddListingGamesCallback +( + PEER peer, + PEERBool success, + SBServer server, + int msg +); + +void piAddEnumPlayersCallback +( + PEER peer, + PEERBool success, + RoomType roomType, + int index, + const char * nick, + int flags, + peerEnumPlayersCallback callback, + void * param, + int opID +); + +void piAddGetPlayerInfoCallback +( + PEER peer, + PEERBool success, + const char * nick, + unsigned int IP, + int profileID, + peerGetPlayerInfoCallback callback, + void * param, + int opID +); + +void piAddGetPlayerProfileIDCallback +( + PEER peer, + PEERBool success, + const char * nick, + int profileID, + peerGetPlayerProfileIDCallback callback, + void * param, + int opID +); + +void piAddGetPlayerIPCallback +( + PEER peer, + PEERBool success, + const char * nick, + unsigned int IP, + peerGetPlayerIPCallback callback, + void * param, + int opID +); + +void piAddRoomMessageCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * message, + MessageType messageType +); + +void piAddRoomUTMCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * command, + const char * parameters, + PEERBool authenticated +); + +void piAddRoomNameChangedCallback +( + PEER peer, + RoomType roomType +); + +void piAddRoomModeChangedCallback +( + PEER peer, + RoomType roomType, + CHATChannelMode * mode +); + +void piAddPlayerMessageCallback +( + PEER peer, + const char * nick, + const char * message, + MessageType messageType +); + +void piAddPlayerUTMCallback +( + PEER peer, + const char * nick, + const char * command, + const char * parameters, + PEERBool authenticated +); + +void piAddReadyChangedCallback +( + PEER peer, + const char * nick, + PEERBool ready +); + +void piAddGameStartedCallback +( + PEER peer, + SBServer server, + const char * message +); + +void piAddPlayerJoinedCallback +( + PEER peer, + RoomType roomType, + const char * nick +); + +void piAddPlayerLeftCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * reason +); + +void piAddKickedCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * reason +); + +void piAddNewPlayerListCallback +( + PEER peer, + RoomType roomType +); + +void piAddPlayerChangedNickCallback +( + PEER peer, + RoomType roomType, + const char * oldNick, + const char * newNick +); + +void piAddPlayerInfoCallback +( + PEER peer, + RoomType roomType, + const char * nick, + unsigned int IP, + int profileID +); + +void piAddDisconnectedCallback +( + PEER peer, + const char * reason +); + +void piAddPingCallback +( + PEER peer, + const char * nick, + int ping +); + +void piAddCrossPingCallback +( + PEER peer, + const char * nick1, + const char * nick2, + int crossPing +); + +void piAddNickErrorCallback +( + PEER peer, + int type, + const char * nick, + int numSuggestedNicks, + const char ** suggestedNicks, + void * param, + int opID +); + +void piAddChangeNickCallback +( + PEER peer, + PEERBool success, + const char * oldNick, + const char * newNick, + peerChangeNickCallback callback, + void * param, + int opID +); + +void piAddGlobalKeyChangedCallback +( + PEER peer, + const char * nick, + const char * key, + const char * value +); + +void piAddRoomKeyChangedCallback +( + PEER peer, + RoomType roomType, + const char * nick, + const char * key, + const char * value +); + +void piAddGetGlobalKeysCallback +( + PEER peer, + PEERBool success, + const char * nick, + int num, + const char ** keys, + const char ** values, + peerGetGlobalKeysCallback callback, + void * param, + int opID +); + +void piAddGetRoomKeysCallback +( + PEER peer, + PEERBool success, + RoomType roomType, + const char * nick, + int num, + const char ** keys, + const char ** values, + peerGetRoomKeysCallback callback, + void * param, + int opID +); + +void piAddPlayerFlagsChangedCallback +( + PEER peer, + RoomType roomType, + const char * nick, + int oldFlags, + int newFlags +); + +void piAddAuthenticateCDKeyCallback +( + PEER peer, + int result, + const char * message, + peerAuthenticateCDKeyCallback callback, + void * param, + int opID +); + +void piAddAutoMatchStatusCallback +( + PEER peer +); + +int piCallAutoMatchRateCallback +( + PEER peer, + SBServer server +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerGlobalCallbacks.c b/code/gamespy/Peer/peerGlobalCallbacks.c new file mode 100644 index 00000000..781d8503 --- /dev/null +++ b/code/gamespy/Peer/peerGlobalCallbacks.c @@ -0,0 +1,1081 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include +#include +#include "peer.h" +#include "peerAscii.h" +#include "peerMain.h" +#include "peerGlobalCallbacks.h" +#include "peerRooms.h" +#include "peerPlayers.h" +#include "peerCallbacks.h" +#include "peerOperations.h" +#include "peerPing.h" +#include "peerMangle.h" +#include "peerKeys.h" +#include "peerAutoMatch.h" + +/************ +** DEFINES ** +************/ +#define PI_UTM_MATCH(utm) (strncmp(piUTMCommand, utm, strlen(utm) - 1) == 0) + +#define PI_UTM_COMMAND_LEN 8 +#define PI_UTM_PARAMATERS_LEN 512 + +/************ +** GLOBALS ** +************/ +static char piUTMCommand[PI_UTM_COMMAND_LEN]; +static char piUTMParameters[PI_UTM_PARAMATERS_LEN]; + +/************** +** FUNCTIONS ** +**************/ +// Prototype the ASCII versions that are needed even in unicode mode +void peerMessagePlayerA(PEER peer, const char * nick, const char * message, MessageType messageType); + + +/* Chat. +*******/ +static PEERBool piIsOldUTM +( + const char * message +) +{ + // Check for no message. + //////////////////////// + assert(message); + if(!message) + return PEERFalse; + + // Check for too short for prefix + 1 char. + /////////////////////////////////////////// + if(strlen(message) < 4) + return PEERFalse; + + // Check for no prefix. + /////////////////////// + if((message[0] != '@') || + (message[1] != '@') || + (message[2] != '@') || + (message[3] == ' ')) + { + return PEERFalse; + } + + return PEERTrue; +} + +// Returns PEERTrue if a UTM. +///////////////////////////// +static PEERBool piParseUTM +( + const char * message +) +{ + int len; + + // Check for no message. + //////////////////////// + assert(message); + if(!message) + return PEERFalse; + + // Find the end of the command. + /////////////////////////////// + len = (int)strcspn(message, "/ "); + if(len >= PI_UTM_COMMAND_LEN) + return PEERFalse; + memcpy(piUTMCommand, message, (unsigned int)len); + piUTMCommand[len] = '\0'; + + // Copy off the parameters. + /////////////////////////// + message += len; + if(message[0]) + { + message++; + if(strlen(message) >= PI_UTM_PARAMATERS_LEN) + return PEERFalse; + strcpy(piUTMParameters, message); + } + else + { + piUTMParameters[0] = '\0'; + } + + return PEERTrue; +} + +static void piProcessUTM +( + PEER peer, + piPlayer * player, + PEERBool inRoom, + RoomType roomType +) +{ + char * params = piUTMParameters; + + PEER_CONNECTION; + + assert(piUTMCommand[0]); + assert(player); + + if(PI_UTM_MATCH(PI_UTM_LAUNCH)) + { +#ifdef _DEBUG + assert(connection->inRoom[StagingRoom]); + if(inRoom) + assert(roomType == StagingRoom); + else + assert(player->inRoom[StagingRoom]); +#endif + if(!connection->inRoom[StagingRoom]) + return; + if(inRoom && (roomType != StagingRoom)) + return; + if(!inRoom && !player->inRoom[StagingRoom]) + return; + + // Ignore if we're hosting. + /////////////////////////// + if(connection->hosting) + return; + + // Only accept launch from ops. + /////////////////////////////// + if(!piIsPlayerOp(player)) + return; + + // We're playing. + ///////////////// + connection->playing = PEERTrue; + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); + + // Add the callback. + //////////////////// + piAddGameStartedCallback(peer, connection->hostServer, params); + + // If we're AutoMatching, we're now done. + ///////////////////////////////////////// + if(peerIsAutoMatching(peer)) + piSetAutoMatchStatus(peer, PEERComplete); + } + else if(PI_UTM_MATCH(PI_UTM_XPING)) + { + piPlayer * other; + int ping; + unsigned int IP; + +#ifdef _DEBUG +// if(inRoom) +// assert(connection->xpingRoom[roomType]); +#endif + if(inRoom && !connection->xpingRoom[roomType]) + return; + + // Check for no params. + /////////////////////// + if(!params[0]) + return; + + // Get the IP. + ////////////// + IP = piDemangleIP(params); + + // Get the ping. + //////////////// + params += 11; + ping = atoi(params); + + // Figure out who this ping is to. + ////////////////////////////////// + other = piFindPlayerByIP(peer, IP); + if(!other) + return; + if(strcasecmp(player->nick, other->nick) == 0) + return; + if(inRoom && !player->inRoom[roomType]) + return; + if(!inRoom) + { + int i; + PEERBool success = PEERFalse; + + // Check that the three of us are in a room with xping enabled. + /////////////////////////////////////////////////////////////// + for(i = 0 ; i < NumRooms ; i++) + { + if(connection->xpingRoom[i] && connection->inRoom[i] && player->inRoom[i] && other->inRoom[i]) + success = PEERTrue; + } + if(!success) + return; + } + + // Update. + ////////// + piUpdateXping(peer, player->nick, other->nick, ping); + + // Add a xping callback. + //////////////////////// + piAddCrossPingCallback(peer, player->nick, other->nick, ping); + } +} + +void piChatDisconnectedA +( + CHAT chat, + const char * reason, + PEER peer +) +{ + PEER_CONNECTION; + + // We're disconnected. + ////////////////////// + connection->disconnect = PEERTrue; + + // Add the disconnected callback. + ///////////////////////////////// + piAddDisconnectedCallback(peer, reason); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +void piChatDisconnectedW +( + CHAT chat, + const unsigned short * reason, + PEER peer +) +{ + char* reason_A = UCS2ToUTF8StringAlloc(reason); + piChatDisconnectedA(chat, reason_A, peer); + gsifree(reason_A); +} +#endif + +static void piHandleOldNFO +( + PEER peer, + piPlayer * player, + const char * message +) +{ + // Ignore old NFOs from new clients. + //////////////////////////////////// + if(strncmp(message + strlen(message) - 2, "X\\", 2) != 0) + { + const char * str; + + if(!player->inRoom[StagingRoom]) + return; + + str = strstr(message, "\\$flags$\\"); + if(str) + { + PEERBool ready = PEERFalse; + str += 9; + while(*str && (*str != '\\')) + { + if(*str++ == 'r') + { + ready = PEERTrue; + break; + } + } + + if(ready) + player->flags[StagingRoom] |= PEER_FLAG_READY; + else + player->flags[StagingRoom] &= ~PEER_FLAG_READY; + } + } + + GSI_UNUSED(peer); +} + +void piChatPrivateMessageA +( + CHAT chat, + const char * user, + const char * message, + int type, + PEER peer +) +{ + assert(message); + + if(!user || !user[0]) + return; + + // Check for old-style UTMs. + //////////////////////////// + if(piIsOldUTM(message)) + { + // Check for ready. + /////////////////// + if(strncasecmp(message, "@@@NFO", 6) == 0) + { + piPlayer * player; + + player = piGetPlayer(peer, user); + if(player) + piHandleOldNFO(peer, player, message); + } + + return; + } + + // Check if it's a UTM. + /////////////////////// + if((type == CHAT_UTM) || (type == CHAT_ATM)) + { + if(piParseUTM(message)) + { + piPlayer * player; + + // Get the player it's from. + //////////////////////////// + player = piGetPlayer(peer, user); + if(player) + { + // Process it. + ////////////// + piProcessUTM(peer, player, PEERFalse, (RoomType)0); + } + + // Pass it along. + ///////////////// + piAddPlayerUTMCallback(peer, user, piUTMCommand, piUTMParameters, (PEERBool)(type == CHAT_ATM)); + } + + return; + } + + // It's a regular message, deliver it. + ////////////////////////////////////// + piAddPlayerMessageCallback(peer, user, message, (MessageType)type); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +void piChatPrivateMessageW +( + CHAT chat, + const unsigned short * user, + const unsigned short * message, + int type, + PEER peer +) +{ + char* user_A = UCS2ToUTF8StringAlloc(user); + char* message_A = UCS2ToUTF8StringAlloc(message); + piChatPrivateMessageA(chat, user_A, message_A, type, peer); + gsifree(user_A); + gsifree(message_A); +} +#endif + +static void piChannelMessageA +( + CHAT chat, + const char * channel, + const char * user, + const char * message, + int type, + PEER peer +) +{ + piPlayer * player; + RoomType roomType; + + //PEER_CONNECTION; + + assert(message); + + // Check the room type. + /////////////////////// + if(!piRoomToType(peer, channel, &roomType)) + return; + + // Get the player. + ////////////////// + player = piGetPlayer(peer, user); + + // Check for old-style UTMs. + //////////////////////////// + if(player && piIsOldUTM(message)) + { + // Only take stuff in staging rooms. + //////////////////////////////////// + if(roomType != StagingRoom) + return; + + // Check for a launch. + ////////////////////// + if(strncasecmp(message, "@@@GML", 6) == 0) + { + // Ignore old launches from new clients. + //////////////////////////////////////// + if(strncmp(message + strlen(message) - 4, "/OLD", 4) == 0) + return; + + // Convert this into its modern equivalent. + /////////////////////////////////////////// + type = CHAT_UTM; + message = "GML"; + } + // Check for ready. + /////////////////// + else if(strncasecmp(message, "@@@NFO", 6) == 0) + { + piHandleOldNFO(peer, player, message); + + return; + } + else + { + return; + } + } + + // Check if it's a UTM. + /////////////////////// + if((type == CHAT_UTM) || (type == CHAT_ATM)) + { + if(piParseUTM(message)) + { + // Process it. + ////////////// + if(player) + piProcessUTM(peer, player, PEERTrue, roomType); + + // Pass it along. + ///////////////// + piAddRoomUTMCallback(peer, roomType, user, piUTMCommand, piUTMParameters, (type == CHAT_ATM)?PEERTrue:PEERFalse); + } + + return; + } + + // Add the callback. + //////////////////// + piAddRoomMessageCallback(peer, roomType, user, message, (MessageType)type); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piChannelMessageW +( + CHAT chat, + const unsigned short * channel, + const unsigned short * user, + const unsigned short * message, + int type, + PEER peer +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* user_A = UCS2ToUTF8StringAlloc(user); + char* message_A = UCS2ToUTF8StringAlloc(message); + piChannelMessageA(chat, channel_A, user_A, message_A, type, peer); + gsifree(channel_A); + gsifree(user_A); + gsifree(message_A); +} +#endif + +static void piChannelKickedA +( + CHAT chat, + const char * channel, + const char * user, + const char * reason, + PEER peer +) +{ + RoomType roomType; + + //PEER_CONNECTION; + + // Figure out the room type. + //////////////////////////// + if(!piRoomToType(peer, channel, &roomType)) + return; + + // Leave the room. + ////////////////// + piLeaveRoom(peer, roomType, NULL); + + // Add the callback. + //////////////////// + piAddKickedCallback(peer, roomType, user, reason); + + // If we were kicked from an AutoMatch room, start searching. + ///////////////////////////////////////////////////////////// + if((roomType == StagingRoom) && peerIsAutoMatching(peer)) + piSetAutoMatchStatus(peer, PEERSearching); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piChannelKickedW +( + CHAT chat, + const unsigned short * channel, + const unsigned short * user, + const unsigned short * reason, + PEER peer +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* user_A = UCS2ToUTF8StringAlloc(user); + char* reason_A = UCS2ToUTF8StringAlloc(reason); + piChannelKickedA(chat, channel_A, user_A, reason_A, peer); + gsifree(channel_A); + gsifree(user_A); + gsifree(reason_A); +} +#endif + +static void piChannelUserJoinedA +( + CHAT chat, + const char * channel, + const char * user, + int mode, + PEER peer +) +{ + RoomType roomType; + piPlayer * player; + + PEER_CONNECTION; + + // Figure out the room type. + //////////////////////////// + if(!piRoomToType(peer, channel, &roomType)) + return; + + // Add this player to the room. + /////////////////////////////// + player = piPlayerJoinedRoom(peer, user, roomType, mode); + if(!player) + return; + + // Get IP and profile ID if we don't already have it. + ///////////////////////////////////////////////////// + if(!player->gotIPAndProfileID) + { + const char * info; + unsigned int IP; + int profileID; + + if(chatGetBasicUserInfoNoWaitA(connection->chat, user, &info, NULL) && piDemangleUser(info, &IP, &profileID)) + { + piSetPlayerIPAndProfileID(peer, user, IP, profileID); + } + } + + // Refresh this player's watch keys. + //////////////////////////////////// + piKeyCacheRefreshPlayer(peer, roomType, user); + + // Add the callback. + //////////////////// + piAddPlayerJoinedCallback(peer, roomType, user); + +#if 1 + // If this is the staging room, send our ready state. + ///////////////////////////////////////////////////// + if((roomType == StagingRoom) && connection->ready) + peerMessagePlayerA(peer, user, "@@@NFO \\$flags$\\rX\\", NormalMessage); +#endif + + // Check if this is an AutoMatch room. + ////////////////////////////////////// + if((roomType == StagingRoom) && peerIsAutoMatching(peer)) + { + // If we are Waiting, we're now Staging. + //////////////////////////////////////// + if(connection->autoMatchStatus == PEERWaiting) + piSetAutoMatchStatus(peer, PEERStaging); + + // If we've got maxplayers, we're now Ready. + //////////////////////////////////////////// + if((connection->autoMatchStatus == PEERStaging) && (connection->numPlayers[StagingRoom] >= connection->maxPlayers)) + piSetAutoMatchStatus(peer, PEERReady); + } + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piChannelUserJoinedW +( + CHAT chat, + const unsigned short * channel, + const unsigned short * user, + int mode, + PEER peer +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* user_A = UCS2ToUTF8StringAlloc(user); + piChannelUserJoinedA(chat, channel_A, user_A, mode, peer); + gsifree(channel_A); + gsifree(user_A); +} +#endif + +static void piChannelUserPartedA +( + CHAT chat, + const char * channel, + const char * user, + int why, + const char * reason, + const char * kicker, + PEER peer +) +{ + RoomType roomType; + PEERAutoMatchStatus status = PEERFailed; + PEERBool newStatus = PEERFalse; + PEERBool autoMatchRoom; + + PEER_CONNECTION; + + // Figure out the room type. + //////////////////////////// + if(!piRoomToType(peer, channel, &roomType)) + return; + + // Check if this is an AutoMatch room. + ////////////////////////////////////// + autoMatchRoom = ((roomType == StagingRoom) && peerIsAutoMatching(peer))?PEERTrue:PEERFalse; + if(autoMatchRoom) + { + piPlayer * player; + + // Get the players. + /////////////////// + player = piGetPlayer(peer, user); + + // Check if he was the last op. + /////////////////////////////// + if(!connection->hosting && piIsPlayerHost(player)) + { + status = PEERSearching; + newStatus = PEERTrue; + } + // Check for host and everyone left. + //////////////////////////////////// + else if(connection->hosting && connection->numPlayers[StagingRoom] == 2) + { + status = PEERWaiting; + newStatus = PEERTrue; + } + // Check for no longer at maxplayers. + ///////////////////////////////////// + else if(connection->numPlayers[StagingRoom] == connection->maxPlayers) + { + status = PEERStaging; + newStatus = PEERTrue; + } + } + + // Remove this player from the room. + //////////////////////////////////// + piPlayerLeftRoom(peer, user, roomType); + + // Figure out the reason. + ///////////////////////// + if((why == CHAT_KICKED) || (why == CHAT_KILLED)) + reason = "Kicked"; + else if(!reason) + reason = ""; + + // Add the callback. + //////////////////// + piAddPlayerLeftCallback(peer, roomType, user, reason); + + // Set status if needed. + //////////////////////// + if(newStatus) + piSetAutoMatchStatus(peer, status); + + GSI_UNUSED(chat); + GSI_UNUSED(kicker); +} +#ifdef GSI_UNICODE +static void piChannelUserPartedW +( + CHAT chat, + const unsigned short * channel, + const unsigned short * user, + int why, + const unsigned short * reason, + const unsigned short * kicker, + PEER peer +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* user_A = UCS2ToUTF8StringAlloc(user); + char* reason_A = UCS2ToUTF8StringAlloc(reason); + char* kicker_A = UCS2ToUTF8StringAlloc(kicker); + piChannelUserPartedA(chat, channel_A, user_A, why, reason_A, kicker_A, peer); + gsifree(channel_A); + gsifree(user_A); + gsifree(reason_A); + gsifree(kicker_A); +} +#endif + +static void piChannelUserChangedNickA +( + CHAT chat, + const char * channel, + const char * oldNick, + const char * newNick, + PEER peer +) +{ + RoomType roomType; + + // Figure out the room type. + //////////////////////////// + if(!piRoomToType(peer, channel, &roomType)) + return; + + // Change the nick locally. + /////////////////////////// + piPlayerChangedNick(peer, oldNick, newNick); + + // Add the callback. + //////////////////// + piAddPlayerChangedNickCallback(peer, roomType, oldNick, newNick); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piChannelUserChangedNickW +( + CHAT chat, + const unsigned short * channel, + const unsigned short * oldNick, + const unsigned short * newNick, + PEER peer +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* oldNick_A = UCS2ToUTF8StringAlloc(oldNick); + char* newNick_A = UCS2ToUTF8StringAlloc(newNick); + piChannelUserChangedNickA(chat, channel_A, oldNick_A, newNick_A, peer); + gsifree(channel_A); + gsifree(oldNick_A); + gsifree(newNick_A); +} +#endif + +static void piChannelTopicChangedA +( + CHAT chat, + const char * channel, + const char * topic, + PEER peer +) +{ + RoomType roomType; + + PEER_CONNECTION; + + // Figure out the room type. + //////////////////////////// + if(!piRoomToType(peer, channel, &roomType)) + return; + + // Don't allow blank names. + /////////////////////////// + if(!topic[0]) + return; + + // Is it the same as the old name? + ////////////////////////////////// + //if(strcmp(NAME, topic) == 0) + // return; + + // Copy the new name. + ///////////////////// + strzcpy(NAME, topic, PI_NAME_MAX_LEN); + + // Add a callback. + ////////////////// + if(IN_ROOM) + piAddRoomNameChangedCallback(peer, roomType); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piChannelTopicChangedW +( + CHAT chat, + const unsigned short * channel, + const unsigned short * topic, + PEER peer +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* topic_A = UCS2ToUTF8StringAlloc(topic); + piChannelTopicChangedA(chat, channel_A, topic_A, peer); + gsifree(channel_A); + gsifree(topic_A); +} +#endif + +static void piChannelNewUserListA +( + CHAT chat, + const char * channel, + int num, + const char ** users, + int * modes, + PEER peer +) +{ + int i; + RoomType roomType; + + //PEER_CONNECTION; + + // Figure out the room type. + //////////////////////////// + if(!piRoomToType(peer, channel, &roomType)) + return; + + // Clear all the players out of this room. + ////////////////////////////////////////// + piClearRoomPlayers(peer, roomType); + + // Add all the new ones. + //////////////////////// + for(i = 0 ; i < num ; i++) + piPlayerJoinedRoom(peer, users[i], roomType, modes[i]); + + // Refresh keys. + //////////////// + piKeyCacheRefreshRoom(peer, roomType); + + // Call the callback. + ///////////////////// + piAddNewPlayerListCallback(peer, roomType); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piChannelNewUserListW +( + CHAT chat, + const unsigned short * channel, + int num, + const unsigned short ** users, + int * modes, + PEER peer +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char** users_A = UCS2ToUTF8StringArrayAlloc(users, num); + int i; + piChannelNewUserListA(chat, channel_A, num, (const char**)users_A, modes, peer); + gsifree(channel_A); + for (i=0; iparam = peer; +#ifndef GSI_UNICODE // NOT GSI_PEER_UNICODE + channelCallbacks->channelMessage = piChannelMessageA; + channelCallbacks->kicked = piChannelKickedA; + channelCallbacks->userJoined = piChannelUserJoinedA; + channelCallbacks->userParted = piChannelUserPartedA; + channelCallbacks->userChangedNick = piChannelUserChangedNickA; + channelCallbacks->topicChanged = piChannelTopicChangedA; + channelCallbacks->newUserList = piChannelNewUserListA; + channelCallbacks->broadcastKeyChanged = piBroadcastKeyChangedA; + channelCallbacks->userModeChanged = piUserModeChangedA; + channelCallbacks->channelModeChanged = piChannelModeChangedA; +#else + channelCallbacks->channelMessage = piChannelMessageW; + channelCallbacks->kicked = piChannelKickedW; + channelCallbacks->userJoined = piChannelUserJoinedW; + channelCallbacks->userParted = piChannelUserPartedW; + channelCallbacks->userChangedNick = piChannelUserChangedNickW; + channelCallbacks->topicChanged = piChannelTopicChangedW; + channelCallbacks->newUserList = piChannelNewUserListW; + channelCallbacks->broadcastKeyChanged = piBroadcastKeyChangedW; + channelCallbacks->userModeChanged = piUserModeChangedW; + channelCallbacks->channelModeChanged = piChannelModeChangedW; +#endif +} + diff --git a/code/gamespy/Peer/peerGlobalCallbacks.h b/code/gamespy/Peer/peerGlobalCallbacks.h new file mode 100644 index 00000000..290278c2 --- /dev/null +++ b/code/gamespy/Peer/peerGlobalCallbacks.h @@ -0,0 +1,56 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERGLOBALCALLBACKS_H_ +#define _PEERGLOBALCALLBACKS_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/************ +** DEFINES ** +************/ +#define PI_UTM_LAUNCH "GML" +#define PI_UTM_XPING "PNG" + +/************** +** FUNCTIONS ** +**************/ + +/* Chat. +*******/ +#ifndef GSI_UNICODE +#define piChatDisconnected piChatDisconnectedA +#define piChatPrivateMessage piChatPrivateMessageA +#else +#define piChatDisconnected piChatDisconnectedW +#define piChatPrivateMessage piChatPrivateMessageW +#endif + +// Include both forms here so CodeWarrior will have it's happy prototypes +void piChatDisconnectedA(CHAT chat, const char * reason, PEER peer); +void piChatDisconnectedW(CHAT chat, const unsigned short * reason, PEER peer); + +void piChatPrivateMessageA(CHAT chat, const char * user, const char * message, int type, PEER peer); +void piChatPrivateMessageW(CHAT chat, const unsigned short * user, const unsigned short * message, int type, PEER peer); + +void piSetChannelCallbacks(PEER peer, chatChannelCallbacks * channelCallbacks); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerHost.c b/code/gamespy/Peer/peerHost.c new file mode 100644 index 00000000..f21c2c27 --- /dev/null +++ b/code/gamespy/Peer/peerHost.c @@ -0,0 +1,79 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include "peerQR.h" +#include "peerRooms.h" + +/************** +** FUNCTIONS ** +**************/ +#ifdef __MWERKS__ // CodeWarrior will warn if not prototyped +PEERBool piStartHosting (PEER peer, SOCKET socket, unsigned short port); // peerOperations.c +void piStopHosting(PEER peer, PEERBool stopReporting); // peerOperations.c +#endif + +PEERBool piStartHosting +( + PEER peer, + SOCKET socket, + unsigned short port +) +{ + PEER_CONNECTION; + + // Check that we're not hosting. + //////////////////////////////// + assert(!connection->hosting); + if(connection->hosting) + return PEERFalse; + + // Now we're hosting. + ///////////////////// + connection->hosting = PEERTrue; + + // Start reporting. + /////////////////// + if(!piStartReporting(peer, socket, port)) + return PEERFalse; + + return PEERTrue; +} + +void piStopHosting +( + PEER peer, + PEERBool stopReporting +) +{ + PEER_CONNECTION; + + // Stop reporting. + ////////////////// + if(stopReporting) + piStopReporting(peer); + + // Check that we're hosting. + //////////////////////////// + if(!connection->hosting) + return; + + // Reset states. + //////////////// + connection->hosting = PEERFalse; + connection->playing = PEERFalse; + connection->ready = PEERFalse; + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); +} diff --git a/code/gamespy/Peer/peerHost.h b/code/gamespy/Peer/peerHost.h new file mode 100644 index 00000000..4fe94df3 --- /dev/null +++ b/code/gamespy/Peer/peerHost.h @@ -0,0 +1,34 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERHOST_H_ +#define _PEERHOST_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/************** +** FUNCTIONS ** +**************/ +PEERBool piStartHosting(PEER peer, SOCKET socket, unsigned short port); +void piStopHosting(PEER peer, PEERBool stopReporting); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerKeys.c b/code/gamespy/Peer/peerKeys.c new file mode 100644 index 00000000..d96ff7ce --- /dev/null +++ b/code/gamespy/Peer/peerKeys.c @@ -0,0 +1,999 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include "peerKeys.h" +#include "peerCallbacks.h" +#include "peerRooms.h" +#include "peerGlobalCallbacks.h" +#include "peerMangle.h" + +/************ +** DEFINES ** +************/ +#define PI_WATCH_KEYS_NUM_BUCKETS 16 +#define PI_WATCH_CACHE_NUM_BUCKETS 128 + +/********** +** TYPES ** +**********/ +// Stored in the *WatckKey tables. +////////////////////////////////// +typedef struct piWatchKey +{ + char * key; +} piWatchKey; + +// Stored in the *WatchCache tables. +//////////////////////////////////// +typedef struct piCacheKey +{ + char * nick; + char * key; + char * value; +#ifdef GSI_UNICODE + unsigned short * value_W; +#endif +} piCacheKey; + +typedef struct piClearWatchKeyCacheData +{ + const char * key; + HashTable watchCache; +} piClearWatchKeyCacheData; + +typedef struct piRemoveExistingKeysData +{ + int num; + const char ** keys; + HashTable watchKeys; +} piRemoveExistingKeysData; + +typedef struct piPlayerChangedNickMapData +{ + const char * oldNick; + const char * newNick; +} piPlayerChangedNickMapData; + +typedef struct piSetupKeysMapData +{ + int next; + char ** keys; +} piSetupKeysMapData; + +typedef struct piCleanseRoomCacheMapData +{ + PEER peer; + RoomType roomType; +} piCleanseRoomCacheMapData; + +/************** +** FUNCTIONS ** +**************/ +#ifndef GSI_UNICODE // NOT GSI_UNICODE, these have to coincide with the chatsdk build +#define piGetRoomKeysCallback piGetRoomKeysCallbackA +#define piGetGlobalKeysCallback piGetGlobalKeysCallbackA +#else +#define piGetRoomKeysCallback piGetRoomKeysCallbackW +#define piGetGlobalKeysCallback piGetGlobalKeysCallbackW +#endif + + +static int WatchKeysHash +( + const void * elem, + int numBuckets +) +{ + piWatchKey * key = (piWatchKey *)elem; + int c; + const char * str; + unsigned int hash; + + assert(key->key && key->key[0]); + + // Get the hash. + //////////////// + str = key->key; + hash = 0; + while((c = *str++) != '\0') + hash += (unsigned int)tolower(c); + hash %= (unsigned int)numBuckets; + + return (int)hash; +} + +static int GS_STATIC_CALLBACK WatchKeysCompare +( + const void * elem1, + const void * elem2 +) +{ + piWatchKey * key1 = (piWatchKey *)elem1; + piWatchKey * key2 = (piWatchKey *)elem2; + + assert(key1->key && key1->key[0] && key2->key && key2->key[0]); + + return strcasecmp(key1->key, key2->key); +} + +static void WatchKeysFree +( + void * elem +) +{ + piWatchKey * key = (piWatchKey *)elem; + + gsifree(key->key); +} + +static int WatchCacheHash +( + const void * elem, + int numBuckets +) +{ + piCacheKey * key = (piCacheKey *)elem; + int c; + const char * str; + unsigned int hash; + + assert(key); + assert(key->nick[0]); + + // Get the hash. + //////////////// + str = key->key; + hash = 0; + while((c = *str++) != '\0') + hash += (unsigned int)tolower(c); + hash %= (unsigned int)numBuckets; + + return (int)hash; +} + +static int GS_STATIC_CALLBACK WatchCacheCompare +( + const void * elem1, + const void * elem2 +) +{ + int rcode; + + piCacheKey * key1 = (piCacheKey *)elem1; + piCacheKey * key2 = (piCacheKey *)elem2; + + rcode = strcasecmp(key1->nick, key2->nick); + if(rcode) + return rcode; + + return strcasecmp(key1->key, key2->key); +} + +static void WatchCacheFree +( + void * elem +) +{ + piCacheKey * key = (piCacheKey *)elem; + + gsifree(key->nick); + gsifree(key->key); + gsifree(key->value); +#ifdef GSI_UNICODE + gsifree(key->value_W); +#endif +} + +PEERBool piKeysInit +( + PEER peer +) +{ + int roomType; + PEER_CONNECTION; + + memset(connection->globalWatchKeys, 0, sizeof(HashTable) * NumRooms); + memset(connection->roomWatchKeys, 0, sizeof(HashTable) * NumRooms); + memset(connection->roomWatchCache, 0, sizeof(HashTable) * NumRooms); + + // Create all the tables. + ///////////////////////// + connection->globalWatchCache = TableNew(sizeof(piCacheKey), PI_WATCH_CACHE_NUM_BUCKETS, + WatchCacheHash, WatchCacheCompare, WatchCacheFree); + if(!connection->globalWatchCache) + return PEERFalse; + for(roomType = 0 ; roomType < NumRooms ; roomType++) + { + connection->globalWatchKeys[roomType] = TableNew(sizeof(piWatchKey), PI_WATCH_KEYS_NUM_BUCKETS, + WatchKeysHash, WatchKeysCompare, WatchKeysFree); + connection->roomWatchKeys[roomType] = TableNew(sizeof(piWatchKey), PI_WATCH_KEYS_NUM_BUCKETS, + WatchKeysHash, WatchKeysCompare, WatchKeysFree); + connection->roomWatchCache[roomType] = TableNew(sizeof(piCacheKey), PI_WATCH_CACHE_NUM_BUCKETS, + WatchCacheHash, WatchCacheCompare, WatchCacheFree); + + if(!connection->globalWatchKeys[roomType] || !connection->roomWatchKeys[roomType] || + !connection->roomWatchCache[roomType]) + { + return PEERFalse; + } + } + + return PEERTrue; +} + +void piKeysCleanup +( + PEER peer +) +{ + int roomType; + + PEER_CONNECTION; + + if(connection->globalWatchCache) + TableFree(connection->globalWatchCache); + + for(roomType = 0 ; roomType < NumRooms ; roomType++) + { + if(connection->globalWatchKeys[roomType]) + TableFree(connection->globalWatchKeys[roomType]); + if(connection->roomWatchKeys[roomType]) + TableFree(connection->roomWatchKeys[roomType]); + if(connection->roomWatchCache[roomType]) + TableFree(connection->roomWatchCache[roomType]); + } +} + +static const char * piGetWatchKeyA +( + const char * nick, + const char * key, + HashTable watchCache +) +{ + piCacheKey keyTemp; + piCacheKey * cacheKey; + + keyTemp.nick = (char *)nick; + keyTemp.key = (char *)key; + cacheKey = (piCacheKey *)TableLookup(watchCache, &keyTemp); + + if(!cacheKey) + return NULL; + + if(cacheKey->value) + return cacheKey->value; + return ""; +} +static const unsigned short * piGetWatchKeyW +( + const unsigned short * nick, + const unsigned short * key, + HashTable watchCache +) +{ +#ifndef GSI_UNICODE + GSI_UNUSED(nick); + GSI_UNUSED(key); + GSI_UNUSED(watchCache); + return NULL; // can't use this function unless in GSI_UNICODE mode +#else + piCacheKey keyTemp; + piCacheKey * cacheKey; + + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char* key_A = UCS2ToUTF8StringAlloc(key); + + keyTemp.nick = (char *)nick_A; + keyTemp.key = (char *)key_A; + cacheKey = (piCacheKey *)TableLookup(watchCache, &keyTemp); + + gsifree(nick_A); + gsifree(key_A); + + if (!cacheKey) + return NULL; + + if(cacheKey->value_W) + return cacheKey->value_W; + return L""; +#endif +} + +const char * piGetGlobalWatchKeyA +( + PEER peer, + const char * nick, + const char * key +) +{ + PEER_CONNECTION; + + return piGetWatchKeyA(nick, key, connection->globalWatchCache); +} +const unsigned short * piGetGlobalWatchKeyW +( + PEER peer, + const unsigned short * nick, + const unsigned short * key +) +{ + PEER_CONNECTION; + + return piGetWatchKeyW(nick, key, connection->globalWatchCache); +} + +const char * piGetRoomWatchKeyA +( + PEER peer, + RoomType roomType, + const char * nick, + const char * key +) +{ + PEER_CONNECTION; + + return piGetWatchKeyA(nick, key, connection->roomWatchCache[roomType]); +} +const unsigned short * piGetRoomWatchKeyW +( + PEER peer, + RoomType roomType, + const unsigned short * nick, + const unsigned short * key +) +{ + PEER_CONNECTION; + + return piGetWatchKeyW(nick, key, connection->roomWatchCache[roomType]); +} + +static void piCleanseGlobalCacheMap +( + void * elem, + void * clientData +) +{ + piPlayer * player; + int roomType; + piCacheKey * cacheKey = (piCacheKey *)elem; + PEER peer = (PEER)clientData; + piConnection * connection = (piConnection *)peer; + piWatchKey watchKeyTemp; + + watchKeyTemp.key = cacheKey->key; + + player = piGetPlayer(peer, cacheKey->nick); + if(player) + { + for(roomType = 0 ; roomType < NumRooms ; roomType++) + { + if(player->inRoom[roomType]) + { + if(TableLookup(connection->globalWatchKeys[roomType], &watchKeyTemp)) + return; + } + } + } + + TableRemove(connection->globalWatchCache, cacheKey); +} + +static void piCleanseRoomCacheMap +( + void * elem, + void * clientData +) +{ + piPlayer * player; + piCacheKey * cacheKey = (piCacheKey *)elem; + piCleanseRoomCacheMapData * data = (piCleanseRoomCacheMapData *)clientData; + piConnection * connection = (piConnection *)data->peer; + piWatchKey watchKeyTemp; + + watchKeyTemp.key = cacheKey->key; + + player = piGetPlayer(data->peer, cacheKey->nick); + if(player && player->inRoom[data->roomType]) + { + if(TableLookup(connection->roomWatchKeys[data->roomType], &watchKeyTemp)) + return; + } + + TableRemove(connection->globalWatchCache, cacheKey); +} + +void piKeyCacheCleanse +( + PEER peer +) +{ + int roomType; + piCleanseRoomCacheMapData data; + + PEER_CONNECTION; + + TableMapSafe(connection->globalWatchCache, piCleanseGlobalCacheMap, peer); + + data.peer = peer; + for(roomType = 0 ; roomType < NumRooms ; roomType++) + { + if(IN_ROOM || ENTERING_ROOM) + { + data.roomType = (RoomType)roomType; + TableMapSafe(connection->roomWatchCache[roomType], piCleanseRoomCacheMap, &data); + } + else + { + TableClear(connection->roomWatchCache[roomType]); + } + } +} + +static void piGetGlobalKeysCallbackA +( + CHAT chat, + CHATBool success, + const char * user, + int num, + const char ** keys, + const char ** values, + void * param +) +{ + PEER peer = (PEER)param; + + if(success && user) + { + int i; + + for(i = 0 ; i < num ; i++) + piGlobalKeyChanged(peer, user, keys[i], values[i]); + } + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piGetGlobalKeysCallbackW +( + CHAT chat, + CHATBool success, + const unsigned short * user, + int num, + const unsigned short ** keys, + const unsigned short ** values, + void * param +) +{ + char* user_A = UCS2ToUTF8StringAlloc(user); + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + char** values_A = UCS2ToUTF8StringArrayAlloc(values, num); + int i; + piGetGlobalKeysCallbackA(chat, success, user_A, num, (const char**)keys_A, (const char**)values_A, param); + gsifree(user_A); + for (i=0; i is NULL, then this has been called by the + // GETCKEY list-end handler (ciRplEndGetCKeyHandler), + // and the PlayerInfo callback should be called one final + // time with NULL as the user. + // + // Alter the username and proceed so that all of the + // subsequent handlers and callbacks have a real name to + // work with. Note "(END)" is illegal as an IRC nickname + // (09mar01/bgw). + // + int i; + RoomType roomType; + static const char * szEndName = "(END)"; + + if(!piRoomToType(peer, channel, &roomType)) + return; + + for(i = 0 ; i < num ; i++) + piRoomKeyChanged(peer, roomType, szEndName, keys[i], NULL); + return; + } + + if(success /*&& user*/) + { + int i; + RoomType roomType; + + if(!piRoomToType(peer, channel, &roomType)) + return; + + for(i = 0 ; i < num ; i++) + piRoomKeyChanged(peer, roomType, user, keys[i], values[i]); + } + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piGetRoomKeysCallbackW +( + CHAT chat, + CHATBool success, + const unsigned short * channel, + const unsigned short * user, + int num, + const unsigned short ** keys, + const unsigned short ** values, + void * param +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* user_A = UCS2ToUTF8StringAlloc(user); + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + char** values_A = UCS2ToUTF8StringArrayAlloc(values, num); + int i; + piGetRoomKeysCallbackA(chat, success, channel_A, user_A, num, (const char**)keys_A, (const char**)values_A, param); + gsifree(channel_A); + gsifree(user_A); + for(i=0; inum ; i++) + if(strcasecmp(key->key, data->keys[i]) == 0) + return; + + TableRemove(data->watchKeys, key); +} + +void piSetGlobalWatchKeys +( + PEER peer, + RoomType roomType, + int num, + const char ** keys, + PEERBool addKeys +) +{ + piWatchKey watchKey; + int i; + + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + assert(num >= 0); + + // If no keys. + ////////////// + if(num == 0) + { + // Nothing to add. + ////////////////// + if(addKeys) + return; + + // Clear the list and cache. + //////////////////////////// + TableClear(connection->globalWatchKeys[roomType]); + piKeyCacheCleanse(peer); + + return; + } + + assert(keys); + + if(!addKeys) + { + piRemoveExistingKeysData data; + data.num = num; + data.keys = keys; + data.watchKeys = connection->globalWatchKeys[roomType]; + TableMapSafe(connection->globalWatchKeys[roomType], piRemoveExistingKeysMap, &data); + } + + for(i = 0 ; i < num ; i++) + { + watchKey.key = goastrdup(keys[i]); + TableEnter(connection->globalWatchKeys[roomType], &watchKey); + } + + // Update them all. + /////////////////// + if(ENTERING_ROOM || IN_ROOM) + chatGetGlobalKeysA(connection->chat, ROOM, num, keys, piGetGlobalKeysCallback, peer, CHATFalse); +} + +void piSetRoomWatchKeys +( + PEER peer, + RoomType roomType, + int num, + const char ** keys, + PEERBool addKeys +) +{ + piWatchKey watchKey; + int i; + + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + assert(num >= 0); + + // If no keys. + ////////////// + if(num == 0) + { + // Nothing to add. + ////////////////// + if(addKeys) + return; + + // Clear the list and cache. + //////////////////////////// + TableClear(connection->roomWatchKeys[roomType]); + TableClear(connection->roomWatchCache[roomType]); + + return; + } + + assert(keys); + + if(!addKeys) + { + piRemoveExistingKeysData data; + data.num = num; + data.keys = keys; + data.watchKeys = connection->roomWatchKeys[roomType]; + TableMapSafe(connection->roomWatchKeys[roomType], piRemoveExistingKeysMap, &data); + } + + for(i = 0 ; i < num ; i++) + { + watchKey.key = goastrdup(keys[i]); + TableEnter(connection->roomWatchKeys[roomType], &watchKey); + } + + // Update them all. + /////////////////// + if(ENTERING_ROOM || IN_ROOM) + chatGetChannelKeysA(connection->chat, ROOM, "*", num, keys, piGetRoomKeysCallback, peer, CHATFalse); +} + +static PEERBool piKeyChanged +( + PEER peer, + const char * nick, + const char * key, + const char * value, + HashTable watchKeys, + HashTable watchCache, + PEERBool inRoom, + RoomType roomType +) +{ + piWatchKey watchKeyTemp; + piCacheKey cacheKey; + + assert(key && key[0]); + + // We don't track anything for rooms. + ///////////////////////////////////// + if(!nick || !nick[0]) + return PEERTrue; + + // No NULL values. + ////////////////// + if(!value) + value = ""; + + // Is this a username (IP and Profile ID)? + ////////////////////////////////////////// + if(strcasecmp(key, "username") == 0) + { + piPlayer * player; + + if(strcmp(nick, "(END)") == 0) + { + // + // If is "(END)", this is the final message from + // a GETCKEYS list. Call straight into the PlayerInfo + // callback with zeroed arguments (09mar01/bgw). + // + assert(inRoom); + piAddPlayerInfoCallback(peer, roomType, NULL, 0, 0); + return PEERFalse; + } + + // Get the player. + ////////////////// + player = piGetPlayer(peer, nick); + if(player && !player->gotIPAndProfileID) + { + int profileID; + unsigned int IP; + + // Get the info. + //////////////// + if(piDemangleUser(value, &IP, &profileID)) + { + // Cache the info. + ////////////////// + piSetPlayerIPAndProfileID(peer, nick, IP, profileID); + } + } + + // If this was in a room, call the callback. + //////////////////////////////////////////// + if(inRoom) + { + if(player && player->gotIPAndProfileID) + piAddPlayerInfoCallback(peer, roomType, nick, player->IP, player->profileID); + else + piAddPlayerInfoCallback(peer, roomType, nick, 0, 0); + } + } + + // Is this flags? + ///////////////// + if(inRoom && strcasecmp(key, "b_flags") == 0) + piSetPlayerRoomFlags(peer, nick, roomType, value); + + // Check if this is a watch key. + //////////////////////////////// + watchKeyTemp.key = (char *)key; + if(!TableLookup(watchKeys, &watchKeyTemp)) + { + // Is it a broadcast key? + ///////////////////////// + if(inRoom && (strncmp(key, "b_", 2) == 0)) + return PEERTrue; + return PEERFalse; + } + + // Cache it. + //////////// + memset(&cacheKey, 0, sizeof(piCacheKey)); + cacheKey.nick = goastrdup(nick); + cacheKey.key = goastrdup(key); + cacheKey.value = goastrdup(value); +#ifdef GSI_UNICODE + cacheKey.value_W = UTF8ToUCS2StringAlloc(cacheKey.value); +#endif + TableEnter(watchCache, &cacheKey); + + return PEERTrue; +} + +void piGlobalKeyChanged +( + PEER peer, + const char * nick, + const char * key, + const char * value +) +{ + PEER_CONNECTION; + + if(piKeyChanged(peer, nick, key, value, connection->globalWatchKeys[TitleRoom], connection->globalWatchCache, PEERFalse, (RoomType)0) || + piKeyChanged(peer, nick, key, value, connection->globalWatchKeys[GroupRoom], connection->globalWatchCache, PEERFalse, (RoomType)0) || + piKeyChanged(peer, nick, key, value, connection->globalWatchKeys[StagingRoom], connection->globalWatchCache, PEERFalse, (RoomType)0)) + piAddGlobalKeyChangedCallback(peer, nick, key, value); +} + +void piRoomKeyChanged +( + PEER peer, + RoomType roomType, + const char * nick, + const char * key, + const char * value +) +{ + PEER_CONNECTION; + + if(piKeyChanged(peer, nick, key, value, connection->roomWatchKeys[roomType], connection->roomWatchCache[roomType], PEERTrue, roomType)) + piAddRoomKeyChangedCallback(peer, roomType, nick, key, value); +} + +static void piPlayerChangedNickMap +( + void * elem, + void * clientData +) +{ + piCacheKey * key = (piCacheKey *)elem; + piPlayerChangedNickMapData * data = (piPlayerChangedNickMapData *)clientData; + + if(strcasecmp(key->nick, data->oldNick) == 0) + { + gsifree(key->nick); + key->nick = goastrdup(data->newNick); + } +} + +void piKeyCachePlayerChangedNick +( + PEER peer, + const char * oldNick, + const char * newNick +) +{ + piPlayerChangedNickMapData data; + + PEER_CONNECTION; + + data.oldNick = oldNick; + data.newNick = newNick; + + TableMap(connection->globalWatchCache, piPlayerChangedNickMap, &data); + TableMap(connection->roomWatchCache[0], piPlayerChangedNickMap, &data); + TableMap(connection->roomWatchCache[1], piPlayerChangedNickMap, &data); + TableMap(connection->roomWatchCache[2], piPlayerChangedNickMap, &data); +} + +static void piSetupKeysMap +( + void * elem, + void * clientData +) +{ + piWatchKey * key = (piWatchKey *)elem; + piSetupKeysMapData * data = (piSetupKeysMapData *)clientData; + + // Add the key to the list. + /////////////////////////// + data->keys[data->next++] = key->key; +} + +static void piKeyCacheRefresh +( + PEER peer, + RoomType roomType, + const char * nick +) +{ + int num; + piSetupKeysMapData data; + PEERBool getIP; + PEERBool getFlags; + piWatchKey watchKey; + + PEER_CONNECTION; + + assert(IN_ROOM || ENTERING_ROOM); + if(!IN_ROOM && !ENTERING_ROOM) + return; + + // Get the IP if we're pinging this room. + ///////////////////////////////////////// + if(!nick) + getIP = (connection->pingRoom[roomType] || connection->alwaysRequestPlayerInfo)?PEERTrue:PEERFalse; + else + getIP = PEERFalse; + + // Check if we're already getting the IP. + ///////////////////////////////////////// + if(getIP) + { + watchKey.key = "username"; + getIP = (PEERBool)(!TableLookup(connection->roomWatchKeys[roomType], &watchKey)); + } + + // Don't get flags on a player - he just joined, and will set them. + /////////////////////////////////////////////////////////////////// + getFlags = (PEERBool)(nick == NULL); + + // Check for a b_flags watch key. + ///////////////////////////////// + if(getFlags) + { + watchKey.key = "b_flags"; + getFlags = (PEERBool)(!(TableLookup(connection->globalWatchKeys[roomType], &watchKey) || + TableLookup(connection->roomWatchKeys[roomType], &watchKey))); + } + + data.next = 0; + num = TableCount(connection->globalWatchKeys[roomType]); + if(num) + { + data.keys = (char **)gsimalloc(sizeof(char *) * num); + if(!data.keys) + return; + TableMap(connection->globalWatchKeys[roomType], piSetupKeysMap, &data); + assert(data.next == num); + chatGetGlobalKeysA(connection->chat, nick?nick:ROOM, num, (const char **)data.keys, piGetGlobalKeysCallback, peer, CHATFalse); + gsifree(data.keys); + } + + if(!nick) + { + data.next = 0; + num = TableCount(connection->roomWatchKeys[roomType]); + if(getIP) + num++; + if(getFlags) + num++; + if(num) + { + data.keys = (char **)gsimalloc(sizeof(char *) * num); + if(!data.keys) + return; + TableMap(connection->roomWatchKeys[roomType], piSetupKeysMap, &data); + if(getIP) + data.keys[data.next++] = "username"; + if(getFlags) + data.keys[data.next++] = "b_flags"; + assert(data.next == num); + chatGetChannelKeysA(connection->chat, ROOM, nick?nick:"*", num, (const char **)data.keys, piGetRoomKeysCallback, peer, CHATFalse); + gsifree(data.keys); + } + } +} + +void piKeyCacheRefreshPlayer +( + PEER peer, + RoomType roomType, + const char * nick +) +{ + piKeyCacheRefresh(peer, roomType, nick); +} + +void piKeyCacheRefreshRoom +( + PEER peer, + RoomType roomType +) +{ + piKeyCacheRefresh(peer, roomType, NULL); +} diff --git a/code/gamespy/Peer/peerKeys.h b/code/gamespy/Peer/peerKeys.h new file mode 100644 index 00000000..f27333d2 --- /dev/null +++ b/code/gamespy/Peer/peerKeys.h @@ -0,0 +1,49 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERKEYS_H_ +#define _PEERKEYS_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" +#include "peerPlayers.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/************** +** FUNCTIONS ** +**************/ +PEERBool piKeysInit(PEER peer); +void piKeysCleanup(PEER peer); +const char * piGetGlobalWatchKeyA(PEER peer, const char * nick, const char * key); +const char * piGetRoomWatchKeyA(PEER peer, RoomType roomType, const char * nick, const char * key); +const unsigned short * piGetGlobalWatchKeyW(PEER peer, const unsigned short * nick, const unsigned short * key); +const unsigned short * piGetRoomWatchKeyW(PEER peer, RoomType roomType, const unsigned short * nick, const unsigned short * key); +void piSetGlobalWatchKeys(PEER peer, RoomType roomType, int num, const char ** keys, PEERBool addKeys); +void piSetRoomWatchKeys(PEER peer, RoomType roomType, int num, const char ** keys, PEERBool addKeys); +void piGlobalKeyChanged(PEER peer, const char * nick, const char * key, const char * value); +void piRoomKeyChanged(PEER peer, RoomType roomType, const char * nick, const char * key, const char * value); +void piKeyCachePlayerChangedNick(PEER peer, const char * oldNick, const char * newNick); +void piKeyCachePlayerLeftRoom(PEER peer, RoomType roomType, piPlayer * player); +void piKeyCacheLeftRoom(PEER peer, RoomType roomType); +void piKeyCacheRefreshPlayer(PEER peer, RoomType roomType, const char * nick); +void piKeyCacheRefreshRoom(PEER peer, RoomType roomType); +void piKeyCacheCleanse(PEER peer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerMain.c b/code/gamespy/Peer/peerMain.c new file mode 100644 index 00000000..c40c98be --- /dev/null +++ b/code/gamespy/Peer/peerMain.c @@ -0,0 +1,4409 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include "peer.h" +#include "peerAscii.h" +#include "peerMain.h" +#include "peerCallbacks.h" +#include "peerGlobalCallbacks.h" +#include "peerOperations.h" +#include "peerPlayers.h" +#include "peerRooms.h" +#include "peerPing.h" +#include "peerSB.h" +#include "peerMangle.h" +#include "peerKeys.h" +#include "peerQR.h" +#include "peerHost.h" +#include "peerAutoMatch.h" +#include "../common/gsAvailable.h" + +/************ +** DEFINES ** +************/ +#define PEER_CONNECTED assert(connection->connected); + +#define PI_CHECK_SHUTDOWN if(connection->shutdown && (connection->callbackDepth == 0))\ + {\ + peerShutdown(peer);\ + } + +#define PI_INIT_FAILED {\ + piShutdownCleanup(peer);\ + return NULL;\ + } + +#define PI_DO_BLOCKING if(blocking)\ + {\ + do\ + {\ + msleep(1);\ + piThink(peer, opID);\ + }\ + while(!piCheckBlockingID(peer, opID));\ + PI_CHECK_SHUTDOWN\ + } + +#define PI_OP_ID int opID = piGetNextID(peer) +#if 0 +int opID; // for visual assist +#endif + +/*************** +** PROTOTYPES ** +***************/ +static void piShutdownCleanup(PEER peer); +static void piDisconnectCleanup(PEER peer); +static void piThink(PEER peer,int ID); + +void peerCreateStagingRoomWithSocketA(PEER peer, const char * name, int maxPlayers, const char password[PEER_PASSWORD_LEN], SOCKET socket, unsigned short port, peerJoinRoomCallback callback, void * param, PEERBool blocking); +void peerStartAutoMatchWithSocketA(PEER peer, int maxPlayers, const char * filter, SOCKET socket, unsigned short port, peerAutoMatchStatusCallback statusCallback, peerAutoMatchRateCallback rateCallback, void * param, PEERBool blocking); + +/************ +** GENERAL ** +************/ +static PEERBool piCheckBlockingID +( + PEER peer, + int ID +) +{ + return (PEERBool)(piIsOperationFinished(peer, ID) && piIsCallbackFinished(peer, ID)); +} + +static unsigned int piGetPrivateIP(void) +{ + HOSTENT * host; + IN_ADDR * addr; + int i; + + host = getlocalhost(); + if(!host) + return 0; + + for(i = 0 ; host->h_addr_list[i] ; i++) + { + addr = (IN_ADDR *)host->h_addr_list[i]; + if(IsPrivateIP(addr)) + return addr->s_addr; + } + + return 0; +} + +PEER peerInitialize +( + PEERCallbacks * callbacks +) +{ + PEER peer; + piConnection * connection; + + assert(callbacks); + + // Check if the backend is available. + ///////////////////////////////////// + if(__GSIACResult != GSIACAvailable) + return NULL; + + // Init sockets. + //////////////// + SocketStartUp(); + + // Create an object. + //////////////////// + connection = (piConnection *)gsimalloc(sizeof(piConnection)); + if(!connection) + return NULL; + memset(connection, 0, sizeof(piConnection)); + peer = (PEER)connection; + + // Chat. + //////// + connection->chat = NULL; + connection->nick[0] = '\0'; + connection->connecting = PEERFalse; + connection->connected = PEERFalse; + + // Game. + //////// + connection->privateIP = piGetPrivateIP(); + connection->title[0] = '\0'; + +#ifdef GSI_UNICODE + connection->title_W[0] = '\0'; + connection->nick_W[0] = '\0'; +#endif + + // ID. + ////// + connection->nextID = 0; + + // Operations. + ////////////// + if(!piOperationsInit(peer)) + PI_INIT_FAILED + + // Callbacks. + ///////////// + connection->callbacks = *callbacks; + if(!piCallbacksInit(peer)) + PI_INIT_FAILED + + // Keys. + //////// + if(!piKeysInit(peer)) + PI_INIT_FAILED + + // Misc. + //////// + connection->shutdown = PEERFalse; + + return peer; +} + +void peerConnectA +( + PEER peer, + const char * nick, + int profileID, + peerNickErrorCallback nickErrorCallback, + peerConnectCallback connectCallback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + + PI_OP_ID; + PEER_CONNECTION; + + assert(nick); + assert(nick[0]); + assert(profileID >= 0); + assert(connectCallback); + assert(strlen(nick) < PI_NICK_MAX_LEN); + + // Are we already connecting or connected? + ////////////////////////////////////////// + if(connection->connected || connection->connecting) + success = PEERFalse; + + // We must have a title set to connect. + /////////////////////////////////////// + if(success && !connection->title[0]) + success = PEERFalse; + + if(success) + { + // Chat. + //////// + connection->chat = NULL; + strzcpy(connection->nick, nick, PI_NICK_MAX_LEN); + connection->connected = PEERFalse; + connection->connecting = PEERTrue; + connection->nickErrorCallback = nickErrorCallback; + +#ifdef GSI_UNICODE + UTF8ToUCS2String(connection->nick, connection->nick_W); +#endif + // Misc. + //////// + connection->profileID = profileID; + connection->disconnect = PEERFalse; + + // Start connecting. + //////////////////// + if(!piNewConnectOperation(peer, PI_CONNECT, nick, 0, NULL, NULL, NULL, NULL, NULL, NULL, connectCallback, param, opID)) + { + success = PEERFalse; + piDisconnectCleanup(peer); + } + } + + if(!success) + piAddConnectCallback(peer, PEERFalse, PEER_DISCONNECTED, connectCallback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerConnectW +( + PEER peer, + const unsigned short * nick, + int profileID, + peerNickErrorCallback nickErrorCallback, + peerConnectCallback connectCallback, + void * param, + PEERBool blocking +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + peerConnectA(peer, nick_A, profileID, nickErrorCallback, connectCallback, param, blocking); + gsifree(nick_A); +} +#endif + +void peerConnectLoginA +( + PEER peer, + int namespaceID, + const char * email, + const char * profilenick, + const char * uniquenick, + const char * password, + peerNickErrorCallback nickErrorCallback, + peerConnectCallback connectCallback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + + PI_OP_ID; + PEER_CONNECTION; + + assert(connectCallback); + + // Are we already connecting or connected? + ////////////////////////////////////////// + if(connection->connected || connection->connecting) + success = PEERFalse; + + // We must have a title set to connect. + /////////////////////////////////////// + if(success && !connection->title[0]) + success = PEERFalse; + + if(success) + { + // Chat. + //////// + connection->chat = NULL; + connection->nick[0] = '\0'; + connection->connected = PEERFalse; + connection->connecting = PEERTrue; + connection->nickErrorCallback = nickErrorCallback; + +#ifdef GSI_UNICODE + UTF8ToUCS2String(connection->nick, connection->nick_W); +#endif + // Misc. + //////// + connection->profileID = 0; + connection->disconnect = PEERFalse; + + // Start connecting. + //////////////////// + if(!piNewConnectOperation(peer, (uniquenick && uniquenick[0])?PI_CONNECT_UNIQUENICK_LOGIN:PI_CONNECT_PROFILENICK_LOGIN, NULL, namespaceID, email, profilenick, uniquenick, password, NULL, NULL, connectCallback, param, opID)) + { + success = PEERFalse; + piDisconnectCleanup(peer); + } + } + + if(!success) + piAddConnectCallback(peer, PEERFalse, PEER_DISCONNECTED, connectCallback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerConnectLoginW +( + PEER peer, + int namespaceID, + const unsigned short * email, + const unsigned short * profilenick, + const unsigned short * uniquenick, + const unsigned short * password, + peerNickErrorCallback nickErrorCallback, + peerConnectCallback connectCallback, + void * param, + PEERBool blocking +) +{ + char* email_A = UCS2ToUTF8StringAlloc(email); + char* profilenick_A = UCS2ToUTF8StringAlloc(profilenick); + char* uniquenick_A = UCS2ToUTF8StringAlloc(uniquenick); + char* password_A = UCS2ToUTF8StringAlloc(password); + peerConnectLoginA(peer, namespaceID, email_A, profilenick_A, uniquenick_A, password_A, nickErrorCallback, connectCallback, param, blocking); + gsifree(email_A); + gsifree(profilenick_A); + gsifree(uniquenick_A); + gsifree(password_A); +} +#endif + +void peerConnectPreAuthA +( + PEER peer, + const char * authtoken, + const char * partnerchallenge, + peerNickErrorCallback nickErrorCallback, + peerConnectCallback connectCallback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + + PI_OP_ID; + PEER_CONNECTION; + + assert(authtoken && authtoken[0]); + assert(partnerchallenge && partnerchallenge[0]); + assert(connectCallback); + + // Are we already connecting or connected? + ////////////////////////////////////////// + if(connection->connected || connection->connecting) + success = PEERFalse; + + // We must have a title set to connect. + /////////////////////////////////////// + if(success && !connection->title[0]) + success = PEERFalse; + + if(success) + { + // Chat. + //////// + connection->chat = NULL; + connection->nick[0] = '\0'; + connection->connected = PEERFalse; + connection->connecting = PEERTrue; + connection->nickErrorCallback = nickErrorCallback; + +#ifdef GSI_UNICODE + UTF8ToUCS2String(connection->nick, connection->nick_W); +#endif + // Misc. + //////// + connection->profileID = 0; + connection->disconnect = PEERFalse; + + // Start connecting. + //////////////////// + if(!piNewConnectOperation(peer, PI_CONNECT_PREAUTH, NULL, 0, NULL, NULL, NULL, NULL, authtoken, partnerchallenge, connectCallback, param, opID)) + { + success = PEERFalse; + piDisconnectCleanup(peer); + } + } + + if(!success) + piAddConnectCallback(peer, PEERFalse, PEER_DISCONNECTED, connectCallback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerConnectPreAuthW +( + PEER peer, + const unsigned short * authtoken, + const unsigned short * partnerchallenge, + peerNickErrorCallback nickErrorCallback, + peerConnectCallback connectCallback, + void * param, + PEERBool blocking +) +{ + char* authtoken_A = UCS2ToUTF8StringAlloc(authtoken); + char* partnerchallenge_A = UCS2ToUTF8StringAlloc(partnerchallenge); + peerConnectPreAuthA(peer, authtoken_A, partnerchallenge_A, nickErrorCallback, connectCallback, param, blocking); + gsifree(authtoken_A); + gsifree(partnerchallenge_A); +} +#endif + +void peerRetryWithNickA +( + PEER peer, + const char * nick +) +{ + PEER_CONNECTION; + + // Check that we're connecting. + /////////////////////////////// + assert(connection->connecting); + if(!connection->connecting) + return; + + // Set the new nick we're using. + //////////////////////////////// + if(nick && nick[0]) + { + strzcpy(connection->nick, nick, PI_NICK_MAX_LEN); + +#ifdef GSI_UNICODE + UTF8ToUCS2String(connection->nick, connection->nick_W); +#endif + } + + // Retry with the new nick. + /////////////////////////// + chatRetryWithNickA(connection->chat, nick); +} +#ifdef GSI_UNICODE +void peerRetryWithNickW +( + PEER peer, + const unsigned short * nick +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + peerRetryWithNickA(peer, nick_A); + gsifree(nick_A); +} +#endif + +void peerRegisterUniqueNickA +( + PEER peer, + int namespaceID, + const char * uniquenick, + const char * cdkey) +{ + PEER_CONNECTION; + + // Check that we're connecting. + /////////////////////////////// + assert(connection->connecting); + if(!connection->connecting) + return; + + // Register the nick. + ///////////////////// + chatRegisterUniqueNickA(connection->chat, namespaceID, uniquenick, cdkey); +} +#ifdef GSI_UNICODE +void peerRegisterUniqueNickW +( + PEER peer, + int namespaceID, + const unsigned short * uniquenick, + const unsigned short * cdkey +) +{ + char* uniquenick_A = UCS2ToUTF8StringAlloc(uniquenick); + char* cdkey_A = UCS2ToUTF8StringAlloc(cdkey); + peerRegisterUniqueNickA(peer, namespaceID, uniquenick_A, cdkey_A); + gsifree(uniquenick_A); + gsifree(cdkey_A); +} +#endif + +PEERBool peerIsConnected(PEER peer) +{ + PEER_CONNECTION; + + return connection->connected; +} + +PEERBool piConnectTitle(PEER peer) +{ + // Rooms. + ///////// + if(!piRoomsInit(peer)) + return PEERFalse; + + // Players. + /////////// + if(!piPlayersInit(peer)) + return PEERFalse; + + // Ping. + // If it fails, keep going. + /////////////////////////// + piPingInit(peer); + + return PEERTrue; +} + +void piDisconnectTitle(PEER peer) +{ + // Rooms. + ///////// + piRoomsCleanup(peer); + + // Players. + /////////// + piPlayersCleanup(peer); + + // Ping. + //////// + piPingCleanup(peer); + + // AutoMatch. + ///////////// + piStopAutoMatch(peer); +} + +PEERBool peerSetTitleA +( + PEER peer, + const char * title, + const char * qrSecretKey, + const char * sbName, + const char * sbSecretKey, + int sbGameVersion, + int sbMaxUpdates, + PEERBool natNegotiate, + PEERBool pingRooms[NumRooms], + PEERBool crossPingRooms[NumRooms] +) +{ + static PEERBool noPings[NumRooms]; + PEERBool pingTitleRoom = PEERFalse; + PEERBool xpingTitleRoom = PEERFalse; + + PEER_CONNECTION; + + assert(title); + assert(title[0]); + assert(strlen(title) < PI_TITLE_MAX_LEN); + assert(qrSecretKey); + assert(sbName); + assert(sbName[0]); + assert(sbSecretKey); + + // Check if a title is set. + /////////////////////////// + if(connection->title[0]) + peerClearTitle(peer); + + // Game. + //////// + strcpy(connection->title, title); + +#ifdef GSI_UNICODE + AsciiToUCS2String(title, connection->title_W); +#endif + + // Null pings means don't do pings. + /////////////////////////////////// + if(!pingRooms) + pingRooms = noPings; + if(!crossPingRooms) + crossPingRooms = noPings; + + // If staying in the title room, leave the room's ping setting alone. + ///////////////////////////////////////////////////////////////////// + if(connection->stayInTitleRoom) + { + pingTitleRoom = connection->pingRoom[TitleRoom]; + xpingTitleRoom = connection->xpingRoom[TitleRoom]; + } + + // Save our ping settings. + ////////////////////////// + memcpy(connection->pingRoom, pingRooms, sizeof(PEERBool) * NumRooms); + memcpy(connection->xpingRoom, crossPingRooms, sizeof(PEERBool) * NumRooms); + + // If staying in the title room, leave the room's ping setting alone. + ///////////////////////////////////////////////////////////////////// + if(connection->stayInTitleRoom) + { + connection->pingRoom[TitleRoom] = pingTitleRoom; + connection->xpingRoom[TitleRoom] = xpingTitleRoom; + } + + // Save SB settings. + //////////////////// + strzcpy(connection->sbName, sbName, PI_SB_LEN); + strzcpy(connection->sbSecretKey, sbSecretKey, PI_SB_LEN); + connection->sbGameVersion = sbGameVersion; + connection->sbMaxUpdates = sbMaxUpdates; + + // Init SB. + /////////// + if(!piSBInit(peer)) + return PEERFalse; + + // If we're already connected, do the connect stuff. + //////////////////////////////////////////////////// + if(connection->connected) + { + if(!piConnectTitle(peer)) + { + peerClearTitle(peer); + return PEERFalse; + } + } + + // Hosting. + /////////// + strcpy(connection->qrSecretKey, qrSecretKey); + piStopHosting(peer, PEERTrue); + connection->hosting = PEERFalse; + connection->playing = PEERFalse; + connection->natNegotiate = natNegotiate; + + // Game states. + /////////////// + connection->ready = PEERFalse; + + // Make sure the "stay in title room" setting is cleared. + ///////////////////////////////////////////////////////// + connection->stayInTitleRoom = PEERFalse; + + return PEERTrue; +} +#ifdef GSI_UNICODE +PEERBool peerSetTitleW +( + PEER peer, + const unsigned short * title, + const unsigned short * qrSecretKey, + const unsigned short * sbName, + const unsigned short * sbSecretKey, + int sbGameVersion, + int sbMaxUpdates, + PEERBool natNegotiate, + PEERBool pingRooms[NumRooms], + PEERBool crossPingRooms[NumRooms] +) +{ + char* title_A = UCS2ToUTF8StringAlloc(title); + char* qrSecretKey_A = UCS2ToUTF8StringAlloc(qrSecretKey); + char* sbName_A = UCS2ToUTF8StringAlloc(sbName); + char* sbSecretKey_A = UCS2ToUTF8StringAlloc(sbSecretKey); + PEERBool result = peerSetTitleA(peer, title_A, qrSecretKey_A, sbName_A, sbSecretKey_A, sbGameVersion, sbMaxUpdates, natNegotiate, pingRooms, crossPingRooms); + gsifree(title_A); + gsifree(qrSecretKey_A); + gsifree(sbName_A); + gsifree(sbSecretKey_A); + return result; +} +#endif + +void peerClearTitle(PEER peer) +{ + PEER_CONNECTION; + + // Stop hosting if we are. + ////////////////////////// + piStopHosting(peer, PEERTrue); + + // Cleanup SB. + ////////////// + piSBCleanup(peer); + + // Cleanup title stuff. + /////////////////////// + piDisconnectTitle(peer); + + // Cleanup game. + //////////////// + connection->title[0] = '\0'; + +#ifdef GSI_UNICODE + connection->title_W[0] = '\0'; +#endif + + // Cleanup qr secret key. + ///////////////////////// + connection->qrSecretKey[0] = '\0'; +} + +const char* peerGetTitleA(PEER peer) +{ + PEER_CONNECTION; + + if(!connection->title[0]) + return NULL; + + return connection->title; +} +#ifdef GSI_UNICODE +const unsigned short* peerGetTitleW(PEER peer) +{ + PEER_CONNECTION; + + if(!connection->title_W[0]) + return NULL; + + return connection->title_W; +} +#endif + +static void piDisconnectCleanup(PEER peer) +{ + PEER_CONNECTION; + + // Chat. + //////// + if(connection->chat) + chatDisconnect(connection->chat); + connection->chat = NULL; + connection->nick[0] = '\0'; + connection->connecting = PEERFalse; + connection->connected = PEERFalse; + +#ifdef GSI_UNICODE + connection->nick_W[0] = '\0'; +#endif + + // Operations. + ////////////// + piOperationsReset(peer); + + // Title. + ///////// + piDisconnectTitle(peer); + + // Away. + //////// + connection->away = PEERFalse; + connection->awayReason[0] = '\0'; + + // We're not trying to disconnect. + ////////////////////////////////// + connection->disconnect = PEERFalse; +} + +static void piDisconnect(PEER peer) +{ + PEER_CONNECTION; + + // Are we within a callback? + //////////////////////////// + if(connection->callbackDepth > 0) + { + // Flag for disconnect later. + ///////////////////////////// + connection->disconnect = PEERTrue; + + return; + } + + // Can't stay in the title room if we're disconnecting. + /////////////////////////////////////////////////////// + connection->stayInTitleRoom = PEERFalse; + + // Cleanup the connection. + ////////////////////////// + piDisconnectCleanup(peer); + + // Think to make sure the disconnected callback gets called. + //////////////////////////////////////////////////////////// + piThink(peer, -1); +} + +void peerDisconnect(PEER peer) +{ + PEER_CONNECTION; + + // Do the disconnect. + ///////////////////// + piDisconnect(peer); + + // Check if we got shutdown in the disconnect callback. + /////////////////////////////////////////////////////// + PI_CHECK_SHUTDOWN +} + +static void piShutdownCleanup(PEER peer) +{ + PEER_CONNECTION; + + // Operations. + ////////////// + piOperationsCleanup(peer); + + // Callbacks. + ///////////// + piCallbacksCleanup(peer); + + // Shut down sockets. + ///////////////////// + SocketShutDown(); + + // Keys. + //////// + piKeysCleanup(peer); + + // gsifree the connection. + /////////////////////// + gsifree(connection); +} + +void peerShutdown(PEER peer) +{ + PEER_CONNECTION; + + // Cleanup the connection? + ////////////////////////// + if(connection->connected || connection->connecting) + { + peerDisconnectedCallback callback; + + // We don't want the disconnected callback + // called if we're being explicitly shutdown. + ///////////////////////////////////////////// + callback = connection->callbacks.disconnected; + connection->callbacks.disconnected = NULL; + piDisconnect(peer); + connection->callbacks.disconnected = callback; + } + + // Cleanup title if needed. + /////////////////////////// + if(connection->title[0]) + peerClearTitle(peer); + + // Are we within a callback? + //////////////////////////// + if(connection->callbackDepth > 0) + { + // Flag for shutdown later. + /////////////////////////// + connection->shutdown = PEERTrue; + + return; + } + + // Cleanup. + /////////// + piShutdownCleanup(peer); +} + +#ifdef _DEBUG +static void piNumPlayersConsistencyCheckMap +( + piPlayer * player, + int count[NumRooms] +) +{ + int i; + + assert(player); + assert(count); + + for(i = 0 ; i < NumRooms ; i++) + { + if(player->inRoom[i]) + count[i]++; + } +} +#endif + +static void piThink +( + PEER peer, + int opID +) +{ + gsi_time now; + PEER_CONNECTION; + +#ifdef _DEBUG + if(connection->players) + { + // Consistency check number of players in each room. + //////////////////////////////////////////////////// + { + int count[NumRooms]; + int i; + + // Init the counts to 0. + //////////////////////// + for(i = 0 ; i < NumRooms ; i++) + count[i] = 0; + + // Map through the players checking the count. + ////////////////////////////////////////////// + TableMap(connection->players, (TableMapFn)piNumPlayersConsistencyCheckMap, count); + + // Check the counts. + //////////////////// + for(i = 0 ; i < NumRooms ; i++) + assert(count[i] == connection->numPlayers[i]); + } + } +#endif + +#if 0 + // Show info. + ///////////// + { + char buffer[1024]; + static int counter = -1; + + counter++; + counter %= 20; + if(counter == 0) + { + sprintf(buffer, + "---------------------\n" + "operationsStarted: %d\n" + "operationsFinished: %d\n" + "callbacksQueued: %d\n" + "callbacksCalled: %d\n" + "callbackDepth: %d\n" + "titleRoomPlayers: %d\n" + "groupRoomPlayers: %d\n" + "stagingRoomPlayers: %d\n", + connection->operationsStarted, + connection->operationsFinished, + connection->callbacksQueued, + connection->callbacksCalled, + connection->callbackDepth, + connection->numPlayers[TitleRoom], + connection->numPlayers[GroupRoom], + connection->numPlayers[StagingRoom]); + OutputDebugString(buffer); + } + } +#endif + + // Let chat think. + ////////////////// + if(connection->connected || connection->connecting) + { + chatThink(connection->chat); + + // Only do this if we weren't disconnected. + /////////////////////////////////////////// + if(!connection->disconnect) + { + // Is a title set? + ////////////////// + if(connection->title[0]) + { + // Do ping stuff. + ///////////////// + piPingThink(peer); + } + + // Are we connected? + //////////////////// + if(connection->connected) + { + // Ge the current time. + /////////////////////// + now = current_time(); + + // Check if we need to ping the chat server. + //////////////////////////////////////////// + if((now - connection->lastChatPing) > PI_CHAT_PING_TIME) + { + // Send the ping. + ///////////////// + chatSendRawA(connection->chat, "PING"); + + // Store the current time. + ////////////////////////// + connection->lastChatPing = now; + } + } + } + } + + // Let SB think. + //////////////// + piSBThink(peer); + + // Let query-reporting think. + ///////////////////////////// + piQRThink(peer); + + // If we got disconnected from chat, do the cleanup before calling the callback. + //////////////////////////////////////////////////////////////////////////////// + if(connection->disconnect && (connection->callbackDepth == 0)) + piDisconnect(peer); + + // Let the callbacks think. + /////////////////////////// + piCallbacksThink(peer, opID); +} + +void peerThink(PEER peer) +{ + PEER_CONNECTION; + + // Think. + ///////// + piThink(peer, -1); + + PI_CHECK_SHUTDOWN +} + +CHAT peerGetChat(PEER peer) +{ + PEER_CONNECTION; + + // Return it. + ///////////// + return connection->chat; +} + +const char * peerGetNickA(PEER peer) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + // Check if connected. + ////////////////////// + if(!connection->connected) + return NULL; + + // Return it. + ///////////// + return connection->nick; +} +#ifdef GSI_UNICODE +const unsigned short * peerGetNickW(PEER peer) +{ + PEER_CONNECTION; + PEER_CONNECTED; + if (!connection->connected) + return NULL; + return connection->nick_W; +} +#endif + +#ifndef GSI_UNICODE +void peerFixNickA +( + char * newNick, + const char * oldNick +) +{ + chatFixNickA(newNick, oldNick); +} +const char * peerTranslateNickA +( + char * nick, + const char * extension +) +{ + return chatTranslateNickA(nick, extension); +} +#else +void peerFixNickW +( + unsigned short * newNick, + const unsigned short * oldNick +) +{ + chatFixNickW(newNick, oldNick); +} +const unsigned short * peerTranslateNickW +( + unsigned short * nick, + const unsigned short * extension +) +{ + return chatTranslateNickW(nick, extension); +} +#endif + +unsigned int peerGetPublicIP(PEER peer) +{ + PEER_CONNECTION; + + // Return it. + ///////////// + return connection->publicIP; +} + +unsigned int peerGetPrivateIP(PEER peer) +{ + PEER_CONNECTION; + + // Return it. + ///////////// + return connection->privateIP; +} + +int peerGetUserID(PEER peer) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + if(!connection->connected) + return 0; + + return chatGetUserID(connection->chat); +} + +int peerGetProfileID(PEER peer) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + if(!connection->connected) + return 0; + + return chatGetProfileID(connection->chat); +} + +void peerChangeNickA +( + PEER peer, + const char * newNick, + peerChangeNickCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + + PI_OP_ID; + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + + // Start the operation. + /////////////////////// + if(!piNewChangeNickOperation(peer, newNick, callback, param, opID)) + success = PEERFalse; + + // Check for failure. + ///////////////////// + if(!success) + piAddChangeNickCallback(peer, PEERFalse, connection->nick, newNick, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerChangeNickW +( + PEER peer, + const unsigned short * newNick, + peerChangeNickCallback callback, + void * param, + PEERBool blocking +) +{ + char* newNick_A = UCS2ToUTF8StringAlloc(newNick); + peerChangeNickA(peer, newNick_A, callback, param, blocking); + gsifree(newNick_A); +} +#endif + +void peerStayInRoom +( + PEER peer, + RoomType roomType +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + assert(roomType == TitleRoom); + if(roomType != TitleRoom) + return; + + if(!connection->title[0]) + return; + + connection->stayInTitleRoom = PEERTrue; +} + +void peerSetQuietMode +( + PEER peer, + PEERBool quiet +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + chatSetQuietMode(connection->chat, (CHATBool)quiet); +} + +void peerSetAwayModeA +( + PEER peer, + const char * reason +) +{ + char buffer[PI_AWAY_MAX_LEN + 6]; + PEER_CONNECTION; + PEER_CONNECTED; + + if(!reason) + reason = ""; + + // Store the setting. + ///////////////////// + connection->away = (PEERBool)(reason[0] != '\0'); + strzcpy(connection->awayReason, reason, PI_AWAY_MAX_LEN); + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); + + // Send the chat command. + ///////////////////////// + sprintf(buffer, "AWAY :%s", connection->awayReason); + chatSendRawA(connection->chat, buffer); +} +#ifdef GSI_UNICODE +void peerSetAwayModeW +( + PEER peer, + const unsigned short* reason +) +{ + char* reason_A = UCS2ToUTF8StringAlloc(reason); + peerSetAwayModeA(peer, reason_A); + gsifree(reason_A); +} +#endif + +void peerParseQueryA +( + PEER peer, + char * query, + int len, + struct sockaddr * sender +) +{ + PEER_CONNECTION; + + assert(query); + assert(sender); + + // Handle the query based on what type of reporting we're doing. + //////////////////////////////////////////////////////////////// + if(connection->queryReporting) + qr2_parse_queryA(connection->queryReporting, query, len, sender); + else if(connection->autoMatchReporting) + qr2_parse_queryA(connection->autoMatchReporting, query, len, sender); +} +/* NOT necessary since qr2_parse_query uses char not unsigned short +#ifdef GSI_UNICODE +void peerParseQueryW +( + PEER peer, + unsigned short * query, + int len, + struct sockaddr * sender +) +{ + char* query_A = UCS2ToUTF8StringAlloc(query); + peerParseQueryA(peer, query_A, len, sender); + gsifree(query_A); +} +#endif +*/ +void peerAuthenticateCDKeyA +( + PEER peer, + const char * cdkey, + peerAuthenticateCDKeyCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + + PI_OP_ID; + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + + // Start the operation. + /////////////////////// + if(!piNewAuthenticateCDKeyOperation(peer, cdkey, callback, param, opID)) + success = PEERFalse; + + // Check for failure. + ///////////////////// + if(!success) + piAddAuthenticateCDKeyCallback(peer, 0, "Error starting CD Key check", callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerAuthenticateCDKeyW +( + PEER peer, + const unsigned short* cdkey, + peerAuthenticateCDKeyCallback callback, + void * param, + PEERBool blocking +) +{ + char* cdkey_A = UCS2ToUTF8StringAlloc(cdkey); + peerAuthenticateCDKeyA(peer, cdkey_A, callback, param, blocking); + gsifree(cdkey_A); +} +#endif + +void peerSendNatNegotiateCookie +( + PEER peer, + unsigned int ip, + unsigned short port, + int cookie +) +{ + piSendNatNegotiateCookie(peer, ip, port, cookie); +} + +void peerSendMessageToServer +( + PEER peer, + unsigned int ip, + unsigned short port, + const char * data, + int len +) +{ + piSendMessageToServer(peer, ip, port, data, len); +} + +void peerAlwaysGetPlayerInfo +( + PEER peer, + PEERBool always +) +{ + PEER_CONNECTION; + + connection->alwaysRequestPlayerInfo = always; +} + +/********** +** ROOMS ** +**********/ +void peerJoinTitleRoomA +( + PEER peer, + const char password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + PEERJoinResult result = PEERJoinFailed; + char buffer[PI_ROOM_MAX_LEN]; + + PI_OP_ID; + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + + // NULL password is the same as empty password. + /////////////////////////////////////////////// + if(!password) + password = ""; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + { + success = PEERFalse; + result = PEERNoTitleSet; + } + + // Check for a connection. + ////////////////////////// + if(success && !connection->connected) + { + success = PEERFalse; + result = PEERNoConnection; + } + + // Check if we're in the title room. + //////////////////////////////////// + assert(!connection->enteringRoom[TitleRoom] && !connection->inRoom[TitleRoom]); + if((success && connection->enteringRoom[TitleRoom]) || connection->inRoom[TitleRoom]) + { + success = PEERFalse; + result = PEERAlreadyInRoom; + } + + // Check if we're AutoMatching. + /////////////////////////////// + assert(!peerIsAutoMatching(peer)); + if(success && peerIsAutoMatching(peer)) + { + success = PEERFalse; + result = PEERAutoMatching; + } + + // Get the room name. + ///////////////////// + if(success) + { + if(connection->titleRoomChannel[0]) + strcpy(buffer, connection->titleRoomChannel); + else + piMangleTitleRoom(buffer, connection->title); + } + + // Start the operation. + /////////////////////// + if(success && !piNewJoinRoomOperation(peer, TitleRoom, buffer, password, callback, param, opID)) + success = PEERFalse; + + // Check for failure. + ///////////////////// + if(!success) + piAddJoinRoomCallback(peer, PEERFalse, result, TitleRoom, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerJoinTitleRoomW +( + PEER peer, + const unsigned short password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + char* password_A = NULL; + if (password != NULL) + password_A = UCS2ToUTF8StringAlloc(password); + peerJoinTitleRoomA(peer, password_A, callback, param, blocking); + gsifree(password_A); +} +#endif + +void peerJoinGroupRoom +( + PEER peer, + int groupID, + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + PEERJoinResult result = PEERJoinFailed; + char room[PI_ROOM_MAX_LEN]; + + PI_OP_ID; + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + { + success = PEERFalse; + result = PEERNoTitleSet; + } + + // Check for a connection. + ////////////////////////// + if(success && !connection->connected) + { + success = PEERFalse; + result = PEERNoConnection; + } + + // Check if we're AutoMatching. + /////////////////////////////// + assert(!peerIsAutoMatching(peer)); + if(success && peerIsAutoMatching(peer)) + { + success = PEERFalse; + result = PEERAutoMatching; + } + + // Check the ID. + //////////////// + assert(groupID); + if(success && !groupID) + success = PEERFalse; + + // Check if we're in a group room. + ////////////////////////////////// + if(success && (connection->enteringRoom[GroupRoom] || connection->inRoom[GroupRoom])) + { + success = PEERFalse; + result = PEERAlreadyInRoom; + } + + // Create the name. + /////////////////// + piMangleGroupRoom(room, groupID); + + // Save off the group id. + ///////////////////////// + connection->groupID = groupID; + + // Start the operation. + /////////////////////// + if(success && !piNewJoinRoomOperation(peer, GroupRoom, room, NULL, callback, param, opID)) + success = PEERFalse; + + // Check for failure. + ///////////////////// + if(!success) + piAddJoinRoomCallback(peer, PEERFalse, result, GroupRoom, callback, param, opID); + + PI_DO_BLOCKING; +} + +void peerSetGroupID +( + PEER peer, + int groupID +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return; + + // Save off the group id. + ///////////////////////// + connection->groupID = groupID; +} + +int peerGetGroupID +( + PEER peer +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return 0; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return 0; + + // Get the group id. + //////////////////// + return connection->groupID; +} + +static void piJoinStagingRoom +( + PEER peer, + SBServer server, + const char * channel, + const char password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + unsigned int publicIP = 0; + unsigned int privateIP = 0; + unsigned short privatePort = 0; + char room[PI_ROOM_MAX_LEN]; + PEERBool success = PEERTrue; + PEERJoinResult result = PEERJoinFailed; + + PI_OP_ID; + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + + // NULL password is the same as empty password. + /////////////////////////////////////////////// + if(!password) + password = ""; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + { + success = PEERFalse; + result = PEERNoTitleSet; + } + + // Check for a connection. + ////////////////////////// + if(success && !connection->connected) + { + success = PEERFalse; + result = PEERNoConnection; + } + + // Check if we're in a staging room. + //////////////////////////////////// + if(success && (connection->enteringRoom[StagingRoom] || connection->inRoom[StagingRoom])) + { + success = PEERFalse; + result = PEERAlreadyInRoom; + } + + // Check if we're AutoMatching. + /////////////////////////////// + assert(!peerIsAutoMatching(peer)); + if(success && peerIsAutoMatching(peer)) + { + success = PEERFalse; + result = PEERAutoMatching; + } + + // If we have a server, get the public and private IPs and ports. + ///////////////////////////////////////////////////////////////// + if(success && server) + { + publicIP = SBServerGetPublicInetAddress(server); + privateIP = SBServerGetPrivateInetAddress(server); + if(SBServerHasPrivateAddress(server)) + privatePort = SBServerGetPrivateQueryPort(server); + else + privatePort = SBServerGetPublicQueryPort(server); + + if(!publicIP) + success = PEERFalse; + } + + // If we have a channel, check it. + ////////////////////////////////// + if(success && !server) + { + assert(channel); + assert(channel[0]); + if(!channel || !channel[0]) + success = PEERFalse; + } + + // Stop hosting. + //////////////// + if(success) + piStopHosting(peer, PEERTrue); + + // If we have a server, get the staging room. + ///////////////////////////////////////////// + if(success && server) + piMangleStagingRoom(room, connection->title, publicIP, privateIP, privatePort); + + // Start the operation. + /////////////////////// + if(success && !piNewJoinRoomOperation(peer, StagingRoom, server?room:channel, password, callback, param, opID)) + success = PEERFalse; + + // If we have a server, clone it. + ///////////////////////////////// + if(success && server) + connection->hostServer = piSBCloneServer(server); + + // Check for failure. + ///////////////////// + if(!success) + piAddJoinRoomCallback(peer, PEERFalse, result, StagingRoom, callback, param, opID); + + PI_DO_BLOCKING; +} + +void peerJoinStagingRoomA +( + PEER peer, + SBServer server, + const char password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + piJoinStagingRoom(peer, server, NULL, password, callback, param, blocking); +} +#ifdef GSI_UNICODE +void peerJoinStagingRoomW +( + PEER peer, + SBServer server, + const unsigned short password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + char* password_A = UCS2ToUTF8StringAlloc(password); + peerJoinStagingRoomA(peer, server, password_A, callback, param, blocking); + gsifree(password_A); +} +#endif + +void peerJoinStagingRoomByChannelA +( + PEER peer, + const char * channel, + const char password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + piJoinStagingRoom(peer, NULL, channel, password, callback, param, blocking); +} +#ifdef GSI_UNICODE +void peerJoinStagingRoomByChannelW +( + PEER peer, + const unsigned short * channel, + const unsigned short password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* password_A = UCS2ToUTF8StringAlloc(password); + peerJoinStagingRoomByChannelA(peer, channel_A, password_A, callback, param, blocking); + gsifree(password_A); + gsifree(channel_A); +} +#endif + +void peerCreateStagingRoomA +( + PEER peer, + const char * name, + int maxPlayers, + const char password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + peerCreateStagingRoomWithSocketA(peer, name, maxPlayers, password, INVALID_SOCKET, 0, callback, param, blocking); +} +#ifdef GSI_UNICODE +void peerCreateStagingRoomW +( + PEER peer, + const unsigned short * name, + int maxPlayers, + const unsigned short password[PEER_PASSWORD_LEN], + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + char* name_A = UCS2ToUTF8StringAlloc(name); + char* password_A = UCS2ToUTF8StringAlloc(password); + peerCreateStagingRoomA(peer, name_A, maxPlayers, password_A, callback, param, blocking); + gsifree(password_A); + gsifree(name_A); +} +#endif + +void peerCreateStagingRoomWithSocketA +( + PEER peer, + const char * name, + int maxPlayers, + const char password[PEER_PASSWORD_LEN], + SOCKET socket, + unsigned short port, + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + PEERJoinResult result = PEERJoinFailed; + PI_OP_ID; + + PEER_CONNECTION; + + assert(name); + assert(connection->title[0]); + assert(callback); + assert(maxPlayers >= 0); + + // NULL password is the same as empty password. + /////////////////////////////////////////////// + if(!password) + password = ""; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + { + success = PEERFalse; + result = PEERNoTitleSet; + } + + // Check for a connection. + ////////////////////////// + if(success && !connection->connected) + { + success = PEERFalse; + result = PEERNoConnection; + } + + // Check if we're in a staging room. + //////////////////////////////////// + if(success && (connection->enteringRoom[StagingRoom] || connection->inRoom[StagingRoom])) + { + success = PEERFalse; + result = PEERAlreadyInRoom; + } + + // Check if we're AutoMatching. + /////////////////////////////// + assert(!peerIsAutoMatching(peer)); + if(success && peerIsAutoMatching(peer)) + { + success = PEERFalse; + result = PEERAutoMatching; + } + + // Stop hosting. + //////////////// + if(success) + piStopHosting(peer, PEERTrue); + + // Start the operation. + /////////////////////// + if(success && !piNewCreateStagingRoomOperation(peer, name, password, maxPlayers, socket, port, callback, param, opID)) + success = PEERFalse; + + // Add callback if error. + ///////////////////////// + if(!success) + piAddJoinRoomCallback(peer, PEERFalse, result, StagingRoom, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerCreateStagingRoomWithSocketW +( + PEER peer, + const unsigned short * name, + int maxPlayers, + const unsigned short password[PEER_PASSWORD_LEN], + SOCKET socket, + unsigned short port, + peerJoinRoomCallback callback, + void * param, + PEERBool blocking +) +{ + char* name_A = UCS2ToUTF8StringAlloc(name); + char* password_A = UCS2ToUTF8StringAlloc(password); + peerCreateStagingRoomWithSocketA(peer, name_A, maxPlayers, password_A, socket, port, callback, param, blocking); + gsifree(password_A); + gsifree(name_A); +} +#endif + +// Should only be called when a game report is already in progress +// Always call after creating staging room, starting reporting, +// starting automatch +////////////////////////////////////////////////////////////////////////// +/* +qr2_t peerGetReportingRecord(PEER peer) +{ + PEER_CONNECTION; + if (!connection->title[0]) + return NULL; + + if (!connection->connected) + return NULL; + + assert(connection->queryReporting || connection->autoMatchReporting); + // When we are reporting normal games, the normal qr2 record + // is returned. + if (connection->queryReporting) + { + return connection->queryReporting; + } + + + // When we are reporting automatch games, the automatch qr2 record + // is returned. + if (peerIsAutoMatching(peer) && connection->autoMatchReporting) + { + return connection->autoMatchReporting; + } + + return NULL; +} +*/ + +void peerLeaveRoomA +( + PEER peer, + RoomType roomType, + const char * reason +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return; + + // Check if we're in or entering. + ///////////////////////////////// + if(!ENTERING_ROOM && !IN_ROOM) + return; + + // Leave. + ///////// + piLeaveRoom(peer, roomType, reason); + + // Is this an AutoMatch room? + ///////////////////////////// + if((roomType == StagingRoom) && peerIsAutoMatching(peer)) + { + // Go back to searching. + //////////////////////// + piSetAutoMatchStatus(peer, PEERSearching); + } +} +#ifdef GSI_UNICODE +void peerLeaveRoomW +( + PEER peer, + RoomType roomType, + const unsigned short* reason +) +{ + if (reason != NULL) + { + char* reason_A = UCS2ToUTF8StringAlloc(reason); + peerLeaveRoomA(peer, roomType, reason_A); + gsifree(reason_A); + } + else + peerLeaveRoomA(peer, roomType, NULL); +} +#endif + +void peerListGroupRoomsA +( + PEER peer, + const char * fields, + peerListGroupRoomsCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + PI_OP_ID; + PEER_CONNECTION; + + assert(callback); + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + success = PEERFalse; + + // Can't have a NULL fields. + //////////////////////////// + if(!fields) + fields = ""; + + // Start the listing. + ///////////////////// + if(success && !piNewListGroupRoomsOperation(peer, fields, callback, param, opID)) + success = PEERFalse; + + // Call the callback if failed. + /////////////////////////////// + if(!success) + piAddListGroupRoomsCallback(peer, PEERFalse, 0, NULL, NULL, 0, 0, 0, 0, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerListGroupRoomsW +( + PEER peer, + const unsigned short * fields, + peerListGroupRoomsCallback callback, + void * param, + PEERBool blocking +) +{ + char* fields_A = UCS2ToUTF8StringAlloc(fields); + peerListGroupRoomsA(peer, fields_A, callback, param, blocking); + gsifree(fields_A); +} +#endif + +void peerStartListingGamesA +( + PEER peer, + const unsigned char * fields, + int numFields, + const char * filter, + peerListingGamesCallback callback, + void * param +) +{ + PEERBool success; + + PEER_CONNECTION; + + assert(callback); + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Can't have an empty filter. + ////////////////////////////// + if(filter && !filter[0]) + filter = NULL; + + // Check the fields. + //////////////////// + if(!fields || (numFields <= 0)) + numFields = 0; + + // Save the callback info. + ////////////////////////// + connection->gameListCallback = callback; + connection->gameListParam = param; + + // Start the listing. + ///////////////////// + success = piSBStartListingGames(peer, fields, numFields, filter); + + // Call the callback if failed. + /////////////////////////////// + if(!success) + piAddListingGamesCallback(peer, PEERFalse, NULL, 0); +} +#ifdef GSI_UNICODE +void peerStartListingGamesW +( + PEER peer, + const unsigned char * fields, + int numFields, + const unsigned short * filter, + peerListingGamesCallback callback, + void * param +) +{ + char* filter_A = UCS2ToUTF8StringAlloc(filter); + peerStartListingGamesA(peer, fields, numFields, filter_A, callback, param); + gsifree(filter_A); +} +#endif + +void peerStopListingGames +( + PEER peer +) +{ + PEER_CONNECTION; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Stop the listing. + //////////////////// + piSBStopListingGames(peer); +} + +void peerUpdateGame +( + PEER peer, + SBServer server, + PEERBool fullUpdate +) +{ + PEER_CONNECTION; + + assert(server); + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Update the server. + // Changed 08-26-2004 + // Saad Nader + // Added force update by master server parameter + // for internal update function + ///////////////////// + piSBUpdateGame(peer, server, fullUpdate, PEERFalse, PEERFalse); +} + +// Added 08-26-2004 +// By Saad Nader +// per request of developer +//////////////////////////////////////////////////////////////////////////// +void peerUpdateGameByMaster(PEER peer, SBServer server, PEERBool fullUpdate) +{ + // obtain and check the peer connection object + PEER_CONNECTION; + + // validate server for sanity check + assert(server); + + // Check that we have set a title + if(!connection->title[0]) + return; + + // Let internal update take place via the master server + piSBUpdateGame(peer, server, fullUpdate, PEERTrue, PEERFalse); +} + +void peerUpdateGamePing(PEER peer, SBServer server) +{ + // obtain and check the peer connection object + PEER_CONNECTION; + + // validate server for sanity check + assert(server); + + // Check that we have set a title + if(!connection->title[0]) + return; + + // Let internal update take place via the master server + piSBUpdateGame(peer, server, PEERFalse, PEERFalse, PEERTrue); +} + +void peerMessageRoomA +( + PEER peer, + RoomType roomType, + const char * message, + MessageType messageType +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + ASSERT_MESSAGETYPE(messageType); + + // Check for no message. + //////////////////////// + if(!message || !message[0]) + return; + + // Check that we're in this room. + ///////////////////////////////// + assert(IN_ROOM); + if(!IN_ROOM) + return; + + // Send the message. + //////////////////// + chatSendChannelMessageA(connection->chat, ROOM, message, (int)messageType); +} +#ifdef GSI_UNICODE +void peerMessageRoomW +( + PEER peer, + RoomType roomType, + const unsigned short * message, + MessageType messageType +) +{ + char* message_A = UCS2ToUTF8StringAlloc(message); + peerMessageRoomA(peer, roomType, message_A, messageType); + gsifree(message_A); +} +#endif + +void peerUTMRoomA +( + PEER peer, + RoomType roomType, + const char * command, + const char * parameters, + PEERBool authenticate +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + + // Check that we're in this room. + ///////////////////////////////// + assert(IN_ROOM); + if(!IN_ROOM) + return; + + // Send it. + /////////// + piSendChannelUTM(peer, ROOM, command, parameters, authenticate); +} +#ifdef GSI_UNICODE +void peerUTMRoomW +( + PEER peer, + RoomType roomType, + const unsigned short * command, + const unsigned short * parameters, + PEERBool authenticate +) +{ + char* command_A = UCS2ToUTF8StringAlloc(command); + char* parameters_A = UCS2ToUTF8StringAlloc(parameters); + peerUTMRoomA(peer, roomType, command_A, parameters_A, authenticate); + gsifree(parameters_A); + gsifree(command_A); +} +#endif + +void peerSetPasswordA +( + PEER peer, + RoomType roomType, + const char password[PEER_PASSWORD_LEN] +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + + // Check room type. + //////////////////// + assert(roomType == StagingRoom); + if(roomType != StagingRoom) + return; + + // NULL password is the same as empty password. + /////////////////////////////////////////////// + if(!password) + password = ""; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return; + + // Check if we're in or entering. + ///////////////////////////////// + if(!ENTERING_ROOM && !IN_ROOM) + return; + + // Make sure we're hosting. + /////////////////////////// + assert(connection->hosting); + if(!connection->hosting) + return; + + // Set/clear the password. + ////////////////////////// + if(password[0]) + chatSetChannelPasswordA(connection->chat, ROOM, CHATTrue, password); + else + chatSetChannelPasswordA(connection->chat, ROOM, CHATFalse, "x"); + + // Set the passworded flag. + /////////////////////////// + connection->passwordedRoom = password[0]?PEERTrue:PEERFalse; + + // Send a state-changed. + //////////////////////// + piSendStateChanged(peer); +} +#ifdef GSI_UNICODE +void peerSetPasswordW +( + PEER peer, + RoomType roomType, + const unsigned short password[PEER_PASSWORD_LEN] +) +{ + char* password_A = UCS2ToUTF8StringAlloc(password); + peerSetPasswordA(peer, roomType, password_A); + gsifree(password_A); +} +#endif + +void peerSetRoomNameA +( + PEER peer, + RoomType roomType, + const char * name +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + + assert(roomType == StagingRoom); + + // NULL name is the same as empty name. + /////////////////////////////////////// + if(!name) + name = ""; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return; + + // Check if we're in or entering. + ///////////////////////////////// + if(!ENTERING_ROOM && !IN_ROOM) + return; + + // Make sure we're hosting. + /////////////////////////// + assert(connection->hosting); + if(!connection->hosting) + return; + + // Set it. + ////////// + chatSetChannelTopicA(connection->chat, ROOM, name); +} +#ifdef GSI_UNICODE +void peerSetRoomNameW +( + PEER peer, + RoomType roomType, + const unsigned short * name +) +{ + char* name_A = UCS2ToUTF8StringAlloc(name); + peerSetRoomNameA(peer, roomType, name_A); + gsifree(name_A); +} +#endif + +const char * peerGetRoomNameA +( + PEER peer, + RoomType roomType +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + assert(IN_ROOM); + if(!IN_ROOM) + return NULL; + + return NAME; +} +#ifdef GSI_UNICODE +const unsigned short * peerGetRoomNameW +( + PEER peer, + RoomType roomType +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + assert(IN_ROOM); + if(!IN_ROOM) + return NULL; + + return NAME_W; +} +#endif + +const char * peerGetRoomChannelA +( + PEER peer, + RoomType roomType +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + assert(IN_ROOM || ENTERING_ROOM); + if(!IN_ROOM && ! ENTERING_ROOM) + return NULL; + + return ROOM; +} +#ifdef GSI_UNICODE +const unsigned short * peerGetRoomChannelW +( + PEER peer, + RoomType roomType +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + assert(IN_ROOM || ENTERING_ROOM); + if(!IN_ROOM && ! ENTERING_ROOM) + return NULL; + + return ROOM_W; +} +#endif + +PEERBool peerInRoom +( + PEER peer, + RoomType roomType +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + + return IN_ROOM; +} + +void peerSetTitleRoomChannelA +( + PEER peer, + const char * channel +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return; + + // Check for no channel. + //////////////////////// + if(!channel) + channel = ""; + + // Copy it. + /////////// + strzcpy(connection->titleRoomChannel, channel, PI_ROOM_MAX_LEN); +} +#ifdef GSI_UNICODE +void peerSetTitleRoomChannelW +( + PEER peer, + const unsigned short * channel +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + peerSetTitleRoomChannelA(peer, channel_A); + gsifree(channel_A); +} +#endif + +SBServer peerGetHostServer(PEER peer) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + return connection->hostServer; +} + +/************ +** PLAYERS ** +************/ +typedef struct piEnumPlayersData +{ + peerEnumPlayersCallback callback; + void * param; +} piEnumPlayersData; + +static void piEnumPlayersEnumRoomPlayersCallback +( + PEER peer, + RoomType roomType, + piPlayer * player, + int index, + void *param +) +{ + piEnumPlayersData * data = (piEnumPlayersData *)param; + const char * nick; + + int flags; + + if(player) + { + nick = player->nick; + flags = player->flags[roomType]; + } + else + { + nick = NULL; + flags = 0; + } + + // Call the callback. + ///////////////////// +#ifndef GSI_UNICODE + data->callback(peer, PEERTrue, roomType, index, nick, flags, data->param); +#else + if (nick == NULL) + data->callback(peer, PEERTrue, roomType, index, NULL, flags, data->param); + else + { + unsigned short nick_W[512]; + UTF8ToUCS2String(nick, nick_W); + data->callback(peer, PEERTrue, roomType, index, nick_W, flags, data->param); + } +#endif +} + +void peerEnumPlayers +( + PEER peer, + RoomType roomType, + peerEnumPlayersCallback callback, + void * param +) +{ + PEERBool success = PEERTrue; + piEnumPlayersData data; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + ASSERT_ROOMTYPE(roomType); + + // Check that we're in this room. + ///////////////////////////////// + assert(IN_ROOM); + if(success && !IN_ROOM) + success = PEERFalse; + + // Check for failure. + ///////////////////// + if(!success) + { + // Call the callback. + ///////////////////// + callback(peer, PEERFalse, roomType, -1, NULL, PEERFalse, param); + return; + } + + // Enumerate through the players, using a local copy. + ///////////////////////////////////////////////////// + data.callback = callback; + data.param = param; + piEnumRoomPlayers(peer, roomType, piEnumPlayersEnumRoomPlayersCallback, &data); +} + +void peerMessagePlayerA +( + PEER peer, + const char * nick, + const char * message, + MessageType messageType +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_MESSAGETYPE(messageType); + assert(nick); + assert(nick[0]); + + // Check for connection succeeded. + ////////////////////////////////// + if(!connection->connected) + return; + + // Check for no message. + //////////////////////// + if(!message || !message[0]) + return; + + // Send the message to this player. + /////////////////////////////////// + chatSendUserMessageA(connection->chat, nick, message, (int)messageType); +} +#ifdef GSI_UNICODE +void peerMessagePlayerW +( + PEER peer, + const unsigned short * nick, + const unsigned short * message, + MessageType messageType +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char* message_A = UCS2ToUTF8StringAlloc(message); + peerMessagePlayerA(peer, nick_A, message_A, messageType); + gsifree(nick_A); + gsifree(message_A); +} +#endif + +void peerUTMPlayerA +( + PEER peer, + const char * nick, + const char * command, + const char * parameters, + PEERBool authenticate +) +{ + // Send it. + /////////// + piSendPlayerUTM(peer, nick, command, parameters, authenticate); +} +#ifdef GSI_UNICODE +void peerUTMPlayerW +( + PEER peer, + const unsigned short * nick, + const unsigned short * command, + const unsigned short * parameters, + PEERBool authenticate +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char* command_A = UCS2ToUTF8StringAlloc(command); + char* parameters_A = UCS2ToUTF8StringAlloc(parameters); + peerUTMPlayerA(peer, nick_A, command_A, parameters_A, authenticate); + gsifree(parameters_A); + gsifree(command_A); + gsifree(nick_A); +} +#endif + +void peerKickPlayerA +( + PEER peer, + RoomType roomType, + const char * nick, + const char * reason +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + ASSERT_ROOMTYPE(roomType); + assert(IN_ROOM || ENTERING_ROOM); + assert(nick); + assert(nick[0]); + + // Check for connection succeeded. + ////////////////////////////////// + if(!connection->connected) + return; + + // Kick the player. + /////////////////// + chatKickUserA(connection->chat, ROOM, nick, reason); +} +#ifdef GSI_UNICODE +void peerKickPlayerW +( + PEER peer, + RoomType roomType, + const unsigned short * nick, + const unsigned short * reason +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char* reason_A = UCS2ToUTF8StringAlloc(reason); + peerKickPlayerA(peer, roomType, nick_A, reason_A); + gsifree(reason_A); + gsifree(nick_A); +} +#endif + +PEERBool peerGetPlayerPingA +( + PEER peer, + const char * nick, + int * ping +) +{ + piPlayer * player; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(nick); + assert(nick[0]); + assert(ping); + + // Get the player. + ////////////////// + player = piGetPlayer(peer, nick); + if(!player) + return PEERFalse; + + // Is it the local player? + ////////////////////////// + if(player->local) + { + *ping = 0; + } + else + { + // Check if there's a ping. + /////////////////////////// + if(!player->numPings) + return PEERFalse; + + *ping = player->pingAverage; + } + + return PEERTrue; +} +#ifdef GSI_UNICODE +PEERBool peerGetPlayerPingW +( + PEER peer, + const unsigned short * nick, + int * ping +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + PEERBool result = peerGetPlayerPingA(peer, nick_A, ping); + gsifree(nick_A); + return result; +} +#endif + +PEERBool peerGetPlayersCrossPingA +( + PEER peer, + const char * nick1, + const char * nick2, + int * crossPing +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + assert(nick1); + assert(nick1[0]); + assert(nick2); + assert(nick2[0]); + assert(crossPing); + + // Do it. + ///////// + return piGetXping(peer, nick1, nick2, crossPing); +} +#ifdef GSI_UNICODE +PEERBool peerGetPlayersCrossPingW +( + PEER peer, + const unsigned short * nick1, + const unsigned short * nick2, + int * crossPing +) +{ + char* nick1_A = UCS2ToUTF8StringAlloc(nick1); + char* nick2_A = UCS2ToUTF8StringAlloc(nick2); + PEERBool result = peerGetPlayersCrossPingA(peer, nick1_A, nick2_A, crossPing); + gsifree(nick2_A); + gsifree(nick1_A); + return result; +} +#endif + +PEERBool peerPingPlayerA +( + PEER peer, + const char * nick +) +{ + piPlayer * player; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(nick); + assert(nick[0]); + + // Get the player. + ////////////////// + player = piGetPlayer(peer, nick); + if(!player) + return PEERFalse; + + // Is it the local player? + ////////////////////////// + if(player->local) + return PEERFalse; + + // Do we have the IP? + ///////////////////// + if(!player->gotIPAndProfileID) + return PEERFalse; + + // Is the player already being pinged? + ////////////////////////////////////// + if(player->waitingForPing) + return PEERTrue; + + // Set this player as a must ping. + ////////////////////////////////// + player->mustPing = PEERTrue; + + // Set this player as a one-time ping. + ////////////////////////////////////// + if(!player->inPingRoom) + player->pingOnce = PEERTrue; + + return PEERTrue; +} +#ifdef GSI_UNICODE +PEERBool peerPingPlayerW +( + PEER peer, + const unsigned short * nick +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + PEERBool result = peerPingPlayerA(peer, nick_A); + gsifree(nick_A); + return result; +} +#endif + +PEERBool peerGetPlayerInfoNoWaitA +( + PEER peer, + const char * nick, + unsigned int * IP, + int * profileID +) +{ + piPlayer * player; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(nick); + + player = piGetPlayer(peer, nick); + if(!player || !player->gotIPAndProfileID) + { + const char * info; + unsigned int locIP; + int locProfileID; + + // Can we get it from chat? + /////////////////////////// + if(chatGetBasicUserInfoNoWaitA(connection->chat, nick, &info, NULL) && piDemangleUser(info, &locIP, &locProfileID)) + { + if(player) + piSetPlayerIPAndProfileID(peer, nick, locIP, locProfileID); + + if(IP) + *IP = locIP; + if(profileID) + *profileID = locProfileID; + + return PEERTrue; + } + return PEERFalse; + } + + if(IP) + *IP = player->IP; + if(profileID) + *profileID = player->profileID; + + return PEERTrue; +} +#ifdef GSI_UNICODE +PEERBool peerGetPlayerInfoNoWaitW +( + PEER peer, + const unsigned short * nick, + unsigned int * IP, + int * profileID +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + PEERBool result = peerGetPlayerInfoNoWaitA(peer, nick_A, IP, profileID); + gsifree(nick_A); + return result; +} +#endif + +void peerGetPlayerInfoA +( + PEER peer, + const char * nick, + peerGetPlayerInfoCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + piPlayer * player; + PI_OP_ID; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + assert(nick); + assert(nick[0]); + assert(callback); + + // Find the player. + /////////////////// + player = piGetPlayer(peer, nick); + + // Check if chat has it. + //////////////////////// + if(player && !player->gotIPAndProfileID) + { + const char * info; + unsigned int IP; + int profileID; + + if(chatGetBasicUserInfoNoWaitA(connection->chat, nick, &info, NULL) && piDemangleUser(info, &IP, &profileID)) + { + piSetPlayerIPAndProfileID(peer, nick, IP, profileID); + } + } + + // See if we already have it. + ///////////////////////////// + if(player && player->gotIPAndProfileID) + { + piAddGetPlayerInfoCallback(peer, PEERTrue, nick, player->IP, player->profileID, callback, param, opID); + } + else + { + // Start an op to get it. + ///////////////////////// + if(!piNewGetPlayerInfoOperation(peer, nick, callback, param, opID)) + success = PEERFalse; + } + + // If failed, add the callback. + /////////////////////////////// + if(!success) + piAddGetPlayerInfoCallback(peer, PEERFalse, nick, 0, 0, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerGetPlayerInfoW +( + PEER peer, + const unsigned short * nick, + peerGetPlayerInfoCallback callback, + void * param, + PEERBool blocking +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + peerGetPlayerInfoA(peer, nick_A, callback, param, blocking); + gsifree(nick_A); +} +#endif + +void peerGetPlayerProfileIDA +( + PEER peer, + const char * nick, + peerGetPlayerProfileIDCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + piPlayer * player; + PI_OP_ID; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + assert(nick); + assert(nick[0]); + assert(callback); + + // Find the player. + /////////////////// + player = piGetPlayer(peer, nick); + + // Check if chat has it. + //////////////////////// + if(player && !player->gotIPAndProfileID) + { + const char * info; + unsigned int IP; + int profileID; + + if(chatGetBasicUserInfoNoWaitA(connection->chat, nick, &info, NULL) && piDemangleUser(info, &IP, &profileID)) + { + piSetPlayerIPAndProfileID(peer, nick, IP, profileID); + } + } + + // See if we already have it. + ///////////////////////////// + if(player && player->gotIPAndProfileID) + { + piAddGetPlayerProfileIDCallback(peer, PEERTrue, nick, player->profileID, callback, param, opID); + } + else + { + // Start an op to get it. + ///////////////////////// + if(!piNewGetProfileIDOperation(peer, nick, callback, param, opID)) + success = PEERFalse; + } + + // If failed, add the callback. + /////////////////////////////// + if(!success) + piAddGetPlayerProfileIDCallback(peer, PEERFalse, nick, 0, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerGetPlayerProfileIDW +( + PEER peer, + const unsigned short * nick, + peerGetPlayerProfileIDCallback callback, + void * param, + PEERBool blocking +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + peerGetPlayerProfileIDA(peer, nick_A, callback, param, blocking); + gsifree(nick_A); +} +#endif + +void peerGetPlayerIPA +( + PEER peer, + const char * nick, + peerGetPlayerIPCallback callback, + void * param, + PEERBool blocking +) +{ + PEERBool success = PEERTrue; + piPlayer * player; + PI_OP_ID; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(callback); + assert(nick); + assert(nick[0]); + assert(callback); + + // Find the player. + /////////////////// + player = piGetPlayer(peer, nick); + + // Check if chat has it. + //////////////////////// + if(player && !player->gotIPAndProfileID) + { + const char * info; + unsigned int IP; + int profileID; + + if(chatGetBasicUserInfoNoWaitA(connection->chat, nick, &info, NULL) && piDemangleUser(info, &IP, &profileID)) + { + piSetPlayerIPAndProfileID(peer, nick, IP, profileID); + } + } + + // Check if we already have it. + /////////////////////////////// + if(player && player->gotIPAndProfileID) + { + piAddGetPlayerIPCallback(peer, PEERTrue, nick, player->IP, callback, param, opID); + } + else + { + // Start an op to get it. + ///////////////////////// + if(!piNewGetIPOperation(peer, nick, callback, param, opID)) + success = PEERFalse; + } + + // If failed, add the callback. + /////////////////////////////// + if(!success) + piAddGetPlayerIPCallback(peer, PEERFalse, nick, 0, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerGetPlayerIPW +( + PEER peer, + const unsigned short * nick, + peerGetPlayerIPCallback callback, + void * param, + PEERBool blocking +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + peerGetPlayerIPA(peer, nick_A, callback, param, blocking); + gsifree(nick_A); +} +#endif + +PEERBool peerIsPlayerHostA +( + PEER peer, + const char * nick, + RoomType roomType +) +{ + piPlayer * player; + + PEER_CONNECTION; + PEER_CONNECTED; + + // Are we in this type of room? + /////////////////////////////// + assert(IN_ROOM); + if(!IN_ROOM) + return PEERFalse; + + // Get the player. + ////////////////// + player = piGetPlayer(peer, nick); + if(!player) + return PEERFalse; + + // If it's the local player, return the value we store. + /////////////////////////////////////////////////////// + if(player->local) + return connection->hosting; + + // Is he host? + ////////////// + return piIsPlayerHost(player); +} +#ifdef GSI_UNICODE +PEERBool peerIsPlayerHostW +( + PEER peer, + const unsigned short * nick, + RoomType roomType +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + PEERBool result = peerIsPlayerHostA(peer, nick_A, roomType); + gsifree(nick_A); + return result; +} +#endif + +PEERBool peerGetPlayerFlagsA +( + PEER peer, + const char * nick, + RoomType roomType, + int * flags +) +{ + piPlayer * player; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(flags); + if(!flags) + return PEERFalse; + + // Are we in this type of room? + /////////////////////////////// + assert(IN_ROOM); + if(!IN_ROOM) + return PEERFalse; + + // Get the player. + ////////////////// + player = piGetPlayer(peer, nick); + if(!player) + return PEERFalse; + + // Is he in? + //////////// + if(!player->inRoom[roomType]) + return PEERFalse; + + // Get the flags. + ///////////////// + *flags = player->flags[roomType]; + + return PEERTrue; +} +#ifdef GSI_UNICODE +PEERBool peerGetPlayerFlagsW +( + PEER peer, + const unsigned short * nick, + RoomType roomType, + int * flags +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + PEERBool result = peerGetPlayerFlagsA(peer, nick_A, roomType, flags); + gsifree(nick_A); + return result; +} +#endif + +/********* +** GAME ** +*********/ +void peerSetReady +( + PEER peer, + PEERBool ready +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return; + + // Are we in a staging room? + //////////////////////////// + assert(connection->inRoom[StagingRoom]); + if(!connection->inRoom[StagingRoom]) + return; + + // Don't do anything if the state isn't changing. + ///////////////////////////////////////////////// + if(connection->ready == ready) + return; + + // Set it. + ////////// + connection->ready = ready; + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); + +#if 1 + // Send an old-style ready notice. + // THIS IS ONLY NEEDED FOR BACKWARDS COMPATIBILITY AND SHOULD BE REMOVED AT SOME POINT IN THE FUTURE. + //////////////////////////////////////////////////////////////////////////////////////////////////// + { + char buffer[32]; + //IN_ADDR addr; + //addr.s_addr = connection->publicIP; + strcpy(buffer, "@@@NFO \\$flags$\\"); + if(ready) + strcat(buffer, "r"); + strcat(buffer, "X\\"); // Flag to indicate this was sent by a new client. + peerMessageRoomA(peer, StagingRoom, buffer, NormalMessage); + } +#endif +} + +PEERBool peerGetReadyA +( + PEER peer, + const char * nick, + PEERBool * ready +) +{ + piPlayer * player; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(nick); + assert(nick[0]); + assert(ready); + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return PEERFalse; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return PEERFalse; + + // Are we in a staging room? + //////////////////////////// + assert(connection->inRoom[StagingRoom]); + if(!connection->inRoom[StagingRoom]) + return PEERFalse; + + // Get the player. + ////////////////// + player = piGetPlayer(peer, nick); + if(!player || !player->inRoom[StagingRoom]) + return PEERFalse; + + *ready = (PEERBool)((player->flags[StagingRoom] & PEER_FLAG_READY) != 0); + + return PEERTrue; +} +#ifdef GSI_UNICODE +PEERBool peerGetReadyW +( + PEER peer, + const unsigned short * nick, + PEERBool * ready +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + PEERBool result = peerGetReadyA(peer, nick_A, ready); + gsifree(nick_A); + return result; +} +#endif + +static void piAreAllReadyEnumRoomPlayersCallback +( + PEER peer, + RoomType roomType, + piPlayer * player, + int index, + void *param +) +{ + if(player) + { + PEERBool * allReadyPtr = (PEERBool *)param; + + // If this player's not ready, set the flag. + //////////////////////////////////////////// + if(!(player->flags[StagingRoom] & PEER_FLAG_READY)) + *allReadyPtr = PEERFalse; + } + + GSI_UNUSED(peer); + GSI_UNUSED(roomType); + GSI_UNUSED(index); +} + +PEERBool peerAreAllReady +( + PEER peer +) +{ + PEERBool allReady; + + PEER_CONNECTION; + PEER_CONNECTED; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return PEERFalse; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return PEERFalse; + + // Are we in a staging room? + //////////////////////////// + assert(connection->inRoom[StagingRoom]); + if(!connection->inRoom[StagingRoom]) + return PEERFalse; + + // Enum through all the room's players. + /////////////////////////////////////// + allReady = PEERTrue; + piEnumRoomPlayers(peer, StagingRoom, piAreAllReadyEnumRoomPlayersCallback, &allReady); + + return allReady; +} + +void peerStartGameA +( + PEER peer, + const char * message, + int reportingOptions +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return; + + // Check that we're in a staging room. + ////////////////////////////////////// + assert(connection->inRoom[StagingRoom]); + if(!connection->inRoom[StagingRoom]) + return; + + // Make sure we're the host. + //////////////////////////// + assert(connection->hosting); + if(!connection->hosting) + return; + + // Change NULL messages to empty messages. + ////////////////////////////////////////// + if(!message) + message = ""; + + // Send the launch UTM. + /////////////////////// + piSendChannelUTM(peer, connection->rooms[StagingRoom], PI_UTM_LAUNCH, message, PEERFalse); + +#if 1 + // Send an old-style launch command. + // THIS IS ONLY NEEDED FOR BACKWARDS COMPATIBILITY AND SHOULD BE REMOVED AT SOME POINT IN THE FUTURE. + //////////////////////////////////////////////////////////////////////////////////////////////////// + { + char buffer[32]; + IN_ADDR addr; + addr.s_addr = connection->publicIP; + sprintf(buffer, "@@@GML %s/OLD", inet_ntoa(addr)); + peerMessageRoomA(peer, StagingRoom, buffer, NormalMessage); + } +#endif + + // We're playing. + ///////////////// + connection->playing = PEERTrue; + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); + + // If we're AutoMatching, we're now done. + ///////////////////////////////////////// + if(peerIsAutoMatching(peer)) + { + piSetAutoMatchStatus(peer, PEERComplete); + } + else if(connection->queryReporting) + { + // Check if we should stop GOA reporting. + ///////////////////////////////////////// + if(reportingOptions & PEER_STOP_REPORTING) + { + // Stop. + //////// + piStopReporting(peer); + } + else + { + // Set the options. + /////////////////// + connection->reportingOptions = reportingOptions; + + // Send a state-changed. + //////////////////////// + piSendStateChanged(peer); + } + } +} +#ifdef GSI_UNICODE +void peerStartGameW +( + PEER peer, + const unsigned short * message, + int reportingOptions +) +{ + char* message_A = UCS2ToUTF8StringAlloc(message); + peerStartGameA(peer, message_A, reportingOptions); + gsifree(message_A); +} +#endif + +PEERBool peerStartReporting +( + PEER peer +) +{ + return peerStartReportingWithSocket(peer, INVALID_SOCKET, 0); +} + +PEERBool peerStartReportingWithSocket +( + PEER peer, + SOCKET socket, + unsigned short port +) +{ + PEER_CONNECTION; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return PEERFalse; + + // Start. + ///////// + if(!piStartReporting(peer, socket, port)) + return PEERFalse; + + return PEERTrue; +} + +void peerStartPlaying +( + PEER peer +) +{ + PEER_CONNECTION; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Mark us as playing. + ////////////////////// + connection->playing = PEERTrue; + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); +} + +PEERBool peerIsPlaying +( + PEER peer +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return PEERFalse; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return PEERFalse; + + return connection->playing; +} + +void peerStopGame +( + PEER peer +) +{ + PEER_CONNECTION; + + // We're done playing. + ////////////////////// + connection->playing = PEERFalse; + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); + + // Are we reporting? + //////////////////// + if(connection->queryReporting) + { + // Are we still in the staging room? + //////////////////////////////////// + if(connection->inRoom[StagingRoom]) + piSendStateChanged(peer); + else + piStopReporting(peer); + } +} + +void peerStateChanged +( + PEER peer +) +{ + PEER_CONNECTION; + + // We should be reporting. + ////////////////////////// + assert(connection->queryReporting); + + // Send a state-changed. + //////////////////////// + piSendStateChanged(peer); +} + +void piSendChannelUTM +( + PEER peer, + const char * channel, + const char * command, + const char * parameters, + PEERBool authenticate +) +{ + char buffer[512]; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(channel && channel[0]); + assert(command && command[0]); + assert(parameters); + + // Check for connection succeeded. + ////////////////////////////////// + if(!connection->connected) + return; + + // Check for no channel. + //////////////////////// + if(!channel || !channel[0]) + return; + + // Check for no command. + //////////////////////// + if(!command || !command[0]) + return; + + // Check for no parameters. + /////////////////////////// + if(!parameters) + parameters = ""; + + // Make sure this UTM isn't too long. + ///////////////////////////////////// + if((strlen(command) + strlen(parameters) + 5) > sizeof(buffer)) + return; + + // Form the message. + //////////////////// + sprintf(buffer, "%s %s", command, parameters); + + // Send it. + /////////// + chatSendChannelMessageA(connection->chat, channel, buffer, authenticate?CHAT_ATM:CHAT_UTM); +} + +void piSendPlayerUTM +( + PEER peer, + const char * nick, + const char * command, + const char * parameters, + PEERBool authenticate +) +{ + char buffer[512]; + + PEER_CONNECTION; + PEER_CONNECTED; + + assert(nick && nick[0]); + assert(command && command[0]); + assert(parameters); + + // Check for connection succeeded. + ////////////////////////////////// + if(!connection->connected) + return; + + // Check for no nick. + ///////////////////// + if(!nick || !nick[0]) + return; + + // Check for no command. + //////////////////////// + if(!command || !command[0]) + return; + + // Check for no parameters. + /////////////////////////// + if(!parameters) + parameters = ""; + + // Make sure this UTM isn't too long. + ///////////////////////////////////// + if((strlen(command) + strlen(parameters) + 5) > sizeof(buffer)) + return; + + // Form the message. + //////////////////// + sprintf(buffer, "%s %s", command, parameters); + + // Send it. + /////////// + chatSendUserMessageA(connection->chat, nick, buffer, authenticate?CHAT_ATM:CHAT_UTM); +} + +/********* +** KEYS ** +*********/ +void peerSetGlobalKeysA +( + PEER peer, + int num, + const char ** keys, + const char ** values +) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + assert(keys); + assert(values); + assert(num > 0); + + // Check for connection succeeded. + ////////////////////////////////// + if(!connection->connected) + return; + + // Set the keys. + //////////////// + chatSetGlobalKeysA(connection->chat, num, keys, values); +} +#ifdef GSI_UNICODE +void peerSetGlobalKeysW +( + PEER peer, + int num, + const unsigned short ** keys, + const unsigned short ** values +) +{ + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + char** values_A = UCS2ToUTF8StringArrayAlloc(values, num); + int i; + peerSetGlobalKeysA(peer, num, (const char**)keys_A, (const char**)values_A); + for (i=0; iconnected) + return; + + if(!nick || !nick[0]) + nick = connection->nick; + + assert(callback); + + // Start the operation. + /////////////////////// + if(!piNewGetGlobalKeysOperation(peer, nick, num, keys, callback, param, opID)) + success = PEERFalse; + + // Check for failure. + ///////////////////// + if(!success) + piAddGetGlobalKeysCallback(peer, PEERFalse, nick, 0, NULL, NULL, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerGetPlayerGlobalKeysW +( + PEER peer, + const unsigned short * nick, + int num, + const unsigned short ** keys, + peerGetGlobalKeysCallback callback, + void * param, + PEERBool blocking +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + int i; + peerGetPlayerGlobalKeysA(peer, nick_A, num, (const char**)keys_A, callback, param, blocking); + gsifree(nick_A); + for (i=0; iconnected) + return; + + ASSERT_ROOMTYPE(roomType); + assert(callback); + + // Check that we're in or entering this room. + ///////////////////////////////////////////// + if(!ENTERING_ROOM && !IN_ROOM) + return; + + // Start the operation. + /////////////////////// + if(!piNewGetGlobalKeysOperation(peer, ROOM, num, keys, callback, param, opID)) + success = PEERFalse; + + // Check for failure. + ///////////////////// + if(!success) + piAddGetGlobalKeysCallback(peer, PEERFalse, "", 0, NULL, NULL, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerGetRoomGlobalKeysW +( + PEER peer, + RoomType roomType, + int num, + const unsigned short ** keys, + peerGetGlobalKeysCallback callback, + void * param, + PEERBool blocking +) +{ + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + int i; + peerGetRoomGlobalKeysA(peer, roomType, num, (const char**)keys_A, callback, param, blocking); + for (i=0; i 0); + ASSERT_ROOMTYPE(roomType); + + // Check for connection succeeded. + ////////////////////////////////// + if(!connection->connected) + return; + + // Check that we're in or entering this room. + ///////////////////////////////////////////// + if(!ENTERING_ROOM && !IN_ROOM) + return; + + // Set the keys. + //////////////// + chatSetChannelKeysA(connection->chat, ROOM, nick, num, keys, values); +} +#ifdef GSI_UNICODE +void peerSetRoomKeysW +( + PEER peer, + RoomType roomType, + const unsigned short * nick, + int num, + const unsigned short ** keys, + const unsigned short ** values +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + char** values_A = UCS2ToUTF8StringArrayAlloc(values, num); + int i; + peerSetRoomKeysA(peer, roomType, nick_A, num, (const char**)keys_A, (const char**)values_A); + gsifree(nick_A); + for (i=0; iconnected) + return; + + ASSERT_ROOMTYPE(roomType); + assert(callback); + + // Check that we're in or entering this room. + ///////////////////////////////////////////// + if(!ENTERING_ROOM && !IN_ROOM) + return; + + // Start the operation. + /////////////////////// + if(!piNewGetRoomKeysOperation(peer, roomType, nick, num, keys, callback, param, opID)) + success = PEERFalse; + + // Check for failure. + ///////////////////// + if(!success) + piAddGetRoomKeysCallback(peer, PEERFalse, roomType, nick, 0, NULL, NULL, callback, param, opID); + + PI_DO_BLOCKING; +} +#ifdef GSI_UNICODE +void peerGetRoomKeysW +( + PEER peer, + RoomType roomType, + const unsigned short * nick, + int num, + const unsigned short ** keys, + peerGetRoomKeysCallback callback, + void * param, + PEERBool blocking +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + int i; + peerGetRoomKeysA(peer, roomType, nick_A, num, (const char**)keys_A, callback, param, blocking); + gsifree(nick_A); + for (i=0; i= 2); + + // Check the params. + //////////////////// + if(!filter) + filter = ""; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + goto failed; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + goto failed; + + // Check for an AutoMatch in progress. + ////////////////////////////////////// + assert(!peerIsAutoMatching(peer)); + if(peerIsAutoMatching(peer)) + goto failed; + + // If entering a staging room, leave. + ///////////////////////////////////// + if(connection->enteringRoom[StagingRoom]) + piLeaveRoom(peer, StagingRoom, ""); + + // Stop any reporting. + ////////////////////// + piStopReporting(peer); + + // Stop any game listing. + ///////////////////////// + piSBStopListingGames(peer); + + // Store some parameters. + ///////////////////////// + connection->maxPlayers = maxPlayers; + connection->autoMatchFilter = goastrdup(filter); + if(!connection->autoMatchFilter) + goto failed; + + // Initialize the AutoMatch status. + /////////////////////////////////// + connection->autoMatchStatus = PEERFailed; + + // Clear the SB and QR failed flags. + //////////////////////////////////// + connection->autoMatchSBFailed = PEERFalse; + connection->autoMatchQRFailed = PEERFalse; + + // Start the AutoMatch. + /////////////////////// + if(!piNewAutoMatchOperation(peer, socket, port, statusCallback, rateCallback, param, opID)) + { + gsifree(connection->autoMatchFilter); + goto failed; + } + + PI_DO_BLOCKING; + + return; + +failed: + // Failed to start the attempt. + /////////////////////////////// + connection->autoMatchStatus = PEERFailed; + piAddAutoMatchStatusCallback(peer); +} +#ifdef GSI_UNICODE +void peerStartAutoMatchWithSocketW +( + PEER peer, + int maxPlayers, + const unsigned short * filter, + SOCKET socket, + unsigned short port, + peerAutoMatchStatusCallback statusCallback, + peerAutoMatchRateCallback rateCallback, + void * param, + PEERBool blocking +) +{ + char* filter_A = UCS2ToUTF8StringAlloc(filter); + peerStartAutoMatchWithSocketA(peer, maxPlayers, filter_A, socket, port, statusCallback, rateCallback, param, blocking); + gsifree(filter_A); +} +#endif + +void peerStopAutoMatch(PEER peer) +{ + PEER_CONNECTION; + PEER_CONNECTED; + + // Check for a title. + ///////////////////// + if(!connection->title[0]) + return; + + // Check for a connection. + ////////////////////////// + if(!connection->connected) + return; + + // Stop the AutoMatch. + ////////////////////// + piStopAutoMatch(peer); +} + +PEERBool peerIsAutoMatching(PEER peer) +{ + PEER_CONNECTION; + + // If the status is Failed or Done, then we're not matching. + //////////////////////////////////////////////////////////// + if(connection->autoMatchStatus == PEERFailed) + return PEERFalse; + if(connection->autoMatchStatus == PEERComplete) + return PEERFalse; + + return PEERTrue; +} + +PEERAutoMatchStatus peerGetAutoMatchStatus(PEER peer) +{ + PEER_CONNECTION; + + return connection->autoMatchStatus; +} + +void peerSetStagingRoomMaxPlayers(PEER peer, int maxPlayers) +{ + PEER_CONNECTION; + GS_ASSERT(connection->inRoom[StagingRoom]); + if (connection->inRoom[StagingRoom]) + { + // Let QR2 report the new max players, and set the new chat channel limit + connection->maxPlayers = maxPlayers; + piSendStateChanged(peer); + + chatSetChannelLimitA(connection->chat, connection->rooms[StagingRoom], maxPlayers); + } +} diff --git a/code/gamespy/Peer/peerMain.h b/code/gamespy/Peer/peerMain.h new file mode 100644 index 00000000..5b459f81 --- /dev/null +++ b/code/gamespy/Peer/peerMain.h @@ -0,0 +1,223 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERMAIN_H_ +#define _PEERMAIN_H_ + +/************* +** INCLUDES ** +*************/ +#include "peer.h" +#include "../darray.h" +#include "../hashtable.h" +#include "../pinger/pinger.h" +#include "../Chat/chatASCII.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#if 0 +piConnection * connection; // This is to fool Visual Assist. +#endif + +/************ +** DEFINES ** +************/ +#define PI_ROOM_MAX_LEN 257 +#define PI_NICK_MAX_LEN 64 +#define PI_NAME_MAX_LEN 512 +#define PI_TITLE_MAX_LEN 32 +#define PI_AWAY_MAX_LEN 128 +#define PI_SB_LEN 32 // from sb_internal.h + +#ifndef PI_CHAT_PING_TIME + #ifndef _NITRO + #define PI_CHAT_PING_TIME (5 * 60000) + #else + #define PI_CHAT_PING_TIME (20000) + #endif +#endif + +#define PEER_CONNECTION piConnection * connection;\ + assert(peer);\ + connection = (piConnection *)peer;\ + GSI_UNUSED(connection); + +#define ASSERT_ROOMTYPE(type) assert((type == TitleRoom) || (type == GroupRoom) || (type == StagingRoom)) +#define ASSERT_MESSAGETYPE(type) assert((type == NormalMessage) || (type == ActionMessage) || (type == NoticeMessage)) + +#define ROOM (connection->rooms[roomType]) +#define ROOM_W (connection->rooms_W[roomType]) +#define ROOMS (connection->rooms) +#define NAME (connection->names[roomType]) +#define NAME_W (connection->names_W[roomType]) +#define NAMES (connection->names) +#define IN_ROOM (connection->inRoom[roomType]) +#define ENTERING_ROOM (connection->enteringRoom[roomType]) + +#define strzcpy(dest, src, len) { strncpy(dest, src, (len)); (dest)[(len) - 1] = '\0'; } +#define strzcat(dest, src, len) { strncat(dest, src, (len) - strlen(dest)); (dest)[(len) - 1] = '\0'; } + +#if defined(_PS3) +#define PEERCBType void* // Note: ANSI function pointers should int rather than void* +#else +#define PEERCBType int // Note: ANSI function pointers should int rather than void* +#endif + +/********** +** TYPES ** +**********/ +typedef struct piConnection +{ + // Chat. + //////// + CHAT chat; // The chat connection. + char nick[PI_NICK_MAX_LEN]; // The local nick. + PEERBool connecting; + PEERBool connected; + peerNickErrorCallback nickErrorCallback; + gsi_time lastChatPing; + + // Game. + //////// + unsigned int publicIP; + unsigned int privateIP; + int profileID; + char title[PI_TITLE_MAX_LEN]; + +#ifdef GSI_UNICODE + unsigned short title_W[PI_TITLE_MAX_LEN]; + unsigned short nick_W[PI_NICK_MAX_LEN]; + unsigned short names_W[NumRooms][PI_NAME_MAX_LEN]; + unsigned short rooms_W[NumRooms][PI_ROOM_MAX_LEN]; +#endif + + // Rooms. + ///////// + char rooms[NumRooms][PI_ROOM_MAX_LEN]; + PEERBool enteringRoom[NumRooms]; + PEERBool inRoom[NumRooms]; + char names[NumRooms][PI_NAME_MAX_LEN]; + int oldFlags[NumRooms]; + int groupID; + char titleRoomChannel[PI_ROOM_MAX_LEN]; + PEERBool stayInTitleRoom; + + // Players. + /////////// + HashTable players; + int numPlayers[NumRooms]; + PEERBool alwaysRequestPlayerInfo; + + // Ping. + //////// + PEERBool doPings; + int lastPingTimeMod; + PEERBool pingRoom[NumRooms]; + PEERBool xpingRoom[NumRooms]; + HashTable xpings; + unsigned int lastXpingSend; + + // Reporting. + ///////////// + qr2_t queryReporting; + char qrSecretKey[64]; // i ripped the length from qr2.c + PEERBool natNegotiate; + int reportingOptions; + int reportingGroupID; // might be different than groupID if left group room after started reporting + + // Hosting. + /////////// + PEERBool hosting; + PEERBool playing; + int maxPlayers; + PEERBool passwordedRoom; + + // Staging room. + //////////////// + SBServer hostServer; + PEERBool ready; + + // SB. + ////// + char sbName[PI_SB_LEN]; + char sbSecretKey[PI_SB_LEN]; + int sbGameVersion; + int sbMaxUpdates; + PEERBool sbInitialized; + SBServerList gameList; + SBServerList groupList; + SBQueryEngine gameEngine; + peerListingGamesCallback gameListCallback; + void * gameListParam; + PEERBool initialGameList; + struct piOperation * listingGroupsOperation; + + // ID. + ////// + int nextID; + + // Operations. + ////////////// + DArray operationList; + int operationsStarted; + int operationsFinished; + + // Callbacks. + ///////////// + PEERCallbacks callbacks; + DArray callbackList; + int callbacksQueued; + int callbacksCalled; + int callbackDepth; + + // Away. + //////// + PEERBool away; + char awayReason[PI_AWAY_MAX_LEN]; + + // Keys. + //////// + HashTable globalWatchKeys[NumRooms]; + HashTable roomWatchKeys[NumRooms]; + HashTable globalWatchCache; + HashTable roomWatchCache[NumRooms]; + + // AutoMatch. + ///////////// + PEERAutoMatchStatus autoMatchStatus; + SBServerList autoMatchList; + SBQueryEngine autoMatchEngine; + PEERBool autoMatchBrowsing; + struct piOperation * autoMatchOperation; + qr2_t autoMatchReporting; + char * autoMatchFilter; + PEERBool autoMatchSBFailed; + PEERBool autoMatchQRFailed; + + // Misc. + //////// + PEERBool disconnect; + PEERBool shutdown; +} piConnection; + +void piSendChannelUTM(PEER peer, const char * channel, const char * command, const char * parameters, PEERBool authenticate); +void piSendPlayerUTM(PEER peer, const char * nick, const char * command, const char * parameters, PEERBool authenticate); +PEERBool piConnectTitle(PEER peer); +void piDisconnectTitle(PEER peer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerMangle.c b/code/gamespy/Peer/peerMangle.c new file mode 100644 index 00000000..dbe02426 --- /dev/null +++ b/code/gamespy/Peer/peerMangle.c @@ -0,0 +1,292 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/* +** +** Title Room +** #GSP! +** +** Group Room +** #GPG! +** +** Staging Room +** #GSP!!XX +** +** User +** XX| +** +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include "peerMain.h" +#include "peerMangle.h" + +/************ +** DEFINES ** +************/ +#define PI_SEPERATOR "!" + +/************ +** GLOBALS ** +************/ +PEERBool piOldMangleStagingRooms; +static const char digits_hex[] = "0123456789abcdef"; +static const char digits_crypt[] = "aFl4uOD9sfWq1vGp"; +static const char new_digits_crypt[] = "qJ1h4N9cP3lzD0Ka"; +static const unsigned int ip_xormask = 0xc3801dc7; +static char cryptbuffer[32]; + +/************** +** FUNCTIONS ** +**************/ +//originally ripped from Aphex's ipencode.h +static const char * EncodeIP(unsigned int ip, char * buffer, PEERBool newCrypt) +{ + const char * crypt = newCrypt?new_digits_crypt:digits_crypt; + int i; + char * str; + int digit_idx; + + // XOR the IP address. + ip ^= ip_xormask; + + // Print out the ip addr in hex form. + sprintf(cryptbuffer, "%08x", ip); + + // Translate chars in positions 0 through 7 from hex digits to "crypt" digits. + for(i = 0 ; i < 8 ; i++) + { + str = strchr(digits_hex, cryptbuffer[i]); + digit_idx = (str - digits_hex); + + if((digit_idx < 0) || (digit_idx > 15)) // sanity check + { + strcpy(cryptbuffer, "14saFv19"); // equivalent to 0.0.0.0 + break; + } + + cryptbuffer[i] = crypt[digit_idx]; + } + + if(buffer) + { + strcpy(buffer, cryptbuffer); + return buffer; + } + + return cryptbuffer; +} + +//originally ripped from Aphex's ipencode.h +static unsigned int DecodeIP(const char * buffer, PEERBool newCrypt) +{ + const char * crypt = newCrypt?new_digits_crypt:digits_crypt; + unsigned int ip; + char * str; + int digit_idx; + int i; + + if(!buffer) + return 0; + + // Translate chars from hex digits to "crypt" digits. + for(i = 0 ; i < 8 ; i++) + { + str = strchr(crypt, buffer[i]); + digit_idx = (str - crypt); + + if((digit_idx < 0) || (digit_idx > 15)) + return 0; + + cryptbuffer[i] = digits_hex[digit_idx]; + } + + // Cap the buffer. + cryptbuffer[i] = '\0'; + + // Convert the string to an unsigned long (the XORd ip addr). + sscanf(cryptbuffer, "%x", &ip); + + // re-XOR the IP address. + ip ^= ip_xormask; + + return ip; +} + +static const char * piStagingRoomHash(unsigned int publicIP, unsigned int privateIP, unsigned short port, char * buffer) +{ + unsigned int result; + + publicIP = ntohl(publicIP); + privateIP = ntohl(privateIP); + + result = (((privateIP >> 24) & 0xFF) | ((privateIP >> 8) & 0xFF00) | ((privateIP << 8) & 0xFF0000) | ((privateIP << 24) & 0xFF000000)); + result ^= publicIP; + result ^= (port | (port << 16)); + + return EncodeIP(result, buffer, PEERTrue); +} + +void piMangleTitleRoom +( + char buffer[PI_ROOM_MAX_LEN], + const char * title +) +{ + assert(buffer); + assert(title); + assert(title[0]); + + sprintf(buffer, "#GSP" PI_SEPERATOR "%s", + title); +} + +void piMangleGroupRoom +( + char buffer[PI_ROOM_MAX_LEN], + int groupID +) +{ + assert(buffer); + assert(groupID); + + sprintf(buffer, "#GPG" PI_SEPERATOR "%d", groupID); +} + +void piMangleStagingRoom +( + char buffer[PI_ROOM_MAX_LEN], + const char * title, + unsigned int publicIP, + unsigned int privateIP, + unsigned short privatePort +) +{ + char encodeBuffer[9]; + int borderChar; + + assert(buffer); + assert(title); + assert(title[0]); + + if(piOldMangleStagingRooms) + { + EncodeIP(publicIP, encodeBuffer, PEERFalse); + borderChar = 'X'; + } + else + { + piStagingRoomHash(publicIP, privateIP, privatePort, encodeBuffer); + borderChar = 'M'; + } + + sprintf(buffer, "#GSP" PI_SEPERATOR "%s" PI_SEPERATOR "%c%s%c", title, borderChar, encodeBuffer, borderChar); +} + +void piMangleUser +( + char buffer[PI_USER_MAX_LEN], + unsigned int IP, + int profileID +) +{ + assert(buffer); + assert(IP != 0); + assert(profileID >= 0); + + sprintf(buffer, "X%sX|%d", + EncodeIP(IP, NULL, PEERFalse), + profileID); +} + +PEERBool piDemangleUser +( + const char buffer[PI_USER_MAX_LEN], + unsigned int * IP, + int * profileID +) +{ + unsigned int decodedIP; + int scannedProfileID; + + assert(buffer); + if(buffer == NULL) + return PEERFalse; + + // Check the length. + //////////////////// + if(strlen(buffer) < 12) + return PEERFalse; + + // Check for the Xs. + //////////////////// + if((buffer[0] != 'X') && (buffer[9] != 'X')) + return PEERFalse; + + // Get the IP. + ////////////// + decodedIP = DecodeIP(buffer + 1, PEERFalse); + if(!decodedIP) + return PEERFalse; + + // Check the profile ID. + //////////////////////// + if(!isdigit(buffer[11])) + return PEERFalse; + + // Get the pid. + /////////////// + scannedProfileID = atoi(buffer + 11); + + // Check what is wanted. + //////////////////////// + if(IP) + *IP = decodedIP; + if(profileID) + *profileID = scannedProfileID; + + + return PEERTrue; +} + +void piMangleIP +( + char buffer[11], + unsigned int IP +) +{ + assert(buffer); + assert(IP != 0); + + EncodeIP(IP, buffer + 1, PEERFalse); + buffer[0] = 'X'; + buffer[9] = 'X'; + buffer[10] = '\0'; +} + +unsigned int piDemangleIP +( + const char buffer[11] +) +{ + assert(buffer); + if(!buffer) + return 0; + + // Check for the Xs. + //////////////////// + if((buffer[0] != 'X') && (buffer[9] != 'X')) + return 0; + + return DecodeIP(buffer + 1, PEERFalse); +} diff --git a/code/gamespy/Peer/peerMangle.h b/code/gamespy/Peer/peerMangle.h new file mode 100644 index 00000000..4d826b60 --- /dev/null +++ b/code/gamespy/Peer/peerMangle.h @@ -0,0 +1,46 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERMANGLE_H_ +#define _PEERMANGLE_H_ + +/************* +** INCLUDES ** +*************/ +#include "peer.h" +#include "peerMain.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/************ +** DEFINES ** +************/ +#define PI_USER_MAX_LEN 128 + +/************** +** FUNCTIONS ** +**************/ +void piMangleTitleRoom(char buffer[PI_ROOM_MAX_LEN], const char * title); +void piMangleGroupRoom(char buffer[PI_ROOM_MAX_LEN], int groupID); +void piMangleStagingRoom(char buffer[PI_ROOM_MAX_LEN], const char * title, + unsigned int publicIP, unsigned int privateIP, unsigned short privatePort); +void piMangleUser(char buffer[PI_USER_MAX_LEN], unsigned int IP, int profileID); +PEERBool piDemangleUser(const char buffer[PI_USER_MAX_LEN], unsigned int * IP, int * profileID); +void piMangleIP(char buffer[11], unsigned int IP); +unsigned int piDemangleIP(const char buffer[11]); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerOperations.c b/code/gamespy/Peer/peerOperations.c new file mode 100644 index 00000000..975e09d6 --- /dev/null +++ b/code/gamespy/Peer/peerOperations.c @@ -0,0 +1,1844 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include +#include +#include +#include "peerOperations.h" +#include "peerCallbacks.h" +#include "peerGlobalCallbacks.h" +#include "peerMangle.h" +#include "peerRooms.h" +#include "peerPlayers.h" +#include "peerKeys.h" +#include "peerSB.h" +#include "peerHost.h" +#include "peerAutoMatch.h" +#include "peerQR.h" +#include "../md5.h" + +/************ +** DEFINES ** +************/ +#define PI_CHAT_SERVER_ADDRESS "peerchat." GSI_DOMAIN_NAME +#define PI_CHAT_SERVER_PORT 6667 + +#define PEER_CONNECTION_OP piOperation * operation = (piOperation *)param;\ + piConnection * connection = NULL;\ + PEER peer = NULL;\ + assert(operation && operation->peer);\ + if(operation && operation->peer)\ + peer = operation->peer;\ + connection = (piConnection *)peer;\ + GSI_UNUSED(connection); +#if 0 +// for Visual Assist +PEER peer; +piOperation * operation; +#endif + +/********** +** TYPES ** +**********/ +typedef struct piOperationContainer +{ + piOperation * operation; +} piOperationContainer; + +/************** +** FUNCTIONS ** +**************/ +#ifndef GSI_UNICODE +#define piGetGlobalKeysCallback piGetGlobalKeysCallbackA +#define piGetChannelKeysCallback piGetChannelKeysCallbackA +#define piGetPlayerInfoCallback piGetPlayerInfoCallbackA +#define piConnectNickErrorCallback piConnectNickErrorCallbackA +#define piConnectFillInUserCallback piConnectFillInUserCallbackA +#define piCreateStagingRoomEnumUsersCallback piCreateStagingRoomEnumUsersCallbackA +#define piCreateStagingRoomEnterChannelCallback piCreateStagingRoomEnterChannelCallbackA +#define piJoinRoomEnumUsersCallback piJoinRoomEnumUsersCallbackA +#define piJoinRoomEnterChannelCallback piJoinRoomEnterChannelCallbackA +#define piChangeNickCallback piChangeNickCallbackA +#define piAuthenticateCDKeyCallback piAuthenticateCDKeyCallbackA +#else +#define piGetGlobalKeysCallback piGetGlobalKeysCallbackW +#define piGetChannelKeysCallback piGetChannelKeysCallbackW +#define piGetPlayerInfoCallback piGetPlayerInfoCallbackW +#define piConnectNickErrorCallback piConnectNickErrorCallbackW +#define piConnectFillInUserCallback piConnectFillInUserCallbackW +#define piCreateStagingRoomEnumUsersCallback piCreateStagingRoomEnumUsersCallbackW +#define piCreateStagingRoomEnterChannelCallback piCreateStagingRoomEnterChannelCallbackW +#define piJoinRoomEnumUsersCallback piJoinRoomEnumUsersCallbackW +#define piJoinRoomEnterChannelCallback piJoinRoomEnterChannelCallbackW +#define piChangeNickCallback piChangeNickCallbackW +#define piAuthenticateCDKeyCallback piAuthenticateCDKeyCallbackW +#endif + +static void piOperationsListFree(void *elem1) +{ + piOperationContainer * container = (piOperationContainer *)elem1; + piOperation * operation; + + assert(container); + assert(container->operation); + + operation = container->operation; + + // Free the name. + ///////////////// + gsifree(operation->name); + + // Free the password. + ///////////////////// + gsifree(operation->password); + + // Free the user data. + ////////////////////// + gsifree(operation->data); + + // Close the socket if needed. + ////////////////////////////// + if(operation->socketClose) + { + closesocket(operation->socket); + SocketShutDown(); + } + + // Free the operation. + ////////////////////// + gsifree(operation); +} + +PEERBool piOperationsInit(PEER peer) +{ + PEER_CONNECTION; + + assert(!connection->operationList); + + // Init data. + ///////////// + connection->operationsStarted = 0; + connection->operationsFinished = 0; + + // Init the list. + ///////////////// + connection->operationList = ArrayNew(sizeof(piOperationContainer), 0, piOperationsListFree); + if(!connection->operationList) + return PEERFalse; + + return PEERTrue; +} + +void piOperationsReset(PEER peer) +{ + PEER_CONNECTION; + + // Clear the list. + ////////////////// + if(connection->operationList) + ArrayClear(connection->operationList); + + // Clear the listingGroupsOperation + if (connection->listingGroupsOperation) + connection->listingGroupsOperation = NULL; +} + +void piOperationsCleanup(PEER peer) +{ + PEER_CONNECTION; + + // Cleanup the list. + //////////////////// + if(connection->operationList) + ArrayFree(connection->operationList); + connection->operationList = NULL; +} + +void piRemoveOperation(PEER peer, piOperation * operation) +{ + piOperationContainer * container; + piOperation * op; + int i; + int count; + + PEER_CONNECTION; + + // Make sure there is a list. + ///////////////////////////// + if(!connection->operationList) + return; + + // Loop through the operations. + /////////////////////////////// + count = ArrayLength(connection->operationList); + for(i = 0 ; i < count ; i++) + { + // Get the operation. + ///////////////////// + container = (piOperationContainer *)ArrayNth(connection->operationList, i); + op = container->operation; + + // Check the op. + //////////////// + if(op == operation) + { + // Remove it. + ///////////// + ArrayDeleteAt(connection->operationList, i); + + // One more finished. + ///////////////////// + connection->operationsFinished++; + + return; + } + } +} + +PEERBool piIsOperationFinished(PEER peer, int opID) +{ + piOperationContainer * container; + piOperation * op; + int i; + int count; + + PEER_CONNECTION; + + // Make sure there is a list. + ///////////////////////////// + if(!connection->operationList) + return PEERTrue; + + // Loop through the operations. + /////////////////////////////// + count = ArrayLength(connection->operationList); + for(i = 0 ; i < count ; i++) + { + // Get the operation. + ///////////////////// + container = (piOperationContainer *)ArrayNth(connection->operationList, i); + op = container->operation; + + // Check the ID. + //////////////// + if(op->ID == opID) + return PEERFalse; + } + + return PEERTrue; +} + +void piCancelJoinOperation(PEER peer, RoomType roomType) +{ + piOperationContainer * container; + piOperation * operation; + int i; + int count; + + PEER_CONNECTION; + + // Make sure there is a list. + ///////////////////////////// + if(!connection->operationList) + return; + + // Loop through the operations. + /////////////////////////////// + count = ArrayLength(connection->operationList); + for(i = 0 ; i < count ; i++) + { + // Get the operation. + ///////////////////// + container = (piOperationContainer *)ArrayNth(connection->operationList, i); + operation = container->operation; + + // Is it a join or create? + ////////////////////////// + if((operation->type == PI_JOIN_ROOM_OPERATION) || (operation->type == PI_CREATE_ROOM_OPERATION)) + { + // Is it the same room? + /////////////////////// + if(operation->roomType == roomType) + { + // Cancel the operation. + //////////////////////// + operation->cancel = PEERTrue; + + return; + } + } + } +} + +int piGetNextID(PEER peer) +{ + int ID; + + PEER_CONNECTION; + + // Get the ID. + ////////////// + ID = connection->nextID; + + // Increment. + ///////////// + connection->nextID++; + if(connection->nextID < 0) + connection->nextID = 0; + + return ID; +} + +static piOperation * piAddOperation +( + PEER peer, + piOperationType type, + void * data, + PEERCBType callback, + void * callbackParam, + int opID +) +{ + piOperation * operation; + piOperationContainer container; + + PEER_CONNECTION; + + //assert(type >= 0); + assert(type < PI_NUM_OPERATION_TYPES); + + // Make sure there is a list. + ///////////////////////////// + if(!connection->operationList) + return NULL; + + // Alloc the operaiton. + /////////////////////// + operation = (piOperation *)gsimalloc(sizeof(piOperation)); + if(!operation) + return NULL; + + // Fill in the operation. + ///////////////////////// + memset(operation, 0, sizeof(piOperation)); + operation->peer = peer; + operation->type = type; + operation->data = data; + operation->ID = opID; + operation->callback = callback; + operation->callbackParam = callbackParam; + operation->name = NULL; + operation->cancel = PEERFalse; + + // Add the operation to the list. + ///////////////////////////////// + container.operation = operation; + ArrayAppend(connection->operationList, &container); + + // One more op. + /////////////// + connection->operationsStarted++; + + return operation; +} + +/*************** +** OPERATIONS ** +***************/ + +/* Connect. +**********/ +static void piConnectConnectCallback +( + CHAT chat, + CHATBool success, + int failureReason, + void *param +) +{ + PEER_CONNECTION_OP; + + // If successful, try and do the connect title stuff. + ///////////////////////////////////////////////////// + if(success) + { + if(!piConnectTitle(peer)) + { + piDisconnectTitle(peer); + success = CHATFalse; + } + } + + // Connection attempt finished. + /////////////////////////////// + connection->connecting = PEERFalse; + connection->connected = (PEERBool)success; + + if(success) + { + const char * nick; + + // Setup server pinging. + //////////////////////// + connection->lastChatPing = current_time(); + + // Check the nick. + ////////////////// + nick = chatGetNickA(chat); + if(strcasecmp(connection->nick, nick) != 0) + { + strcpy(connection->nick, nick); + +#ifdef GSI_UNICODE + UTF8ToUCS2String(connection->nick, connection->nick_W); +#endif + } + } + else + { + // Set the disconnect flag. + /////////////////////////// + connection->disconnect = PEERTrue; + } + + // Add the callback. + //////////////////// + piAddConnectCallback(peer, (PEERBool)success, failureReason, (peerConnectCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + piRemoveOperation(peer, operation); +} + +static void piConnectNickErrorCallbackA +( + CHAT chat, + int type, + const char * nick, + int numSuggestedNicks, + const char ** suggestedNicks, + void *param +) +{ + PEER_CONNECTION_OP; + + // Add the peer callback. + ///////////////////////// + piAddNickErrorCallback(peer, type, nick, numSuggestedNicks, suggestedNicks, operation->callbackParam, operation->ID); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piConnectNickErrorCallbackW +( + CHAT chat, + int type, + const unsigned short * nick, + int numSuggestedNicks, + const unsigned short ** suggestedNicks, + void *param +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char** suggestedNicks_A = UCS2ToUTF8StringArrayAlloc(suggestedNicks, numSuggestedNicks); + int i; + piConnectNickErrorCallbackA(chat, type, nick_A, numSuggestedNicks, (const char**)suggestedNicks_A, param); + gsifree(nick_A); + if(suggestedNicks_A) + { + for (i=0; ipublicIP = IP; + + // If chat has a pid, override ours. + //////////////////////////////////// + pid = chatGetProfileID(connection->chat); + if(pid) + connection->profileID = pid; + + // Mangle the IP and pid into the user. + /////////////////////////////////////// + piMangleUser(user, connection->publicIP, connection->profileID); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piConnectFillInUserCallbackW +( + CHAT chat, + unsigned int IP, + unsigned short user[128], // OUT parameter!! + void * param +) +{ + // This one is reversed! Call the callback, THEN convert + char user_A[256]; + piConnectFillInUserCallbackA(chat, IP, user_A, param); + UTF8ToUCS2String(user_A, user); +} +#endif + +PEERBool piNewConnectOperation +( + PEER peer, + piConnectType connectType, + const char * nick, + int namespaceID, + const char * email, + const char * profilenick, + const char * uniquenick, + const char * password, + const char * authtoken, + const char * partnerchallenge, + peerConnectCallback callback, + void * callbackParam, + int opID +) +{ + piOperation * operation; + chatGlobalCallbacks globalCallbacks; + const char * uniqueID; + char encodedUniqueID[33]; + peerNickErrorCallback nickErrorCallback; + + PEER_CONNECTION; + + assert(callback); + +#ifdef _DEBUG + if(connectType == PI_CONNECT) + { + assert(nick && nick[0]); + } + else if(connectType == PI_CONNECT_UNIQUENICK_LOGIN) + { + assert(namespaceID > 0); + assert(uniquenick && uniquenick[0]); + assert(password && password[0]); + } + else if(connectType == PI_CONNECT_PROFILENICK_LOGIN) + { + assert(namespaceID >= 0); + assert(email && email[0]); + assert(profilenick && profilenick[0]); + assert(password && password[0]); + } + else if(connectType == PI_CONNECT_PREAUTH) + { + assert(authtoken && authtoken[0]); + assert(partnerchallenge && partnerchallenge[0]); + } +#endif + + // Add an operation. + //////////////////// + operation = piAddOperation(peer, PI_CONNECT_OPERATION, NULL, (PEERCBType)callback, callbackParam, opID); + if(!operation) + return PEERFalse; + + // Setup the global callbacks. + ////////////////////////////// + memset(&globalCallbacks, 0, sizeof(chatGlobalCallbacks)); + globalCallbacks.disconnected = piChatDisconnected; + globalCallbacks.privateMessage = piChatPrivateMessage; + globalCallbacks.param = peer; + + // Encode the unique ID. + //////////////////////// + uniqueID = GOAGetUniqueID(); + MD5Digest((unsigned char *)uniqueID, strlen(uniqueID), encodedUniqueID); + + // Connect to chat. + /////////////////// + nickErrorCallback = (connection->nickErrorCallback ? piConnectNickErrorCallback : NULL); + if(connectType == PI_CONNECT) + { + connection->chat = chatConnectSecureA( + PI_CHAT_SERVER_ADDRESS, + PI_CHAT_SERVER_PORT, + nick, + encodedUniqueID, + connection->sbName, + connection->sbSecretKey, + &globalCallbacks, + nickErrorCallback, + piConnectFillInUserCallback, + piConnectConnectCallback, + operation, + CHATFalse); + } + else if((connectType == PI_CONNECT_UNIQUENICK_LOGIN) || (connectType == PI_CONNECT_PROFILENICK_LOGIN)) + { + connection->chat = chatConnectLoginA( + PI_CHAT_SERVER_ADDRESS, + PI_CHAT_SERVER_PORT, + namespaceID, + email, + profilenick, + uniquenick, + password, + encodedUniqueID, + connection->sbName, + connection->sbSecretKey, + &globalCallbacks, + nickErrorCallback, + piConnectFillInUserCallback, + piConnectConnectCallback, + operation, + CHATFalse); + } + else if(connectType == PI_CONNECT_PREAUTH) + { + connection->chat = chatConnectPreAuthA( + PI_CHAT_SERVER_ADDRESS, + PI_CHAT_SERVER_PORT, + authtoken, + partnerchallenge, + encodedUniqueID, + connection->sbName, + connection->sbSecretKey, + &globalCallbacks, + nickErrorCallback, + piConnectFillInUserCallback, + piConnectConnectCallback, + operation, + CHATFalse); + } + if(!connection->chat) + { + piRemoveOperation(peer, operation); + return PEERFalse; + } + + return PEERTrue; +} + +/* Create Staging Room. +**********************/ +static PEERJoinResult piEnterResultToJoinResult +( + CHATEnterResult result +) +{ + switch(result) + { + case CHATEnterSuccess: + return PEERJoinSuccess; + case CHATChannelIsFull: + return PEERFullRoom; + case CHATInviteOnlyChannel: + return PEERInviteOnlyRoom; + case CHATBannedFromChannel: + return PEERBannedFromRoom; + case CHATBadChannelPassword: + return PEERBadPassword; + default: + break; + } + + return PEERJoinFailed; +} + +static void piCreateStagingRoomEnumUsersCallbackA +( + CHAT chat, + CHATBool success, + const char * channel, + int numUsers, + const char ** users, + int * modes, + void *param +) +{ + PEER_CONNECTION_OP; + + // Check if this was cancelled. + /////////////////////////////// + if(operation->cancel) + { + piRemoveOperation(peer, operation); + return; + } + + // Start hosting/reporting. + /////////////////////////// + if(success) + { + if(!peerIsAutoMatching(peer)) + { + if(!piStartHosting(peer, operation->socket, operation->port)) + success = CHATFalse; + else + { + // If we created the socket, hand over responsibility for it to qr2. + //////////////////////////////////////////////////////////////////// + if(operation->socketClose) + { + operation->socketClose = PEERFalse; + connection->queryReporting->read_socket = 1; + } + } + } + else + { + connection->hosting = PEERTrue; + } + } + + // Do stuff based on success. + ///////////////////////////// + if(success) + { + int i; + + // Done entering. + ///////////////// + piFinishedEnteringRoom(peer, StagingRoom, operation->name); + + // Add everyone to the room. + //////////////////////////// + for(i = 0 ; i < numUsers ; i++) + piPlayerJoinedRoom(peer, users[i], StagingRoom, modes[i]); + + // Set the name. + //////////////// + chatSetChannelTopicA(connection->chat, channel, operation->name); + + // Set a limit on the room. + /////////////////////////// +#ifndef PI_NO_STAGING_ROOM_LIMIT + if(connection->maxPlayers) + chatSetChannelLimitA(connection->chat, channel, connection->maxPlayers); +#endif + + // If this is AutoMatch, and we created a socket, hand it over. + /////////////////////////////////////////////////////////////// + if(operation->socketClose && peerIsAutoMatching(peer)) + { + connection->autoMatchOperation->socket = operation->socket; + connection->autoMatchOperation->port = operation->port; + connection->autoMatchOperation->socketClose = PEERTrue; + operation->socketClose = PEERFalse; + } + } + else + { + // Leave the room. + ////////////////// + piLeaveRoom(peer, StagingRoom, NULL); + } + + // Add the callback. + //////////////////// + piAddJoinRoomCallback(peer, (PEERBool)success, success?PEERJoinSuccess:PEERJoinFailed, StagingRoom, (peerJoinRoomCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + piRemoveOperation(peer, operation); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piCreateStagingRoomEnumUsersCallbackW +( + CHAT chat, + CHATBool success, + const unsigned short * channel, + int numUsers, + const unsigned short ** users, + int * modes, + void *param +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char** users_A = UCS2ToUTF8StringArrayAlloc(users, numUsers); + int i; + piCreateStagingRoomEnumUsersCallbackA(chat, success, channel_A, numUsers, (const char**)users_A, modes, param); + gsifree(channel_A); + for (i=0; iLimit = connection->maxPlayers; + mode->OpsObeyChannelLimit = CHATTrue; + + // Don't let ops bypass channel limit on the number of players in room + chatSetChannelMode(chat, channel, mode); + } + +} + + +static void piCreateStagingRoomEnterChannelCallbackA +( + CHAT chat, + CHATBool success, + CHATEnterResult result, + const char * channel, + void *param +) +{ + PEER_CONNECTION_OP; + + assert(channel); + assert(channel[0]); + + // Check if this was cancelled. + /////////////////////////////// + if(operation->cancel) + { + piRemoveOperation(peer, operation); + return; + } + + if(success) + { + // If passworded, set it. + ///////////////////////// + if(operation->password) + chatSetChannelPasswordA(connection->chat, channel, CHATTrue, operation->password); + + + // get the channel modes so we can set the limits on our staging channel + chatGetChannelMode(connection->chat, peerGetRoomChannel(peer, StagingRoom), piCreateStagingRoomGetChannelModeCallback, + (void *)connection, CHATFalse); + + + // How many users? + ////////////////// + chatEnumUsersA(chat, channel, piCreateStagingRoomEnumUsersCallback, operation, CHATFalse); + } + else + { + PEERJoinResult joinResult; + + // Not entering. + //////////////// + piLeaveRoom(peer, StagingRoom, NULL); + + // Get the peer result. + /////////////////////// + if(result == CHATEnterSuccess) + joinResult = PEERJoinFailed; + else + joinResult = piEnterResultToJoinResult(result); + + // Add the callback. + //////////////////// + piAddJoinRoomCallback(peer, PEERFalse, joinResult, StagingRoom, (peerJoinRoomCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + piRemoveOperation(peer, operation); + } +} +#ifdef GSI_UNICODE +static void piCreateStagingRoomEnterChannelCallbackW +( + CHAT chat, + CHATBool success, + CHATEnterResult result, + const unsigned short * channel, + void *param +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + piCreateStagingRoomEnterChannelCallbackA(chat, success, result, channel_A, param); + gsifree(channel_A); +} +#endif + +// internal QR2 function +qr2_error_t qr2_create_socket(/*[out]*/SOCKET *sock, const char *ip, /*[in/out]*/int * port); + +PEERBool piNewCreateStagingRoomOperation +( + PEER peer, + const char * name, + const char * password, + int maxPlayers, + SOCKET socket, + unsigned short port, + peerJoinRoomCallback callback, + void * callbackParam, + int opID +) +{ + piOperation * operation; + chatChannelCallbacks channelCallbacks; + char room[PI_ROOM_MAX_LEN]; + PEERBool createdSocket = PEERFalse; + + PEER_CONNECTION; + + assert(name); + if(!name) + name = ""; + + assert(callback); + if(!callback) + return PEERFalse; + + // Save off the maxplayers. + /////////////////////////// + connection->maxPlayers = maxPlayers; + + // If we don't have a socket, create one to use. + //////////////////////////////////////////////// + if(socket == INVALID_SOCKET) + { + IN_ADDR addr; + qr2_error_t rcode; + int privatePort; + + addr.s_addr = 0;//crt -- don't bind to privateIP (for clients that have both public and private) connection->privateIP; + if(port) + privatePort = port; + else + privatePort = PI_QUERYPORT; + + rcode = qr2_create_socket(&socket, inet_ntoa(addr), &privatePort); + if(rcode != e_qrnoerror) + return PEERFalse; + + port = (unsigned short)privatePort; + createdSocket = PEERTrue; + } + + // Get the room name. + ///////////////////// + piMangleStagingRoom(room, connection->title, connection->publicIP, connection->privateIP, port); + assert(room[0]); + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_CREATE_ROOM_OPERATION, NULL, (PEERCBType)callback, callbackParam, opID); + if(!operation) + return PEERFalse; + operation->socketClose = createdSocket; + operation->name = goastrdup(name); + if(!operation->name) + { + piRemoveOperation(peer, operation); + return PEERFalse; + } + operation->socket = socket; + operation->port = port; + operation->roomType = StagingRoom; + if(password[0]) + operation->password = goastrdup(password); + + // Set the callbacks. + ///////////////////// + piSetChannelCallbacks(peer, &channelCallbacks); + + // Create the room. + /////////////////// + piStartedEnteringRoom(peer, StagingRoom, room); + chatEnterChannelA(connection->chat, room, NULL, &channelCallbacks, piCreateStagingRoomEnterChannelCallback, operation, CHATFalse); + + // Store passworded flag if needed. + /////////////////////////////////// + if(password[0]) + connection->passwordedRoom = PEERTrue; + + return PEERTrue; +} + +/* Join Room. +************/ +static void piJoinRoomEnumUsersCallbackA +( + CHAT chat, + CHATBool success, + const char * channel, + int numUsers, + const char ** users, + int * modes, + void * param +) +{ + PEER_CONNECTION_OP; + + // Check if this was cancelled. + /////////////////////////////// + if(operation->cancel) + { + piRemoveOperation(peer, operation); + return; + } + + // Check for success. + ///////////////////// + if(success) + { + int i; + + // Finished entering the room. + ////////////////////////////// + piFinishedEnteringRoom(peer, operation->roomType, ""); + + // Add all these people to the room. + //////////////////////////////////// + for(i = 0 ; i < numUsers ; i++) + piPlayerJoinedRoom(peer, users[i], operation->roomType, modes[i]); + } + else + { + // Leave the room. + ////////////////// + piLeaveRoom(peer, operation->roomType, NULL); + } + + // Add the callback. + //////////////////// + piAddJoinRoomCallback(peer, (PEERBool)success, success?PEERJoinSuccess:PEERJoinFailed, operation->roomType, (peerJoinRoomCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + piRemoveOperation(peer, operation); + + GSI_UNUSED(chat); + GSI_UNUSED(channel); +} +#ifdef GSI_UNICODE +static void piJoinRoomEnumUsersCallbackW +( + CHAT chat, + CHATBool success, + const unsigned short * channel, + int numUsers, + const unsigned short ** users, + int * modes, + void * param +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char** users_A = UCS2ToUTF8StringArrayAlloc(users, numUsers); + int i; + piJoinRoomEnumUsersCallbackA(chat, success, channel_A, numUsers, (const char**)users_A, modes, param); + gsifree(channel_A); + for (i=0; icancel) + { + piSBFreeHostServer(peer); + piRemoveOperation(peer, operation); + return; + } + + if(success) + { + // How many users? + ////////////////// + chatEnumUsersA(chat, channel, piJoinRoomEnumUsersCallback, operation, CHATFalse); + } + else + { + PEERJoinResult joinResult; + + // Not entering. + //////////////// + piLeaveRoom(peer, operation->roomType, NULL); + + // Get the peer result. + /////////////////////// + if(result == CHATEnterSuccess) + joinResult = PEERJoinFailed; + else + joinResult = piEnterResultToJoinResult(result); + + // Add the callback. + //////////////////// + piAddJoinRoomCallback(peer, PEERFalse, joinResult, operation->roomType, (peerJoinRoomCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + piRemoveOperation(peer, operation); + } +} +#ifdef GSI_UNICODE +static void piJoinRoomEnterChannelCallbackW +( + CHAT chat, + CHATBool success, + CHATEnterResult result, + const unsigned short * channel, + void *param +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + piJoinRoomEnterChannelCallbackA(chat, success, result, channel_A, param); + gsifree(channel_A); +} +#endif + +PEERBool piNewJoinRoomOperation +( + PEER peer, + RoomType roomType, + const char * channel, + const char * password, + peerJoinRoomCallback callback, + void * callbackParam, + int opID +) +{ + piOperation * operation; + chatChannelCallbacks channelCallbacks; + + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + assert(callback); + + // Check the name. + ////////////////// + assert(channel); + assert(channel[0]); + if(!channel || !channel[0]) + return PEERFalse; + + // Check password. + ////////////////// + if(!password) + password = ""; + + // Check that the name isn't too long. + ////////////////////////////////////// + assert(strlen(channel) < PI_ROOM_MAX_LEN); + if(strlen(channel) >= PI_ROOM_MAX_LEN) + return PEERFalse; + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_JOIN_ROOM_OPERATION, NULL, (PEERCBType)callback, callbackParam, opID); + if(!operation) + return PEERFalse; + operation->roomType = roomType; + + // Set the callbacks. + ///////////////////// + piSetChannelCallbacks(peer, &channelCallbacks); + + // Join the room. + ///////////////// + piStartedEnteringRoom(peer, roomType, channel); + chatEnterChannelA(connection->chat, channel, password, &channelCallbacks, piJoinRoomEnterChannelCallback, operation, CHATFalse); + + return PEERTrue; +} + +/* List Group Rooms. +*******************/ +PEERBool piNewListGroupRoomsOperation +( + PEER peer, + const char * fields, + peerListGroupRoomsCallback callback, + void * param, + int opID +) +{ + piOperation * operation; + + PEER_CONNECTION; + + assert(callback); + + // Add the operation. + ///////////////////// + operation = connection->listingGroupsOperation = piAddOperation(peer, PI_LIST_GROUP_ROOMS_OPERATION, NULL, (PEERCBType)callback, param, opID); + if(!operation) + return PEERFalse; + + // Start the listing. + ///////////////////// + return piSBStartListingGroups(peer, fields); +} + +/* Get Player Info. +*******************/ +static void piGetPlayerInfoCallbackA +( + CHAT chat, + CHATBool success, + const char * nick, + const char * user, + const char * address, + void * param +) +{ + int profileID = 0; + unsigned int IP = 0; + + PEER_CONNECTION_OP; + + assert(nick); + assert(nick[0]); + + // Check for success. + ///////////////////// + if(success) + { + assert(user); + assert(user[0]); + + // Get the info. + //////////////// + if(!piDemangleUser(user, &IP, &profileID)) + success = CHATFalse; + + // Cache the info. + ////////////////// + if(success) + piSetPlayerIPAndProfileID(peer, nick, IP, profileID); + } + if(!success) + { + profileID = 0; + IP = 0; + } + + // Add the callback. + //////////////////// + if(operation->callback) + { + if(operation->type == PI_GET_PLAYER_INFO_OPERATION) + piAddGetPlayerInfoCallback(peer, (PEERBool)success, nick, IP, profileID, (peerGetPlayerInfoCallback)operation->callback, operation->callbackParam, operation->ID); + else if(operation->type == PI_GET_PROFILE_ID_OPERATION) + piAddGetPlayerProfileIDCallback(peer, (PEERBool)success, nick, profileID, (peerGetPlayerProfileIDCallback)operation->callback, operation->callbackParam, operation->ID); + else if(operation->type == PI_GET_IP_OPERATION) + piAddGetPlayerIPCallback(peer, (PEERBool)success, nick, IP, (peerGetPlayerIPCallback)operation->callback, operation->callbackParam, operation->ID); + else + assert(0); + } + + // Remove the operation. + //////////////////////// + piRemoveOperation(peer, operation); + + GSI_UNUSED(chat); + GSI_UNUSED(address); +} +#ifdef GSI_UNICODE +static void piGetPlayerInfoCallbackW +( + CHAT chat, + CHATBool success, + const unsigned short * nick, + const unsigned short * user, + const unsigned short * address, + void * param +) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + char* user_A = UCS2ToUTF8StringAlloc(user); + char* address_A = UCS2ToUTF8StringAlloc(address); + piGetPlayerInfoCallbackA(chat, success, nick_A, user_A, address_A, param); + gsifree(nick_A); + gsifree(user_A); + gsifree(address_A); +} +#endif + +PEERBool piNewGetPlayerInfoOperation +( + PEER peer, + const char * nick, + peerGetPlayerInfoCallback callback, + void * param, + int opID +) +{ + piOperation * operation; + + PEER_CONNECTION; + + assert(nick); + assert(nick[0]); + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_GET_PLAYER_INFO_OPERATION, NULL, (PEERCBType)callback, param, opID); + if(!operation) + return PEERFalse; + + // Get the user's info. + /////////////////////// + chatGetBasicUserInfoA(connection->chat, nick, piGetPlayerInfoCallback, operation, CHATFalse); + + return PEERTrue; +} + +PEERBool piNewGetProfileIDOperation +( + PEER peer, + const char * nick, + peerGetPlayerProfileIDCallback callback, + void * param, + int opID +) +{ + piOperation * operation; + + PEER_CONNECTION; + + assert(nick); + assert(nick[0]); + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_GET_PROFILE_ID_OPERATION, NULL, (PEERCBType)callback, param, opID); + if(!operation) + return PEERFalse; + + // Get the user's info. + /////////////////////// + chatGetBasicUserInfoA(connection->chat, nick, piGetPlayerInfoCallback, operation, CHATFalse); + + return PEERTrue; +} + +PEERBool piNewGetIPOperation +( + PEER peer, + const char * nick, + peerGetPlayerIPCallback callback, + void * param, + int opID +) +{ + piOperation * operation; + + PEER_CONNECTION; + + assert(nick); + assert(nick[0]); + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_GET_IP_OPERATION, NULL, (PEERCBType)callback, param, opID); + if(!operation) + return PEERFalse; + + // Get the user's info. + /////////////////////// + chatGetBasicUserInfoA(connection->chat, nick, piGetPlayerInfoCallback, operation, CHATFalse); + + return PEERTrue; +} + +/* Change Nick. +**************/ +static void piChangeNickCallbackA +( + CHAT chat, + CHATBool success, + const char * oldNick, + const char * newNick, + void * param +) +{ + PEER_CONNECTION_OP; + + // Check for success. + ///////////////////// + if(success) + { + // Update the nick locally. + /////////////////////////// + strcpy(connection->nick, newNick); + +#ifdef GSI_UNICODE + UTF8ToUCS2String(connection->nick, connection->nick_W); +#endif + } + + // Add the callback. + //////////////////// + if(operation->callback) + piAddChangeNickCallback(peer, (PEERBool)success, oldNick, newNick, (peerChangeNickCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + piRemoveOperation(peer, operation); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piChangeNickCallbackW +( + CHAT chat, + CHATBool success, + const unsigned short * oldNick, + const unsigned short * newNick, + void * param +) +{ + char* oldNick_A = UCS2ToUTF8StringAlloc(oldNick); + char* newNick_A = UCS2ToUTF8StringAlloc(newNick); + piChangeNickCallbackA(chat, success, oldNick_A, newNick_A, param); + gsifree(oldNick_A); + gsifree(newNick_A); +} +#endif + +PEERBool piNewChangeNickOperation +( + PEER peer, + const char * newNick, + peerChangeNickCallback callback, + void * param, + int opID +) +{ + piOperation * operation; + + PEER_CONNECTION; + + assert(newNick); + assert(newNick[0]); + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_CHANGE_NICK_OPERATION, NULL, (PEERCBType)callback, param, opID); + if(!operation) + return PEERFalse; + + // Change the nick. + /////////////////// + chatChangeNickA(connection->chat, newNick, piChangeNickCallback, operation, CHATFalse); + + return PEERTrue; +} + +/* Get Global Keys. +******************/ +static void piGetGlobalKeysCallbackA +( + CHAT chat, + CHATBool success, + const char * user, + int num, + const char ** keys, + const char ** values, + void * param +) +{ + int i; + + PEER_CONNECTION_OP; + + // Update the watch keys. + ///////////////////////// + if(success && user) + { + for(i = 0 ; i < num ; i++) + piGlobalKeyChanged(peer, user, keys[i], values[i]); + } + + // Add the callback. + //////////////////// + if(operation->callback) + piAddGetGlobalKeysCallback(peer, (PEERBool)success, user, num, keys, values, (peerGetGlobalKeysCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + if(!success || !user || !operation->num) + piRemoveOperation(peer, operation); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piGetGlobalKeysCallbackW +( + CHAT chat, + CHATBool success, + const unsigned short * user, + int num, + const unsigned short ** keys, + const unsigned short ** values, + void * param +) +{ + char* user_A = UCS2ToUTF8StringAlloc(user); + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + char** values_A = UCS2ToUTF8StringArrayAlloc(values, num); + int i; + piGetGlobalKeysCallbackA(chat, success, user_A, num, (const char**)keys_A, (const char**)values_A, param); + gsifree(user_A); + for (i=0; i 0); + assert(keys); + + if(!target || !target[0]) + return PEERFalse; + if(num <= 0) + return PEERFalse; + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_GET_GLOBAL_KEYS_OPERATION, NULL, (PEERCBType)callback, param, opID); + if(!operation) + return PEERFalse; + + // Use num as a flag for getting a whole channel. + ///////////////////////////////////////////////// + operation->num = (target[0] == '#'); + + // Get the keys. + //////////////// + chatGetGlobalKeysA(connection->chat, target, num, keys, piGetGlobalKeysCallback, operation, CHATFalse); + + return PEERTrue; +} + +/* Get Room Keys. +****************/ +static void piGetChannelKeysCallbackA +( + CHAT chat, + CHATBool success, + const char * channel, + const char * user, + int num, + const char ** keys, + const char ** values, + void * param +) +{ + int i; + + PEER_CONNECTION_OP; + + // Update the watch keys. + ///////////////////////// + if(success && user) + { + for(i = 0 ; i < num ; i++) + piRoomKeyChanged(peer, operation->roomType, user, keys[i], values[i]); + } + + // Add the callback. + //////////////////// + if(operation->callback) + piAddGetRoomKeysCallback(peer, (PEERBool)success, operation->roomType, user, num, keys, values, (peerGetRoomKeysCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + if(!success || !user || !operation->num) + piRemoveOperation(peer, operation); + + GSI_UNUSED(chat); + GSI_UNUSED(channel); +} +#ifdef GSI_UNICODE +static void piGetChannelKeysCallbackW +( + CHAT chat, + CHATBool success, + const unsigned short * channel, + const unsigned short * user, + int num, + const unsigned short ** keys, + const unsigned short ** values, + void * param +) +{ + char* channel_A = UCS2ToUTF8StringAlloc(channel); + char* user_A = UCS2ToUTF8StringAlloc(user); + char** keys_A = UCS2ToUTF8StringArrayAlloc(keys, num); + char** values_A = UCS2ToUTF8StringArrayAlloc(values, num); + int i; + piGetChannelKeysCallbackA(chat, success, channel_A, user_A, num, (const char**)keys_A, (const char**)values_A, param); + gsifree(channel_A); + gsifree(user_A); + for (i=0; i= 0); + assert(!num || keys); + + if(num < 0) + return PEERFalse; + if((num > 0) && !keys) + return PEERFalse; + + if(!ENTERING_ROOM && !IN_ROOM) + return PEERFalse; + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_GET_ROOM_KEYS_OPERATION, NULL, (PEERCBType)callback, param, opID); + if(!operation) + return PEERFalse; + operation->roomType = roomType; + + // Use num as a flag for getting a whole channel. + ///////////////////////////////////////////////// + if(nick) + operation->num = (strcmp(nick, "*") == 0); + else + operation->num = 0; + + // Get the keys. + //////////////// + chatGetChannelKeysA(connection->chat, ROOM, nick, num, keys, piGetChannelKeysCallback, operation, CHATFalse); + + return PEERTrue; +} + +/* Authenticate CD Key +*********************/ +static void piAuthenticateCDKeyCallbackA +( + CHAT chat, + int result, + const char * message, + void * param +) +{ + + PEER_CONNECTION_OP; + + // Add the callback. + //////////////////// + if(operation->callback) + piAddAuthenticateCDKeyCallback(peer, result, message, (peerAuthenticateCDKeyCallback)operation->callback, operation->callbackParam, operation->ID); + + // Remove the operation. + //////////////////////// + piRemoveOperation(peer, operation); + + GSI_UNUSED(chat); +} +#ifdef GSI_UNICODE +static void piAuthenticateCDKeyCallbackW +( + CHAT chat, + int result, + const unsigned short * message, + void * param +) +{ + char* message_A = UCS2ToUTF8StringAlloc(message); + piAuthenticateCDKeyCallbackA(chat, result, message_A, param); + gsifree(message_A); +} +#endif + +PEERBool piNewAuthenticateCDKeyOperation +( + PEER peer, + const char * cdkey, + peerAuthenticateCDKeyCallback callback, + void * param, + int opID +) +{ + piOperation * operation; + + PEER_CONNECTION; + + assert(cdkey); + assert(cdkey[0]); + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_AUTHENTICATE_CDKEY_OPERATION, NULL, (PEERCBType)callback, param, opID); + if(!operation) + return PEERFalse; + + // Check the CD key. + //////////////////// + chatAuthenticateCDKeyA(connection->chat, cdkey, piAuthenticateCDKeyCallback, operation, CHATFalse); + + return PEERTrue; +} + +/* AutoMatch +***********/ +PEERBool piNewAutoMatchOperation +( + PEER peer, + SOCKET socket, + unsigned short port, + peerAutoMatchStatusCallback statusCallback, + peerAutoMatchRateCallback rateCallback, + void * param, + int opID +) +{ + piOperation * operation; + PEERAutoMatchStatus status; + + PEER_CONNECTION; + + // Add the operation. + ///////////////////// + operation = piAddOperation(peer, PI_AUTO_MATCH_OPERATION, NULL, (PEERCBType)statusCallback, param, opID); + if(!operation) + return PEERFalse; + + // Store the second callback. + ///////////////////////////// + operation->callback2 = (PEERCBType)rateCallback; + + // Store the socket and port. + ///////////////////////////// + operation->socket = socket; + operation->port = port; + + // Track this op. + ///////////////// + connection->autoMatchOperation = operation; + + // Figure out which status to use. + ////////////////////////////////// +/* if(connection->inRoom[StagingRoom]) + { + if(connection->numPlayers[StagingRoom] <= 1) + { + if(connection->hosting) + status = PEERWaiting; + else + status = PEERStaging; + } + else if(connection->numPlayers[StagingRoom] >= connection->maxPlayers) + { + status = PEERReady; + } + else + { + status = PEERStaging; + } + } + else*/ + { + status = PEERSearching; + } + + // Set the status. + ////////////////// + piSetAutoMatchStatus(peer, status); + + return PEERTrue; +} diff --git a/code/gamespy/Peer/peerOperations.h b/code/gamespy/Peer/peerOperations.h new file mode 100644 index 00000000..b0675ccd --- /dev/null +++ b/code/gamespy/Peer/peerOperations.h @@ -0,0 +1,221 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEEROPERATIONS_H_ +#define _PEEROPERATIONS_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/********** +** TYPES ** +**********/ +typedef enum piOperationType +{ + PI_CONNECT_OPERATION, + PI_CREATE_ROOM_OPERATION, + PI_JOIN_ROOM_OPERATION, + PI_ENUM_PLAYERS_OPERATION, + PI_LIST_GROUP_ROOMS_OPERATION, + PI_LIST_STAGING_ROOMS_OPERATION, + PI_GET_PLAYER_INFO_OPERATION, + PI_GET_PROFILE_ID_OPERATION, + PI_GET_IP_OPERATION, + PI_CHANGE_NICK_OPERATION, + PI_GET_GLOBAL_KEYS_OPERATION, + PI_GET_ROOM_KEYS_OPERATION, + PI_AUTHENTICATE_CDKEY_OPERATION, + PI_AUTO_MATCH_OPERATION, + PI_NUM_OPERATION_TYPES +} piOperationType; + +typedef enum piConnectType +{ + PI_CONNECT, + PI_CONNECT_UNIQUENICK_LOGIN, + PI_CONNECT_PROFILENICK_LOGIN, + PI_CONNECT_PREAUTH +} piConnectType; + +typedef struct piOperation +{ + PEER peer; // the peer object + piOperationType type; // PI__OPERATION + void * data; // operation-specific data + int ID; // unique ID for this operation + PEERCBType callback; // the callback for this operation + PEERCBType callback2; // second callback if needed + void * callbackParam; // user-data for the callback + RoomType roomType; // lots of operations need this + char * name; // general purpose name + char * password; // general purpose password + int num; // general purpose integer + SOCKET socket; // general purpose socket + unsigned short port; // general purpose port + PEERBool socketClose; // close the socket when done + PEERBool cancel; // this op has been cancelled +} piOperation; + +/************** +** FUNCTIONS ** +**************/ +PEERBool piOperationsInit(PEER peer); +void piOperationsReset(PEER peer); +void piOperationsCleanup(PEER peer); +PEERBool piIsOperationFinished(PEER peer, int opID); +void piRemoveOperation(PEER peer, piOperation * operation); +void piCancelJoinOperation(PEER peer, RoomType roomType); +piOperation * piGetOperation(PEER peer, int opID); +int piGetNextID(PEER peer); + +/*************** +** OPERATIONS ** +***************/ +PEERBool piNewConnectOperation +( + PEER peer, + piConnectType connectType, + const char * nick, + int namespaceID, + const char * email, + const char * profilenick, + const char * uniquenick, + const char * password, + const char * authtoken, + const char * partnerchallenge, + peerConnectCallback callback, + void * callbackParam, + int opID +); + +PEERBool piNewCreateStagingRoomOperation +( + PEER peer, + const char * name, + const char * password, + int maxPlayers, + SOCKET socket, + unsigned short port, + peerJoinRoomCallback callback, + void * callbackParam, + int opID +); + +PEERBool piNewJoinRoomOperation +( + PEER peer, + RoomType roomType, + const char * channel, + const char * password, + peerJoinRoomCallback callback, + void * callbackParam, + int opID +); + +PEERBool piNewListGroupRoomsOperation +( + PEER peer, + const char * fields, + peerListGroupRoomsCallback callback, + void * param, + int opID +); + +PEERBool piNewGetPlayerInfoOperation +( + PEER peer, + const char * nick, + peerGetPlayerInfoCallback callback, + void * param, + int opID +); + +PEERBool piNewGetProfileIDOperation +( + PEER peer, + const char * nick, + peerGetPlayerProfileIDCallback callback, + void * param, + int opID +); + +PEERBool piNewGetIPOperation +( + PEER peer, + const char * nick, + peerGetPlayerIPCallback callback, + void * param, + int opID +); + +PEERBool piNewChangeNickOperation +( + PEER peer, + const char * newNick, + peerChangeNickCallback callback, + void * param, + int opID +); + +PEERBool piNewGetGlobalKeysOperation +( + PEER peer, + const char * target, + int num, + const char ** keys, + peerGetGlobalKeysCallback callback, + void * param, + int opID +); + +PEERBool piNewGetRoomKeysOperation +( + PEER peer, + RoomType roomType, + const char * nick, + int num, + const char ** keys, + peerGetRoomKeysCallback callback, + void * param, + int opID +); + +PEERBool piNewAuthenticateCDKeyOperation +( + PEER peer, + const char * cdkey, + peerAuthenticateCDKeyCallback callback, + void * param, + int opID +); + +PEERBool piNewAutoMatchOperation +( + PEER peer, + SOCKET socket, + unsigned short port, + peerAutoMatchStatusCallback statusCallback, + peerAutoMatchRateCallback rateCallback, + void * param, + int opID +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerPing.c b/code/gamespy/Peer/peerPing.c new file mode 100644 index 00000000..65330e79 --- /dev/null +++ b/code/gamespy/Peer/peerPing.c @@ -0,0 +1,1118 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include +#include "peerPlayers.h" +#include "peerPing.h" +#include "peerGlobalCallbacks.h" +#include "peerCallbacks.h" +#include "peerMangle.h" + +/************ +** DEFINES ** +************/ +#define PI_PING_TIMEOUT 5000 +#define PI_PINGS_PER_SEC 25 +#define PI_PING_INTERVAL (1000 / PI_PINGS_PER_SEC) +#define PI_XPING_INTERVAL 2000 +#define PI_XPING_PLAYER_INTERVAL 5000 +#define PI_PINGER_PORT 13139 +#define PI_MAX_PING_PLAYERS 12 +#define PI_XPING_NUM_BUCKETS 32 +#define PI_DONT_PING_FLAGS (PEER_FLAG_PLAYING | PEER_FLAG_AWAY) +#define PI_MAX_XPING_NUM_PLAYERS 32 + +#define PEER_CONNECTION_DATA piConnection * connection;\ + assert(data);\ + assert(data->peer);\ + connection = (piConnection *)data->peer;\ + GSI_UNUSED(connection); + + +/********** +** TYPES ** +**********/ +typedef struct piXping +{ + char nicks[2][PI_NICK_MAX_LEN]; + int ping; +} piXping; + +/************** +** FUNCTIONS ** +**************/ +static int piXpingTableHashFn +( + const void *param, + int numBuckets +) +{ + piXping * xping = (piXping *)param; + int i; + int c; + const char * str; + unsigned int hash = 0; + const char * nicks[2]; + + assert(xping); + assert(xping->nicks[0][0]); + assert(xping->nicks[1][0]); + + nicks[0] = xping->nicks[0]; + nicks[1] = xping->nicks[1]; + + // Reverse the order if the second is smaller. + ////////////////////////////////////////////// + if(strcmp(nicks[1], nicks[0]) < 0) + { + const char * temp = nicks[0]; + nicks[0] = nicks[1]; + nicks[1] = temp; + } + + // Get the hash. + //////////////// + for(i = 0 ; i < 2 ; i++) + { + str = nicks[i]; + while((c = *str++) != '\0') + hash += (unsigned int)tolower(c); + hash %= (unsigned int)numBuckets; + } + + return (int)hash; +} + +static int GS_STATIC_CALLBACK piXpingTableCompareFn +( + const void *param1, + const void *param2 +) +{ + piXping * xping1 = (piXping *)param1; + piXping * xping2 = (piXping *)param2; + + int i; + int rcode; + const char * nicks[2][2]; + + assert(xping1); + assert(xping1->nicks[0][0]); + assert(xping1->nicks[1][0]); + assert(xping2); + assert(xping2->nicks[0][0]); + assert(xping2->nicks[1][0]); + + nicks[0][0] = xping1->nicks[0]; + nicks[0][1] = xping1->nicks[1]; + nicks[1][0] = xping2->nicks[0]; + nicks[1][1] = xping2->nicks[1]; + + // Reverse the order if the second is smaller. + ////////////////////////////////////////////// + for(i = 0 ; i < 2 ; i++) + { + if(strcmp(nicks[i][1], nicks[i][0]) < 0) + { + const char * temp = nicks[i][0]; + nicks[i][0] = nicks[i][1]; + nicks[i][1] = temp; + } + } + + for(i = 0 ; i < 2 ; i++) + { + rcode = strcasecmp(nicks[0][i], nicks[1][i]); + if(rcode != 0) + return rcode; + } + + return 0; +} + +static void piXpingTableElementFreeFn +( + void *param +) +{ + piXping * xping = (piXping *)param; + assert(xping); + assert(xping->nicks[0][0]); + assert(xping->nicks[1][0]); + GSI_UNUSED(xping); +} + +static void piProcessPing +( + PEER peer, + piPlayer * player, + int ping +) +{ + int i; + int total; + + //PEER_CONNECTION; + + // One more returned. + ///////////////////// + player->pingsReturned++; + player->pingsLostConsecutive = 0; + + // We have one more ping for this player. + ///////////////////////////////////////// + player->numPings++; + + //Update last ping received. + //////////////////////////// + player->lastPingRecv = current_time(); + + // Update the ping history. + /////////////////////////// + if(player->pingHistoryNum > 0) + memmove(player->pingHistory + 1, player->pingHistory, min(player->pingHistoryNum, PI_PING_HISTORY_LEN - 1) * sizeof(int)); + player->pingHistory[0] = ping; + if(player->pingHistoryNum < PI_PING_HISTORY_LEN) + player->pingHistoryNum++; + + // Recompute the ping average. + ////////////////////////////// + total = 0; + for(i = 0 ; i < player->pingHistoryNum ; i++) + total += player->pingHistory[i]; + player->pingAverage = (total / player->pingHistoryNum); + + // Add a ping callback. + /////////////////////// + piAddPingCallback(peer, player->nick, ping); + + // New ping, no xping yet. + ////////////////////////// + player->xpingSent = PEERFalse; + + // If this is a one-timer, stop pinging. + //////////////////////////////////////// + if(player->pingOnce) + player->pingOnce = PEERFalse; +} + +static void piPinged +( + unsigned int IP, + unsigned short port, + int ping, + const char * data, + int len, + PEER peer +) +{ + piPlayer * player; + + // Find the player. + /////////////////// + player = piFindPlayerByIP(peer, IP); + if(!player) + return; + + // Process the ping. + //////////////////// + piProcessPing(peer, player, ping); + + GSI_UNUSED(port); + GSI_UNUSED(data); + GSI_UNUSED(len); +} + +PEERBool piPingInit +( + PEER peer +) +{ + static PEERBool noPings[NumRooms]; + + PEER_CONNECTION; + + // If there are no ping rooms, skip this. + ///////////////////////////////////////// + if(memcmp(connection->pingRoom, noPings, sizeof(noPings)) == 0) + return PEERTrue; + + // Init the xping table. + //////////////////////// + connection->xpings = TableNew(sizeof(piXping), PI_XPING_NUM_BUCKETS, piXpingTableHashFn, piXpingTableCompareFn, piXpingTableElementFreeFn); + if(!connection->xpings) + return PEERFalse; + + // Init pinger. + /////////////// + if(!pingerInit(NULL, PI_PINGER_PORT, piPinged, peer, NULL, NULL)) + return PEERFalse; + + // No pings yet. + //////////////// + connection->lastPingTimeMod = 0; + connection->lastXpingSend = 0; + + // Init the random number generator. + //////////////////////////////////// + srand(current_time()); + + // We're doing pings. + ///////////////////// + connection->doPings = PEERTrue; + + return PEERTrue; +} + +void piPingCleanup +( + PEER peer +) +{ + PEER_CONNECTION; + + // Nothing to do if we weren't pinging. + /////////////////////////////////////// + if(!connection->doPings) + return; + + // If we're staying in the title room, get out. + /////////////////////////////////////////////// + if(connection->stayInTitleRoom) + return; + + // Clear timing stuff. + ////////////////////// + connection->lastPingTimeMod = 0; + connection->lastXpingSend = 0; + + // gsifree the xping table. + //////////////////////// + if(connection->xpings) + TableFree(connection->xpings); + connection->xpings = NULL; + + // Cleanup pinger. + ////////////////// + pingerShutdown(); + + // Not doing pings. + /////////////////// + connection->doPings = PEERFalse; +} + +typedef struct piPingerReplyData +{ + PEER peer; + unsigned int IP; + int ping; +} piPingerReplyData; + +static void piPingerReplyMapFn +( + void *elem, + void *clientdata +) +{ + piPlayer * player = (piPlayer *)elem; + piPingerReplyData * data = (piPingerReplyData *)clientdata; + + PEER_CONNECTION_DATA; + + assert(player); + + // Check if waiting for a ping. + /////////////////////////////// + if(!player->waitingForPing) + return; + + // Check the IP. + //////////////// + if(!player->gotIPAndProfileID || (player->IP != data->IP)) + return; + + // Not waiting anymore. + /////////////////////// + player->waitingForPing = PEERFalse; + + // Check timeout. + ///////////////// + if(data->ping == PINGER_TIMEOUT) + { + // One more lost. + ///////////////// + player->pingsLostConsecutive++; + + // If a one-timer drops three in a row, give up. + //////////////////////////////////////////////// + if(player->pingOnce && (player->pingsLostConsecutive >= 3)) + { + player->pingsLostConsecutive = 0; + player->pingOnce = PEERFalse; + } + } + else + { + // Process the ping. + //////////////////// + piProcessPing(data->peer, player, data->ping); + } +} + +static void piPingerReply +( + unsigned int IP, + unsigned short port, + int ping, + const char * pingData, + int pingDataLen, + PEER peer +) +{ + piPingerReplyData data; + + PEER_CONNECTION; + + // Find who sent the ping. + ////////////////////////// + data.peer = peer; + data.IP = IP; + data.ping = ping; + TableMap(connection->players, piPingerReplyMapFn, &data); + + GSI_UNUSED(port); + GSI_UNUSED(pingData); + GSI_UNUSED(pingDataLen); + +} + +static void piPingPlayer +( + PEER peer, + piPlayer * player +) +{ + assert(player->gotIPAndProfileID); + + // Make sure we're not already waiting. + /////////////////////////////////////// + assert(!player->waitingForPing); + if(player->waitingForPing) + return; + + // Do the ping. + /////////////// + pingerPing(player->IP, PI_PINGER_PORT, piPingerReply, peer, PINGERFalse, PI_PING_TIMEOUT); + + // Waiting for a ping to return. + //////////////////////////////// + player->waitingForPing = PEERTrue; + + // Last ping send time. + /////////////////////// + player->lastPingSend = current_time(); + + // Clear must ping flag. + //////////////////////// + player->mustPing = PEERFalse; +} + +typedef struct piPickPingPlayersData +{ + PEER peer; + piPlayer ** players; + int max; + int num; +} piPickPingPlayersData; + +static void piPickPingPlayersMap +( + void * elem, + void * clientData +) +{ + piPlayer * player = (piPlayer *)elem; + piPickPingPlayersData * data = (piPickPingPlayersData *)clientData; + PEER peer = (PEER)data->peer; + piPlayer * other; + unsigned long now; + unsigned long delay; + int i; + int j; + + PEER_CONNECTION; + + // Not pinging? + /////////////// + if(!player->inPingRoom && !player->pingOnce) + return; + + // Don't have IP? + ///////////////// + if(!player->gotIPAndProfileID) + return; + + // Waiting for a ping? + ////////////////////// + if(player->waitingForPing) + return; + + // Don't ping the local player. + /////////////////////////////// + if(player->local) + return; + + // Don't ping someone who's playing or away. + //////////////////////////////////////////// + if((player->inRoom[TitleRoom] && (player->flags[TitleRoom] & PI_DONT_PING_FLAGS)) || + (player->inRoom[GroupRoom] && (player->flags[GroupRoom] & PI_DONT_PING_FLAGS)) || + (player->inRoom[StagingRoom] && (player->flags[StagingRoom] & PI_DONT_PING_FLAGS))) + return; + + // Check if we have to ping this player. + //////////////////////////////////////// + if(!player->mustPing) + { + // Get the current time. + //////////////////////// + now = current_time(); + + // Slow pings for no response. + ////////////////////////////// + if((player->pingsLostConsecutive >= 4) && ((now - player->lastPingSend) < 120000)) + return; + + // Pinged recently? + /////////////////// + if(player->inRoom[StagingRoom]) + delay = 2000; + else if(player->pingsReturned < 3) + delay = 5000; + else + delay = 30000; + if((now - player->lastPingSend) < delay) + return; + if((now - player->lastPingRecv) < (delay + 1500)) + return; + } + + // Go through all the spots. + //////////////////////////// + for(i = (data->max - 1) ; i >= 0 ; i--) + { + // Is it empty? + /////////////// + if(!data->players[i]) + continue; + + // The "other" player. + ////////////////////// + other = data->players[i]; + + // Is this player higher priority than us? + ////////////////////////////////////////// + if((!other->numPings && player->numPings) || // Not pinged. + (other->inRoom[StagingRoom] && !player->inRoom[StagingRoom]) || // In staging room. + (piIsPlayerVIP(other, StagingRoom) && !piIsPlayerVIP(player, StagingRoom)) || // Op or voice in the same staging room. + (strcasecmp(other->nick, player->nick) < 0)) // Alphabetical. + { + break; + } + } + + // Bump it up one, so it's set to our spot. + /////////////////////////////////////////// + i++; + + // Did we not find anything? + //////////////////////////// + if(i == data->max) + return; + + // Bump down the other player and all below. + //////////////////////////////////////////// + for(j = (data->max - 1) ; j > i ; j--) + data->players[j] = data->players[j - 1]; + + // Set this player. + /////////////////// + data->players[i] = player; + + // One more. + //////////// + if(data->num < data->max) + data->num++; +} + +// Returns an array of pointers to players to ping, +// or NULL if there's noone to ping. +// The number of players in the array will be no +// larger than min(PI_MAX_PING_PLAYERS, numPings). +/////////////////////////////////////////////////// +static piPlayer ** piPickPingPlayers +( + PEER peer, + int * numPings +) +{ + static piPlayer * players[PI_MAX_PING_PLAYERS]; + piPickPingPlayersData data; + PEER_CONNECTION; + + // Check for no players. + //////////////////////// + if(!connection->players || !(*numPings) || !TableCount(connection->players)) + { + *numPings = 0; + return NULL; + } + + // Setup a list of players to ping. + /////////////////////////////////// + data.peer = peer; + data.players = players; + data.max = min(PI_MAX_PING_PLAYERS, *numPings); + data.num = 0; + memset(players, 0, sizeof(piPlayer *) * data.max); + TableMap(connection->players, piPickPingPlayersMap, &data); + + // Set number of pings. + /////////////////////// + *numPings = data.num; + + // Check for noone to ping. + /////////////////////////// + if(!data.players[0]) + return NULL; + + return data.players; +} + +static void piXpingPlayer +( + PEER peer, + piPlayer * player +) +{ + int roomType; + char message[(PI_NICK_MAX_LEN * 2) + 32]; + char encodedIP[11]; + + PEER_CONNECTION; + + assert(player->inXpingRoom); + if(!player->inXpingRoom) + return; + + // Setup the message. + ///////////////////// + piMangleIP(encodedIP, player->IP); + sprintf(message, "%s %d", encodedIP, player->pingAverage); + + // Figure out which room to send the xping in. + ////////////////////////////////////////////// + for(roomType = 0 ; roomType < NumRooms ; roomType++) + { + if(player->inRoom[roomType] && connection->xpingRoom[roomType]) + { + if(connection->numPlayers[roomType] <= PI_MAX_XPING_NUM_PLAYERS) + { + assert(IN_ROOM || ENTERING_ROOM); + if(IN_ROOM || ENTERING_ROOM) + piSendChannelUTM(peer, ROOM, PI_UTM_XPING, message, PEERFalse); + } + } + } + + // Sent it. + /////////// + player->xpingSent = PEERTrue; + player->lastXping = current_time(); + connection->lastXpingSend = (unsigned int)player->lastXping; +} + +typedef struct piPickXpingPlayerData +{ + PEER peer; + piPlayer * player; +} piPickXpingPlayerData; + +static void piPickXpingPlayerMap +( + void * elem, + void * clientData +) +{ + piPlayer * player = (piPlayer *)elem; + piPickXpingPlayerData * data = (piPickXpingPlayerData *)clientData; + PEER peer = (PEER)data->peer; + unsigned long now; + + PEER_CONNECTION; + + // Not xpinging? + //////////////// + if(!player->inXpingRoom) + return; + + // Don't xping the local player. + //////////////////////////////// + if(player->local) + return; + + // Don't xping if no pings received. + /////////////////////////////////// + if(!player->numPings) + return; + + // Don't xping with the same info. + ////////////////////////////////// + if(player->xpingSent) + return; + + // Don't xping too often. + ///////////////////////// + now = current_time(); + if((now - player->lastXping) < PI_XPING_PLAYER_INTERVAL) + return; + + // First player? + //////////////// + if(!data->player) + { + data->player = player; + } + else if((now - player->lastXping) > (now - data->player->lastXping)) + { + data->player = player; + } +} + +// Returns a player to ping, or NULL if there's noone to ping. +////////////////////////////////////////////////////////////// +static piPlayer * piPickXpingPlayer +( + PEER peer +) +{ + piPickXpingPlayerData data; + PEER_CONNECTION; + + // Check for no players. + //////////////////////// + if(!connection->players || !TableCount(connection->players)) + return NULL; + + // Find someone near the top of the list. + ///////////////////////////////////////// + data.peer = peer; + data.player = NULL; + TableMap(connection->players, piPickXpingPlayerMap, &data); + + return data.player; +} + +void piPingThink +( + PEER peer +) +{ + unsigned long now; + int pingTimeMod; + int numPings; + piPlayer * player; + piPlayer ** players; + int i; + + PEER_CONNECTION; + + // Check if we're not doing pings. + ////////////////////////////////// + if(!connection->doPings) + return; + + // Don't do pings while playing! + //////////////////////////////// + if(connection->playing) + return; + + // Don't do pings while away. + ///////////////////////////// + if(connection->away) + return; + + // Get the current time. + //////////////////////// + now = current_time(); + + // Get the number of pings to send. + /////////////////////////////////// + pingTimeMod = (int)(now / PI_PING_INTERVAL); + if(connection->lastPingTimeMod) + numPings = (pingTimeMod - connection->lastPingTimeMod); + else + numPings = 1; + if(numPings) + connection->lastPingTimeMod = pingTimeMod; + + // Send pings. + ////////////// + players = piPickPingPlayers(peer, &numPings); + if(players) + { + for(i = 0 ; (i < numPings) && players[i] ; i++) + piPingPlayer(peer, players[i]); + } + + // Check for sending a crossping. + ///////////////////////////////// + if((now - connection->lastXpingSend) > PI_XPING_INTERVAL) + { + // Try and pick a player to crossping. + ////////////////////////////////////// + player = piPickXpingPlayer(peer); + if(player) + piXpingPlayer(peer, player); + } + + // Let pinger think. + //////////////////// + pingerThink(); +} + +PEERBool piPingInitPlayer +( + PEER peer, + piPlayer * player +) +{ + int i; + + PEER_CONNECTION; + + assert(player); + + // Check if we're not doing pings. + ////////////////////////////////// + if(!connection->doPings) + return PEERTrue; + + player->lastPingSend = 0; + player->lastPingRecv = 0; + player->lastXping = 0; + player->waitingForPing = PEERFalse; + player->pingsReturned = 0; + player->pingsLostConsecutive = 0; + player->pingAverage = 0; + for(i = 0 ; i < PI_PING_HISTORY_LEN ; i++) + player->pingHistory[i] = 0; + player->pingHistoryNum = 0; + player->numPings = 0; + player->xpingSent = PEERFalse; + player->inPingRoom = PEERFalse; + player->inXpingRoom = PEERFalse; + player->mustPing = PEERFalse; + player->pingOnce = PEERFalse; + + return PEERTrue; +} + +void piPingPlayerJoinedRoom +( + PEER peer, + piPlayer * player, + RoomType roomType +) +{ + PEER_CONNECTION; + + assert(player); + ASSERT_ROOMTYPE(roomType); + + // Check if we're not doing pings. + ////////////////////////////////// + if(!connection->doPings) + return; + + // Is this a ping room? + /////////////////////// + if(connection->pingRoom[roomType]) + player->inPingRoom = PEERTrue; + + // Is this an xping room? + ///////////////////////// + if(connection->xpingRoom[roomType]) + player->inXpingRoom = PEERTrue; + + // Immediately ping players when they join a staging room. + ////////////////////////////////////////////////////////// + if(roomType == StagingRoom) + player->mustPing = PEERTrue; +} + +static void piRemoveXping +( + PEER peer, + piXping * xping +) +{ + PEER_CONNECTION; + + assert(xping); + + TableRemove(connection->xpings, xping); +} + +typedef struct piPingPlayerLeftRoomData +{ + PEER peer; + const char * nick; +} piPingPlayerLeftRoomData; + +static void piPingPlayerLeftRoomTableMapFn +( + void *elem, + void *clientdata +) +{ + piXping * xping = (piXping *)elem; + piPingPlayerLeftRoomData * data = (piPingPlayerLeftRoomData *)clientdata; + assert(xping); + assert(data); + + // Check if this player is part of this xping. + ////////////////////////////////////////////// + if((strcmp(xping->nicks[0], data->nick) == 0) || (strcmp(xping->nicks[1], data->nick) == 0)) + piRemoveXping(data->peer, xping); +} + +void piPingPlayerLeftRoom +( + PEER peer, + piPlayer * player +) +{ + PEER_CONNECTION; + + assert(player); + + // Check if we're not doing pings. + ////////////////////////////////// + if(!connection->doPings) + return; + + // Was this player in a ping room? + ////////////////////////////////// + if(player->inPingRoom) + { + int i; + PEERBool inPingRoom = PEERFalse; + + // Is he still in a ping room? + ////////////////////////////// + for(i = 0 ; i < NumRooms ; i++) + { + if(player->inRoom[i] && connection->pingRoom[i]) + inPingRoom = PEERTrue; + } + player->inPingRoom = inPingRoom; + } + + // Was this player in an xping room? + //////////////////////////////////// + if(player->inXpingRoom) + { + int i; + PEERBool inXpingRoom = PEERFalse; + + // Is he still in a ping room? + ////////////////////////////// + for(i = 0 ; i < NumRooms ; i++) + { + if(player->inRoom[i] && connection->xpingRoom[i]) + inXpingRoom = PEERTrue; + } + player->inXpingRoom = inXpingRoom; + + if(!player->inXpingRoom) + { + piPingPlayerLeftRoomData data; + data.peer = peer; + data.nick = player->nick; + + // Get rid of all this players xpings. + ////////////////////////////////////// + TableMapSafe(connection->xpings, piPingPlayerLeftRoomTableMapFn, &data); + } + } +} + +static piXping * piFindXping +( + PEER peer, + const char * nick1, + const char * nick2 +) +{ + piXping xpingMatch; + + PEER_CONNECTION; + + assert(nick1); + assert(nick1[0]); + assert(piGetPlayer(peer, nick1)); + assert(nick2); + assert(nick2[0]); + assert(piGetPlayer(peer, nick2)); + + // Setup the xping match. + ///////////////////////// + strzcpy(xpingMatch.nicks[0], nick1, PI_NICK_MAX_LEN); + _strlwr(xpingMatch.nicks[0]); + strzcpy(xpingMatch.nicks[1], nick2, PI_NICK_MAX_LEN); + _strlwr(xpingMatch.nicks[0]); + + // Find the xping. + ////////////////// + return (piXping *)TableLookup(connection->xpings, &xpingMatch); +} + +static piXping * piAddXping +( + PEER peer, + const char * nick1, + const char * nick2 +) +{ + piXping xpingMatch; + piXping * xping; + + PEER_CONNECTION; + + assert(nick1); + assert(nick1[0]); + assert(piGetPlayer(peer, nick1)); + assert(nick2); + assert(nick2[0]); + assert(piGetPlayer(peer, nick2)); + + // Setup the one to add. + //////////////////////// + xping = &xpingMatch; + strzcpy(xping->nicks[0], nick1, PI_NICK_MAX_LEN); + _strlwr(xping->nicks[0]); + strzcpy(xping->nicks[1], nick2, PI_NICK_MAX_LEN); + _strlwr(xping->nicks[1]); + + // Add it. + ////////// + TableEnter(connection->xpings, xping); + + // Get it. + ////////// + xping = (piXping *)TableLookup(connection->xpings, &xpingMatch); + + // Return it. + ///////////// + return xping; +} + +void piUpdateXping +( + PEER peer, + const char * nick1, + const char * nick2, + int ping +) +{ + piPlayer * player1; + piPlayer * player2; + piXping * xping; + + PEER_CONNECTION; + + assert(nick1); + assert(nick1[0]); + assert(piGetPlayer(peer, nick1)); + assert(nick2); + assert(nick2[0]); + assert(piGetPlayer(peer, nick2)); + assert(ping >= 0); + + // Check if we're not doing pings. + ////////////////////////////////// + if(!connection->doPings) + return; + + player1 = piGetPlayer(peer, nick1); + if(!player1) + return; + if(!player1->inXpingRoom) + return; + player2 = piGetPlayer(peer, nick2); + if(!player2) + return; + if(!player2->inXpingRoom) + return; + + // Add it. + ////////// + xping = piAddXping(peer, nick1, nick2); + assert(xping); + if(!xping) + return; + + // Update. + ////////// + xping->ping = ping; +} + +PEERBool piGetXping +( + PEER peer, + const char * nick1, + const char * nick2, + int * ping +) +{ + piXping * xping; + + PEER_CONNECTION; + + assert(nick1); + assert(nick1[0]); + assert(piGetPlayer(peer, nick1)); + assert(nick2); + assert(nick2[0]); + assert(piGetPlayer(peer, nick2)); + assert(ping); + + // Check if we're not doing pings. + ////////////////////////////////// + if(!connection->doPings) + return PEERFalse; + + // Get the xping. + ///////////////// + xping = piFindXping(peer, nick1, nick2); + assert(xping); + if(!xping) + return PEERFalse; + + // Return the ping. + /////////////////// + *ping = xping->ping; + + return PEERTrue; +} diff --git a/code/gamespy/Peer/peerPing.h b/code/gamespy/Peer/peerPing.h new file mode 100644 index 00000000..99757acd --- /dev/null +++ b/code/gamespy/Peer/peerPing.h @@ -0,0 +1,41 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERPING_H_ +#define _PEERPING_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/************** +** FUNCTIONS ** +**************/ +PEERBool piPingInit(PEER peer); +void piPingCleanup(PEER peer); +void piPingThink(PEER peer); +PEERBool piPingInitPlayer(PEER peer, piPlayer * player); +void piPingPlayerJoinedRoom(PEER peer, piPlayer * player, RoomType roomType); +void piPingPlayerLeftRoom(PEER peer, piPlayer * player); +void piUpdateXping(PEER peer, const char * nick1, const char * nick2, int ping); +PEERBool piGetXping(PEER peer, const char * nick1, const char * nick2, int * ping); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerPlayers.c b/code/gamespy/Peer/peerPlayers.c new file mode 100644 index 00000000..0d5db959 --- /dev/null +++ b/code/gamespy/Peer/peerPlayers.c @@ -0,0 +1,824 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include "peerPlayers.h" +#include "peerPing.h" +#include "peerCallbacks.h" +#include "peerKeys.h" + +/************ +** DEFINES ** +************/ +#define PI_PLAYERS_NUM_BUCKETS 32 +#define PI_PLAYER_INFO_SIZE (sizeof(piPlayer) - PI_NICK_MAX_LEN) +#define PI_PLAYER_INFO(player) ((char *)(player) + PI_NICK_MAX_LEN) + +/************** +** FUNCTIONS ** +**************/ +static int piPlayersTableHashFn +( + const void *elem, + int numBuckets +) +{ + piPlayer * player = (piPlayer *)elem; + int c; + const char * str; + unsigned int hash; + + assert(player); + assert(player->nick[0]); + + // Get the hash. + //////////////// + str = player->nick; + hash = 0; + while((c = *str++) != '\0') + hash += (unsigned int)tolower(c); + hash %= (unsigned int)numBuckets; + + return (int)hash; +} + +static int GS_STATIC_CALLBACK piPlayersTableCompareFn +( + const void *elem1, + const void *elem2 +) +{ + piPlayer * player1 = (piPlayer *)elem1; + piPlayer * player2 = (piPlayer *)elem2; + assert(player1); + assert(player1->nick[0]); + assert(player2); + assert(player2->nick[0]); + + return strcasecmp(player1->nick, player2->nick); +} + +static void piPlayersTableElementFreeFn +( + void *elem +) +{ + piPlayer * player = (piPlayer *)elem; + assert(player); + assert(player->nick); + GSI_UNUSED(player); +} + +PEERBool piPlayersInit +( + PEER peer +) +{ + int i; + + PEER_CONNECTION; + + if(connection->stayInTitleRoom) + return PEERTrue; + + // Setup the player table. + ////////////////////////// + connection->players = TableNew(sizeof(piPlayer), PI_PLAYERS_NUM_BUCKETS, piPlayersTableHashFn, piPlayersTableCompareFn, piPlayersTableElementFreeFn); + if(!connection->players) + return PEERFalse; + + // No players in any room. + ////////////////////////// + for(i = 0 ; i < NumRooms ; i++) + connection->numPlayers[i] = 0; + + return PEERTrue; +} + +void piPlayersCleanup +( + PEER peer +) +{ + int i; + + PEER_CONNECTION; + + if(connection->stayInTitleRoom) + return; + + // gsifree the player table. + ///////////////////////// + if(connection->players) + TableFree(connection->players); + connection->players = NULL; + for(i = 0 ; i < NumRooms ; i++) + connection->numPlayers[i] = 0; +} + +static piPlayer * piAddPlayer +( + PEER peer, + const char * nick, + PEERBool initialize +) +{ + piPlayer * player; + piPlayer playerMatch; + + PEER_CONNECTION; + + assert(!piGetPlayer(peer, nick)); + + // Setup the player. + //////////////////// + player = &playerMatch; + memset(player, 0, sizeof(piPlayer)); + strzcpy(player->nick, nick, PI_NICK_MAX_LEN); + if(initialize) + { + int roomType; + + player->local = (PEERBool)(strcasecmp(nick, connection->nick) == 0); + + for(roomType = 0 ; roomType < NumRooms ; roomType++) + { + player->inRoom[roomType] = PEERFalse; + player->flags[roomType] = 0; + } + + player->IP = 0; + player->profileID = 0; + player->gotIPAndProfileID = PEERFalse; + + if(!piPingInitPlayer(peer, player)) + return NULL; + } + + // Add the player. + ////////////////// + TableEnter(connection->players, player); + + player = piGetPlayer(peer, nick); + assert(player); + + return player; +} + +static void piRemovePlayer +( + PEER peer, + piPlayer * player +) +{ + PEER_CONNECTION; + + assert(player); + + // Remove it. + ///////////// + TableRemove(connection->players, player); +} + +piPlayer * piPlayerJoinedRoom +( + PEER peer, + const char * nick, + RoomType roomType, + int mode +) +{ + piPlayer * player; + + PEER_CONNECTION; + + assert(nick); + assert(nick[0]); + ASSERT_ROOMTYPE(roomType); + assert(IN_ROOM || ENTERING_ROOM); + + // Check that we're in/entering this room. + ////////////////////////////////////////// + if(!ENTERING_ROOM && !IN_ROOM) + return NULL; + + // Find the player. + /////////////////// + player = piGetPlayer(peer, nick); + + // Is it a new player? + ////////////////////// + if(!player) + { + // Add the player. + ////////////////// + player = piAddPlayer(peer, nick, PEERTrue); + } + + // In this room. + //////////////// + assert(!player->inRoom[roomType]); + player->inRoom[roomType] = PEERTrue; + connection->numPlayers[roomType]++; + player->flags[roomType] = 0; + if(mode & CHAT_OP) + player->flags[roomType] |= PEER_FLAG_OP; + if(mode & CHAT_VOICE) + player->flags[roomType] |= PEER_FLAG_VOICE; + + // Do ping stuff. + ///////////////// + piPingPlayerJoinedRoom(peer, player, roomType); + + return player; +} + +void piPlayerLeftRoom +( + PEER peer, + const char * nick, + RoomType roomType +) +{ + piPlayer * player; + + PEER_CONNECTION; + + assert(nick); + assert(nick[0]); + ASSERT_ROOMTYPE(roomType); + + // Find the player. + /////////////////// + player = piGetPlayer(peer, nick); + assert(player); + if(!player) + return; + + // Leave the room. + ////////////////// + assert(player->inRoom[roomType]); + player->inRoom[roomType] = PEERFalse; + connection->numPlayers[roomType]--; + player->flags[roomType] = 0; + + // Do ping stuff. + ///////////////// + piPingPlayerLeftRoom(peer, player); + + // Are we not in any rooms now? + /////////////////////////////// + if(!player->inRoom[0] && !player->inRoom[1] && !player->inRoom[2]) + { + // This player is out. + ////////////////////// + piRemovePlayer(peer, player); + } + + // Cleanse the key cache. + ///////////////////////// + piKeyCacheCleanse(peer); +} + +void piPlayerChangedNick +( + PEER peer, + const char * oldNick, + const char * newNick +) +{ + char playerInfoBuffer[PI_PLAYER_INFO_SIZE]; + piPlayer * player; + + assert(oldNick); + assert(oldNick[0]); + assert(newNick); + assert(newNick[0]); + + // Find the player. + /////////////////// + player = piGetPlayer(peer, oldNick); + + // Check if we can't find this player + // (we could have already done the nick change + // if we're in more than one channel together). + /////////////////////////////////////////////// + if(!player) + return; + + // Save off the player info. + //////////////////////////// + memcpy(playerInfoBuffer, PI_PLAYER_INFO(player), PI_PLAYER_INFO_SIZE); + + // Remove the old nick. + /////////////////////// + piRemovePlayer(peer, player); + + // Add the new one. + /////////////////// + player = piAddPlayer(peer, newNick, PEERFalse); + assert(player); + if(!player) + return; + + // Copy the player info back in. + //////////////////////////////// + memcpy(PI_PLAYER_INFO(player), playerInfoBuffer, PI_PLAYER_INFO_SIZE); + + // Update the key cache. + //////////////////////// + piKeyCachePlayerChangedNick(peer, oldNick, newNick); +} + +typedef struct piLeftRoomData +{ + PEER peer; + RoomType roomType; +} piLeftRoomData; + +static void piLeftRoomMapFn +( + void *elem, + void *clientdata +) +{ + piPlayer * player = (piPlayer *)elem; + piLeftRoomData * data = (piLeftRoomData *)clientdata; + assert(player); + assert(player->nick); + assert(data); + + // Is the player in this room? + ////////////////////////////// + if(player->inRoom[data->roomType]) + { + // Leave the room. + ////////////////// + piPlayerLeftRoom(data->peer, player->nick, data->roomType); + } +} + +void piClearRoomPlayers +( + PEER peer, + RoomType roomType +) +{ + piLeftRoomData data; + + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + + // Go through all the players. + ////////////////////////////// + data.peer = peer; + data.roomType = roomType; + TableMapSafe(connection->players, piLeftRoomMapFn, &data); +} + +piPlayer * piGetPlayer +( + PEER peer, + const char * nick +) +{ + piPlayer playerMatch; + piPlayer * player; + + PEER_CONNECTION; + + assert(nick); + assert(nick[0]); + + // Check for no table. + ////////////////////// + if(!connection->players) + return NULL; + + // Lookup this player. + ////////////////////// + strzcpy(playerMatch.nick, nick, PI_NICK_MAX_LEN); + player = (piPlayer *)TableLookup(connection->players, &playerMatch); + + return player; +} + +typedef struct piEnumRoomPlayersData +{ + PEER peer; + RoomType roomType; + int count; + piEnumRoomPlayersCallback callback; + void * param; +} piEnumRoomPlayersData; + +static void piEnumRoomPlayersMap +( + void *elem, + void *clientdata +) +{ + piPlayer * player = (piPlayer *)elem; + piEnumRoomPlayersData * data = (piEnumRoomPlayersData *)clientdata; + // Is this player in the room? + ////////////////////////////// + if(player->inRoom[data->roomType]) + { + // Call the callback. + ///////////////////// + data->callback(data->peer, data->roomType, player, data->count, data->param); + + // One more player. + /////////////////// + data->count++; + } +} + +void piEnumRoomPlayers +( + PEER peer, + RoomType roomType, + piEnumRoomPlayersCallback callback, + void * param +) +{ + piEnumRoomPlayersData data; + + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + assert(callback); + + // Init the data. + ///////////////// + data.peer = peer; + data.roomType = roomType; + data.count = 0; + data.callback = callback; + data.param = param; + + // Enum through the players. + //////////////////////////// + TableMap(connection->players, piEnumRoomPlayersMap, &data); + + // Call the callback once to terminate the enum. + //////////////////////////////////////////////// + callback(peer, roomType, NULL, -1, param); +} + +static int piFindPlayersByIPMap +( + void * elem, + void * clientdata +) +{ + piPlayer * player = (piPlayer *)elem; + unsigned int IP; + + IP = *(unsigned int *)clientdata; + + // Is this the same IP? + /////////////////////// + if(player->gotIPAndProfileID && (player->IP == IP)) + return 0; + + return 1; +} + +piPlayer * piFindPlayerByIP +( + PEER peer, + unsigned int IP +) +{ + PEER_CONNECTION; + + return (piPlayer *)TableMap2(connection->players, piFindPlayersByIPMap, &IP); +} + +void piSetPlayerIPAndProfileID +( + PEER peer, + const char * nick, + unsigned int IP, + int profileID +) +{ + piPlayer * player; + + assert(nick); + assert(nick[0]); + + if(!nick) + return; + + // Cache the info. + ////////////////// + player = piGetPlayer(peer, nick); + if(player) + { + player->IP = IP; + player->profileID = profileID; + + if(!player->gotIPAndProfileID) + { + player->gotIPAndProfileID = PEERTrue; + + // The IP is now available, send a ping + //////////////////////////////////////// + if(player->inRoom[TitleRoom]) + piPingPlayerJoinedRoom(peer, player, TitleRoom); + if(player->inRoom[GroupRoom]) + piPingPlayerJoinedRoom(peer, player, GroupRoom); + if(player->inRoom[StagingRoom]) + piPingPlayerJoinedRoom(peer, player, StagingRoom); + } + + } +} + +static void piSetNewPlayerFlags +( + PEER peer, + const char * nick, + RoomType roomType, + int flags +) +{ + piPlayer * player; + int oldFlags; + + assert(nick); + assert(nick[0]); + + if(!nick) + return; + + // Find the player. + /////////////////// + player = piGetPlayer(peer, nick); + if(!player || !player->inRoom[roomType]) + return; + + // Copy off the old flags. + ////////////////////////// + oldFlags = player->flags[roomType]; + + // Check for no change. + /////////////////////// + if(flags == oldFlags) + return; + + // Set the new flags. + ///////////////////// + player->flags[roomType] = flags; + + // If ready changed in the staging room, call a callback. + ///////////////////////////////////////////////////////// + if((roomType == StagingRoom) && ((oldFlags & PEER_FLAG_READY) != (flags & PEER_FLAG_READY))) + piAddReadyChangedCallback(peer, player->nick, (PEERBool)((player->flags[roomType] & PEER_FLAG_READY) != 0)); + + // Call the flags changed callback. + /////////////////////////////////// + piAddPlayerFlagsChangedCallback(peer, roomType, nick, oldFlags, flags); +} + +int piParseFlags +( + const char * flags +) +{ + int nFlags = 0; + + if(strchr(flags, 's')) + nFlags |= PEER_FLAG_STAGING; + if(strchr(flags, 'r')) + nFlags |= PEER_FLAG_READY; + if(strchr(flags, 'g')) + nFlags |= PEER_FLAG_PLAYING; + if(strchr(flags, 'a')) + nFlags |= PEER_FLAG_AWAY; + if(strchr(flags, 'h')) + nFlags |= PEER_FLAG_HOST; + + return nFlags; +} + +void piSetPlayerRoomFlags +( + PEER peer, + const char * nick, + RoomType roomType, + const char * flags +) +{ + piPlayer * player; + int nFlags; + + assert(nick); + assert(nick[0]); + + if(!nick) + return; + + // Find the player. + /////////////////// + player = piGetPlayer(peer, nick); + if(!player || !player->inRoom[roomType]) + return; + + // Get the mode. + //////////////// + nFlags = (player->flags[roomType] & (PEER_FLAG_OP | PEER_FLAG_VOICE)); + + // Add the new flags. + ///////////////////// + nFlags |= piParseFlags(flags); + + // Set them. + //////////// + piSetNewPlayerFlags(peer, nick, roomType, nFlags); +} + +void piSetPlayerModeFlags +( + PEER peer, + const char * nick, + RoomType roomType, + int mode +) +{ + piPlayer * player; + int nFlags; + + assert(nick); + assert(nick[0]); + + if(!nick) + return; + + // Find the player. + /////////////////// + player = piGetPlayer(peer, nick); + if(!player || !player->inRoom[roomType]) + return; + + // Get the non-mode flags. + ////////////////////////// + nFlags = (player->flags[roomType] & (~(PEER_FLAG_OP | PEER_FLAG_VOICE))); + + // Add the new flags. + ///////////////////// + if(mode & CHAT_OP) + nFlags |= PEER_FLAG_OP; + if(mode & CHAT_VOICE) + nFlags |= PEER_FLAG_VOICE; + + // Set them. + //////////// + piSetNewPlayerFlags(peer, nick, roomType, nFlags); +} + +PEERBool piIsPlayerVIP +( + piPlayer * player, + RoomType roomType +) +{ + if(!player) + return PEERFalse; + + if(!player->inRoom[roomType]) + return PEERFalse; + + return (PEERBool)((player->flags[roomType] & (PEER_FLAG_OP | PEER_FLAG_VOICE)) != 0); +} + +PEERBool piIsPlayerHost(piPlayer * player) +{ + if(!player) + return PEERFalse; + + if(!player->inRoom[StagingRoom]) + return PEERFalse; + + return (PEERBool)((player->flags[StagingRoom] & (PEER_FLAG_OP | PEER_FLAG_HOST)) == (PEER_FLAG_OP | PEER_FLAG_HOST)); +} + +PEERBool piIsPlayerOp(piPlayer * player) +{ + if(!player) + return PEERFalse; + + if(!player->inRoom[StagingRoom]) + return PEERFalse; + + return (PEERBool)(player->flags[StagingRoom] & PEER_FLAG_OP); +} + +typedef struct piFindPlayerByIndexData +{ + int index; + int count; + RoomType roomType; +} piFindPlayerByIndexData; + +static int piFindPlayerByIndexMap +( + void * elem, + void * clientdata +) +{ + piPlayer * player = (piPlayer *)elem; + piFindPlayerByIndexData * data = (piFindPlayerByIndexData *)clientdata; + + // check if the player is in the room + if(player->inRoom[data->roomType]) + { + // check if this is the one we're looking for + if(data->index == data->count) + return 0; + + // one more player found + data->count++; + } + + return 1; +} + +piPlayer * piFindPlayerByIndex +( + PEER peer, + RoomType roomType, + int index +) +{ + piFindPlayerByIndexData data; + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + + // setup the data + data.index = index; + data.count = 0; + data.roomType = roomType; + + // enum through the players + return TableMap2(connection->players, piFindPlayerByIndexMap, &data); +} + +typedef struct piCountRoomOpsMapData +{ + int count; + RoomType roomType; + const char * exclude; +} piCountRoomOpsMapData; + +static void piCountRoomOpsMap +( + void * elem, + void * clientdata +) +{ + piPlayer * player = (piPlayer *)elem; + piCountRoomOpsMapData * data = (piCountRoomOpsMapData *)clientdata; + + // check the exlude nick + if(data->exclude && (strcasecmp(data->exclude, player->nick) == 0)) + return; + + // is this player an op? + if(player->flags[data->roomType] & PEER_FLAG_OP) + data->count++; +} + +int piCountRoomOps(PEER peer, RoomType roomType, const char * exclude) +{ + piCountRoomOpsMapData data; + + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + + // setup the data struct + data.count = 0; + data.roomType = roomType; + data.exclude = exclude; + + // enum through the players + TableMap(connection->players, piCountRoomOpsMap, &data); + + // return the count + return data.count; +} diff --git a/code/gamespy/Peer/peerPlayers.h b/code/gamespy/Peer/peerPlayers.h new file mode 100644 index 00000000..9820e821 --- /dev/null +++ b/code/gamespy/Peer/peerPlayers.h @@ -0,0 +1,92 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERPLAYERS_H_ +#define _PEERPLAYERS_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/************ +** DEFINES ** +************/ +#define PI_PING_HISTORY_LEN 4 + +/********** +** TYPES ** +**********/ +typedef struct piPlayer +{ + char nick[PI_NICK_MAX_LEN]; // this has to be first + PEERBool inRoom[NumRooms]; // true if in the room + PEERBool local; // true for the local player + + unsigned int IP; // the IP in network byte order + int profileID; // gp profile id + PEERBool gotIPAndProfileID; // true if we have the IP and profile ID + + int flags[NumRooms]; // player's room flags + + // Ping stuff + unsigned long lastPingSend; // last time a ping was sent + unsigned long lastPingRecv; // last time a ping was received + unsigned long lastXping; // last time a xping was sent + PEERBool waitingForPing; // if true we're waiting for a ping to return + int pingsReturned; // number of pings returned + int pingsLostConsecutive; // number of pings lost in a row + int pingAverage; // the average ping time + int pingHistory[PI_PING_HISTORY_LEN]; // history of pings + int pingHistoryNum; // the number of pings in the ping history + int numPings; // number of pings for this player + PEERBool xpingSent; // if true, we already sent a xping with the current average + PEERBool inPingRoom; // true if the player is in a room getting pinged + PEERBool inXpingRoom; // true if the player is in a room getting xpinged + PEERBool mustPing; // if true, ping this player as soon as possible + PEERBool pingOnce; // a one-time ping has been triggered with peerPingPlayer +} piPlayer; + +/************** +** FUNCTIONS ** +**************/ +PEERBool piPlayersInit(PEER peer); +void piPlayersCleanup(PEER peer); +piPlayer * piPlayerJoinedRoom(PEER peer, const char * nick, RoomType roomType, int mode); +void piPlayerLeftRoom(PEER peer, const char * nick, RoomType roomType); +void piPlayerChangedNick(PEER peer, const char * oldNick, const char * newNick); +void piClearRoomPlayers(PEER peer, RoomType roomType); +piPlayer * piGetPlayer(PEER peer, const char * nick); +typedef void (* piEnumRoomPlayersCallback)(PEER peer, RoomType roomType, piPlayer * player, int index, void * param); +void piEnumRoomPlayers(PEER peer, RoomType roomType, piEnumRoomPlayersCallback callback, void * param); +piPlayer * piFindPlayerByIP(PEER peer, unsigned int IP); +void piSetPlayerIPAndProfileID(PEER peer, const char * nick, unsigned int IP, int profileID); +int piParseFlags(const char * flags); +void piSetPlayerRoomFlags(PEER peer, const char * nick, RoomType roomType, const char * flags); +void piSetPlayerModeFlags(PEER peer, const char * nick, RoomType roomType, int mode); +PEERBool piIsPlayerVIP(piPlayer * player, RoomType roomType); +PEERBool piIsPlayerHost(piPlayer * player); +PEERBool piIsPlayerOp(piPlayer * player); +piPlayer * piFindPlayerByIndex(PEER peer, RoomType roomType, int index); +piPlayer * piFindRoomHost(PEER peer, RoomType roomType); +piPlayer * piFindRoomOp(PEER peer, RoomType roomType); +int piCountRoomOps(PEER peer, RoomType roomType, const char * exclude); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerQR.c b/code/gamespy/Peer/peerQR.c new file mode 100644 index 00000000..d70ab916 --- /dev/null +++ b/code/gamespy/Peer/peerQR.c @@ -0,0 +1,494 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include "peerQR.h" +#include "peerGlobalCallbacks.h" +#include "peerPlayers.h" +#include "peerAutoMatch.h" +#include "peerOperations.h" + +/************** +** CALLBACKS ** +**************/ +#ifndef GSI_UNICODE +#define piQRAddErrorCallback piQRAddErrorCallbackA +#else +#define piQRAddErrorCallback piQRAddErrorCallbackW +#endif + +static void piQRServerKeyCallback +( + int key, + qr2_buffer_t buffer, + PEER peer +) +{ + PEER_CONNECTION; + + // check if we should report it + if(connection->inRoom[StagingRoom] && (!connection->playing || (connection->reportingOptions & PEER_REPORT_INFO))) + { + // check if it's one of our keys + switch(key) + { + case HOSTNAME_KEY: + qr2_buffer_addA(buffer, NAMES[StagingRoom]); + return; + case NUMPLAYERS_KEY: + qr2_buffer_add_int(buffer, connection->numPlayers[StagingRoom]); + return; + case MAXPLAYERS_KEY: + if(!connection->maxPlayers) + break; + qr2_buffer_add_int(buffer, connection->maxPlayers); + return; + case GAMEMODE_KEY: + if(connection->playing) + break; + qr2_buffer_addA(buffer, "openstaging"); + return; + case PASSWORD_KEY: + qr2_buffer_add_int(buffer, connection->passwordedRoom?1:0); + return; + } + } + + // we always report groupid + if(key == GROUPID_KEY) + { + qr2_buffer_add_int(buffer, connection->reportingGroupID); + return; + } + + // pass it to the app + if(connection->callbacks.qrServerKey) + connection->callbacks.qrServerKey(peer, key, buffer, connection->callbacks.param); +} + +static void piQRPlayerKeyCallback +( + int key, + int index, + qr2_buffer_t buffer, + PEER peer +) +{ + piPlayer * player; + PEER_CONNECTION; + + // check if we should report it + if(connection->inRoom[StagingRoom] && (!connection->playing || (connection->reportingOptions & PEER_REPORT_PLAYERS))) + { + // check if it's a key we report + if((key == PLAYER__KEY) || (key == PING__KEY)) + { + // convert the index to a player + player = piFindPlayerByIndex(peer, StagingRoom, index); + if(!player) + qr2_buffer_addA(buffer, ""); + else if(key == PLAYER__KEY) + qr2_buffer_addA(buffer, player->nick); + else if(key == PING__KEY) + qr2_buffer_add_int(buffer, player->pingAverage); + + return; + } + } + + // pass it to the app + if(connection->callbacks.qrPlayerKey) + connection->callbacks.qrPlayerKey(peer, key, index, buffer, connection->callbacks.param); +} + +static void piQRTeamKeyCallback +( + int key, + int index, + qr2_buffer_t buffer, + PEER peer +) +{ + PEER_CONNECTION; + + // pass it to the app + if(connection->callbacks.qrTeamKey) + connection->callbacks.qrTeamKey(peer, key, index, buffer, connection->callbacks.param); +} + +static void piQRKeyListCallback +( + qr2_key_type type, + qr2_keybuffer_t keyBuffer, + PEER peer +) +{ + PEER_CONNECTION; + + // add our own keys + switch(type) + { + case key_server: + if(!connection->autoMatchReporting) + { + qr2_keybuffer_add(keyBuffer, HOSTNAME_KEY); + qr2_keybuffer_add(keyBuffer, GAMEMODE_KEY); + if(connection->passwordedRoom) + qr2_keybuffer_add(keyBuffer, PASSWORD_KEY); + if(connection->reportingGroupID) + qr2_keybuffer_add(keyBuffer, GROUPID_KEY); + } + qr2_keybuffer_add(keyBuffer, NUMPLAYERS_KEY); + if(connection->maxPlayers) + qr2_keybuffer_add(keyBuffer, MAXPLAYERS_KEY); + break; + case key_player: + qr2_keybuffer_add(keyBuffer, PLAYER__KEY); + qr2_keybuffer_add(keyBuffer, PING__KEY); + break; + default: + break; + } + + // pass it to the app + if(connection->callbacks.qrKeyList) + connection->callbacks.qrKeyList(peer, type, keyBuffer, connection->callbacks.param); +} + +static int piQRCountCallback +( + qr2_key_type type, + PEER peer +) +{ + PEER_CONNECTION; + + // we only handle player counts + if(type == key_player) + { + // check if we should report it + if(connection->inRoom[StagingRoom] && (!connection->playing || (connection->reportingOptions & PEER_REPORT_PLAYERS))) + return connection->numPlayers[StagingRoom]; + } + + // pass it to the app + if(connection->callbacks.qrCount) + return connection->callbacks.qrCount(peer, type, connection->callbacks.param); + + // app's not handling it + return 0; +} + +static void piQRAddErrorCallbackA +( + qr2_error_t error, + char * errorString, + PEER peer +) +{ + PEER_CONNECTION; + + // handle this if automatching + if(peerIsAutoMatching(peer)) + { + PEERAutoMatchStatus status; + + // track the error + connection->autoMatchQRFailed = PEERTrue; + + // switch to a new state + if(connection->autoMatchSBFailed && (connection->autoMatchStatus != PEERStaging)) + status = PEERFailed; + else + status = PEERSearching; + piSetAutoMatchStatus(peer, status); + + return; + } + + // pass it to the app + if(connection->callbacks.qrAddError) + { +#ifndef GSI_UNICODE + connection->callbacks.qrAddError(peer, error, errorString, connection->callbacks.param); +#else + unsigned short* errorString_W = UTF8ToUCS2StringAlloc(errorString); + connection->callbacks.qrAddError(peer, error, errorString_W, connection->callbacks.param); + gsifree(errorString_W); +#endif + } +} +#ifdef GSI_UNICODE +static void piQRAddErrorCallbackW +( + qr2_error_t error, + unsigned short * errorString, + PEER peer +) +{ + char* errorString_A = UCS2ToUTF8StringAlloc(errorString); + piQRAddErrorCallbackA(error, errorString_A, peer); + gsifree(errorString_A); +} +#endif + +static void piQRNatNegotiateCallback +( + int cookie, + PEER peer +) +{ + PEER_CONNECTION; + + // pass it to the app + if(connection->callbacks.qrNatNegotiateCallback) + connection->callbacks.qrNatNegotiateCallback(peer, cookie, connection->callbacks.param); +} + +static void piQRPublicAddressCallback +( + unsigned int ip, + unsigned short port, + PEER peer +) +{ + PEER_CONNECTION; + + // pass it to the app + if(connection->callbacks.qrPublicAddressCallback) + connection->callbacks.qrPublicAddressCallback(peer, ip, port, connection->callbacks.param); +} + +/************** +** FUNCTIONS ** +**************/ +PEERBool piStartReporting +( + PEER peer, + SOCKET socket, + unsigned short port +) +{ + int rcode; + + PEER_CONNECTION; + + // Check that we're not reporting. + ////////////////////////////////// + assert(!connection->queryReporting); + if(connection->queryReporting) + piStopReporting(peer); + + // Init qr2. + //////////// + if(socket == INVALID_SOCKET) + { + rcode = qr2_initA(&connection->queryReporting, NULL, PI_QUERYPORT, connection->title, connection->qrSecretKey, + 1, connection->natNegotiate, + piQRServerKeyCallback, + piQRPlayerKeyCallback, + piQRTeamKeyCallback, + piQRKeyListCallback, + piQRCountCallback, + piQRAddErrorCallback, + peer); + } + else + { + rcode = qr2_init_socketA(&connection->queryReporting, socket, port, connection->title, connection->qrSecretKey, + 1, connection->natNegotiate, + piQRServerKeyCallback, + piQRPlayerKeyCallback, + piQRTeamKeyCallback, + piQRKeyListCallback, + piQRCountCallback, + piQRAddErrorCallback, + peer); + } + + if(rcode != 0) + return PEERFalse; + + // Store the ID of the group room we're in. + /////////////////////////////////////////// + connection->reportingGroupID = connection->groupID; + + // Setup the nat-negotiate callback. + //////////////////////////////////// + qr2_register_natneg_callback(connection->queryReporting, piQRNatNegotiateCallback); + + // Set the public address callback. + /////////////////////////////////// + qr2_register_publicaddress_callback(connection->queryReporting, piQRPublicAddressCallback); + + // No options. + ////////////// + connection->reportingOptions = 0; + + return PEERTrue; +} + +void piStopReporting +( + PEER peer +) +{ + PEER_CONNECTION; + + // Check that we're reporting. + ////////////////////////////// + if(!connection->queryReporting) + return; + + // Clean up query-reporting. + //////////////////////////// + qr2_shutdown(connection->queryReporting); + connection->queryReporting = NULL; +} + +void piSendStateChanged +( + PEER peer +) +{ + PEER_CONNECTION; + + // Check that we're reporting. + ////////////////////////////// + if(!connection->queryReporting) + return; + + // Send the statechanged. + ///////////////////////// + qr2_send_statechanged(connection->queryReporting); +} + +void piQRThink +( + PEER peer +) +{ + PEER_CONNECTION; + + if(connection->queryReporting) + qr2_think(connection->queryReporting); + if(connection->autoMatchReporting) + qr2_think(connection->autoMatchReporting); +} + +PEERBool piStartAutoMatchReporting +( + PEER peer +) +{ + piOperation * operation; + char autoMatchTitle[PI_TITLE_MAX_LEN]; + int rcode; + + PEER_CONNECTION; + + // Check that we're not reporting. + ////////////////////////////////// + assert(!connection->autoMatchReporting); + if(connection->autoMatchReporting) + piStopAutoMatchReporting(peer); + + // Get the operation. + ///////////////////// + operation = connection->autoMatchOperation; + assert(operation); + + // Setup the AutoMatch gamename. + //////////////////////////////// + strzcpy(autoMatchTitle, connection->title, sizeof(autoMatchTitle)); + strzcat(autoMatchTitle, "am", sizeof(autoMatchTitle)); + + // Init qr2. + //////////// + if(operation->socket == INVALID_SOCKET) + { + rcode = qr2_initA(&connection->autoMatchReporting, NULL, PI_QUERYPORT, autoMatchTitle, connection->qrSecretKey, + 1, connection->natNegotiate, + piQRServerKeyCallback, + piQRPlayerKeyCallback, + piQRTeamKeyCallback, + piQRKeyListCallback, + piQRCountCallback, + piQRAddErrorCallback, + peer); + } + else + { + rcode = qr2_init_socketA(&connection->autoMatchReporting, operation->socket, operation->port, autoMatchTitle, connection->qrSecretKey, + 1, connection->natNegotiate, + piQRServerKeyCallback, + piQRPlayerKeyCallback, + piQRTeamKeyCallback, + piQRKeyListCallback, + piQRCountCallback, + piQRAddErrorCallback, + peer); + + // If we created the socket, hand over responsibility for it to qr2. + //////////////////////////////////////////////////////////////////// + if(operation->socketClose) + { + operation->socketClose = PEERFalse; + connection->autoMatchReporting->read_socket = 1; + } + } + + // Set the error flag. + ////////////////////// + connection->autoMatchQRFailed = (rcode == 0)?PEERTrue:PEERFalse; + + // Return false if there was an error. + ////////////////////////////////////// + if(rcode != 0) + return PEERFalse; + + // Setup the nat-negotiate callback. + //////////////////////////////////// + qr2_register_natneg_callback(connection->autoMatchReporting, piQRNatNegotiateCallback); + + // Set the public address callback. + /////////////////////////////////// + qr2_register_publicaddress_callback(connection->autoMatchReporting, piQRPublicAddressCallback); + + return PEERTrue; +} + +void piStopAutoMatchReporting +( + PEER peer +) +{ + PEER_CONNECTION; + + // Check that we're reporting. + ////////////////////////////// + if(!connection->autoMatchReporting) + return; + + // Clean up query-reporting. + //////////////////////////// + qr2_shutdown(connection->autoMatchReporting); + connection->autoMatchReporting = NULL; + + // This socket should have been cleared after qr2_shutdown + // but it's a copy of connection->autoMatchReporting->hbsock + // Thus it needs to be cleared if it hasn't been already + if (connection->autoMatchOperation->socket != INVALID_SOCKET) + { + connection->autoMatchOperation->socket = INVALID_SOCKET; + } +} diff --git a/code/gamespy/Peer/peerQR.h b/code/gamespy/Peer/peerQR.h new file mode 100644 index 00000000..0805a656 --- /dev/null +++ b/code/gamespy/Peer/peerQR.h @@ -0,0 +1,44 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERQR_H_ +#define _PEERQR_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/************ +** DEFINES ** +************/ +#define PI_QUERYPORT 6500 + +/************** +** FUNCTIONS ** +**************/ +PEERBool piStartReporting(PEER peer, SOCKET socket, unsigned short port); +void piStopReporting(PEER peer); +void piSendStateChanged(PEER peer); +void piQRThink(PEER peer); +PEERBool piStartAutoMatchReporting(PEER peer); +void piStopAutoMatchReporting(PEER peer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerRooms.c b/code/gamespy/Peer/peerRooms.c new file mode 100644 index 00000000..d7128e8d --- /dev/null +++ b/code/gamespy/Peer/peerRooms.c @@ -0,0 +1,416 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include +#include +#include "peerRooms.h" +#include "peerPlayers.h" +#include "peerMangle.h" +#include "peerCallbacks.h" +#include "peerKeys.h" +#include "peerQR.h" +#include "peerHost.h" +#include "peerOperations.h" +#include "peerSB.h" + +/************** +** FUNCTIONS ** +**************/ +PEERBool piRoomsInit +( + PEER peer +) +{ + int roomType; + + PEER_CONNECTION; + + // Init rooms. + ////////////// + for(roomType = 0 ; roomType < NumRooms ; roomType++) + { + if(connection->stayInTitleRoom && ((RoomType)roomType == TitleRoom)) + continue; + + ROOM[0] = '\0'; + NAME[0] = '\0'; + ENTERING_ROOM = PEERFalse; + IN_ROOM = PEERFalse; + connection->oldFlags[roomType] = 0; + +#ifdef GSI_UNICODE + ROOM_W[0] = '\0'; +#endif + } + connection->groupID = 0; + connection->titleRoomChannel[0] = '\0'; + + return PEERTrue; +} + +void piRoomsCleanup +( + PEER peer +) +{ + int roomType; + + PEER_CONNECTION; + + // Check all the rooms. + /////////////////////// + for(roomType = 0 ; roomType < NumRooms ; roomType++) + { + if(connection->stayInTitleRoom && ((RoomType)roomType == TitleRoom)) + continue; + + // Are we in or entering the room? + ////////////////////////////////// + if(IN_ROOM || ENTERING_ROOM) + { + // Leave it. + //////////// + piLeaveRoom(peer, (RoomType)roomType, NULL); + } + ROOM[0] = '\0'; + NAME[0] = '\0'; + ENTERING_ROOM = PEERFalse; + IN_ROOM = PEERFalse; + } + connection->titleRoomChannel[0] = '\0'; +} + +void piStartedEnteringRoom +( + PEER peer, + RoomType roomType, + const char * room +) +{ + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + assert(room); + assert(room[0]); + assert(strlen(room) < PI_ROOM_MAX_LEN); + if(strlen(room) >= PI_ROOM_MAX_LEN) + return; + + // Check that we're not entering, or in, this room. + /////////////////////////////////////////////////// + assert(!ROOM[0]); + assert(!ENTERING_ROOM); + assert(!IN_ROOM); + + // Start entering. + ////////////////// + strcpy(ROOM, room); + ENTERING_ROOM = PEERTrue; + connection->oldFlags[roomType] = 0; + +#ifdef GSI_UNICODE + UTF8ToUCS2String(ROOM, ROOM_W); +#endif +} + + +void piFinishedEnteringRoom +( + PEER peer, + RoomType roomType, + const char * name +) +{ + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + + if(!name) + name = ""; + + // Check that we're entering. + ///////////////////////////// + assert(ROOM[0]); + assert(ENTERING_ROOM); + assert(!IN_ROOM); + assert(strlen(name) < PI_ROOM_MAX_LEN); + + // We're in. + //////////// + IN_ROOM = PEERTrue; + ENTERING_ROOM = PEERFalse; + strzcpy(NAME, name, PI_NAME_MAX_LEN); + +#ifdef GSI_UNICODE + UTF8ToUCS2String(NAME, NAME_W); +#endif + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); + + // Refresh the watch keys for this room. + //////////////////////////////////////// + piKeyCacheRefreshRoom(peer, roomType); +} + +void piLeaveRoom +( + PEER peer, + RoomType roomType, + const char * reason +) +{ + PEER_CONNECTION; + + ASSERT_ROOMTYPE(roomType); + + // Check that we're in/entering this room. + ////////////////////////////////////////// + if(!ENTERING_ROOM && !IN_ROOM) + return; + + assert(ROOM[0]); + + // Are we entering? + /////////////////// + if(ENTERING_ROOM) + { + // Cancel the operation. + //////////////////////// + piCancelJoinOperation(peer, roomType); + } + + // Leave the channel. + ///////////////////// + if(connection->connected) + chatLeaveChannelA(connection->chat, ROOM, reason); + + // Clear all the players out of this room. + ////////////////////////////////////////// + piClearRoomPlayers(peer, roomType); + + // Reset in/entering states. + //////////////////////////// + if(IN_ROOM) + { + assert(!ENTERING_ROOM); + IN_ROOM = PEERFalse; + } + else + { + assert(ENTERING_ROOM); + ENTERING_ROOM = PEERFalse; + } + + // Clear the room/name. + /////////////////////// + ROOM[0] = '\0'; + NAME[0] = '\0'; + +#ifdef GSI_UNICODE + ROOM_W[0] = '\0'; +#endif + + // Clear the flags. + /////////////////// + connection->oldFlags[roomType] = 0; + + // Do roomtype specific stuff. + ////////////////////////////// + if(roomType == StagingRoom) + { + // Stop hosting. + //////////////// + piStopHosting(peer, PEERFalse); + + // Stop reporting as long as we're not playing. + /////////////////////////////////////////////// + if(!connection->playing) + piStopReporting(peer); + + // No host server. + ////////////////// + piSBFreeHostServer(peer); + + // Turn ready off. + ////////////////// + connection->ready = PEERFalse; + + // Clear passworded flag. + ///////////////////////// + connection->passwordedRoom = PEERFalse; + + // Set the flags. + ///////////////// + piSetLocalFlags(peer); + } + else if(roomType == GroupRoom) + { + // Clear the group ID. + ////////////////////// + connection->groupID = 0; + } + + // Cleanse the key cache. + ///////////////////////// + piKeyCacheCleanse(peer); +} + +PEERBool piRoomToType +( + PEER peer, + const char * room, + RoomType * roomType +) +{ + int i; + + PEER_CONNECTION; + + for(i = 0 ; i < NumRooms ; i++) + { + if(strcasecmp(room, ROOMS[i]) == 0) + { + *roomType = (RoomType)i; + return PEERTrue; + } + } + + return PEERFalse; +} + +void piSetLocalFlags +( + PEER peer +) +{ + char buffer[NumRooms][128]; + char * titleRoom; + char * groupRoom; + char * stagingRoom; + const char * key = "b_flags"; + int nFlags; + + PEER_CONNECTION; + + if(!connection->connected) + return; + + if(connection->inRoom[TitleRoom] || connection->enteringRoom[TitleRoom]) + titleRoom = buffer[TitleRoom]; + else + titleRoom = NULL; + if(connection->inRoom[GroupRoom] || connection->enteringRoom[GroupRoom]) + groupRoom = buffer[GroupRoom]; + else + groupRoom = NULL; + if(connection->inRoom[StagingRoom] || connection->enteringRoom[StagingRoom]) + stagingRoom = buffer[StagingRoom]; + else + stagingRoom = NULL; + + // Check for staging room. + ////////////////////////// + if(connection->inRoom[StagingRoom]) + { + if(titleRoom) + *titleRoom++ = 's'; + if(groupRoom) + *groupRoom++ = 's'; + *stagingRoom++ = 's'; + + // Check for ready. + /////////////////// + if(connection->ready) + *stagingRoom++ = 'r'; + + // Check for hosting. + ///////////////////// + if(connection->hosting) + *stagingRoom++ = 'h'; + } + + // Check for playing. + ///////////////////// + if(connection->playing) + { + if(titleRoom) + *titleRoom++ = 'g'; + if(groupRoom) + *groupRoom++ = 'g'; + if(stagingRoom) + *stagingRoom++ = 'g'; + } + + // Check for away. + ////////////////// + if(connection->away) + { + if(titleRoom) + *titleRoom++ = 'a'; + if(groupRoom) + *groupRoom++ = 'a'; + if(stagingRoom) + *stagingRoom++ = 'a'; + } + + // Cap it off. + ////////////// + if(titleRoom) + { + *titleRoom = '\0'; + titleRoom = buffer[TitleRoom]; + } + if(groupRoom) + { + *groupRoom = '\0'; + groupRoom = buffer[GroupRoom]; + } + if(stagingRoom) + { + *stagingRoom = '\0'; + stagingRoom = buffer[StagingRoom]; + } + + // Set the keys. + //////////////// + if(titleRoom) + { + nFlags = piParseFlags(titleRoom); + if(nFlags != connection->oldFlags[TitleRoom]) + { + chatSetChannelKeysA(connection->chat, connection->rooms[TitleRoom], connection->nick, 1, &key, (const char **)&titleRoom); + connection->oldFlags[TitleRoom] = nFlags; + } + } + if(groupRoom) + { + nFlags = piParseFlags(groupRoom); + if(nFlags != connection->oldFlags[GroupRoom]) + { + chatSetChannelKeysA(connection->chat, connection->rooms[GroupRoom], connection->nick, 1, &key, (const char **)&groupRoom); + connection->oldFlags[GroupRoom] = nFlags; + } + } + if(stagingRoom) + { + nFlags = piParseFlags(stagingRoom); + if(nFlags != connection->oldFlags[StagingRoom]) + { + chatSetChannelKeysA(connection->chat, connection->rooms[StagingRoom], connection->nick, 1, &key, (const char **)&stagingRoom); + connection->oldFlags[StagingRoom] = nFlags; + } + } +} diff --git a/code/gamespy/Peer/peerRooms.h b/code/gamespy/Peer/peerRooms.h new file mode 100644 index 00000000..cc6388b4 --- /dev/null +++ b/code/gamespy/Peer/peerRooms.h @@ -0,0 +1,40 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERROOMS_H_ +#define _PEERROOMS_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/************** +** FUNCTIONS ** +**************/ +PEERBool piRoomsInit(PEER peer); +void piRoomsCleanup(PEER peer); +void piStartedEnteringRoom(PEER peer, RoomType roomType, const char * room); +void piFinishedEnteringRoom(PEER peer, RoomType roomType, const char * name); +void piLeaveRoom(PEER peer, RoomType roomType, const char * reason); +PEERBool piRoomToType(PEER peer, const char * room, RoomType * roomType); +void piSetLocalFlags(PEER peer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerSB.c b/code/gamespy/Peer/peerSB.c new file mode 100644 index 00000000..cb5a1b75 --- /dev/null +++ b/code/gamespy/Peer/peerSB.c @@ -0,0 +1,1112 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/************* +** INCLUDES ** +*************/ +#include +#include +#include +#include "peerMain.h" +#include "peerOperations.h" +#include "peerSB.h" +#include "peerCallbacks.h" +#include "peerMangle.h" +#include "peerRooms.h" +#include "peerAutoMatch.h" +#include "peerQR.h" + +/************ +** GLOBALS ** +************/ +int piSBQueryVersion = QVERSION_QR2; + +/********** +** GAMES ** +**********/ +static void piSBGamesListCallback +( + SBServerListPtr serverlist, + SBListCallbackReason reason, + SBServer server, + void * instance +) +{ + PEER peer = (PEER)instance; + PEER_CONNECTION; + +#if 0 + { + static const char reasonStrings[][32] = + { + "serveradded", + "serverupdated", + "serverdeleted", + "initiallistcomplete", + "disconnected", + "queryerror", + "publicipdetermined" + }; + char buffer[256]; + sprintf(buffer, "GAMES | LIST | %s", reasonStrings[reason]); + if(server) + sprintf(buffer + strlen(buffer), " | %s:%d", SBServerGetPublicAddress(server), SBServerGetPublicQueryPort(server)); + strcat(buffer, "\n"); + OutputDebugString(buffer); + } +#endif + + // handle based on the callback reason + switch(reason) + { + case slc_serveradded: + // if it's added call the callback and get it updated + piAddListingGamesCallback(peer, PEERTrue, server, PEER_ADD); + if(!SBServerHasBasicKeys(server)) + { + SBBool usequerychallenge = SBFalse; + if (serverlist->backendgameflags & QR2_USE_QUERY_CHALLENGE) + usequerychallenge = SBTrue; + SBQueryEngineUpdateServer(&connection->gameEngine, server, 0, QTYPE_BASIC, usequerychallenge); + } + break; + + case slc_serverupdated: + // if it's updated let the app know + piAddListingGamesCallback(peer, PEERTrue, server, PEER_UPDATE); + break; + + case slc_serverdeleted: + // if it's deleted, call the callback + if ((server->state & (STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY|STATE_PENDINGICMPQUERY)) != 0) + SBQueryEngineRemoveServerFromFIFOs(&connection->gameEngine, server); + piAddListingGamesCallback(peer, PEERTrue, server, PEER_REMOVE); + break; + + case slc_initiallistcomplete: + // done with the initial list + connection->initialGameList = PEERFalse; + + // let the app know + piAddListingGamesCallback(peer, PEERTrue, NULL, PEER_COMPLETE); + + break; + + case slc_queryerror: + // call the failed callback + piAddListingGamesCallback(peer, PEERFalse, NULL, 0); + break; + + case slc_publicipdetermined: + // let the engine know the IP + connection->publicIP = serverlist->mypublicip; + SBQueryEngineSetPublicIP(&connection->gameEngine, serverlist->mypublicip); + break; + + default: + break; + } +} + +static void piSBGamesEngineCallback +( + SBQueryEnginePtr engine, + SBQueryEngineCallbackReason reason, + SBServer server, + void * instance +) +{ + PEER peer = (PEER)instance; + //PEER_CONNECTION; + +#if 0 + { + static const char reasonStrings[][32] = + { + "updatesuccess", + "updatefailed", + "engineidle" + }; + char buffer[256]; + sprintf(buffer, "GAMES | ENGINE | %s", reasonStrings[reason]); + if(server) + sprintf(buffer + strlen(buffer), " | %s:%d", SBServerGetPublicAddress(server), SBServerGetPublicQueryPort(server)); + strcat(buffer, "\n"); + OutputDebugString(buffer); + } +#endif + + // if this is an updated server, call the callback + if(reason == qe_updatesuccess) + piAddListingGamesCallback(peer, PEERTrue, server, PEER_UPDATE); + + GSI_UNUSED(engine); +} + +PEERBool piSBStartListingGames +( + PEER peer, + const unsigned char * fields, + int numFields, + const char * filter +) +{ + char groupFilter[32]; + char smartSpyFilter[MAX_FILTER_LEN]; + char fullFields[MAX_FIELD_LIST_LEN]; + int listLen; + int keyLen; + int i; + + PEER_CONNECTION; + + // check that we're initialized. + assert(connection->sbInitialized); + if(!connection->sbInitialized) + return PEERFalse; + + // Stop it if it's going. + ///////////////////////// + piSBStopListingGames(peer); + + // Clear the list. + ////////////////// + SBServerListClear(&connection->gameList); + + // Filter by group if in one. + ///////////////////////////// + if(connection->groupID) + sprintf(groupFilter, "groupid=%d", connection->groupID); + else + strcpy(groupFilter, "groupid is null"); + + // Setup the actual filter. + /////////////////////////// + if(filter) + { + sprintf(smartSpyFilter, "(%s) AND (", groupFilter); + strzcat(smartSpyFilter, filter, sizeof(smartSpyFilter) - 1); + strcat(smartSpyFilter, ")"); + } + else + { + strcpy(smartSpyFilter, groupFilter); + } + + // Clear this in case it was set. + ///////////////////////////////// + connection->gameEngine.numserverkeys = 0; + + // We always set these keys. + //////////////////////////// + strcpy(fullFields, "\\hostname\\gamemode"); + listLen = (int)strlen(fullFields); + SBQueryEngineAddQueryKey(&connection->gameEngine, HOSTNAME_KEY); + SBQueryEngineAddQueryKey(&connection->gameEngine, GAMEMODE_KEY); + + // Build the key list. + ////////////////////// + for(i = 0 ; i < numFields ; i++) + { + // Check that we have the space to add the key. + /////////////////////////////////////////////// + keyLen = (int)strlen(qr2_registered_key_list[fields[i]]); + if((listLen + keyLen + 1) >= MAX_FIELD_LIST_LEN) + break; + + // Add the key. + /////////////// + listLen += sprintf(fullFields + listLen, "\\%s", qr2_registered_key_list[fields[i]]); + + // Add to the engine query list. + //////////////////////////////// + SBQueryEngineAddQueryKey(&connection->gameEngine, fields[i]); + } + + // Start updating the list. + /////////////////////////// + if(SBServerListConnectAndQuery(&connection->gameList, fullFields, smartSpyFilter, PUSH_UPDATES, 0) != sbe_noerror) + { + piSBStopListingGames(peer); + return PEERFalse; + } + + // Doing the initial listing. + ///////////////////////////// + connection->initialGameList = PEERTrue; + + // Clear the list before they start getting servers. + //////////////////////////////////////////////////// + piAddListingGamesCallback(peer, PEERTrue, NULL, PEER_CLEAR); + + return PEERTrue; +} + +void piSBStopListingGames +( + PEER peer +) +{ + PEER_CONNECTION; + + // check that we're initialized. + assert(connection->sbInitialized); + if(!connection->sbInitialized) + return; + + // Stop the listing. + //////////////////// + SBServerListDisconnect(&connection->gameList); + + // Stop the engine. + /////////////////// + SBEngineHaltUpdates(&connection->gameEngine); + + // Remove all pending game list callbacks. + ////////////////////////////////////////// + piClearCallbacks(peer, PI_LISTING_GAMES_CALLBACK); +} + +/*********** +** GROUPS ** +***********/ +static void piSBGroupsListCallback +( + SBServerListPtr serverlist, + SBListCallbackReason reason, + SBServer server, + void * instance +) +{ + piOperation * operation; + PEER peer = (PEER)instance; + PEER_CONNECTION; + + operation = connection->listingGroupsOperation; + +#if 0 + { + static const char reasonStrings[][32] = + { + "serveradded", + "serverupdated", + "serverdeleted", + "initiallistcomplete", + "disconnected", + "queryerror", + "publicipdetermined" + }; + char buffer[256]; + sprintf(buffer, "GROUPS | LIST | %s", reasonStrings[reason]); + if(server) + sprintf(buffer + strlen(buffer), " | %d", ntohl(SBServerGetPublicInetAddress(server))); + strcat(buffer, "\n"); + OutputDebugString(buffer); + } +#endif + + // handle based on the callback reason + switch(reason) + { + // if it's added call the callback + case slc_serveradded: + { + int groupID = (int)ntohl(SBServerGetPublicInetAddress(server)); +//#ifndef GSI_PEER_UNICODE + const char * name = SBServerGetStringValueA(server, "hostname", "(No Name)"); + int numWaiting = SBServerGetIntValueA(server, "numwaiting", 0); + int maxWaiting = SBServerGetIntValueA(server, "maxwaiting", 0); + int numGames = SBServerGetIntValueA(server, "numservers", 0); + int numPlaying = SBServerGetIntValueA(server, "numplayers", 0); +//#else +// const unsigned short * name = SBServerGetStringValue(server, L"hostname", L"(No Name)"); +// int numWaiting = SBServerGetIntValue(server, L"numwaiting", 0); +// int maxWaiting = SBServerGetIntValue(server, L"maxwaiting", 0); +// int numGames = SBServerGetIntValue(server, L"numservers", 0); +// int numPlaying = SBServerGetIntValue(server, L"numplayers", 0); +//#endif + piAddListGroupRoomsCallback(peer, PEERTrue, groupID, server, name, numWaiting, maxWaiting, numGames, numPlaying, (peerListGroupRoomsCallback)operation->callback, operation->callbackParam, operation->ID); + break; + } + + // initial list completion + case slc_initiallistcomplete: + // calling the terminating callback + piAddListGroupRoomsCallback(peer, PEERTrue, 0, NULL, NULL, 0, 0, 0, 0, (peerListGroupRoomsCallback)operation->callback, operation->callbackParam, operation->ID); + piRemoveOperation(peer, operation); + connection->listingGroupsOperation = NULL; + break; + + // check for a query error + case slc_queryerror: + // call the failed callback + piAddListGroupRoomsCallback(peer, PEERFalse, 0, NULL, NULL, 0, 0, 0, 0, (peerListGroupRoomsCallback)operation->callback, operation->callbackParam, operation->ID); + piRemoveOperation(peer, operation); + connection->listingGroupsOperation = NULL; + break; + + default: + break; + } + + GSI_UNUSED(serverlist); +} + +PEERBool piSBStartListingGroups +( + PEER peer, + const char * fields +) +{ + char fullFields[MAX_FIELD_LIST_LEN + 1]; + int len; + + PEER_CONNECTION; + + // check that we're initialized. + assert(connection->sbInitialized); + if(!connection->sbInitialized) + return PEERFalse; + + // Stop it if it's going. + ///////////////////////// + piSBStopListingGroups(peer); + + // Clear the list. + ////////////////// + SBServerListClear(&connection->groupList); + + // Setup the fields list. + ///////////////////////// + strcpy(fullFields, "\\hostname\\numwaiting\\maxwaiting\\numservers\\numplayers"); + len = (int)strlen(fullFields); + strzcpy(fullFields + len, fields, (unsigned int)MAX_FIELD_LIST_LEN - len); + + // Start updating the list. + /////////////////////////// + if(SBServerListConnectAndQuery(&connection->groupList, fullFields, "", SEND_GROUPS, 0) != sbe_noerror) + { + piSBStopListingGroups(peer); + return PEERFalse; + } + + return PEERTrue; +} + +void piSBStopListingGroups +( + PEER peer +) +{ + PEER_CONNECTION; + + // check that we're initialized. + assert(connection->sbInitialized); + if(!connection->sbInitialized) + return; + + // Stop the listing. + //////////////////// + SBServerListDisconnect(&connection->groupList); +} + +/************** +** AUTOMATCH ** +**************/ +static PEERBool piIsLocalServer(PEER peer, SBServer server) +{ + PEER_CONNECTION; + + // check the public ip + if(SBServerGetPublicInetAddress(server) != connection->publicIP) + return PEERFalse; + + // check the private ip and port + if(SBServerHasPrivateAddress(server)) + { + if(SBServerGetPrivateInetAddress(server) != connection->privateIP) + return PEERFalse; + + if(SBServerGetPrivateQueryPort(server) != connection->autoMatchOperation->port) + return PEERFalse; + } + // check the public port + else + { + if(SBServerGetPublicQueryPort(server) != connection->autoMatchOperation->port) + return PEERFalse; + } + + return PEERTrue; +} + +static int piSBAutoMatchGetServerRating(PEER peer, SBServer server) +{ + PEER_CONNECTION; + + // ignore the local machine + if(connection->autoMatchReporting && piIsLocalServer(peer, server)) + return 0; + + // check if it got the keys. + if(!SBServerHasFullKeys(server)) + return 0; + + // ignore full servers +#ifndef GSI_UNICODE + if(SBServerGetIntValue(server, "numplayers", 0) >= SBServerGetIntValue(server, "maxplayers", 0)) + return 0; +#else + if(SBServerGetIntValue(server, L"numplayers", 0) >= SBServerGetIntValue(server, L"maxplayers", 0)) + return 0; +#endif + + // return the rating for this server + return piCallAutoMatchRateCallback(peer, server); +} + +static void piSBAutoMatchCheckUpdatedServer(PEER peer, SBServer server) +{ + int rating; + + PEER_CONNECTION; + + // only do this if we're waiting + if(connection->autoMatchStatus != PEERWaiting) + return; + + // if we're already joining a staging room, ignore this + if(connection->enteringRoom[StagingRoom]) + return; + + // get the rating for this server + rating = piSBAutoMatchGetServerRating(peer, server); + + // if it's not acceptable, ignore + if(rating <= 0) + return; + + // stop reporting and leave the room + piStopAutoMatchReporting(peer); + piLeaveRoom(peer, StagingRoom, ""); + + // join the match + if(!piJoinAutoMatchRoom(peer, server)) + { + // it's bad news if it failed to start the op + piSetAutoMatchStatus(peer, PEERFailed); + } +} + +static void piSBAutoMatchCheckPushedServer(PEER peer, SBServer server) +{ + SortInfo info; + int aRating; + PEER_CONNECTION; + + // We must be in the waiting or searching phase to rate a server this way + if(connection->autoMatchStatus != PEERWaiting && connection->autoMatchStatus != PEERSearching) + return; + + // if we're already joining a staging room, ignore this + if(connection->enteringRoom[StagingRoom]) + return; + + // Check if this is the only server in our list + if (SBServerListCount(&connection->autoMatchList) == 1) + { + // get the rating for this server + aRating = piSBAutoMatchGetServerRating(peer, server); + + // if it's not acceptable, ignore + if(aRating <= 0) + return; + + // stop reporting and leave the room + piStopAutoMatchReporting(peer); + piLeaveRoom(peer, StagingRoom, ""); + + // join the match + if(!piJoinAutoMatchRoom(peer, server)) + { + // it's bad news if it failed to start the op + piSetAutoMatchStatus(peer, PEERFailed); + } + } + + // otherwise we'll need to run through all the servers + // rate each of them and join the one with the highest rating. + else if (SBServerListCount(&connection->autoMatchList) > 1) + { + int count, i; + SBServer aServer; + + // loop through all the servers. + count = SBServerListCount(&connection->autoMatchList); + for(i = (count - 1) ; i >= 0 ; i--) + { + // get the server. + aServer = SBServerListNth(&connection->autoMatchList, i); + + // get the rating for this server + aRating = piSBAutoMatchGetServerRating(peer, aServer); + if(aRating <= 0) + { + SBServerListRemoveAt(&connection->autoMatchList, i); + continue; + } + + // store this server's rating + SBServerAddIntKeyValue(aServer, PI_AUTOMATCH_RATING_KEY, aRating); + } + + // recount + count = SBServerListCount(&connection->autoMatchList); + + // no matches? + if(!count) + { + piSetAutoMatchStatus(peer, PEERWaiting); + return; + } + + // sort by rating +#ifdef GSI_UNICODE + _tcscpy(info.sortkey, (const unsigned short *)PI_AUTOMATCH_RATING_KEY); +#else + _tcscpy(info.sortkey, PI_AUTOMATCH_RATING_KEY); +#endif + info.comparemode = sbcm_int; + SBServerListSort(&connection->autoMatchList, SBTrue, info); + + piStopAutoMatchReporting(peer); + piLeaveRoom(peer, StagingRoom, ""); + + // join the top rated match + if(!piJoinAutoMatchRoom(peer, SBServerListNth(&connection->autoMatchList, 0))) + { + // it's bad news if it failed to start the op + piSetAutoMatchStatus(peer, PEERFailed); + } + } + +} + +static void piSBAutoMatchListCallback +( + SBServerListPtr serverlist, + SBListCallbackReason reason, + SBServer server, + void * instance +) +{ + PEER peer = (PEER)instance; + PEER_CONNECTION; + +#if 0 + { + static const char reasonStrings[][32] = + { + "serveradded", + "serverupdated", + "serverdeleted", + "initiallistcomplete", + "disconnected", + "queryerror", + "publicipdetermined" + }; + char buffer[256]; + sprintf(buffer, "AUTOMATCH | LIST | %s", reasonStrings[reason]); + if(server) + sprintf(buffer + strlen(buffer), " | %s:%d", SBServerGetPublicAddress(server), SBServerGetPublicQueryPort(server)); + strcat(buffer, "\n"); + OutputDebugString(buffer); + } +#endif + + // handle based on the callback reason + switch(reason) + { + case slc_serveradded: + // If we have the server info, or we already requested it + // just bail + if(server->state & (STATE_FULLKEYS|STATE_PENDINGFULLQUERY)) + break; + + // If it's behind a firewall, get the keys from the master + if (!SBServerDirectConnect(server)) + SBGetServerRulesFromMaster(&connection->autoMatchList, SBServerGetPublicInetAddress(server), SBServerGetPublicQueryPortNBO(server)); + else // send normal update + { + SBBool usequerychallenge = SBFalse; + if (serverlist->backendgameflags & QR2_USE_QUERY_CHALLENGE) + usequerychallenge = SBTrue; + SBQueryEngineUpdateServer(&connection->autoMatchEngine, server, 0, QTYPE_FULL, usequerychallenge); + } + break; + + case slc_serverupdated: + // If we don't have the full keys, request them + if(!SBServerHasFullKeys(server) && SBServerDirectConnect(server) && !(server->state & STATE_PENDINGFULLQUERY)) + { + // Send a normal query, if the server is firewalled we wouldn't be here + // assert(SBServerDirectConnect(server)) + SBBool usequerychallenge = SBFalse; + if (serverlist->backendgameflags & QR2_USE_QUERY_CHALLENGE) + usequerychallenge = SBTrue; + SBQueryEngineUpdateServer(&connection->autoMatchEngine, server, 0, QTYPE_FULL, usequerychallenge); + } + else if(!SBServerHasFullKeys(server) && !SBServerDirectConnect(server)) + { + SBGetServerRulesFromMaster(&connection->autoMatchList, SBServerGetPublicInetAddress(server), SBServerGetPublicQueryPortNBO(server)); + } + else + piSBAutoMatchCheckPushedServer(peer, server); + break; + + case slc_serverdeleted: + // make sure it's not in the update queue + if ((server->state & (STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY)) != 0) + SBQueryEngineRemoveServerFromFIFOs(&connection->autoMatchEngine, server); + break; + + case slc_initiallistcomplete: + // if no servers were found, start Waiting + // or if servers were found but all were behind a firewall, start Waiting + if(!SBServerListCount(&connection->autoMatchList) || + (0==connection->autoMatchEngine.querylist.count)) + { + piSetAutoMatchStatus(peer, PEERWaiting); + } + break; + case slc_queryerror: + // browsing failed + connection->autoMatchSBFailed = PEERTrue; + if(connection->autoMatchStatus == PEERSearching) + piSetAutoMatchStatus(peer, connection->autoMatchQRFailed?PEERFailed:PEERWaiting); + break; + + case slc_publicipdetermined: + // let the engine know the IP + connection->publicIP = serverlist->mypublicip; + SBQueryEngineSetPublicIP(&connection->gameEngine, serverlist->mypublicip); + break; + + default: + break; + } +} + +static void piSBAutoMatchEngineCallback +( + SBQueryEnginePtr engine, + SBQueryEngineCallbackReason reason, + SBServer server, + void * instance +) +{ + PEER peer = (PEER)instance; + int i; + int count; + int rating; + SortInfo info; + + PEER_CONNECTION; + +#if 0 + { + static const char reasonStrings[][32] = + { + "updatesuccess", + "updatefailed", + "engineidle" + }; + char buffer[256]; + sprintf(buffer, "AUTOMATCH | ENGINE | %s", reasonStrings[reason]); + if(server) + sprintf(buffer + strlen(buffer), " | %s:%d", SBServerGetPublicAddress(server), SBServerGetPublicQueryPort(server)); + strcat(buffer, "\n"); + OutputDebugString(buffer); + } +#endif + + // handle based on the callback reason + switch(reason) + { + case qe_updatesuccess: + // check the server + piSBAutoMatchCheckUpdatedServer(peer, server); + break; + + case qe_updatefailed: + if(!SBServerListCount(&connection->autoMatchList)) + piSetAutoMatchStatus(peer, PEERWaiting); + break; + + // check if the querying is complete. + case qe_engineidle: + // make sure we're searching + if(connection->autoMatchStatus != PEERSearching) + return; + + // if we're already in or joining a staging room, ignore this + if(connection->inRoom[StagingRoom] || connection->enteringRoom[StagingRoom]) + return; + + // loop through all the servers. + count = SBServerListCount(&connection->autoMatchList); + for(i = (count - 1) ; i >= 0 ; i--) + { + // get the server. + server = SBServerListNth(&connection->autoMatchList, i); + + // get the rating for this server + rating = piSBAutoMatchGetServerRating(peer, server); + if(rating <= 0) + { + SBServerListRemoveAt(&connection->autoMatchList, i); + continue; + } + + // store this server's rating + SBServerAddIntKeyValue(server, PI_AUTOMATCH_RATING_KEY, rating); + } + + // recount + count = SBServerListCount(&connection->autoMatchList); + + // no matches? + if(!count) + { + piSetAutoMatchStatus(peer, PEERWaiting); + return; + } + + // sort by rating +#ifdef GSI_UNICODE + _tcscpy(info.sortkey, (const unsigned short *)PI_AUTOMATCH_RATING_KEY); +#else + _tcscpy(info.sortkey, PI_AUTOMATCH_RATING_KEY); +#endif + + info.comparemode = sbcm_int; + SBServerListSort(&connection->autoMatchList, SBTrue, info); + + // join the top rated match + if(!piJoinAutoMatchRoom(peer, SBServerListNth(&connection->autoMatchList, 0))) + { + // it's bad news if it failed to start the op + piSetAutoMatchStatus(peer, PEERFailed); + } + + break; + + default: + break; + } + + GSI_UNUSED(engine); +} + +PEERBool piSBStartListingAutoMatches +( + PEER peer +) +{ + PEER_CONNECTION; + + // check that we're initialized. + assert(connection->sbInitialized); + if(!connection->sbInitialized) + { + connection->autoMatchSBFailed = PEERTrue; + return PEERFalse; + } + + // stop it if it's going. + piSBStopListingAutoMatches(peer); + + // clear the list. + SBServerListClear(&connection->autoMatchList); + + // start updating the list. + if(SBServerListConnectAndQuery(&connection->autoMatchList, NULL, connection->autoMatchFilter, PUSH_UPDATES, 0) != sbe_noerror) + { + piSBStopListingAutoMatches(peer); + connection->autoMatchSBFailed = PEERTrue; + return PEERFalse; + } + + // clear the error flag + connection->autoMatchSBFailed = PEERFalse; + + // we're browsing + connection->autoMatchBrowsing = PEERTrue; + + return PEERTrue; +} + +void piSBStopListingAutoMatches +( + PEER peer +) +{ + PEER_CONNECTION; + + // check that we're initialized. + assert(connection->sbInitialized); + if(!connection->sbInitialized) + return; + + // we're done browsing + connection->autoMatchBrowsing = PEERFalse; + + // stop the listing. + SBServerListDisconnect(&connection->autoMatchList); + + // stop the engine. + SBEngineHaltUpdates(&connection->autoMatchEngine); +} + +/************** +** FUNCTIONS ** +**************/ +PEERBool piSBInit +( + PEER peer +) +{ + char autoMatchTitle[PI_TITLE_MAX_LEN]; + + PEER_CONNECTION; + + // init the game list and engine + memset(&connection->gameList, 0, sizeof(connection->gameList)); + SBServerListInit(&connection->gameList, connection->title, connection->sbName, connection->sbSecretKey, connection->sbGameVersion, SBFalse, piSBGamesListCallback, peer); + SBQueryEngineInit(&connection->gameEngine, connection->sbMaxUpdates, piSBQueryVersion, SBFalse, piSBGamesEngineCallback, peer); + + // init the group list + memset(&connection->groupList, 0, sizeof(connection->groupList)); + SBServerListInit(&connection->groupList, connection->title, connection->sbName, connection->sbSecretKey, connection->sbGameVersion, SBFalse, piSBGroupsListCallback, peer); + + // init the AutoMatch list and engine + strzcpy(autoMatchTitle, connection->title, sizeof(autoMatchTitle)); + strzcat(autoMatchTitle, "am", sizeof(autoMatchTitle)); + memset(&connection->autoMatchList, 0, sizeof(connection->autoMatchList)); + SBServerListInit(&connection->autoMatchList, autoMatchTitle, connection->sbName, connection->sbSecretKey, connection->sbGameVersion, SBFalse, piSBAutoMatchListCallback, peer); + SBQueryEngineInit(&connection->autoMatchEngine, connection->sbMaxUpdates, piSBQueryVersion, SBFalse, piSBAutoMatchEngineCallback, peer); + + // no host server yet + connection->hostServer = NULL; + + // initialized + connection->sbInitialized = PEERTrue; + + return PEERTrue; +} + +void piSBCleanup +( + PEER peer +) +{ + PEER_CONNECTION; + + // stop listing + piSBStopListingGames(peer); + piSBStopListingGroups(peer); + + // cleanup the SB + if(connection->sbInitialized) + { + SBServerListCleanup(&connection->gameList); + SBEngineCleanup(&connection->gameEngine); + SBServerListCleanup(&connection->groupList); + SBServerListCleanup(&connection->autoMatchList); + SBEngineCleanup(&connection->autoMatchEngine); + } + + // AutoMatch cleanup + connection->autoMatchBrowsing = PEERFalse; +} + + +void piSBUpdateGame +( + PEER peer, + SBServer server, + PEERBool fullUpdate, + PEERBool forceUpdateByMaster, + PEERBool icmpEcho +) +{ + PEER_CONNECTION; + + // check that we're initialized. + assert(connection->sbInitialized); + + if(!connection->sbInitialized) + return; + + // Changed 08-26-2004 + // Saad Nader + // Added force update by master server parameter + // for internal update function + ///////////////////// + if (forceUpdateByMaster) + { + // remove from the existing update lists if present + if(server->flags & UNSOLICITED_UDP_FLAG) + SBQueryEngineRemoveServerFromFIFOs(&connection->gameEngine, server); + + // Obtain the information via the master server + SBGetServerRulesFromMaster(&connection->gameList, server->publicip, server->publicport); + return; + } + + if (icmpEcho && (server->flags & ICMP_IP_FLAG)) + { + SBBool usequerychallenge = SBFalse; + if (connection->gameList.backendgameflags & QR2_USE_QUERY_CHALLENGE) + usequerychallenge = SBTrue; + + if (server->flags & UNSOLICITED_UDP_FLAG) + SBQueryEngineRemoveServerFromFIFOs(&connection->gameEngine, server); + + SBQueryEngineUpdateServer(&connection->gameEngine, server, 1, QTYPE_ICMP, usequerychallenge); + return; + } + // if the server supports unsolicited UDP, query it directly + if(server->flags & UNSOLICITED_UDP_FLAG) + { + SBBool usequerychallenge = SBFalse; + if (connection->gameList.backendgameflags & QR2_USE_QUERY_CHALLENGE) + usequerychallenge = SBTrue; + + // remove from the existing update lists if present + SBQueryEngineRemoveServerFromFIFOs(&connection->gameEngine, server); + + // query the server + SBQueryEngineUpdateServer(&connection->gameEngine, server, 1, fullUpdate?QTYPE_FULL:QTYPE_BASIC, usequerychallenge); + } + else + { + // get the values from the master server + SBGetServerRulesFromMaster(&connection->gameList, server->publicip, server->publicport); + } +} + +void piSBThink +( + PEER peer +) +{ + PEER_CONNECTION; + + // check that we're initialized. + if(!connection->sbInitialized) + return; + + SBListThink(&connection->gameList); + SBListThink(&connection->groupList); + SBQueryEngineThink(&connection->gameEngine); + SBListThink(&connection->autoMatchList); + SBQueryEngineThink(&connection->autoMatchEngine); +} + +void piSendNatNegotiateCookie +( + PEER peer, + unsigned int ip, + unsigned short port, + int cookie +) +{ + PEER_CONNECTION; + + // check that we're initialized. + if(!connection->sbInitialized) + return; + + // send the cookie + if (peerIsAutoMatching(peer)) + { + SBSendNatNegotiateCookieToServer(&connection->autoMatchList, ip, htons(port), cookie); + } + else + SBSendNatNegotiateCookieToServer(&connection->gameList, ip, htons(port), cookie); +} + +void piSendMessageToServer +( + PEER peer, + unsigned int ip, + unsigned short port, + const char * data, + int len +) +{ + PEER_CONNECTION; + + // check that we're initialized. + if(!connection->sbInitialized) + return; + + // send the message + if(peerIsAutoMatching(peer)) + { + SBSendMessageToServer(&connection->autoMatchList, ip, port, data, len); + } + else + { + SBSendMessageToServer(&connection->gameList, ip, port, data, len); + } +} + +static void piSBCloneServerTableMap(void * elem, void * clientData) +{ + SBKeyValuePair * kvPair = (SBKeyValuePair *)elem; + SBServer clone = (SBServer)clientData; + + SBServerAddKeyValue(clone, kvPair->key, kvPair->value); +} + +SBServer piSBCloneServer(SBServer server) +{ + SBServer clone; + HashTable table; + + // allocate the clone + clone = SBAllocServer(NULL, server->publicip, server->publicport); + + // save off the table + table = clone->keyvals; + + // copy the source server + memcpy(clone, server, sizeof(struct _SBServer)); + clone->keyvals = table; + clone->next = NULL; + + // copy all the values from the original table + TableMap(server->keyvals, piSBCloneServerTableMap, clone); + + return clone; +} + +void piSBFreeHostServer(PEER peer) +{ + PEER_CONNECTION; + + if(!connection->hostServer) + return; + + SBServerFree(&connection->hostServer); + connection->hostServer = NULL; +} diff --git a/code/gamespy/Peer/peerSB.h b/code/gamespy/Peer/peerSB.h new file mode 100644 index 00000000..ed97057c --- /dev/null +++ b/code/gamespy/Peer/peerSB.h @@ -0,0 +1,46 @@ +/* +GameSpy Peer SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _PEERSB_H_ +#define _PEERSB_H_ + +/************* +** INCLUDES ** +*************/ +#include "peerMain.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/************** +** FUNCTIONS ** +**************/ +PEERBool piSBInit(PEER peer); +void piSBCleanup(PEER peer); +PEERBool piSBStartListingGames(PEER peer, const unsigned char * fields, int numFields, const char * filter); +void piSBStopListingGames(PEER peer); +void piSBUpdateGame(PEER peer, SBServer server, PEERBool fullUpdate, PEERBool forceUpdateByMaster, PEERBool icmpEcho); +PEERBool piSBStartListingGroups(PEER peer, const char * fields); +void piSBStopListingGroups(PEER peer); +void piSBThink(PEER peer); +void piSendNatNegotiateCookie(PEER peer, unsigned int ip, unsigned short port, int cookie); +void piSendMessageToServer(PEER peer, unsigned int ip, unsigned short port, const char * data, int len); +PEERBool piSBStartListingAutoMatches(PEER peer); +void piSBStopListingAutoMatches(PEER peer); +SBServer piSBCloneServer(SBServer server); +void piSBFreeHostServer(PEER peer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/Peer/peerc/peerc.c b/code/gamespy/Peer/peerc/peerc.c new file mode 100644 index 00000000..46ca28a2 --- /dev/null +++ b/code/gamespy/Peer/peerc/peerc.c @@ -0,0 +1,773 @@ +// GameSpy Peer SDK C Test App +// Dan "Mr. Pants" Schoenblum +// dan@gamespy.com + +/********* +INCLUDES +*********/ +#include "../peer.h" +#include "../../common/gsStringUtil.h" +#include "../../common/gsAvailable.h" + +/******** +DEFINES +********/ +#define TITLE _T("gmtest") + +// ensure cross-platform compatibility for printf +#ifdef UNDER_CE + void RetailOutputA(CHAR *tszErr, ...); + #define printf RetailOutputA +#elif defined(_NITRO) + #include "../../common/nitro/screen.h" + #define printf Printf + #define vprintf VPrintf +#endif + +#define LOOP_SECONDS 60 + +#define NICK_SIZE 32 // size of nick array, below + +/************* +GLOBAL VARS +*************/ +gsi_char nick[NICK_SIZE]; // our nickname for peerConnect + +// RoomType to string +gsi_char RtoS[3][16] = +{ + _T("TitleRoom"), + _T("GroupRoom"), + _T("StagingRoom") +}; + +PEERBool groupRoomCallbackDone = PEERFalse; + + +static const gsi_char * ResultToString (PEERJoinResult result) +{ + switch(result) + { + case PEERJoinSuccess: + return _T("Success"); + case PEERFullRoom: + return _T("Full"); + case PEERInviteOnlyRoom: + return _T("Invite Only"); + case PEERBannedFromRoom: + return _T("Banned"); + case PEERBadPassword: + return _T("Bad Password"); + case PEERAlreadyInRoom: + return _T("Already in room"); + case PEERNoTitleSet: + return _T("No Title"); + case PEERNoConnection: + return _T("No Connection"); + case PEERAutoMatching: + return _T("Auto Matching"); + case PEERJoinFailed: + return _T("Join Failed"); + } + + //ASSERT(0); + return _T(""); +} + +static void JoinRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + void * param + ) +{ + if(!success) + { + _tprintf(_T("Join failure: %s"), ResultToString(result)); + return; + } + else + { + _tprintf(_T("Joined %s.\n"), RtoS[roomType]); + /*dlg->FillPlayerList(roomType); + if(roomType == StagingRoom) + { + dlg->m_name = peerGetRoomName(peer, StagingRoom); + dlg->UpdateData(FALSE); + }*/ + } + + GSI_UNUSED(param); + GSI_UNUSED(roomType); + GSI_UNUSED(peer); +} + +static void CreateStagingRoomCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + void * param + ) +{ + if (success) + _tprintf(_T("Staging Room Created\n")); + + GSI_UNUSED(param); + GSI_UNUSED(roomType); + GSI_UNUSED(result); + GSI_UNUSED(peer); +} + +static void DisconnectedCallback +( + PEER peer, + const gsi_char * reason, + void * param +) +{ + _tprintf(_T("Disconnected: %s\n"), reason); + + GSI_UNUSED(peer); + GSI_UNUSED(param); +} + +static void RoomMessageCallback +( + PEER peer, + RoomType roomType, + const gsi_char * nick, + const gsi_char * message, + MessageType messageType, + void * param +) +{ + _tprintf(_T(" (%s) %s: %s\n"), RtoS[roomType], nick, message); + //if(strcasecmp(message, _T("quit")) == 0) + // stop = PEERTrue; + //else if((strlen(message) > 5) && (strncasecmp(message, _T("echo"), 4) == 0)) + // peerMessageRoom(peer, roomType, message + 5, messageType); + + GSI_UNUSED(param); + GSI_UNUSED(peer); + GSI_UNUSED(messageType); +} + +static void RoomUTMCallback +( + PEER peer, + RoomType roomType, + const gsi_char * nick, + const gsi_char * command, + const gsi_char * parameters, + PEERBool authenticated, + void * param +) +{ + GSI_UNUSED(peer); + GSI_UNUSED(roomType); + GSI_UNUSED(nick); + GSI_UNUSED(command); + GSI_UNUSED(parameters); + GSI_UNUSED(authenticated); + GSI_UNUSED(param); +} + +static void PlayerMessageCallback +( + PEER peer, + const gsi_char * nick, + const gsi_char * message, + MessageType messageType, + void * param +) +{ + _tprintf(_T("(PRIVATE) %s: %s\n"), nick, message); + + GSI_UNUSED(peer); + GSI_UNUSED(messageType); + GSI_UNUSED(param); +} + +static void ReadyChangedCallback +( + PEER peer, + const gsi_char * nick, + PEERBool ready, + void * param +) +{ + if(ready) + _tprintf(_T("%s is ready\n"), nick); + else + _tprintf(_T("%s is not ready\n"), nick); + + GSI_UNUSED(peer); + GSI_UNUSED(param); +} + +static void GameStartedCallback +( + PEER peer, + SBServer server, + const gsi_char * message, + void * param +) +{ + _tprintf(_T("The game is starting at %s\n"), SBServerGetPublicAddress(server)); + if(message && message[0]) + _tprintf(_T(": %s\n"), message); + else + _tprintf(_T("\n")); + + GSI_UNUSED(peer); + GSI_UNUSED(param); +} + +static void PlayerJoinedCallback +( + PEER peer, + RoomType roomType, + const gsi_char * nick, + void * param +) +{ + _tprintf(_T("%s joined the %s\n"), nick, RtoS[roomType]); + + GSI_UNUSED(peer); + GSI_UNUSED(param); +} + +static void PlayerLeftCallback +( + PEER peer, + RoomType roomType, + const gsi_char * nick, + const gsi_char * reason, + void * param +) +{ + _tprintf(_T("%s left the %s\n"), nick, RtoS[roomType]); + + GSI_UNUSED(peer); + GSI_UNUSED(param); + GSI_UNUSED(reason); +} + +static void PlayerChangedNickCallback +( + PEER peer, + RoomType roomType, + const gsi_char * oldNick, + const gsi_char * newNick, + void * param +) +{ + _tprintf(_T("%s in %s changed nicks to %s\n"), oldNick, RtoS[roomType], newNick); + + GSI_UNUSED(peer); + GSI_UNUSED(param); +} + +PEERBool connectSuccess; +static void ConnectCallback +( + PEER peer, + PEERBool success, + int failureReason, + void * param +) +{ + connectSuccess = success; + + GSI_UNUSED(peer); + GSI_UNUSED(success); + GSI_UNUSED(failureReason); + GSI_UNUSED(param); +} + +static void EnumPlayersCallback +( + PEER peer, + PEERBool success, + RoomType roomType, + int index, + const gsi_char * nick, + int flags, + void * param +) +{ + if(!success) + { + _tprintf(_T("Enum %s players failed\n"), RtoS[roomType]); + return; + } + + if(index == -1) + { + _tprintf(_T("--------------------\n")); + return; + } + + _tprintf(_T("%d: %s\n"), index, nick); + /*if(flags & PEER_FLAG_OP) + _tprintf(_T(" (host)\n")); + else + _tprintf(_T("\n"));*/ + + + GSI_UNUSED(peer); + GSI_UNUSED(param); + GSI_UNUSED(flags); +} +/* +int gStartListing; +static void ListingGamesCallback +( + PEER peer, + PEERBool success, + const gsi_char * name, + SBServer server, + PEERBool staging, + int msg, + int progress, + void * param +) +{ + if(success) + { + gsi_char *msgname; + switch (msg) + { + case PEER_CLEAR: + msgname = _T("PEER_CLEAR"); + break; + case PEER_ADD: + msgname = _T("PEER_ADD"); + if (SBServerHasBasicKeys(server)) + _tprintf(_T(" firewall server name: %s numplayers: %d ping: %d firewall: %s\n"), SBServerGetStringValue(server,_T("hostname"),_T("UNKNOWN")), SBServerGetIntValue(server,_T("numplayers"),0), SBServerGetPing(server), SBServerDirectConnect(server)==SBTrue?_T("NO"):_T("YES")); + if (SBServerHasFullKeys(server)) + _tprintf(_T(" server gametype: %s \n"), SBServerGetStringValue(server, _T("gametype"), _T("unknown"))); + break; + case PEER_UPDATE: + msgname = _T("PEER_UPDATE"); + if(server) + { + _tprintf(_T(" update server name: %s numplayers: %d ping: %d firewall: %s\n"), SBServerGetStringValue(server,_T("hostname"),_T("UNKNOWN")), SBServerGetIntValue(server,_T("numplayers"),0), SBServerGetPing(server), SBServerDirectConnect(server)==SBTrue?_T("NO"):_T("YES")); + _tprintf(_T(" server gametype: %s \n"), SBServerGetStringValue(server, _T("gametype"), _T("unknown"))); + } + break; + case PEER_REMOVE: + msgname = _T("PEER_REMOVE"); + break; + case PEER_COMPLETE: + msgname = _T("PEER_COMPLETE"); + gStartListing = 1; + break; + default: + msgname = _T("ERROR!"); + } + //_tprintf("game: %s\n", msgname); + //if(server) + // _tprintf(" server name: %s numplayers: %d ping: %d\n", SBServerGetStringValue(server,_T("hostname"),_T("UNKNOWN")), SBServerGetIntValue(server,_T("numplayers"),0), SBServerGetPing(server)); + } + + GSI_UNUSED(peer); + GSI_UNUSED(name); + GSI_UNUSED(staging); + GSI_UNUSED(progress); + GSI_UNUSED(param); +} +*/ +static void ListGroupRoomsCallback +( + PEER peer, + PEERBool success, + int groupID, + SBServer server, + const gsi_char * name, + int numWaiting, + int maxWaiting, + int numGames, + int numPlaying, + void * param +) +{ + if(success && (groupID > 0)) + { + _tprintf(_T(" %s\n"), name); + _tprintf(_T(" Players in room: %d\n"), numWaiting); + _tprintf(_T(" Max players in room: %d\n"), maxWaiting); + _tprintf(_T(" Games: %d\n"), numGames); + _tprintf(_T(" Players in games: %d\n"), numPlaying); + } + else + groupRoomCallbackDone = PEERTrue; // if groupID is set to 0 it means all group rooms have been listed + + GSI_UNUSED(peer); + GSI_UNUSED(server); + GSI_UNUSED(numWaiting); + GSI_UNUSED(maxWaiting); + GSI_UNUSED(numGames); + GSI_UNUSED(numPlaying); + GSI_UNUSED(param); +} + +PEERBool joinSuccess; +static void JoinCallback +( + PEER peer, + PEERBool success, + PEERJoinResult result, + RoomType roomType, + void * param +) +{ + joinSuccess = success; + + GSI_UNUSED(peer); + GSI_UNUSED(result); + GSI_UNUSED(roomType); + GSI_UNUSED(param); +} + +static void NickErrorCallback +( + PEER peer, + int type, + const gsi_char * badNick, + int numSuggestedNicks, + const gsi_char ** suggestedNicks, + void * param +) +{ + static int errCount; + + if(errCount < 20) + { + _tsnprintf(nick,NICK_SIZE,_T("peerc%u"),(unsigned int)current_time()); + nick[NICK_SIZE - 1] = '\0'; + peerRetryWithNick(peer, nick); + } + else + { + //peerDisconnect(peer); + peerRetryWithNick(peer, NULL); + } + errCount++; + + GSI_UNUSED(type); + GSI_UNUSED(badNick); + GSI_UNUSED(suggestedNicks); + GSI_UNUSED(numSuggestedNicks); + GSI_UNUSED(param); +} +/* +static void AutoMatchStatusCallback(PEER thePeer, PEERAutoMatchStatus theStatus, void* theParam) +{ + _tprintf(_T("Automatch status: %d\r\n"), theStatus); + GSI_UNUSED(thePeer); + GSI_UNUSED(theStatus); + GSI_UNUSED(theParam); +} + +static int AutoMatchRateCallback(PEER thePeer, SBServer theServer, void* theParam) +{ + GSI_UNUSED(thePeer); + GSI_UNUSED(theServer); + GSI_UNUSED(theParam); + return 0; +} +*/ +static void RoomKeyChangedCallback(PEER thePeer, RoomType theType, const gsi_char* theNick, const gsi_char* theKey, const gsi_char* theValue, void* theParam) +{ + GSI_UNUSED(thePeer); + GSI_UNUSED(theType); + GSI_UNUSED(theNick); + GSI_UNUSED(theKey); + GSI_UNUSED(theValue); + GSI_UNUSED(theParam); +} + +static void QRServerKeyCallback +( + PEER peer, + int key, + qr2_buffer_t buffer, + void * param +) +{ +#if VERBOSE + gsi_char verbose[256]; + _stprintf(verbose, _T("QR_SERVER_KEY | %d\n"), key); + OutputDebugString(verbose); +#endif + + switch(key) + { + case HOSTNAME_KEY: + _tprintf(_T(" Server Key callback is being called for HOSTNAME_KEY\n")); + qr2_buffer_add(buffer, _T("My Game")); + break; + case NUMPLAYERS_KEY: + _tprintf(_T(" Server Key callback is being called for NUMPLAYERS_KEY\n")); + qr2_buffer_add_int(buffer, 1); + break; + case GAMEMODE_KEY: + _tprintf(_T(" Server Key callback is being called for GAMEMODE_KEY\n")); + qr2_buffer_add(buffer, _T("openplaying")); + break; + case HOSTPORT_KEY: + _tprintf(_T(" Server Key callback is being called for HOSTPORT_KEY\n")); + qr2_buffer_add_int(buffer, 15151); + break; + case MAPNAME_KEY: + _tprintf(_T(" Server Key callback is being called for MAPNAME_KEY\n")); + qr2_buffer_add(buffer, _T("Big Crate Room")); + break; + case GAMETYPE_KEY: + _tprintf(_T(" Server Key callback is being called for GAMETYPE_KEY\n")); + qr2_buffer_add(buffer, _T("Friendly")); + break; + case TIMELIMIT_KEY: + _tprintf(_T(" Server Key callback is being called for TIMELIMIT_KEY\n")); + qr2_buffer_add_int(buffer, 100); + break; + case FRAGLIMIT_KEY: + _tprintf(_T(" Server Key callback is being called for FRAGLIMIT_KEY\n")); + qr2_buffer_add_int(buffer, 0); + break; + case TEAMPLAY_KEY: + _tprintf(_T(" Server Key callback is being called for TEAMPLAY_KEY\n")); + qr2_buffer_add_int(buffer, 0); + break; + default: + qr2_buffer_add(buffer, _T("")); + break; + } + + GSI_UNUSED(peer); + GSI_UNUSED(param); +} + +static void QRKeyListCallback +( + PEER peer, + qr2_key_type type, + qr2_keybuffer_t keyBuffer, + void * param +) +{ + // register the keys we use + switch(type) + { + case key_server: + _tprintf(_T(" Key List Callback is being called for server keys\n")); + if(!peerIsAutoMatching(peer)) + { + qr2_keybuffer_add(keyBuffer, HOSTPORT_KEY); + qr2_keybuffer_add(keyBuffer, MAPNAME_KEY); + qr2_keybuffer_add(keyBuffer, GAMETYPE_KEY); + qr2_keybuffer_add(keyBuffer, TIMELIMIT_KEY); + qr2_keybuffer_add(keyBuffer, FRAGLIMIT_KEY); + qr2_keybuffer_add(keyBuffer, TEAMPLAY_KEY); + } + break; + case key_player: + _tprintf(_T(" Key List Callback is being called for player keys\n")); + // no custom player keys + break; + case key_team: + _tprintf(_T(" Key List Callback is being called for team keys\n")); + // no custom team keys + break; + default: break; + } + + GSI_UNUSED(param); +} + + + + +#ifdef __MWERKS__ // CodeWarrior will warn if not prototyped + int test_main(int argc, char **argv); +#endif + +int test_main(int argc, char **argv) +{ + PEER peer; // peer object (initialized with peerInitialize + + /* peerInitialize */ + PEERCallbacks callbacks; // we will need to add all of the supported callbacks + + /* peerSetTitle parameters */ + gsi_char secret_key[9]; // your title's assigned secret key + int maxUpdates = 10; // max server queries the SDK will send out at a time + PEERBool natNeg = PEERFalse; // nat negotiation will not be supported + PEERBool pingRooms[NumRooms]; + PEERBool crossPingRooms[NumRooms]; + + /* peerConnect parameters */ + int profileID = 0; // we will not be using gp accounts, so this will be ignored + void * userData = NULL; // optional data passed to peerConnectCallback + PEERBool blocking = PEERTrue; // true means function runs synchronously + PEERBool non_blocking = PEERFalse; // false means function runs asynchronously + + int newbiesGroupID = 2; + + gsi_char * serverName = _T("UberNoobServer"); + int maxPlayers = 2; + gsi_char * noPassword = _T(""); + + GSIACResult result; // used for backend availability check + gsi_time startTime; + + // set our nickname to a random string so that we'll have different nicks when running multiple instances of peerc + _tsnprintf(nick,NICK_SIZE,_T("peerc%u"),(unsigned int)current_time()); + nick[NICK_SIZE - 1] = '\0'; + + // set the secret key, in a semi-obfuscated manner + secret_key[0] = 'H'; + secret_key[1] = 'A'; + secret_key[2] = '6'; + secret_key[3] = 'z'; + secret_key[4] = 'k'; + secret_key[5] = 'S'; + secret_key[6] = '\0'; + + // check that the game's backend is available + GSIStartAvailableCheck(_T("gmtest")); + while((result = GSIAvailableCheckThink()) == GSIACWaiting) + msleep(5); + if(result != GSIACAvailable) + { + _tprintf(_T("The backend is not available\n")); + return 1; + } + + // set the callbacks + memset(&callbacks, 0, sizeof(PEERCallbacks)); + callbacks.disconnected = DisconnectedCallback; + //callbacks.qrNatNegotiateCallback + callbacks.gameStarted = GameStartedCallback; + callbacks.playerChangedNick = PlayerChangedNickCallback; + callbacks.playerJoined = PlayerJoinedCallback; + callbacks.playerLeft = PlayerLeftCallback; + callbacks.readyChanged = ReadyChangedCallback; + callbacks.roomMessage = RoomMessageCallback; + callbacks.playerMessage = PlayerMessageCallback; + callbacks.roomUTM = RoomUTMCallback; + callbacks.roomKeyChanged = RoomKeyChangedCallback; + callbacks.qrKeyList = QRKeyListCallback; + callbacks.qrServerKey = QRServerKeyCallback; + callbacks.param = NULL; + + // initialize peer object, specifying the supported callbacks + peer = peerInitialize(&callbacks); + if(!peer) + { + _tprintf(_T("Failed to init\n")); + return 1; + } + + // ping/cross-ping in every room + pingRooms[TitleRoom] = PEERTrue; + pingRooms[GroupRoom] = PEERTrue; + pingRooms[StagingRoom] = PEERTrue; + crossPingRooms[TitleRoom] = PEERTrue; + crossPingRooms[GroupRoom] = PEERTrue; + crossPingRooms[StagingRoom] = PEERTrue; + + // set the title + if(!peerSetTitle(peer, TITLE, secret_key, TITLE, secret_key, 0, maxUpdates, natNeg, pingRooms, crossPingRooms)) + { + peerShutdown(peer); + _tprintf(_T("Failed to set the title\n")); + return 1; + } + + // connect to the chat server + _tprintf(_T("Connecting as %s..."), nick); + peerConnect(peer, nick, profileID, NickErrorCallback, ConnectCallback, userData, blocking); + if(!connectSuccess) + { + peerShutdown(peer); + _tprintf(_T("Failed to connect\n")); + return 1; + } + printf("Connected\n\n"); + + // join the title room + printf("Joining title room..."); + peerJoinTitleRoom(peer, NULL, JoinCallback, NULL, PEERTrue); + if(!joinSuccess) + { + peerDisconnect(peer); + peerShutdown(peer); + _tprintf(_T("Failed to join the title room\n")); + return 1; + } + printf("Joined\n\n"); + + // list the group rooms + printf("Listing group rooms:\n"); + peerListGroupRooms(peer, _T(""), ListGroupRoomsCallback, userData, non_blocking); + + while (!groupRoomCallbackDone) + { + peerThink(peer); + msleep(10); + } + + // send a chat message to the room + printf("\nSending message to the Title Room...\n"); + peerMessageRoom(peer, TitleRoom, _T("Hi everyone in the Title Room!\n"), NormalMessage); + + // Loop for a while + startTime = current_time(); + while((current_time() - startTime) < (1000)) + { + peerThink(peer); + msleep(10); + } + + _tprintf(_T("\nJoining Group Room 'Newbies'...")); + peerJoinGroupRoom(peer, newbiesGroupID, JoinRoomCallback, userData, blocking); + + _tprintf(_T("\nPlayers in Group Room: \n")); + peerEnumPlayers(peer, GroupRoom, EnumPlayersCallback, NULL); + + _tprintf(_T("Hosting Staging Room...\n")); + peerCreateStagingRoom(peer, serverName, maxPlayers, noPassword, CreateStagingRoomCallback, userData, blocking); + + // Loop for a while + startTime = current_time(); + while((current_time() - startTime) < (1000)) + { + peerThink(peer); + msleep(10); + } + + //peerStartAutoMatch(peer, 3, _T(""), AutoMatchStatusCallback, AutoMatchRateCallback, NULL, PEERFalse); + + //peerStopListingGames(peer); + + // Leave the title room + peerLeaveRoom(peer, TitleRoom, NULL); + + // Stop auto match if it's in progress + //peerStopAutoMatch(peer); + + // disconnect local client from chat server (peerShutdown must still be called) + peerDisconnect(peer); + + peerShutdown(peer); // clean up (free internal sdk memory) + + GSI_UNUSED(argc); + GSI_UNUSED(argv); + return 0; +} diff --git a/code/gamespy/Peer/peerc/peernitrocw/Nitro.lcf b/code/gamespy/Peer/peerc/peernitrocw/Nitro.lcf new file mode 100644 index 00000000..998f6b00 --- /dev/null +++ b/code/gamespy/Peer/peerc/peernitrocw/Nitro.lcf @@ -0,0 +1,493 @@ +#--------------------------------------------------------------------------- +# Project: NitroSDK - tools - makelcf +# File: ARM9-TS.lcf.template +# +# Copyright 2003-2006 Nintendo. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# proprietary information of Nintendo of America Inc. and/or Nintendo +# Company Ltd., and are protected by Federal copyright law. They may +# not be disclosed to third parties or copied or duplicated in any form, +# in whole or in part, without the prior written consent of Nintendo. +# +# $Log: ARM9-TS.lcf.template,v $ +# Revision 1.34 04/06/2006 09:02:36 kitase_hirotake +# support for .itcm.bss and .dtcm.bss +# +# Revision 1.33 03/30/2006 23:59:22 AM yasu +# changed creation year +# +# Revision 1.32 03/29/2006 13:14:22 AM yasu +# support for overlays in CWVER 2.x +# +# Revision 1.31 11/24/2005 01:16:47 yada +# change start address of mainEX arena from 0x2400000 to 0x23e0000 +# +# Revision 1.30 09/02/2005 04:14:22 AM yasu +# Old symbols were redefined so they can be used even under SDK2.2 +# +# Revision 1.29 08/31/2005 09:34:57 AM yasu +# Corrected a problem where code would not function normally when using section names such as section_BSS +# +# Revision 1.28 08/26/2005 11:22:16 AM yasu +# overlay support for ITCM/DTCM +# +# Revision 1.27 06/20/2005 12:29:20 AM yasu +# Changed Surffix to Suffix +# +# Revision 1.26 06/14/2005 09:03:42 yada +# fix around minus value of SDK_STACKSIZE +# +# Revision 1.25 04/13/2005 12:51:00 terui +# Change SDK_AUTOLOAD.DTCM.START 0x027c0000 -> 0x027e0000 +# +# Revision 1.24 03/30/2005 00:02:14 yosizaki +# fix copyright header. +# +# Revision 1.23 03/25/2005 12:54:59 AM yasu +# Include .version section +# +# Revision 1.22 10/03/2004 02:00:56 AM yasu +# Output component file list for compstatic tool +# +# Revision 1.21 09/27/2004 05:28:21 AM yasu +# Support .sinit +# +# Revision 1.20 09/09/2004 11:49:20 AM yasu +# Support compstatic in default +# +# Revision 1.19 09/06/2004 06:40:00 AM yasu +# Add labels for digest +# +# Revision 1.18 08/20/2004 06:19:59 AM yasu +# DTCM moves to 0x027c0000 at default +# +# Revision 1.17 08/02/2004 10:38:53 AM yasu +# Add autoload-done callback address in overlaydefs +# +# Revision 1.16 07/26/2004 02:22:32 AM yasu +# Change DTCM address to 0x023c0000 +# +# Revision 1.15 07/26/2004 00:08:27 AM yasu +# Fix label of exception table +# +# Revision 1.14 07/24/2004 05:42:25 AM yasu +# Set default values for SDK_AUTOGEN_xTCM_START +# +# Revision 1.13 07/23/2004 11:32:14 AM yasu +# Define labels for __exception_table_start__ and _end__ +# +# Revision 1.12 07/12/2004 12:21:08 AM yasu +# Check size of ITCM/DTCM +# +# Revision 1.11 07/10/2004 04:10:26 AM yasu +# Support command 'Library' +# +# Revision 1.10 07/02/2004 08:13:02 AM yasu +# Support OBJECT( ) +# +# Revision 1.9 07/01/2004 12:54:38 yasu +# support ITCM/DTCM/WRAM autoload +# +# Revision 1.8 07/01/2004 10:41:46 yasu +# support autoload +# +# Revision 1.7 06/02/2004 07:35:37 yasu +# Set libsyscall.a in FORCE_ACTIVE +# Put NitroMain at the top of ROM image +# +# Revision 1.6 06/02/2004 04:56:28 yasu +# Change to fit to new ROM map of TS +# +# Revision 1.5 2004/06/01 06:12:00 miya +# add padding at top of ROM image. +# +# Revision 1.4 04/26/2004 12:16:48 yasu +# add KEEP_SECTIONS +# +# Revision 1.3 04/20/2004 07:41:32 yasu +# Set STATICINIT instead of .ctor temporarily +# +# Revision 1.2 04/14/2004 07:16:42 yasu +# add ALIGN(32) for convenience to handle cache line +# +# Revision 1.1 04/06/2004 01:59:54 yasu +# newly added +# +# $NoKeywords: $ +#--------------------------------------------------------------------------- +MEMORY +{ + main (RWX) : ORIGIN = 0x02000000, LENGTH = 0x0 > main.sbin + ITCM (RWX) : ORIGIN = 0x01ff8000, LENGTH = 0x0 >> main.sbin + DTCM (RWX) : ORIGIN = 0x027e0000, LENGTH = 0x0 >> main.sbin + binary.AUTOLOAD_INFO (RWX) : ORIGIN = 0, LENGTH = 0x0 >> main.sbin + binary.STATIC_FOOTER (RWX) : ORIGIN = 0, LENGTH = 0x0 >> main.sbin + + main_defs (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 > main_defs.sbin + main_table (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 > main_table.sbin + dummy.MAIN_EX (RW) : ORIGIN = 0x023e0000, LENGTH = 0x0 + arena.MAIN (RW) : ORIGIN = AFTER(main), LENGTH = 0x0 + arena.MAIN_EX (RW) : ORIGIN = AFTER(dummy.MAIN_EX), LENGTH = 0x0 + arena.ITCM (RW) : ORIGIN = AFTER(ITCM), LENGTH = 0x0 + arena.DTCM (RW) : ORIGIN = AFTER(DTCM), LENGTH = 0x0 + binary.MODULE_FILES (RW) : ORIGIN = 0x0, LENGTH = 0x0 > component.files + check.ITCM (RWX) : ORIGIN = 0x0, LENGTH = 0x08000 > itcm.check + check.DTCM (RW) : ORIGIN = 0x0, LENGTH = 0x04000 > dtcm.check +} + +FORCE_ACTIVE +{ + SVC_SoftReset +} + +KEEP_SECTION +{ + .sinit +} + +SECTIONS +{ + ############################ STATIC ################################# + .main: + { + ALIGNALL(4); . = ALIGN(32); # Fit to cache line + + # + # TEXT BLOCK: READ ONLY + # + SDK_STATIC_START =.; + SDK_STATIC_TEXT_START =.; + #:::::::::: text/rodata + libsyscall.a (.text) + crt0.o (.text) + crt0.o (.rodata) + * (.version) + OBJECT(NitroMain,*) + GROUP(ROOT) (.text) + . = ALIGN(4); + * (.exception) + . = ALIGN(4); + SDK_STATIC_ETABLE_START =.; + EXCEPTION + SDK_STATIC_ETABLE_END =.; + . = ALIGN(4); + GROUP(ROOT) (.init) + . = ALIGN(4); + GROUP(ROOT) (.rodata) + . = ALIGN(4); + + SDK_STATIC_SINIT_START =.; + #:::::::::: ctor + GROUP(ROOT) (.ctor) + GROUP(ROOT) (.sinit) + WRITEW 0; + #:::::::::: ctor + SDK_STATIC_SINIT_END =.; + + #:::::::::: text/rodata + . = ALIGN(32); + SDK_STATIC_TEXT_END =.; + + # + # DATA BLOCK: READ WRITE + # + SDK_STATIC_DATA_START =.; + #:::::::::: data + GROUP(ROOT) (.sdata) + . = ALIGN(4); + GROUP(ROOT) (.data) + . = ALIGN(4); + SDK_OVERLAY_DIGEST =.; + # NO DIGEST + SDK_OVERLAY_DIGEST_END =.; + #:::::::::: data + . = ALIGN(32); + SDK_STATIC_DATA_END =.; + SDK_STATIC_END =.; + + SDK_STATIC_TEXT_SIZE = SDK_STATIC_TEXT_END - SDK_STATIC_TEXT_START; + SDK_STATIC_DATA_SIZE = SDK_STATIC_DATA_END - SDK_STATIC_DATA_START; + SDK_STATIC_SIZE = SDK_STATIC_END - SDK_STATIC_START; + __sinit__ = SDK_STATIC_SINIT_START; # for static initializer + __exception_table_start__ = SDK_STATIC_ETABLE_START; # for exception table + __exception_table_end__ = SDK_STATIC_ETABLE_END; # for exception table + } > main + + .main.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_STATIC_BSS_START =.; + #:::::::::: bss + GROUP(ROOT) (.sbss) + . = ALIGN(4); + GROUP(ROOT) (.bss) + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_STATIC_BSS_END = .; + SDK_STATIC_BSS_SIZE = SDK_STATIC_BSS_END - SDK_STATIC_BSS_START; + + } >> main + + + ############################ AUTOLOADS ############################## + SDK_AUTOLOAD.ITCM.START = 0x01ff8000; + SDK_AUTOLOAD.ITCM.END = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD.ITCM.BSS_END = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD.ITCM.SIZE = 0; + SDK_AUTOLOAD.ITCM.BSS_SIZE = 0; + SDK_AUTOLOAD.DTCM.START = 0x027e0000; + SDK_AUTOLOAD.DTCM.END = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD.DTCM.BSS_END = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD.DTCM.SIZE = 0; + SDK_AUTOLOAD.DTCM.BSS_SIZE = 0; + SDK_AUTOLOAD_START = SDK_STATIC_END; + SDK_AUTOLOAD_SIZE = 0; + SDK_AUTOLOAD_NUMBER = 2; + + .ITCM: + { + ALIGNALL(4); . = ALIGN(32); + + # + # TEXT BLOCK: READ ONLY + # + SDK_AUTOLOAD_ITCM_ID =0; + SDK_AUTOLOAD.ITCM.ID =0; + SDK_AUTOLOAD.ITCM.START =.; + SDK_AUTOLOAD.ITCM.TEXT_START =.; + #:::::::::: text/rodata + . = ALIGN(4); + * (.itcm) + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: text/rodata + SDK_AUTOLOAD.ITCM.TEXT_END =.; + + # + # DATA BLOCK: READ WRITE BLOCK + # + SDK_AUTOLOAD.ITCM.DATA_START =.; + #:::::::::: data + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: data + . = ALIGN(32); + SDK_AUTOLOAD.ITCM.DATA_END =.; + SDK_AUTOLOAD.ITCM.END =.; + + SDK_AUTOLOAD.ITCM.TEXT_SIZE = SDK_AUTOLOAD.ITCM.TEXT_END - SDK_AUTOLOAD.ITCM.TEXT_START; + SDK_AUTOLOAD.ITCM.DATA_SIZE = SDK_AUTOLOAD.ITCM.DATA_END - SDK_AUTOLOAD.ITCM.DATA_START; + SDK_AUTOLOAD.ITCM.SIZE = SDK_AUTOLOAD.ITCM.END - SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SDK_AUTOLOAD.ITCM.SIZE; + + } > ITCM + + .ITCM.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_AUTOLOAD.ITCM.BSS_START = .; + #:::::::::: bss + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + * (.itcm.bss) + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_AUTOLOAD.ITCM.BSS_END = .; + + SDK_AUTOLOAD.ITCM.BSS_SIZE = SDK_AUTOLOAD.ITCM.BSS_END - SDK_AUTOLOAD.ITCM.BSS_START; + + } >> ITCM + + .DTCM: + { + ALIGNALL(4); . = ALIGN(32); + + # + # TEXT BLOCK: READ ONLY + # + SDK_AUTOLOAD_DTCM_ID =1; + SDK_AUTOLOAD.DTCM.ID =1; + SDK_AUTOLOAD.DTCM.START =.; + SDK_AUTOLOAD.DTCM.TEXT_START =.; + #:::::::::: text/rodata + . = ALIGN(4); + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: text/rodata + SDK_AUTOLOAD.DTCM.TEXT_END =.; + + # + # DATA BLOCK: READ WRITE BLOCK + # + SDK_AUTOLOAD.DTCM.DATA_START =.; + #:::::::::: data + . = ALIGN(4); + . = ALIGN(4); + * (.dtcm) + . = ALIGN(4); + #:::::::::: data + . = ALIGN(32); + SDK_AUTOLOAD.DTCM.DATA_END =.; + SDK_AUTOLOAD.DTCM.END =.; + + SDK_AUTOLOAD.DTCM.TEXT_SIZE = SDK_AUTOLOAD.DTCM.TEXT_END - SDK_AUTOLOAD.DTCM.TEXT_START; + SDK_AUTOLOAD.DTCM.DATA_SIZE = SDK_AUTOLOAD.DTCM.DATA_END - SDK_AUTOLOAD.DTCM.DATA_START; + SDK_AUTOLOAD.DTCM.SIZE = SDK_AUTOLOAD.DTCM.END - SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SDK_AUTOLOAD.DTCM.SIZE; + + } > DTCM + + .DTCM.bss: + { + ALIGNALL(4); . = ALIGN(32); + + # + # BSS BLOCK + # + SDK_AUTOLOAD.DTCM.BSS_START = .; + #:::::::::: bss + . = ALIGN(4); + . = ALIGN(4); + * (.dtcm.bss) + . = ALIGN(4); + . = ALIGN(4); + #:::::::::: bss + . = ALIGN(32); + SDK_AUTOLOAD.DTCM.BSS_END = .; + + SDK_AUTOLOAD.DTCM.BSS_SIZE = SDK_AUTOLOAD.DTCM.BSS_END - SDK_AUTOLOAD.DTCM.BSS_START; + + } >> DTCM + + + SDK_AUTOLOAD_ITCM_START = SDK_AUTOLOAD.ITCM.START; + SDK_AUTOLOAD_ITCM_END = SDK_AUTOLOAD.ITCM.END; + SDK_AUTOLOAD_ITCM_BSS_END = SDK_AUTOLOAD.ITCM.BSS_END; + SDK_AUTOLOAD_ITCM_SIZE = SDK_AUTOLOAD.ITCM.SIZE; + SDK_AUTOLOAD_ITCM_BSS_SIZE = SDK_AUTOLOAD.ITCM.BSS_SIZE; + SDK_AUTOLOAD_DTCM_START = SDK_AUTOLOAD.DTCM.START; + SDK_AUTOLOAD_DTCM_END = SDK_AUTOLOAD.DTCM.END; + SDK_AUTOLOAD_DTCM_BSS_END = SDK_AUTOLOAD.DTCM.BSS_END; + SDK_AUTOLOAD_DTCM_SIZE = SDK_AUTOLOAD.DTCM.SIZE; + SDK_AUTOLOAD_DTCM_BSS_SIZE = SDK_AUTOLOAD.DTCM.BSS_SIZE; + + ############################ AUTOLOAD_INFO ########################## + .binary.AUTOLOAD_INFO: + { + WRITEW ADDR(.ITCM); + WRITEW SDK_AUTOLOAD.ITCM.SIZE; + WRITEW SDK_AUTOLOAD.ITCM.BSS_SIZE; + WRITEW ADDR(.DTCM); + WRITEW SDK_AUTOLOAD.DTCM.SIZE; + WRITEW SDK_AUTOLOAD.DTCM.BSS_SIZE; + } > binary.AUTOLOAD_INFO + + SDK_AUTOLOAD_LIST = SDK_AUTOLOAD_START + SDK_AUTOLOAD_SIZE; + SDK_AUTOLOAD_LIST_END = SDK_AUTOLOAD_START + SDK_AUTOLOAD_SIZE + SIZEOF(.binary.AUTOLOAD_INFO); + SDK_AUTOLOAD_SIZE = SDK_AUTOLOAD_SIZE + SIZEOF(.binary.AUTOLOAD_INFO); + + ############################ STATIC_FOOTER ########################## + .binary.STATIC_FOOTER: + { + WRITEW 0xdec00621; # LE(0x2106C0DE) = NITRO CODE + WRITEW _start_ModuleParams - ADDR(.main); + WRITEW 0; # NO DIGEST + } > binary.STATIC_FOOTER + + ############################ OVERLAYS ############################### + SDK_OVERLAY_NUMBER = 0; + + + ############################ MAIN EX ################################## + # MAIN EX Area + .dummy.MAIN_EX: + { + . = ALIGN(32); + } > dummy.MAIN_EX + + ############################ ARENA ################################## + .arena.MAIN: + { + . = ALIGN(32); + SDK_SECTION_ARENA_START =.; + } > arena.MAIN + + .arena.MAIN_EX: + { + . = ALIGN(32); + SDK_SECTION_ARENA_EX_START =.; + } > arena.MAIN_EX + + .arena.ITCM: + { + . = ALIGN(32); + SDK_SECTION_ARENA_ITCM_START =.; + } > arena.ITCM + + .arena.DTCM: + { + . = ALIGN(32); + SDK_SECTION_ARENA_DTCM_START =.; + } > arena.DTCM + + ############################ OVERLAYDEFS ############################ + .main_defs: + { + ### main module information + WRITEW ADDR(.main); # load address + WRITEW _start; # entry address + WRITEW SDK_STATIC_SIZE + SDK_AUTOLOAD_SIZE; # size of module + WRITEW _start_AutoloadDoneCallback; # callback autoload done + + ### overlay filename + + } > main_defs + + + ############################ OVERLAYTABLE ########################### + .main_table: + { + + } > main_table + + + ############################ OTHERS ################################# + SDK_MAIN_ARENA_LO = SDK_SECTION_ARENA_START; + SDK_IRQ_STACKSIZE = 4096; # allocated in DTCM + SDK_SYS_STACKSIZE = 0; # when 0 means all remains of DTCM + + # Module filelist + .binary.MODULE_FILES: + { + WRITES ("main.sbin"); + WRITES ("main_defs.sbin"); + WRITES ("main_table.sbin"); + } > binary.MODULE_FILES + + # ITCM/DTCM size checker => check AUTOLOAD_ITCM/DTCM + .check.ITCM: + { + . = . + SDK_AUTOLOAD_ITCM_SIZE + SDK_AUTOLOAD_ITCM_BSS_SIZE; + } > check.ITCM + + SDK_SYS_STACKSIZE_SIGN = (SDK_SYS_STACKSIZE < 0x80000000) * 2 - 1; + .check.DTCM: + { + . = . + SDK_AUTOLOAD_DTCM_SIZE + SDK_AUTOLOAD_DTCM_BSS_SIZE; + . = . + SDK_IRQ_STACKSIZE + SDK_SYS_STACKSIZE * SDK_SYS_STACKSIZE_SIGN; + } > check.DTCM + +} diff --git a/code/gamespy/Peer/peerc/peernitrocw/ROM-TS.rsf b/code/gamespy/Peer/peerc/peernitrocw/ROM-TS.rsf new file mode 100644 index 00000000..cec9e1db --- /dev/null +++ b/code/gamespy/Peer/peerc/peernitrocw/ROM-TS.rsf @@ -0,0 +1,116 @@ +#---------------------------------------------------------------------------- +# Project: NitroSDK - include +# File: ROM-TS.lsf +# +# Copyright 2003-2005 Nintendo. All rights reserved. +# +# These coded insructions, statements, and computer programs contain +# proprietary information of Nintendo of America Inc. and/or Nintendo +# Company Ltd., and are protected by Federal copyright law. They may +# not be disclosed to third parties or copied or duplicated in any form, +# in whole or in part, without the prior written consent of Nintendo. +# +# $Log: ROM-TS.rsf,v $ +# Revision 1.6 2005/04/05 23:52:58 yosizaki +# fix copyright date. +# +# Revision 1.5 2005/04/05 12:16:10 yosizaki +# support RomSpeedType parameter. +# +# Revision 1.4 2004/09/21 02:18:49 yasu +# Add default banner +# +# Revision 1.3 2004/09/09 11:39:09 yasu +# Unified ROM-TS and ROM-TS-C, also ROM-TEG and ROM-TEG-C +# +# Revision 1.2 2004/05/26 12:03:38 yasu +# add :r option to get basename for supporting IDE with makerom +# +# Revision 1.1 2004/04/06 01:59:59 yasu +# newly added +# +# $NoKeywords: $ +#---------------------------------------------------------------------------- +# +# Nitro ROM SPEC FILE +# + +Arm9 +{ + Static "$(MAKEROM_ARM9:r).sbin$(COMPSUFFIX9)" + OverlayDefs "$(MAKEROM_ARM9:r)_defs.sbin$(COMPSUFFIX9)" + OverlayTable "$(MAKEROM_ARM9:r)_table.sbin$(COMPSUFFIX9)" + Elf "$(MAKEROM_ARM9:r).nef" +} + +Arm7 +{ + Static "$(MAKEROM_ARM7:r).sbin$(COMPSUFFIX7)" + OverlayDefs "$(MAKEROM_ARM7:r)_defs.sbin$(COMPSUFFIX7)" + OverlayTable "$(MAKEROM_ARM7:r)_table.sbin$(COMPSUFFIX7)" + Elf "$(MAKEROM_ARM7:r).nef" +} + +Property +{ + ### + ### Settings for FinalROM + ### + #### BEGIN + # + # TITLE NAME: Your product name within 12bytes + # + #TitleName "YourAppName" + + # + # MAKER CODE: Your company ID# in 2 ascii words + # issued by NINTENDO + # + #MakerCode "00" + + # + # REMASTER VERSION: Mastering version + # + #RomVersion 0 + + # + # ROM SPEED TYPE: [MROM/1TROM/UNDEFINED] + # + RomSpeedType $(MAKEROM_ROMSPEED) + + # + # ROM SIZE: in bit [64M/128M/256M/512M/1G/2G] + # + #RomSize 128M + #RomSize 256M + + # + # ROM PADDING: TRUE if finalrom + # + #RomFootPadding TRUE + + # + # ROM HEADER TEMPLATE: Provided to every product by NINTENDO + # + #RomHeaderTemplate ./etc/rom_header.template.sbin + + # + # BANNER FILE: generated from Banner Spec File + # + #BannerFile ./etc/myGameBanner.bnr + BannerFile $(NITROSDK_ROOT)/include/nitro/specfiles/default.bnr + + ### + ### + ### + #### END +} + +RomSpec +{ + Offset 0x00000000 + Segment ALL + HostRoot $(MAKEROM_ROMROOT) + Root / + File $(MAKEROM_ROMFILES) +} diff --git a/code/gamespy/Peer/peerc/peerps2prodg/peerps2prodg.dsp b/code/gamespy/Peer/peerc/peerps2prodg/peerps2prodg.dsp new file mode 100644 index 00000000..056ed2b6 --- /dev/null +++ b/code/gamespy/Peer/peerc/peerps2prodg/peerps2prodg.dsp @@ -0,0 +1,564 @@ +# Microsoft Developer Studio Project File - Name="peerps2prodg" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=peerps2prodg - Win32 Release_Insock +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "peerps2prodg.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "peerps2prodg.mak" CFG="peerps2prodg - Win32 Release_Insock" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "peerps2prodg - Win32 Debug_EENet" (based on "Win32 (x86) Console Application") +!MESSAGE "peerps2prodg - Win32 Debug_Insock" (based on "Win32 (x86) Console Application") +!MESSAGE "peerps2prodg - Win32 Debug_SNSystems" (based on "Win32 (x86) Console Application") +!MESSAGE "peerps2prodg - Win32 Release_EENet" (based on "Win32 (x86) Console Application") +!MESSAGE "peerps2prodg - Win32 Release_Insock" (based on "Win32 (x86) Console Application") +!MESSAGE "peerps2prodg - Win32 Release_SNSystems" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/Gamespy/GOA/Peer/peerc/peerps2prodg", NTEDAAAA" +# PROP Scc_LocalPath "." +CPP=snCl.exe +RSC=rc.exe + +!IF "$(CFG)" == "peerps2prodg - Win32 Debug_EENet" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug_EENet" +# PROP BASE Intermediate_Dir "Debug_EENet" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_EENet" +# PROP Intermediate_Dir "Debug_EENet" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Debug/" /FD /debug /c +# ADD CPP /nologo /W4 /WX /Od /I "c:\usr\local\sce\ee\include\libeenet" /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "EENET" /D "_DEBUG" /D "SN_TARGET_PS2" /D "UNIQUEID" /FD /debug /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"PS2_EE_Debug\peerps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 libc.a eenetctl.a ent_smap.a ent_eth.a ent_ppp.a libeenet.a libscf.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /nodefaultlib /out:"Debug_EENet\peerps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "peerps2prodg - Win32 Debug_Insock" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "peerps2prodg___Win32_Debug_Insock" +# PROP BASE Intermediate_Dir "peerps2prodg___Win32_Debug_Insock" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_Insock" +# PROP Intermediate_Dir "Debug_Insock" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "_DEBUG" /D "SN_SYSTEMS" /D "SN_TARGET_PS2" /D "UNIQUEID" /FD /debug /c +# ADD CPP /nologo /W4 /WX /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "_DEBUG" /D "SN_TARGET_PS2" /D "INSOCK" /D "UNIQUEID" /FD /debug /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libc.a sneetcp.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /nodefaultlib /out:"Debug_SNSystems\peerps2prodg.elf" /D:SN_TARGET_PS2 -fshort-wchar +# ADD LINK32 libinsck.a libnet.a libmrpc.a libkernl.a libcdvd.a libnetif.a netcnfif.a /nologo /pdb:none /debug /machine:IX86 /nodefaultlib /out:"Debug_Insock\peerps2prodg.elf" /D:SN_TARGET_PS2 -fshort-wchar + +!ELSEIF "$(CFG)" == "peerps2prodg - Win32 Debug_SNSystems" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug_SNSystems" +# PROP BASE Intermediate_Dir "Debug_SNSystems" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_SNSystems" +# PROP Intermediate_Dir "Debug_SNSystems" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Debug/" /FD /debug /c +# ADD CPP /nologo /W4 /WX /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "_DEBUG" /D "SN_SYSTEMS" /D "SN_TARGET_PS2" /D "UNIQUEID" /FD /debug /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /out:"PS2_EE_Debug\peerps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 libc.a sneetcp.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /debug /machine:IX86 /nodefaultlib /out:"Debug_SNSystems\peerps2prodg.elf" /D:SN_TARGET_PS2 -fshort-wchar + +!ELSEIF "$(CFG)" == "peerps2prodg - Win32 Release_EENet" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Release_EENet" +# PROP BASE Intermediate_Dir "Release_EENet" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Release_EENet" +# PROP Intermediate_Dir "Release_EENet" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /O2 /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Release/" /FD /c +# ADD CPP /nologo /W4 /WX /Od /I "c:\usr\local\sce\ee\include\libeenet" /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "EENET" /D "SN_TARGET_PS2" /D "UNIQUEID" /FD /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"PS2_EE_Release\peerps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 eenetctl.a ent_smap.a ent_eth.a ent_ppp.a libeenet.a libscf.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"Release_EENet\peerps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "peerps2prodg - Win32 Release_Insock" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "peerps2prodg___Win32_Release_Insock" +# PROP BASE Intermediate_Dir "peerps2prodg___Win32_Release_Insock" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Release_Insock" +# PROP Intermediate_Dir "Release_Insock" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W4 /WX /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_SYSTEMS" /D "SN_TARGET_PS2" /D "UNIQUEID" /FD /c +# ADD CPP /nologo /W4 /WX /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /D "INSOCK" /D "UNIQUEID" /FD /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 sneetcp.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"Release_SNSystems\peerps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 libinsck.a libnet.a libmrpc.a libkernl.a libcdvd.a libnetif.a netcnfif.a /nologo /pdb:none /machine:IX86 /out:"Release_Insock\peerps2prodg.elf" /D:SN_TARGET_PS2 + +!ELSEIF "$(CFG)" == "peerps2prodg - Win32 Release_SNSystems" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Release_SNSystems" +# PROP BASE Intermediate_Dir "Release_SNSystems" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Release_SNSystems" +# PROP Intermediate_Dir "Release_SNSystems" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /w /W0 /O2 /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_TARGET_PS2" /Fo"PS2_EE_Release/" /FD /c +# ADD CPP /nologo /W4 /WX /Od /I "C:\usr\local\sce\ee\include" /I "C:\usr\local\sce\common\include" /D "SN_SYSTEMS" /D "SN_TARGET_PS2" /D "UNIQUEID" /FD /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=snBsc.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=snLink.exe +# ADD BASE LINK32 libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"PS2_EE_Release\peerps2prodg.elf" /D:SN_TARGET_PS2 +# ADD LINK32 sneetcp.a libcdvd.a libsn.a libgraph.a libdma.a libdev.a libpad.a libpkt.a libvu0.a /nologo /pdb:none /machine:IX86 /out:"Release_SNSystems\peerps2prodg.elf" /D:SN_TARGET_PS2 + +!ENDIF + +# Begin Target + +# Name "peerps2prodg - Win32 Debug_EENet" +# Name "peerps2prodg - Win32 Debug_Insock" +# Name "peerps2prodg - Win32 Debug_SNSystems" +# Name "peerps2prodg - Win32 Release_EENet" +# Name "peerps2prodg - Win32 Release_Insock" +# Name "peerps2prodg - Win32 Release_SNSystems" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\peerc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\ps2\ps2common.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\..\common\ps2\prodg\PS2_in_VC.h +# End Source File +# End Group +# Begin Group "Peer" + +# PROP Default_Filter "" +# Begin Group "ServerBrowsingSDK" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\serverbrowsing\sb_crypt.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\serverbrowsing\sb_crypt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\serverbrowsing\sb_internal.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\serverbrowsing\sb_queryengine.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\serverbrowsing\sb_server.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\serverbrowsing\sb_serverbrowsing.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\serverbrowsing\sb_serverbrowsing.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\serverbrowsing\sb_serverlist.c +# End Source File +# End Group +# Begin Group "QueryReportingSDK" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\qr2\qr2.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\qr2\qr2.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\qr2\qr2regkeys.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\qr2\qr2regkeys.h +# End Source File +# End Group +# Begin Group "ChatSDK" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Chat\chat.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatCallbacks.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatCallbacks.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatChannel.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatChannel.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatCrypt.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatCrypt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatHandlers.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatHandlers.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatMain.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatMain.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatSocket.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\Chat\chatSocket.h +# End Source File +# End Group +# Begin Group "Pinger" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\pinger\pinger.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\pinger\pingerMain.c +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\peer.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerAutoMatch.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerAutoMatch.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerCallbacks.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerCallbacks.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerGlobalCallbacks.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerGlobalCallbacks.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerHost.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerHost.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerKeys.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerKeys.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerMain.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerMain.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerMangle.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerMangle.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerOperations.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerOperations.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerPing.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerPing.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerPlayers.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerPlayers.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerQR.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerQR.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerRooms.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerRooms.h +# End Source File +# Begin Source File + +SOURCE=..\..\peerSB.c +# End Source File +# Begin Source File + +SOURCE=..\..\peerSB.h +# End Source File +# End Group +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\darray.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\darray.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAssert.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAssert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAvailable.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsAvailable.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsCommon.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsDebug.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsDebug.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsMemory.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsMemory.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatform.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatform.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformSocket.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformSocket.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformThread.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformThread.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformUtil.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsPlatformUtil.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsStringUtil.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\common\gsStringUtil.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\hashtable.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\hashtable.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\md5.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\md5c.c +# End Source File +# End Group +# Begin Source File + +SOURCE=c:\usr\local\sce\ee\lib\app.cmd +# End Source File +# Begin Source File + +SOURCE=c:\usr\local\sce\ee\lib\crt0.s +# End Source File +# Begin Source File + +SOURCE=..\..\..\ps2common\prodg\ps2.lk +# End Source File +# End Target +# End Project diff --git a/code/gamespy/Peer/peerc/peerps2prodg/peerps2prodg.dsw b/code/gamespy/Peer/peerc/peerps2prodg/peerps2prodg.dsw new file mode 100644 index 00000000..7c1581af --- /dev/null +++ b/code/gamespy/Peer/peerc/peerps2prodg/peerps2prodg.dsw @@ -0,0 +1,37 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "peerps2prodg"=.\peerps2prodg.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Gamespy/GOA/Peer/peerc/peerps2prodg", NTEDAAAA + . + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ + begin source code control + "$/gamespy/goa/peer/peerc/peerps2prodg", NTEDAAAA + . + end source code control +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/LoginDlg.cpp b/code/gamespy/Voice2/Voice2BuddyMFC/LoginDlg.cpp new file mode 100644 index 00000000..4938f083 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/LoginDlg.cpp @@ -0,0 +1,107 @@ +// LoginDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "Voice2BuddyMFC.h" +#include "LoginDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + + +CLoginDlg::CLoginDlg(CWnd* pParent /*=NULL*/) + : CDialog(CLoginDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CLoginDlg) + m_Email = _T(""); + m_Nickname = _T(""); + m_Password = _T(""); + //}}AFX_DATA_INIT +} + + +void CLoginDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CLoginDlg) + DDX_Text(pDX, IDC_EMAIL, m_Email); + DDX_Text(pDX, IDC_NICKNAME, m_Nickname); + DDX_Text(pDX, IDC_PASSWORD, m_Password); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CLoginDlg, CDialog) + //{{AFX_MSG_MAP(CLoginDlg) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg message handlers + +void CLoginDlg::OnOK() +{ + // Retrieve settings from the view + UpdateData(TRUE); + + // Save settings to the registry + HKEY aRegKey = NULL; + LONG aResult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\GameSpy\\Voice2BuddyMFC", 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &aRegKey, NULL); + if (aResult == ERROR_SUCCESS) + { + RegSetValue(aRegKey, "LastLoginNickname", REG_SZ, m_Nickname, m_Nickname.GetLength()); + RegSetValue(aRegKey, "LastLoginEmail", REG_SZ, m_Email, m_Email.GetLength()); + RegCloseKey(aRegKey); + } + + + CDialog::OnOK(); +} + +void CLoginDlg::OnCancel() +{ + + CDialog::OnCancel(); +} + +BOOL CLoginDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Load settings from the registry + char aStringBuf[255]; + LONG aLongBuf; + + HKEY aRegKey = NULL; + LONG aResult = RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\GameSpy\\Voice2BuddyMFC", 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &aRegKey, NULL); + if (aResult == ERROR_SUCCESS) + { + // Load last session username + aLongBuf = 255; + aResult = RegQueryValue(aRegKey, "LastLoginNickname", aStringBuf, &aLongBuf); + if (aResult == ERROR_SUCCESS) + m_Nickname = aStringBuf; + + // Load last session email + aLongBuf = 255; + aResult = RegQueryValue(aRegKey, "LastLoginEmail", aStringBuf, &aLongBuf); + if (aResult == ERROR_SUCCESS) + m_Email = aStringBuf; + + RegCloseKey(aRegKey); + } + + // Reflect new settings into the view + UpdateData(FALSE); + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/LoginDlg.h b/code/gamespy/Voice2/Voice2BuddyMFC/LoginDlg.h new file mode 100644 index 00000000..dd0d2490 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/LoginDlg.h @@ -0,0 +1,50 @@ +#if !defined(AFX_LOGINDLG_H__0E2CE143_57CF_4FE7_9DFB_9C63388C0680__INCLUDED_) +#define AFX_LOGINDLG_H__0E2CE143_57CF_4FE7_9DFB_9C63388C0680__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// LoginDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + +class CLoginDlg : public CDialog +{ +// Construction +public: + CLoginDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CLoginDlg) + enum { IDD = IDD_LOGIN }; + CString m_Email; + CString m_Nickname; + CString m_Password; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CLoginDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CLoginDlg) + virtual void OnOK(); + virtual void OnCancel(); + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_LOGINDLG_H__0E2CE143_57CF_4FE7_9DFB_9C63388C0680__INCLUDED_) diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/SetupDlg.cpp b/code/gamespy/Voice2/Voice2BuddyMFC/SetupDlg.cpp new file mode 100644 index 00000000..bf54bc68 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/SetupDlg.cpp @@ -0,0 +1,236 @@ +// SetupDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "Voice2BuddyMFC.h" +#include "SetupDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define VOICE_THINK_TIMER_ID 101 +#define VOICE_THINK_TIMER_DELAY 10 + +enum SetupMessage +{ + V2B_STARTDEVICES = 0 +}; + +///////////////////////////////////////////////////////////////////////////// +// CSetupDlg dialog + + +CSetupDlg::CSetupDlg(CWnd* pParent /*=NULL*/) + : CDialog(CSetupDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CSetupDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void CSetupDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CSetupDlg) + DDX_Control(pDX, IDC_ACTIVATELEVEL, m_ActivateLevel); + DDX_Control(pDX, IDC_VOICELEVEL, m_VoiceLevelCtrl); + DDX_Control(pDX, IDC_ISSPEAKING, m_IsSpeakingCtrl); + DDX_Control(pDX, IDC_PLAYBACKCOMBO, m_PlaybackCombo); + DDX_Control(pDX, IDC_CAPTURECOMBO, m_CaptureCombo); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CSetupDlg, CDialog) + //{{AFX_MSG_MAP(CSetupDlg) + ON_CBN_SELCHANGE(IDC_CAPTURECOMBO, OnSelChangeCaptureCombo) + ON_CBN_SELCHANGE(IDC_PLAYBACKCOMBO, OnSelChangePlaybackCombo) + ON_WM_TIMER() + ON_WM_HSCROLL() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CSetupDlg message handlers + +BOOL CSetupDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Begin Enumerate devices + int aNumDevices = gvListDevices(m_SetupInfo->m_DeviceInfoArray, MAX_DEVICES, GV_CAPTURE_AND_PLAYBACK); + + // Populate the combo boxes + for (int aDeviceNum = 0; aDeviceNum < aNumDevices; aDeviceNum++) + { + // Add capture devices to display list + if (GV_CAPTURE & m_SetupInfo->m_DeviceInfoArray[aDeviceNum].m_deviceType) + { + // Set the item data to the device number (this may differ from the row due to sorting) + m_CaptureCombo.InsertString(0, m_SetupInfo->m_DeviceInfoArray[aDeviceNum].m_name); + m_CaptureCombo.SetItemData(0, aDeviceNum); + if (GV_CAPTURE & m_SetupInfo->m_DeviceInfoArray[aDeviceNum].m_defaultDevice) + m_CaptureCombo.SetCurSel(0); + } + + // Add playback devices to display list + if (GV_PLAYBACK & m_SetupInfo->m_DeviceInfoArray[aDeviceNum].m_deviceType) + { + // Set the item data to the device number (this may differ from the row due to sorting) + m_PlaybackCombo.InsertString(0, m_SetupInfo->m_DeviceInfoArray[aDeviceNum].m_name); + m_PlaybackCombo.SetItemData(0, aDeviceNum); + if (GV_PLAYBACK & m_SetupInfo->m_DeviceInfoArray[aDeviceNum].m_defaultDevice) + m_PlaybackCombo.SetCurSel(0); + } + } + + // If no device has been set, try setting to the first one + if (m_CaptureCombo.GetCurSel() == -1) + m_CaptureCombo.SetCurSel(0); + if (m_PlaybackCombo.GetCurSel() == -1) + m_PlaybackCombo.SetCurSel(0); + + // Try to start the capture and playback device + StartSelCaptureDevice(); + StartSelPlaybackDevice(); + + UpdateData(FALSE); + + SetTimer(VOICE_THINK_TIMER_ID, VOICE_THINK_TIMER_DELAY, NULL); + + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + + +// Switch the active device +void CSetupDlg::OnSelChangeCaptureCombo() +{ + StartSelCaptureDevice(); +} + +// Switch the active device +void CSetupDlg::OnSelChangePlaybackCombo() +{ + StartSelPlaybackDevice(); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void CSetupDlg::StartSelCaptureDevice() +{ + // Stop the previous device + if (m_SetupInfo->m_CaptureDevice != NULL) + { + gvStopDevice(m_SetupInfo->m_CaptureDevice, GV_CAPTURE); + gvFreeDevice(m_SetupInfo->m_CaptureDevice); + } + + // Get the selected device id + int aSelDevice = m_CaptureCombo.GetCurSel(); + if (aSelDevice == -1) + return; + + int aDeviceNum = m_CaptureCombo.GetItemData(aSelDevice); + if (aDeviceNum == CB_ERR) + return; + + // Create and start the new device + m_SetupInfo->m_CaptureDevice = gvNewDevice(m_SetupInfo->m_DeviceInfoArray[aDeviceNum].m_id, GV_CAPTURE); + if (m_SetupInfo->m_CaptureDevice != NULL) + gvStartDevice(m_SetupInfo->m_CaptureDevice, GV_CAPTURE); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void CSetupDlg::StartSelPlaybackDevice() +{ + // Stop the previous device + if (m_SetupInfo->m_PlaybackDevice != NULL) + { + gvStopDevice(m_SetupInfo->m_PlaybackDevice, GV_PLAYBACK); + gvFreeDevice(m_SetupInfo->m_PlaybackDevice); + } + + // Get the selected device id + int aSelDevice = m_PlaybackCombo.GetCurSel(); + if (aSelDevice == -1) + return; + + int aDeviceNum = m_PlaybackCombo.GetItemData(aSelDevice); + if (aDeviceNum == CB_ERR) + return; + + // Create and start the new device + m_SetupInfo->m_PlaybackDevice = gvNewDevice(m_SetupInfo->m_DeviceInfoArray[aDeviceNum].m_id, GV_PLAYBACK); + if (m_SetupInfo->m_PlaybackDevice != NULL) + gvStartDevice(m_SetupInfo->m_PlaybackDevice, GV_PLAYBACK); +} + +static GVBool gMute = GVFalse; + +void CSetupDlg::OnTimer(UINT nIDEvent) +{ + gvThink(); + + // Check for voice data, play as local echo + int aBytesAvailable = gvGetAvailableCaptureBytes(m_SetupInfo->m_CaptureDevice); + if (aBytesAvailable > 0) + { + GVByte aBuffer[1024]; + int aLength = 1024; + GVFrameStamp aFrameStamp; + GVScalar aVolume = 0; + + GVBool gotPacket = gvCapturePacket(m_SetupInfo->m_CaptureDevice, aBuffer, &aLength, &aFrameStamp, &aVolume); + if (gotPacket == GVTrue) + { + gvPlayPacket(m_SetupInfo->m_PlaybackDevice, aBuffer, aLength, 0, aFrameStamp, gMute); + m_IsSpeakingCtrl.ShowWindow(SW_SHOW); + } + else + { + m_IsSpeakingCtrl.ShowWindow(SW_HIDE); + } + + // Display the volume level + m_VoiceLevelCtrl.SetPos( (int)(aVolume*100) ); + } + + + CDialog::OnTimer(nIDEvent); +} + +void CSetupDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) +{ + if (pScrollBar == (CScrollBar*)&m_ActivateLevel) + { + double aThreshold = (float)m_ActivateLevel.GetPos()/100; + if (m_SetupInfo->m_CaptureDevice != NULL) + gvSetCaptureThreshold(m_SetupInfo->m_CaptureDevice, aThreshold); + } + CDialog::OnHScroll(nSBCode, nPos, pScrollBar); +} + +BOOL CSetupDlg::DestroyWindow() +{ + // Stop the devices + if (m_SetupInfo->m_CaptureDevice != NULL) + gvStopDevice(m_SetupInfo->m_CaptureDevice, GV_CAPTURE); + if (m_SetupInfo->m_PlaybackDevice != NULL) + gvStopDevice(m_SetupInfo->m_PlaybackDevice, GV_PLAYBACK); + + + // Kill the think timer + KillTimer(VOICE_THINK_TIMER_ID); + + + return CDialog::DestroyWindow(); +} diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/SetupDlg.h b/code/gamespy/Voice2/Voice2BuddyMFC/SetupDlg.h new file mode 100644 index 00000000..a832760b --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/SetupDlg.h @@ -0,0 +1,81 @@ +#if !defined(AFX_SETUPDLG_H__36B5B6D7_690D_4628_92E4_96B2CAE34B82__INCLUDED_) +#define AFX_SETUPDLG_H__36B5B6D7_690D_4628_92E4_96B2CAE34B82__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// SetupDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CSetupDlg dialog +#include "../../voice2/gv.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +const unsigned int MAX_DEVICES = 10; + +struct VoiceSetupInfo +{ + GVDevice m_PlaybackDevice; + GVDevice m_CaptureDevice; + GVDeviceInfo m_DeviceInfoArray[MAX_DEVICES]; +}; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +class CSetupDlg : public CDialog +{ +public: + VoiceSetupInfo* m_SetupInfo; + + void StartSelCaptureDevice(); + void StartSelPlaybackDevice(); + + + // MFC STUFF BELOW + +// Construction +public: + CSetupDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CSetupDlg) + enum { IDD = IDD_SETUPDIALOG }; + CSliderCtrl m_ActivateLevel; + CProgressCtrl m_VoiceLevelCtrl; + CStatic m_IsSpeakingCtrl; + CComboBox m_PlaybackCombo; + CComboBox m_CaptureCombo; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CSetupDlg) + public: + virtual BOOL DestroyWindow(); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CSetupDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnSelChangeCaptureCombo(); + afx_msg void OnSelChangePlaybackCombo(); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_SETUPDLG_H__36B5B6D7_690D_4628_92E4_96B2CAE34B82__INCLUDED_) diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/StdAfx.cpp b/code/gamespy/Voice2/Voice2BuddyMFC/StdAfx.cpp new file mode 100644 index 00000000..16c74863 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// Voice2BuddyMFC.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/StdAfx.h b/code/gamespy/Voice2/Voice2BuddyMFC/StdAfx.h new file mode 100644 index 00000000..19665974 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/StdAfx.h @@ -0,0 +1,27 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__FCEAFCA1_440C_4599_B89A_56952F1DB30C__INCLUDED_) +#define AFX_STDAFX_H__FCEAFCA1_440C_4599_B89A_56952F1DB30C__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__FCEAFCA1_440C_4599_B89A_56952F1DB30C__INCLUDED_) diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.cpp b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.cpp new file mode 100644 index 00000000..fca45c65 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.cpp @@ -0,0 +1,74 @@ +// Voice2BuddyMFC.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "Voice2BuddyMFC.h" +#include "Voice2BuddyMFCDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CVoice2BuddyMFCApp + +BEGIN_MESSAGE_MAP(CVoice2BuddyMFCApp, CWinApp) + //{{AFX_MSG_MAP(CVoice2BuddyMFCApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CVoice2BuddyMFCApp construction + +CVoice2BuddyMFCApp::CVoice2BuddyMFCApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CVoice2BuddyMFCApp object + +CVoice2BuddyMFCApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CVoice2BuddyMFCApp initialization + +BOOL CVoice2BuddyMFCApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. +/* +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif +*/ + CVoice2BuddyMFCDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + // TODO: Place code here to handle when the dialog is + // dismissed with OK + } + else if (nResponse == IDCANCEL) + { + // TODO: Place code here to handle when the dialog is + // dismissed with Cancel + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.h b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.h new file mode 100644 index 00000000..d8196222 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.h @@ -0,0 +1,49 @@ +// Voice2BuddyMFC.h : main header file for the VOICE2BUDDYMFC application +// + +#if !defined(AFX_VOICE2BUDDYMFC_H__B124CD13_D911_4447_AF83_E1503E9147BF__INCLUDED_) +#define AFX_VOICE2BUDDYMFC_H__B124CD13_D911_4447_AF83_E1503E9147BF__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CVoice2BuddyMFCApp: +// See Voice2BuddyMFC.cpp for the implementation of this class +// + +class CVoice2BuddyMFCApp : public CWinApp +{ +public: + CVoice2BuddyMFCApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CVoice2BuddyMFCApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CVoice2BuddyMFCApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_VOICE2BUDDYMFC_H__B124CD13_D911_4447_AF83_E1503E9147BF__INCLUDED_) diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.rc b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.rc new file mode 100644 index 00000000..9feae435 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFC.rc @@ -0,0 +1,300 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\Voice2BuddyMFC.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\Voice2BuddyMFC.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ABOUTBOX DIALOG DISCARDABLE 0, 0, 235, 55 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About Voice2BuddyMFC" +FONT 8, "MS Sans Serif" +BEGIN + ICON IDR_MAINFRAME,IDC_STATIC,11,17,20,20 + LTEXT "Voice2BuddyMFC Version 1.0",IDC_STATIC,40,10,119,8, + SS_NOPREFIX + LTEXT "Copyright (C) 2004",IDC_STATIC,40,25,119,8 + DEFPUSHBUTTON "OK",IDOK,178,7,50,14,WS_GROUP +END + +IDD_VOICE2BUDDYMFC_DIALOG DIALOGEX 0, 0, 150, 126 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "Voice2 Buddy MFC" +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "Exit",IDCANCEL,7,105,50,14 + PUSHBUTTON "Setup",ID_SETUP,7,78,50,14,WS_DISABLED + PUSHBUTTON "Voice Chat",ID_VOICECHAT,7,59,50,14,WS_DISABLED + LISTBOX IDC_BUDDYLIST,64,7,79,112,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_HSCROLL | + WS_TABSTOP + LTEXT "Select a buddy to chat with --->",IDC_STATIC,7,7,50,46 +END + +IDD_LOGIN DIALOG DISCARDABLE 0, 0, 143, 95 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Login" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,75,74,50,14 + PUSHBUTTON "Cancel",IDCANCEL,18,74,50,14 + EDITTEXT IDC_EMAIL,48,7,88,14,ES_AUTOHSCROLL + EDITTEXT IDC_NICKNAME,48,28,88,14,ES_AUTOHSCROLL + EDITTEXT IDC_PASSWORD,48,49,88,14,ES_PASSWORD | ES_AUTOHSCROLL + RTEXT "Email:",IDC_STATIC,7,7,36,14,SS_CENTERIMAGE + RTEXT "Nickname:",IDC_STATIC,7,28,36,14,SS_CENTERIMAGE + RTEXT "Password:",IDC_STATIC,7,49,36,14,SS_CENTERIMAGE +END + +IDD_SESSION DIALOG DISCARDABLE 0, 0, 121, 79 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Session" +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "Exit",IDCANCEL,35,58,50,14 + CTEXT "",IDC_DISPLAYTEXT,7,7,107,23 + CONTROL 146,IDC_SPEAKING1,"Static",SS_BITMAP | SS_SUNKEN | NOT + WS_VISIBLE,25,36,28,14 + CONTROL 134,IDC_SPEAKING2,"Static",SS_BITMAP | SS_SUNKEN | NOT + WS_VISIBLE,68,36,28,14 +END + +IDD_SETUPDIALOG DIALOG DISCARDABLE 0, 0, 197, 207 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Voice Setup" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,73,186,50,14 + COMBOBOX IDC_PLAYBACKCOMBO,43,82,136,105,CBS_DROPDOWNLIST | + CBS_SORT | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_CAPTURECOMBO,43,116,136,96,CBS_DROPDOWNLIST | + CBS_SORT | WS_VSCROLL | WS_TABSTOP + LTEXT "Capture Device:",IDC_STATIC,43,103,98,12 + LTEXT "Playback Device:",IDC_STATIC,43,68,117,12 + CONTROL 136,IDC_SPEAKERBITMAP,"Static",SS_BITMAP,13,74,21,20 + CONTROL 135,IDC_MICROPHONEBITMAP,"Static",SS_BITMAP,13,106,21,20 + GROUPBOX "",IDC_STATIC,7,58,183,82,BS_CENTER + CONTROL "Slider1",IDC_ACTIVATELEVEL,"msctls_trackbar32",TBS_BOTH | + TBS_NOTICKS | WS_TABSTOP,48,146,105,12 + CONTROL "Progress1",IDC_VOICELEVEL,"msctls_progress32",WS_BORDER, + 51,160,97,11 + GROUPBOX "",IDC_STATIC,7,138,183,40 + LTEXT "Activate",IDC_STATIC,155,147,27,10 + LTEXT "Volume",IDC_STATIC,155,161,27,10 + CONTROL 146,IDC_ISSPEAKING,"Static",SS_BITMAP,16,153,27,12 + CONTROL 142,IDC_STATIC,"Static",SS_BITMAP | SS_SUNKEN,7,7,185,52 +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "Voice2BuddyMFC MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "Voice2BuddyMFC\0" + VALUE "LegalCopyright", "Copyright (C) 2004\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "Voice2BuddyMFC.EXE\0" + VALUE "ProductName", "Voice2BuddyMFC Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_ABOUTBOX, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 228 + TOPMARGIN, 7 + BOTTOMMARGIN, 48 + END + + IDD_VOICE2BUDDYMFC_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 143 + TOPMARGIN, 7 + BOTTOMMARGIN, 119 + END + + IDD_LOGIN, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 136 + TOPMARGIN, 7 + BOTTOMMARGIN, 88 + END + + IDD_SESSION, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 114 + TOPMARGIN, 7 + BOTTOMMARGIN, 72 + END + + IDD_SETUPDIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 190 + TOPMARGIN, 7 + BOTTOMMARGIN, 200 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +IDB_SPEAKER BITMAP DISCARDABLE "res\\microsoft-speaker.bmp" +IDB_MICROPHONE BITMAP DISCARDABLE "res\\microsoft-microphone.bmp" +IDB_GAMESPYLOGO BITMAP DISCARDABLE "res\\logo270x83.bmp" +IDB_SPEAKING BITMAP DISCARDABLE "res\\speaking.bmp" +IDB_GAMESPYLOGO1 BITMAP DISCARDABLE "res\\gamespyl.bmp" +IDB_SPEAKING2 BITMAP DISCARDABLE "res\\speaking2.bmp" + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDS_ABOUTBOX "&About Voice2BuddyMFC..." +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\Voice2BuddyMFC.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFCDlg.cpp b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFCDlg.cpp new file mode 100644 index 00000000..54281b3c --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFCDlg.cpp @@ -0,0 +1,600 @@ +// Voice2BuddyMFCDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "Voice2BuddyMFC.h" +#include "Voice2BuddyMFCDlg.h" + +#include "LoginDlg.h" +#include "SetupDlg.h" +#include "VoiceSessionDlg.h" + + +#include "../../common/gsAvailable.h" +#include "../../voice2/gv.h" + + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +enum V2BuddyMessage +{ + V2B_MSG_DOSETUP, + V2B_MSG_DOLOGIN, + V2B_MSG_DOVOICECHAT, +}; + +#define THINK_TIMER_ID 100 +#define THINK_TIMER_DELAY 10 +#define GAME_NAME "gmtest" +#define NAMESPACE_GAMESPY_SHARED 0 +#define PRODUCTID_GMTEST 1 // can't use 0! + +#define V2B_GP_LOCATION "Voice2BuddyMFC" +#define V2B_GP_STATUS_IDLE "Idle" // free to invite +#define V2B_GP_STATUS_CHATTING "Chatting" // actively chatting +#define V2B_GP_INVITE_DECLINED "Voice invitation declined." +#define V2B_GP_INVITE_ACCEPTED "Voice invitation accepted." + +// Utility to check buddy status and make sure they're running V2B +BOOL IsBuddyUsingV2B(GPConnection* theConnection, GPProfile theProfileId, GPBuddyStatus* theStatus); + + +///////////////////////////////////////////////////////////////////////////// +// CAboutDlg dialog used for App About + +class CAboutDlg : public CDialog +{ +public: + CAboutDlg(); + +// Dialog Data + //{{AFX_DATA(CAboutDlg) + enum { IDD = IDD_ABOUTBOX }; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CAboutDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + //{{AFX_MSG(CAboutDlg) + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) +{ + //{{AFX_DATA_INIT(CAboutDlg) + //}}AFX_DATA_INIT +} + +void CAboutDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CAboutDlg) + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) + //{{AFX_MSG_MAP(CAboutDlg) + // No message handlers + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CVoice2BuddyMFCDlg dialog + +CVoice2BuddyMFCDlg::CVoice2BuddyMFCDlg(CWnd* pParent /*=NULL*/) + : CDialog(CVoice2BuddyMFCDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CVoice2BuddyMFCDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT + // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); +} + +void CVoice2BuddyMFCDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CVoice2BuddyMFCDlg) + DDX_Control(pDX, ID_SETUP, m_SetupButton); + DDX_Control(pDX, ID_VOICECHAT, m_VoiceChatButton); + DDX_Control(pDX, IDC_BUDDYLIST, m_BuddyList); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CVoice2BuddyMFCDlg, CDialog) + //{{AFX_MSG_MAP(CVoice2BuddyMFCDlg) + ON_WM_SYSCOMMAND() + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_BN_CLICKED(ID_SETUP, OnSetup) + ON_BN_CLICKED(ID_VOICECHAT, OnVoiceChat) + ON_BN_CLICKED(IDCANCEL, OnExit) + ON_WM_TIMER() + ON_WM_DESTROY() + ON_LBN_SELCHANGE(IDC_BUDDYLIST, OnSelchangeBuddylist) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CVoice2BuddyMFCDlg message handlers + +BOOL CVoice2BuddyMFCDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Add "About..." menu item to system menu. + + // IDM_ABOUTBOX must be in the system command range. + ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); + ASSERT(IDM_ABOUTBOX < 0xF000); + + CMenu* pSysMenu = GetSystemMenu(FALSE); + if (pSysMenu != NULL) + { + CString strAboutMenu; + strAboutMenu.LoadString(IDS_ABOUTBOX); + if (!strAboutMenu.IsEmpty()) + { + pSysMenu->AppendMenu(MF_SEPARATOR); + pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); + } + } + + // Set the icon for this dialog. The framework does this automatically + // when the application's main window is not a dialog + SetIcon(m_hIcon, TRUE); // Set big icon + SetIcon(m_hIcon, FALSE); // Set small icon + + // We're not connected yet + m_GP = NULL; + m_Initialized = FALSE; + m_Connected = FALSE; + m_InvitedPlayer = 0; + m_MyProfileId = 0; + + // Start network + SocketStartUp(); + + // Init voice SDK (pre-init, do not start devices yet) + GVBool aResult = gvStartup(m_hWnd); + if (aResult != GVTrue) + { + MessageBox("Failed on gvStartup!"); + PostQuitMessage(0); + } + + // Set the codec (this sample hard codes it) + gvSetSampleRate(GVRate_16KHz); + aResult = gvSetCodec(GVCodecSuperHighQuality); + if (aResult != GVTrue) + { + MessageBox("Failed on gvSetCodec!"); + PostQuitMessage(0); + } + + // Disable the "voice chat" button until a buddy is selected + m_VoiceChatButton.EnableWindow(FALSE); + + // Clear the setup info + memset(&m_SetupInfo, 0, sizeof(m_SetupInfo)); + + // Show the setup dialog + PostMessage(WM_USER+1, V2B_MSG_DOSETUP, 0); + + return TRUE; // return TRUE unless you set the focus to a control +} + +void CVoice2BuddyMFCDlg::OnSysCommand(UINT nID, LPARAM lParam) +{ + if ((nID & 0xFFF0) == IDM_ABOUTBOX) + { + CAboutDlg dlgAbout; + dlgAbout.DoModal(); + } + else + { + CDialog::OnSysCommand(nID, lParam); + } +} + +// If you add a minimize button to your dialog, you will need the code below +// to draw the icon. For MFC applications using the document/view model, +// this is automatically done for you by the framework. + +void CVoice2BuddyMFCDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CDialog::OnPaint(); + } +} + +// The system calls this to obtain the cursor to display while the user drags +// the minimized window. +HCURSOR CVoice2BuddyMFCDlg::OnQueryDragIcon() +{ + return (HCURSOR) m_hIcon; +} + +void CVoice2BuddyMFCDlg::OnSetup() +{ + // Setup and calibrate the voice hardware + CSetupDlg aSetupDlg; + aSetupDlg.m_SetupInfo = &m_SetupInfo; + aSetupDlg.DoModal(); +} + +void CVoice2BuddyMFCDlg::OnVoiceChat() +{ + // Get the selected buddy + int selIndex = m_BuddyList.GetCurSel(); + if (selIndex == -1) + return; + + // Get the buddy profile + GPProfile aProfile = m_BuddyList.GetItemData(selIndex); + + // Make sure the buddy is using V2B + GPBuddyStatus aStatus; + if (FALSE == IsBuddyUsingV2B(&m_GP, aProfile, &aStatus)) + { + MessageBox("Buddy is not running V2B."); + return; + } + + // Make sure this buddy isn't chatting already + if (strcmp(V2B_GP_STATUS_CHATTING, aStatus.locationString) == 0) + { + MessageBox("Buddy is currently involved in a voice chat."); + return; + } + + // Invite the buddy to voice chat + gpInvitePlayer(&m_GP, aProfile, PRODUCTID_GMTEST, NULL); + m_InvitedPlayer = aProfile; // remember who we invited +} + +void CVoice2BuddyMFCDlg::OnExit() +{ + // TODO: Add your control notification handler code here + CDialog::OnCancel(); +} + +LRESULT CVoice2BuddyMFCDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) +{ + if (message == (WM_USER+1)) + { + // Process custom message + switch(wParam) + { + case V2B_MSG_DOSETUP: + { + // Show the setup dialog + OnSetup(); + + // Pop up the login dialog + PostMessage(WM_USER+1, V2B_MSG_DOLOGIN, 0); + + // Start the app timer + SetTimer(THINK_TIMER_ID, THINK_TIMER_DELAY, NULL); + return 0; + } + case V2B_MSG_DOLOGIN: + { + // Show the login dialog + CLoginDlg aDlg; + int result = aDlg.DoModal(); + if (result == IDCANCEL) + PostQuitMessage(0); + else + DoLogin(aDlg.m_Email, aDlg.m_Nickname, aDlg.m_Password); + return 0; + } + case V2B_MSG_DOVOICECHAT: + { + // Create the voice session dialog + CVoiceSessionDlg aVoiceSessionDlg; + aVoiceSessionDlg.m_NNCookie = lParam; + aVoiceSessionDlg.m_SetupInfo = m_SetupInfo; + + // We use the profileId of the host as the NNCookie + // now, check to see if we're the host + if (m_MyProfileId == aVoiceSessionDlg.m_NNCookie) + aVoiceSessionDlg.m_IsHost = TRUE; + else + aVoiceSessionDlg.m_IsHost = FALSE; + + // Run the dialog + aVoiceSessionDlg.DoModal(); + return 0; + } + }; + } // end custom messages + return CDialog::WindowProc(message, wParam, lParam); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Utility to check if a buddy is using this app +BOOL IsBuddyUsingV2B(GPConnection* theConnection, GPProfile theProfileId, GPBuddyStatus* theStatus) +{ + GPBuddyStatus aTempStatus; + GPBuddyStatus* aStatus; + if (theStatus != NULL) + aStatus = theStatus; // store location in return value + else + aStatus = &aTempStatus; // store location in temp + + // get the internal buddy index + int aBuddyIndex; + GPResult aResult = gpGetBuddyIndex(theConnection, theProfileId, &aBuddyIndex); + if (aResult != GP_NO_ERROR) + return FALSE; // not a buddy + + // Get the buddy status + aResult = gpGetBuddyStatus(theConnection, aBuddyIndex, aStatus); + if (aResult != GP_NO_ERROR) + return FALSE; // couldn't get status + + // Is the buddy using V2B? + if (strcmp(V2B_GP_LOCATION, aStatus->locationString) != 0) + return FALSE; // no using V2B + + return TRUE; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void OnGPError(GPConnection* theConnection, GPErrorArg* theArg, void* theParam) +{ + CVoice2BuddyMFCDlg* aDialog = (CVoice2BuddyMFCDlg*)theParam; + + char buf[1024]; + sprintf(buf, "GP ERROR: %s", theArg->errorString); + aDialog->MessageBox(buf); + + if (theArg->fatal == GP_FATAL) + aDialog->m_Connected = FALSE; + GSI_UNUSED(theConnection); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void OnGPBuddyInfo(GPConnection* theConnection, GPGetInfoResponseArg* theArg, void* theParam) +{ + CVoice2BuddyMFCDlg* aDialog = (CVoice2BuddyMFCDlg*)theParam; + + // Make sure this name is in the list + int anIndex = aDialog->m_BuddyList.FindString(0, theArg->nick); + if (anIndex == -1) + { + int anIndex = aDialog->m_BuddyList.AddString(theArg->nick); + if (anIndex != -1) + aDialog->m_BuddyList.SetItemData(anIndex, theArg->profile); + } + + GSI_UNUSED(theConnection); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void OnGPBuddyStatus(GPConnection* theConnection, GPRecvBuddyStatusArg* theArg, void* theParam) +{ + CVoice2BuddyMFCDlg* aDialog = (CVoice2BuddyMFCDlg*)theParam; + + // Retrieve info for the buddy (nickname etc.) + // will return with file cache information, if available + gpGetInfo(theConnection, theArg->profile, GP_CHECK_CACHE, GP_NON_BLOCKING, (GPCallback)OnGPBuddyInfo, theParam); + GSI_UNUSED(aDialog); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void OnGPBuddyMessage(GPConnection* theConnection, GPRecvBuddyMessageArg* theArg, void* theParam) +{ + CVoice2BuddyMFCDlg* aDialog = (CVoice2BuddyMFCDlg*)theParam; + + // Ignore messages that are not from V2B + if (FALSE == IsBuddyUsingV2B(theConnection, theArg->profile, NULL)) + return; + + // If this wasn't the guy we invited, ignore it + if (theArg->profile != aDialog->m_InvitedPlayer) + return; + + // Is this an invite accept? + if (strcmp(V2B_GP_INVITE_ACCEPTED, theArg->message)==0) + aDialog->PostMessage(WM_USER+1, V2B_MSG_DOVOICECHAT, aDialog->m_MyProfileId); + + // Is this an invite decline? + if (strcmp(V2B_GP_INVITE_DECLINED, theArg->message)==0) + aDialog->MessageBox(theArg->message); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void OnGPInvite(GPConnection* theConnection, GPRecvGameInviteArg* theArg, void* theParam) +{ + CVoice2BuddyMFCDlg* aDialog = (CVoice2BuddyMFCDlg*)theParam; + + // Make sure the invite is from V2B + if (FALSE == IsBuddyUsingV2B(theConnection, theArg->profile, NULL)) + return; + + // Find the nickname of the buddy, for display + // (may not be the selected buddy) + CString aBuddyName = ""; + int anIndex = 0; + while(anIndex < aDialog->m_BuddyList.GetCount()) + { + // If this is the buddy, store off the name + if (aDialog->m_BuddyList.GetItemData(anIndex) == (unsigned int)theArg->profile) + { + aDialog->m_BuddyList.GetText(anIndex, aBuddyName); + break; + } + anIndex++; + } + if (aBuddyName.IsEmpty()) + return; // buddy not found, ignore the invite + + // Ask the user if they want to accept + char buf[255]; + sprintf(buf, "%s has invited you to voice chat. Accept?", (LPCSTR)aBuddyName); + int aDialogResult = aDialog->MessageBox(buf, "Voice Chat", MB_YESNO); + if (aDialogResult == IDYES) + { + // begin voice session + gpSendBuddyMessage(&aDialog->m_GP, theArg->profile, V2B_GP_INVITE_ACCEPTED); + aDialog->PostMessage(WM_USER+1, V2B_MSG_DOVOICECHAT, theArg->profile); + } + else + { + // send rejection + gpSendBuddyMessage(&aDialog->m_GP, theArg->profile, V2B_GP_INVITE_DECLINED); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void OnGPConnect(GPConnection* theConnection, GPConnectResponseArg* theArg, void* theParam) +{ + CVoice2BuddyMFCDlg* aDialog = (CVoice2BuddyMFCDlg*)theParam; + + if (theArg->result == GP_NO_ERROR) + { + aDialog->m_Connected = TRUE; + aDialog->m_SetupButton.EnableWindow(TRUE); + aDialog->m_MyProfileId = theArg->profile; + } + else + { + aDialog->MessageBox("Failed to connect!"); + PostQuitMessage(0); + } + GSI_UNUSED(theConnection); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void CVoice2BuddyMFCDlg::DoLogin(const CString& theEmail, const CString& theNickname, const CString& thePassword) +{ + // Begin the availability check + GSIACResult aResult = GSIACWaiting; + GSIStartAvailableCheck(GAME_NAME); + while(aResult == GSIACWaiting) + { + aResult = GSIAvailableCheckThink(); + Sleep(20); + } + + // Initialize the Presence SDK + GPResult result = gpInitialize(&m_GP, PRODUCTID_GMTEST, NAMESPACE_GAMESPY_SHARED, GP_PARTNERID_GAMESPY); + if (result != GP_NO_ERROR) + { + MessageBox("Failed on gpInitialize\r\n"); + PostQuitMessage(0); + } + + gpSetCallback(&m_GP, GP_ERROR, (GPCallback)OnGPError, this); + gpSetCallback(&m_GP, GP_RECV_BUDDY_STATUS, (GPCallback)OnGPBuddyStatus, this); + gpSetCallback(&m_GP, GP_RECV_GAME_INVITE, (GPCallback)OnGPInvite, this); + gpSetCallback(&m_GP, GP_RECV_BUDDY_MESSAGE, (GPCallback)OnGPBuddyMessage,this); + m_Initialized = TRUE; + + // Connect to the presence server (goes to callback even though it's blocking) + gpConnect(&m_GP, theNickname, theEmail, thePassword, GP_NO_FIREWALL, GP_BLOCKING, (GPCallback)OnGPConnect, this); + + // Set the think timer if we connected + // (m_Connected is set from the callback function above) + if (m_Connected) + SetTimer(THINK_TIMER_ID, THINK_TIMER_DELAY, NULL); + + // Set our location + gpSetStatus(&m_GP, GP_ONLINE, V2B_GP_STATUS_IDLE, V2B_GP_LOCATION); +} + +void CVoice2BuddyMFCDlg::OnTimer(UINT nIDEvent) +{ + // Win32 timers can be called concurrently by the OS. + // (you can get a second timer callback before the first finishes!) + // CRITICAL_SECTION will not prevent this as Win32 allows concurrent access + static bool inTimer = false; + if (inTimer) + return; + + // Prevent windows from entering the timer again + inTimer = true; + + if (m_Connected) + gpProcess(&m_GP); + + // Allow windows to enter the timer again + inTimer = false; + + CDialog::OnTimer(nIDEvent); +} + +void CVoice2BuddyMFCDlg::OnDestroy() +{ + CDialog::OnDestroy(); + + // Kill the think timer + KillTimer(THINK_TIMER_ID); + + // Disconnect from GP + if (m_Connected) + gpDisconnect(&m_GP); + + // Destruct GP + if (m_Initialized) + gpDestroy(&m_GP); + + gvCleanup(); +} + +void CVoice2BuddyMFCDlg::OnSelchangeBuddylist() +{ + int aSel = m_BuddyList.GetCurSel(); + if (aSel == -1) + m_VoiceChatButton.EnableWindow(FALSE); + else + m_VoiceChatButton.EnableWindow(TRUE); +} diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFCDlg.h b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFCDlg.h new file mode 100644 index 00000000..fdd4d25b --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/Voice2BuddyMFCDlg.h @@ -0,0 +1,74 @@ +// Voice2BuddyMFCDlg.h : header file +// + +#if !defined(AFX_VOICE2BUDDYMFCDLG_H__F4BA3DE6_2C65_4B7D_A1BC_A9C3BAF53DEE__INCLUDED_) +#define AFX_VOICE2BUDDYMFCDLG_H__F4BA3DE6_2C65_4B7D_A1BC_A9C3BAF53DEE__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +///////////////////////////////////////////////////////////////////////////// +// CVoice2BuddyMFCDlg dialog +#include "../../gp/gp.h" +#include "SetupDlg.h" + +class CVoice2BuddyMFCDlg : public CDialog +{ + // Non MFC +public: + GPConnection m_GP; + BOOL m_Initialized; + BOOL m_Connected; + unsigned int m_NNCookie; + GPProfile m_InvitedPlayer; // the last player we invited + GPProfile m_MyProfileId; // the local player's profile id + + VoiceSetupInfo m_SetupInfo; + + void DoLogin(const CString& theEmail, const CString& theNickname, const CString& thePassword); + + +// Construction +public: + CVoice2BuddyMFCDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CVoice2BuddyMFCDlg) + enum { IDD = IDD_VOICE2BUDDYMFC_DIALOG }; + CButton m_SetupButton; + CButton m_VoiceChatButton; + CListBox m_BuddyList; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CVoice2BuddyMFCDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam); + //}}AFX_VIRTUAL + +// Implementation +protected: + HICON m_hIcon; + + // Generated message map functions + //{{AFX_MSG(CVoice2BuddyMFCDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnSysCommand(UINT nID, LPARAM lParam); + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + afx_msg void OnSetup(); + afx_msg void OnVoiceChat(); + afx_msg void OnExit(); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnDestroy(); + afx_msg void OnSelchangeBuddylist(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_VOICE2BUDDYMFCDLG_H__F4BA3DE6_2C65_4B7D_A1BC_A9C3BAF53DEE__INCLUDED_) diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/VoiceSessionDlg.cpp b/code/gamespy/Voice2/Voice2BuddyMFC/VoiceSessionDlg.cpp new file mode 100644 index 00000000..cdbcccc6 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/VoiceSessionDlg.cpp @@ -0,0 +1,397 @@ +// VoiceSessionDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "Voice2BuddyMFC.h" +#include "VoiceSessionDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define THINK_TIMER_ID 100+2 +#define THINK_TIMER_DELAY 20 + + +///////////////////////////////////////////////////////////////////////////// +// CVoiceSessionDlg dialog +struct VoicePacket +{ + GVFrameStamp mFrameStamp; + int mDataLength; + GVByte mBuffer[1024]; // most likely get < 256 bytes +}; +const int gVoicePacketHeaderSize = sizeof(GVFrameStamp)+sizeof(int); + + +CVoiceSessionDlg::CVoiceSessionDlg(CWnd* pParent /*=NULL*/) + : CDialog(CVoiceSessionDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CVoiceSessionDlg) + m_DisplayText = _T(""); + //}}AFX_DATA_INIT +} + + +void CVoiceSessionDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CVoiceSessionDlg) + DDX_Control(pDX, IDC_SPEAKING2, m_RemoteSpeaking); + DDX_Control(pDX, IDC_SPEAKING1, m_LocalSpeaking); + DDX_Text(pDX, IDC_DISPLAYTEXT, m_DisplayText); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CVoiceSessionDlg, CDialog) + //{{AFX_MSG_MAP(CVoiceSessionDlg) + ON_WM_TIMER() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CVoiceSessionDlg message handlers +void CVoiceSessionDlg::OnTimer(UINT nIDEvent) +{ + // Win32 timers can be called concurrently by the OS. + // (you can get a second timer callback before the first finishes!) + // CRITICAL_SECTION will not prevent this as Win32 allows concurrent access + static bool inTimer = false; + if (inTimer) + return; + + // Prevent windows from entering the timer again + inTimer = true; + + // Process voice data + gvThink(); + + // Process NN + NNThink(); + + // Process network traffic + if (m_Socket) + gt2Think(m_Socket); + + // If we're not connected we don't need to do the rest + if (!m_Connection || (gt2GetConnectionState(m_Connection) != GT2Connected)) + { + inTimer = false; + return; + } + + // Show a bitmap if the sources are speaking + static bool wasLocalSpeaking = false; + static bool wasRemoteSpeaking = false; + + bool isLocalSpeaking = (GVTrue == gvIsSourceTalking(m_SetupInfo.m_PlaybackDevice, 0)); + bool isRemoteSpeaking = (GVTrue == gvIsSourceTalking(m_SetupInfo.m_PlaybackDevice, m_RemoteAddr)); + + if (isLocalSpeaking != wasLocalSpeaking) + { + wasLocalSpeaking = isLocalSpeaking; + m_LocalSpeaking.ShowWindow(isLocalSpeaking ? SW_SHOW:SW_HIDE); + } + if (isRemoteSpeaking != wasRemoteSpeaking) + { + wasRemoteSpeaking = isRemoteSpeaking; + m_RemoteSpeaking.ShowWindow(isRemoteSpeaking ? SW_SHOW:SW_HIDE); + } + + // Check for microphone activity + int aBytesAvailable = gvGetAvailableCaptureBytes(m_SetupInfo.m_CaptureDevice); + if (aBytesAvailable > 0) + { + // Voice packet struct, so we don't have to do a buffer copy + // We just record the data directly into the packet + // (the actual packet may be smaller + + VoicePacket aVoicePacket; + memset(&aVoicePacket, 0, sizeof(aVoicePacket)); + aVoicePacket.mDataLength = sizeof(aVoicePacket.mBuffer); + + GVScalar aVolume; // this doesn't need to be sent + + // Read in a voice packet + GVBool gotPacket = gvCapturePacket(m_SetupInfo.m_CaptureDevice, aVoicePacket.mBuffer, &aVoicePacket.mDataLength, &aVoicePacket.mFrameStamp, &aVolume); + if (gotPacket == GVTrue) + { + // Playback for local echo + //gvPlayPacket(m_SetupInfo.m_PlaybackDevice, aVoicePacket.mBuffer, aVoicePacket.mDataLength, 0, aVoicePacket.mFrameStamp); + + // Contruct a message for the buddy + int aTotalPacketSize = gVoicePacketHeaderSize + aVoicePacket.mDataLength; + gt2Send(m_Connection, (GT2Byte*)&aVoicePacket, aTotalPacketSize, GT2False); + } + } + + // Allow new timers to be triggered + inTimer = false; + + CDialog::OnTimer(nIDEvent); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Callbacks for GT2 +void GT2SocketErrorCallback(GT2Socket theSocket) +{ + CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)gt2GetSocketData(theSocket); + aDialog->m_DisplayText = "GT2SocketErrorCallback. Connection Closed"; + aDialog->UpdateData(FALSE); +} + +void GT2ConnectedCallback(GT2Connection theConnection, GT2Result theResult, unsigned char* theMessage, int theLength) +{ + GT2Socket aSocket = gt2GetConnectionSocket(theConnection); + CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)gt2GetSocketData(aSocket); + + // Was there an error? + if (theResult != GT2Success) + { + aDialog->MessageBox("GT2ConnectedCallback returned failure!"); + aDialog->PostMessage(WM_CLOSE, 0, 0); + } + else + { + // Remember our connection + aDialog->m_Connection = theConnection; + aDialog->m_DisplayText = "Session established.\r\n(You may begin speaking)"; + aDialog->UpdateData(FALSE); + + // Start the voice devices + if (aDialog->m_SetupInfo.m_CaptureDevice != NULL) + gvStartDevice(aDialog->m_SetupInfo.m_CaptureDevice, GV_CAPTURE); + if (aDialog->m_SetupInfo.m_PlaybackDevice != NULL) + gvStartDevice(aDialog->m_SetupInfo.m_PlaybackDevice, GV_PLAYBACK); + } + GSI_UNUSED(theMessage); + GSI_UNUSED(theLength); + GSI_UNUSED(theResult); +} + +void GT2ReceivedCallback(GT2Connection theConnection, unsigned char* theBuffer, int theLength, int wasReliable) +{ + // Get the ptr to the dialog + CVoiceSessionDlg* aDlg = (CVoiceSessionDlg*)gt2GetSocketData(gt2GetConnectionSocket(theConnection)); + + // Cast to a voice packet so we can extract data + VoicePacket* aVoicePacket = (VoicePacket*)theBuffer; + + // Sanity check the packet + assert(theLength >= gVoicePacketHeaderSize); + assert(theLength == (gVoicePacketHeaderSize + aVoicePacket->mDataLength)); + + // Play the voice data + GVSource aSource = gt2GetRemoteIP(theConnection); + gvPlayPacket(aDlg->m_SetupInfo.m_PlaybackDevice, aVoicePacket->mBuffer, aVoicePacket->mDataLength, aSource, aVoicePacket->mFrameStamp, 0); + OutputDebugString("Playing voice packet\r\n"); + GSI_UNUSED(wasReliable); + GSI_UNUSED(theLength); +} + +GT2Bool GT2UnrecognizedMessageCallback(GT2Socket theSocket, unsigned int theIp, unsigned short thePort, GT2Byte* theData, int theLength) +{ + static unsigned char aNNHeader[NATNEG_MAGIC_LEN] = + { NN_MAGIC_0, NN_MAGIC_1, NN_MAGIC_2, + NN_MAGIC_3, NN_MAGIC_4, NN_MAGIC_5 }; + + // Bail out if it's too short + if (theLength < NATNEG_MAGIC_LEN) + { + OutputDebugString("\tDiscarded message (too small)\r\n"); + return GT2False; + } + + // Check if it matches the NN Magic bytes + if (0==memcmp(theData, aNNHeader,NATNEG_MAGIC_LEN)) + { + // Hand off to the NN SDK + sockaddr_in anAddr; + anAddr.sin_family = AF_INET; + anAddr.sin_port = htons(thePort); + anAddr.sin_addr.s_addr = theIp; + NNProcessData((char*)theData, theLength, &anAddr); + OutputDebugString("\tProcessed NN message\r\n"); + return GT2True; + } + + // Not handled by us + OutputDebugString("\tDiscarded message (not recognized)\r\n"); + GSI_UNUSED(theSocket); + return GT2False; +} + +void GT2ClosedCallback(GT2Connection theConnection, GT2CloseReason theReason) +{ + GT2Socket aSocket = gt2GetConnectionSocket(theConnection); + CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)gt2GetSocketData(aSocket); + + aDialog->m_DisplayText = "Connection closed. (GT2ClosedCallback)"; + aDialog->UpdateData(FALSE); + GSI_UNUSED(theReason); +} + +void GT2ConnectAttemptCallback(GT2Socket theSocket, GT2Connection theConnection, unsigned int theIp, unsigned short thePort, int theLatency, GT2Byte * theMessage, int theLength) +{ + CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)gt2GetSocketData(theSocket); + + // Set the callbacks so we can receive data + GT2ConnectionCallbacks aCallbackList; + aCallbackList.received = GT2ReceivedCallback; + aCallbackList.connected = GT2ConnectedCallback; // we're connected + aCallbackList.closed = GT2ClosedCallback; + + // Accept the connection + aDialog->m_Connection = theConnection; + gt2Accept(theConnection, &aCallbackList); + + // Store off the remote addr + aDialog->m_RemoteAddr = gt2GetRemoteIP(theConnection); + + // Start the voice devices + if (aDialog->m_SetupInfo.m_CaptureDevice != NULL) + gvStartDevice(aDialog->m_SetupInfo.m_CaptureDevice, GV_CAPTURE); + if (aDialog->m_SetupInfo.m_PlaybackDevice != NULL) + gvStartDevice(aDialog->m_SetupInfo.m_PlaybackDevice, GV_PLAYBACK); + + aDialog->m_DisplayText = "Session established.\r\n(You may begin speaking)"; + aDialog->UpdateData(FALSE); + GSI_UNUSED(theMessage); + GSI_UNUSED(theLength); + GSI_UNUSED(theIp); + GSI_UNUSED(thePort); + GSI_UNUSED(theLatency); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Callbacks for the NN SDK +void NNProgressCallback(NegotiateState theState, void* theParam) +{ + CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)theParam; + aDialog->m_DisplayText.Format("NNProgressCallback: %d\r\n", theState); + aDialog->UpdateData(FALSE); +} + +void NNCompletedCallback(NegotiateResult theResult, SOCKET theSocket, sockaddr_in* theAddr, void* theParam) +{ + CVoiceSessionDlg* aDialog = (CVoiceSessionDlg*)theParam; + + // Check the result, are we connected at the socket layer? + if (theResult == nr_success) + { + // Convert sockaddr_in to a string + CString aAddrString; + aAddrString.Format("%s:%d", inet_ntoa(theAddr->sin_addr), ntohs(theAddr->sin_port)); + aDialog->m_DisplayText = "NN Successful"; + + // If hosting, listen for the client... + if (aDialog->m_IsHost) + { + gt2Listen(aDialog->m_Socket, GT2ConnectAttemptCallback); + aDialog->m_DisplayText = "Listening for GT2 connection"; + } + // ...otherwise connect to the host + else + { + // Store off the remote addr + aDialog->m_RemoteAddr = theAddr->sin_addr.s_addr; + + // Set our gt2 callbacks, so we can receive messages + GT2ConnectionCallbacks aCallbackList; + aCallbackList.connected = GT2ConnectedCallback; // we're connected + aCallbackList.received = GT2ReceivedCallback; // we've received data + aCallbackList.closed = GT2ClosedCallback; + + int aTimeout = 0; + GT2Result aResult = gt2Connect(aDialog->m_Socket, &aDialog->m_Connection, aAddrString, NULL, 0, aTimeout, &aCallbackList, GT2False); + if (aResult != GT2Success) + { + // Error, bail out + aDialog->m_DisplayText = "Failed on gt2Connect"; + //aDialog->PostMessage(WM_CLOSE, 0, 0); + } + aDialog->m_DisplayText = "Connecting with GT2"; + } + } + else + { + aDialog->m_DisplayText = "Failed to negotiate a connection."; + } + + // Draw the new display text + aDialog->UpdateData(FALSE); + GSI_UNUSED(theSocket); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// MFC Event handlers +BOOL CVoiceSessionDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Init some variables + m_Socket = NULL; + m_Connection = NULL; + + // Set the gt2Callbacks + // Create our gt2 socket (we'll need it for NN) + // We use NULL for the local addr because we don't care which + // port we're bound to. We could specify one if we wanted by + // using ":5000" as the addr, for example + GT2Result aResult = gt2CreateSocket(&m_Socket, NULL, 0, 0, GT2SocketErrorCallback); + if (aResult != GT2Success) + { + // We're hosed, bail out by closing the dialog + MessageBox("Failed on gt2CreateSocket"); + PostMessage(WM_CLOSE, 0, 0); + return TRUE; + } + + // Set the socket data (the user param) to our dialog + gt2SetSocketData(m_Socket, this); + gt2SetUnrecognizedMessageCallback(m_Socket, GT2UnrecognizedMessageCallback); + + // Begin nat negotiation (use the gt2 socket) + // m_NNCookie and m_IsHost are set before the dialog is run + SOCKET aWinSocket = gt2GetSocketSOCKET(m_Socket); + NNBeginNegotiationWithSocket(aWinSocket, m_NNCookie, m_IsHost, NNProgressCallback, NNCompletedCallback, this); + + m_DisplayText = "Establishing connection..."; + UpdateData(FALSE); + + // Set the think timer + SetTimer(THINK_TIMER_ID, THINK_TIMER_DELAY, NULL); + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + +BOOL CVoiceSessionDlg::DestroyWindow() +{ + // Clean up NN SDK + NNFreeNegotiateList(); + + // Close the gt2 socket (will close the connection if connected) + if (m_Socket) + gt2CloseSocket(m_Socket); + + // Stop the voice devices + if (m_SetupInfo.m_CaptureDevice != NULL) + gvStopDevice(m_SetupInfo.m_CaptureDevice, GV_CAPTURE); + if (m_SetupInfo.m_PlaybackDevice != NULL) + gvStopDevice(m_SetupInfo.m_PlaybackDevice, GV_PLAYBACK); + + // Kill the timer + KillTimer(THINK_TIMER_ID); + + + return CDialog::DestroyWindow(); +} diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/VoiceSessionDlg.h b/code/gamespy/Voice2/Voice2BuddyMFC/VoiceSessionDlg.h new file mode 100644 index 00000000..86751671 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/VoiceSessionDlg.h @@ -0,0 +1,66 @@ +#if !defined(AFX_VOICESESSIONDLG_H__4D78C45C_9CB3_4F4B_81DF_8BF65316598E__INCLUDED_) +#define AFX_VOICESESSIONDLG_H__4D78C45C_9CB3_4F4B_81DF_8BF65316598E__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// VoiceSessionDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CVoiceSessionDlg dialog +#include "SetupDlg.h" +#include "../../gt2/gt2.h" +#include "../../natneg/natneg.h" + +class CVoiceSessionDlg : public CDialog +{ +public: + int m_NNCookie; // used to connect the two buddies + BOOL m_IsHost; // TRUE = I invited the buddy, FALSE = Buddy invited me + + VoiceSetupInfo m_SetupInfo; // device info + + GT2Socket m_Socket; + GT2Connection m_Connection; + unsigned int m_RemoteAddr; // IP addr of the buddy (used for identification) + + // MFC STUFF BELOW + +// Construction +public: + CVoiceSessionDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CVoiceSessionDlg) + enum { IDD = IDD_SESSION }; + CStatic m_RemoteSpeaking; + CStatic m_LocalSpeaking; + CString m_DisplayText; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CVoiceSessionDlg) + public: + virtual BOOL DestroyWindow(); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CVoiceSessionDlg) + afx_msg void OnTimer(UINT nIDEvent); + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_VOICESESSIONDLG_H__4D78C45C_9CB3_4F4B_81DF_8BF65316598E__INCLUDED_) diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/res/Voice2BuddyMFC.ico b/code/gamespy/Voice2/Voice2BuddyMFC/res/Voice2BuddyMFC.ico new file mode 100644 index 00000000..7eef0bcb Binary files /dev/null and b/code/gamespy/Voice2/Voice2BuddyMFC/res/Voice2BuddyMFC.ico differ diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/res/Voice2BuddyMFC.rc2 b/code/gamespy/Voice2/Voice2BuddyMFC/res/Voice2BuddyMFC.rc2 new file mode 100644 index 00000000..de338e06 --- /dev/null +++ b/code/gamespy/Voice2/Voice2BuddyMFC/res/Voice2BuddyMFC.rc2 @@ -0,0 +1,13 @@ +// +// VOICE2BUDDYMFC.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/res/gamespyl.bmp b/code/gamespy/Voice2/Voice2BuddyMFC/res/gamespyl.bmp new file mode 100644 index 00000000..01142a32 Binary files /dev/null and b/code/gamespy/Voice2/Voice2BuddyMFC/res/gamespyl.bmp differ diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/res/logo270x83.bmp b/code/gamespy/Voice2/Voice2BuddyMFC/res/logo270x83.bmp new file mode 100644 index 00000000..01142a32 Binary files /dev/null and b/code/gamespy/Voice2/Voice2BuddyMFC/res/logo270x83.bmp differ 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 00000000..71d27583 Binary files /dev/null and b/code/gamespy/Voice2/Voice2BuddyMFC/res/microsoft-microphone.bmp differ 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 00000000..b98071f3 Binary files /dev/null and b/code/gamespy/Voice2/Voice2BuddyMFC/res/microsoft-speaker.bmp differ diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/res/speaking.bmp b/code/gamespy/Voice2/Voice2BuddyMFC/res/speaking.bmp new file mode 100644 index 00000000..b42ff519 Binary files /dev/null and b/code/gamespy/Voice2/Voice2BuddyMFC/res/speaking.bmp differ diff --git a/code/gamespy/Voice2/Voice2BuddyMFC/res/speaking2.bmp b/code/gamespy/Voice2/Voice2BuddyMFC/res/speaking2.bmp new file mode 100644 index 00000000..cc274c3d Binary files /dev/null and b/code/gamespy/Voice2/Voice2BuddyMFC/res/speaking2.bmp differ 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 00000000..7eef0bcb Binary files /dev/null and b/code/gamespy/ghttp/ghttpmfc/res/ghttpmfc.ico differ diff --git a/code/gamespy/ghttp/ghttpmfc/res/ghttpmfc.rc2 b/code/gamespy/ghttp/ghttpmfc/res/ghttpmfc.rc2 new file mode 100644 index 00000000..b5533ab2 --- /dev/null +++ b/code/gamespy/ghttp/ghttpmfc/res/ghttpmfc.rc2 @@ -0,0 +1,13 @@ +// +// GHTTPMFC.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/ghttp/ghttpmfc/resource.h b/code/gamespy/ghttp/ghttpmfc/resource.h new file mode 100644 index 00000000..bc4776ba --- /dev/null +++ b/code/gamespy/ghttp/ghttpmfc/resource.h @@ -0,0 +1,54 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by ghttpmfc.rc +// +#define IDD_GHTTPMFC_DIALOG 102 +#define IDR_MAINFRAME 128 +#define IDC_URL 1000 +#define IDC_HEADERS 1001 +#define IDC_BLOCKING 1002 +#define IDC_PROGRESS_CALLBACK 1003 +#define IDC_COMPLETED_CALLBACK 1004 +#define IDC_USER_BUFFER 1005 +#define IDC_GET_FILE 1006 +#define IDC_SAVE_FILE 1007 +#define IDC_STREAM_FILE 1008 +#define IDC_BUFFER_SIZE 1009 +#define IDC_FILE 1010 +#define IDC_SAVE_AS 1011 +#define IDC_SO_FAR 1012 +#define IDC_HEAD_FILE 1013 +#define IDC_HOST_LOOKUP 1014 +#define IDC_RADIO3 1015 +#define IDC_RADIO4 1016 +#define IDC_RADIO5 1017 +#define IDC_RADIO6 1018 +#define IDC_PROGRESS 1019 +#define IDC_THROTTLE 1020 +#define IDC_RADIO7 1021 +#define IDC_RADIO8 1022 +#define IDC_RADIO9 1023 +#define IDC_HEADERS_RECV 1024 +#define IDC_STATUS 1025 +#define IDC_POST 1026 +#define IDC_STEP_THINK 1027 +#define IDC_POST_FILE 1028 +#define IDC_POST_OBJECTS 1029 +#define IDC_POST_BYTES 1030 +#define IDC_PROXY 1031 +#define IDC_START 1050 +#define IDC_CANCEL 1051 +#define IDC_THINK 1052 +#define IDC_IE_SETTINGS 1053 +#define IDC_SET_PROXY 1054 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1029 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/gamespy/gstats/changelog.txt b/code/gamespy/gstats/changelog.txt new file mode 100644 index 00000000..f39098e6 --- /dev/null +++ b/code/gamespy/gstats/changelog.txt @@ -0,0 +1,102 @@ +Changelog for: GameSpy Stats & Tracking / Persistent Storage SDK +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 1.19.00 RMV RELEASE Released to Developer Site +12-10-2007 1.18.02 RMV OTHER Created VS2005 projects for persisttest and gstats +12-07-2007 1.18.01 SAH OTHER Fixed projects that were missing dependencies +08-06-2007 1.18.00 RMV RELEASE Released to Developer Site +12-15-2006 1.17.00 MJW RELEASE Released to Developer Site +10-05-2006 1.16.43 SAH FIX Updated MacOSX Makefile +09-28-2006 1.16.42 SAH FIX Fixed PS3 project to work with PS3 095.00x SDK; changed included libaries in linker input. +08-24-2006 1.16.41 SAH FIX Fixed VC7 project file +08-23-2006 1.16.40 SAH FIX Added a fix for len == -1 in RecvSessionKey function. +08-02-2006 1.16.39 SAH RELEASE Releasing to developer site +07-31-2006 1.16.38 SAH FIX Fixed PS3 project file - added post-build step to create *.SELF for execution +07-25-2006 1.16.37 SAH FIX Fixed NITRO project, include for crt0.o now above others so it add correct file +07-06-2006 1.16.36 SAH FIX Fixed PSP project file to not explicitly include the PSP linker file +06-30-2006 1.16.35 SAH FIX Fixed NITRO projects & linker command files (to work with CW 2.0/NitroSDK 3.1) + SAH FIX Fixed Linux makefiles +05-31-2006 1.16.34 SAH RELEASE Releasing to developer site +05-30-2006 1.16.33 SAH FIX Fixed PS3 projects to work with PS3(084_001 SDK) +05-25-2006 1.16.32 SAH FIX Changed PSP project warning levels +05-19-2006 1.16.31 SAH FIX Added gsTestMain.c to nitro CodeWarrior project +05-15-2006 1.16.30 SAH FIX Added "PS3 Release" configuration(s) to project +05-11-2006 1.16.29 SN FIX Modified the update game message to include the connid so that games aren't discarded when a disconnect occurs +04-25-2006 1.16.28 SAH RELEASE Releasing to developer site +04-24-2006 1.16.28 SAH FIX Fixed Nitro project files to work on build machine +04-20-2006 1.16.27 SAH FIX Removed unused variables, moved GSI_UNUSED above return calls +01-27-2006 1.16.26 SN RELEASE Releasing to developer site +01-27-2006 1.16.26 SN FIX Added PSP to test_main define in statstest + SN OTHER Added psp prodg project and solution to sgv for statstest +01-27-2006 1.16.25 SN FIX Added PSP to test_main define in persisttest + SN OTHER Added psp prodg project and solution to sgv for persisttest +12-19-2005 1.16.24 SN OTHER Cleaned up projects, adding any missing common code. + Replaced gstats project with statstest in its subdirectory +11-17-2005 1.16.23 DES FIX Updated Nitro Makefile. +11-14-2005 1.16.22 DES FIX Updated the OSX Makefiles. + DES FEATURE Added GSI_DOMAIN_NAME support. +09-23-2005 1.16.21 DES FEATURE Updated DS support +07-28-2005 1.16.20 SN RELEASE Releasing to developer site. +07-28-2005 1.16.20 SN FIX fixed PS2 projects to use new common code +06-03-2005 1.16.19 SN RELEASE Releasing to developer site. +05-05-2005 1.16.19 BED FIX Updated project files to use new common folder. +05-03-2005 1.16.18 SN FIX Removed deprecated MFC code for Visual Studio .NET projects + SN OTHER Created Visual Studio .NET projects +04-28-2005 1.16.17 SN RELEASE Releasing to developer site. +04-27-2005 1.16.17 DES RELEASE Limited release to Nintendo DS developers. +04-27-2005 1.16.17 DES FIX Changed the default connect timeout to 20 seconds for the DS. + DES FIX Added extra printfs to statstest. +04-25-2005 1.16.16 DES FIX Check for socket error when receiving the challenge. + DES CLEANUP Removed printf()s from gstats.c +04-04-2005 1.16.15 SN RELEASE Releasing to developer site. +04-03-2005 1.16.15 SN FIX Added a StatsThink function due to socket api buffer clogging +03-25-2005 1.16.14 SN FIX Fixed bug to clean g_statsgame when FreeGame was called with local statsgame_t and both were the same object. +03-16-2005 1.16.13 SN FIX Fixed PS2 bug not being able to print hex chars correctly. +03-14-2005 1.16.13 DES FEATURE Nintendo DS support +12-28-2004 1.16.12 SN FIX Added const qualifiers to unmodified formal function parameters +09-16-2004 1.16.11 SN RELEASE Releasing to developer site. +09-16-2004 1.16.11 SN FIX Renamed a global variable to avoid MacOS X naming confliction +09-09-2004 1.16.10 BED FEATURE Added InitStatsAsync and InitStatsThink. +08-27-2004 1.16.09 DES CLEANUP Removed MacOS style includes + DES CLEANUP Updated Win32 project configurations + DES CLEANUP General Unicode cleanup + DES CLEANUP Fixed warnings under OSX + DES CLEANUP Updated OSX Makefile +08-05-2004 1.16.08 BED RELEASE Releasing to developer site. +08-05-2004 1.16.08 BED FIX Updated samples to use GT2 instead of legacy GT1 SDK +07-20-2004 1.16.07 SN FIX Updated code with explicit casts to remove implicit cast error + when compiling at highest level and warnings treated as errors. +07-20-2004 1.16.06 BED FEATURE Added Remote Auth hooks +06-18-2004 1.16.05 BED RELEASE Releasing to developer site. + FEATURE Added PS2 Insock support +11-10-2003 1.16.04 DES RELEASE Releasing to developer site. +11-07-2003 1.16.04 BED FIX Removed CodeWarrior strictest warnings. +11-07-2003 1.16.03 DES FIX Updated the linux and PS2 makefiles. +11-04-2003 1.16.02 DES FEATURE Added availability check code. +10-30-2003 1.16.01 DES FIX Updated gp_stats to work with the latest versions of GP and stats. +10-21-2003 1.16.00 BED RELEASE Releasing to developer site. (UNIQUE NICK AND UNICODE SUPPORT) +10-01-2003 1.16.00 DDW FEATURE Added modified time checking/reporting to persistent storage + NOTE: Persistent Storage callbacks modified w/ additional parameter + Prepend hostname with gamename for unique DNS names +09-08-2003 1.15.11 BED FEATURE Added UTF-8 wrapper for UNICODE support. +07-24-2003 1.15.10 DES RELEASE Releasing to developer site. +07-18-2003 1.15.10 BED FEATURE Added CodeWarrior (PS2) sample project file. + BED CLEANUP General cleanup to remove CodeWarrior warnings. +07-17-2003 1.15.09 DES CLEANUP Cleaned up the PS2 Makefiles, they now use Makefile.commmon. +07-14-2003 1.15.08 DES FIX ALLOW_DISK is now undef'd whenever NOFILE is defined. + FIX Changed __mips64 check to _PS2 check. + CLEANUP No need to make remove() an empty define on systems with NOFILE defined. + BED FEATURE Added ProDG sample project files to gstats and persistest +07-03-2003 1.15.07 BED FIX Added gtUtility.c to the ladderTrack sample. +05-09-2003 1.15.06 DES CLEANUP Removed Dreamcast support. + FIX Metrowerks for Win32 is no longer falsely identified as MacOS. +03-03-2003 1.15.05 DES CLEANUP General cleanup to remove warnings. +12-19-2002 1.15.04 DES RELEASE Releasing to developer site. +12-19-2002 1.15.04 DES CLEANUP Removed assert.h include. +12-13-2002 1.15.03 DES FEATURE Added PS2 eenet stack support. +11-22-2002 1.15.02 DES RELEASE Releasing to developer site. +11-20-2002 1.15.02 DES CLEANUP Cleaned up to remove PS2 compiler warnings. +11-15-2002 1.15.01 DES CLEANUP Updated persisttest PS2 Makefile +09-25-2002 1.15.00 DDW OTHER Changelog started diff --git a/code/gamespy/gstats/gbucket.c b/code/gamespy/gstats/gbucket.c new file mode 100644 index 00000000..d786fe2c --- /dev/null +++ b/code/gamespy/gstats/gbucket.c @@ -0,0 +1,396 @@ +/****** +gbucket.c +GameSpy Stats/Tracking SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +******/ + +/******** +INCLUDES +********/ +#include "../common/gsCommon.h" +#include "gbucket.h" +#include "../hashtable.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/******** +TYPEDEFS +********/ +struct bucketset_s +{ + HashTable buckets; +}; + + +typedef struct bucket_s +{ + char *name; + BucketType type; + int nvals; //for averaging + union + { + int ival; + double fval; + char *sval; + } vals; +} bucket_t; + +typedef struct dumpdata_s +{ + char *data; + unsigned int maxlen; + unsigned int len; +} dumpdata_t; + +/******** +PROTOTYPES +********/ +static void DumpMap(void *elem, void *clientData); +static void BucketFree(void *elem); +static int BucketCompare(const void *entry1, const void *entry2); +static int BucketHash(const void *elem, int numbuckets); +static void *DoSet(bucket_t *pbucket, void *value); +static void *DoGet(bucket_t *pbucket); +static bucket_t *DoFind(bucketset_t set, char *name); + + +/******** +VARS +********/ +bucketset_t g_buckets; + +/****************************************************************************/ +/* PUBLIC FUNCTIONS */ +/****************************************************************************/ +bucketset_t NewBucketSet(void) +{ + bucketset_t set; + + set = (bucketset_t)gsimalloc(sizeof (struct bucketset_s)); + assert(set); + set->buckets = TableNew(sizeof(bucket_t),32,BucketHash, BucketCompare, BucketFree); + + g_buckets = set; + return set; +} + +void FreeBucketSet(bucketset_t set) +{ + assert(set); + assert(set->buckets); + TableFree(set->buckets); + gsifree(set); +} + +char *DumpBucketSet(bucketset_t set) +{ + dumpdata_t data; + if (set == NULL) + set = g_buckets; + assert(set); + data.data = (char *)gsimalloc(128); //alloc an initial buffer + data.data[0] = 0; + data.len = 0; + data.maxlen = 128; + TableMap(set->buckets,DumpMap, &data); + return data.data; +} + +void *BucketNew(bucketset_t set, char *name, BucketType type, void *initialvalue) +{ + bucket_t bucket; + + if (set == NULL) + set = g_buckets; + assert(set); + bucket.name = goastrdup(name); + bucket.type = type; + bucket.vals.sval = NULL; + bucket.nvals = 1; + DoSet(&bucket, initialvalue); + TableEnter(set->buckets,&bucket); + return DoGet(DoFind(set, name)); +} + +void *BucketSet(bucketset_t set, char *name,void *value) +{ + bucket_t *pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + pbucket->nvals = 0; + return DoSet(pbucket,value); +} + +void *BucketGet(bucketset_t set, char *name) +{ + return DoGet(DoFind(set,name)); +} + +void *BucketAdd(bucketset_t set, char *name, void *value) +{ + bucket_t *pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + if (pbucket->type == bt_int) + return DoSet(pbucket, bint( (*(int *)DoGet(pbucket)) + (*(int *)value))); + if (pbucket->type == bt_float) + return DoSet(pbucket, bfloat( (*(double *)DoGet(pbucket)) + (*(double *)value))); + //else, string -- just concat + return BucketConcat(set, name, value); +} + +void *BucketSub(bucketset_t set, char *name, void *value) +{ + bucket_t *pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + if (pbucket->type == bt_int) + return DoSet(pbucket, bint( (*(int *)DoGet(pbucket)) - (*(int *)value))); + if (pbucket->type == bt_float) + return DoSet(pbucket, bfloat( (*(double *)DoGet(pbucket)) - (*(double *)value))); + //else, string -- just ignore + return DoGet(pbucket); + +} + +void *BucketMult(bucketset_t set, char *name, void *value) +{ + bucket_t *pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + if (pbucket->type == bt_int) + return DoSet(pbucket, bint( (*(int *)DoGet(pbucket)) * (*(int *)value))); + if (pbucket->type == bt_float) + return DoSet(pbucket, bfloat( (*(double *)DoGet(pbucket)) * (*(double *)value))); + //else, string -- just ignore + return DoGet(pbucket); +} + +void *BucketDiv(bucketset_t set, char *name, void *value) +{ + bucket_t *pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + if (pbucket->type == bt_int) + return DoSet(pbucket, bint( (*(int *)DoGet(pbucket)) / (*(int *)value))); + if (pbucket->type == bt_float) + return DoSet(pbucket, bfloat( (*(double *)DoGet(pbucket)) / (*(double *)value))); + //else, string -- just ignore + return DoGet(pbucket); +} + +void *BucketConcat(bucketset_t set, char *name, void *value) +{ + bucket_t *pbucket = DoFind(set, name); + char *temp, *s; + if (!pbucket) + return NULL; + + assert(pbucket->type == bt_string); + s = DoGet(pbucket); + temp = (char *)gsimalloc(strlen(s) + strlen(value) + 1); + strcpy(temp,s); + strcat(temp, value); + + DoSet(pbucket, temp); + gsifree(temp); + + return DoGet(pbucket); +} + +#define AVG(cur, new, num) (((cur * num) + new) / (++num)) +void *BucketAvg(bucketset_t set, char *name, void *value) +{ + bucket_t *pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + if (pbucket->type == bt_int) + return DoSet(pbucket, bint( AVG((*(int *)DoGet(pbucket)), (*(int *)value), pbucket->nvals))); + if (pbucket->type == bt_float) + return DoSet(pbucket, bfloat( AVG((*(double *)DoGet(pbucket)), (*(double *)value), pbucket->nvals))); + //else, string -- just ignore + return DoGet(pbucket); +} + +/* Note: these are NOT thread safe! */ +void *bint(int i) +{ + static int j; + j=i; + return &j; +} + +void *bfloat(double f) +{ + static double g; + g=f; + return &g; +} + + + +/*********** + * UTILITY FUNCTIONS + **********/ +static void DumpMap(void *elem, void *clientData) +{ + bucket_t *bucket = (bucket_t *)elem; + dumpdata_t *data = (dumpdata_t *)clientData; + unsigned int minlen; + + //find out if we need to resize! + minlen = strlen(bucket->name) + 3; + if (bucket->type == bt_int || bucket->type == bt_float) + minlen += data->len + 16; + else if (bucket->type == bt_string) + minlen += data->len + strlen(bucket->vals.sval); + + if (data->maxlen <= minlen) + { + if (data->maxlen == 0) + data->maxlen = minlen * 2; + else + data->maxlen *= 2; + data->data = gsirealloc(data->data, data->maxlen); + + } + + switch (bucket->type) + { + case bt_int: + data->len += (unsigned int)sprintf(data->data + data->len,"\\%s\\%d",bucket->name,bucket->vals.ival); + break; + case bt_float: + data->len += (unsigned int)sprintf(data->data + data->len,"\\%s\\%f",bucket->name,bucket->vals.fval); + break; + case bt_string: + data->len += (unsigned int)sprintf(data->data + data->len,"\\%s\\%s",bucket->name,bucket->vals.sval); + break; + + } +} + + +static char *stripchars(char *s) +{ + char *p = s; + while (*s) + { + if (*s == '\\') + *s = '/'; + s++; + } + return p; +} + +static void *DoSet(bucket_t *pbucket, void *value) +{ + if (pbucket->type == bt_int) + pbucket->vals.ival = *(int*)value; + else if (pbucket->type == bt_float) + pbucket->vals.fval = *(double *)value; + else if (pbucket->type == bt_string) + { + if (pbucket->vals.sval != NULL) + gsifree(pbucket->vals.sval); + pbucket->vals.sval = (value == NULL ? NULL : stripchars(goastrdup((char *)value))); + } + return DoGet(pbucket); +} + +static void *DoGet(bucket_t *pbucket) +{ + if (!pbucket) + return NULL; + if (pbucket->type == bt_string) + return pbucket->vals.sval; + else //since it's a union, we can return any member + return &pbucket->vals.sval; + +} + +static bucket_t *DoFind(bucketset_t set, char *name) +{ + bucket_t tbucket; + + if (set == NULL) + set = g_buckets; + assert(set); + + tbucket.name = name; + return (bucket_t *)TableLookup(set->buckets,&tbucket); +} + + + +/* NonTermHash + * ---------- + * The hash code is computed using a method called "linear congruence." + * This hash function has the additional feature of being case-insensitive, + */ +#define MULTIPLIER -1664117991 +static int BucketHash(const void *elem, int numbuckets) +{ + unsigned int i; + unsigned int len; + unsigned int hashcode = 0; + + char *s = ((bucket_t *)elem)->name; + len = strlen(s); + for (i = 0; i < len ; i++) { + hashcode = (unsigned int)((int)hashcode * MULTIPLIER + tolower(s[i])); + } + return (int)(hashcode % numbuckets); +} + + +/* CaseInsensitiveCompare + * ---------------------- + * Comparison function passed to qsort to sort an array of + * strings in alphabetical order. It uses strcasecmp which is + * identical to strcmp, except that it doesn't consider case of the + * characters when comparing them, thus it sorts case-insensitively. + */ +static int CaseInsensitiveCompare(const void *entry1, const void *entry2) +{ + return strcasecmp(*(char **)entry1,*(char **)entry2); +} + +/* keyval + * Compares two buckets (case insensative) + */ +static int BucketCompare(const void *entry1, const void *entry2) +{ + + return CaseInsensitiveCompare(&((bucket_t *)entry1)->name, + &((bucket_t *)entry2)->name); +} + + +/* KeyValFree + * Frees the memory INSIDE a Bucket structure + */ +static void BucketFree(void *elem) +{ + gsifree(((bucket_t *)elem)->name); + if (((bucket_t *)elem)->type == bt_string && ((bucket_t *)elem)->vals.sval != NULL) + gsifree(((bucket_t *)elem)->vals.sval); + +} + +#ifdef __cplusplus +} +#endif diff --git a/code/gamespy/gstats/gbucket.h b/code/gamespy/gstats/gbucket.h new file mode 100644 index 00000000..add085f6 --- /dev/null +++ b/code/gamespy/gstats/gbucket.h @@ -0,0 +1,52 @@ +/****** +gbucket.h +GameSpy Stats/Tracking SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + +Please see the GameSpy Stats and Tracking SDK for more info +You should not need to use the functions in this file, they +are used to manage the buckets by the gstats SDK. +Use the type-safe bucket functions in the gstats SDK instead. +******/ + + +#ifndef _GBUCKET_H_ +#define _GBUCKET_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct bucketset_s *bucketset_t; +typedef enum {bt_int, bt_float, bt_string} BucketType; + +bucketset_t NewBucketSet(void); +void FreeBucketSet(bucketset_t set); +char *DumpBucketSet(bucketset_t set); + +void *BucketNew(bucketset_t set, char *name, BucketType type, void *initialvalue); +void *BucketSet(bucketset_t set, char *name,void *value); +void *BucketAdd(bucketset_t set, char *name, void *value); +void *BucketSub(bucketset_t set, char *name, void *value); +void *BucketMult(bucketset_t set, char *name, void *value); +void *BucketDiv(bucketset_t set, char *name, void *value); +void *BucketConcat(bucketset_t set, char *name, void *value); +void *BucketAvg(bucketset_t set, char *name, void *value); +void *BucketGet(bucketset_t set, char *name); + +/* Helper functions */ +void *bint(int i); +void *bfloat(double f); +#define bstring(a) ((void *)a) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/gstats/gp_stats/StdAfx.cpp b/code/gamespy/gstats/gp_stats/StdAfx.cpp new file mode 100644 index 00000000..9b9d9667 --- /dev/null +++ b/code/gamespy/gstats/gp_stats/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// gp_stats.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/code/gamespy/gstats/gp_stats/StdAfx.h b/code/gamespy/gstats/gp_stats/StdAfx.h new file mode 100644 index 00000000..840a0e6b --- /dev/null +++ b/code/gamespy/gstats/gp_stats/StdAfx.h @@ -0,0 +1,27 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__14D457F1_07B9_4AB5_97B4_B82E57AFA0D4__INCLUDED_) +#define AFX_STDAFX_H__14D457F1_07B9_4AB5_97B4_B82E57AFA0D4__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__14D457F1_07B9_4AB5_97B4_B82E57AFA0D4__INCLUDED_) diff --git a/code/gamespy/gstats/gp_stats/gp_stats.cpp b/code/gamespy/gstats/gp_stats/gp_stats.cpp new file mode 100644 index 00000000..51822d83 --- /dev/null +++ b/code/gamespy/gstats/gp_stats/gp_stats.cpp @@ -0,0 +1,74 @@ +// gp_stats.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "gp_stats.h" +#include "gp_statsDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CGp_statsApp + +BEGIN_MESSAGE_MAP(CGp_statsApp, CWinApp) + //{{AFX_MSG_MAP(CGp_statsApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CGp_statsApp construction + +CGp_statsApp::CGp_statsApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CGp_statsApp object + +CGp_statsApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CGp_statsApp initialization + +BOOL CGp_statsApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. +/* +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif +*/ + CGp_statsDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + // TODO: Place code here to handle when the dialog is + // dismissed with OK + } + else if (nResponse == IDCANCEL) + { + // TODO: Place code here to handle when the dialog is + // dismissed with Cancel + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/code/gamespy/gstats/gp_stats/gp_stats.h b/code/gamespy/gstats/gp_stats/gp_stats.h new file mode 100644 index 00000000..63b5a7f2 --- /dev/null +++ b/code/gamespy/gstats/gp_stats/gp_stats.h @@ -0,0 +1,49 @@ +// gp_stats.h : main header file for the GP_STATS application +// + +#if !defined(AFX_GP_STATS_H__90B1DDCE_A86E_49A8_B136_0B29EA43F6B4__INCLUDED_) +#define AFX_GP_STATS_H__90B1DDCE_A86E_49A8_B136_0B29EA43F6B4__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CGp_statsApp: +// See gp_stats.cpp for the implementation of this class +// + +class CGp_statsApp : public CWinApp +{ +public: + CGp_statsApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CGp_statsApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CGp_statsApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_GP_STATS_H__90B1DDCE_A86E_49A8_B136_0B29EA43F6B4__INCLUDED_) diff --git a/code/gamespy/gstats/gp_stats/gp_stats.rc b/code/gamespy/gstats/gp_stats/gp_stats.rc new file mode 100644 index 00000000..dee94b35 --- /dev/null +++ b/code/gamespy/gstats/gp_stats/gp_stats.rc @@ -0,0 +1,201 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\gp_stats.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\gp_stats.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_GP_STATS_DIALOG DIALOGEX 0, 0, 197, 266 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "Tracking Stats by Account" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Quit",IDOK,105,85,50,14 + GROUPBOX "Account",IDC_STATIC,10,10,175,95 + LTEXT "E-mail:",IDC_STATIC,20,23,22,8 + LTEXT "Nick:",IDC_STATIC,20,38,18,8 + LTEXT "Password:",IDC_STATIC,20,53,34,8 + LTEXT "Profile ID:",IDC_STATIC,20,68,32,8 + EDITTEXT IDC_EMAIL,65,20,110,12,ES_AUTOHSCROLL + EDITTEXT IDC_NICK,65,35,110,12,ES_AUTOHSCROLL + EDITTEXT IDC_PASSWORD,65,50,110,12,ES_PASSWORD | ES_AUTOHSCROLL + EDITTEXT IDC_PROFILE_ID,65,65,110,12,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "Authenticate",IDC_AUTHENTICATE,35,85,50,14 + GROUPBOX "Persistant Data",IDC_STATIC,10,115,175,140 + CONTROL "Private Read/Write",IDC_PRIVATE_RW,"Button", + BS_AUTORADIOBUTTON | WS_GROUP,20,130,77,10 + CONTROL "Private Read-Only",IDC_PRIVATE_RO,"Button", + BS_AUTORADIOBUTTON,20,145,73,10 + CONTROL "Public Read/Write",IDC_PUBLIC_RW,"Button", + BS_AUTORADIOBUTTON,100,130,75,10 + CONTROL "Public Read-Only",IDC_PUBLIC_RO,"Button", + BS_AUTORADIOBUTTON,100,145,71,10 + PUSHBUTTON "Get",IDC_GET,35,160,50,14,WS_GROUP + PUSHBUTTON "Set",IDC_SET,110,160,50,14 + LTEXT "Key:",IDC_STATIC,20,199,15,8 + LTEXT "Value:",IDC_STATIC,20,181,21,8 + EDITTEXT IDC_VALUE,50,180,125,12,ES_AUTOHSCROLL + LISTBOX IDC_KEYS,50,195,70,50,LBS_SORT | LBS_NOINTEGRALHEIGHT | + WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Add Key",IDC_ADD,125,200,50,14 + EDITTEXT IDC_NEW_KEY,125,215,50,14,ES_AUTOHSCROLL +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "gp_stats MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "gp_stats\0" + VALUE "LegalCopyright", "Copyright (C) 2000\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "gp_stats.EXE\0" + VALUE "ProductName", "gp_stats Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_GP_STATS_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 190 + TOPMARGIN, 7 + BOTTOMMARGIN, 259 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\gp_stats.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/gamespy/gstats/gp_stats/gp_statsDlg.cpp b/code/gamespy/gstats/gp_stats/gp_statsDlg.cpp new file mode 100644 index 00000000..6abd7325 --- /dev/null +++ b/code/gamespy/gstats/gp_stats/gp_statsDlg.cpp @@ -0,0 +1,733 @@ +// gp_statsDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "gp_stats.h" +#include "gp_statsDlg.h" +#include "../gpersist.h" +#include "../../ghttp/ghttp.h" +#include "../../common/gsAvailable.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +// These are controls that are enabled when authenticated, +// and disabled when not authenticated. +////////////////////////////////////////////////////////// +CWnd * ToggleControls[64]; + +///////////////////////////////////////////////////////////////////////////// +// CGp_statsDlg dialog + +CGp_statsDlg::CGp_statsDlg(CWnd* pParent /*=NULL*/) + : CDialog(CGp_statsDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CGp_statsDlg) + m_email = _T("dan@gamespy.com"); + m_nick = _T("mrpants"); + m_password = _T("mrpants"); + m_profileID = 0; + m_type = 0; + m_value = _T(""); + m_newKey = _T(""); + //}}AFX_DATA_INIT + // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); +} + +void CGp_statsDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CGp_statsDlg) + DDX_Control(pDX, IDC_KEYS, m_keys); + DDX_Text(pDX, IDC_EMAIL, m_email); + DDX_Text(pDX, IDC_NICK, m_nick); + DDX_Text(pDX, IDC_PASSWORD, m_password); + DDX_Text(pDX, IDC_PROFILE_ID, m_profileID); + DDX_Radio(pDX, IDC_PRIVATE_RW, m_type); + DDX_Text(pDX, IDC_VALUE, m_value); + DDX_Text(pDX, IDC_NEW_KEY, m_newKey); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CGp_statsDlg, CDialog) + //{{AFX_MSG_MAP(CGp_statsDlg) + ON_BN_CLICKED(IDC_AUTHENTICATE, OnAuthenticate) + ON_WM_DESTROY() + ON_BN_CLICKED(IDC_GET, OnGet) + ON_BN_CLICKED(IDC_SET, OnSet) + ON_BN_CLICKED(IDC_ADD, OnAdd) + ON_LBN_SELCHANGE(IDC_KEYS, OnSelchangeKeys) + ON_EN_CHANGE(IDC_VALUE, OnChangeValue) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CGp_statsDlg message handlers + +BOOL CGp_statsDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Set the icon for this dialog. The framework does this automatically + // when the application's main window is not a dialog + SetIcon(m_hIcon, TRUE); // Set big icon + SetIcon(m_hIcon, FALSE); // Set small icon + + // Build the list of toggle controls. + ///////////////////////////////////// + int num = 0; +#define ADD_TOGGLE(id) ToggleControls[num++] = this->GetDlgItem(id) + ADD_TOGGLE(IDC_PRIVATE_RW); + ADD_TOGGLE(IDC_PRIVATE_RO); + ADD_TOGGLE(IDC_PUBLIC_RW); + ADD_TOGGLE(IDC_PUBLIC_RO); + ADD_TOGGLE(IDC_GET); + ADD_TOGGLE(IDC_SET); + ADD_TOGGLE(IDC_VALUE); + ADD_TOGGLE(IDC_KEYS); + ADD_TOGGLE(IDC_ADD); + ADD_TOGGLE(IDC_NEW_KEY); + ToggleControls[num++] = NULL; + + // Init data. + ///////////// + m_authenticated = FALSE; + m_gp = NULL; + + // Put ourselves in the unauthenticated stats. + ////////////////////////////////////////////// + UnAuthenticate(); + + // Init the connection to the stats manager. + //////////////////////////////////////////// + CheckStatsConnection(); + + // Init GP. + /////////// + if(gpInitialize(&m_gp, 0, 0, GP_PARTNERID_GAMESPY) != GP_NO_ERROR) + MessageBox("FAILED TO INITIALIZE GP!!!"); + + return TRUE; // return TRUE unless you set the focus to a control +} + +void CGp_statsDlg::OnDestroy() +{ + CDialog::OnDestroy(); + + ClearKeys(); + + // Close the stats connection. + ////////////////////////////// + CloseStatsConnection(); + + // Shutdown GP. + /////////////// + gpDestroy(&m_gp); + + // Shutdown GHTTP. + ////////////////// + ghttpCleanup(); +} + +void CGp_statsDlg::Authenticated() +{ + m_authenticated = TRUE; + + // Enable controls. + /////////////////// + int i; + for(i = 0 ; ToggleControls[i] ; i++) + ToggleControls[i]->EnableWindow(); +} + +void CGp_statsDlg::UnAuthenticate() +{ + m_authenticated = FALSE; + m_profileID = 0; + + // Disable controls. + //////////////////// + int i; + for(i = 0 ; ToggleControls[i] ; i++) + ToggleControls[i]->EnableWindow(FALSE); +} + +BOOL CGp_statsDlg::CheckStatsConnection() +{ + // Are we connected? + //////////////////// + if(IsStatsConnected()) + return TRUE; + + // Set the gamename and secret key. + /////////////////////////////////// +#if 1 + strcpy(gcd_gamename, "gmtest"); + gcd_secret_key[0] = 'H'; + gcd_secret_key[1] = 'A'; + gcd_secret_key[2] = '6'; + gcd_secret_key[3] = 'z'; + gcd_secret_key[4] = 'k'; + gcd_secret_key[5] = 'S'; + gcd_secret_key[7] = '\0'; +#else + strcpy(gcd_gamename, "excessive"); + gcd_secret_key[0] = 'G'; + gcd_secret_key[1] = 'n'; + gcd_secret_key[2] = '3'; + gcd_secret_key[3] = 'a'; + gcd_secret_key[4] = 'Y'; + gcd_secret_key[5] = '9'; + gcd_secret_key[7] = '\0'; +#endif + + // check that the game's backend is available + GSIACResult ac_result; + GSIStartAvailableCheck(gcd_gamename); + while((ac_result = GSIAvailableCheckThink()) == GSIACWaiting) + msleep(5); + if(ac_result != GSIACAvailable) + { + MessageBox("The backend is not available\n"); + return TRUE; + } + + // Try to connect. + ////////////////// + int result = InitStatsConnection(0); + if(result == GE_NOERROR) + return TRUE; + + // Error! + ///////// + CString message; + if(result == GE_NOSOCKET) + message = "Unable to create a socket."; + else if(result == GE_NODNS) + message = "Unable to resolve a DNS name."; + else if(result == GE_NOCONNECT) + message = "Unable to connect to stats server, or connection lost."; + else if(result == GE_DATAERROR) + message = "Bad data from the stats server."; + else + message = "Error."; + MessageBox(message, "Error connecting to the stats server"); + + return FALSE; +} + +void CGp_statsDlg::SendPassword() +{ + // Form the URL for the request. + //////////////////////////////// + CString URL = "http://gamespyarcade.com/software/reqemail.asp?email="; + URL += m_email; + + // Set the wait cursor. + /////////////////////// + HCURSOR hPrevCursor = GetCursor(); + SetCursor(LoadCursor(NULL, IDC_WAIT)); + + // Do the request. + ////////////////// + ghttpGetFile(URL, GHTTPTrue, NULL, NULL); + + // Reset the previous cursor. + ///////////////////////////// + SetCursor(hPrevCursor); +} + +void CreateAccountCallback(GPConnection * connection, void * arg_, void * param) +{ + GPNewUserResponseArg * arg = (GPNewUserResponseArg *)arg_; + int * pid = (int *)param; + + // Store the result. + //////////////////// + if(arg->result == GP_NO_ERROR) + *pid = arg->profile; + else + { + // If the nick was already in use, just pretend we created it. + ////////////////////////////////////////////////////////////// + GPErrorCode errorCode; + gpGetErrorCode(connection, &errorCode); + if(errorCode == GP_NEWUSER_BAD_NICK) + *pid = arg->profile; + else + *pid = 0; + } +} + +BOOL CGp_statsDlg::CreateAccount() +{ + int pid; + int rcode; + + // Create the account. + ////////////////////// + GPResult result = gpNewUser( + &m_gp, + m_nick, + NULL, + m_email, + m_password, + NULL, + GP_BLOCKING, + CreateAccountCallback, + &pid); + if(result != GP_NO_ERROR) + { + MessageBox("There was an error creating the account."); + return FALSE; + } + + // Check for success. + ///////////////////// + if(pid) + { + m_profileID = pid; + return TRUE; + } + + // Get the error code. + ////////////////////// + GPErrorCode errorCode; + gpGetErrorCode(&m_gp, &errorCode); + + // Handle the error code. + ///////////////////////// + if(errorCode == GP_NEWUSER_BAD_PASSWORD) + { + rcode = MessageBox( + "You have entered an incorrect password for this e-mail address\n" + "Would you like the password sent to the e-mail address?", + NULL, + MB_YESNO); + + // If no, we're done. + ///////////////////// + if(rcode == IDNO) + return FALSE; + + // Send the password. + ///////////////////// + SendPassword(); + + return FALSE; + } + + // An unknown error code. + ///////////////////////// + MessageBox("There was an error creating the account."); + return FALSE; +} + +void CheckAccountCallback(GPConnection * connection, void * arg_, void * param) +{ + GPCheckResponseArg * arg = (GPCheckResponseArg *)arg_; + int * pid = (int *)param; + + // Store the result. + //////////////////// + if(arg->result == GP_NO_ERROR) + *pid = arg->profile; + else + *pid = 0; +} + +BOOL CGp_statsDlg::CheckAccount() +{ + int pid; + int rcode; + + // Check for the account. + ///////////////////////// + GPResult result = gpCheckUser( + &m_gp, + m_nick, + m_email, + m_password, + GP_BLOCKING, + CheckAccountCallback, + &pid); + if(result != GP_NO_ERROR) + { + MessageBox("There was an error authenticating the account."); + return FALSE; + } + + // Check for success. + ///////////////////// + if(pid) + { + m_profileID = pid; + return TRUE; + } + + // Get the error code. + ////////////////////// + GPErrorCode errorCode; + gpGetErrorCode(&m_gp, &errorCode); + + // Handle the error code. + ///////////////////////// + if(errorCode == GP_CHECK_BAD_EMAIL) + { + // Ask if they want to create the account. + ////////////////////////////////////////// + rcode = MessageBox( + "This account does not exist.\n" + "Would you like to create it?", + NULL, + MB_YESNO); + + // If no, we're done. + ///////////////////// + if(rcode == IDNO) + return FALSE; + + // Create the account. + ////////////////////// + return CreateAccount(); + } + else if(errorCode == GP_CHECK_BAD_NICK) + { + rcode = MessageBox( + "There are no profiles under this account with the nick you have entered\n" + "Would you like to create one?", + NULL, + MB_YESNO); + + // If no, we're done. + ///////////////////// + if(rcode == IDNO) + return FALSE; + + // Create the account. + ////////////////////// + return CreateAccount(); + } + else if(errorCode == GP_CHECK_BAD_PASSWORD) + { + rcode = MessageBox( + "You have entered an incorrect password for this e-mail address\n" + "Would you like the password sent to the e-mail address?", + NULL, + MB_YESNO); + + // If no, we're done. + ///////////////////// + if(rcode == IDNO) + return FALSE; + + // Send the password. + ///////////////////// + SendPassword(); + + return FALSE; + } + + // An unknown error code. + ///////////////////////// + MessageBox("There was an error authenticating the account."); + return FALSE; +} + +BOOL statsAuthFinished; +void StatsAuthenticationCallback(int localid, int profileid, int authenticated, char *errmsg, void *instance) +{ + int * result = (int *)instance; + + *result = authenticated; + + if(authenticated != 1) + MessageBox(NULL, errmsg, "Error authenticating with the stats backend", MB_OK); + + statsAuthFinished = TRUE; +} + +BOOL CGp_statsDlg::StatsAuthentication() +{ + char response[33]; + int result; + + // The auth call. + ///////////////// + statsAuthFinished = FALSE; + PreAuthenticatePlayerPM( + 0, + m_profileID, + GenerateAuth(GetChallenge(NULL), (char *)(LPCSTR)m_password, response), + StatsAuthenticationCallback, + &result); + + // Wait for it to finish. + ///////////////////////// + while(!statsAuthFinished) + if(!PersistThink()) + CheckStatsConnection(); + + return (result == 1); +} + +void CGp_statsDlg::OnAuthenticate() +{ + // Update dialog members. + ///////////////////////// + UpdateData(); + + // Sanity check args. + ///////////////////// + if(!m_email.GetLength() || !m_nick.GetLength() || !m_password.GetLength()) + { + MessageBox("E-mail, nick, and password must not be blank."); + return; + } + + // If we're authenticated, unauthenticate. + ////////////////////////////////////////// + if(m_authenticated) + UnAuthenticate(); + + // Check the account. + ///////////////////// + if(!CheckAccount()) + return; + + // Do stats authentication. + /////////////////////////// + if(!StatsAuthentication()) + return; + + // We're authenticated. + /////////////////////// + Authenticated(); + + // Update dialog display. + ///////////////////////// + UpdateData(FALSE); +} + +persisttype_t TypeConversion(int type) +{ + if(type == 0) + return pd_private_rw; + if(type == 1) + return pd_private_ro; + if(type == 2) + return pd_public_rw; + return pd_public_ro; +} + +BOOL GetKeyValue(LPCSTR src, CString & key, CString & value) +{ + const char * str; + const char * keyStart; + const char * valueStart; + int keyLen; + int valueLen; + + // Check for no input. + ////////////////////// + if(!src) + return FALSE; + + // Check the starting \. + //////////////////////// + if(src[0] != '\\') + return FALSE; + + // Clear the key and value. + /////////////////////////// + key.Empty(); + value.Empty(); + + // Find the key and value, plus lengths. + //////////////////////////////////////// + keyStart = (src + 1); + valueStart = strchr(keyStart, '\\'); + if(!valueStart || (valueStart == keyStart)) + return FALSE; + keyLen = (valueStart - keyStart); + valueStart++; + str = strchr(valueStart, '\\'); + if(str) + valueLen = (str - valueStart); + else + valueLen = strlen(valueStart); + + // Copy off the key. + //////////////////// + char * keyStr = key.GetBuffer(keyLen); + memcpy(keyStr, keyStart, keyLen); + key.ReleaseBuffer(keyLen); + + // Copy off the value. + //////////////////// + char * valueStr = value.GetBuffer(valueLen); + memcpy(valueStr, valueStart, valueLen); + value.ReleaseBuffer(valueLen); + + return TRUE; +} + +void CGp_statsDlg::ClearKeys() +{ + int count = m_keys.GetCount(); + int i; + for(i = 0 ; i < count ; i++) + delete (CString *)m_keys.GetItemDataPtr(i); + m_keys.ResetContent(); +} + +void CGp_statsDlg::GotData(LPCSTR data) +{ + CString key; + CString value; + int nIndex; + + // Go through the keys/values. + ////////////////////////////// + while(GetKeyValue(data, key, value)) + { + // Adjust the data based on the key and value lengths. + ////////////////////////////////////////////////////// + data += (key.GetLength() + value.GetLength() + 2); + + // Add the new key/value. + ///////////////////////// + nIndex = m_keys.AddString(key); + if(nIndex != -1) + m_keys.SetItemDataPtr(nIndex, new CString(value)); + } +} + +BOOL getDataFinished; +void GetDataCallback(int localid, int profileid, persisttype_t type, int index, int success, time_t modified, char *data, int len, void *instance) +{ + getDataFinished = TRUE; + + if(success) + { + CGp_statsDlg * dlg = (CGp_statsDlg *)instance; + dlg->GotData(data); + } +} + +void CGp_statsDlg::OnGet() +{ + // Update dialog members. + ///////////////////////// + UpdateData(); + + // Clear the keys and value. + //////////////////////////// + ClearKeys(); + m_value.Empty(); + + // Make sure we're connected to the stats server. + ///////////////////////////////////////////////// + if(!CheckStatsConnection()) + return; + + // Get the data. + //////////////// + getDataFinished = FALSE; + GetPersistDataValues(0, m_profileID, TypeConversion(m_type), 0, "", GetDataCallback, this); + while(!getDataFinished) + if(!PersistThink()) + CheckStatsConnection(); + + // Update dialog display. + ///////////////////////// + UpdateData(FALSE); +} + +BOOL setDataFinished; +void SetDataCallback(int localid, int profileid, persisttype_t type, int index, int success, time_t modified, void *instance) +{ + setDataFinished = TRUE; +} + +void CGp_statsDlg::OnSet() +{ + // Update dialog members. + ///////////////////////// + UpdateData(); + + // Make sure we're connected to the stats server. + ///////////////////////////////////////////////// + if(!CheckStatsConnection()) + return; + + // Build the data string. + ///////////////////////// + int count = m_keys.GetCount(); + int i; + CString data; + CString key; + CString * value; + for(i = 0 ; i < count ; i++) + { + m_keys.GetText(i, key); + value = (CString *)m_keys.GetItemDataPtr(i); + + data += '\\'; + data += key; + data += '\\'; + data += *value; + } + + // Set some data. + ///////////////// + setDataFinished = FALSE; + SetPersistDataValues(0, m_profileID, TypeConversion(m_type), 0, (char *)(LPCSTR)data, SetDataCallback, this); + while(!setDataFinished) + if(!PersistThink()) + CheckStatsConnection(); +} + +void CGp_statsDlg::OnSelchangeKeys() +{ + UpdateData(); + + // Get the new key/value. + ///////////////////////// + int nIndex = m_keys.GetCurSel(); + if(nIndex != -1) + m_value = *(CString *)m_keys.GetItemDataPtr(nIndex); + + UpdateData(FALSE); +} + +void CGp_statsDlg::OnAdd() +{ + UpdateData(); + + if(m_newKey.IsEmpty()) + return; + + int nIndex = m_keys.AddString(m_newKey); + if(nIndex != -1) + m_keys.SetItemDataPtr(nIndex, new CString); + + m_newKey.Empty(); + + UpdateData(FALSE); +} + +void CGp_statsDlg::OnChangeValue() +{ + UpdateData(); + int nIndex = m_keys.GetCurSel(); + if(nIndex != -1) + { + CString * string = (CString *)m_keys.GetItemDataPtr(nIndex); + *string = m_value; + } +} diff --git a/code/gamespy/gstats/gp_stats/gp_statsDlg.h b/code/gamespy/gstats/gp_stats/gp_statsDlg.h new file mode 100644 index 00000000..b5646418 --- /dev/null +++ b/code/gamespy/gstats/gp_stats/gp_statsDlg.h @@ -0,0 +1,77 @@ +// gp_statsDlg.h : header file +// + +#if !defined(AFX_GP_STATSDLG_H__B7B25B2A_61E9_4BFE_BBD7_D7C4D7242B7D__INCLUDED_) +#define AFX_GP_STATSDLG_H__B7B25B2A_61E9_4BFE_BBD7_D7C4D7242B7D__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "../../gp/gp.h" + +///////////////////////////////////////////////////////////////////////////// +// CGp_statsDlg dialog + +class CGp_statsDlg : public CDialog +{ +// Construction +public: + CGp_statsDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CGp_statsDlg) + enum { IDD = IDD_GP_STATS_DIALOG }; + CListBox m_keys; + CString m_email; + CString m_nick; + CString m_password; + int m_profileID; + int m_type; + CString m_value; + CString m_newKey; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CGp_statsDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +public: + void GotData(LPCSTR data); + +// Implementation +protected: + HICON m_hIcon; + + BOOL m_authenticated; + GPConnection m_gp; + + void Authenticated(); + void UnAuthenticate(); + BOOL CheckStatsConnection(); + BOOL CheckAccount(); + BOOL CreateAccount(); + void SendPassword(); + BOOL StatsAuthentication(); + void ClearKeys(); + + // Generated message map functions + //{{AFX_MSG(CGp_statsDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnAuthenticate(); + afx_msg void OnDestroy(); + afx_msg void OnGet(); + afx_msg void OnSet(); + afx_msg void OnAdd(); + afx_msg void OnSelchangeKeys(); + afx_msg void OnChangeValue(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_GP_STATSDLG_H__B7B25B2A_61E9_4BFE_BBD7_D7C4D7242B7D__INCLUDED_) diff --git a/code/gamespy/gstats/gp_stats/res/gp_stats.ico b/code/gamespy/gstats/gp_stats/res/gp_stats.ico new file mode 100644 index 00000000..7eef0bcb Binary files /dev/null and b/code/gamespy/gstats/gp_stats/res/gp_stats.ico differ diff --git a/code/gamespy/gstats/gp_stats/res/gp_stats.rc2 b/code/gamespy/gstats/gp_stats/res/gp_stats.rc2 new file mode 100644 index 00000000..0cd2a66d --- /dev/null +++ b/code/gamespy/gstats/gp_stats/res/gp_stats.rc2 @@ -0,0 +1,13 @@ +// +// GP_STATS.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/gstats/gp_stats/resource.h b/code/gamespy/gstats/gp_stats/resource.h new file mode 100644 index 00000000..85b0316f --- /dev/null +++ b/code/gamespy/gstats/gp_stats/resource.h @@ -0,0 +1,33 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by gp_stats.rc +// +#define IDD_GP_STATS_DIALOG 102 +#define IDR_MAINFRAME 128 +#define IDC_EMAIL 1000 +#define IDC_NICK 1001 +#define IDC_PASSWORD 1002 +#define IDC_PROFILE_ID 1003 +#define IDC_AUTHENTICATE 1004 +#define IDC_PRIVATE_RW 1005 +#define IDC_PRIVATE_RO 1006 +#define IDC_PUBLIC_RW 1007 +#define IDC_PUBLIC_RO 1008 +#define IDC_GET 1009 +#define IDC_SET 1010 +#define IDC_VALUE 1012 +#define IDC_KEYS 1014 +#define IDC_ADD 1015 +#define IDC_NEW_KEY 1016 +#define IDC_EDIT1 1016 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1017 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/gamespy/gstats/gpersist.h b/code/gamespy/gstats/gpersist.h new file mode 100644 index 00000000..cb3b28ed --- /dev/null +++ b/code/gamespy/gstats/gpersist.h @@ -0,0 +1,415 @@ +/****** +gpersist.h +GameSpy Persistent Storage SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +****** + +Please see the GameSpy Persistent Storage SDK for more info + +*****/ +//todo: get/set @ offset / length + +#ifndef _GPERSIST_H_ +#define _GPERSIST_H_ + +#include "gstats.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/************************* +The following defines and prototypes are included +inside the "gstats.h" file, but listed here as well for easy reference +since they are also used by the Persistent Storage SDK. +The comments for them have also been changed to reflect their +specific use inside the Persistent Storage SDK. +**************************/ +#if 0 + /* Error codes */ + #define GE_NOERROR 0 + #define GE_NOSOCKET 1 /* Unable to create a socket */ + #define GE_NODNS 2 /* Unable to resolve a DNS name */ + #define GE_NOCONNECT 3 /* Unable to connect to stats server, or connection lost */ + #define GE_BUSY 4 /* Not used */ + #define GE_DATAERROR 5 /* Bad data from the stats server */ + + /* You need to fill these in with your game-specific info */ + extern char gcd_secret_key[256]; + extern char gcd_gamename[256]; + + /******** + InitStatsConnection + + DESCRIPTION + Opens a connection to the stats / persistent storage server. Should be done before calling + any of the other persistent storage functions. May block for 1-2 secs + while the connection is established so you will want to do this before + gameplay starts or in another thread. + + PARAMETERS + gameport: integer port associated with your server (may be the same as + your developer spec query port). If not appropriate for your game, pass in 0. + + RETURNS + GE_NODNS: Unable to resolve stats server DNS + GE_NOSOCKET: Unable to create data socket + GE_NOCONNECT: Unable to connect to stats server + GE_DATAERROR: Unable to receive challenge from stats server, or bad challenge + GE_NOERROR: Connected to stats server and ready to send data + + Note: If the connection fails, all Persistent Storage functions will fail. + *********/ + int InitStatsConnection(int gameport); + + /******** + IsStatsConnected + + DESCRIPTION + Returns whether or not you are currently connected to the stats server. Even + if your initial connection was successful, you may lose connection later and + want to try to reconnnect. + If a callback returns unsuccessfully, check this function to see if it was + because of a disconnection. + + RETURNS + 1 if connected, 0 otherwise + *********/ + int IsStatsConnected(); + + /******** + CloseStatsConnection + + DESCRIPTION + Closes the connection to the stats server. You should do this when done + with the connection. + *********/ + void CloseStatsConnection(void); + + /******** + GetChallenge + + DESCRIPTION + Returns the string to pass as the challenge to the GenerateAuth function. + + PARAMETERS + game: Pass in NULL (or your current game, if you are also using the Stats SDK) + + RETURNS + A string to pass to GenerateAuth to create the authentication hash + *********/ + char *GetChallenge(statsgame_t game); + + /******** + GenerateAuth + + DESCRIPTION + Used to generate on the "challengeresponse" parameter for the PreAuthenticatePlayer + functions. + + PARAMETERS + challenge: The challenge string generated by GetChallange. + password: The CD Key (un-hashed) or profile password + response: The output authentication string + + RETURNS + A pointer to response + *********/ + char *GenerateAuth(char *challenge, gsi_char *password,/*[out]*/char response[33]); +#endif //#ifdef 0 section from gstats.h + +/************************* +The rest of the prototypes in this file are specific to +the Persistent Storage SDK +**************************/ + +#ifndef GSI_UNICODE +#define GenerateAuth GenerateAuthA +#define PreAuthenticatePlayerCD PreAuthenticatePlayerCDA +#define GetProfileIDFromCD GetProfileIDFromCDA +#define GetPersistDataValues GetPersistDataValuesA +#define GetPersistDataValuesModified GetPersistDataValuesModifiedA +#define SetPersistDataValues SetPersistDataValuesA +#else +#define GenerateAuth GenerateAuthW +#define PreAuthenticatePlayerCD PreAuthenticatePlayerCDW +#define GetProfileIDFromCD GetProfileIDFromCDW +#define GetPersistDataValues GetPersistDataValuesW +#define GetPersistDataValuesModified GetPersistDataValuesModifiedW +#define SetPersistDataValues SetPersistDataValuesW +#endif + +/******** +persisttype_t +There are 4 types of persistent data stored for each player: +pd_private_ro: Readable only by the authenticated client it belongs to, can only by set on the server +pd_private_rw: Readable only by the authenticated client it belongs to, set by the authenticated client it belongs to +pd_public_ro: Readable by any client, can only be set on the server +pd_public_rw: Readable by any client, set by the authenicated client is belongs to +*********/ +typedef enum {pd_private_ro, pd_private_rw, pd_public_ro, pd_public_rw} persisttype_t; + +/***************** +CALLBACK FUNCTIONS +*****************/ + +/**************** +PersAuthCallbackFn + +DESCRIPTION +This type of function is passed to the two PreAuthentication functions. +It returns the result of the Authentication request. + +PARAMETERS +localid: The localid number passed into the PreAuthenticate function +profileid: If authentication was successful, the profileid for this user +authenticated: 1 if the player was authenticated < 1 otherwise +errmsg: Error returned by the server to indicate why the player was not authenticated +instance: Opaque value passed into the PreAuthenticate function (for your use) +*****************/ +typedef void (*PersAuthCallbackFn)(int localid, int profileid, int authenticated, gsi_char *errmsg, void *instance); + + +/**************** +PersDataCallbackFn + +DESCRIPTION +This type of function is passed to the two GetPersistData functions. +It returns the result of the data request. +localid + +PARAMETERS +localid: The localid number passed into the GetPersistData function +profileid: The profileid of the user who the data was requested for +type: The type of persistent data being returned +index: The persistent data index +success: 1 if the data was retrieved successfully + 2 if the data had not been modified since the time requested + < 1 if there was an error +modified: The last time the data for this index was modified (any persist type) + Only returned if success is 1 +data: Pointer to the data being returned. Note: you must use or copy + off the data before returning from the callback, as it may be freed or overwritten + once the callback is complete. +len: Length of the data being returned. 0 indicates that no data was stored on the server + (if success was 1) +instance: Opaque value passed into the GetPersistData function (for your use) +*****************/ +typedef void (*PersDataCallbackFn)(int localid, int profileid, persisttype_t type, int index, int success, time_t modified, char *data, int len, void *instance); + +/**************** +PersDataSaveCallbackFn + +DESCRIPTION +This type of function is passed to the two SetPersistData functions. +It returns the result of the set data request. + +PARAMETERS +localid: The localid number passed into the SetPersistData function +profileid: The profileid of the user who the data is being saved for +success: 1 if the data was saved successfully, < 1 otherwise +modified: The time recorded on the backend for last modification +instance: Opaque value passed into the SetPersistData function (for your use) +*****************/ +typedef void (*PersDataSaveCallbackFn)(int localid, int profileid, persisttype_t type, int index, int success, time_t modified, void *instance); + +/**************** +ProfileCallbackFn + +DESCRIPTION +This type of function is passed to the GetProfileIDFromCD function. +It returns the result of the lookup request. + +PARAMETERS +localid: The localid number passed into the GetProfileIDFromCD function +profileid: The profileid of the requested user, if the lookup was successful +success: 1 if the lookup was successful, < 1 otherwise +instance: Opaque value passed into the GetProfileIDFromCD function (for your use) +*****************/ +typedef void (*ProfileCallbackFn)(int localid, int profileid, int success, void *instance); + + +/*************************** +PERSISTENT STORAGE FUNCTIONS +****************************/ + +/**************** +PreAuthenticatePlayerPM +PreAuthenticatePlayerCD + +DESCRIPTION +These two functions are used to authenticate a player on the Stats server. +A player MUST be authenticated before getting private persistent data, or +setting public or private data. +If the StatsServer connection is ever lost and reconnected (using InitStatsConnection) +the player must be reauthenticated before reading / writing their data. +PreAuthenticatePlayerPM authenticates players using the Presence & Messaging SDK account info +PreAuthenticatePlayerCD authenticates players using the CDKey SDK CD Key. +Typically you will only use one of these in your game (depending on whether you use +the Presence & Messaging SDK, or the CD Key SDK), however they can both be used in the +same game if needed. + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the callback + to allow you to identify which player it is referring to. +profileid: (PreAuthenticatePlayerPM) The profileid of the player being authenicated. This can be obtained in the + Presence & Messaging SDK through gpIDFromProfile() +nick: (PreAuthenticatePlayerCD) Nickname of the player to authenticate. Note that the nickname is not actually + authenticated, it is simply used to determine which profile under the authenticated CD Key to use. + Each CD Key can have mutiple profiles, each using a different nick. +keyhash: (PreAuthenticatePlayerCD) Hash of the player's CD Key. If used on the server, this can be obtained from gcd_getkeyhash + On the client, you can easily get the hash by calling GenerateAuth() with challenge as an empty string ("") + and the CD Key has the password parameter. +challengeresponse: Result of the GenerateAuth() call, after passing in the challenge and the client's + password or CD Key +PersAuthCallbackFn: Callback to be called after the authentication is complete +instance: Pointer that will be passed to the callback function (for your use) + Typically used for passing an object or structure pointer into the callback. +*****************/ +void PreAuthenticatePlayerPartner(int localid, const char* authtoken, const char *challengeresponse, PersAuthCallbackFn callback, void *instance); +void PreAuthenticatePlayerPM(int localid, int profileid, const char *challengeresponse, PersAuthCallbackFn callback, void *instance); +void PreAuthenticatePlayerCD(int localid, const gsi_char *nick, const char *keyhash, const char *challengeresponse, PersAuthCallbackFn callback, void *instance); + +/**************** +GetProfileIDFromCD + +DESCRIPTION +Given a nickname and CD Key hash, this will lookup the profileid for the user. +If the user has never authenticated (and has no persistent data associated with them), +the callback will indicate a failure. No persistent data can be retreived for the user, +since they don't have any stored. Persistent data can be stored, but only after authenticating +with PreAuthenticatePlayerCD. + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the callback + to allow you to identify which player it is referring to. +nick: The nick of the user whose profileid you are looking up +keyhash: The CD Key Hash of the user whose profileid you are looking up +ProfileCallbackFn: Callback to be called when the lookup is completed +instance: Pointer that will be passed to the callback function (for your use) +*****************/ +void GetProfileIDFromCD(int localid, const gsi_char *nick, const char *keyhash, ProfileCallbackFn callback, void *instance); + +/**************** +GetPersistData[Modified] + +DESCRIPTION +Gets the entire block of persistent data for a user. +The data and length are returned in the callback function. +Note that only an authenticated player can get their private data. Any +player can get any other player's public data. + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the callback + to allow you to identify which player it is referring to. +profileid: The profileid of the player whose data you are looking up. + Returned by gpIDFromProfile() in the Presence & Messaging SDK, or using GetProfileIDFromCD +type: The type of persistent data you are looking up +index: Each profile can have multiple persistent data records associated with them. Usually you + just want to use index 0. +modifiedsince: A time value to limit the request for data. Data will only be returned if it has been + modified since the time provided. If data has not been modified since that time, the callback will be + called with a success value that indicates it is unmodified. + Note: modification time is tracked for the given profileid/index, not on a per persisttype basis +PersDataCallbackFn: Callback that will be called with the data when it is returned +ProfileCallbackFn: Callback to be called when the lookup is completed +instance: Pointer that will be passed to the callback function (for your use) +*****************/ +void GetPersistData(int localid, int profileid, persisttype_t type, int index, PersDataCallbackFn callback, void *instance); +void GetPersistDataModified(int localid, int profileid, persisttype_t type, int index, time_t modifiedsince, PersDataCallbackFn callback, void *instance); + +/**************** +GetPersistDataValues[Modified] + +DESCRIPTION +If you store your data in key\value delimited pairs, GetPersistDataValues will +allow you to easily retrieve a subset of the stored data. To retrieve the entire +data set, use GetPersistData. The data will be returned as a null-terminated string, +unless no data is available (in which case len will be 0 in the callback). + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the callback + to allow you to identify which player it is referring to. +profileid: The profileid of the player whose data you are looking up. + Returned by gpIDFromProfile() in the Presence & Messaging SDK, or using GetProfileIDFromCD +type: The type of persistent data you are looking up +index: Each profile can have multiple persistent data records associated with them. Usually you + just want to use index 0. +modifiedsince: A time value to limit the request for data. Data will only be returned if it has been + modified since the time provided. If data has not been modified since that time, the callback will be + called with a success value that indicates it is unmodified. + Note: modification time is tracked for the given profileid/index, not on a per-persisttype or per-key basis +keys: A "\" delimited list of the keys you want returned (for example: "\clan\color\homepage\birthday") +PersDataCallbackFn: Callback that will be called with the data when it is returned +instance: Pointer that will be passed to the callback function (for your use) +*****************/ +void GetPersistDataValues(int localid, int profileid, persisttype_t type, int index, gsi_char *keys, PersDataCallbackFn callback, void *instance); +void GetPersistDataValuesModified(int localid, int profileid, persisttype_t type, int index, time_t modifiedsince, gsi_char *keys, PersDataCallbackFn callback, void *instance); + +/**************** +SetPersistData + +DESCRIPTION +Sets the entire block of persistent data for a user. +The profileid for whom the data is being set MUST have been authenticated already. + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the callback + to allow you to identify which player it is referring to. +profileid: The profileid of the player whose data you are setting. + The player must have already been authenticated with one of the PreAuthenticatePlayer functions. +type: The type of persistent data you are setting. Only rw data is setable. +index: Each profile can have multiple persistent data records associated with them. Usually you + just want to use index 0. +data: The persistent data to be saved +len: The length of the data. If you are setting key\value delimited data, make + sure the "len" parameter includes length of the null terminator +PersDataSaveCallbackFn: Callback that will be called with the data save is complete +instance: Pointer that will be passed to the callback function (for your use) +*****************/ +void SetPersistData(int localid, int profileid, persisttype_t type, int index, const char *data, int len, PersDataSaveCallbackFn callback, void *instance); + +/**************** +SetPersistDataValues + +DESCRIPTION +If you are saving data in key\value delimited format, you can use this function +to only set SOME of the key\value pairs. Only the key value pairs you include in +they keyvalues parameter will be updated, the other pairs will stay the same. + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the callback + to allow you to identify which player it is referring to. +profileid: The profileid of the player whose data you are setting. + The player must have already been authenticated with one of the PreAuthenticatePlayer functions. +type: The type of persistent data you are setting. Only rw data is setable. +index: Each profile can have multiple persistent data records associated with them. Usually you + just want to use index 0. +keyvalues: The key\value pairs to be updated (for example: \clan\The A-Team\homepage\http://www.myclan.net\age\15) +PersDataSaveCallbackFn: Callback that will be called with the data save is complete +instance: Pointer that will be passed to the callback function (for your use) +*****************/ +void SetPersistDataValues(int localid, int profileid, persisttype_t type, int index, const gsi_char *keyvalues, PersDataSaveCallbackFn callback, void *instance); + +/**************** +PersistThink + +DESCRIPTION +This function needs to be called any time a asynchronous operation is in progress. It will +check for the incoming replies and call the callbacks associated with them as needed. +It's recommened that you call this in your main loop at all times while you are connected +to the stats server, so that if the stats server disconnects it can be detected immediately. + +RETURNS +0 if the connection to the stats server is lost, 1 otherwise +*****************/ +int PersistThink(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/gstats/gstats.c b/code/gamespy/gstats/gstats.c new file mode 100644 index 00000000..2edb3412 --- /dev/null +++ b/code/gamespy/gstats/gstats.c @@ -0,0 +1,1681 @@ +/****** +gstats.c +GameSpy Stats/Tracking SDK +GameSpy Persistent Storage SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +****** + +Please see the GameSpy Stats and Tracking SDK documentation for more info + +******/ + +/******** +INCLUDES +********/ +#include "gstats.h" +#include "gpersist.h" +#include "../common/gsAvailable.h" +#include "../darray.h" +#include "../md5.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/******** +TYPEDEFS +********/ +struct statsgame_s +{ + int connid; + int sesskey; + int usebuckets; + bucketset_t buckets; + char challenge[9]; + DArray playernums; //for player number translation + DArray teamnums; //for team number translation + int totalplayers, totalteams; + gsi_time sttime; + +}; + +typedef enum {rt_authcb, rt_datacb, rt_savecb, rt_profilecb} reqtype_t; +typedef struct +{ + reqtype_t reqtype; + int localid; + int profileid; + persisttype_t pdtype; + int pdindex; + void *instance; + void *callback; + +} serverreq_t; + + +/******** +PROTOTYPES +********/ +static int ServerOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index); +static double ServerOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index); +static char *ServerOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index); + +static int TeamOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index); +static double TeamOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index); +static char *TeamOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index); + +static int PlayerOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index); +static double PlayerOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index); +static char *PlayerOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index); + +static char *CreateBucketSnapShot(bucketset_t buckets); + +#ifdef ALLOW_DISK +static void CheckDiskFile(); +static void DiskWrite(char *line, int len); +#endif +static void InternalInit(); +static int SendChallengeResponse(const char *indata, int gameport); +static int RecvSessionKey(); +static int DoSend(char *data, int len); +static void xcode_buf(char *buf, int len); +static int g_crc32(char *s, int len); +static void create_challenge(int challenge, char chstr[9]); +static char *value_for_key(const char *s, const char *key); +static char *value_for_key_safe(const char *s, const char *key); +static int get_sockaddrin(const char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent); +/************** +PERSISTENT STORAGE PROTOTYPES +**************/ +static void AddRequestCallback(reqtype_t reqtype, int localid, int profileid, persisttype_t pdtype, int pdindex, void *callback, void *instance); +static void SendPlayerAuthRequest(char *data, int len, int localid, PersAuthCallbackFn callback, void *instance); +static void SendPlayerAuthRequest(char *data, int len, int localid, PersAuthCallbackFn callback, void *instance); +static int SocketReadable(SOCKET s); +static char *FindFinal(char *buff, int len); +static int FindRequest(reqtype_t reqtype, int localid, int profileid); +static void ProcessPlayerAuth(const char *buf, int len); +static void ProcessGetPid(const char *buf, int len); +static void ProcessGetData(const char *buf, int len); +static void ProcessSetData(const char *buf, int len); +static void ProcessStatement(char *buff, int len); +static int ProcessInBuffer(char *buff, int len); +static void CallReqCallback(int reqindex, int success, time_t modified, char *data, int length); +static void ClosePendingCallbacks(); +static void SetPersistDataHelper(int localid, int profileid, persisttype_t type, int index, const char *data, int len, PersDataSaveCallbackFn callback, void *instance, int kvset); +void GetPersistDataValuesA(int localid, int profileid, persisttype_t type, int index, char *keys, PersDataCallbackFn callback, void *instance); +void GetPersistDataValuesModifiedA(int localid, int profileid, persisttype_t type, int index, time_t modifiedsince, char *keys, PersDataCallbackFn callback, void *instance); + +/******** +DEFINES +********/ +//#define SSHOST "207.199.80.230" +#define SSHOST "gamestats." GSI_DOMAIN_NAME +#define SSPORT 29920 + +#define FIXGAME(g,r) if (g == NULL) g = g_statsgame; if (g == NULL) return r; +#define DoFunc(f,g, n, v, t, r) \ + if (g == NULL) g = g_statsgame; \ + if (!g) r = v; \ + else { \ + r = f(g->buckets, n, v); \ + if (!r) \ + r = BucketNew(g->buckets, n, t, v); } +#define DOXCODE(b, l, e) enc = e; xcode_buf(b,l); + + +/******** +VARS +********/ +char gcd_gamename[256] = ""; +char gcd_secret_key[256] = ""; +static statsgame_t g_statsgame = NULL; +static int connid = 0; +static int sesskey = 0; +static SOCKET sock = INVALID_SOCKET; +/* #define enc1 "GameSpy 3D" + #define enc2 "Industries" + #define enc3 "ProjectAphex" + #define STATSFILE "gstats.dat" */ +/* A couple vars to help avoid the string table */ +static char enc1[16] = {'\0','a','m','e','S','p','y','3','D','\0'}; +static char enc3[16] = {'\0','r','o','j','e','c','t','A','p','h','e','x','\0'}; + +#ifdef ALLOW_DISK +static char statsfile[16] = {'\0','s','t','a','t','s','.','d','a','t','\0'}; +static char enc2[16]= {'\0','n','d','u','s','t','r','i','e','s','\0'}; +#endif + +static char finalstr[10] = {'\0','f','i','n','a','l','\\','\0'}; +static char *enc = enc1; +static int internal_init = 0; +static char *rcvbuffer = NULL; +static int rcvmax = 0; +static int rcvlen = 0; +// Changed By Saad Nader, 09-16-2004 +// Due to confliction with MacOS X +/////////////////////////////////////////// +static int stats_initstate = init_none; +static int gameport = 0; + +static gsi_time initstart = 0; +static gsi_time inittimeout = 20000; // 20 seconds + +char StatsServerHostname[64] = SSHOST; + +static DArray serverreqs = NULL; //for pre-authentication requests + + +BucketFunc bucketfuncs[NUMOPS] = +{BucketSet, BucketAdd, BucketSub, BucketMult, BucketDiv, BucketConcat, BucketAvg}; + +void * bopfuncs[][3] = +{ + {ServerOpInt, ServerOpFloat, ServerOpString}, + {TeamOpInt, TeamOpFloat, TeamOpString}, + {PlayerOpInt, PlayerOpFloat, PlayerOpString}, +}; + +/****************************************************************************/ +/* PUBLIC FUNCTIONS */ +/****************************************************************************/ +#define RAWSIZE 128 +char *GenerateAuthA(const char *challenge, const char *password, char response[33]) +{ + char rawout[RAWSIZE]; + + /* check to make sure we weren't passed a huge pass/challenge */ + if (strlen(password) + strlen(challenge) + 20>= RAWSIZE) + { + strcpy(response,"CD Key or challenge too long"); + return response; + } + + /* response = MD5(pass + challenge) */ + sprintf(rawout, "%s%s",password, challenge ); + + /* do the response md5 */ + MD5Digest((unsigned char *)rawout, strlen(rawout), response); + return response; +} +#ifdef GSI_UNICODE +char *GenerateAuthW(const char* challenge, const unsigned short *password, char response[33]) +{ + char* password_A = UCS2ToUTF8StringAlloc(password); + GenerateAuthA(challenge, password_A, response); + gsifree(password_A); + return response; +} +#endif + +/****************************************************************************/ +int InitStatsAsync(int theGamePort, gsi_time theInitTimeout) +{ + struct sockaddr_in saddr; + char tempHostname[128]; + int ret; + + gameport = theGamePort; + + if (theInitTimeout != 0) + inittimeout = theInitTimeout; + + /* check if the backend is available */ + if(__GSIACResult != GSIACAvailable) + return GE_NOSOCKET; + + /* Init our hidden strings if needed */ + if (!internal_init) + InternalInit(); + + SocketStartUp(); + sesskey = (int)current_time(); + + /* Get connected */ + if (sock != INVALID_SOCKET) + CloseStatsConnection(); + + rcvlen = 0; //make sure ther receive buffer is cleared + + if (inet_addr(StatsServerHostname) == INADDR_NONE) + { + strcpy(tempHostname, gcd_gamename); + strcat(tempHostname,"."); + strcat(tempHostname,StatsServerHostname); + } else + strcpy(tempHostname, StatsServerHostname); //it's already been resolved + + if (get_sockaddrin(tempHostname,SSPORT,&saddr,NULL) == 0) + return GE_NODNS; + +#ifdef INSOCK + sock = socket ( AF_INET, SOCK_STREAM, 0 ); +#else + sock = socket ( AF_INET, SOCK_STREAM, IPPROTO_TCP ); +#endif + if (sock == INVALID_SOCKET) + return GE_NOSOCKET; + + SetSockBlocking(sock, 0); + + ret = connect(sock, (struct sockaddr*)&saddr, sizeof(saddr)); + if (gsiSocketIsError(ret)) + { + int anError = GOAGetLastError(sock); + if ((anError != WSAEWOULDBLOCK) && (anError != WSAETIMEDOUT) && (anError != WSAEINPROGRESS)) + { + stats_initstate = init_failed; + closesocket(sock); + return GE_NOCONNECT; + } + } + + // allocate the recv buffer + rcvbuffer = gsimalloc(64); + if (rcvbuffer == NULL) + return GE_NOCONNECT; // add a new error code for out of mem? + + rcvmax = 64; + rcvlen = 0; + + initstart = current_time(); + stats_initstate = init_connecting; + return GE_CONNECTING; +} + +/****************************************************************************/ +int InitStatsThink() +{ + switch(stats_initstate) + { + case init_failed: return GE_NOCONNECT; + case init_connecting: + { + // Check if socket is writeable yet + int aWriteFlag = 0; + int aExceptFlag = 0; + int aResult = GSISocketSelect(sock, NULL, &aWriteFlag, &aExceptFlag); + if ((gsiSocketIsError(aResult)) || // socket error + (aResult == 1 && aExceptFlag == 1)) // exception + { + stats_initstate = init_failed; + CloseStatsConnection(); + return GE_NOCONNECT; + } + else if (aResult == 0) // no progress yet + { + // Should we continue to wait? + if (current_time() - initstart > inittimeout) + { + stats_initstate = init_failed; + CloseStatsConnection(); + return GE_TIMEDOUT; + } + else + return GE_CONNECTING; + } + + // Otherwise connected + assert(aResult == 1 && aWriteFlag == 1); + stats_initstate = init_awaitchallenge; + // fall through + } + case init_awaitchallenge: + { + int ret = 0; + + // Try to receive data + if (!CanReceiveOnSocket(sock)) + { + // should we continue to wait? + if (current_time() - initstart > inittimeout) + { + stats_initstate = init_failed; + CloseStatsConnection(); + return GE_TIMEDOUT; + } + return GE_CONNECTING; + } + + // Receive the 38 byte challenge + ret = recv(sock, rcvbuffer+rcvlen, rcvmax-rcvlen, 0); + if (gsiSocketIsError(ret)) + { + stats_initstate = init_failed; + CloseStatsConnection(); + return GE_NOCONNECT; + } + rcvlen += ret; + rcvmax -= ret; + + // need at least 38 bytes + if (rcvlen < 38) + return GE_CONNECTING; + + // Process challenge + rcvbuffer[rcvlen] = '\0'; + stats_initstate = init_awaitsessionkey; + + /* Decode it */ + DOXCODE(rcvbuffer, rcvlen, enc1); + /* Send a response */ + ret = SendChallengeResponse(rcvbuffer, gameport); + if (ret != GE_NOERROR) + { + stats_initstate = init_failed; + CloseStatsConnection(); + return ret; + } + + stats_initstate = init_awaitsessionkey; + + // clear receive buffer for next stage + rcvmax += rcvlen; // reclaim the used bytes as free space + rcvlen = 0; + memset(rcvbuffer, 0, (unsigned int)rcvmax); + + // fall through + } + case init_awaitsessionkey: + { + int ret = 0; + + // Try to receive data + if (!CanReceiveOnSocket(sock)) + { + // should we continue to wait? + if (current_time() - initstart > inittimeout) + { + stats_initstate = init_failed; + CloseStatsConnection(); + return GE_TIMEDOUT; + } + return GE_CONNECTING; + } + + ret = RecvSessionKey(); + if (ret != GE_NOERROR) + { + stats_initstate = init_failed; + CloseStatsConnection(); + return ret; + } + + // Init complete + // Clear the receive buffer + rcvmax += rcvlen; + rcvlen = 0; + memset(rcvbuffer, 0, (unsigned int)rcvmax); + + #ifdef ALLOW_DISK + /* Check for old data */ + CheckDiskFile(); + #endif + + stats_initstate = init_complete; + + // fall through + } + case init_complete: + return GE_NOERROR; + + default: + return GE_NOCONNECT; + }; +} + + +/****************************************************************************/ +// Blocking version of InitStatsAsync, for backwards compatability +int InitStatsConnection(int gameport) +{ + int aResult = InitStatsAsync(gameport, 0); + while (aResult == GE_CONNECTING) + { + aResult = InitStatsThink(); + msleep(5); + } + return aResult; +} + +/****************************************************************************/ +void CloseStatsConnection() +{ + if (sock != INVALID_SOCKET) + { + shutdown(sock,2); + closesocket(sock); + } + sock = INVALID_SOCKET; + //call any pending callbacks with the data as lost + ClosePendingCallbacks(); + if (rcvbuffer != NULL) + { + gsifree(rcvbuffer); + rcvbuffer = NULL; + rcvmax = 0; + rcvlen = 0; + } + +} + +/****************************************************************************/ +int IsStatsConnected() +{ + return (sock != INVALID_SOCKET); +} + +/****************************************************************************/ +#define CHALLENGEXOR 0x38F371E6 +char *GetChallenge(statsgame_t game) +{ + static char challenge[9]; + if (game == NULL) + game = g_statsgame; + if (game == NULL) + { + create_challenge(connid ^ CHALLENGEXOR,challenge); + return challenge; + } + return game->challenge; +} + +/****************************************************************************/ +statsgame_t NewGame(int usebuckets) +{ + statsgame_t game = (statsgame_t)gsimalloc(sizeof (struct statsgame_s)); + char data[256]; + int len; + + if (!internal_init) + InternalInit(); + game->connid = connid; + game->sesskey = sesskey++; + game->buckets = NULL; + game->playernums = NULL; + game->teamnums = NULL; + game->usebuckets = usebuckets; + /* If connected, try to send */ + if (sock != INVALID_SOCKET) + { + char respformat[] = "\xC\x1C\xA\x1D\x2\x2\x19\x24\x2C\x34\x6\x17\x3E\x1C\x6\xE\x39\x46\x10\x1D\x3\xD\x16\xB\x3B\x17\x16\x36\x40\x7"; + //"\newgame\\connid\%d\sesskey\%d" + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(data,respformat,game->connid, game->sesskey); + len = DoSend(data, len); + if (len <= 0) + { + CloseStatsConnection(); + } + create_challenge(game->connid ^ CHALLENGEXOR,game->challenge); + } + /* If send failed then write to disk */ + if (sock == INVALID_SOCKET) + { +#ifdef ALLOW_DISK + char respformat[] = "\xC\x1C\xA\x1D\x2\x2\x19\x24\x2C\x34\x16\x1D\x23\x1\x4\xF\x1C\x3F\x51\x25\x2C\xB\xD\x19\x3C\x1E\xA\x4\x2\x6\x28\x64\x14"; + // "\newgame\\sesskey\%d\challenge\%d"; + + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(data,respformat,game->sesskey, game->sesskey ^ CHALLENGEXOR); + DiskWrite(data, len); + game->connid = 0; + create_challenge(game->sesskey ^ CHALLENGEXOR,game->challenge); + +#else + gsifree(game); + game = NULL; +#endif + + } + + if (game && game->usebuckets) + { + game->buckets = NewBucketSet(); + game->playernums = ArrayNew(sizeof(int),32,NULL); + game->teamnums = ArrayNew(sizeof(int),2,NULL); + game->totalplayers = game->totalteams = 0; + } + if (game) + game->sttime = current_time(); + g_statsgame = game; + return game; + +} + + +/****************************************************************************/ +void FreeGame(statsgame_t game) +{ + if (!game || game == g_statsgame) + { + game = g_statsgame; + g_statsgame = NULL; + } + if (!game) + return; + if (game->usebuckets) + { + if (game->buckets != NULL) + FreeBucketSet(game->buckets); + if (game->playernums != NULL) + ArrayFree(game->playernums); + if (game->teamnums != NULL) + ArrayFree(game->teamnums); + } + gsifree(game); +} + +/****************************************************************************/ +int SendGameSnapShotA(statsgame_t game, const char *snapshot, int final) +{ + int snaplen; + int len; + int ret = GE_NOERROR; + char *snapcopy; + char *data; + FIXGAME(game, GE_DATAERROR); + + /* If using buckets, get the data out of the buckets */ + if (game->usebuckets) + snapcopy = CreateBucketSnapShot(game->buckets); + else + snapcopy = goastrdup(snapshot); + snaplen = (int)strlen(snapcopy); + + data = (char *)gsimalloc((unsigned int)snaplen + 256); + + /* Escape the data */ + while (snaplen--) + if (snapcopy[snaplen] == '\\') + snapcopy[snaplen] = '\x1'; + + /* If connected, try to send it */ + if (sock != INVALID_SOCKET) + { + // Updated response format to contain connid + //char respformat[] = "\xC\x7\x1F\xE\x2\x2\x19\x24\x2C\x34\x16\x1D\x23\x1\x4\xF\x1C\x3F\x51\x25\x2C\xC\xA\x16\x35\x2E\x4A\xE\x39\x4\x15\x2C\x15\xC\x4\xC\x31\x2E\x4A\x19"; + char respformat[] = "\xC\x7\x1F\xE\x2\x2\x19\x24\x2C\x34\x16\x1D\x23\x1\x4\xF\x1C\x3F\x51\x25\x2C\xB\xA\x16\x3E\x1B\xB\x36\x40\x7\x28\x25\x1F\x6\x00\x24\x75\x16\x33\xD\x4\xE\x11\x25\x11\x1C\x4\x24\x75\x1"; + // "\updgame\\sesskey\%d\done\%d\gamedata\%s" + // The above string is now: + // "\updgame\\sesskey\%d\connid\%d\done\%d\gamedata\%s" + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(data, respformat, game->sesskey, game->connid, final, snapcopy); + snaplen = DoSend(data, len); + /* If the send failed, close the socket */ + if (snaplen <= 0) + { + CloseStatsConnection(); + } + } + /* If not connected, or send failed, return error or log to disk */ + if (sock == INVALID_SOCKET) + { +#ifdef ALLOW_DISK + char respformat[] = "\xC\x7\x1F\xE\x2\x2\x19\x24\x2C\x34\x16\x1D\x23\x1\x4\xF\x1C\x3F\x51\x25\x2C\xB\xA\x16\x3E\x1B\xB\x36\x40\x7\x28\x25\x1F\x6\x0\x24\x75\x16\x33\xD\x4\xE\x11\x25\x11\x1C\x4\x24\x75\x1\x33\xE\x9\x3F\x45"; + //"\updgame\\sesskey\%d\connid\%d\done\%d\gamedata\%s\dl\1" + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(data, respformat, game->sesskey, game->connid, final, snapcopy); + DiskWrite(data, len); +#else + ret = GE_NOCONNECT; +#endif + } + gsifree(snapcopy); + gsifree(data); + return ret; +} +#ifdef GSI_UNICODE +int SendGameSnapShotW(statsgame_t game, const unsigned short*snapshot, int final) +{ + char* snapshot_A = UCS2ToUTF8StringAlloc(snapshot); + int result = SendGameSnapShotA(game, snapshot_A, final); + gsifree(snapshot_A); + return result; +} +#endif + +/****************************************************************************/ +void NewPlayerA(statsgame_t game, int pnum, char *name) +{ + int i = -1; + FIXGAME(game, ;) + while (pnum >= ArrayLength(game->playernums)) + ArrayAppend(game->playernums, &i); + i = game->totalplayers++; + /* update the pnum array */ + ArrayReplaceAt(game->playernums,&i, pnum); + BucketIntOp(game, "ctime",bo_set,(int)(current_time() - game->sttime) / 1000,bl_player,pnum); + BucketStringOp(game,"player",bo_set,name, bl_player,pnum); + +} +#ifdef GSI_UNICODE +void NewPlayerW(statsgame_t game, int pnum, unsigned short *name) +{ + char* name_A = UCS2ToUTF8StringAlloc(name); + NewPlayerA(game, pnum, name_A); + gsifree(name_A); +} +#endif + +/****************************************************************************/ +void RemovePlayer(statsgame_t game,int pnum) +{ + FIXGAME(game, ;); + BucketIntOp(game,"dtime",bo_set,(int)(current_time() - game->sttime) / 1000, bl_player, pnum); +} + +/****************************************************************************/ +void NewTeamA(statsgame_t game,int tnum, char *name) +{ + int i = -1; + FIXGAME(game, ;) + while (tnum >= ArrayLength(game->teamnums)) + ArrayAppend(game->teamnums, &i); + i = game->totalteams++; + /* update the tnum array */ + ArrayReplaceAt(game->teamnums,&i, tnum); + BucketIntOp(game, "ctime",bo_set,(int)(current_time() - game->sttime) / 1000,bl_team,tnum); + BucketStringOp(game,"team",bo_set,name, bl_team,tnum); +} +#ifdef GSI_UNICODE +void NewTeamW(statsgame_t game,int tnum, unsigned short *name) +{ + char* name_A = UCS2ToUTF8StringAlloc(name); + NewTeamA(game, tnum, name_A); + gsifree(name_A); +} +#endif + +/****************************************************************************/ +void RemoveTeam(statsgame_t game, int tnum) +{ + FIXGAME(game, ;); + BucketIntOp(game,"dtime",bo_set,(int)(current_time() - game->sttime) / 1000, bl_team, tnum); +} + +/**************************************************************************** + * PERSISTENT STORAGE FUNCTIONS + ****************************************************************************/ + +/****************************************************************************/ +void PreAuthenticatePlayerPartner(int localid, const char * authtoken, const char *challengeresponse, PersAuthCallbackFn callback, void *instance) +{ + char respformat[] = "\xC\x13\x1A\x1E\xD\x13\x28\x1D\x11\x1D\x11\x10\x24\x1D\x04\x0F\x0B\x3F\x51\x32\x2C\x1A\x00\x0B\x20\x2E\x4A\x19\x39\x0F\x1D\x25\x2C\x4D\x01"; + //\authp\\authtoken\%s\resp\%s\lid\%d"; + int len; + char data[256]; + + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(data, respformat, authtoken, challengeresponse, localid); + + SendPlayerAuthRequest(data, len, localid, callback, instance); +} + + +/****************************************************************************/ +void PreAuthenticatePlayerPM(int localid, int profileid, const char *challengeresponse, PersAuthCallbackFn callback, void *instance) +{ + char respformat[] = "\xC\x13\x1A\x1E\xD\x13\x28\x1D\x0\x1\x1\x24\x75\x16\x33\x18\x0\x10\x4\x1D\x55\x1B\x39\x14\x39\x16\x33\x4F\x1"; + //\authp\\pid\%d\resp\%s\lid\%d + int len; + char data[256]; + + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(data, respformat, profileid, challengeresponse,localid); + + SendPlayerAuthRequest(data, len, localid, callback, instance); + +} + +/****************************************************************************/ +void PreAuthenticatePlayerCDA(int localid, const char *nick, const char *keyhash, const char *challengeresponse, PersAuthCallbackFn callback, void *instance) +{ + char respformat[] = "\xC\x13\x1A\x1E\xD\x13\x28\x1D\x1E\x1\x6\x13\xC\x57\x1C\x36\xE\x6\xD\x29\x11\x1B\xD\x24\x75\x1\x33\x18\x0\x10\x4\x1D\x55\x1B\x39\x14\x39\x16\x33\x4F\x1"; + //\authp\\nick\%s\keyhash\%s\resp\%s\lid\%d + int len; + char data[256]; + + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(data, respformat, nick, keyhash, challengeresponse,localid); + + SendPlayerAuthRequest(data, len, localid, callback, instance); + +} +#ifdef GSI_UNICODE +void PreAuthenticatePlayerCDW(int localid, const unsigned short *nick, const char *keyhash, const char *challengeresponse, PersAuthCallbackFn callback, void *instance) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + PreAuthenticatePlayerCDA(localid, nick_A, keyhash, challengeresponse, callback, instance); + gsifree(nick_A); +} +#endif + +/****************************************************************************/ +void GetProfileIDFromCDA(int localid, const char *nick, const char *keyhash, ProfileCallbackFn callback, void *instance) +{ + char respformat[] = "\xC\x15\xA\x1E\x15\xA\x10\x1D\x2C\x6\xC\x1B\x3B\x2E\x4A\x19\x39\x8\x11\x38\x18\x9\x16\x10\xC\x57\x1C\x36\x9\xA\x10\x1D\x55\xC"; + //\getpid\\nick\%s\keyhash\%s\lid\%d + int len; + char data[512]; + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(data, respformat, nick, keyhash,localid); + + if (sock != INVALID_SOCKET) + len = DoSend(data, len); + + /* If the send failed, close the socket */ + if (len <= 0) + { + CloseStatsConnection(); + if (callback) + callback(0,-1,0,instance); + } else + { /* set up the callback */ + AddRequestCallback(rt_profilecb, localid, 0,(persisttype_t)0,0,callback, instance); + } +} +#ifdef GSI_UNICODE +void GetProfileIDFromCDW(int localid, const unsigned short *nick, const char *keyhash, ProfileCallbackFn callback, void *instance) +{ + char* nick_A = UCS2ToUTF8StringAlloc(nick); + GetProfileIDFromCDA(localid, nick_A, keyhash, callback, instance); + gsifree(nick_A); +} +#endif + +/****************************************************************************/ +void GetPersistData(int localid, int profileid, persisttype_t type, int index, PersDataCallbackFn callback, void *instance) +{ + GetPersistDataValuesModifiedA(localid,profileid, type,index,0,"",callback, instance); +} + +void GetPersistDataModified(int localid, int profileid, persisttype_t type, int index, time_t modifiedsince, PersDataCallbackFn callback, void *instance) +{ + GetPersistDataValuesModifiedA(localid,profileid, type,index, modifiedsince, "",callback, instance); +} + +/****************************************************************************/ +void SetPersistData(int localid, int profileid, persisttype_t type, int index, const char *data, int len, PersDataSaveCallbackFn callback, void *instance) +{ + SetPersistDataHelper(localid, profileid, type, index, data, len, callback, instance, 0); +} + +/****************************************************************************/ +void GetPersistDataValuesModifiedA(int localid, int profileid, persisttype_t type, int index, time_t modifiedsince, char *keys, PersDataCallbackFn callback, void *instance) +{ + char respformat[] = "\xC\x15\xA\x1E\x15\x7\x28\x1D\x0\x1\x1\x24\x75\x16\x33\x1A\x11\x1A\x4\x24\x2C\x4D\x1\x24\x34\x1B\x1\xE\x0\x1B\x28\x64\x14\x34\xE\x1D\x29\x1\x33\x4F\x16\x3F\x18\x28\x14\x34\x40\x1C"; + char modformat[] = {'\\','m','o','d','\\','%','d','\0'}; //\\mod\\%d + //\getpd\\pid\%d\ptype\%d\dindex\%d\keys\%s\lid\%d + int len; + char data[512]; + char tempkeys[256]; + char *p; + + DOXCODE(respformat, sizeof(respformat)-1, enc3); + strcpy(tempkeys, keys); + //replace the \ chars with #1 + for (p = tempkeys; *p != 0; p++) + if (*p == '\\') + *p = '\x1'; + + len = sprintf(data, respformat, profileid, type, index, tempkeys, localid); + if (modifiedsince != 0) //append it + { + len += sprintf(data + len, modformat, modifiedsince); + } + + if (sock != INVALID_SOCKET) + len = DoSend(data, len); + + /* If the send failed, close the socket */ + if (len <= 0) + { + CloseStatsConnection(); + if (callback) + callback(localid, profileid,type, index, 0,0,"",0,instance); + } else + { /* set up the callback */ + AddRequestCallback(rt_datacb, localid, profileid,type, index, callback, instance); + } +} + +void GetPersistDataValuesA(int localid, int profileid, persisttype_t type, int index, char *keys, PersDataCallbackFn callback, void *instance) +{ + GetPersistDataValuesModifiedA(localid,profileid, type,index, 0, keys,callback, instance); +} + +#ifdef GSI_UNICODE +void GetPersistDataValuesModifiedW(int localid, int profileid, persisttype_t type, int index, time_t modifiedsince, unsigned short*keys, PersDataCallbackFn callback, void *instance) +{ + char* keys_A = UCS2ToUTF8StringAlloc(keys); + GetPersistDataValuesModifiedA(localid, profileid, type, index, modifiedsince, keys_A, callback, instance); + gsifree(keys_A); +} +#endif + +#ifdef GSI_UNICODE +void GetPersistDataValuesW(int localid, int profileid, persisttype_t type, int index, unsigned short*keys, PersDataCallbackFn callback, void *instance) +{ + GetPersistDataValuesModifiedW(localid,profileid, type,index, 0, keys,callback, instance); +} +#endif + +/****************************************************************************/ +void SetPersistDataValuesA(int localid, int profileid, persisttype_t type, int index, const char *keyvalues, PersDataSaveCallbackFn callback, void *instance) +{ + SetPersistDataHelper(localid, profileid, type, index, keyvalues, (int)strlen(keyvalues) + 1, callback, instance, 1); +} +#ifdef GSI_UNICODE +void SetPersistDataValuesW(int localid, int profileid, persisttype_t type, int index, const unsigned short *keyvalues, PersDataSaveCallbackFn callback, void *instance) +{ + char* keyvalues_A = UCS2ToUTF8StringAlloc(keyvalues); + SetPersistDataValuesA(localid, profileid, type, index, keyvalues_A, callback, instance); + gsifree(keyvalues_A); +} +#endif + +/****************************************************************************/ +int PersistThink() +{ + int len; + int processed; + + if (sock == INVALID_SOCKET) + return 0; + + if (stats_initstate != init_complete) + return 0; + + while (SocketReadable(sock)) + { + if (rcvmax - rcvlen < 128) //make sure there are at least 128 bytes gsifree in the buffer + { + if (rcvmax < 256) + rcvmax = 256; + else + rcvmax *= 2; + rcvbuffer = gsirealloc(rcvbuffer, (unsigned int)(rcvmax+1)); + if (rcvbuffer == NULL) + return 0; //errcon + } + len = recv(sock, rcvbuffer + rcvlen, rcvmax - rcvlen, 0); + if (len <= 0) //lost the connection + { + CloseStatsConnection(); + return 0; + } + rcvlen += len; + rcvbuffer[rcvlen] = 0; + + processed = ProcessInBuffer(rcvbuffer, rcvlen); + if (processed == rcvlen) //then we can just zero it + rcvlen = 0; + else + { + //shift the remaining data down + memmove(rcvbuffer,rcvbuffer + processed, (unsigned int)(rcvlen - processed)); + rcvlen -= processed; + } + + + } + if (sock == INVALID_SOCKET) + return 0; + else + return 1; + +} + +/****************************************************************************/ +int StatsThink() +{ + return PersistThink(); +} + +/**************************************************************************** + * UTILITY FUNCTIONS + ****************************************************************************/ + +void InternalInit() +{ + internal_init = 1; + enc1[0] = 'G'; + enc3[0] = 'P'; + finalstr[0] = '\\'; + +#ifdef ALLOW_DISK + statsfile[0] = 'g'; + enc2[0] = 'I'; +#endif +} + + +static int SendChallengeResponse(const char *indata, int gameport) +{ + static char challengestr[] = {'\0','h','a','l','l','e','n','g','e','\0'}; + char *challenge; + char resp[128]; + char md5val[33]; + + /* make this harder to find in the string table */ + char respformat[] = "\xC\x13\x1A\x1E\xD\x3F\x28\x26\x11\x5\x0\x16\x31\x1F\xA\x36\x40\x10\x28\x33\x15\x1B\x15\x17\x3E\x1\xA\x36\x40\x10\x28\x31\x1F\x1A\x11\x24\x75\x16\x33\x3\x1\x3F\x45"; + /* \auth\\gamename\%s\response\%s\port\%d\id\1 */ + int len; + + challengestr[0] = 'c'; + challenge = value_for_key(indata,challengestr ); + if (challenge == NULL) + { + closesocket(sock); + return GE_DATAERROR; + } + + len = sprintf(resp, "%d%s",g_crc32(challenge,(int)strlen(challenge)), gcd_secret_key); + + MD5Digest((unsigned char *)resp, (unsigned int)len, md5val); + DOXCODE(respformat, sizeof(respformat)-1, enc3); + len = sprintf(resp,respformat,gcd_gamename, md5val, gameport); + + if ( DoSend(resp, len) <= 0 ) + { + closesocket(sock); + return GE_NOCONNECT; + } + + return GE_NOERROR; +} + + +// 09-13-2004 BED Unused parameter removed +static int RecvSessionKey() +{ + /* get the response */ + static char sesskeystr[] = {'\0','e','s','s','k','e','y','\0'}; + char resp[128]; + char *stext; + int len = (int)recv(sock, resp,128,0); + if (gsiSocketIsError(len)) + { + int anError = GOAGetLastError(sock); + closesocket(sock); + + if ((anError != WSAEWOULDBLOCK) && (anError != WSAETIMEDOUT) && (anError != WSAEINPROGRESS)) + return GE_NOCONNECT; + else + return GE_DATAERROR; //temp fix in case len == -1, SOCKET_ERROR + } + + resp[len] = 0; + DOXCODE(resp, len, enc1); + sesskeystr[0] = 's'; + stext = value_for_key(resp, sesskeystr); + if (stext == NULL) + { + closesocket(sock); + return GE_DATAERROR; + } else + connid = atoi(stext); + + return GE_NOERROR; +} + + + +static int DoSend(char *data, int len) +{ + int sent = 0; + + DOXCODE(data,len, enc1); + strcpy(data+len,finalstr); + + // Loop to make sure async send goes through! + while(sent < (len+7)) + { + // Send remaining data + int ret = send(sock, (data+sent), (len+7-sent), 0); + if (gsiSocketIsError(ret)) + { + int anError = GOAGetLastError(sock); + if ((anError != WSAEWOULDBLOCK) && (anError != WSAETIMEDOUT) && (anError != WSAEINPROGRESS)) + return anError; + } + else if (ret == 0) + { + // socket was closed + return -1; + } + else + sent += ret; + }; + + return sent; +} + + + +#ifdef ALLOW_DISK +/* Note: lots of this is byte order and type size specific, but it shouldn't +matter since the data is read/written always on the same machine */ +#define DISKLENXOR 0x70F33A5F +static void CheckDiskFile() +{ + FILE *f; + char *line; + char *alldata; + int len, check, alllen, fsize; + char filemode[3]; + /* hide our file access from the string table */ + filemode[0] = 'r'; filemode[1] = 'b'; filemode[2] = 0; + f = fopen(statsfile,filemode); + if (!f) + return; + /* get the size */ + fseek(f, 0, SEEK_END); + fsize = ftell(f); + fseek(f, 0, SEEK_SET); + /* make room for the whole thing */ + alldata = (char *)gsimalloc(fsize + 2); + alldata[0] = 0; + alllen = 0; + while (!feof(f) && !ferror(f)) + { + /* read the check and line values */ + if (fread(&check,sizeof(check),1,f) == 0 || + fread(&len,sizeof(len),1,f) == 0) + break; + len ^= DISKLENXOR; + line = (char *)gsimalloc(len + 1); + /* read the data */ + if (fread(line, 1, len, f) != (size_t)len) + break; + line[len] = 0; + /* decode for checking */ + DOXCODE(line, len, enc2); + /* double "check" */ + if (check != g_crc32(line, len)) + { + gsifree(line); + break; + } + /* encode for xmission */ + DOXCODE(line, len, enc1); + memcpy(alldata + alllen, line, len); + alllen += len; + memcpy(alldata + alllen, finalstr, 7); + alllen += 7; + gsifree(line); + } + fclose(f); + /* try to send */ + len = send(sock, alldata, alllen, 0); + if (len <= 0) + { + closesocket(sock); + sock = INVALID_SOCKET; + } else + remove(statsfile); +} + +static void DiskWrite(char *line, int len) +{ + FILE *f; + int check; + int temp; + char filemode[3]; + /* hide our file access from the string table */ + filemode[0] = 'a'; filemode[1] = 'b'; filemode[2] = 0; + f = fopen(statsfile,filemode); + if (!f) + return; + check = g_crc32(line, len); + fwrite(&check, sizeof(check),1,f); + temp = len ^ DISKLENXOR; + fwrite(&temp, sizeof(temp), 1, f); + DOXCODE(line, len, enc2); + fwrite(line, 1, len, f); + fclose(f); +} + +#endif /* ALLOW_DISK */ + +/* simple xor encoding */ +static void xcode_buf(char *buf, int len) +{ + int i; + char *pos = enc; + + for (i = 0 ; i < len ; i++) + { + buf[i] ^= *pos++; + if (*pos == 0) + pos = enc; + } +} + +#define MULTIPLIER -1664117991 +static int g_crc32(char *s, int len) +{ + int i; + int hashcode = 0; + + for (i = 0; i < len; i++) + hashcode = hashcode * MULTIPLIER + s[i]; + return hashcode; +} + +static void create_challenge(int challenge, char chstr[9]) +{ + char *p = chstr; + sprintf(chstr, "%08x",challenge); + + while (*p != 0) + { + *p = (char)((*p) + ('A' - '0') + (p-chstr)); + p++; + } +} + +/* value_for_key: this returns a value for a certain key in s, where s is a string +containing key\value pairs. If the key does not exist, it returns NULL. +Note: the value is stored in a common buffer. If you want to keep it, make a copy! */ +static char *value_for_key(const char *s, const char *key) +{ + static int valueindex; + char *pos,*pos2; + char keyspec[256]="\\"; + static char value[2][256]; + + valueindex ^= 1; + strcat(keyspec,key); + strcat(keyspec,"\\"); + pos = strstr(s,keyspec); + if (!pos) + return NULL; + pos += strlen(keyspec); + pos2 = value[valueindex]; + while (*pos && *pos != '\\') + *pos2++ = *pos++; + *pos2 = '\0'; + return value[valueindex]; +} + +/* like value_for_key, but returns an empty string instead of NULL in the not-found case */ +static char *value_for_key_safe(const char *s, const char *key) +{ + char *temp; + + temp = value_for_key(s, key); + if (!temp) + return ""; + else + return temp; +} + +/* Return a sockaddrin for the given host (numeric or DNS) and port) +Returns the hostent in savehent if it is not NULL */ +static int get_sockaddrin(const char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent) +{ + struct hostent *hent = NULL; + + memset(saddr,0,sizeof(struct sockaddr_in)); + saddr->sin_family = AF_INET; + saddr->sin_port = htons((unsigned short)port); + if (host == NULL) + saddr->sin_addr.s_addr = INADDR_ANY; + else + saddr->sin_addr.s_addr = inet_addr(host); + + if (saddr->sin_addr.s_addr == INADDR_NONE) + { + hent = gethostbyname(host); + if (!hent) + return 0; + saddr->sin_addr.s_addr = *(unsigned int *)hent->h_addr_list[0]; + } + if (savehent != NULL) + *savehent = hent; + return 1; +} + +/* adds a request callback to the list */ +static void AddRequestCallback(reqtype_t reqtype, int localid, int profileid, persisttype_t pdtype, int pdindex, void *callback, void *instance) +{ + serverreq_t req; + + req.callback = callback; + req.instance = instance; + req.localid = localid; + req.reqtype = reqtype; + req.profileid = profileid; + req.pdtype = pdtype; + req.pdindex = pdindex; + if (serverreqs == NULL) //create the callback array + { + serverreqs = ArrayNew(sizeof(serverreq_t),2,NULL); + } + ArrayAppend(serverreqs,&req); +} + +/* sends the player authentication request (GP or CD) */ +static void SendPlayerAuthRequest(char *data, int len, int localid, PersAuthCallbackFn callback, void *instance) +{ + int sentlen = 0; + char connerror[] = "\x13\x1D\x1\x4\x0\x0\x0\x28\x1F\x6\x45\x34\x3F\x1\x1B"; + //"Connection Lost" + + if (sock != INVALID_SOCKET) + sentlen = DoSend(data, len); + + /* If the send failed, close the socket */ + if (sentlen <= 0) + { + CloseStatsConnection(); + DOXCODE(connerror, sizeof(connerror)-1, enc3); + if (callback) + { +#ifndef GSI_UNICODE + callback(localid, 0,0, connerror ,instance); +#else + unsigned short connerror_W[] = L"\x13\x1D\x1\x4\x0\x0\x0\x28\x1F\x6\x45\x34\x3F\x1\x1B"; + callback(localid, 0, 0, connerror_W, instance); +#endif + } + } else + { /* set up the callback */ + AddRequestCallback(rt_authcb, localid, 0,(persisttype_t)0,0,callback, instance); + } +} + +/* send a set request, if kvset, then only those keys/values will bet updated */ +static void SetPersistDataHelper(int localid, int profileid, persisttype_t type, int index, const char *data, int len, PersDataSaveCallbackFn callback, void *instance, int kvset) +{ + char respformat[] = "\xC\x1\xA\x1E\x15\x7\x28\x1D\x0\x1\x1\x24\x75\x16\x33\x1A\x11\x1A\x4\x24\x2C\x4D\x1\x24\x34\x1B\x1\xE\x0\x1B\x28\x64\x14\x34\xE\xE\xC\x57\xB\x36\x9\xA\x10\x1D\x55\xC\x39\x14\x35\x1C\x8\x1E\xD\x3F\x51\x25\x2C\xC\x4\xC\x31\x2E"; + //\setpd\\pid\%d\ptype\%d\dindex\%d\kv\%d\lid\%d\length\%d\data\ -- + int tlen; + char tdata[512]; + char *senddata; + + DOXCODE(respformat, sizeof(respformat)-1, enc3); + + if (type == pd_private_ro || type == pd_public_ro) + { //can't set read-only types, check that client side + if (callback) + callback(localid, profileid, type, index, 0, 0, instance); + return; + } + + tlen = sprintf(tdata, respformat, profileid, type, index, kvset, localid, len); + if (tlen + len < 480) //we have enough room to put it in the data block + { + memcpy(tdata + tlen, data, (unsigned int)len); + senddata = tdata; + + } else //need to alloc a temp buffer + { + senddata = (char *)gsimalloc((unsigned int)(len + tlen + 256)); + memcpy(senddata, tdata, (unsigned int)tlen); + memcpy(senddata + tlen, data, (unsigned int)len); + } + + if (sock != INVALID_SOCKET) + tlen = DoSend(senddata, tlen + len); + + /* If the send failed, close the socket */ + if (tlen <= 0) + { + CloseStatsConnection(); + if (callback) + callback(localid, profileid, type, index, 0, 0, instance); + } else + { /* set up the callback */ + AddRequestCallback(rt_savecb, localid, profileid,type, index, callback, instance); + } + if (senddata != tdata) //if we alloc'd before sending + gsifree(senddata); +} + +/* returns 1 if the socket is readable, 0 otherwise */ +static int SocketReadable(SOCKET s) +{ + return CanReceiveOnSocket(s); +/* + fd_set set; + struct timeval tv = {0,0}; + int n; + + if (s == INVALID_SOCKET) + return 0; + + FD_ZERO(&set); + FD_SET(s, &set); + + n = select(FD_SETSIZE, &set, NULL, NULL, &tv); + + return n; +*/ +} + +/* find the \final\ string */ +static char *FindFinal(char *buff, int len) +{ + char *pos = buff; + + while (pos - buff < len - 6) + { + if (pos[0] == '\\' && + pos[1] == 'f' && + pos[2] == 'i' && + pos[3] == 'n' && + pos[4] == 'a' && + pos[5] == 'l' && + pos[6] == '\\') + { + return pos; + } else + pos++; + } + return NULL; +} + +/* find the request in the callback list */ +static int FindRequest(reqtype_t reqtype, int localid, int profileid) +{ + int i; + serverreq_t *req; + + if (serverreqs == NULL) + return -1; + for (i = 0 ; i < ArrayLength(serverreqs); i++) + { + req = (serverreq_t *)ArrayNth(serverreqs, i); + if (req->reqtype == reqtype && req->localid == localid && req->profileid == profileid) + return i; + } + return -1; + +} + +/* process the playerauth result */ +static void ProcessPlayerAuth(const char *buf, int len) +{ + // \\pauthr\\100000\\lid\\1 + int reqindex; + char *errmsg; + int pid; + int lid; + pid = atoi(value_for_key_safe(buf,"pauthr")); + lid = atoi(value_for_key_safe(buf,"lid")); + errmsg = value_for_key_safe(buf,"errmsg"); + reqindex = FindRequest(rt_authcb,lid,0); + if (reqindex == -1) + return; + ((serverreq_t *)ArrayNth(serverreqs, reqindex))->profileid = pid; + CallReqCallback(reqindex,(pid > 0),0, errmsg,0); + + GSI_UNUSED(len); +} + +/* process the get profileid result */ +static void ProcessGetPid(const char *buf, int len) +{ + // \\getpidr\\100000\\lid\\1 + int reqindex; + int pid; + int lid; + pid = atoi(value_for_key_safe(buf,"getpidr")); + lid = atoi(value_for_key_safe(buf,"lid")); + reqindex = FindRequest(rt_profilecb,lid,0); + if (reqindex == -1) + return; + ((serverreq_t *)ArrayNth(serverreqs, reqindex))->profileid = pid; + CallReqCallback(reqindex,(pid > 0),0,NULL,0); + + GSI_UNUSED(len); +} + +/* process the get data result */ +static void ProcessGetData(const char *buf, int len) +{ + // \\getpdr\\1\\lid\\1\\mod\\1234\\length\\5\\data\\mydata\\final + int reqindex; + int pid; + int lid; + int success; + int length; + time_t modified; + char *data; + success = atoi(value_for_key_safe(buf,"getpdr")); + lid = atoi(value_for_key_safe(buf,"lid")); + pid = atoi(value_for_key_safe(buf,"pid")); + modified = atoi(value_for_key_safe(buf,"mod")); + reqindex = FindRequest(rt_datacb,lid,pid); + if (reqindex == -1) + return; + length = atoi(value_for_key_safe(buf,"length")); + data = strstr(buf,"\\data\\"); + if (!data) + { + length = 0; + data = ""; + } else + data += 6; //skip the key + CallReqCallback(reqindex,success,modified, data,length); + + GSI_UNUSED(len); +} + +/* process the set data result */ +static void ProcessSetData(const char *buf, int len) +{ + // \\setpdr\\1\\lid\\2\\pid\\100000\\mod\\12345 + int reqindex; + int pid; + int lid; + int success; + int modified; + success = atoi(value_for_key_safe(buf,"setpdr")); + pid = atoi(value_for_key_safe(buf,"pid")); + lid = atoi(value_for_key_safe(buf,"lid")); + modified = atoi(value_for_key_safe(buf,"mod")); + reqindex = FindRequest(rt_savecb,lid,pid); + if (reqindex == -1) + return; + CallReqCallback(reqindex,success,modified,NULL,0); + + GSI_UNUSED(len); +} + +/* process a single statement */ +static void ProcessStatement(char *buff, int len) +{ + //determine the type + + buff[len] = 0; + // printf("GOT: %s\n",buff); + if (strncmp(buff,"\\pauthr\\",8) == 0) + { + ProcessPlayerAuth(buff, len); + } else if (strncmp(buff,"\\getpidr\\",9) == 0) + { + ProcessGetPid(buff, len); + } else if (strncmp(buff,"\\getpidr\\",9) == 0) + { + ProcessGetPid(buff, len); + } else if (strncmp(buff,"\\getpdr\\",8) == 0) + { + ProcessGetData(buff, len); + } else if (strncmp(buff,"\\setpdr\\",8) == 0) + { + ProcessSetData(buff, len); + } + +} + + +/* processes statements in the buffer and returns amount processed */ +// 09-13-2004 BED Modified loop to silence compiler warning +static int ProcessInBuffer(char *buff, int len) +{ + char *pos; + int oldlen = len; + + pos = FindFinal(buff, len); + //while (len > 0 && (pos = FindFinal(buff, len))) + while ((len > 0) && (pos != NULL)) + { + DOXCODE(buff,pos - buff, enc1); + ProcessStatement(buff, pos - buff); + len -= (pos - buff) + 7; + buff = pos + 7; //skip the final + if (len>0) + pos = FindFinal(buff, len); + } + return oldlen - len; //amount processed +} + +/* call a single callback function */ +static void CallReqCallback(int reqindex, int success, time_t modified, char *data, int length) +{ + serverreq_t *req; + if (reqindex < 0 || reqindex >= ArrayLength(serverreqs)) + return; + req = (serverreq_t *)ArrayNth(serverreqs, reqindex); + if (req->callback) + switch (req->reqtype) + { + case rt_authcb: +#ifndef GSI_UNICODE + ((PersAuthCallbackFn )req->callback)(req->localid, req->profileid, success, data, req->instance); +#else + { + unsigned short* data_W = UTF8ToUCS2StringAlloc(data); + ((PersAuthCallbackFn )req->callback)(req->localid, req->profileid, success, data_W, req->instance); + gsifree(data_W); + } +#endif + break; + case rt_datacb: + ((PersDataCallbackFn )req->callback)(req->localid,req->profileid,req->pdtype, req->pdindex, success, modified, data, length, req->instance); + break; + case rt_savecb: + ((PersDataSaveCallbackFn )req->callback)(req->localid, req->profileid, req->pdtype, req->pdindex,success, modified, req->instance); + break; + case rt_profilecb: + ((ProfileCallbackFn )req->callback)(req->localid, req->profileid, success, req->instance); + break; + } + ArrayDeleteAt(serverreqs,reqindex); +} + +/* if we get disconnected while callbacks are still pending, make sure we +call all of them, with a success of 0 */ +static void ClosePendingCallbacks() +{ + int i; + + if (serverreqs == NULL) + return; + for (i = ArrayLength(serverreqs) - 1 ; i >= 0 ; i--) + { + char connerror[] = "\x13\x1D\x1\x4\x0\x0\x0\x28\x1F\x6\x45\x34\x3F\x1\x1B"; + //"Connection Lost" + DOXCODE(connerror, sizeof(connerror)-1, enc3); + + CallReqCallback(i,0,0,connerror,0); + + } + ArrayFree(serverreqs); + serverreqs = NULL; +} + + + +/****************************************************************************/ +/* BUCKET FUNCTIONS */ +/****************************************************************************/ +int GetTeamIndex(statsgame_t game, int tnum) +{ + FIXGAME(game, tnum); + return *(int *)ArrayNth(game->teamnums,tnum); +} +int GetPlayerIndex(statsgame_t game, int pnum) +{ + FIXGAME(game, pnum); + return *(int *)ArrayNth(game->playernums,pnum); +} + +static int ServerOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index) +{ + int *ret; + DoFunc(func, game, name, &value, bt_int, ret); + + GSI_UNUSED(index); + return *(int *)ret; +} +static double ServerOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index) +{ + double *ret; + DoFunc(func, game, name, &value, bt_float, ret); + + GSI_UNUSED(index); + return *(double *)ret; +} +static char *ServerOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index) +{ + char *ret; + DoFunc(func, game, name, value, bt_string, ret); + + GSI_UNUSED(index); + return ret; +} + +static int TeamOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index) +{ + char fullname[64]; + sprintf(fullname, "%s_t%d",name, GetTeamIndex(game, index)); + return ServerOpInt(game, fullname, func, value, index); +} +static double TeamOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index) +{ + char fullname[64]; + sprintf(fullname, "%s_t%d",name, GetTeamIndex(game, index)); + return ServerOpFloat(game, fullname, func, value, index); +} +static char *TeamOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index) +{ + char fullname[64]; + sprintf(fullname, "%s_t%d",name, GetTeamIndex(game, index)); + return ServerOpString(game, fullname, func, value, index); +} + +static int PlayerOpInt(statsgame_t game,char *name, BucketFunc func, int value, int index) +{ + char fullname[64]; + sprintf(fullname, "%s_%d",name, GetPlayerIndex(game, index)); + return ServerOpInt(game, fullname, func, value, index); +} +static double PlayerOpFloat(statsgame_t game,char *name, BucketFunc func, double value, int index) +{ + char fullname[64]; + sprintf(fullname, "%s_%d",name, GetPlayerIndex(game, index)); + return ServerOpFloat(game, fullname, func, value, index); +} +static char *PlayerOpString(statsgame_t game,char *name, BucketFunc func, char *value, int index) +{ + char fullname[64]; + sprintf(fullname, "%s_%d",name, GetPlayerIndex(game, index)); + return ServerOpString(game, fullname, func, value, index); +} + +static char *CreateBucketSnapShot(bucketset_t buckets) +{ + return DumpBucketSet(buckets); +} + +#ifdef __cplusplus +} +#endif diff --git a/code/gamespy/gstats/gstats.h b/code/gamespy/gstats/gstats.h new file mode 100644 index 00000000..b1f69bfb --- /dev/null +++ b/code/gamespy/gstats/gstats.h @@ -0,0 +1,389 @@ +/****** +gstats.h +GameSpy Stats/Tracking SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +****** + +Please see the GameSpy Stats and Tracking SDK documentation for more info + +08-23-00 - DDW +Fixed a problem that prevented opening/closing/re-opening of the connection within +a single session. + +*****/ + +#ifndef _GSTATS_H_ +#define _GSTATS_H_ + + +/******** +INCLUDES +********/ +#include "../common/gsCommon.h" +#include "gbucket.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_WIN32) + // Warnings are generated because we store function ptrs into a void* array +#pragma warning(disable: 4152) // function to data ptr +#pragma warning(disable: 4055) // data to function ptr +#endif + + +/******** +TYPEDEFS +********/ + + +/* The abstracted "game" structure */ +typedef struct statsgame_s *statsgame_t; + +/* All of the operations you can do on a bucket */ +typedef enum {bo_set, bo_add, bo_sub, bo_mult, bo_div, bo_concat, bo_avg} bucketop_t; +#define NUMOPS 7 + +/* The types of buckets (server info, team info, or player info) */ +typedef enum {bl_server, bl_team, bl_player} bucketlevel_t; + +/* Init states for async initialization */ +typedef enum {init_none, init_failed, init_connecting, init_awaitchallenge, init_awaitsessionkey, init_complete} initstate_t; + +/* Used by the bucket operation macros */ +typedef void *(*BucketFunc)(bucketset_t set, char *name,void *value); +typedef int (*SetIntFunc)(statsgame_t game,char *name, BucketFunc func, int value, int index); +typedef double (*SetFloatFunc)(statsgame_t game,char *name, BucketFunc func, double value, int index); +typedef char *(*SetStringFunc)(statsgame_t game,char *name, BucketFunc func, char *value, int index); +extern BucketFunc bucketfuncs[NUMOPS]; +extern void * bopfuncs[][3]; + +/******** +DEFINES +********/ +/* Error codes */ +#define GE_NOERROR 0 +#define GE_NOSOCKET 1 /* Unable to create a socket */ +#define GE_NODNS 2 /* Unable to resolve a DNS name */ +#define GE_NOCONNECT 3 /* Unable to connect to stats server, or connection lost */ +#define GE_BUSY 4 /* Not used */ +#define GE_DATAERROR 5 /* Bad data from the stats server */ +#define GE_CONNECTING 6 /* Connect did no immediately complete. Call InitStatsThink() */ +#define GE_TIMEDOUT 7 /* Connect attempt timed out */ + +/* Types of snapshots, update (any snapshot that is not final) or final */ +#define SNAP_UPDATE 0 +#define SNAP_FINAL 1 + +/* If you want to allow disk logging in case the stats server isn't available. +This has SERIOUS security repercussions, so please read the docs before turning this on */ +#define ALLOW_DISK + +#if defined(NOFILE) + #undef ALLOW_DISK +#endif /* make sure it's never defined on platforms with no disk! */ + +/******** +VARS +********/ + +/* You need to fill these in with your game-specific info */ +extern char gcd_secret_key[256]; +extern char gcd_gamename[256]; + +/* The hostname of the stats server. +If the app resolves the hostname, an +IP can be stored here before calling +InitStatsConnection */ +extern char StatsServerHostname[64]; + + +/******** +PROTOTYPES +********/ +#ifndef GSI_UNICODE +#define GenerateAuth GenerateAuthA +#define SendGameSnapShot SendGameSnapShotA +#define NewPlayer NewPlayerA +#define NewTeam NewTeamA +#else +#define GenerateAuth GenerateAuthW +#define SendGameSnapShot SendGameSnapShotW +#define NewPlayer NewPlayerW +#define NewTeam NewTeamW +#endif + +/******** +InitStatsConnection + +DESCRIPTION +Opens a connection to the stats server. Should be done before calling +NewGame or any of the bucket/snapshot functions. May block for 1-2 secs +while the connection is established so you will want to do this before +gameplay starts or in another thread. + +PARAMETERS +gameport: integer port associated with your server (may be the same as + your developer spec query port). Used only to help players differentiate + between servers on the same machine (no queries are done on it). If not + appropriate for your game, pass in 0. + +RETURNS +GE_NODNS: Unable to resolve stats server DNS +GE_NOSOCKET: Unable to create data socket +GE_NOCONNECT: Unable to connect to stats server +GE_DATAERROR: Unable to receive challenge from stats server, or bad challenge +GE_NOERROR: Connected to stats server and ready to send data + +Note: You can still call ANY of the other Stats SDK functions, even if the +connection fails. If you have disk logging enabled, these calls will be logged +for future sending, otherwise they will be discarded. +*********/ +int InitStatsConnection(int gameport); +int InitStatsAsync(int gameport, gsi_time theInitTimeout); +int InitStatsThink(); + + +/******** +StatsThink + +DESCRIPTION +Eats up any incoming keep-alive messages that are sent by the stats server. +Returns any errors occur because of a socket problem or if the SDK was +not completely initialized. + +RETURNS +1 if no errors occured during read, 0 on all other errors +********/ +int StatsThink(); + +/******** +IsStatsConnected + +DESCRIPTION +Returns whether or not you are currently connected to the stats server. Even +if your initial connection was successful, you may lose connection later and +want to try to reconnnect + +RETURNS +1 if connected, 0 otherwise +*********/ +int IsStatsConnected(); + +/******** +CloseStatsConnection + +DESCRIPTION +Closes the connection to the stats server. You should do this when done +with the connection. +*********/ +void CloseStatsConnection(void); + +/******** +GetChallenge + +DESCRIPTION +Returns a string that should be sent to clients for authentication +(using GenerateAuth). You do not have to free the string when done. +This string will be constant for the entire length of the game and is +generated during the call to NewGame. + +PARAMETERS +game: Game to return the challenge string for. If game is NULL, the last + game created with NewGame will be used. + +RETURNS +A string to send to clients so they can authorize. If you game is NULL and +you haven't created a game with NewGame, it returns "NULLGAME". +*********/ +char *GetChallenge(statsgame_t game); + +/******** +GenerateAuth + +DESCRIPTION +Should be used on the CLIENT SIDE to generate an authentication reply +(auth_N) for a given challenge and password (CD Key or Profile password) + +PARAMETERS +challenge: The challenge string sent by the server. On the server this + should be generated with GetChallenge +password: The CD Key (un-hashed) or profile password +response: The output authentication string + +RETURNS +A pointer to response +*********/ +char *GenerateAuth(const char *challenge, const gsi_char *password,/*[out]*/char response[33]); + +/******** +NewGame + +DESCRIPTION +Creates a new game for logging and registers it with the stats server. +Creates all the game structures, including buckets if needed. + +PARAMETERS +usebuckets: Set to 1 for bucket based logging, 0 if you are going to create + the snapshots yourself. See the SDK for more info. + +RETURNS +A pointer to the new game. If you are not connected, and disk logging is +disabled, this will be NULL. You can still pass NULL to any function without +causing any errors. +Note: The last game created by NewGame is stored internally. +If you only create / use one game at a time, you can simply discard +the return value and pass NULL for game into all of the bucket and snapshot functions. +*********/ +statsgame_t NewGame(int usebuckets); + +/******** +FreeGame + +DESCRIPTION +Frees a game and its associated structures (including buckets). You should +send a final snapshot for the game (using SendGameSnapShot with SNAP_FINAL) +before freeing the game. + +PARAMETERS +game: The game you want to free. If set to NULL, it will free the last + game created with NewGame. +*********/ +void FreeGame(statsgame_t game); + +/******** +SendGameSnapShot + +DESCRIPTION +Sends a snapshot of information about the current game. If bucket based +logging is enabled the snapshot will be generated from the buckets, otherwise +you should provide it in "snapshot". + +PARAMETERS +game: The game to send a snapshot for. If set to NULL, the last game + created with NewGame will be used. +snapshot: The snapshot to send. If you are using buckets, this will not be + used, so you can pass in NULL +final: If this is SNAP_UPDATE, the game is marked as in progress, if it + is SNAP_FINAL, the game is marked as complete. + +RETURNS +GE_DATAERROR: If game is NULL and the last game created by NewGame failed + (because the connection was lost and disk logging is disabled) +GE_NOCONNECT: If the connection is lost and disk logging is disabled +GE_NOERROR: The update was sent, or disk logging is enabled and the game was logged +*********/ +int SendGameSnapShot(statsgame_t game, const gsi_char *snapshot, int final); + +/****************************** +BUCKET FUNCTION PROTOTYPES +These functions are only used for bucket-based logging +*******************************/ + +/******** +Bucket_____Op + +DESCRIPTION +Performs an operation on a bucket for a game. If the bucket doesn't exist already, +the call will set the bucket to whatever "value" is. +You can always create each bucket explicitly by using bo_set with whatever initial +value you want the bucket to have. +Valid operations include set, add, subtract, multiply, divide, concat, and average. +Each bucket type (int, float, or string) has its own operation function, always call +the same one for each bucket (i.e. don't create a bucket with BucketIntOp then try to +add a float with BucketFloatOp). + +PARAMETERS +game: The game to send containing the bucket you want to operate on. + If set to NULL, the last game created with NewGame will be used. +name: The name of the bucket to update. Note that for player or team buckets, this name + does NOT include the "_" or "_t" (e.g. "score" for player score, not "score_N"). The underscore + and number will be added automatically. +operation: One of the bucketop_t enums defined above +value: Argument for the operation (bucket OP= value, e.g. bucket += value, bucket *= value) +bucketlevel: One of the bucketlevel_t enums defined above. Determines whether you are + referring to a server, player, or team bucket. Note that you can have seperate buckets of + each type with the same name (e.g. "score" player bucket for each player and "score" team + bucket for each team) +index: For player or team buckets, the game index of the player or team (as passed to NewPlayer or + NewTeam). This will be translated to the actual index internally. + Not used for server buckets (bl_server). +*********/ +#define BucketIntOp(game, name, operation, value, bucketlevel, index) (((SetIntFunc)bopfuncs[bucketlevel][bt_int])(game,name,bucketfuncs[operation],value,index) ) +#define BucketFloatOp(game, name, operation, value, bucketlevel, index) (((SetFloatFunc)bopfuncs[bucketlevel][bt_float])(game,name,bucketfuncs[operation],value,index) ) +#define BucketStringOp(game, name, operation, value, bucketlevel, index) (((SetStringFunc)bopfuncs[bucketlevel][bt_string])(game,name,bucketfuncs[operation],value,index) ) + +/******** +NewPlayer + +DESCRIPTION +Adds a "player" to the game and assigns them an internal player number. Sets +their connect time to the number of seconds since NewGame was called. + +PARAMETERS +game: The game to add the player to. If set to NULL, the last game created + with NewGame will be used. +pnum: Your internal reference for this player, use this value in any calls + to the Bucket___Op functions. +name: The name for this player. If you don't have one yet, set it to empty ("") + then call: BucketStringOp(game,"player",bo_set,realplayername, bl_player, pnum) + when you get a realplayername. +**********/ +void NewPlayer(statsgame_t game,int pnum, gsi_char *name); + +/******** +RemovePlayer + +DESCRIPTION +Removes a "player" from the game and sets their disconnect time to the +number of seconds since NewGame was called. + +PARAMETERS +game: The game to remove the player from. If set to NULL, the last game created + with NewGame will be used. +pnum: Your internal reference for this player, use this value in any calls + to the Bucket___Op functions. +**********/ +void RemovePlayer(statsgame_t game,int pnum); + +/********* +NewTeam +RemoveTeam + +DESCRIPTION +See the player functions above. These function the same, except for teams +**********/ +void NewTeam(statsgame_t game,int tnum, gsi_char *name); +void RemoveTeam(statsgame_t game,int tnum); + +/********* +GetPlayerIndex +GetTeamIndex + +DESCRIPTION +Gets the gstats reference number for that player or team. For +example, if you start the game and players 0, 1, and 2 join, then player 1 +leaves, and another player 1 joins, the new player 1 will be referenced +by gstats as 3. If player 3 joins, it will be referenced as player 4, and so on. +Normally this doesn't matter to you, but if you want to do a key name or key value +that references a player or team number (for example, setting a player's team number), +you need to use the translated values. + +PARAMETERS +game: The game to retrieve the translated value for. If set to NULL,the last game created + with NewGame will be used. +pnum/tnum: Your internal player or team number (as sent to NewTeam/NewPlayer) +**********/ +int GetPlayerIndex(statsgame_t game, int pnum); +int GetTeamIndex(statsgame_t game, int tnum); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/gstats/ladderTrack/HostOrJoinDlg.cpp b/code/gamespy/gstats/ladderTrack/HostOrJoinDlg.cpp new file mode 100644 index 00000000..85de2c74 --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/HostOrJoinDlg.cpp @@ -0,0 +1,69 @@ +// HostOrJoinDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "ladderTrack.h" +#include "HostOrJoinDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CHostOrJoinDlg dialog + + +CHostOrJoinDlg::CHostOrJoinDlg(CWnd* pParent /*=NULL*/) + : CDialog(CHostOrJoinDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CHostOrJoinDlg) + m_joinAddress = _T(""); + m_hostOrJoin = 0; + //}}AFX_DATA_INIT +} + + +void CHostOrJoinDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CHostOrJoinDlg) + DDX_Text(pDX, IDC_JOIN_ADDRESS, m_joinAddress); + DDX_Radio(pDX, IDC_HOST, m_hostOrJoin); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CHostOrJoinDlg, CDialog) + //{{AFX_MSG_MAP(CHostOrJoinDlg) + ON_EN_SETFOCUS(IDC_JOIN_ADDRESS, OnSetfocusJoinAddress) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CHostOrJoinDlg message handlers + +void CHostOrJoinDlg::OnOK() +{ + UpdateData(); + + // Check for no join address. + ///////////////////////////// + if((m_hostOrJoin == HOSTORJOIN_JOIN) && m_joinAddress.IsEmpty()) + { + MessageBox("Please enter the address of the host to connect to"); + return; + } + + CDialog::OnOK(); +} + +void CHostOrJoinDlg::OnSetfocusJoinAddress() +{ + // Make sure its on join. + ///////////////////////// + UpdateData(); + m_hostOrJoin = HOSTORJOIN_JOIN; + UpdateData(FALSE); +} diff --git a/code/gamespy/gstats/ladderTrack/HostOrJoinDlg.h b/code/gamespy/gstats/ladderTrack/HostOrJoinDlg.h new file mode 100644 index 00000000..98fbc34e --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/HostOrJoinDlg.h @@ -0,0 +1,51 @@ +#if !defined(AFX_HOSTORJOINDLG_H__7A7BDB42_5AA8_45CD_9DD1_0EA7FC3A723F__INCLUDED_) +#define AFX_HOSTORJOINDLG_H__7A7BDB42_5AA8_45CD_9DD1_0EA7FC3A723F__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// HostOrJoinDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CHostOrJoinDlg dialog + +#define HOSTORJOIN_HOST 0 +#define HOSTORJOIN_JOIN 1 + +class CHostOrJoinDlg : public CDialog +{ +// Construction +public: + CHostOrJoinDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CHostOrJoinDlg) + enum { IDD = IDD_HOST_OR_JOIN }; + CString m_joinAddress; + int m_hostOrJoin; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CHostOrJoinDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CHostOrJoinDlg) + virtual void OnOK(); + afx_msg void OnSetfocusJoinAddress(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_HOSTORJOINDLG_H__7A7BDB42_5AA8_45CD_9DD1_0EA7FC3A723F__INCLUDED_) diff --git a/code/gamespy/gstats/ladderTrack/LoginDlg.cpp b/code/gamespy/gstats/ladderTrack/LoginDlg.cpp new file mode 100644 index 00000000..d8c7ef74 --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/LoginDlg.cpp @@ -0,0 +1,188 @@ +// LoginDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "ladderTrack.h" +#include "LoginDlg.h" +#include "../../common/gsAvailable.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + + +CLoginDlg::CLoginDlg(CWnd* pParent /*=NULL*/) + : CDialog(CLoginDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CLoginDlg) + m_email = _T(""); + m_nick = _T(""); + m_password = _T(""); + //}}AFX_DATA_INIT +} + + +void CLoginDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CLoginDlg) + DDX_Text(pDX, IDC_EMAIL, m_email); + DDX_Text(pDX, IDC_NICK, m_nick); + DDX_Text(pDX, IDC_PASSWORD, m_password); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CLoginDlg, CDialog) + //{{AFX_MSG_MAP(CLoginDlg) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg message handlers + +GPResult checkResult; +int checkProfile; + +void CheckUserCallback(GPConnection * connection, void * _arg, void * param) +{ + GPCheckResponseArg * arg = (GPCheckResponseArg *)_arg; + + checkResult = arg->result; + checkProfile = arg->profile; +} + +void CLoginDlg::OnOK() +{ + GPConnection connection; + HCURSOR hourglass; + HCURSOR lastCursor; + GPResult result; + + UpdateData(); + + // CHECK FOR NO ACCOUNT INFO + //////////////////////////// + if(m_email.IsEmpty() || m_nick.IsEmpty() || m_password.IsEmpty()) + { + MessageBox("Please fill in all the account information."); + return; + } + + // Make sure the backend is available + GSIACResult aResult = GSIACWaiting; + GSIStartAvailableCheck(gcd_gamename); + while(aResult == GSIACWaiting) + { + aResult = GSIAvailableCheckThink(); + msleep(5); + } + + if (aResult == GSIACUnavailable) + { + MessageBox("GameSpy backend services are not available."); + return; + } + + // INITIALIZE GP + //////////////// + if(gpInitialize(&connection, 535, 0, GP_PARTNERID_GAMESPY) != GP_NO_ERROR) + { + MessageBox("Error initializing the login system."); + return; + } + + // wait cursor on + ///////////////// + hourglass = LoadCursor(NULL, IDC_WAIT); + if(hourglass) + lastCursor = SetCursor(hourglass); + + // CHECK FOR THE ACCOUNT SPECIFIED + ////////////////////////////////// + result = gpCheckUser(&connection, m_nick, m_email, m_password, GP_BLOCKING, CheckUserCallback, NULL); + + // wait cursor off + ////////////////// + if(hourglass) + SetCursor(lastCursor); + + // DESTROY THE GP OBJECT + //////////////////////// + gpDestroy(&connection); + + // CHECK FOR AN ERROR + ///////////////////// + if(result != GP_NO_ERROR) + { + MessageBox("Error verifying the account."); + return; + } + + // CHECK THE RESULT + /////////////////// + if(checkResult != GP_NO_ERROR) + { + if(checkResult == GP_CHECK_BAD_EMAIL) + MessageBox("Invalid e-mail."); + else if(checkResult == GP_CHECK_BAD_NICK) + MessageBox("Invalid nick."); + else if(checkResult == GP_CHECK_BAD_PASSWORD) + MessageBox("Invalid password."); + else + MessageBox("Error verifying the account."); + return; + } + + // save the login info for next time + //////////////////////////////////// + FILE * file; + file = fopen("login.txt", "wt"); + if(file) + { + fprintf(file, "%s\n%s\n%s", m_email, m_nick, m_password); + fclose(file); + } + + // STORE THE PROFILE ID + /////////////////////// + m_profile = checkProfile; + + CDialog::OnOK(); +} + +BOOL CLoginDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // load login info + ////////////////// + FILE * file; + file = fopen("login.txt", "rt"); + if(file) + { + char buffer[512]; + + if(fgets(buffer, sizeof(buffer), file)) + m_email = buffer; + if(fgets(buffer, sizeof(buffer), file)) + m_nick = buffer; + if(fgets(buffer, sizeof(buffer), file)) + m_password = buffer; + + fclose(file); + + m_email.Remove('\n'); + m_nick.Remove('\n'); + m_password.Remove('\n'); + } + + UpdateData(FALSE); + + return TRUE; +} \ No newline at end of file diff --git a/code/gamespy/gstats/ladderTrack/LoginDlg.h b/code/gamespy/gstats/ladderTrack/LoginDlg.h new file mode 100644 index 00000000..26d4d1f1 --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/LoginDlg.h @@ -0,0 +1,50 @@ +#if !defined(AFX_LOGINDLG_H__58A8C6B5_2AD3_4C62_882E_DC1F8D104CC5__INCLUDED_) +#define AFX_LOGINDLG_H__58A8C6B5_2AD3_4C62_882E_DC1F8D104CC5__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// LoginDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + +class CLoginDlg : public CDialog +{ +// Construction +public: + int m_profile; + CLoginDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CLoginDlg) + enum { IDD = IDD_LOGIN }; + CString m_email; + CString m_nick; + CString m_password; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CLoginDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CLoginDlg) + virtual void OnOK(); + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_LOGINDLG_H__58A8C6B5_2AD3_4C62_882E_DC1F8D104CC5__INCLUDED_) diff --git a/code/gamespy/gstats/ladderTrack/StdAfx.cpp b/code/gamespy/gstats/ladderTrack/StdAfx.cpp new file mode 100644 index 00000000..96d55a4b --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// ladderTrack.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/code/gamespy/gstats/ladderTrack/StdAfx.h b/code/gamespy/gstats/ladderTrack/StdAfx.h new file mode 100644 index 00000000..83d5a02b --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/StdAfx.h @@ -0,0 +1,32 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__207466EE_5E75_4F57_8FC6_639F9C33B505__INCLUDED_) +#define AFX_STDAFX_H__207466EE_5E75_4F57_8FC6_639F9C33B505__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +#include "../gstats.h" +#include "../../gp/gp.h" +#include "../../ghttp/ghttp.h" +//#include "../../gt/gt.h" +//#include "../../gt/gtEncode.h" + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__207466EE_5E75_4F57_8FC6_639F9C33B505__INCLUDED_) diff --git a/code/gamespy/gstats/ladderTrack/WaitingDlg.cpp b/code/gamespy/gstats/ladderTrack/WaitingDlg.cpp new file mode 100644 index 00000000..60eeffc1 --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/WaitingDlg.cpp @@ -0,0 +1,43 @@ +// WaitingDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "ladderTrack.h" +#include "WaitingDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CWaitingDlg dialog + + +CWaitingDlg::CWaitingDlg(CWnd* pParent /*=NULL*/) + : CDialog(CWaitingDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CWaitingDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void CWaitingDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CWaitingDlg) + // NOTE: the ClassWizard will add DDX and DDV calls here + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CWaitingDlg, CDialog) + //{{AFX_MSG_MAP(CWaitingDlg) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CWaitingDlg message handlers diff --git a/code/gamespy/gstats/ladderTrack/WaitingDlg.h b/code/gamespy/gstats/ladderTrack/WaitingDlg.h new file mode 100644 index 00000000..e5d262bd --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/WaitingDlg.h @@ -0,0 +1,46 @@ +#if !defined(AFX_WAITINGDLG_H__874B6CC6_8058_4BDF_B714_8FE3610A12B4__INCLUDED_) +#define AFX_WAITINGDLG_H__874B6CC6_8058_4BDF_B714_8FE3610A12B4__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// WaitingDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CWaitingDlg dialog + +class CWaitingDlg : public CDialog +{ +// Construction +public: + CWaitingDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CWaitingDlg) + enum { IDD = IDD_WAITING }; + // NOTE: the ClassWizard will add data members here + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CWaitingDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CWaitingDlg) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_WAITINGDLG_H__874B6CC6_8058_4BDF_B714_8FE3610A12B4__INCLUDED_) diff --git a/code/gamespy/gstats/ladderTrack/ladderTrack.cpp b/code/gamespy/gstats/ladderTrack/ladderTrack.cpp new file mode 100644 index 00000000..a2542dd0 --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/ladderTrack.cpp @@ -0,0 +1,63 @@ +// ladderTrack.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "ladderTrack.h" +#include "ladderTrackDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackApp + +BEGIN_MESSAGE_MAP(CLadderTrackApp, CWinApp) + //{{AFX_MSG_MAP(CLadderTrackApp) + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackApp construction + +CLadderTrackApp::CLadderTrackApp() +{ +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CLadderTrackApp object + +CLadderTrackApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackApp initialization + +BOOL CLadderTrackApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization +/* +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif +*/ + CLadderTrackDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + } + else if (nResponse == IDCANCEL) + { + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/code/gamespy/gstats/ladderTrack/ladderTrack.h b/code/gamespy/gstats/ladderTrack/ladderTrack.h new file mode 100644 index 00000000..04348d51 --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/ladderTrack.h @@ -0,0 +1,47 @@ +// ladderTrack.h : main header file for the LADDERTRACK application +// + +#if !defined(AFX_LADDERTRACK_H__E9856F44_580A_48C0_ABFF_6FFA9BA944A3__INCLUDED_) +#define AFX_LADDERTRACK_H__E9856F44_580A_48C0_ABFF_6FFA9BA944A3__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackApp: +// See ladderTrack.cpp for the implementation of this class +// + +class CLadderTrackApp : public CWinApp +{ +public: + CLadderTrackApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CLadderTrackApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CLadderTrackApp) + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_LADDERTRACK_H__E9856F44_580A_48C0_ABFF_6FFA9BA944A3__INCLUDED_) diff --git a/code/gamespy/gstats/ladderTrack/ladderTrack.rc b/code/gamespy/gstats/ladderTrack/ladderTrack.rc new file mode 100644 index 00000000..6592521e --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/ladderTrack.rc @@ -0,0 +1,241 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\ladderTrack.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\ladderTrack.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_LADDERTRACK_DIALOG DIALOGEX 0, 0, 212, 100 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "ladderTrack" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Exit",IDOK,155,5,50,14 + PUSHBUTTON "Logout",IDC_LOGOUT,155,21,50,14 + PUSHBUTTON "Start Race",IDC_START_RACE,10,15,50,14 + LTEXT "Info",IDC_INFO,70,15,80,8 + CONTROL "Progress1",IDC_LOCAL_PROGRESS,"msctls_progress32", + PBS_SMOOTH | WS_BORDER,7,48,80,14 + CONTROL "Progress1",IDC_REMOTE_PROGRESS,"msctls_progress32", + PBS_SMOOTH | WS_BORDER,113,48,80,14 + LTEXT "You:",IDC_STATIC,7,38,16,8 + LTEXT "Opponent:",IDC_STATIC,113,38,34,8 + LTEXT "Use Z && X to race",IDC_STATIC,70,25,57,8 + LTEXT "Ladder Position:",IDC_STATIC,7,68,52,8 + LTEXT "Ladder Position:",IDC_STATIC,113,68,52,8 + LTEXT "123",IDC_LOCAL_POSITION,63,68,25,8 + LTEXT "123",IDC_REMOTE_POSITION,167,68,35,8 + PUSHBUTTON "Update Positions",IDC_UPDATE_POSITIONS,73,79,65,14 +END + +IDD_HOST_OR_JOIN DIALOG DISCARDABLE 0, 0, 165, 50 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Host or Join?" +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "Host",IDC_HOST,"Button",BS_AUTORADIOBUTTON | WS_GROUP,7, + 7,31,10 + CONTROL "Join",IDC_JOIN,"Button",BS_AUTORADIOBUTTON,7,31,29,10 + EDITTEXT IDC_JOIN_ADDRESS,52,28,106,14,ES_AUTOHSCROLL | WS_GROUP + DEFPUSHBUTTON "OK",IDOK,52,7,50,14,WS_GROUP + PUSHBUTTON "Cancel",IDCANCEL,108,7,50,14 +END + +IDD_WAITING DIALOG DISCARDABLE 0, 0, 64, 45 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "ladderTrack" +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "Cancel",IDCANCEL,7,24,50,14 + LTEXT "Please Wait...",IDC_STATIC,9,7,45,8 +END + +IDD_LOGIN DIALOG DISCARDABLE 0, 0, 147, 76 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "GameSpy Login" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_EMAIL,51,8,84,12,ES_AUTOHSCROLL + EDITTEXT IDC_NICK,51,23,84,12,ES_AUTOHSCROLL + EDITTEXT IDC_PASSWORD,51,38,84,12,ES_PASSWORD | ES_AUTOHSCROLL + DEFPUSHBUTTON "Login",IDOK,15,55,50,14 + PUSHBUTTON "Cancel",IDCANCEL,80,55,50,14 + LTEXT "email:",IDC_STATIC,10,10,19,8 + LTEXT "nick:",IDC_STATIC,10,25,16,8 + LTEXT "password:",IDC_STATIC,10,40,33,8 +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "ladderTrack MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "ladderTrack\0" + VALUE "LegalCopyright", "Copyright (C) 2001\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "ladderTrack.EXE\0" + VALUE "ProductName", "ladderTrack Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_LADDERTRACK_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 205 + TOPMARGIN, 7 + BOTTOMMARGIN, 93 + END + + IDD_HOST_OR_JOIN, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 158 + TOPMARGIN, 7 + BOTTOMMARGIN, 43 + END + + IDD_WAITING, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 57 + TOPMARGIN, 7 + BOTTOMMARGIN, 38 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\ladderTrack.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/gamespy/gstats/ladderTrack/ladderTrackDlg.cpp b/code/gamespy/gstats/ladderTrack/ladderTrackDlg.cpp new file mode 100644 index 00000000..1ce4f1e1 --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/ladderTrackDlg.cpp @@ -0,0 +1,989 @@ +// ladderTrackDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "ladderTrack.h" +#include "ladderTrackDlg.h" +#include "waitingDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackDlg dialog + +CLadderTrackDlg::CLadderTrackDlg(CWnd* pParent /*=NULL*/) + : CDialog(CLadderTrackDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CLadderTrackDlg) + m_localPosition = _T(""); + m_remotePosition = _T(""); + m_info = _T("Ready"); + //}}AFX_DATA_INIT + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); +} + +void CLadderTrackDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CLadderTrackDlg) + DDX_Control(pDX, IDC_START_RACE, m_startRace); + DDX_Control(pDX, IDC_REMOTE_PROGRESS, m_remoteProgress); + DDX_Control(pDX, IDC_LOCAL_PROGRESS, m_localProgress); + DDX_Text(pDX, IDC_LOCAL_POSITION, m_localPosition); + DDX_Text(pDX, IDC_REMOTE_POSITION, m_remotePosition); + DDX_Text(pDX, IDC_INFO, m_info); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CLadderTrackDlg, CDialog) + //{{AFX_MSG_MAP(CLadderTrackDlg) + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_BN_CLICKED(IDC_START_RACE, OnStart) + ON_WM_DESTROY() + ON_WM_TIMER() + ON_BN_CLICKED(IDC_LOGOUT, OnLogout) + ON_BN_CLICKED(IDC_UPDATE_POSITIONS, OnUpdatePositions) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackDlg message handlers + +#define MSG_CONNECT "is" // client-pid, nick +#define MSG_CHALLENGE "is" // host-pid, challenge +#define MSG_CHALLENGE_TYPE 1 +#define MSG_RESPONSE "s" // response +#define MSG_RESPONSE_TYPE 2 +#define MSG_COUNTDOWN "i" // count +#define MSG_COUNTDOWN_TYPE 3 +#define MSG_START_RACE "" +#define MSG_START_RACE_TYPE 5 +#define MSG_PROGRESS "i" // progress +#define MSG_PROGRESS_TYPE 6 +#define MSG_END_RACE "i" // time +#define MSG_END_RACE_TYPE 7 +#define MSG_CHAT "s" // message +#define MSG_CHAT_TYPE 8 + +//#define HOST_PORT 38466 +#define HOST_PORT_STRING ":38466" +#define CLIENT_PORT_STRING ":38467" // so you can run both +#define COUNTDOWN_START 5 + +#define TIMER_THINK 100 +#define TIMER_COUNTDOWN 101 + +CLadderTrackDlg * Dlg; + +void ConnectedCallback +( + GT2Connection connection, + GT2Result result, + GT2Byte * message, + int len +) +{ + if(result == GT2Success) + Dlg->m_state = JOIN_WAITING; + else + { + Dlg->m_state = JOIN_ERROR; + Dlg->m_GT2Connection = NULL; + } +} + +void ReceivedCallback +( + GT2Connection connection, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + if(!message || !len) + return; + + GTMessageType type; + + type = gtEncodedMessageType((char*)message); + + if(type == MSG_CHALLENGE_TYPE) + { + ASSERT(!Dlg->m_hosting && (Dlg->m_state == JOIN_WAITING)); + + int pid; + char challenge[64]; + char response[33]; + if(gtDecode(MSG_CHALLENGE, (char*)message, len, &pid, challenge) == -1) + { + Dlg->m_state = JOIN_ERROR; + return; + } + + GenerateAuth(challenge, (char *)(LPCSTR)Dlg->m_loginDlg.m_password, response); + + // Send the response. + ///////////////////// + char message[64]; + int rcode; + rcode = gtEncode(MSG_RESPONSE_TYPE, MSG_RESPONSE, message, sizeof(message), response); + ASSERT(rcode != -1); + gt2Send(connection, (const GT2Byte *)message, rcode, GT2True); + + Dlg->m_remoteProfile = pid; + Dlg->m_state = JOIN_CONNECTED; + } + else if(type == MSG_RESPONSE_TYPE) + { + ASSERT(Dlg->m_hosting && (Dlg->m_state == HOST_CHALLENGING)); + + char response[33]; + if(gtDecode(MSG_RESPONSE, (char*)message, len, response) == -1) + { + Dlg->m_state = HOST_ERROR; + return; + } + Dlg->m_remoteResponse = response; + Dlg->m_state = HOST_CONNECTED; + } + else if(type == MSG_COUNTDOWN_TYPE) + { + ASSERT(!Dlg->m_hosting); + + if(gtDecode(MSG_COUNTDOWN, (char*)message, len, &Dlg->m_countdown) == -1) + { + Dlg->m_state = JOIN_ERROR; + return; + } + + Dlg->Countdown(); + } + else if(type == MSG_START_RACE_TYPE) + { + ASSERT(!Dlg->m_hosting); + + Dlg->StartRace(); + } + else if(type == MSG_PROGRESS_TYPE) + { + if(Dlg->m_racing) + { + int progress; + if(gtDecode(MSG_PROGRESS, (char*)message, len, &progress) != -1) + Dlg->m_remoteProgress.SetPos(progress); + } + } + else if(type == MSG_END_RACE_TYPE) + { + if(Dlg->m_racing) + { + gtDecode(MSG_END_RACE, (char*)message, len, &Dlg->m_remoteTime); + } + } +} + +void ClosedCallback +( + GT2Connection connection, + GT2CloseReason reason +) +{ + // Logout triggers this function when it's a local close + // so we need to make sure we don't loop + if (reason != GT2LocalClose) + { + Dlg->MessageBox("Connection closed"); + Dlg->Logout(); + } + else + Dlg->m_GT2Connection = NULL; +} + +void ConnectAttemptCallback +( + GT2Socket listener, + GT2Connection connection, + unsigned int ip, + unsigned short port, + int latency, + GT2Byte * message, + int len +) +{ + int pid = 0; + char nick[128]; + + // Only allow one connection. + ///////////////////////////// + if(Dlg->m_GT2Connection) + { + gt2Reject(connection, NULL, 0); + return; + } + + // Decode the pid. + ////////////////// + if(message && len) + { + if(gtDecodeNoType(MSG_CONNECT, (char*)message, len, &pid, nick) == -1) + pid = 0; + } + + // If we didn't/couldn't get the pid, reject the attempt. + ///////////////////////////////////////////////////////// + if(!pid) + { + gt2Reject(connection, NULL, 0); + return; + } + + // Accept the connection. + ///////////////////////// + GT2ConnectionCallbacks callbacks; + memset(&callbacks, 0, sizeof(GT2ConnectionCallbacks)); + callbacks.received = ReceivedCallback; + callbacks.closed = ClosedCallback; + if(!gt2Accept(connection, &callbacks)) + return; + + // Set some states. + /////////////////// + Dlg->m_remoteProfile = pid; + Dlg->m_GT2Connection = connection; + Dlg->m_state = HOST_CHALLENGING; + Dlg->m_challenged = FALSE; + Dlg->m_remoteNick = nick; +} + +/*void StoppedCallback +( + GT2Socket listener, + GTStopReason reason +) +{ + if(reason != GTStopped) + { + Dlg->MessageBox("Listener stopped"); + Dlg->Logout(); + } +}*/ + +void SocketErrorCallback +( + GT2Socket socket +) +{ + Dlg->MessageBox("Socket Error!"); + Dlg->Logout(); +} + + +BOOL CLadderTrackDlg::SetupHosting() +{ + int rcode; + CString str; + + if(!IsStatsConnected()) + { + // Use the development system. + ////////////////////////////// + strcpy(StatsServerHostname, "sdkdev.gamespy.com"); + + // Set the gamename and secret key. + /////////////////////////////////// + strcpy(gcd_gamename, "st_ladder"); + gcd_secret_key[0] = 'K'; + gcd_secret_key[1] = 'w'; + gcd_secret_key[2] = 'F'; + gcd_secret_key[3] = 'J'; + gcd_secret_key[4] = '2'; + gcd_secret_key[5] = 'X'; + + // Init the connection to the backend. + ////////////////////////////////////// + rcode = InitStatsConnection(0); + if(rcode != GE_NOERROR) + { + str.Format("Failed to connect to the stats server (%d).", rcode); + MessageBox(str); + PostQuitMessage(1); + return TRUE; + } + + // Get the challenge. + ///////////////////// + m_challenge = GetChallenge(NULL); + + //FakeStats(); + } + + /* + // Setup the listener's callbacks. + ////////////////////////////////// + GTListenerCallbacks callbacks; + memset(&callbacks, 0, sizeof(GTListenerCallbacks)); + callbacks.connectAttempt = ConnectAttemptCallback; + callbacks.stopped = StoppedCallback; + + // Create the listener. + /////////////////////// + + m_listener = gtListen(gtAddressToString(0, HOST_PORT, NULL), &callbacks); + if(!m_listener) + return FALSE;*/ + + GT2ConnectionCallbacks connectionCallbacks; + memset(&connectionCallbacks, 0, sizeof(GT2ConnectionCallbacks)); + connectionCallbacks.received = ReceivedCallback; + connectionCallbacks.closed = ClosedCallback; + + GT2Result aResult = gt2CreateSocket(&m_GT2Socket, HOST_PORT_STRING, 0, 0, SocketErrorCallback); + if (GT2Success != aResult) + return FALSE; + + gt2Listen(m_GT2Socket, ConnectAttemptCallback); + m_state = HOST_LISTENING; + + // Bring up the "waiting" dialog. + ///////////////////////////////// + rcode = m_waitingDlg.DoModal(); + + // If it was cancelled, try again. + ////////////////////////////////// + if(rcode != IDOK) + Logout(); + + return TRUE; +} + +BOOL CLadderTrackDlg::SetupJoining() +{ + int rcode; + + // Setup the address to connect to. + /////////////////////////////////// + CString remoteAddress; + remoteAddress.Format("%s%s", m_hostOrJoinDlg.m_joinAddress, HOST_PORT_STRING); + + // Encode the profile id. + ///////////////////////// + char buffer[64]; + rcode = gtEncodeNoType(MSG_CONNECT, buffer, sizeof(buffer), m_loginDlg.m_profile, m_loginDlg.m_nick); + ASSERT(rcode != -1); + + // Setup the callbacks. + /////////////////////// + GT2ConnectionCallbacks callbacks; + memset(&callbacks, 0, sizeof(GT2ConnectionCallbacks)); + callbacks.connected = ConnectedCallback; + callbacks.received = ReceivedCallback; + callbacks.closed = ClosedCallback; + + // Create the socket + GT2Result aResult = gt2CreateSocket(&m_GT2Socket, CLIENT_PORT_STRING, 0, 0, SocketErrorCallback); + if (aResult != GT2Success) + { + MessageBox("Failed to create socket!"); + return FALSE; + } + + // Connect. + /////////// + m_state = JOIN_CONNECTING; + GT2Result result; + result = gt2Connect(m_GT2Socket, &m_GT2Connection, remoteAddress, (const GT2Byte *)buffer, sizeof(buffer), -1, &callbacks, GT2False); + if(!m_GT2Connection) + return FALSE; + + // Bring up the "waiting" dialog. + ///////////////////////////////// + rcode = m_waitingDlg.DoModal(); + + // If it was cancelled, try again. + ////////////////////////////////// + if(rcode != IDOK) + Logout(); + + return TRUE; +} + +GHTTPBool PlayerPositionPageCompleted +( + GHTTPRequest request, + GHTTPResult result, + char * buffer, + __int64 bufferLen, + void * param +) +{ + if(result == GHTTPSuccess) + { + BOOL localPlayer; + int position; + + Dlg->UpdateData(); + + localPlayer = (BOOL)param; + position = atoi(buffer); + + if(localPlayer) + Dlg->m_localPosition.Format("%d", position); + else + Dlg->m_remotePosition.Format("%d", position); + + Dlg->UpdateData(FALSE); + } + + return GHTTPTrue; +} + +void CLadderTrackDlg::UpdatePlayerPositions() +{ + CString url; + url.Format("http://sdkdev.gamespy.com/games/st_ladder/web/playerposition.asp?pid=%d", m_loginDlg.m_profile); + ghttpGet(url, GHTTPFalse, PlayerPositionPageCompleted, (void *)TRUE); + url.Format("http://sdkdev.gamespy.com/games/st_ladder/web/playerposition.asp?pid=%d", m_remoteProfile); + ghttpGet(url, GHTTPFalse, PlayerPositionPageCompleted, (void *)FALSE); +} + +BOOL CLadderTrackDlg::SetupMatch() +{ + int rcode; + BOOL result; + + m_state = SETTING_UP; + + ASSERT(!m_GT2Connection); + ASSERT(!m_GT2Socket); + + m_state = 0; + m_remoteResponse.Empty(); + m_remoteProfile = 0; + m_GT2Connection = NULL; + m_startRace.EnableWindow(FALSE); + m_start = 0; + m_countdown = 0; + m_racing = FALSE; + m_numSteps = 0; + m_step = NONE; + + do + { + // Login the user (actually just verifying the login). + ////////////////////////////////////////////////////// + rcode = m_loginDlg.DoModal(); + if(rcode != IDOK) + { + PostQuitMessage(1); + return FALSE; + } + + // See if they want to host or join. + //////////////////////////////////// + rcode = m_hostOrJoinDlg.DoModal(); + } + while(rcode != IDOK); + + m_GT2Connection = NULL; + m_hosting = (m_hostOrJoinDlg.m_hostOrJoin == HOSTORJOIN_HOST); + + CString str; + str.Format("ladderTrack%s", m_hosting?" (hosting)":""); + SetWindowText(str); + + if(m_hosting) + result = SetupHosting(); + else + result = SetupJoining(); + + if(result && m_hosting) + { + m_startRace.EnableWindow(); + } + + UpdatePlayerPositions(); + + return result; +} + +BOOL CLadderTrackDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + SetIcon(m_hIcon, TRUE); + SetIcon(m_hIcon, FALSE); + + // Basic initialization. + //////////////////////// + Dlg = this; + m_GT2Connection = NULL; + m_GT2Socket = NULL; + m_state = LOGGED_OUT; + m_countdown = 0; + m_racing = FALSE; + + // Init gt. + /////////// + //gtStartup(); + + // Set a think timer. + ///////////////////// + SetTimer(TIMER_THINK, 50, NULL); + + return TRUE; +} + +void CLadderTrackDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CDialog::OnPaint(); + } +} + +HCURSOR CLadderTrackDlg::OnQueryDragIcon() +{ + return (HCURSOR) m_hIcon; +} + +void CLadderTrackDlg::OnLogout() +{ + // Clean stuff up. + ////////////////// + if(m_GT2Connection) + { + gt2CloseConnection(m_GT2Connection); + m_GT2Connection = NULL; + } + if(m_GT2Socket) + { + gt2CloseSocket(m_GT2Socket); + m_GT2Socket = NULL; + } + if(m_waitingDlg.m_hWnd && m_waitingDlg.IsWindowEnabled()) + { + m_waitingDlg.EndDialog(IDCANCEL); + } + + m_state = LOGGED_OUT; +} + +void CLadderTrackDlg::OnDestroy() +{ + CDialog::OnDestroy(); + + if(IsStatsConnected()) + CloseStatsConnection(); + + ghttpCleanup(); + + //gt2Cleanup(GTFalse); + if (m_GT2Connection) + { + gt2CloseConnection(m_GT2Connection); + m_GT2Connection = NULL; + } + if (m_GT2Socket) + { + gt2CloseSocket(m_GT2Socket); + m_GT2Socket = NULL; + } +} + +void CLadderTrackDlg::OnTimer(UINT nIDEvent) +{ + char buffer[64]; + int rcode; + + if(nIDEvent == TIMER_THINK) + { + static BOOL thinking; + + if(!thinking) + { + thinking = TRUE; + + if (m_GT2Socket) + gt2Think(m_GT2Socket); + ghttpThink(); + + if(m_state == HOST_CHALLENGING) + { + if(!m_challenged) + { + // Send the challenge string and our profile. + ///////////////////////////////////////////// + rcode = gtEncode(MSG_CHALLENGE_TYPE, MSG_CHALLENGE, buffer, sizeof(buffer), m_loginDlg.m_profile, (LPCSTR)m_challenge); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)buffer, rcode, GT2True); + m_challenged = TRUE; + } + } + else if(m_state == HOST_CONNECTED) + { + m_waitingDlg.EndDialog(IDOK); + m_state = RACING; + } + else if(m_state == HOST_ERROR) + { + MessageBox("Error setting up hosting"); + m_waitingDlg.EndDialog(IDCANCEL); + } + else if(m_state == JOIN_CONNECTED) + { + if(m_waitingDlg.m_hWnd && m_waitingDlg.IsWindowEnabled()) + m_waitingDlg.EndDialog(IDOK); + } + else if(m_state == JOIN_ERROR) + { + MessageBox("Error joining a game"); + m_waitingDlg.EndDialog(IDCANCEL); + } + + thinking = FALSE; + } + + if(m_state == LOGGED_OUT) + { + if(!Dlg->SetupMatch()) + { + MessageBox("Error setting up the match"); + Logout(); + } + } + + // Are we racing? + ///////////////// + if(m_racing) + { + // Did we finish? + ///////////////// + if(m_localTime) + { + // Did we both finish? + ////////////////////// + if(m_remoteTime) + { + // Done racing. + /////////////// + m_racing = FALSE; + + m_info = "Race Complete"; + UpdateData(FALSE); + + // Report the stats. + //////////////////// + if(m_hosting) + ReportStats(); + + // Show the times. + ////////////////// + CString message; + if(m_localTime < m_remoteTime) + message.Format("You won!\n%0.3fs to %0.3fs", m_localTime / 1000.0, m_remoteTime / 1000.0); + else if(m_remoteTime < m_localTime) + message.Format("You lost!\n%0.3fs to %0.3fs", m_localTime / 1000.0, m_remoteTime / 1000.0); + else + message.Format("You tied!\n%0.3fs", m_localTime / 1000.0); + MessageBox(message); + + m_localProgress.SetPos(0); + m_remoteProgress.SetPos(0); + } + } + else + { + // Let our opponent know how far we are. + //////////////////////////////////////// + rcode = gtEncode(MSG_PROGRESS_TYPE, MSG_PROGRESS, buffer, sizeof(buffer), m_numSteps); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)buffer, rcode, GT2False); + } + } + } + else if(nIDEvent == TIMER_COUNTDOWN) + { + m_countdown--; + if(m_countdown <= 0) + KillTimer(TIMER_COUNTDOWN); + + if(m_countdown < 0) + return; + + Countdown(); + if(!m_countdown) + StartRace(); + } + + CDialog::OnTimer(nIDEvent); +} + +void CLadderTrackDlg::Logout() +{ + OnLogout(); +} + +void CLadderTrackDlg::Countdown() +{ + if(m_hosting) + { + int rcode; + char message[32]; + rcode = gtEncode(MSG_COUNTDOWN_TYPE, MSG_COUNTDOWN, message, sizeof(message), m_countdown); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)message, rcode, GT2True); + } + + if(m_countdown) + { + UpdateData(); + + m_info.Format("Race starts in %ds", m_countdown); + + UpdateData(FALSE); + } +} + +void CLadderTrackDlg::OnStart() +{ + // Start the countdown. + /////////////////////// + m_countdown = COUNTDOWN_START; + SetTimer(TIMER_COUNTDOWN, 1000, NULL); + Countdown(); +} + +BOOL CLadderTrackDlg::PreTranslateMessage(MSG* pMsg) +{ + if(pMsg->message == WM_KEYDOWN) + { + int nChar = pMsg->wParam; + if((nChar == 'Z') || (nChar == 'X')) + { + if((pMsg->lParam & 0xFFFF) == 1) + { + CString str; + BOOL stepped = FALSE; + + if((nChar == 'Z') && (m_step != LEFT)) + { + m_step = LEFT; + m_numSteps++; + stepped = TRUE; + } + else if ((nChar == 'X') && (m_step != RIGHT)) + { + m_step = RIGHT; + m_numSteps++; + stepped = TRUE; + } + + if(stepped && m_racing) + { + m_localProgress.SetPos(m_numSteps); + if(m_numSteps == m_totalSteps) + { + m_localTime = (GetTickCount() - m_start); + str.Format("%0.3fs\n", m_localTime / 1000.0); + OutputDebugString(str); + //MessageBox(str); + + if(!m_remoteTime) + { + UpdateData(); + m_info = "Waiting for opponent"; + UpdateData(FALSE); + } + + // Let them know we finished. + ///////////////////////////// + char buffer[32]; + int rcode; + rcode = gtEncode(MSG_END_RACE_TYPE, MSG_END_RACE, buffer, sizeof(buffer), m_localTime); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)buffer, rcode, GT2True); + } + } + } + + return TRUE; + } + } + + return CDialog::PreTranslateMessage(pMsg); +} + +void CLadderTrackDlg::StartRace() +{ + if(m_hosting) + { + int rcode; + char buffer[32]; + rcode = gtEncode(MSG_START_RACE_TYPE, MSG_START_RACE, buffer, sizeof(buffer)); + ASSERT(rcode != -1); + gt2Send(m_GT2Connection, (const unsigned char*)buffer, rcode, GT2True); + } + + m_localTime = 0; + m_remoteTime = 0; + m_racing = TRUE; + m_numSteps = 0; + m_step = NONE; + m_racing = TRUE; + m_start = GetTickCount(); + m_totalSteps = RACE_STEPS; + m_localProgress.SetRange(0, m_totalSteps); + m_localProgress.SetPos(0); + m_remoteProgress.SetRange(0, m_totalSteps); + m_remoteProgress.SetPos(0); + + UpdateData(); + m_info.Format("GO!"); + UpdateData(FALSE); +} + +void CLadderTrackDlg::ReportStats() +{ + char response[33]; + + // Game. + //////// + NewGame(1); + BucketStringOp(NULL, "hostname", bo_set, (char *)(LPCSTR)m_loginDlg.m_nick, bl_server, 0); + + // Local player (host). + /////////////////////// + NewPlayer(NULL, 0, (char *)(LPCSTR)m_loginDlg.m_nick); + BucketIntOp(NULL, "time", bo_set, m_localTime, bl_player, 0); + BucketIntOp(NULL, "pid", bo_set, m_loginDlg.m_profile, bl_player, 0); + GenerateAuth((char *)(LPCSTR)m_challenge, (char *)(LPCSTR)m_loginDlg.m_password, response); + BucketStringOp(NULL, "auth", bo_set, response, bl_player, 0); + + // Remote player (joined). + ////////////////////////// + NewPlayer(NULL, 1, (char *)(LPCSTR)m_remoteNick); + BucketIntOp(NULL, "time", bo_set, m_remoteTime, bl_player, 1); + BucketIntOp(NULL, "pid", bo_set, m_remoteProfile, bl_player, 1); + BucketStringOp(NULL, "auth", bo_set, (char *)(LPCSTR)m_remoteResponse, bl_player, 1); + + SendGameSnapShot(NULL, NULL, SNAP_FINAL); + FreeGame(NULL); +} + +struct FakeProfile +{ + int pid; + char password[32]; + char nick[32]; + DWORD minTime; + DWORD maxTime; +}; + +FakeProfile FakeProfiles[] = +{ + { 4933555, "mrpants", "ABCDEF", 4500, 6000}, + { 4903509, "mrpants", "BigFatty", 5500, 7000}, + { 4933502, "mrpants", "Colonel Op", 5500, 7000}, + { 4933634, "mrpants", "GHIJKL", 4500, 7000}, + { 3017545, "mrpants", "test1", 4500, 8000}, + { 3017574, "mrpants", "test2", 4500, 6000}, + { 3360407, "mrpants", "test3", 6500, 7000}, + { 4933752, "mrpants", "tester", 5500, 7000}, + { 2840651, "mrpants", "a,b,c", 6500, 8000}, + { 3410517, "mrpants", "abcabc", 4500, 6000}, + { 3652959, "mrpants", "bane", 6500, 8000}, + { 3654054, "mrpants", "cccccc", 6500, 7000}, + { 8696884, "mrpants", "Jimmy Page", 5500, 6000}, + { 100001, "mrpants", "Mr. Pants", 5500, 8000}, + { 2881317, "mrpants", "mrpants", 4500, 7000}, + { 3651362, "mrpants", "mrpantsz", 6500, 7000}, + { 3671851, "mrpants", "mrwasabi", 5500, 6000}, + { 3652930, "mrpants", "pants", 6500, 8000}, + { 2833041, "mrpants", "pants3", 6500, 7000}, + { 2833042, "mrpants", "pants4", 5500, 7000}, + { 2833043, "mrpants", "pants5", 6500, 7000}, + { 2833045, "mrpants", "pants6", 4500, 7000}, + { 2833048, "mrpants", "pants7", 6500, 7000}, + { 2833049, "mrpants", "pants8", 4500, 6000}, + { 2833050, "mrpants", "pants9", 6500, 8000}, + { 2833074, "mrpants", "pants16", 7000, 8000}, + { 2833075, "mrpants", "pants17", 4500, 8000}, + { 2833076, "mrpants", "pants18", 6500, 7000}, + { 2833077, "mrpants", "pants19", 5500, 7000}, + { 2833079, "mrpants", "pants20", 6500, 7000}, + { 2833080, "mrpants", "pants21", 6500, 8000}, + { 2833081, "mrpants", "pants22", 4500, 6000}, + { 3654074, "mrpants", "sdfrrr", 4500, 6000}, + { 3733688, "mrpants", "Skeletor", 4500, 7000}, + { 2977286, "mrpants", "testtesttest", 6500, 8000}, + { 3654019, "mrpants", "tttttest", 4500, 6000} +}; + +void CLadderTrackDlg::FakeStats() +{ + int num = (sizeof(FakeProfiles) / sizeof(FakeProfile)); + int total = 1000; + int p1; + int p2; + int i; + char response[33]; + CString msg; + + srand(time(NULL)); + + for(i = 0 ; i < total ; i++) + { + p1 = (rand() % num); + p2 = (rand() % num); + + if(p1 == p2) + continue; + + m_loginDlg.m_nick = FakeProfiles[p1].nick; + m_loginDlg.m_profile = FakeProfiles[p1].pid; + m_loginDlg.m_password = FakeProfiles[p1].password; + m_localTime = (DWORD)(FakeProfiles[p1].minTime + ((rand() / (float)RAND_MAX) * (FakeProfiles[p1].maxTime - FakeProfiles[p1].minTime))); + + m_remoteNick = FakeProfiles[p2].nick; + m_remoteProfile = FakeProfiles[p2].pid; + GenerateAuth((char *)(LPCSTR)m_challenge, FakeProfiles[p2].password, response); + m_remoteResponse = response; + m_remoteTime = (DWORD)(FakeProfiles[p2].minTime + ((rand() / (float)RAND_MAX) * (FakeProfiles[p2].maxTime - FakeProfiles[p2].minTime))); + + ReportStats(); + + msg.Format("game %d reported\n", i); + OutputDebugString(msg); + + Sleep(1200); + } + + exit(1); +} + +void CLadderTrackDlg::OnUpdatePositions() +{ + UpdatePlayerPositions(); +} diff --git a/code/gamespy/gstats/ladderTrack/ladderTrackDlg.h b/code/gamespy/gstats/ladderTrack/ladderTrackDlg.h new file mode 100644 index 00000000..0929139e --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/ladderTrackDlg.h @@ -0,0 +1,117 @@ +// ladderTrackDlg.h : header file +// + +#if !defined(AFX_LADDERTRACKDLG_H__82121F55_1FDB_427A_B616_B59EB16C5E6D__INCLUDED_) +#define AFX_LADDERTRACKDLG_H__82121F55_1FDB_427A_B616_B59EB16C5E6D__INCLUDED_ + +#include "LoginDlg.h" +#include "HostOrJoinDlg.h" +#include "..\..\GT2\gt2.h" // Added by ClassView +#include "..\..\GT2\gt2Encode.h" +#include "WaitingDlg.h" // Added by ClassView + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +///////////////////////////////////////////////////////////////////////////// +// CLadderTrackDlg dialog + +#define LOGGED_OUT 1 +#define SETTING_UP 2 +#define RACING 3 + +#define HOST_LISTENING 11 +#define HOST_CHALLENGING 12 +#define HOST_CONNECTED 13 +#define HOST_ERROR 14 + +#define JOIN_CONNECTING 21 +#define JOIN_WAITING 22 +#define JOIN_CONNECTED 23 +#define JOIN_ERROR 24 + +#define NONE -1 +#define LEFT 0 +#define RIGHT 1 + +#define RACE_STEPS 60 + +class CLadderTrackDlg : public CDialog +{ +// Construction +public: + void FakeStats(); + CString m_remoteNick; + DWORD m_remoteTime; + DWORD m_localTime; + int m_totalSteps; + int m_step; + int m_numSteps; + DWORD m_start; + BOOL m_racing; + int m_countdown; + BOOL m_challenged; + CString m_remoteResponse; + int m_state; + BOOL m_hosting; + CString m_challenge; + int m_remoteProfile; + CLoginDlg m_loginDlg; + CHostOrJoinDlg m_hostOrJoinDlg; + CWaitingDlg m_waitingDlg; + + GT2Socket m_GT2Socket; // raw socket + GT2Connection m_GT2Connection; // established connection + + BOOL SetupHosting(); + BOOL SetupJoining(); + BOOL SetupMatch(); + void UpdatePlayerPositions(); + void Countdown(); + void Logout(); + void StartRace(); + void ReportStats(); + CLadderTrackDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CLadderTrackDlg) + enum { IDD = IDD_LADDERTRACK_DIALOG }; + CButton m_startRace; + CProgressCtrl m_remoteProgress; + CProgressCtrl m_localProgress; + CString m_localPosition; + CString m_remotePosition; + CString m_info; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CLadderTrackDlg) + public: + virtual BOOL PreTranslateMessage(MSG* pMsg); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + HICON m_hIcon; + + // Generated message map functions + //{{AFX_MSG(CLadderTrackDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + afx_msg void OnStart(); + afx_msg void OnDestroy(); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnLogout(); + afx_msg void OnUpdatePositions(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_LADDERTRACKDLG_H__82121F55_1FDB_427A_B616_B59EB16C5E6D__INCLUDED_) diff --git a/code/gamespy/gstats/ladderTrack/res/ladderTrack.ico b/code/gamespy/gstats/ladderTrack/res/ladderTrack.ico new file mode 100644 index 00000000..7eef0bcb Binary files /dev/null and b/code/gamespy/gstats/ladderTrack/res/ladderTrack.ico differ diff --git a/code/gamespy/gstats/ladderTrack/res/ladderTrack.rc2 b/code/gamespy/gstats/ladderTrack/res/ladderTrack.rc2 new file mode 100644 index 00000000..c70ab2ee --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/res/ladderTrack.rc2 @@ -0,0 +1,13 @@ +// +// LADDERTRACK.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/gstats/ladderTrack/resource.h b/code/gamespy/gstats/ladderTrack/resource.h new file mode 100644 index 00000000..8cc94c9f --- /dev/null +++ b/code/gamespy/gstats/ladderTrack/resource.h @@ -0,0 +1,34 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by ladderTrack.rc +// +#define IDD_LADDERTRACK_DIALOG 102 +#define IDC_EMAIL 103 +#define IDC_NICK 104 +#define IDC_PASSWORD 105 +#define IDR_MAINFRAME 128 +#define IDD_LOGIN 129 +#define IDD_HOST_OR_JOIN 130 +#define IDD_WAITING 132 +#define IDC_HOST 1000 +#define IDC_LOCAL_POSITION 1000 +#define IDC_JOIN 1001 +#define IDC_REMOTE_POSITION 1001 +#define IDC_JOIN_ADDRESS 1002 +#define IDC_LOGOUT 1004 +#define IDC_START_RACE 1005 +#define IDC_INFO 1006 +#define IDC_LOCAL_PROGRESS 1007 +#define IDC_REMOTE_PROGRESS 1008 +#define IDC_UPDATE_POSITIONS 1009 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/gamespy/gstats/multiTrack/HostOrJoinDlg.cpp b/code/gamespy/gstats/multiTrack/HostOrJoinDlg.cpp new file mode 100644 index 00000000..2988012a --- /dev/null +++ b/code/gamespy/gstats/multiTrack/HostOrJoinDlg.cpp @@ -0,0 +1,70 @@ +// HostOrJoinDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "multiTrack.h" +#include "HostOrJoinDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CHostOrJoinDlg dialog + + +CHostOrJoinDlg::CHostOrJoinDlg(CWnd* pParent /*=NULL*/) + : CDialog(CHostOrJoinDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CHostOrJoinDlg) + m_hostOrJoin = 0; + m_joinAddress = _T(""); + //}}AFX_DATA_INIT +} + + +void CHostOrJoinDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CHostOrJoinDlg) + DDX_Radio(pDX, IDC_HOST, m_hostOrJoin); + DDX_Text(pDX, IDC_JOIN_ADDRESS, m_joinAddress); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CHostOrJoinDlg, CDialog) + //{{AFX_MSG_MAP(CHostOrJoinDlg) + ON_EN_SETFOCUS(IDC_JOIN_ADDRESS, OnSetfocusJoinAddress) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CHostOrJoinDlg message handlers + + +void CHostOrJoinDlg::OnOK() +{ + UpdateData(); + + // Check for no join address. + ///////////////////////////// + if((m_hostOrJoin == HOSTORJOIN_JOIN) && m_joinAddress.IsEmpty()) + { + MessageBox("Please enter the address of the host to connect to"); + return; + } + + CDialog::OnOK(); +} + +void CHostOrJoinDlg::OnSetfocusJoinAddress() +{ + // Make sure its on join. + ///////////////////////// + UpdateData(); + m_hostOrJoin = HOSTORJOIN_JOIN; + UpdateData(FALSE); +} diff --git a/code/gamespy/gstats/multiTrack/HostOrJoinDlg.h b/code/gamespy/gstats/multiTrack/HostOrJoinDlg.h new file mode 100644 index 00000000..1041829b --- /dev/null +++ b/code/gamespy/gstats/multiTrack/HostOrJoinDlg.h @@ -0,0 +1,51 @@ +#if !defined(AFX_HOSTORJOINDLG_H__7D7ACBE1_D82A_4CB0_B070_368D4263332E__INCLUDED_) +#define AFX_HOSTORJOINDLG_H__7D7ACBE1_D82A_4CB0_B070_368D4263332E__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// HostOrJoinDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CHostOrJoinDlg dialog + +#define HOSTORJOIN_HOST 0 +#define HOSTORJOIN_JOIN 1 + +class CHostOrJoinDlg : public CDialog +{ +// Construction +public: + CHostOrJoinDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CHostOrJoinDlg) + enum { IDD = IDD_HOST_OR_JOIN }; + int m_hostOrJoin; + CString m_joinAddress; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CHostOrJoinDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CHostOrJoinDlg) + virtual void OnOK(); + afx_msg void OnSetfocusJoinAddress(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_HOSTORJOINDLG_H__7D7ACBE1_D82A_4CB0_B070_368D4263332E__INCLUDED_) diff --git a/code/gamespy/gstats/multiTrack/LoginDlg.cpp b/code/gamespy/gstats/multiTrack/LoginDlg.cpp new file mode 100644 index 00000000..13ecf2d1 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/LoginDlg.cpp @@ -0,0 +1,186 @@ +// LoginDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "multiTrack.h" +#include "LoginDlg.h" +#include "../../common/gsAvailable.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + + +CLoginDlg::CLoginDlg(CWnd* pParent /*=NULL*/) + : CDialog(CLoginDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CLoginDlg) + m_email = _T(""); + m_nick = _T(""); + m_password = _T(""); + //}}AFX_DATA_INIT +} + + +void CLoginDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CLoginDlg) + DDX_Text(pDX, IDC_EMAIL, m_email); + DDX_Text(pDX, IDC_NICK, m_nick); + DDX_Text(pDX, IDC_PASSWORD, m_password); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CLoginDlg, CDialog) + //{{AFX_MSG_MAP(CLoginDlg) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg message handlers + +GPResult checkResult; +int checkProfile; + +void CheckUserCallback(GPConnection * connection, void * _arg, void * param) +{ + GPCheckResponseArg * arg = (GPCheckResponseArg *)_arg; + + checkResult = arg->result; + checkProfile = arg->profile; +} + +void CLoginDlg::OnOK() +{ + GPConnection connection; + HCURSOR hourglass; + HCURSOR lastCursor; + GPResult result; + const char* aGameName = "gmtest"; + + UpdateData(); + + // CHECK FOR NO ACCOUNT INFO + //////////////////////////// + if(m_email.IsEmpty() || m_nick.IsEmpty() || m_password.IsEmpty()) + { + MessageBox("Please fill in all the account information."); + return; + } + + // AVAILABILITY CHECK + GSIStartAvailableCheck(aGameName); + GSIACResult ac_result = GSIACWaiting;; + while((ac_result = GSIAvailableCheckThink()) == GSIACWaiting) + msleep(5); + if(ac_result != GSIACAvailable) + { + printf("The backend is not available\n"); + return ;; + } + + + // INITIALIZE GP + //////////////// + if(gpInitialize(&connection, 502, 0, GP_PARTNERID_GAMESPY) != GP_NO_ERROR) + { + MessageBox("Error initializing the login system."); + return; + } + + // wait cursor on + ///////////////// + hourglass = LoadCursor(NULL, IDC_WAIT); + if(hourglass) + lastCursor = SetCursor(hourglass); + + // CHECK FOR THE ACCOUNT SPECIFIED + ////////////////////////////////// + result = gpCheckUser(&connection, m_nick, m_email, m_password, GP_BLOCKING, CheckUserCallback, NULL); + + // wait cursor off + ////////////////// + if(hourglass) + SetCursor(lastCursor); + + // DESTROY THE GP OBJECT + //////////////////////// + gpDestroy(&connection); + + // CHECK FOR AN ERROR + ///////////////////// + if(result != GP_NO_ERROR) + { + MessageBox("Error verifying the account."); + return; + } + + // CHECK THE RESULT + /////////////////// + if(checkResult != GP_NO_ERROR) + { + if(checkResult == GP_CHECK_BAD_EMAIL) + MessageBox("Invalid e-mail."); + else if(checkResult == GP_CHECK_BAD_NICK) + MessageBox("Invalid nick."); + else if(checkResult == GP_CHECK_BAD_PASSWORD) + MessageBox("Invalid password."); + else + MessageBox("Error verifying the account."); + return; + } + + // save the login info for next time + //////////////////////////////////// + FILE * file; + file = fopen("login.txt", "wt"); + if(file) + { + fprintf(file, "%s\n%s\n%s", m_email, m_nick, m_password); + fclose(file); + } + + // STORE THE PROFILE ID + /////////////////////// + m_profile = checkProfile; + + CDialog::OnOK(); +} + +BOOL CLoginDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // load login info + ////////////////// + FILE * file; + file = fopen("login.txt", "rt"); + if(file) + { + char buffer[512]; + + if(fgets(buffer, sizeof(buffer), file)) + m_email = buffer; + if(fgets(buffer, sizeof(buffer), file)) + m_nick = buffer; + if(fgets(buffer, sizeof(buffer), file)) + m_password = buffer; + + fclose(file); + + m_email.Remove('\n'); + m_nick.Remove('\n'); + m_password.Remove('\n'); + } + + UpdateData(FALSE); + + return TRUE; +} \ No newline at end of file diff --git a/code/gamespy/gstats/multiTrack/LoginDlg.h b/code/gamespy/gstats/multiTrack/LoginDlg.h new file mode 100644 index 00000000..11a5e2a7 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/LoginDlg.h @@ -0,0 +1,50 @@ +#if !defined(AFX_LOGINDLG_H__E77DE9BB_5AB3_4731_B26A_403169643353__INCLUDED_) +#define AFX_LOGINDLG_H__E77DE9BB_5AB3_4731_B26A_403169643353__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// LoginDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + +class CLoginDlg : public CDialog +{ +// Construction +public: + int m_profile; + CLoginDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CLoginDlg) + enum { IDD = IDD_LOGIN }; + CString m_email; + CString m_nick; + CString m_password; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CLoginDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CLoginDlg) + virtual BOOL OnInitDialog(); + virtual void OnOK(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_LOGINDLG_H__E77DE9BB_5AB3_4731_B26A_403169643353__INCLUDED_) diff --git a/code/gamespy/gstats/multiTrack/StdAfx.cpp b/code/gamespy/gstats/multiTrack/StdAfx.cpp new file mode 100644 index 00000000..aacf2ef2 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// multiTrack.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/code/gamespy/gstats/multiTrack/StdAfx.h b/code/gamespy/gstats/multiTrack/StdAfx.h new file mode 100644 index 00000000..4c8d2878 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/StdAfx.h @@ -0,0 +1,31 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__535325A8_F784_4AFE_827C_AF35A881DD77__INCLUDED_) +#define AFX_STDAFX_H__535325A8_F784_4AFE_827C_AF35A881DD77__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +#include "../gstats.h" +#include "../../gp/gp.h" +#include "../../ghttp/ghttp.h" +#include "../../gt2/gt2.h" +#include "../../gt2/gt2Encode.h" + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__535325A8_F784_4AFE_827C_AF35A881DD77__INCLUDED_) diff --git a/code/gamespy/gstats/multiTrack/WaitingDlg.cpp b/code/gamespy/gstats/multiTrack/WaitingDlg.cpp new file mode 100644 index 00000000..5b6b322c --- /dev/null +++ b/code/gamespy/gstats/multiTrack/WaitingDlg.cpp @@ -0,0 +1,43 @@ +// WaitingDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "multiTrack.h" +#include "WaitingDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CWaitingDlg dialog + + +CWaitingDlg::CWaitingDlg(CWnd* pParent /*=NULL*/) + : CDialog(CWaitingDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CWaitingDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void CWaitingDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CWaitingDlg) + // NOTE: the ClassWizard will add DDX and DDV calls here + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CWaitingDlg, CDialog) + //{{AFX_MSG_MAP(CWaitingDlg) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CWaitingDlg message handlers + diff --git a/code/gamespy/gstats/multiTrack/WaitingDlg.h b/code/gamespy/gstats/multiTrack/WaitingDlg.h new file mode 100644 index 00000000..594f33f7 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/WaitingDlg.h @@ -0,0 +1,45 @@ +#if !defined(AFX_WAITINGDLG_H__E9C283EE_68D1_40E9_83C6_AAB81D13ED25__INCLUDED_) +#define AFX_WAITINGDLG_H__E9C283EE_68D1_40E9_83C6_AAB81D13ED25__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// WaitingDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CWaitingDlg dialog + +class CWaitingDlg : public CDialog +{ +// Construction +public: + CWaitingDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CWaitingDlg) + enum { IDD = IDD_WAITING }; + // NOTE: the ClassWizard will add data members here + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CWaitingDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CWaitingDlg) + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_WAITINGDLG_H__E9C283EE_68D1_40E9_83C6_AAB81D13ED25__INCLUDED_) diff --git a/code/gamespy/gstats/multiTrack/multiTrack.cpp b/code/gamespy/gstats/multiTrack/multiTrack.cpp new file mode 100644 index 00000000..5300e019 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/multiTrack.cpp @@ -0,0 +1,61 @@ +// multiTrack.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "multiTrack.h" +#include "multiTrackDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CMultiTrackApp + +BEGIN_MESSAGE_MAP(CMultiTrackApp, CWinApp) + //{{AFX_MSG_MAP(CMultiTrackApp) + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CMultiTrackApp construction + +CMultiTrackApp::CMultiTrackApp() +{ +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CMultiTrackApp object + +CMultiTrackApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CMultiTrackApp initialization + +BOOL CMultiTrackApp::InitInstance() +{ + // Standard initialization +/* +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif +*/ + CMultiTrackDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + } + else if (nResponse == IDCANCEL) + { + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/code/gamespy/gstats/multiTrack/multiTrack.h b/code/gamespy/gstats/multiTrack/multiTrack.h new file mode 100644 index 00000000..3c41b229 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/multiTrack.h @@ -0,0 +1,47 @@ +// multiTrack.h : main header file for the MULTITRACK application +// + +#if !defined(AFX_MULTITRACK_H__6EF40CA1_83C8_448A_883A_63AF15457C5E__INCLUDED_) +#define AFX_MULTITRACK_H__6EF40CA1_83C8_448A_883A_63AF15457C5E__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CMultiTrackApp: +// See multiTrack.cpp for the implementation of this class +// + +class CMultiTrackApp : public CWinApp +{ +public: + CMultiTrackApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CMultiTrackApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CMultiTrackApp) + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_MULTITRACK_H__6EF40CA1_83C8_448A_883A_63AF15457C5E__INCLUDED_) diff --git a/code/gamespy/gstats/multiTrack/multiTrack.rc b/code/gamespy/gstats/multiTrack/multiTrack.rc new file mode 100644 index 00000000..735ea833 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/multiTrack.rc @@ -0,0 +1,253 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\multiTrack.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\multiTrack.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_MULTITRACK_DIALOG DIALOGEX 0, 0, 252, 135 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "multiTrack" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Exit",IDOK,195,5,50,14 + PUSHBUTTON "Logout",IDC_LOGOUT,195,21,50,14 + PUSHBUTTON "Start 50m",IDC_START_50,10,15,50,14 + LTEXT "Info",IDC_INFO,10,35,80,8 + CONTROL "Progress1",IDC_LOCAL_PROGRESS,"msctls_progress32", + PBS_SMOOTH | WS_BORDER,10,70,80,14 + CONTROL "Progress1",IDC_REMOTE_PROGRESS,"msctls_progress32", + PBS_SMOOTH | WS_BORDER,10,105,80,14 + PUSHBUTTON "Start 100m",IDC_START_100,70,15,50,14 + PUSHBUTTON "Start 200m",IDC_START_200,130,15,50,14 + CTEXT "8888888888",IDC_LOCAL_INFO_OVERALL,145,70,41,8 + CTEXT "8888888888",IDC_REMOTE_INFO_OVERALL,195,70,41,8 + GROUPBOX "Rating",IDC_STATIC,105,45,140,85 + LTEXT "Opponent",IDC_STATIC,201,55,32,8 + LTEXT "Overall",IDC_STATIC,111,70,23,8 + LTEXT "50m",IDC_STATIC,111,85,14,8 + LTEXT "100m",IDC_STATIC,111,100,18,8 + LTEXT "200m",IDC_STATIC,111,115,18,8 + LTEXT "You",IDC_STATIC,161,55,14,8 + CTEXT "8888888888",IDC_LOCAL_INFO_50,145,85,41,8 + CTEXT "8888888888",IDC_LOCAL_INFO_100,145,100,41,8 + CTEXT "8888888888",IDC_LOCAL_INFO_200,145,115,41,8 + CTEXT "8888888888",IDC_REMOTE_INFO_100,195,100,41,8 + CTEXT "8888888888",IDC_REMOTE_INFO_50,195,85,41,8 + CTEXT "8888888888",IDC_REMOTE_INFO_200,195,115,41,8 + LTEXT "You:",IDC_STATIC,10,60,16,8 + LTEXT "Opponent:",IDC_STATIC,10,95,34,8 + LTEXT "Use Z && X to race",IDC_STATIC,10,45,57,8 +END + +IDD_LOGIN DIALOG DISCARDABLE 0, 0, 147, 76 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "GameSpy Login" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_EMAIL,51,8,84,12,ES_AUTOHSCROLL + EDITTEXT IDC_NICK,51,23,84,12,ES_AUTOHSCROLL + EDITTEXT IDC_PASSWORD,51,38,84,12,ES_PASSWORD | ES_AUTOHSCROLL + DEFPUSHBUTTON "Login",IDOK,15,55,50,14 + PUSHBUTTON "Cancel",IDCANCEL,80,55,50,14 + LTEXT "email:",IDC_STATIC,10,10,19,8 + LTEXT "nick:",IDC_STATIC,10,25,16,8 + LTEXT "password:",IDC_STATIC,10,40,33,8 +END + +IDD_HOST_OR_JOIN DIALOG DISCARDABLE 0, 0, 165, 50 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Host or Join?" +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "Host",IDC_HOST,"Button",BS_AUTORADIOBUTTON | WS_GROUP,7, + 7,31,10 + CONTROL "Join",IDC_JOIN,"Button",BS_AUTORADIOBUTTON,7,31,29,10 + EDITTEXT IDC_JOIN_ADDRESS,52,28,106,14,ES_AUTOHSCROLL | WS_GROUP + DEFPUSHBUTTON "OK",IDOK,52,7,50,14,WS_GROUP + PUSHBUTTON "Cancel",IDCANCEL,108,7,50,14 +END + +IDD_WAITING DIALOG DISCARDABLE 0, 0, 64, 45 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "multiTrack" +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "Cancel",IDCANCEL,7,24,50,14 + LTEXT "Please Wait...",IDC_STATIC,9,7,45,8 +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "multiTrack MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "multiTrack\0" + VALUE "LegalCopyright", "Copyright (C) 2001\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "multiTrack.EXE\0" + VALUE "ProductName", "multiTrack Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_MULTITRACK_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 245 + TOPMARGIN, 7 + BOTTOMMARGIN, 128 + END + + IDD_HOST_OR_JOIN, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 158 + TOPMARGIN, 7 + BOTTOMMARGIN, 43 + END + + IDD_WAITING, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 57 + TOPMARGIN, 7 + BOTTOMMARGIN, 38 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\multiTrack.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/gamespy/gstats/multiTrack/multiTrackDlg.cpp b/code/gamespy/gstats/multiTrack/multiTrackDlg.cpp new file mode 100644 index 00000000..ed0dcdf3 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/multiTrackDlg.cpp @@ -0,0 +1,1028 @@ +// multiTrackDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "multiTrack.h" +#include "multiTrackDlg.h" +#include "waitingDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CMultiTrackDlg dialog + +CMultiTrackDlg::CMultiTrackDlg(CWnd* pParent /*=NULL*/) + : CDialog(CMultiTrackDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CMultiTrackDlg) + m_info = _T("Ready"); + m_localInfo100 = _T(""); + m_localInfo200 = _T(""); + m_localInfo50 = _T(""); + m_localInfoOverall = _T(""); + m_remoteInfo100 = _T(""); + m_remoteInfo200 = _T(""); + m_remoteInfo50 = _T(""); + m_remoteInfoOverall = _T(""); + //}}AFX_DATA_INIT + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); +} + +void CMultiTrackDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CMultiTrackDlg) + DDX_Control(pDX, IDC_START_100, m_start100); + DDX_Control(pDX, IDC_START_200, m_start200); + DDX_Control(pDX, IDC_REMOTE_PROGRESS, m_remoteProgress); + DDX_Control(pDX, IDC_LOCAL_PROGRESS, m_localProgress); + DDX_Control(pDX, IDC_START_50, m_start50); + DDX_Text(pDX, IDC_INFO, m_info); + DDX_Text(pDX, IDC_LOCAL_INFO_100, m_localInfo100); + DDX_Text(pDX, IDC_LOCAL_INFO_200, m_localInfo200); + DDX_Text(pDX, IDC_LOCAL_INFO_50, m_localInfo50); + DDX_Text(pDX, IDC_LOCAL_INFO_OVERALL, m_localInfoOverall); + DDX_Text(pDX, IDC_REMOTE_INFO_100, m_remoteInfo100); + DDX_Text(pDX, IDC_REMOTE_INFO_200, m_remoteInfo200); + DDX_Text(pDX, IDC_REMOTE_INFO_50, m_remoteInfo50); + DDX_Text(pDX, IDC_REMOTE_INFO_OVERALL, m_remoteInfoOverall); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CMultiTrackDlg, CDialog) + //{{AFX_MSG_MAP(CMultiTrackDlg) + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_BN_CLICKED(IDC_LOGOUT, OnLogout) + ON_WM_DESTROY() + ON_WM_TIMER() + ON_BN_CLICKED(IDC_START_50, OnStart50) + ON_BN_CLICKED(IDC_START_100, OnStart100) + ON_BN_CLICKED(IDC_START_200, OnStart200) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CMultiTrackDlg message handlers + +#define MSG_CONNECT "is" // client-pid, nick +#define MSG_CHALLENGE "is" // host-pid, challenge +#define MSG_CHALLENGE_TYPE 1 +#define MSG_RESPONSE "s" // response +#define MSG_RESPONSE_TYPE 2 +#define MSG_COUNTDOWN "ii" // event, count +#define MSG_COUNTDOWN_TYPE 3 +#define MSG_START_RACE "" +#define MSG_START_RACE_TYPE 5 +#define MSG_PROGRESS "i" // progress +#define MSG_PROGRESS_TYPE 6 +#define MSG_END_RACE "i" // time +#define MSG_END_RACE_TYPE 7 +#define MSG_CHAT "s" // message +#define MSG_CHAT_TYPE 8 + +#define HOST_PORT 38465 +#define HOST_PORT_STRING ":38465" +#define CLIENT_PORT_STRING ":38446" +#define COUNTDOWN_START 5 + +#define TIMER_THINK 100 +#define TIMER_COUNTDOWN 101 + +#define DEFRATING 1400 +#define POINTSWIN 16 +#define POINTSLOSS -16 + +CMultiTrackDlg * Dlg; + +void ConnectedCallback +( + GT2Connection connection, + GT2Result result, + GT2Byte * message, + int len +) +{ + if(result == GT2Success) + Dlg->m_state = JOIN_WAITING; + else + { + Dlg->m_state = JOIN_ERROR; + Dlg->m_connection = NULL; + } +} + +void ReceivedCallback +( + GT2Connection connection, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + if(!message || !len) + return; + + GTMessageType type; + + type = gtEncodedMessageType((char*)message); + + if(type == MSG_CHALLENGE_TYPE) + { + ASSERT(!Dlg->m_hosting && (Dlg->m_state == JOIN_WAITING)); + + int pid; + char challenge[64]; + char response[33]; + if(gtDecode(MSG_CHALLENGE, (char*)message, len, &pid, challenge) == -1) + { + Dlg->m_state = JOIN_ERROR; + return; + } + + GenerateAuth(challenge, (char *)(LPCSTR)Dlg->m_loginDlg.m_password, response); + + // Send the response. + ///////////////////// + char message[64]; + int rcode; + rcode = gtEncode(MSG_RESPONSE_TYPE, MSG_RESPONSE, message, sizeof(message), response); + ASSERT(rcode != -1); + gt2Send(connection, (const GT2Byte*)message, rcode, GT2True); + + Dlg->m_remoteProfile = pid; + Dlg->m_state = JOIN_CONNECTED; + } + else if(type == MSG_RESPONSE_TYPE) + { + ASSERT(Dlg->m_hosting && (Dlg->m_state == HOST_CHALLENGING)); + + char response[33]; + if(gtDecode(MSG_RESPONSE, (char*)message, len, response) == -1) + { + Dlg->m_state = HOST_ERROR; + return; + } + Dlg->m_remoteResponse = response; + Dlg->m_state = HOST_CONNECTED; + } + else if(type == MSG_COUNTDOWN_TYPE) + { + ASSERT(!Dlg->m_hosting); + + if(gtDecode(MSG_COUNTDOWN, (char*)message, len, &Dlg->m_event, &Dlg->m_countdown) == -1) + { + Dlg->m_state = JOIN_ERROR; + return; + } + + Dlg->Countdown(); + } + else if(type == MSG_START_RACE_TYPE) + { + ASSERT(!Dlg->m_hosting); + + Dlg->StartRace(); + } + else if(type == MSG_PROGRESS_TYPE) + { + if(Dlg->m_racing) + { + int progress; + if(gtDecode(MSG_PROGRESS, (char*)message, len, &progress) != -1) + Dlg->m_remoteProgress.SetPos(progress); + } + } + else if(type == MSG_END_RACE_TYPE) + { + if(Dlg->m_racing) + { + gtDecode(MSG_END_RACE, (char*)message, len, &Dlg->m_remoteTime); + } + } +} + +void ClosedCallback +( + GT2Connection connection, + GT2CloseReason reason +) +{ + if(reason != GT2LocalClose) + { + Dlg->MessageBox("Connection closed"); + Dlg->Logout(); + } +} + +void ConnectAttemptCallback +( + GT2Socket listener, + GT2Connection connection, + unsigned int ip, + unsigned short port, + int latency, + GT2Byte * message, + int len +) +{ + int pid = 0; + char nick[128]; + + // Only allow one connection. + ///////////////////////////// + if(Dlg->m_connection) + { + gt2Reject(connection, NULL, 0); + return; + } + + // Decode the pid. + ////////////////// + if(message && len) + { + if(gtDecodeNoType(MSG_CONNECT, (char*)message, len, &pid, nick) == -1) + pid = 0; + } + + // If we didn't/couldn't get the pid, reject the attempt. + ///////////////////////////////////////////////////////// + if(!pid) + { + gt2Reject(connection, NULL, 0); + return; + } + + // Accept the connection. + ///////////////////////// + GT2ConnectionCallbacks callbacks; + memset(&callbacks, 0, sizeof(GT2ConnectionCallbacks)); + callbacks.received = ReceivedCallback; + callbacks.closed = ClosedCallback; + if(!gt2Accept(connection, &callbacks)) + return; + + // Set some states. + /////////////////// + Dlg->m_remoteProfile = pid; + Dlg->m_connection = connection; + Dlg->m_state = HOST_CHALLENGING; + Dlg->m_challenged = FALSE; + Dlg->m_remoteNick = nick; +} + +/*void StoppedCallback +( + GTListener listener, + GTStopReason reason +) +{ + if(reason != GTStopped) + { + Dlg->MessageBox("Listener stopped"); + Dlg->Logout(); + } +}*/ + +void SocketErrorCallback +( + GT2Socket socket +) +{ + Dlg->MessageBox("Socket Error!"); + Dlg->Logout(); +} + +BOOL CMultiTrackDlg::SetupHosting() +{ + int rcode; + CString str; + + if(!IsStatsConnected()) + { + // Use the development system. + ////////////////////////////// + strcpy(StatsServerHostname, "sdkdev.gamespy.com"); + + // Set the gamename and secret key. + /////////////////////////////////// + strcpy(gcd_gamename, "st_rank"); + gcd_secret_key[0] = '5'; + gcd_secret_key[1] = '3'; + gcd_secret_key[2] = 'J'; + gcd_secret_key[3] = 'x'; + gcd_secret_key[4] = '7'; + gcd_secret_key[5] = 'W'; + + // Init the connection to the backend. + ////////////////////////////////////// + rcode = InitStatsConnection(0); + if(rcode != GE_NOERROR) + { + str.Format("Failed to connect to the stats server (%d).", rcode); + MessageBox(str); + PostQuitMessage(1); + return TRUE; + } + + // Get the challenge. + ///////////////////// + m_challenge = GetChallenge(NULL); + } + + /* + // Setup the listener's callbacks. + ////////////////////////////////// + GTListenerCallbacks callbacks; + memset(&callbacks, 0, sizeof(GTListenerCallbacks)); + callbacks.connectAttempt = ConnectAttemptCallback; + callbacks.stopped = StoppedCallback; + + // Create the listener. + /////////////////////// + m_listener = gtListen(gtAddressToString(0, HOST_PORT, NULL), &callbacks); + if(!m_listener) + return FALSE; + */ + + GT2ConnectionCallbacks connectionCallbacks; + memset(&connectionCallbacks, 0, sizeof(GT2ConnectionCallbacks)); + connectionCallbacks.received = ReceivedCallback; + connectionCallbacks.closed = ClosedCallback; + + GT2Result aResult = gt2CreateSocket(&m_socket, HOST_PORT_STRING, 0, 0, SocketErrorCallback); + if (GT2Success != aResult) + return FALSE; + + gt2Listen(m_socket, ConnectAttemptCallback); + + m_state = HOST_LISTENING; + + // Bring up the "waiting" dialog. + ///////////////////////////////// + rcode = m_waitingDlg.DoModal(); + + // If it was cancelled, try again. + ////////////////////////////////// + if(rcode != IDOK) + Logout(); + + return TRUE; +} + +BOOL CMultiTrackDlg::SetupJoining() +{ + int rcode; + + // Setup the address to connect to. + /////////////////////////////////// + CString remoteAddress; + remoteAddress.Format("%s:%d", m_hostOrJoinDlg.m_joinAddress, HOST_PORT); + + // Encode the profile id. + ///////////////////////// + char buffer[64]; + rcode = gtEncodeNoType(MSG_CONNECT, buffer, sizeof(buffer), m_loginDlg.m_profile, m_loginDlg.m_nick); + ASSERT(rcode != -1); + + // Setup the callbacks. + /////////////////////// + GT2ConnectionCallbacks callbacks; + memset(&callbacks, 0, sizeof(GT2ConnectionCallbacks)); + callbacks.connected = ConnectedCallback; + callbacks.received = ReceivedCallback; + callbacks.closed = ClosedCallback; + + // Create the socket + GT2Result aResult = gt2CreateSocket(&m_socket, CLIENT_PORT_STRING, 0, 0, SocketErrorCallback); + if (aResult != GT2Success) + { + MessageBox("Failed to create socket!"); + return FALSE; + } + + // Connect. + /////////// + m_state = JOIN_CONNECTING; + aResult = gt2Connect(m_socket, &m_connection, remoteAddress, (const GT2Byte*)buffer, sizeof(buffer), -1, &callbacks, GT2False); + if(!m_connection) + return FALSE; + + // Bring up the "waiting" dialog. + ///////////////////////////////// + rcode = m_waitingDlg.DoModal(); + + // If it was cancelled, try again. + ////////////////////////////////// + if(rcode != IDOK) + Logout(); + + return TRUE; +} + +void CMultiTrackDlg::UpdateRatingsDisplay() +{ + Dlg->UpdateData(); + + m_localInfoOverall.Format("%d", m_localRatings[0]); + m_localInfo50.Format("%d", m_localRatings[1]); + m_localInfo100.Format("%d", m_localRatings[2]); + m_localInfo200.Format("%d", m_localRatings[3]); + + m_remoteInfoOverall.Format("%d", m_remoteRatings[0]); + m_remoteInfo50.Format("%d", m_remoteRatings[1]); + m_remoteInfo100.Format("%d", m_remoteRatings[2]); + m_remoteInfo200.Format("%d", m_remoteRatings[3]); + + Dlg->UpdateData(FALSE); +} + +GHTTPBool PlayerRatingsPageCompleted +( + GHTTPRequest request, + GHTTPResult result, + char * buffer, + __int64 bufferLen, + void * param +) +{ + if(result == GHTTPSuccess) + { + BOOL localPlayer = (BOOL)param; + int ratings[4]; + int rcode; + + // Scan out ratings. + //////////////////// + rcode = sscanf(buffer, "%d %d %d %d", &ratings[0], &ratings[1], &ratings[2], &ratings[3]); + if(rcode == 4) + { + if(localPlayer) + memcpy(Dlg->m_localRatings, ratings, sizeof(int) * 4); + else + memcpy(Dlg->m_remoteRatings, ratings, sizeof(int) * 4); + + Dlg->UpdateRatingsDisplay(); + } + } + + return GHTTPTrue; +} + +BOOL CMultiTrackDlg::SetupMatch() +{ + int rcode; + BOOL result; + + m_state = SETTING_UP; + + ASSERT(!m_connection); + ASSERT(!m_socket); + + m_state = 0; + m_remoteResponse.Empty(); + m_remoteProfile = 0; + m_connection = NULL; + m_start50.EnableWindow(FALSE); + m_start100.EnableWindow(FALSE); + m_start200.EnableWindow(FALSE); + m_start = 0; + m_countdown = 0; + m_racing = FALSE; + m_event = EVENT_NONE; + m_numSteps = 0; + m_step = NONE; + + do + { + // Login the user (actually just verifying the login). + ////////////////////////////////////////////////////// + rcode = m_loginDlg.DoModal(); + if(rcode != IDOK) + { + PostQuitMessage(1); + return FALSE; + } + + // See if they want to host or join. + //////////////////////////////////// + rcode = m_hostOrJoinDlg.DoModal(); + } + while(rcode != IDOK); + + m_connection = NULL; + m_hosting = (m_hostOrJoinDlg.m_hostOrJoin == HOSTORJOIN_HOST); + + CString str; + str.Format("multiTrack%s", m_hosting?" (hosting)":""); + SetWindowText(str); + + if(m_hosting) + result = SetupHosting(); + else + result = SetupJoining(); + + if(result && m_hosting) + { + m_start50.EnableWindow(); + m_start100.EnableWindow(); + m_start200.EnableWindow(); + } + + CString url; + url.Format("http://sdkdev.gamespy.com/games/st_rank/web/playerratings.asp?pid=%d", m_loginDlg.m_profile); + ghttpGet(url, GHTTPFalse, PlayerRatingsPageCompleted, (void *)TRUE); + url.Format("http://sdkdev.gamespy.com/games/st_rank/web/playerratings.asp?pid=%d", m_remoteProfile); + ghttpGet(url, GHTTPFalse, PlayerRatingsPageCompleted, (void *)FALSE); + + return result; +} + +BOOL CMultiTrackDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + SetIcon(m_hIcon, TRUE); + SetIcon(m_hIcon, FALSE); + + // Basic initialization. + //////////////////////// + Dlg = this; + m_connection = NULL; + m_socket = NULL; + m_state = LOGGED_OUT; + m_countdown = 0; + m_racing = FALSE; + + // Init gt. + /////////// + //gt2Startup(); + + // Set a think timer. + ///////////////////// + SetTimer(TIMER_THINK, 50, NULL); + + return TRUE; +} + +void CMultiTrackDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CDialog::OnPaint(); + } +} + +HCURSOR CMultiTrackDlg::OnQueryDragIcon() +{ + return (HCURSOR) m_hIcon; +} + +void CMultiTrackDlg::OnLogout() +{ + m_state = LOGGED_OUT; + + // Clean stuff up. + ////////////////// + if(m_connection) + { + gt2CloseConnection(m_connection); + m_connection = NULL; + } + if(m_socket) + { + gt2CloseSocket(m_socket); + m_socket = NULL; + } + if(m_waitingDlg.m_hWnd && m_waitingDlg.IsWindowEnabled()) + { + m_waitingDlg.EndDialog(IDCANCEL); + } +} + +void CMultiTrackDlg::OnDestroy() +{ + CDialog::OnDestroy(); + + if(IsStatsConnected()) + CloseStatsConnection(); + + ghttpCleanup(); + + //gt2Cleanup(GT2False); + if (m_connection) + { + gt2CloseConnection(m_connection); + m_connection = NULL; + } + if (m_socket) + { + gt2CloseSocket(m_socket); + m_socket = NULL; + } + +} + +int CalculateHandicap(int rating1, int rating2) +{ + static int htable[] = { 12, 34, 56, 78, 101, 126, 151, 177, 206, 239, 273, 315, 366, 446, 471, 715 }; + int diff; + int i; + + diff = abs(rating1 - rating2); + + for(i = 0 ; i < 16 ; i++) + { + if(diff < htable[i]) + return i; + } + + return 16; +} + +void CMultiTrackDlg::UpdateStats() +{ + int handicap; + + handicap = CalculateHandicap(m_localRatings[0], m_remoteRatings[0]); + if(handicap) + { + if(m_localRatings[0] > m_remoteRatings[0]) + { + m_localRatings[0] -= handicap; + m_remoteRatings[0] += handicap; + } + else + { + m_localRatings[0] += handicap; + m_remoteRatings[0] -= handicap; + } + } + + handicap = CalculateHandicap(m_localRatings[m_event], m_remoteRatings[m_event]); + if(handicap) + { + if(m_localRatings[m_event] > m_remoteRatings[m_event]) + { + m_localRatings[m_event] -= handicap; + m_remoteRatings[m_event] += handicap; + } + else + { + m_localRatings[m_event] += handicap; + m_remoteRatings[m_event] -= handicap; + } + } + + if(m_localTime < m_remoteTime) + { + m_localRatings[0] += POINTSWIN; + m_localRatings[m_event] += POINTSWIN; + m_remoteRatings[0] += POINTSLOSS; + m_remoteRatings[m_event] += POINTSLOSS; + } + else if(m_remoteTime < m_localTime) + { + m_remoteRatings[0] += POINTSWIN; + m_remoteRatings[m_event] += POINTSWIN; + m_localRatings[0] += POINTSLOSS; + m_localRatings[m_event] += POINTSLOSS; + } + + UpdateRatingsDisplay(); +} + +void CMultiTrackDlg::OnTimer(UINT nIDEvent) +{ + char buffer[64]; + int rcode; + + if(nIDEvent == TIMER_THINK) + { + static BOOL thinking; + + if(!thinking) + { + thinking = TRUE; + + if (m_socket) + gt2Think(m_socket); + ghttpThink(); + + if(m_state == HOST_CHALLENGING) + { + if(!m_challenged) + { + // Send the challenge string and our profile. + ///////////////////////////////////////////// + rcode = gtEncode(MSG_CHALLENGE_TYPE, MSG_CHALLENGE, buffer, sizeof(buffer), m_loginDlg.m_profile, (LPCSTR)m_challenge); + ASSERT(rcode != -1); + gt2Send(m_connection, (const unsigned char*)buffer, rcode, GT2True); + m_challenged = TRUE; + } + } + else if(m_state == HOST_CONNECTED) + { + m_waitingDlg.EndDialog(IDOK); + m_state = RACING; + } + else if(m_state == HOST_ERROR) + { + MessageBox("Error setting up hosting"); + m_waitingDlg.EndDialog(IDCANCEL); + } + else if(m_state == JOIN_CONNECTED) + { + if(m_waitingDlg.m_hWnd && m_waitingDlg.IsWindowEnabled()) + m_waitingDlg.EndDialog(IDOK); + } + else if(m_state == JOIN_ERROR) + { + MessageBox("Error joining a game"); + m_waitingDlg.EndDialog(IDCANCEL); + } + + thinking = FALSE; + } + + if(m_state == LOGGED_OUT) + { + if(!Dlg->SetupMatch()) + { + MessageBox("Error setting up the match"); + Logout(); + } + } + + // Are we racing? + ///////////////// + if(m_racing) + { + // Did we finish? + ///////////////// + if(m_localTime) + { + // Did we both finish? + ////////////////////// + if(m_remoteTime) + { + // Done racing. + /////////////// + m_racing = FALSE; + + // Show the times. + ////////////////// + CString message; + if(m_localTime < m_remoteTime) + message.Format("You won!\n%0.3fs to %0.3fs", m_localTime / 1000.0, m_remoteTime / 1000.0); + else if(m_remoteTime < m_localTime) + message.Format("You lost!\n%0.3fs to %0.3fs", m_localTime / 1000.0, m_remoteTime / 1000.0); + else + message.Format("You tied!\n%0.3fs", m_localTime / 1000.0); + MessageBox(message); + + m_localProgress.SetPos(0); + m_remoteProgress.SetPos(0); + + // Report the stats. + //////////////////// + if(m_hosting) + ReportStats(); + + m_event = EVENT_NONE; + + UpdateStats(); + } + } + else + { + // Let our opponent know how far we are. + //////////////////////////////////////// + rcode = gtEncode(MSG_PROGRESS_TYPE, MSG_PROGRESS, buffer, sizeof(buffer), m_numSteps); + ASSERT(rcode != -1); + gt2Send(m_connection, (const GT2Byte*)buffer, rcode, GT2False); + } + } + } + else if(nIDEvent == TIMER_COUNTDOWN) + { + m_countdown--; + if(m_countdown <= 0) + KillTimer(TIMER_COUNTDOWN); + + if(m_countdown < 0) + return; + + Countdown(); + if(!m_countdown) + StartRace(); + } + + CDialog::OnTimer(nIDEvent); +} + +void CMultiTrackDlg::Logout() +{ + OnLogout(); +} + +void CMultiTrackDlg::Countdown() +{ + if(m_hosting) + { + int rcode; + char message[32]; + rcode = gtEncode(MSG_COUNTDOWN_TYPE, MSG_COUNTDOWN, message, sizeof(message), m_event, m_countdown); + ASSERT(rcode != -1); + gt2Send(m_connection, (const GT2Byte*)message, rcode, GT2True); + } + + if(m_countdown) + { + UpdateData(); + + CString strEvent; + if(m_event == EVENT_50) + strEvent = "50m"; + else if(m_event == EVENT_100) + strEvent = "100m"; + else if(m_event == EVENT_200) + strEvent = "200m"; + m_info.Format("%s starts in %ds", strEvent, m_countdown); + + UpdateData(FALSE); + } +} + +void CMultiTrackDlg::OnStart50() +{ + // Start the countdown. + /////////////////////// + m_countdown = COUNTDOWN_START; + SetTimer(TIMER_COUNTDOWN, 1000, NULL); + m_event = EVENT_50; + Countdown(); +} + +void CMultiTrackDlg::OnStart100() +{ + // Start the countdown. + /////////////////////// + m_countdown = COUNTDOWN_START; + SetTimer(TIMER_COUNTDOWN, 1000, NULL); + m_event = EVENT_100; + Countdown(); +} + +void CMultiTrackDlg::OnStart200() +{ + // Start the countdown. + /////////////////////// + m_countdown = COUNTDOWN_START; + SetTimer(TIMER_COUNTDOWN, 1000, NULL); + m_event = EVENT_200; + Countdown(); +} + +BOOL CMultiTrackDlg::PreTranslateMessage(MSG* pMsg) +{ + if(pMsg->message == WM_KEYDOWN) + { + int nChar = pMsg->wParam; + if((nChar == 'Z') || (nChar == 'X')) + { + if((pMsg->lParam & 0xFFFF) == 1) + { + CString str; + BOOL stepped = FALSE; + + if((nChar == 'Z') && (m_step != LEFT)) + { + m_step = LEFT; + m_numSteps++; + stepped = TRUE; + } + else if ((nChar == 'X') && (m_step != RIGHT)) + { + m_step = RIGHT; + m_numSteps++; + stepped = TRUE; + } + + if(stepped && m_racing) + { + m_localProgress.SetPos(m_numSteps); + if(m_numSteps == m_totalSteps) + { + m_localTime = (GetTickCount() - m_start); + str.Format("%0.3fs\n", m_localTime / 1000.0); + OutputDebugString(str); + //MessageBox(str); + + UpdateData(); + + m_info = "Race Complete"; + + // Let them know we finished. + ///////////////////////////// + char buffer[32]; + int rcode; + rcode = gtEncode(MSG_END_RACE_TYPE, MSG_END_RACE, buffer, sizeof(buffer), m_localTime); + ASSERT(rcode != -1); + gt2Send(m_connection, (const GT2Byte*)buffer, rcode, GT2True); + + UpdateData(FALSE); + } + } + } + + return TRUE; + } + } + + return CDialog::PreTranslateMessage(pMsg); +} + +void CMultiTrackDlg::StartRace() +{ + if(m_hosting) + { + int rcode; + char buffer[32]; + rcode = gtEncode(MSG_START_RACE_TYPE, MSG_START_RACE, buffer, sizeof(buffer)); + ASSERT(rcode != -1); + gt2Send(m_connection, (const GT2Byte*)buffer, rcode, GT2True); + } + + m_localTime = 0; + m_remoteTime = 0; + m_racing = TRUE; + m_numSteps = 0; + m_step = NONE; + m_racing = TRUE; + m_start = GetTickCount(); + if(m_event == EVENT_50) + m_totalSteps = RACE_STEPS_50; + else if(m_event == EVENT_100) + m_totalSteps = RACE_STEPS_100; + else if(m_event == EVENT_200) + m_totalSteps = RACE_STEPS_200; + m_localProgress.SetRange(0, m_totalSteps); + m_localProgress.SetPos(0); + m_remoteProgress.SetRange(0, m_totalSteps); + m_remoteProgress.SetPos(0); + + UpdateData(); + m_info.Format("GO!"); + UpdateData(FALSE); +} + +void CMultiTrackDlg::ReportStats() +{ + char response[33]; + + // Game. + //////// + NewGame(1); + BucketStringOp(NULL, "hostname", bo_set, (char *)(LPCSTR)m_loginDlg.m_nick, bl_server, 0); + BucketIntOp(NULL, "event", bo_set, m_event, bl_server, 0); + + // Local player (host). + /////////////////////// + NewPlayer(NULL, 0, (char *)(LPCSTR)m_loginDlg.m_nick); + BucketIntOp(NULL, "time", bo_set, m_localTime, bl_player, 0); + BucketIntOp(NULL, "pid", bo_set, m_loginDlg.m_profile, bl_player, 0); + GenerateAuth((char *)(LPCSTR)m_challenge, (char *)(LPCSTR)m_loginDlg.m_password, response); + BucketStringOp(NULL, "auth", bo_set, response, bl_player, 0); + + // Remote player (joined). + ////////////////////////// + NewPlayer(NULL, 1, (char *)(LPCSTR)m_remoteNick); + BucketIntOp(NULL, "time", bo_set, m_remoteTime, bl_player, 1); + BucketIntOp(NULL, "pid", bo_set, m_remoteProfile, bl_player, 1); + BucketStringOp(NULL, "auth", bo_set, (char *)(LPCSTR)m_remoteResponse, bl_player, 1); + + SendGameSnapShot(NULL, NULL, SNAP_FINAL); + FreeGame(NULL); +} diff --git a/code/gamespy/gstats/multiTrack/multiTrackDlg.h b/code/gamespy/gstats/multiTrack/multiTrackDlg.h new file mode 100644 index 00000000..af84f899 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/multiTrackDlg.h @@ -0,0 +1,135 @@ +// multiTrackDlg.h : header file +// + +#if !defined(AFX_MULTITRACKDLG_H__4B977C2B_DD72_4B3A_B1F9_E95D26F0C328__INCLUDED_) +#define AFX_MULTITRACKDLG_H__4B977C2B_DD72_4B3A_B1F9_E95D26F0C328__INCLUDED_ + +#include "LoginDlg.h" +#include "HostOrJoinDlg.h" +#include "..\..\GT2\gt2.h" // Added by ClassView +#include "WaitingDlg.h" // Added by ClassView + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +///////////////////////////////////////////////////////////////////////////// +// CMultiTrackDlg dialog + +#define LOGGED_OUT 1 +#define SETTING_UP 2 +#define RACING 3 + +#define HOST_LISTENING 11 +#define HOST_CHALLENGING 12 +#define HOST_CONNECTED 13 +#define HOST_ERROR 14 + +#define JOIN_CONNECTING 21 +#define JOIN_WAITING 22 +#define JOIN_CONNECTED 23 +#define JOIN_ERROR 24 + +#define NONE -1 +#define LEFT 0 +#define RIGHT 1 + +#define RACE_STEPS_50 60 +#define RACE_STEPS_100 120 +#define RACE_STEPS_200 240 + +#define EVENT_NONE 0 +#define EVENT_50 1 +#define EVENT_100 2 +#define EVENT_200 3 + +class CMultiTrackDlg : public CDialog +{ +// Construction +public: + void UpdateRatingsDisplay(); + int m_localRatings[4]; + int m_remoteRatings[4]; + void UpdateStats(); + CString m_remoteNick; + void ReportStats(); + DWORD m_remoteTime; + DWORD m_localTime; + void StartRace(); + int m_totalSteps; + int m_event; + int m_step; + int m_numSteps; + DWORD m_start; + BOOL m_racing; + void Countdown(); + int m_countdown; + BOOL m_challenged; + void Logout(); + CWaitingDlg m_waitingDlg; + CString m_remoteResponse; + int m_state; + CLoginDlg m_loginDlg; + CHostOrJoinDlg m_hostOrJoinDlg; + BOOL m_hosting; + CString m_challenge; + int m_remoteProfile; + + GT2Connection m_connection; + GT2Socket m_socket; + + BOOL SetupJoining(); + BOOL SetupHosting(); + BOOL SetupMatch(); + CMultiTrackDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CMultiTrackDlg) + enum { IDD = IDD_MULTITRACK_DIALOG }; + CButton m_start100; + CButton m_start200; + CProgressCtrl m_remoteProgress; + CProgressCtrl m_localProgress; + CButton m_start50; + CString m_info; + CString m_localInfo100; + CString m_localInfo200; + CString m_localInfo50; + CString m_localInfoOverall; + CString m_remoteInfo100; + CString m_remoteInfo200; + CString m_remoteInfo50; + CString m_remoteInfoOverall; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CMultiTrackDlg) + public: + virtual BOOL PreTranslateMessage(MSG* pMsg); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + HICON m_hIcon; + + // Generated message map functions + //{{AFX_MSG(CMultiTrackDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + afx_msg void OnLogout(); + afx_msg void OnDestroy(); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnStart50(); + afx_msg void OnStart100(); + afx_msg void OnStart200(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_MULTITRACKDLG_H__4B977C2B_DD72_4B3A_B1F9_E95D26F0C328__INCLUDED_) diff --git a/code/gamespy/gstats/multiTrack/res/multiTrack.ico b/code/gamespy/gstats/multiTrack/res/multiTrack.ico new file mode 100644 index 00000000..7eef0bcb Binary files /dev/null and b/code/gamespy/gstats/multiTrack/res/multiTrack.ico differ diff --git a/code/gamespy/gstats/multiTrack/res/multiTrack.rc2 b/code/gamespy/gstats/multiTrack/res/multiTrack.rc2 new file mode 100644 index 00000000..94875273 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/res/multiTrack.rc2 @@ -0,0 +1,13 @@ +// +// MULTITRACK.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/gstats/multiTrack/resource.h b/code/gamespy/gstats/multiTrack/resource.h new file mode 100644 index 00000000..96e71e41 --- /dev/null +++ b/code/gamespy/gstats/multiTrack/resource.h @@ -0,0 +1,41 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by multiTrack.rc +// +#define IDD_MULTITRACK_DIALOG 102 +#define IDC_EMAIL 103 +#define IDC_NICK 104 +#define IDC_PASSWORD 105 +#define IDR_MAINFRAME 128 +#define IDD_LOGIN 129 +#define IDD_HOST_OR_JOIN 130 +#define IDD_WAITING 132 +#define IDC_HOST 1000 +#define IDC_JOIN 1001 +#define IDC_JOIN_ADDRESS 1002 +#define IDC_LOGOUT 1004 +#define IDC_START_50 1005 +#define IDC_INFO 1006 +#define IDC_LOCAL_PROGRESS 1007 +#define IDC_REMOTE_PROGRESS 1008 +#define IDC_START_100 1009 +#define IDC_START_200 1010 +#define IDC_LOCAL_INFO_OVERALL 1011 +#define IDC_LOCAL_INFO_50 1012 +#define IDC_LOCAL_INFO_100 1013 +#define IDC_LOCAL_INFO_200 1014 +#define IDC_REMOTE_INFO_100 1015 +#define IDC_REMOTE_INFO_50 1016 +#define IDC_REMOTE_INFO_200 1017 +#define IDC_REMOTE_INFO_OVERALL 1018 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 133 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1019 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/gamespy/gstats/track/LoginDlg.cpp b/code/gamespy/gstats/track/LoginDlg.cpp new file mode 100644 index 00000000..c465d962 --- /dev/null +++ b/code/gamespy/gstats/track/LoginDlg.cpp @@ -0,0 +1,174 @@ +// LoginDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "track.h" +#include "LoginDlg.h" + +#include "../../gp/gp.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + + +CLoginDlg::CLoginDlg(CWnd* pParent /*=NULL*/) + : CDialog(CLoginDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CLoginDlg) + m_email = _T(""); + m_nick = _T(""); + m_password = _T(""); + //}}AFX_DATA_INIT +} + + +void CLoginDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CLoginDlg) + DDX_Text(pDX, IDC_EMAIL, m_email); + DDX_Text(pDX, IDC_NICK, m_nick); + DDX_Text(pDX, IDC_PASSWORD, m_password); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CLoginDlg, CDialog) + //{{AFX_MSG_MAP(CLoginDlg) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg message handlers + +GPResult checkResult; +int checkProfile; + +void CheckUserCallback(GPConnection * connection, void * _arg, void * param) +{ + GPCheckResponseArg * arg = (GPCheckResponseArg *)_arg; + + checkResult = arg->result; + checkProfile = arg->profile; +} + +void CLoginDlg::OnOK() +{ + GPConnection connection; + HCURSOR hourglass; + HCURSOR lastCursor; + GPResult result; + + UpdateData(); + + // CHECK FOR NO ACCOUNT INFO + //////////////////////////// + if(m_email.IsEmpty() || m_nick.IsEmpty() || m_password.IsEmpty()) + { + MessageBox("Please fill in all the account information."); + return; + } + + // INITIALIZE GP + //////////////// + if(gpInitialize(&connection, 488, 0, GP_PARTNERID_GAMESPY) != GP_NO_ERROR) + { + MessageBox("Error initializing the login system."); + return; + } + + // wait cursor on + ///////////////// + hourglass = LoadCursor(NULL, IDC_WAIT); + if(hourglass) + lastCursor = SetCursor(hourglass); + + // CHECK FOR THE ACCOUNT SPECIFIED + ////////////////////////////////// + result = gpCheckUser(&connection, m_nick, m_email, m_password, GP_BLOCKING, CheckUserCallback, NULL); + + // wait cursor off + ////////////////// + if(hourglass) + SetCursor(lastCursor); + + // DESTROY THE GP OBJECT + //////////////////////// + gpDestroy(&connection); + + // CHECK FOR AN ERROR + ///////////////////// + if(result != GP_NO_ERROR) + { + MessageBox("Error verifying the account."); + return; + } + + // CHECK THE RESULT + /////////////////// + if(checkResult != GP_NO_ERROR) + { + if(checkResult == GP_CHECK_BAD_EMAIL) + MessageBox("Invalid e-mail."); + else if(checkResult == GP_CHECK_BAD_NICK) + MessageBox("Invalid nick."); + else if(checkResult == GP_CHECK_BAD_PASSWORD) + MessageBox("Invalid password."); + else + MessageBox("Error verifying the account."); + return; + } + + // save the login info for next time + //////////////////////////////////// + FILE * file; + file = fopen("login.txt", "wt"); + if(file) + { + fprintf(file, "%s\n%s\n%s", m_email, m_nick, m_password); + fclose(file); + } + + // STORE THE PROFILE ID + /////////////////////// + m_profile = checkProfile; + + CDialog::OnOK(); +} + +BOOL CLoginDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // load login info + ////////////////// + FILE * file; + file = fopen("login.txt", "rt"); + if(file) + { + char buffer[512]; + + if(fgets(buffer, sizeof(buffer), file)) + m_email = buffer; + if(fgets(buffer, sizeof(buffer), file)) + m_nick = buffer; + if(fgets(buffer, sizeof(buffer), file)) + m_password = buffer; + + fclose(file); + + m_email.Remove('\n'); + m_nick.Remove('\n'); + m_password.Remove('\n'); + } + + UpdateData(FALSE); + + return TRUE; +} diff --git a/code/gamespy/gstats/track/LoginDlg.h b/code/gamespy/gstats/track/LoginDlg.h new file mode 100644 index 00000000..8d98d134 --- /dev/null +++ b/code/gamespy/gstats/track/LoginDlg.h @@ -0,0 +1,50 @@ +#if !defined(AFX_LOGINDLG_H__6C88CC46_47E9_420F_BC33_BA7F28711BAD__INCLUDED_) +#define AFX_LOGINDLG_H__6C88CC46_47E9_420F_BC33_BA7F28711BAD__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// LoginDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CLoginDlg dialog + +class CLoginDlg : public CDialog +{ +// Construction +public: + int m_profile; + CLoginDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CLoginDlg) + enum { IDD = IDD_LOGIN }; + CString m_email; + CString m_nick; + CString m_password; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CLoginDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CLoginDlg) + virtual void OnOK(); + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_LOGINDLG_H__6C88CC46_47E9_420F_BC33_BA7F28711BAD__INCLUDED_) diff --git a/code/gamespy/gstats/track/StdAfx.cpp b/code/gamespy/gstats/track/StdAfx.cpp new file mode 100644 index 00000000..5a50dd72 --- /dev/null +++ b/code/gamespy/gstats/track/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// track.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/code/gamespy/gstats/track/StdAfx.h b/code/gamespy/gstats/track/StdAfx.h new file mode 100644 index 00000000..7b0d7191 --- /dev/null +++ b/code/gamespy/gstats/track/StdAfx.h @@ -0,0 +1,26 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__1CD5EB62_03A4_40FF_8CFA_8AF283DA824A__INCLUDED_) +#define AFX_STDAFX_H__1CD5EB62_03A4_40FF_8CFA_8AF283DA824A__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__1CD5EB62_03A4_40FF_8CFA_8AF283DA824A__INCLUDED_) diff --git a/code/gamespy/gstats/track/res/track.ico b/code/gamespy/gstats/track/res/track.ico new file mode 100644 index 00000000..7eef0bcb Binary files /dev/null and b/code/gamespy/gstats/track/res/track.ico differ diff --git a/code/gamespy/gstats/track/res/track.rc2 b/code/gamespy/gstats/track/res/track.rc2 new file mode 100644 index 00000000..e962277e --- /dev/null +++ b/code/gamespy/gstats/track/res/track.rc2 @@ -0,0 +1,13 @@ +// +// TRACK.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/gstats/track/resource.h b/code/gamespy/gstats/track/resource.h new file mode 100644 index 00000000..936c3154 --- /dev/null +++ b/code/gamespy/gstats/track/resource.h @@ -0,0 +1,34 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by track.rc +// +#define IDC_EMAIL 101 +#define IDD_TRACK_DIALOG 102 +#define IDC_NICK 102 +#define IDC_PASSWORD 103 +#define IDR_MAINFRAME 128 +#define IDD_LOGIN 129 +#define IDC_LOGOUT 1003 +#define IDC_PROGRESS 1005 +#define IDC_INFO 1006 +#define IDC_START_100 1007 +#define IDC_START_50 1008 +#define IDC_START_200 1009 +#define IDC_RANDOM_STATS 1010 +#define IDC_BEST_50 1011 +#define IDC_BEST_100 1012 +#define IDC_BEST_200 1013 +#define IDC_TOP_50 1014 +#define IDC_TOP_100 1015 +#define IDC_TOP_200 1016 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 130 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1012 +#define _APS_NEXT_SYMED_VALUE 104 +#endif +#endif diff --git a/code/gamespy/gstats/track/track.cpp b/code/gamespy/gstats/track/track.cpp new file mode 100644 index 00000000..71d228d2 --- /dev/null +++ b/code/gamespy/gstats/track/track.cpp @@ -0,0 +1,74 @@ +// track.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "track.h" +#include "trackDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CTrackApp + +BEGIN_MESSAGE_MAP(CTrackApp, CWinApp) + //{{AFX_MSG_MAP(CTrackApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CTrackApp construction + +CTrackApp::CTrackApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CTrackApp object + +CTrackApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CTrackApp initialization + +BOOL CTrackApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. +/* +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif +*/ + CTrackDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + // TODO: Place code here to handle when the dialog is + // dismissed with OK + } + else if (nResponse == IDCANCEL) + { + // TODO: Place code here to handle when the dialog is + // dismissed with Cancel + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/code/gamespy/gstats/track/track.h b/code/gamespy/gstats/track/track.h new file mode 100644 index 00000000..721cf157 --- /dev/null +++ b/code/gamespy/gstats/track/track.h @@ -0,0 +1,49 @@ +// track.h : main header file for the TRACK application +// + +#if !defined(AFX_TRACK_H__0DF04935_1FD6_4741_AEC5_5A40455C0C7F__INCLUDED_) +#define AFX_TRACK_H__0DF04935_1FD6_4741_AEC5_5A40455C0C7F__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CTrackApp: +// See track.cpp for the implementation of this class +// + +class CTrackApp : public CWinApp +{ +public: + CTrackApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CTrackApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CTrackApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_TRACK_H__0DF04935_1FD6_4741_AEC5_5A40455C0C7F__INCLUDED_) diff --git a/code/gamespy/gstats/track/track.rc b/code/gamespy/gstats/track/track.rc new file mode 100644 index 00000000..e89ea5da --- /dev/null +++ b/code/gamespy/gstats/track/track.rc @@ -0,0 +1,214 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\track.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\track.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_TRACK_DIALOG DIALOGEX 0, 0, 312, 76 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "track" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Exit",IDOK,260,7,50,14 + PUSHBUTTON "Logout",IDC_LOGOUT,260,23,50,14 + CONTROL "Progress1",IDC_PROGRESS,"msctls_progress32",PBS_SMOOTH | + WS_BORDER,15,50,80,14 + LTEXT "Info",IDC_INFO,100,55,60,8 + PUSHBUTTON "Start 100m",IDC_START_100,90,30,65,14 + PUSHBUTTON "Start 50m",IDC_START_50,15,30,65,14 + PUSHBUTTON "Start 200m",IDC_START_200,165,30,65,14 + LTEXT "88.888",IDC_BEST_50,55,10,23,8 + LTEXT "88.888",IDC_BEST_100,130,10,23,8 + LTEXT "88.888",IDC_BEST_200,205,10,23,8 + LTEXT "88.888",IDC_TOP_50,55,20,23,8 + LTEXT "88.888",IDC_TOP_100,130,20,23,8 + LTEXT "88.888",IDC_TOP_200,205,20,23,8 + LTEXT "Best Time:",IDC_STATIC,15,20,34,8 + LTEXT "Your Time:",IDC_STATIC,15,10,35,8 + LTEXT "Best Time:",IDC_STATIC,90,20,34,8 + LTEXT "Your Time:",IDC_STATIC,90,10,35,8 + LTEXT "Best Time:",IDC_STATIC,165,20,34,8 + LTEXT "Your Time:",IDC_STATIC,165,10,35,8 + LTEXT "Use Z && X to race",IDC_STATIC,165,55,75,8 +END + +IDD_LOGIN DIALOG DISCARDABLE 0, 0, 147, 76 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "GameSpy Login" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_EMAIL,51,8,84,12,ES_AUTOHSCROLL + EDITTEXT IDC_NICK,51,23,84,12,ES_AUTOHSCROLL + EDITTEXT IDC_PASSWORD,51,38,84,12,ES_PASSWORD | ES_AUTOHSCROLL + DEFPUSHBUTTON "Login",IDOK,15,55,50,14 + PUSHBUTTON "Cancel",IDCANCEL,80,55,50,14 + LTEXT "email:",IDC_STATIC,10,10,19,8 + LTEXT "nick:",IDC_STATIC,10,25,16,8 + LTEXT "password:",IDC_STATIC,10,40,33,8 +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "track MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "track\0" + VALUE "LegalCopyright", "Copyright (C) 2001\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "track.EXE\0" + VALUE "ProductName", "track Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_TRACK_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 305 + TOPMARGIN, 7 + BOTTOMMARGIN, 69 + END + + IDD_LOGIN, DIALOG + BEGIN + LEFTMARGIN, 7 + TOPMARGIN, 7 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\track.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/gamespy/gstats/track/trackDlg.cpp b/code/gamespy/gstats/track/trackDlg.cpp new file mode 100644 index 00000000..2fc44c42 --- /dev/null +++ b/code/gamespy/gstats/track/trackDlg.cpp @@ -0,0 +1,441 @@ +// trackDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "track.h" +#include "trackDlg.h" + +#include "../gstats.h" +#include "../../common/gsAvailable.h" +#include "../../ghttp/ghttp.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define TIMER_ONE_SECOND 100 +#define TIMER_THINK 101 + +///////////////////////////////////////////////////////////////////////////// +// CTrackDlg dialog + +CTrackDlg::CTrackDlg(CWnd* pParent /*=NULL*/) + : CDialog(CTrackDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CTrackDlg) + m_info = _T("Ready"); + m_best100 = _T(""); + m_best200 = _T(""); + m_best50 = _T(""); + m_top100 = _T(""); + m_top200 = _T(""); + m_top50 = _T(""); + //}}AFX_DATA_INIT + // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); +} + +void CTrackDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CTrackDlg) + DDX_Control(pDX, IDC_PROGRESS, m_progress); + DDX_Text(pDX, IDC_INFO, m_info); + DDX_Text(pDX, IDC_BEST_100, m_best100); + DDX_Text(pDX, IDC_BEST_200, m_best200); + DDX_Text(pDX, IDC_BEST_50, m_best50); + DDX_Text(pDX, IDC_TOP_100, m_top100); + DDX_Text(pDX, IDC_TOP_200, m_top200); + DDX_Text(pDX, IDC_TOP_50, m_top50); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CTrackDlg, CDialog) + //{{AFX_MSG_MAP(CTrackDlg) + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_BN_CLICKED(IDC_LOGOUT, OnLogout) + ON_WM_TIMER() + ON_BN_CLICKED(IDC_START_50, OnStart50) + ON_BN_CLICKED(IDC_START_100, OnStart100) + ON_BN_CLICKED(IDC_START_200, OnStart200) + ON_WM_DESTROY() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CTrackDlg message handlers + +CTrackDlg * Dlg; + +GHTTPBool PlayerBestTimesPageCompleted +( + GHTTPRequest request, + GHTTPResult result, + char * buffer, + GHTTPByteCount bufferLen, + void * param +) +{ + if(result == GHTTPSuccess) + { + int bestTimes[3]; + int rcode; + + // Scan out the best times. + /////////////////////////// + rcode = sscanf(buffer, "%d %d %d", &bestTimes[0], &bestTimes[1], &bestTimes[2]); + if(rcode == 3) + { + Dlg->UpdateData(); + Dlg->m_best50.Format("%0.3f", bestTimes[0] / 1000.0); + Dlg->m_best100.Format("%0.3f", bestTimes[1] / 1000.0); + Dlg->m_best200.Format("%0.3f", bestTimes[2] / 1000.0); + Dlg->UpdateData(FALSE); + } + } + + return GHTTPTrue; +} + +GHTTPBool TopTimePageCompleted +( + GHTTPRequest request, + GHTTPResult result, + char * buffer, + GHTTPByteCount bufferLen, + void * param +) +{ + if(result == GHTTPSuccess) + { + int time = atoi(buffer); + if(time) + { + CString str; + str.Format("%0.3f", time / 1000.0); + + int event = (int)param; + Dlg->UpdateData(); + if(event == 1) + Dlg->m_top50 = str; + else if(event == 2) + Dlg->m_top100 = str; + else if(event == 3) + Dlg->m_top200 = str; + Dlg->UpdateData(FALSE); + } + } + + return GHTTPTrue; +} + +void CTrackDlg::SetupUser() +{ + CString title; + title.Format("track - best time (%s - %s - %d)", m_loginDlg.m_email, m_loginDlg.m_nick, m_loginDlg.m_profile); + SetWindowText(title); + + CString url; + int event; + url.Format("http://sdkdev.gamespy.com/games/st_highscore/web/playertimes.asp?pid=%d", m_loginDlg.m_profile); + ghttpGet(url, GHTTPFalse, PlayerBestTimesPageCompleted, NULL); + for(event = 1 ; event <= 3 ; event++) + { + url.Format("http://sdkdev.gamespy.com/games/st_highscore/web/top_%d.txt", event); + ghttpGet(url, GHTTPFalse, TopTimePageCompleted, (void *)event); + } + + m_start = 0; + m_count = -1; + m_racing = FALSE; + m_event = EVENT_NONE; + m_numSteps = 0; + m_step = NONE; +} + +BOOL CTrackDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + int rcode; + CString str; + + SetIcon(m_hIcon, TRUE); + SetIcon(m_hIcon, FALSE); + + Dlg = this; + + // Use the development system. + ////////////////////////////// + strcpy(StatsServerHostname, "sdkdev.gamespy.com"); + + // Set the gamename and secret key. + /////////////////////////////////// + strcpy(gcd_gamename, "st_highscore"); + gcd_secret_key[0] = 'K'; + gcd_secret_key[1] = 'S'; + gcd_secret_key[2] = '3'; + gcd_secret_key[3] = 'p'; + gcd_secret_key[4] = '2'; + gcd_secret_key[5] = 'Q'; + + // Perform the availability check + ////////////////////////////////////// + GSIACResult aResult = GSIACWaiting; + GSIStartAvailableCheck(gcd_gamename); + while(aResult == GSIACWaiting) + { + aResult = GSIAvailableCheckThink(); + Sleep(50); + } + + // Init the connection to the backend. + ////////////////////////////////////// + rcode = InitStatsConnection(0); + if(rcode != GE_NOERROR) + { + str.Format("Failed to connect to the stats server (%d).", rcode); + MessageBox(str); + PostQuitMessage(1); + return TRUE; + } + + // Login the user (actually just verifying the login). + ////////////////////////////////////////////////////// + if(m_loginDlg.DoModal() != IDOK) + { + PostQuitMessage(1); + return TRUE; + } + + SetTimer(TIMER_ONE_SECOND, 1000, NULL); + SetTimer(TIMER_THINK, 50, NULL); + + SetupUser(); + + return TRUE; +} + +void CTrackDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); + + SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CDialog::OnPaint(); + } +} + +HCURSOR CTrackDlg::OnQueryDragIcon() +{ + return (HCURSOR) m_hIcon; +} + +void CTrackDlg::OnDestroy() +{ + CDialog::OnDestroy(); + + if(IsStatsConnected()) + CloseStatsConnection(); + + ghttpCleanup(); +} + +void CTrackDlg::OnLogout() +{ + ShowWindow(SW_HIDE); + if(m_loginDlg.DoModal() != IDOK) + PostQuitMessage(1); + SetupUser(); + ShowWindow(SW_SHOW); +} + +void CTrackDlg::OnTimer(UINT nIDEvent) +{ + if(nIDEvent == TIMER_ONE_SECOND) + { + if(m_count) + { + UpdateData(TRUE); + + if(m_count == 3) + m_info = "READY"; + else if(m_count == 2) + m_info = "SET"; + else if(m_count == 1) + m_info = "GO"; + + UpdateData(FALSE); + + if(m_count == 1) + { + m_numSteps = 0; + m_step = NONE; + m_racing = TRUE; + m_start = GetTickCount(); + } + + m_count--; + } + } + else if(nIDEvent == TIMER_THINK) + { + ghttpThink(); + } + + CDialog::OnTimer(nIDEvent); +} + +void CTrackDlg::ReportStats(DWORD time) +{ + char response[33]; + + NewGame(1); + BucketStringOp(NULL, "hostname", bo_set, (char *)(LPCSTR)m_loginDlg.m_nick, bl_server, 0); + BucketIntOp(NULL, "event", bo_set, m_event, bl_server, 0); + + NewPlayer(NULL, 0, (char *)(LPCSTR)m_loginDlg.m_nick); + BucketIntOp(NULL, "time", bo_set, time, bl_player, 0); + BucketIntOp(NULL, "pid", bo_set, m_loginDlg.m_profile, bl_player, 0); + GenerateAuth(GetChallenge(NULL), (char *)(LPCSTR)m_loginDlg.m_password, response); + BucketStringOp(NULL, "auth", bo_set, response, bl_player, 0); + + SendGameSnapShot(NULL, NULL, SNAP_FINAL); + FreeGame(NULL); +} + +BOOL CTrackDlg::PreTranslateMessage(MSG* pMsg) +{ + if(pMsg->message == WM_KEYDOWN) + { + int nChar = pMsg->wParam; + if((nChar == 'Z') || (nChar == 'X')) + { + if((pMsg->lParam & 0xFFFF) == 1) + { + CString str; + BOOL stepped = FALSE; + + if((nChar == 'Z') && (m_step != LEFT)) + { + m_step = LEFT; + m_numSteps++; + stepped = TRUE; + } + else if ((nChar == 'X') && (m_step != RIGHT)) + { + m_step = RIGHT; + m_numSteps++; + stepped = TRUE; + } + + if(stepped && m_racing) + { + m_progress.SetPos(m_numSteps); + if(m_numSteps == m_totalSteps) + { + DWORD diff; + + m_racing = FALSE; + diff = (GetTickCount() - m_start); + str.Format("%0.3fs\n", diff / 1000.0); + OutputDebugString(str); + MessageBox(str); + + UpdateData(); + + m_info = "DONE"; + + // Report the stats. + //////////////////// + ReportStats(diff); + + // Update best time(s) if needed. + ///////////////////////////////// + CString * topStr; + CString * bestStr; + DWORD topTime; + DWORD bestTime; + if(m_event == EVENT_50) + { + topStr = &m_top50; + bestStr = &m_best50; + } + else if(m_event == EVENT_100) + { + topStr = &m_top100; + bestStr = &m_best100; + } + else + { + topStr = &m_top200; + bestStr = &m_best200; + } + + topTime = (DWORD)(atof(*topStr) * 1000); + bestTime = (DWORD)(atof(*bestStr) * 1000); + if(!bestTime || (diff < bestTime)) + { + bestStr->Format("%0.3f", diff / 1000.0); + if(diff < topTime) + *topStr = *bestStr; + } + + UpdateData(FALSE); + + m_event = EVENT_NONE; + } + } + } + + return TRUE; + } + } + + return CDialog::PreTranslateMessage(pMsg); +} + +void CTrackDlg::OnStart50() +{ + m_count = 3; + m_totalSteps = RACE_STEPS_50; + m_progress.SetRange(0, m_totalSteps); + m_progress.SetPos(0); + m_event = EVENT_50; +} + +void CTrackDlg::OnStart100() +{ + m_count = 3; + m_totalSteps = RACE_STEPS_100; + m_progress.SetRange(0, m_totalSteps); + m_progress.SetPos(0); + m_event = EVENT_100; +} + +void CTrackDlg::OnStart200() +{ + m_count = 3; + m_totalSteps = RACE_STEPS_200; + m_progress.SetRange(0, m_totalSteps); + m_progress.SetPos(0); + m_event = EVENT_200; +} diff --git a/code/gamespy/gstats/track/trackDlg.h b/code/gamespy/gstats/track/trackDlg.h new file mode 100644 index 00000000..946821dc --- /dev/null +++ b/code/gamespy/gstats/track/trackDlg.h @@ -0,0 +1,90 @@ +// trackDlg.h : header file +// + +#if !defined(AFX_TRACKDLG_H__E16D2398_5E86_47A2_80DA_8F843E24C4A6__INCLUDED_) +#define AFX_TRACKDLG_H__E16D2398_5E86_47A2_80DA_8F843E24C4A6__INCLUDED_ + +#include "LoginDlg.h" + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +///////////////////////////////////////////////////////////////////////////// +// CTrackDlg dialog + +#define NONE -1 +#define LEFT 0 +#define RIGHT 1 + +#define RACE_STEPS_50 60 +#define RACE_STEPS_100 120 +#define RACE_STEPS_200 240 + +#define EVENT_NONE 0 +#define EVENT_50 1 +#define EVENT_100 2 +#define EVENT_200 3 + +class CTrackDlg : public CDialog +{ +// Construction +public: + void SetupUser(); + DWORD m_start; + int m_count; + int m_event; + BOOL m_racing; + int m_numSteps; + int m_step; + int m_totalSteps; + void ReportStats(DWORD time); + + CTrackDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CTrackDlg) + enum { IDD = IDD_TRACK_DIALOG }; + CProgressCtrl m_progress; + CString m_info; + CString m_best100; + CString m_best200; + CString m_best50; + CString m_top100; + CString m_top200; + CString m_top50; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CTrackDlg) + public: + virtual BOOL PreTranslateMessage(MSG* pMsg); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + + CLoginDlg m_loginDlg; + +// Implementation +protected: + HICON m_hIcon; + + // Generated message map functions + //{{AFX_MSG(CTrackDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + afx_msg void OnLogout(); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnStart50(); + afx_msg void OnStart100(); + afx_msg void OnStart200(); + afx_msg void OnDestroy(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_TRACKDLG_H__E16D2398_5E86_47A2_80DA_8F843E24C4A6__INCLUDED_) diff --git a/code/gamespy/gt2/changelog.txt b/code/gamespy/gt2/changelog.txt new file mode 100644 index 00000000..8d6dd70d --- /dev/null +++ b/code/gamespy/gt2/changelog.txt @@ -0,0 +1,144 @@ +Changelog for: GameSpy Transport SDK 2 +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 2.09.00 RMV RELEASE Released to Developer Site +12-07-2007 2.08.03 SAH OTHER Fixed VC6 projects missing dependencies +12-07-2007 2.08.02 SN FIX Added check to sample app for PS3 maximum send size +11-27-2007 2.08.01 SAH CLEANUP Moved extern "c" block below includes to prevent linker errors +08-06-2007 2.08.00 RMV RELEASE Released to Developer Site +07-11-2007 2.07.03 RMV FIX Fixed gt2testc Project files to get rid of Unicode warnings and fixed other compiler warnings +06-07-2007 2.07.02 SN RELEASE Released to Developer Site +04-16-2007 2.07.02 SN CLEANUP Put back a removed calling convention + 2.07.02 SN CLEANUP Added a check to the message handler to pass messages to unrecognized message callback + if a GT2Connection has not been complete. +01-16-2007 2.07.01 DES FEATURE Added X360 support +12-15-2006 2.07.00 MJW RELEASE Released to Developer Site +12-04-2006 2.06.78 SN FIX Added some checks to message size in test application for revolution +12-04-2006 2.06.77 SN FIX Changed the gti2MacToIP and gti2IpToMac functions to be public functions +11-10-2006 2.06.76 JR RELEASE Limited release +10-23-2006 2.06.76 DES RELEASE Limited release +10-05-2006 2.06.76 SAH FIX Updated MacOSX Makefile +09-28-2006 2.06.75 SAH FIX Fixed PS3 project to work with PS3 095.00x SDK; changed included libaries in linker input. +09-07-2006 2.06.74 DES FEATURE Added support for string arrays to gt2Encode + DES FIX Fixed potential to read past the end of a truncated buffer when decoding strings +08-24-2006 2.06.73 SAH FIX Fixed VC7 Project file +08-15-2006 2.06.72 SAH FIX Fixed VDP assertion in gt2main.c (call to gt2Send) +08-04-2006 2.06.71 SAH FIX Changed call in gt2proxy.c from ctime to gsiSecondsToString +08-02-2006 2.06.70 SAH RELEASE Releasing to developer site +07-31-2006 2.06.70 SAH FIX Fixed PS3 project file - added post-build step to create *.SELF for execution +07-25-2006 2.06.69 SAH FIX Fixed NITRO project, include for crt0.o now above others so it add correct file +07-24-2006 2.06.68 SAH FIX Removed #ifdef _PS3 for socket calls (changed platformSocket.h to typecast calls) +07-06-2006 2.06.67 SAH FIX Fixed PSP project file to not explicitly include the PSP linker file +06-30-2006 2.06.66 SAH FIX Fixed NITRO project & linker command file (to work with CW 2.0/NitroSDK 3.1) + SAH FIX Fixed Linux makefile +06-06-2006 2.06.65 MJ FEATURE Added Adhoc Support for PSP. +05-31-2006 2.06.64 SAH RELEASE Releasing to developer site + SAH FIX Fixed Linux makefile +05-30-2006 2.06.63 SAH FIX Fixed PS3 projects to work with PS3(084_001 SDK) +05-25-2006 2.06.62 SAH FIX Changed PSP project warning levels +05-24-2006 2.06.61 SAH FIX Added (socklen_t) typecast for PS3 warnings to gt2socket.c +05-19-2006 2.06.60 SAH FIX Added gsTestMain.c to nitro CodeWarrior project +05-19-2006 2.06.59 SAH FIX Added GS_STATIC_CALLBACK in front of 3 compare functions for __fastcall support +05-15-2006 2.06.58 SAH FIX Added "PS3 Release" configuration to project +05-05-2006 2.06.57 SAH FIX Fixed the gt2action.dsp project - needed to include a few encryption files for http +04-27-2006 2.06.56 MJ FEATURE Added gtEncodedMessageTypeSet +04-25-2006 2.06.55 SAH RELEASE Releasing to developer site +04-24-2006 2.06.55 SAH FIX Added function prototypes, fixed/added typecasts and Nitro project files +04-20-2006 2.06.54 SAH FIX Commented out unused variables, break; statements + FIX Commented out unncessary checks (if unsigned int > 0) +04-20-2006 2.06.53 SAH FIX Added _PS3 wrapper typecast for socket calls +04-14-2006 2.06.52 SAH FIX few more typecasts in gt2memcpy calls (forgot to do PC ones) +04-14-2006 2.06.51 SAH FIX Added some typecasts in gt2memcpy calls to get rid of warnings +01-27-2006 2.05.50 SN RELEASE Releasing to developer site +01-27-2006 2.05.50 SN FIX Updated test_main define for PSP + SN FIX PSP and PS2 only use messages smaller than 8K + SN OTHER Added psp prodg project and solution to sgv +01-26-2006 2.05.49 SN FIX Force gt2IPToHostInfo to return NULL for PSP since PSP doesn't have a gethostbyaddr +01-11-2006 2.05.49 SN FIX Added platform checks to socket error handlers + SN FIX Added visual studio .net project and solution +01-11-2006 2.05.48 SN FIX updated callling convention to only be WIN32 +12-20-2005 2.05.47 SN OTHER Cleaned up and added missing common code to projects if needed +12-20-2005 2.05.46 SN FIX Fixed VDP specific changes that caused messages to be truncated + Removed VDP checks in parts of code that previously had them +12-19-2005 2.05.45 SN FIX Added preprocessor directive around code creating a VDP socket + Changed buffer offset to use a constant +12-07-2005 2.05.44 DDW FEATURE Added Win64 build support +12-06-2005 2.05.43 SN FEATURE Added Xbox VDP support +11-17-2005 2.05.42 DES FIX Updated Nitro Makefile. +11-14-2005 2.05.41 DES FIX Updated the OSX Makefile. +10-11-2005 2.05.40 SN FIX Updated gt2test dsp to have new common code files gsassert and gsmemory +09-21-2005 2.05.39 DES FEATURE Updated DS support + DES FIX Updated to handle additional UDP sending errors + DES FIX Updated OSX Makefile +09-12-2005 2.05.38 SN FIX Fixed unhandled socket error code +07-28-2005 2.05.37 SN RELEASE Releasing to developer site. +07-28-2005 2.05.37 SN FIX Changed IP address to use local host for gt2testc +06-03-2005 2.05.36 SN RELEASE Releasing to developer site. +05-05-2005 2.05.36 BED FIX Updated project files to use new common folder. +05-04-2005 2.05.35 SN FIX Changes for XBox platform +05-03-2005 2.05.34 SN FIX Removed deprecated MFC code for VS .NET project + And fixed implicit cast performed in a callback + SN OTHER Created Visual Studio .NET projects +04-28-2005 2.05.33 SN RELEASE Releasing to developer site. +04-27-2005 2.05.33 DES RELEASE Limited release to Nintendo DS developers. +04-25-2005 2.05.33 DES FIX Limit the Nitro to a 1500 byte message buffer. + DES CLEANUP Disable Win32 linker warning. +04-19-2005 2.05.32 DES FEATURE Added the ability to send raw UDP datagrams through a GT2Socket. + This feature can also be used to send broadcast datagrams. +04-15-2005 2.05.31 DES FIX XBox fix. +04-08-2005 2.05.30 DES FEATURE Changes for XBox support. +04-04-2005 2.05.29 SN RELEASE Release to developer site. +03-14-2005 2.05.29 DES FIX Fixed buffering multiple copies of incoming reliable messages + FIX Fixed detection of incoming reliable message buffer overflow +03-14-2005 2.05.28 DES FEATURE Nintendo DS support +01-27-2003 2.05.27 DES FIX Fixed custom SN sendto and moved it to nonport +01-03-2005 2.05.26 SN FIX Added const qualifiers to unmodified formal function parameters +09-23-2004 2.05.25 DES FIX Changed IPs that were using unsigned long to unsigned int. + DES FIX Changed times that were using unsigned long to gsi_time. + DES FIX Sockets now correctly bind to a provided local IP when created. +09-17-2004 2.05.24 DES FEATURE Added functions for confirming receipt of reliable messages. +09-16-2004 2.05.23 SN RELEASE Releasing to developer site. +08-27-2004 2.05.23 DES CLEANUP General Unicode cleanup + DES CLEANUP Removed MacOS style includes + DES CLEANUP Updated Win32 project configurations + DES CLEANUP Fixed warnings under OSX + DES FEATURE Added OSX Makefile +08-25-2004 2.05.22 DES FEATURE Added OSX makefile +08-16-2004 2.05.22 SN FEATURE Updated gt2hostmig and gt2nat to use QR2 instead of legacy Q & R +08-05-2004 2.05.21 SN RELEASE Releasing to developer site. +07-20-2004 2.05.21 SN FIX Updated code with explicit casts to remove implicit cast error + when compiling at highest level and warnings treated as errors. +06-18-2004 2.05.20 BED RELEASE Releasing to developer site. +06-18-2004 2.05.20 DES FIX Checking for trying to connect to an invalid IP range. +10-21-2003 2.05.19 BED RELEASE Releasing to developer site. (UNIQUE NICK AND UNICODE SUPPORT) +10-21-2003 2.05.19 BED FIX Added #ifdef because SN_SYSTEMS doesn't support EHOSTDOWN +10-09-2003 2.05.18 BED FIX Switched to gsi_time type instead of unsinged long for PS2 compatibility +07-25-2003 2.05.17 DES FIX Ignoring EHOSTDOWN sendto error. +07-24-2003 2.05.16 DES RELEASE Releasing to developer site. +07-18-2003 2.05.16 BED FEATURE Added CodeWarrior (PS2) sample project file. + BED CLEANUP General cleanup to remove CodeWarrior warnings. +07-17-2003 2.05.15 DES CLEANUP Cleaned up the PS2 Makefile, it now uses Makefile.commmon. +07-16-2003 2.05.14 DES FIX Changed a few __mips64 checks to _PS2 checks. + BED FEATURE Added ProDG sample project files. +07-11-2003 2.05.13 DES RELEASE Releasing to developer site. +05-12-2003 2.05.13 DES FIX Fixed a crashing bug that could occur on socket errors. +05-09-2003 2.05.12 DES CLEANUP Removed Dreamcast support. +03-24-2003 2.05.11 DES FIX Fixed assumption that messages passed to callbacks were always NUL terminated. +03-03-2003 2.05.10 DES CLEANUP General cleanup to remove warnings. +02-05-2003 2.05.09 DES RELEASE Releasing to developer site. +02-05-2003 2.05.09 DES CLEANUP Switched to use the common code CanReceiveOnSocket and CanSendOnSocket. +12-19-2002 2.05.08 DES RELEASE Releasing to developer site. +12-19-2002 2.05.08 DES CLEANUP Removed assert.h includes. +12-16-2002 2.05.07 DES OTHER Moved handling of SN Systems lack of support for gethostbyaddr() to nonport. + CLEANUP Removed call to GOAClearSocketError. +12-13-2002 2.05.06 DES FEATURE Added PS2 eenet stack support. +11-22-2002 2.05.05 DES RELEASE Releasing to developer site. +11-20-2002 2.05.05 DES CLEANUP Cleaned up code to remove PS2 compiler warnings. +11-18-2002 2.05.04 DES RELEASE Release to developer site. +11-18-2002 2.05.04 DES FIX Fixed alignment bug in gtEncodedMessageType. +11-14-2002 2.05.03 DES OTHER Removed BLOCKING_SOCKETS define from PS2 makefile, leftover from GT +11-14-2002 2.05.02 DES FIX Fixed gt2testc to stop sending messages at the correct time +11-13-2002 2.05.01 DES FIX Resends an ack if a duplicate message is received +09-25-2002 2.05.00 DDW OTHER Changelog started diff --git a/code/gamespy/gt2/gt2.h b/code/gamespy/gt2/gt2.h new file mode 100644 index 00000000..5ef96914 --- /dev/null +++ b/code/gamespy/gt2/gt2.h @@ -0,0 +1,664 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/**************************** +** GameSpy Transport SDK 2 ** +****************************/ + +/* +** see "configurable defines" in gt2Main.h for certain performance settings that can be changed +*/ + +#ifndef _GT2_H_ +#define _GT2_H_ + +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/********** +** TYPES ** +**********/ + +// boolean +typedef int GT2Bool; +#define GT2False 0 +#define GT2True 1 + +// a byte +typedef unsigned char GT2Byte; + +// a handle to a socket object (can be used to accept connections and initiate connections) +typedef struct GTI2Socket * GT2Socket; + +// a handle to an object representing a connection to a specific IP and port +// the local endpoint is a GT2Socket +typedef struct GTI2Connection * GT2Connection; + +// the id of a reliably sent message +// unreliable messages don't have ids +typedef unsigned short GT2MessageID; + +// the result of a GT2 operation +// check individual function definitions to see possible results +// TODO: list possible results wherever this is used +typedef enum +{ + GT2Success, // success + // errors: + GT2OutOfMemory, // ran out of memory + GT2Rejected, // attempt rejected + GT2NetworkError, // networking error (could be local or remote) + GT2AddressError, // invalid or unreachable address + GT2DuplicateAddress, // a connection was attempted to an address that already has a connection on the socket + GT2TimedOut, // time out reached + GT2NegotiationError, // there was an error negotiating with the remote side + GT2InvalidConnection, // the connection didn't exist + GT2InvalidMessage, // used for vdp reliable messages containing voice data, no voice data in reliable messages + GT2SendFailed // the send failed, +} GT2Result; + +// possible states for any GT2Connection +typedef enum +{ + GT2Connecting, // negotiating the connection + GT2Connected, // the connection is active + GT2Closing, // the connection is being closed + GT2Closed // the connection has been closed and can no longer be used +} GT2ConnectionState; + +// The cause of the connection being closed. +typedef enum +{ + GT2LocalClose, // The connection was closed with gt2CloseConnection. + GT2RemoteClose, // The connection was closed remotely. + // errors: + GT2CommunicationError, // An invalid message was received (it was either unexpected or wrongly formatted). + GT2SocketError, // An error with the socket forced the connection to close. + GT2NotEnoughMemory // There wasn't enough memory to store an incoming or outgoing message. +} GT2CloseReason; + +/************ +** GLOBALS ** +************/ + +// The challenge key is a 32 character string +// that is used in the authentication process. +// The key can be set before GT2 is used so +// that the key will be application-specific. +extern char GT2ChallengeKey[33]; + +/********************* +** SOCKET CALLBACKS ** +*********************/ + +// this callback gets called when there was is an error that forces a socket to close +// all connections that use this socket are terminated, and their gt2CloseCallback callbacks +// will be called before this callback is called (with the reason set to GT2SocketError). +// the socket cannot be used again after this callback returns +typedef void (* gt2SocketErrorCallback) +( + GT2Socket socket +); + +/********************* +** SOCKET FUNCTIONS ** +*********************/ + +// creates a local socket +// if the IP of the local address is 0, then any/all ips will be bound. +// if the port of the local address is 0, then a port will be assigned. +// if either buffer sizes is set to 0, a default value will be used (currently 64K for PC, 4k for Xbox). +// the buffer needs to be able to hold all messages waiting for confirmation of delivery, +// and it needs to hold any messages that arrive out of order. if either buffer runs out +// of space the connection will be dropped. +GT2Result gt2CreateSocket +( + GT2Socket * socket, // if the result is GT2Success, the socket object handle will be stored at this address + const char * localAddress, // the local address to bind to + int outgoingBufferSize, // size of per-connection buffer where sent messages waiting to be confirmed are held, use 0 for default + int incomingBufferSize, // size of per-connection buffer where out-of-order received messages are held, use 0 for default + gt2SocketErrorCallback callback // a callback that is called if there is an error with the socket +); + +// AdHoc Sockets use MAC address instead of IP address. +GT2Result gt2CreateAdHocSocket +( + GT2Socket * socket, // if the result is GT2Success, the socket object handle will be stored at this address + const char * localAddress, // the local address to bind to + int outgoingBufferSize, // size of per-connection buffer where sent messages waiting to be confirmed are held, use 0 for default + int incomingBufferSize, // size of per-connection buffer where out-of-order received messages are held, use 0 for default + gt2SocketErrorCallback callback // a callback that is called if there is an error with the socket +); + +#ifdef _XBOX +// creates a local VDP socket on the Xbox platform +// if the IP of the local address is 0, then any/all ips will be bound. +// if the port of the local address is 0, then a port will be assigned. +// if either buffer sizes is set to 0, a default value will be used (currently 4K). +// the buffer needs to be able to hold all messages waiting for confirmation of delivery, +// and it needs to hold any messages that arrive out of order. if either buffer runs out +// of space the connection will be dropped. +GT2Result gt2CreateVDPSocket +( + GT2Socket * socket, // if the result is GT2Success, the socket object handle will be stored at this address + const char * localAddress, // the local address to bind to + int outgoingBufferSize, // size of per-connection buffer where sent messages waiting to be confirmed are held, use 0 for default + int incomingBufferSize, // size of per-connection buffer where out-of-order received messages are held, use 0 for default + gt2SocketErrorCallback callback // a callback that is called if there is an error with the socket +); +#endif + +// closes a local socket. +// all existing connections will be hard closed, as if gt2CloseAllConnectionsHard was +// called for this socket. all connections send a close message to the remote side, +// and any closed callbacks will be called from within this function +void gt2CloseSocket(GT2Socket socket); + +// processes a socket (and all associated connections) +void gt2Think(GT2Socket socket); + +// sends a raw UDP datagram through the socket +// this function bypasses the normal connection logic +// note that all messages sent this way will be unreliable +// to broadcast a datagram, omit the IP from the remoteAddress (e.g., ":12345") +GT2Result gt2SendRawUDP +( + GT2Socket socket, // the socket through which to send the raw UDP datagram + const char * remoteAddress, // the address to which to send the datagram + const GT2Byte * message, // the message to send, or NULL for an empty datagram + int len // the len of the message (0 for an empty message, ignored if message==NULL) +); + +/************************* +** CONNECTION CALLBACKS ** +*************************/ + +// Called when the connect has completed. +// If the result is GT2Rejected, +// then message is the message that the +// listener passed to gt2Reject. If the +// result is anything else, then message +// is NULL and len is 0. +typedef void (* gt2ConnectedCallback) +( + GT2Connection connection, // The connection object. + GT2Result result, // Result from connect attempt. + GT2Byte * message, // If result==GT2Rejected, the reason. Otherwise, NULL. + int len // If result==GT2Rejected, the length of the reason. Otherwise, 0. +); + +// Called when a message is received. +typedef void (* gt2ReceivedCallback) +( + GT2Connection connection, // The connection the message was received on. + GT2Byte * message, // The message that was received. Will be NULL if an empty message. + int len, // The length of the message in bytes. Will be 0 if an empty message. + GT2Bool reliable // True if this is was sent reliably. +); + +// Called when the connection is closed (remotely or locally). +// The connection can no longer be used after this callback returns. +typedef void (* gt2ClosedCallback) +( + GT2Connection connection, // The connection that was closed. + GT2CloseReason reason // The reason the connection was closed. +); + +// When a reply is received for a ping that was sent, this callback is called. +// The latency reported here is the amount of time between when the ping +// was first sent with gt2Ping and when the pong was received. +typedef void (* gt2PingCallback) +( + GT2Connection connection, // the connection the ping was sent on + int latency // the round-trip time for the ping, in milliseconds +); + +// Callbacks set for each connection. +// The connected callback is ignored +// when this is passed to gt2Accept. +typedef struct +{ + gt2ConnectedCallback connected; // Called when gt2Connect is complete. + gt2ReceivedCallback received; // Called when a message is received. + gt2ClosedCallback closed; // Called when the connection is closed (remotely or locally). + gt2PingCallback ping; // Called when a ping reply is received. +} GT2ConnectionCallbacks; + +/************************* +** CONNECTION FUNCTIONS ** +*************************/ + +// initiates a connection between a local socket and a remote socket +// if blocking is true, the return value signals the connection result: +// GT2Success means the connect attempt succeeded +// anything else means it failed +// if blocking is false, the return value signals the current status of the attempt +// GT2Success means the connection is being attempted +// anything else means there was an error and the connection attempt has been aborted +GT2Result gt2Connect +( + GT2Socket socket, // the local socket to use for the connection + GT2Connection * connection, // if the result is GT2Success, and blocking is false, the connection object handle is stored here + const char * remoteAddress, // the address to connect to + const GT2Byte * message, // an optional initial message (may be NULL) + int len, // length of the initial message (may be 0, or -1 for strlen) + int timeout, // timeout in milliseconds (may be 0 for infinite retries) + GT2ConnectionCallbacks * callbacks, // callbacks for connection related stuff + GT2Bool blocking // if true, don't return until complete (successfuly or unsuccessfuly) +); + +// sends data reliably or unreliably +// reliable messages are guaranteed to arrive, arrive in order, and arrive only once. +// unreliable messages are not guaranteed to arrive, arrive in order, or arrive only once. +// because messages may be held in the outgoing buffer (even unreliable messages may need +// to be put in the buffer), the message size cannot exceed +GT2Result gt2Send +( + GT2Connection connection, // the connection to send the message on + const GT2Byte * message, // the message to send, or NULL for an empty message0 + int len, // the len of the message (0 for an empty message, ignored if message==NULL) + GT2Bool reliable // if true, send the message reliably +); + +// sends a ping on a connection in an attempt to determine latency +// the ping is unreliable, and either it or the pong sent in reply +// could be dropped (resulting in the callback never being called), +// or it could even arrive multiple times (resulting in multiple +// calls to the callback). +void gt2Ping(GT2Connection connection); + +// starts an attempt to close the connection +// when the close is completed, the connection's closed callback will be called +void gt2CloseConnection(GT2Connection connection); + +// same as gt2CloseConnection, but doesn't wait for confirmation from the remote side of the connection +// the closed callback will be called from within this function +void gt2CloseConnectionHard(GT2Connection connection); + +// closes all of a socket's connections (essentially calls gt2CloseConnection on each of them). +void gt2CloseAllConnections(GT2Socket socket); + +// same as gt2CloseAllConnections, but does a hard close +// any closed callbacks will be called from within this function +void gt2CloseAllConnectionsHard(GT2Socket socket); + +/********************* +** LISTEN CALLBACKS ** +*********************/ + +// callback gets called when someone attempts to connect to a socket that is listening for new connections. +// in response to this callback the application should call gt2Accept or gt2Reject. they do not need +// to be called from inside the callback, however they should be called in a timely manner so that the +// remote side does not need to sit around indefinitely waiting for a response. +// the latency is an estimate of the round trip time between connections. +typedef void (* gt2ConnectAttemptCallback) +( + GT2Socket socket, // the socket the attempt came in on + GT2Connection connection, // a connection object for the incoming connection attempt + unsigned int ip, // the IP being used remotely for the connection attempt + unsigned short port, // the port being used remotely for the connection attempt + int latency, // the approximate latency on the connection + GT2Byte * message, // an optional message sent with the attempt. Will be NULL if an empty message. + int len // the length of the message, in characters. Will be 0 if an empty message. +); + +/********************* +** LISTEN FUNCTIONS ** +*********************/ + +// tells a socket to start listening for incoming connections +// any connections attempts will cause the callback to be called +// if the socket is already listening, this callback will replace the exsiting callback being used +// if the callback is NULL, this will cause the connection to stop listening +void gt2Listen(GT2Socket socket, gt2ConnectAttemptCallback callback); + +// after a socket's gt2ConnectAttemptCallback has been called, this function can be used to accept +// the incoming connection attempt. it can be called from either within the callback or some later time. +// as soon as it is called the connection is active, and messages can be sent and received. the remote side +// of the connection will have it's connected callback called with the result set to GT2Success. the callbacks +// that are passed in to this function are the same callbacks that get passed to gt2Connect, with the exception +// that the connected callback can be ignored, as the connection is already established. +// if this function returns GT2True, then the connection has been successfully accepted. if it returns +// GT2False, then the remote side has already closed the connection attempt. in that case, the connection +// is considered closed, and it cannot be referenced again. +GT2Bool gt2Accept(GT2Connection connection, GT2ConnectionCallbacks * callbacks); + +// after a socket's gt2ConnectAttemptCallback has been called, this function can be used to reject +// the incoming connection attempt. it can be called from either within the callback or some later time. +// once the function is called the connection is considered closed and cannot be referenced again. the remote +// side attempting the connection will have its connected callback called with the result set to GT2Rejected. +// if the message is not NULL and the len is not 0, the message will be sent with the rejection, and passed +// into the remote side's connected callback. +void gt2Reject(GT2Connection connection, const GT2Byte * message, int len); + +/************************* +** MESSAGE CONFIRMATION ** +*************************/ +// gets the message id for the last reliably sent message. unreliable messages do not have ids. +// this should be called immediately after gt2Send. waiting until after a call to gt2Think can result in +// an invalid message id being returned. +// note that the use of filters that can either drop or delay messages can complicate the process, because +// in those cases a call to gt2Send does not guarantee that a message will actually be sent. in those cases, +// gt2GetLastSentMessageID should be called after gt2FilteredSend, because the actual message will be sent +// from within that function. +GT2MessageID gt2GetLastSentMessageID(GT2Connection connection); + +// returns true if confirmation was received locally that the reliable message represented by the message id +// was received by the remote end of the connection. returns false if confirmation was not yet received. +// this should only be called on message ids that were returned by gt2GetLastSendMessageID, and should be +// used relatively soon after the message was sent, due to message ids wrapping around after a period of time. +GT2Bool gt2WasMessageIDConfirmed(GT2Connection connection, GT2MessageID messageID); + +/********************* +** FILTER CALLBACKS ** +*********************/ + +// Callback for filtering outgoing data. +// Call gt2FilteredSend with the filtered data, either from within the callback or later. +// the message points to the same memory location as the message passed to gt2Send (or gt2FilteredSend). +// so if the call to gt2FilteredSend is delayed, it is the filter's responsibility to make sure the +// data is still around when and if it is needed. +typedef void (* gt2SendFilterCallback) +( + GT2Connection connection, // The connection on which the message is being sent. + int filterID, // Pass this ID to gt2FilteredSend. + const GT2Byte * message, // The message being sent. Will be NULL if an empty message. + int len, // The length of the message being sent, in bytes. Will be 0 if an empty message. + GT2Bool reliable // If the message is being sent reliably. +); + +// Callback for filtering incoming data. +// Call gt2FilteredRecieve with the filtered data, +// either from within the callback or later. +// the message may point to a memory location supplied to gt2FilteredReceive by a previous filter. +// so if this filter's call to gt2FilteredReceive is delayed, it is the filter's responsibility +// to make sure the data is still around when and if it is needed. +typedef void (* gt2ReceiveFilterCallback) +( + GT2Connection connection, // The connection the message was received on. + int filterID, // Pass this ID to gtFilteredReceive. + GT2Byte * message, // The message that was received. Will be NULL if an empty message. + int len, // The length of the message in bytes. Will be 0 if an empty message. + GT2Bool reliable // True if this is a reliable message. +); + +/********************* +** FILTER FUNCTIONS ** +*********************/ + +// Adds a filter to the connection's outgoing data. +// Returns GT2False if there was an error adding the filter (due to no free memory) +GT2Bool gt2AddSendFilter +( + GT2Connection connection, // The connection on which to add the filter. + gt2SendFilterCallback callback // The callback the outgoing data is filtered through. +); + +// Removes a filter from the connection's outgoing data. +// if callback is NULL, all send filters are removed +void gt2RemoveSendFilter +( + GT2Connection connection, // The connection on which to remove the filter. + gt2SendFilterCallback callback // The callback to remove. +); + +// Called in response to a gt2SendFilterCallback being called. +// It can be called from within the callback, or at any later time. +void gt2FilteredSend +( + GT2Connection connection, // The connection on which the message is being sent. + int filterID, // The ID passed to the gt2SendFilterCallback. + const GT2Byte * message, // The message being sent. May be NULL. + int len, // The lengt2h of the message being sent, in bytes. May be 0 or -1. + GT2Bool reliable // If the message should be sent reliably. +); + +// Adds a filter to the connection's incoming data. +// Returns GT2False if there was an error adding the filter (due to no free memory) +GT2Bool gt2AddReceiveFilter +( + GT2Connection connection, // The connection on which to add the filter. + gt2ReceiveFilterCallback callback // The callback the incoming data is filtered through. +); + +// Removes a filter from the connection's incoming data. +// if callback is NULL, all receive filters are removed +void gt2RemoveReceiveFilter +( + GT2Connection connection, // The connection on which to remove the filter. + gt2ReceiveFilterCallback callback // The callback to remove. +); + +// Called in response to a gt2ReceiveFilterCallback being called. +// It can be called from within the callback, or at any later time. +void gt2FilteredReceive +( + GT2Connection connection, // The connection the message was received on. + int filterID, // The ID passed to the gt2ReceiveFilterCallback. + GT2Byte * message, // The message that was received. May be NULL. + int len, // The lengt2h of the message in bytes. May be 0. + GT2Bool reliable // True if this is a reliable message. +); + +/***************************** +** SOCKET SHARING CALLBACKS ** +*****************************/ + +// this callback gets called when the sock receives a message that it cannot match to an existing +// connection. if the callback recognizes the message and handles it, it should return GT2True, which +// will tell the socket to ignore the message. if the callback does not recognize the message, it +// should return GT2False, which tells the socket to let the other side know there is no connection. +typedef GT2Bool (* gt2UnrecognizedMessageCallback) +( + GT2Socket socket, // the socket the message was received on + unsigned int ip, // the ip of the remote machine the message came from (in network byte order) + unsigned short port, // the port on the remote machine (in host byte order) + GT2Byte * message, // the message (may be NULL for an empty message) + int len // the length of the message (may be 0) +); + +/***************************** +** SOCKET SHARING FUNCTIONS ** +*****************************/ + +// this function returns the actual underlying socket for a GT2Socket. +// this can be used for socket sharing purposes, along with the gt2UnrecognizedMessageCallback. +SOCKET gt2GetSocketSOCKET(GT2Socket socket); + +// sets a callback that all unrecognized messages are passed to. an unrecognized message is one +// that can't be matched up to a specific connection. if the callback handles the message, it +// returns true, and the GT2Socket ignores the message. if the callback does not recognize the message, +// it returns false, and the socket handles the message (by sending a message back indicating the connection +// is closed). if the callback is NULL, it removes any previously set callback. +void gt2SetUnrecognizedMessageCallback(GT2Socket socket, gt2UnrecognizedMessageCallback callback); + +/******************* +** INFO FUNCTIONS ** +*******************/ + +// gets the socket this connection exists on +GT2Socket gt2GetConnectionSocket(GT2Connection connection); + +// gets the connection's connection state +// GT2Connecting - the connection is still being negotiated +// GT2Connected - the connection is active (has successfully connected, and not yet closed) +// GT2Closing - the connection is in the process of closing (i.e., sent a close message and waiting for confirmation). +// GT2Closed - the connection has already been closed and will soon be freed +GT2ConnectionState gt2GetConnectionState(GT2Connection connection); + +// gets a connection's remote IP (in network byte order) +unsigned int gt2GetRemoteIP(GT2Connection connection); + +// gets a connection's remote port (in host byte order) +unsigned short gt2GetRemotePort(GT2Connection connection); + +// gets a socket's local IP (in network byte order) +unsigned int gt2GetLocalIP(GT2Socket socket); + +// gets a socket's local port (in host byte order) +unsigned short gt2GetLocalPort(GT2Socket socket); + +// gets the total size of the connection's incoming buffer. +int gt2GetIncomingBufferSize(GT2Connection connection); + +// gets the amount of available space in the connection's incoming buffer. +int gt2GetIncomingBufferFreeSpace(GT2Connection connection); + +// gets the total size of the connection's outgoing buffer. +int gt2GetOutgoingBufferSize(GT2Connection connection); + +// gets the amount of available space in the connection's outgoing buffer. +int gt2GetOutgoingBufferFreeSpace(GT2Connection connection); + +/************************ +** USER DATA FUNCTIONS ** +************************/ + +void gt2SetSocketData(GT2Socket socket, void * data); +void * gt2GetSocketData(GT2Socket socket); +void gt2SetConnectionData(GT2Connection connection, void * data); +void * gt2GetConnectionData(GT2Connection connection); + +/************************* +** BYTE ORDER FUNCTIONS ** +*************************/ + +unsigned int gt2NetworkToHostInt(unsigned int i); +unsigned int gt2HostToNetworkInt(unsigned int i); +unsigned short gt2HostToNetworkShort(unsigned short s); +unsigned short gt2NetworkToHostShort(unsigned short s); + +/********************** +** ADDRESS FUNCTIONS ** +**********************/ + +// Converts an IP and a port into a text string. The IP must be in network byte order, and the port +// in host byte order. The string must be able to hold at least 22 characters (including the NUL). +// "XXX.XXX.XXX.XXX:XXXXX" +// If both the IP and port are non-zero, the string will be of the form "1.2.3.4:5" (":"). +// If the port is zero, and the IP is non-zero, the string will be of the form "1.2.3.4" (""). +// If the IP is zero, and the port is non-zero, the string will be of the form ":5" (":"). +// If both the IP and port are zero, the string will be an empty string ("") +// The string is returned. If the string paramater is NULL, then an internal static string will be +// used. There are two internal strings that are alternated between. +const char * gt2AddressToString +( + unsigned int ip, // IP in network byte order. Can be 0. + unsigned short port, // Port in host byte order. Can be 0. + char string[22] // String will be placed in here. Can be NULL. +); + +// Converts a string address into an IP and a port. The IP is stored in network byte order, and the port +// is stored in host byte order. Returns false if there was an error parsing the string, or if a supplied +// hostname can't be resolved. +// Possible string forms: +// NULL => all IPs, any port (localAddress only). +// "" => all IPs, any port (localAddress only). +// "1.2.3.4" => 1.2.3.4 IP, any port (localAddress only). +// "host.com" => host.com's IP, any port (localAddress only). +// ":2786" => all IPs, 2786 port (localAddress only). +// "1.2.3.4:0" => 1.2.3.4 IP, any port (localAddress only). +// "host.com:0" => host.com's IP, any port (localAddress only). +// "0.0.0.0:2786" => all IPs, 2786 port (localAddress only). +// "1.2.3.4:2786" => 1.2.3.4 IP, 2786 port (localAddress or remoteAddress). +// "host.com:2786" => host.com's IP, 2786 port (localAddress or remoteAddress). +// If this function needs to resolve a hostname ("host.com") it may need to contact a DNS server, which can +// cause the function to block for an indefinite period of time. Usually it is < 2 seconds, but on certain +// systems, and under certain circumstances, it can take 30 seconds or longer. +GT2Bool gt2StringToAddress +( + const char * string, // The string to convert. + unsigned int * ip, // The IP is stored here, in network byte order. Can be NULL. + unsigned short * port // The port is stored here, in host byte order. Can be NULL. +); + +// Gets the host information for a machine on the Internet. The first version takes an IP in network byte order, +// and the second version takes a string that is either a dotted ip ("1.2.3.4"), or a hostname ("www.gamespy.com"). +// If the function can successfully lookup the host's info, the host's main hostname will be returned. If it +// cannot find the host's info, it returns NULL. +// For the aliases parameter, pass in a pointer to a variable of type (char **). If this parameter is not NULL, +// and the function succeeds, the variable will point to a NULL-terminated list of alternate names for the host. +// For the ips parameter, pass in a pointer to a variable of type (int **). If this parameter is not NULL, and +// the function succeeds, the variable will point to a NULL-terminated list of altername IPs for the host. Each +// element in the list is actually a pointer to an unsigned int, which is an IP address in network byte order. +// The return value, aliases, and IPs all point to an internal data structure, and none of these values should +// be modified directly. Also, the data is only valid until another call needs to use the same data structure +// (virtually ever internet address function will use this data structure). If the data will be needed in the +// future, it should be copied off. +// If this function needs to resolve a hostname ("host.com") it may need to contact a DNS server, which can +// cause the function to block for an indefinite period of time. Usually it is < 2 seconds, but on certain +// systems, and under certain circumstances, it can take 30 seconds or longer. +const char * gt2IPToHostInfo(unsigned int ip, char *** aliases, unsigned int *** ips); +const char * gt2StringToHostInfo(const char * string, char *** aliases, unsigned int *** ips); + +// The following functions are shortcuts for the above two functions (gt2*ToHostInfo()), and each performs a subset +// of the functionality. They are provided so that code that only needs certain information can be a little simpler. +// Before using these, read the comments for the gt2*ToHostInfo() functions, as the info also applies to these functions. +const char * gt2IPToHostname(unsigned int ip); +const char * gt2StringToHostname(const char * string); +char ** gt2IPToAliases(unsigned int ip); +char ** gt2StringToAliases(const char * string); +unsigned int ** gt2IPToIPs(unsigned int ip); +unsigned int ** gt2StringToIPs(const char * string); + +#ifdef _XBOX +unsigned int gt2XnAddrToIP(XNADDR theAddr, XNKID theKeyId); +GT2Bool gt2IPToXnAddr(int ip, XNADDR *theAddr, XNKID *theKeyId); +#endif + +// these are for getting around adhoc which requires a 48 bit address v.s. a 32 bit inet address +void gt2IpToMac(gsi_u32 ip,char *mac); +// change IP address to mac ethernet +gsi_u32 gt2MacToIp(const char *mac); +// change mac ethernet to IP address + +/******************* +** DUMP CALLBACKS ** +*******************/ + +// called with either sent or received data +// trying to send a message from within the send dump callback, or letting the socket think from within the receive +// dump callback can cause serious problems, and should not be done. +typedef void (* gt2DumpCallback) +( + GT2Socket socket, // the socket the message was on + GT2Connection connection, // the connection the message was on, or NULL if there is no connection for this message + unsigned int ip, // the remote ip, in network byte order + unsigned short port, // the remote port, in host byte order + GT2Bool reset, // if true, the connection has been reset (only used by the receive callback) + const GT2Byte * message, // the message (should not be modified) + int len // the length of the message +); + +/******************* +** DUMP FUNCTIONS ** +*******************/ + +// sets a callback to be called whenever a UDP datagram is sent or received, and when a connection reset is received. +// pass in a callback of NULL to remove the callback. the dumps sit at a lower level than the filters, and allow an +// app to keep an eye on exactly what datagrams are being sent and received, allowing for close monitoring. however +// the dumps cannot be used to modify data, only monitor it. the dumps are useful for debugging purposes, and +// to keep track of data send and receive rates (e.g., the Quake 3 engine's netgraph). +// note that these are the actual UDP datagrams being sent and received - datagrams may be dropped, repeated, or +// out-of-order. control datagrams (those used internally by the protocol) will be passed to the dump callbacks, +// and certain application messages will have a header at the beginning. +void gt2SetSendDump(GT2Socket socket, gt2DumpCallback callback); +void gt2SetReceiveDump(GT2Socket socket, gt2DumpCallback callback); + + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/gt2/gt2Auth.c b/code/gamespy/gt2/gt2Auth.c new file mode 100644 index 00000000..6594e042 --- /dev/null +++ b/code/gamespy/gt2/gt2Auth.c @@ -0,0 +1,102 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Main.h" +#include "gt2Auth.h" +#include + +#define CALCULATEODDMODE(buffer, i, oddmode) ((buffer[i-1] & 1) ^ (i & 1) ^ oddmode ^ (buffer[0] & 1) ^ ((buffer[0] < 79) ? 1 : 0) ^ ((buffer[i-1] < buffer[0]) ? 1 : 0)); + +char GT2ChallengeKey[33] = "3b8dd8995f7c40a9a5c5b7dd5b481341"; + +static int gti2VerifyChallenge(const GT2Byte *buffer) +{ + int oddmode = 0; + int i; + for (i = 1; i < GTI2_CHALLENGE_LEN ; i++) + { + oddmode = CALCULATEODDMODE(buffer,i, oddmode); + if ((oddmode && (buffer[i] & 1) == 0) || (!oddmode && ((buffer[i] & 1) == 1))) + return 0; //failed!! + } + return 1; +} + +GT2Byte * gti2GetChallenge +( + GT2Byte * buffer +) +{ + int i; + int oddmode; + assert(buffer); + + srand((unsigned int)current_time()); + buffer[0] = (GT2Byte)(33 + rand() % 93); //use chars in the range 33 - 125 + oddmode = 0; + for (i = 1; i < GTI2_CHALLENGE_LEN ; i++) + { + oddmode = CALCULATEODDMODE(buffer,i, oddmode); + buffer[i] = (GT2Byte)(33 + rand() % 93); //use chars in the range 33 - 125 + //if oddmode make sure the char is odd, otherwise make sure it's even + if ((oddmode && (buffer[i] & 1) == 0) || (!oddmode && ((buffer[i] & 1) == 1))) + buffer[i]++; + + } + return buffer; +} + +GT2Byte * gti2GetResponse +( + GT2Byte * buffer, + const GT2Byte * challenge +) +{ + int i; + int valid; + char cchar; + int keylen = (int)strlen(GT2ChallengeKey); + int chalrand; + valid = gti2VerifyChallenge(challenge); //it's an invalid challenge, give them a bogus response + //assert(GTI2_RESPONSE_LEN <= GTI2_CHALLENGE_LEN); + for (i = 0 ; i < GTI2_RESPONSE_LEN ; i++) + { + //use random vals for spots 0 and 13 + if (!valid || i == 0 || i == 13) + buffer[i] = (GT2Byte)(33 + rand() % 93); //use chars in the range 33 - 125 + else + { //set the character to look back at, never use the random ones! + if (i == 1 || i == 14) + cchar = (char)challenge[i]; + else + cchar = (char)challenge[i-1]; + chalrand = abs((challenge[((i * challenge[i]) + GT2ChallengeKey[(i + challenge[i]) % keylen]) % GTI2_CHALLENGE_LEN] ^ GT2ChallengeKey[(i * 17991 * cchar) % keylen])); + buffer[i] = (GT2Byte)(33 + chalrand % 93); + } + } + return buffer; +} + + +GT2Bool gti2CheckResponse +( + const GT2Byte * response1, + const GT2Byte * response2 +) +{ + int i; //when comparing ignore the ones that are random + for (i = 0 ; i < GTI2_RESPONSE_LEN ; i++) + { + if (i != 0 && i != 13 && response1[i] != response2[i]) + return GT2False; + } + return GT2True; +} + diff --git a/code/gamespy/gt2/gt2Auth.h b/code/gamespy/gt2/gt2Auth.h new file mode 100644 index 00000000..8d3134d6 --- /dev/null +++ b/code/gamespy/gt2/gt2Auth.h @@ -0,0 +1,42 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_AUTH_H_ +#define _GT2_AUTH_H_ + +#define GTI2_CHALLENGE_LEN 32 +#define GTI2_RESPONSE_LEN 32 + +#ifdef __cplusplus +extern "C" { +#endif + +GT2Byte * gti2GetChallenge +( + GT2Byte * buffer +); + +GT2Byte * gti2GetResponse +( + GT2Byte * buffer, + const GT2Byte * challenge +); + +GT2Bool gti2CheckResponse +( + const GT2Byte * response1, + const GT2Byte * response2 +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/gt2/gt2Buffer.c b/code/gamespy/gt2/gt2Buffer.c new file mode 100644 index 00000000..43821a41 --- /dev/null +++ b/code/gamespy/gt2/gt2Buffer.c @@ -0,0 +1,80 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Buffer.h" +#include + +GT2Bool gti2AllocateBuffer(GTI2Buffer * buffer, int size) +{ + buffer->buffer = (GT2Byte *)gsimalloc((unsigned int)size); + if(!buffer->buffer) + return GT2False; + buffer->size = size; + + return GT2True; +} + +int gti2GetBufferFreeSpace(const GTI2Buffer * buffer) +{ + return (buffer->size - buffer->len); +} + +void gti2BufferWriteByte(GTI2Buffer * buffer, GT2Byte b) +{ + assert(buffer->len < buffer->size); +#if 0 + if(buffer->len >= buffer->size) + return; +#endif + + buffer->buffer[buffer->len++] = b; +} + +void gti2BufferWriteUShort(GTI2Buffer * buffer, unsigned short s) +{ + assert((buffer->len + 2) <= buffer->size); +#if 0 + if((buffer->len + 2) > buffer->size) + return; +#endif + + buffer->buffer[buffer->len++] = (GT2Byte)((s >> 8) & 0xFF); + buffer->buffer[buffer->len++] = (GT2Byte)(s & 0xFF); +} + +void gti2BufferWriteData(GTI2Buffer * buffer, const GT2Byte * data, int len) +{ + if(!data || !len) + return; + + if(len == -1) + len = (int)strlen((const char *)data); + + assert((buffer->len + len) <= buffer->size); +#if 0 + if(buffer->len >= buffer->size) + return; +#endif + + memcpy(buffer->buffer + buffer->len, data, (unsigned int)len); + buffer->len += len; +} + +void gti2BufferShorten(GTI2Buffer * buffer, int start, int shortenBy) +{ + if(start == -1) + start = (buffer->len - shortenBy); + + assert(start <= buffer->len); + assert(shortenBy <= (buffer->len - start)); + + memmove(buffer->buffer + start, buffer->buffer + start + shortenBy, (unsigned int)(buffer->len - start - shortenBy)); + buffer->len -= shortenBy; +} diff --git a/code/gamespy/gt2/gt2Buffer.h b/code/gamespy/gt2/gt2Buffer.h new file mode 100644 index 00000000..d93f3927 --- /dev/null +++ b/code/gamespy/gt2/gt2Buffer.h @@ -0,0 +1,27 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_BUFFER_H_ +#define _GT2_BUFFER_H_ + +#include "gt2Main.h" + +GT2Bool gti2AllocateBuffer(GTI2Buffer * buffer, int size); + +int gti2GetBufferFreeSpace(const GTI2Buffer * buffer); + +void gti2BufferWriteByte(GTI2Buffer * buffer, GT2Byte b); +void gti2BufferWriteUShort(GTI2Buffer * buffer, unsigned short s); +void gti2BufferWriteData(GTI2Buffer * buffer, const GT2Byte * data, int len); + +// shortens the buffer by "shortenBy" (length, not size) +void gti2BufferShorten(GTI2Buffer * buffer, int start, int shortenBy); + +#endif diff --git a/code/gamespy/gt2/gt2Callback.c b/code/gamespy/gt2/gt2Callback.c new file mode 100644 index 00000000..b42e0365 --- /dev/null +++ b/code/gamespy/gt2/gt2Callback.c @@ -0,0 +1,431 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Callback.h" +#include "gt2Socket.h" + +/********************* +** SOCKET CALLBACKS ** +*********************/ + +GT2Bool gti2SocketErrorCallback +( + GT2Socket socket +) +{ + assert(socket); + if(!socket) + return GT2True; + + if(!socket->socketErrorCallback) + return GT2True; + + socket->callbackLevel++; + + socket->socketErrorCallback(socket); + + socket->callbackLevel--; + + // check if the socket should be closed + if(socket->close && !socket->callbackLevel) + { + gti2CloseSocket(socket); + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2ConnectAttemptCallback +( + GT2Socket socket, + GT2Connection connection, + unsigned int ip, + unsigned short port, + int latency, + GT2Byte * message, + int len +) +{ + assert(socket && connection); + if(!socket || !connection) + return GT2True; + + if(!socket->connectAttemptCallback) + return GT2True; + + // check for an empty message + if(!len || !message) + { + message = NULL; + len = 0; + } + + socket->callbackLevel++; + connection->callbackLevel++; + + socket->connectAttemptCallback(socket, connection, ip, port, latency, message, len); + + socket->callbackLevel--; + connection->callbackLevel--; + + // check if the socket should be closed + if(socket->close && !socket->callbackLevel) + { + gti2CloseSocket(socket); + return GT2False; + } + + return GT2True; +} + +/************************* +** CONNECTION CALLBACKS ** +*************************/ + +GT2Bool gti2ConnectedCallback +( + GT2Connection connection, + GT2Result result, + GT2Byte * message, + int len +) +{ + assert(connection); + if(!connection) + return GT2True; + + // store the result + connection->connectionResult = result; + + if(!connection->callbacks.connected) + return GT2True; + + // check for an empty message + if(!len || !message) + { + message = NULL; + len = 0; + } + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + connection->callbacks.connected(connection, result, message, len); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if(connection->socket->close && !connection->socket->callbackLevel) + { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2ReceivedCallback +( + GT2Connection connection, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + assert(connection); + if(!connection) + return GT2True; + + if(!connection->callbacks.received) + return GT2True; + + // check for an empty message + if(!len || !message) + { + message = NULL; + len = 0; + } + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + connection->callbacks.received(connection, message, len, reliable); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if(connection->socket->close && !connection->socket->callbackLevel) + { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2ClosedCallback +( + GT2Connection connection, + GT2CloseReason reason +) +{ + assert(connection); + if(!connection) + return GT2True; + + if(!connection->callbacks.closed) + return GT2True; + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + connection->callbacks.closed(connection, reason); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if(connection->socket->close && !connection->socket->callbackLevel) + { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2PingCallback +( + GT2Connection connection, + int latency +) +{ + assert(connection); + if(!connection) + return GT2True; + + if(!connection->callbacks.ping) + return GT2True; + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + connection->callbacks.ping(connection, latency); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if(connection->socket->close && !connection->socket->callbackLevel) + { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +/********************* +** FILTER CALLBACKS ** +*********************/ + +GT2Bool gti2SendFilterCallback +( + GT2Connection connection, + int filterID, + const GT2Byte * message, + int len, + GT2Bool reliable +) +{ + gt2SendFilterCallback * callback; + + assert(connection); + if(!connection) + return GT2True; + + callback = (gt2SendFilterCallback *)ArrayNth(connection->sendFilters, filterID); + if(!callback) + return GT2True; + + // check for an empty message + if(!len || !message) + { + message = NULL; + len = 0; + } + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + (*callback)(connection, filterID, message, len, reliable); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if(connection->socket->close && !connection->socket->callbackLevel) + { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2ReceiveFilterCallback +( + GT2Connection connection, + int filterID, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + gt2ReceiveFilterCallback * callback; + + assert(connection); + if(!connection) + return GT2True; + + callback = (gt2ReceiveFilterCallback *)ArrayNth(connection->receiveFilters, filterID); + if(!callback) + return GT2True; + + // check for an empty message + if(!len || !message) + { + message = NULL; + len = 0; + } + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + (*callback)(connection, filterID, message, len, reliable); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if(connection->socket->close && !connection->socket->callbackLevel) + { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +/******************* +** DUMP CALLBACKS ** +*******************/ + +GT2Bool gti2DumpCallback +( + GT2Socket socket, + GT2Connection connection, + unsigned int ip, + unsigned short port, + GT2Bool reset, + const GT2Byte * message, + int len, + GT2Bool send +) +{ + gt2DumpCallback callback; + + assert(socket); + if(!socket) + return GT2True; + + if(send) + callback = socket->sendDumpCallback; + else + callback = socket->receiveDumpCallback; + + if(!callback) + return GT2True; + + // check for an empty message + if(!len || !message) + { + message = NULL; + len = 0; + } + + socket->callbackLevel++; + if(connection) + connection->callbackLevel++; + + callback(socket, connection, ip, port, reset, message, len); + + socket->callbackLevel--; + if(connection) + connection->callbackLevel--; + + // check if the socket should be closed + if(socket->close && !socket->callbackLevel) + { + gti2CloseSocket(socket); + return GT2False; + } + + return GT2True; +} + +/***************************** +** SOCKET SHARING CALLBACKS ** +*****************************/ + +GT2Bool gti2UnrecognizedMessageCallback +( + GT2Socket socket, + unsigned int ip, + unsigned short port, + GT2Byte * message, + int len, + GT2Bool * handled +) +{ + *handled = GT2False; + + assert(socket); + if(!socket) + return GT2True; + + if(!socket->unrecognizedMessageCallback) + return GT2True; + + // check for an empty message + if(!len || !message) + { + message = NULL; + len = 0; + } + + socket->callbackLevel++; + + *handled = socket->unrecognizedMessageCallback(socket, ip, port, message, len); + + socket->callbackLevel--; + + // check if the socket should be closed + if(socket->close && !socket->callbackLevel) + { + gti2CloseSocket(socket); + return GT2False; + } + + return GT2True; +} diff --git a/code/gamespy/gt2/gt2Callback.h b/code/gamespy/gt2/gt2Callback.h new file mode 100644 index 00000000..f1a74cc5 --- /dev/null +++ b/code/gamespy/gt2/gt2Callback.h @@ -0,0 +1,120 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_CALLBACK_H_ +#define _GT2_CALLBACK_H_ + +#include "gt2Main.h" + +/********************* +** SOCKET CALLBACKS ** +*********************/ + +GT2Bool gti2SocketErrorCallback +( + GT2Socket socket +); + +GT2Bool gti2ConnectAttemptCallback +( + GT2Socket socket, + GT2Connection connection, + unsigned int ip, + unsigned short port, + int latency, + GT2Byte * message, + int len +); + +/************************* +** CONNECTION CALLBACKS ** +*************************/ + +GT2Bool gti2ConnectedCallback +( + GT2Connection connection, + GT2Result result, + GT2Byte * message, + int len +); + +GT2Bool gti2ReceivedCallback +( + GT2Connection connection, + GT2Byte * message, + int len, + GT2Bool reliable +); + +GT2Bool gti2ClosedCallback +( + GT2Connection connection, + GT2CloseReason reason +); + +GT2Bool gti2PingCallback +( + GT2Connection connection, + int latency +); + +/********************* +** FILTER CALLBACKS ** +*********************/ + +GT2Bool gti2SendFilterCallback +( + GT2Connection connection, + int filterID, + const GT2Byte * message, + int len, + GT2Bool reliable +); + +GT2Bool gti2ReceiveFilterCallback +( + GT2Connection connection, + int filterID, + GT2Byte * message, + int len, + GT2Bool reliable +); + +/******************* +** DUMP CALLBACKS ** +*******************/ + +GT2Bool gti2DumpCallback +( + GT2Socket socket, + GT2Connection connection, + unsigned int ip, + unsigned short port, + GT2Bool reset, + const GT2Byte * message, + int len, + GT2Bool send +); + +/***************************** +** SOCKET SHARING CALLBACKS ** +*****************************/ + +GT2Bool gti2UnrecognizedMessageCallback +( + GT2Socket socket, + unsigned int ip, + unsigned short port, + GT2Byte * message, + int len, + GT2Bool * handled +); + +#endif diff --git a/code/gamespy/gt2/gt2Connection.c b/code/gamespy/gt2/gt2Connection.c new file mode 100644 index 00000000..a17d54bb --- /dev/null +++ b/code/gamespy/gt2/gt2Connection.c @@ -0,0 +1,343 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Connection.h" +#include "gt2Socket.h" +#include "gt2Message.h" +#include "gt2Callback.h" +#include "gt2Utility.h" +#include + +GT2Result gti2NewOutgoingConnection(GT2Socket socket, GT2Connection * connection, unsigned int ip, unsigned short port) +{ + GT2Result result; + + // create the object + result = gti2NewSocketConnection(socket, connection, ip, port); + if(result != GT2Success) + return result; + + // set initial states + (*connection)->state = GTI2AwaitingServerChallenge; + (*connection)->initiated = GT2True; + + return GT2Success; +} + +GT2Result gti2NewIncomingConnection(GT2Socket socket, GT2Connection * connection, unsigned int ip, unsigned short port) +{ + GT2Result result; + + // create the object + result = gti2NewSocketConnection(socket, connection, ip, port); + if(result != GT2Success) + return result; + + // set initial states + (*connection)->state = GTI2AwaitingClientChallenge; + (*connection)->initiated = GT2False; + + return GT2Success; +} + +GT2Result gti2StartConnectionAttempt +( + GT2Connection connection, + const GT2Byte * message, + int len, + GT2ConnectionCallbacks * callbacks +) +{ + char challenge[GTI2_CHALLENGE_LEN]; + + // check the message and len + gti2MessageCheck(&message, &len); + + // copy off the message + if(len > 0) + { + connection->initialMessage = (char *)gsimalloc((unsigned int)len); + if(!connection->initialMessage) + return GT2OutOfMemory; + + memcpy(connection->initialMessage, message, (unsigned int)len); + connection->initialMessageLen = len; + } + + // copy the callbacks + if(callbacks) + connection->callbacks = *callbacks; + + // generate a challenge + gti2GetChallenge((GT2Byte *)challenge); + + // generate and store the expected response + gti2GetResponse((GT2Byte *)connection->response, (GT2Byte *)challenge); + + // send the client challenge + gti2SendClientChallenge(connection, challenge); + + // update our state + connection->state = GTI2AwaitingServerChallenge; + + return GT2Success; +} + +GT2Bool gti2AcceptConnection(GT2Connection connection, GT2ConnectionCallbacks * callbacks) +{ + // was the connection already closed? + if(connection->freeAtAcceptReject) + { + // clear the flag + connection->freeAtAcceptReject = GT2False; + + // let the app know if was already closed + return GT2False; + } + + // make sure this flag gets cleared + connection->freeAtAcceptReject = GT2False; + + // check that we're still awaiting this + if(connection->state != GTI2AwaitingAcceptReject) + return GT2False; + + // let the other side know + gti2SendAccept(connection); + + // update our state + connection->state = GTI2Connected; + + // store the callbacks + if(callbacks) + connection->callbacks = *callbacks; + + return GT2True; +} + +void gti2RejectConnection(GT2Connection connection, const GT2Byte * message, int len) +{ + // make sure this flag gets cleared + connection->freeAtAcceptReject = GT2False; + + // check that we're still awaiting this + if(connection->state != GTI2AwaitingAcceptReject) + return; + + // check the message and len + gti2MessageCheck(&message, &len); + + // let the other side know + gti2SendReject(connection, message, len); + + // update our state + connection->state = GTI2Closing; +} + +GT2Bool gti2ConnectionSendData(GT2Connection connection, const GT2Byte * message, int len) +{ + // send the data on the socket + if(!gti2SocketSend(connection->socket, connection->ip, connection->port, message, len)) + return GT2False; + + // mark the time (used for keep-alives) + connection->lastSend = current_time(); + + return GT2True; +} + +static GT2Bool gti2CheckTimeout(GT2Connection connection, gsi_time now) +{ + // are we still trying to connect? + if(connection->state < GTI2Connected) + { + GT2Bool timedOut = GT2False; + + // is this the initiator + if(connection->initiated) + { + // do we have a timeout? + if(connection->timeout) + { + // check the time taken against the timeout + if((now - connection->startTime) > connection->timeout) + timedOut = GT2True; + } + } + else + { + // don't time them out if they're waiting for us + if(connection->state < GTI2AwaitingAcceptReject) + { + // check the time taken against the timeout + if((now - connection->startTime) > GTI2_SERVER_TIMEOUT) + timedOut = GT2True; + } + } + + // check if we timed out + if(timedOut) + { + // let them know + gti2SendClosed(connection); + + // mark it as closed + gti2ConnectionClosed(connection); + + // call the callback + if(!gti2ConnectedCallback(connection, GT2TimedOut, NULL, 0)) + return GT2False; + } + } + + return GT2True; +} + +static GT2Bool gti2SendRetries(GT2Connection connection, gsi_time now) +{ + int i; + int len; + GTI2OutgoingBufferMessage * message; + + // go through the list of outgoing messages awaiting confirmation + len = ArrayLength(connection->outgoingBufferMessages); + for(i = 0 ; i < len ; i++) + { + // get the message + message = (GTI2OutgoingBufferMessage *)ArrayNth(connection->outgoingBufferMessages, i); + + // check if it's time to resend it + if((now - message->lastSend) > GTI2_RESEND_TIME) + { + if(!gti2ResendMessage(connection, message)) + return GT2False; + } + } + + return GT2True; +} + +static GT2Bool gti2CheckPendingAck(GT2Connection connection, gsi_time now) +{ + // check for nothing pending + if(!connection->pendingAck) + return GT2True; + + // check how long it has been pending + if((now - connection->pendingAckTime) > GTI2_PENDING_ACK_TIME) + { + if(!gti2SendAck(connection)) + return GT2False; + } + + return GT2True; +} + +static GT2Bool gti2CheckKeepAlive(GT2Connection connection, gsi_time now) +{ + if((now - connection->lastSend) > GTI2_KEEP_ALIVE_TIME) + { + if(!gti2SendKeepAlive(connection)) + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2ConnectionThink(GT2Connection connection, gsi_time now) +{ + // check timeout + if(!gti2CheckTimeout(connection, now)) + return GT2False; + + // check keep alives + if(!gti2CheckKeepAlive(connection, now)) + return GT2False; + + // send retries + if(!gti2SendRetries(connection, now)) + return GT2False; + + // check the pending ack + if(!gti2CheckPendingAck(connection, now)) + return GT2False; + + return GT2True; +} + +void gti2CloseConnection(GT2Connection connection, GT2Bool hard) +{ + // check if it should be hard or soft closed + if(hard) + { + // check if it's already closed + if(connection->state >= GTI2Closed) + return; + + // mark it as closed + gti2ConnectionClosed(connection); + + // send a closed message + gti2SendClosed(connection); + + // call the callback + gti2ClosedCallback(connection, GT2LocalClose); + + // try and free it + gti2FreeSocketConnection(connection); + } + else + { + // mark it as closing + connection->state = GTI2Closing; + + // send the close + gti2SendClose(connection); + } +} + +void gti2ConnectionClosed(GT2Connection connection) +{ + // check for already closed + if(connection->state == GTI2Closed) + return; + + // mark the connection as closed + connection->state = GTI2Closed; + + // remove it from the connected list + TableRemove(connection->socket->connections, &connection); + + // add it to the closed list + ArrayAppend(connection->socket->closedConnections, &connection); +} + +void gti2ConnectionCleanup(GT2Connection connection) +{ + if(connection->initialMessage) + gsifree(connection->initialMessage); + + if(connection->incomingBuffer.buffer) + gsifree(connection->incomingBuffer.buffer); + if(connection->outgoingBuffer.buffer) + gsifree(connection->outgoingBuffer.buffer); + + if(connection->incomingBufferMessages) + ArrayFree(connection->incomingBufferMessages); + if(connection->outgoingBufferMessages) + ArrayFree(connection->outgoingBufferMessages); + + if(connection->sendFilters) + ArrayFree(connection->sendFilters); + if(connection->receiveFilters) + ArrayFree(connection->receiveFilters); + + gsifree(connection); +} diff --git a/code/gamespy/gt2/gt2Connection.h b/code/gamespy/gt2/gt2Connection.h new file mode 100644 index 00000000..43423f34 --- /dev/null +++ b/code/gamespy/gt2/gt2Connection.h @@ -0,0 +1,41 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_CONNECTION_H_ +#define _GT2_CONNECTION_H_ + +#include "gt2Main.h" + +GT2Result gti2NewOutgoingConnection(GT2Socket socket, GT2Connection * connection, unsigned int ip, unsigned short port); +GT2Result gti2NewIncomingConnection(GT2Socket socket, GT2Connection * connection, unsigned int ip, unsigned short port); + +GT2Result gti2StartConnectionAttempt +( + GT2Connection connection, + const GT2Byte * message, + int len, + GT2ConnectionCallbacks * callbacks +); + +GT2Bool gti2AcceptConnection(GT2Connection connection, GT2ConnectionCallbacks * callbacks); + +void gti2RejectConnection(GT2Connection connection, const GT2Byte * message, int len); + +GT2Bool gti2ConnectionSendData(GT2Connection connection, const GT2Byte * message, int len); + +GT2Bool gti2ConnectionThink(GT2Connection connection, gsi_time now); + +void gti2CloseConnection(GT2Connection connection, GT2Bool hard); + +void gti2ConnectionClosed(GT2Connection connection); + +void gti2ConnectionCleanup(GT2Connection connection); + +#endif diff --git a/code/gamespy/gt2/gt2Encode.c b/code/gamespy/gt2/gt2Encode.c new file mode 100644 index 00000000..aa2f2cea --- /dev/null +++ b/code/gamespy/gt2/gt2Encode.c @@ -0,0 +1,648 @@ +#include +#include +#include "gt2Encode.h" +#include "gt2Main.h" + + + +// This handles alignment issues and endianess +void gt2MemCopy16(char *out, char const *in) +{ + #ifdef _GT2_ENDIAN_CONVERT + *out = in[1]; + out[1] = *in; + #else + // straight copy + *out = *in; + out[1] = in[1]; + #endif +} + +// This handles alignment issues and endianess +void gt2MemCopy32(char *out, char const *in) +{ + #ifdef _GT2_ENDIAN_CONVERT + out[0] = in[3]; + out[1] = in[2]; + out[2] = in[1]; + out[3] = in[0]; + #else + // straight copy + *out = *in; + out[1] = in[1]; + out[2] = in[2]; + out[3] = in[3]; + #endif +} + +// This handles alignment issues and endianess +void gt2MemCopy64(char *out, char const *in) +{ + #ifdef _GT2_ENDIAN_CONVERT + out[0] = in[7]; + out[1] = in[6]; + out[2] = in[5]; + out[3] = in[4]; + out[4] = in[3]; + out[5] = in[2]; + out[6] = in[1]; + out[7] = in[0]; + #else + // straight copy + memcpy(out, in, 8); + #endif + +} +void gt2MemCopy(char *out, char const *in, int size) +{ + if (size == 2) + { + gt2MemCopy16(out, in); + } + else + if (size == 4) + { + gt2MemCopy32(out, in); + } + else + if (size == 8) + { + gt2MemCopy64(out, in); + } + else + { + // warning... no endianess decode. + memcpy(out,in,(size_t)size); + } +} +#if defined(_PS2) || defined(_UNIX) || defined(_PS3) || defined(_WIN64) || defined(_X360) + +#define GT_ENCODE_ELEM(TYPE,b,l,args) \ +{ \ + TYPE v; \ + if (l < sizeof(TYPE)) \ + return -1; \ + v = (TYPE)va_arg(*args, int); \ + gt2MemCopy(b, (const char *)&v, sizeof(TYPE)); \ + return (int)sizeof(TYPE); \ +} + +#define GT_DECODE_ELEM(TYPE,b,l,args) \ +{ \ + TYPE* v; \ + if (l < sizeof(TYPE)) \ + return -1; \ + v = va_arg(*args, TYPE*); \ + gt2MemCopy((char *)v, b, sizeof(TYPE)); \ + return (int)sizeof(TYPE); \ +} + +#define GT_ENCODE_ELEM_NC(TYPE,b,l,args) \ +{ \ + TYPE v; \ + if (l < sizeof(TYPE)) \ + return -1; \ + v = (TYPE)va_arg(*args, int); \ + memcpy(b, &v, sizeof(TYPE)); \ + return (int)sizeof(TYPE); \ +} + +#define GT_DECODE_ELEM_NC(TYPE,b,l,args) \ +{ \ + TYPE* v; \ + if (l < sizeof(TYPE)) \ + return -1; \ + v = va_arg(*args, TYPE*); \ + memcpy(v, b, sizeof(TYPE)); \ + return (int)sizeof(TYPE); \ +} + +#else + +#define GT_ENCODE_ELEM(TYPE,b,l,args) {if (l < sizeof(TYPE)) return -1; gt2MemCopy(b,(const char *)&va_arg(*args,TYPE),sizeof(TYPE)); return sizeof(TYPE);} +#define GT_DECODE_ELEM(TYPE,b,l,args) {if (l < sizeof(TYPE)) return -1; gt2MemCopy((char *)va_arg(*args,TYPE*),b,sizeof(TYPE)); return sizeof(TYPE);} +// nc = no endian convert +#define GT_ENCODE_ELEM_NC(TYPE,b,l,args) {if (l < sizeof(TYPE)) return -1; memcpy(b,&va_arg(*args,TYPE),sizeof(TYPE)); return sizeof(TYPE);} +#define GT_DECODE_ELEM_NC(TYPE,b,l,args) {if (l < sizeof(TYPE)) return -1; memcpy(va_arg(*args,TYPE*),b,sizeof(TYPE)); return sizeof(TYPE);} + +#endif /* _PS || _UNIX */ + +static int dbstrlen(GT_DBSTR_TYPE dbstr) +{ + int len = 0; +#ifdef ALIGNED_COPY + short achar; + do + { + memcpy(&achar, dbstr, sizeof(achar)); + dbstr++; + len++; + } while (achar != 0); + len--; +#else + while (*dbstr++) + len++; +#endif + return len; +} + +static short *dbstrcpy(GT_DBSTR_TYPE dest, GT_DBSTR_TYPE src) +{ + GT_DBSTR_TYPE hold = dest; + #ifdef ALIGNED_COPY + int len = dbstrlen(src); + memcpy(dest, src, (unsigned int)(len + 1) * 2); + #else + while ((*dest++ = *src++) != 0) ; + #endif + return hold; +} + +static int gtiDecodeBits(int bitcount, char *inBuffer, int inLength, va_list *args) +{ + char bucket; + int i; + + if (inLength < 1) + return -1; + bucket = *inBuffer; + for (i = 0 ; i < bitcount ; i++) + { + *va_arg(*args,char*) = (char)((bucket & (1 << i)) ? 1 : 0); + } + + return 1; +} + +static int gtiEncodeBits(int bitcount, char *outBuffer, int outLength, va_list *args) +{ + char bucket = 0; + int i; + + if (outLength < 1) + return -1; + for (i = 0 ; i < bitcount ; i++) + { + bucket |= (char)((va_arg(*args,int) ? 1 : 0) << i); + //bucket |= ((va_arg(*args,char) ? 1 : 0) << i); + } + *outBuffer = bucket; + return 1; +} + +// length in bytes including NUL, or -1 if error +static int gtiCheckStringLen(char *inBuffer, int inLength) +{ + int len = 0; + do + { + len++; + if(len > inLength) + return -1; + } + while(inBuffer[len - 1] != '\0'); + return len; +} + +// length in bytes (not chars) including NUL, or -1 if error +static int gtiCheckDoubleStringLen(char *inBuffer, int inLength) +{ + int len = 0; + do + { + len += 2; + if(len > inLength) + return -1; + } + while((inBuffer[len - 2] != '\0') || (inBuffer[len - 1] != '\0')); + return len; +} + +// length in bytes including NULs, or -1 if error +static int gtiCheckStringArrayLen(char *inBuffer, int inLength) +{ + int len = 0; + int strLen; + do + { + strLen = gtiCheckStringLen(inBuffer + len, inLength - len); + if(strLen == -1) + return -1; + len += strLen; + } + while(strLen > 1); + return len; +} + +static int gtiDecodeSingle(char elemType, char *inBuffer, int inLength, va_list *args) +{ + switch (elemType) + { + case GT_INT: + GT_DECODE_ELEM(GT_INT_TYPE,inBuffer, inLength, args); + //break; + case GT_UINT: + GT_DECODE_ELEM(GT_UINT_TYPE,inBuffer, inLength, args); + //break; + case GT_SHORT: + GT_DECODE_ELEM(GT_SHORT_TYPE,inBuffer, inLength, args); + //break; + case GT_USHORT: + GT_DECODE_ELEM(GT_USHORT_TYPE,inBuffer, inLength, args); + //break; + case GT_CHAR: + GT_DECODE_ELEM(GT_CHAR_TYPE,inBuffer, inLength, args); + //break; + case GT_UCHAR: + GT_DECODE_ELEM(GT_UCHAR_TYPE,inBuffer, inLength, args); + //break; + case GT_FLOAT: + { + #if(0) + // no endian convert + GT_FLOAT_TYPE* v; + if (inLength < sizeof(GT_FLOAT_TYPE)) + return -1; + v = va_arg(*args, GT_FLOAT_TYPE*); + v[0] = inBuffer[0]; + v[1] = inBuffer[1]; + v[2] = inBuffer[2]; + v[3] = inBuffer[3]; + return (int)sizeof(GT_FLOAT_TYPE); + #else + GT_DECODE_ELEM_NC(GT_FLOAT_TYPE,inBuffer, inLength, args); + #endif + } + case GT_DOUBLE: + #if(0) + // no endian convert + { GT_DOUBLE_TYPE* v; + if (inLength < sizeof(GT_DOUBLE_TYPE)) + return -1; + v = va_arg(*args, GT_DOUBLE_TYPE*); + v[0] = inBuffer[0]; + v[1] = inBuffer[1]; + v[2] = inBuffer[2]; + v[3] = inBuffer[3]; + v[4] = inBuffer[4]; + v[5] = inBuffer[5]; + v[6] = inBuffer[6]; + v[7] = inBuffer[7]; + return (int)sizeof(GT_DOUBLE_TYPE); + } + #else + GT_DECODE_ELEM_NC(GT_DOUBLE_TYPE,inBuffer, inLength, args); + #endif + //break; + case GT_BIT: + GT_DECODE_ELEM(GT_BIT_TYPE,inBuffer, inLength, args); + //break; + case GT_CSTR: + { + int len; + GT_CSTR_TYPE s = va_arg(*args, GT_CSTR_TYPE); + assert(s != NULL); + len = gtiCheckStringLen(inBuffer, inLength); + if(len == -1) + return -1; + memcpy(s, inBuffer, (size_t)len); + return len; + } + //break; + case GT_CSTR_PTR: + *va_arg(*args, GT_CSTR_PTR_TYPE) = (GT_CSTR_TYPE)inBuffer; + return gtiCheckStringLen(inBuffer, inLength); + //break; + case GT_DBSTR: + { + int len; + GT_DBSTR_TYPE s = va_arg(*args, GT_DBSTR_TYPE); + assert(s != NULL); + len = gtiCheckDoubleStringLen(inBuffer, inLength); + if (len == -1) + return -1; + memcpy(s, inBuffer, (size_t)len); + return len; + } + //break; + case GT_DBSTR_PTR: + *va_arg(*args, GT_DBSTR_PTR_TYPE) = (GT_DBSTR_TYPE)inBuffer; + return gtiCheckDoubleStringLen(inBuffer, inLength); + //break; + case GT_CSTR_ARRAY: + { + int len; + GT_CSTR_ARRAY_TYPE s = va_arg(*args, GT_CSTR_ARRAY_TYPE); + assert(s != NULL); + len = gtiCheckStringArrayLen(inBuffer, inLength); + if(len == -1) + return -1; + memcpy(s, inBuffer, (size_t)len); + return len; + } + //break; + case GT_CSTR_ARRAY_PTR: + *va_arg(*args, GT_CSTR_ARRAY_PTR_TYPE) = (GT_CSTR_ARRAY_TYPE)inBuffer; + return gtiCheckStringArrayLen(inBuffer, inLength); + //break; + case GT_RAW: + { + int *len, holdlen; + GT_RAW_TYPE data = va_arg(*args, GT_RAW_TYPE); + len = va_arg(*args, int *); + if (inLength < sizeof(*len)) + return -1; + holdlen = *len; + memcpy(len, inBuffer, sizeof(*len)); + if (*len > holdlen) //there isn't enough room in their dest! + return -1; + if (inLength < (int)sizeof(*len) + *len) + return -1; + memcpy(data, inBuffer + sizeof(*len), (unsigned int)*len); + return *len + (int)sizeof(*len); + } + case GT_RAW_PTR: + { + int *len; + *va_arg(*args, GT_RAW_PTR_TYPE) = (GT_RAW_TYPE)(inBuffer + sizeof(*len)); + len = va_arg(*args, int *); + if (inLength < sizeof(*len)) + return -1; + memcpy(len, inBuffer, sizeof(*len)); + return *len + (int)sizeof(*len); + } + //break; + + } + return -1; //bad type! +} + +static int gtiEncodeSingle(char elemType, char *outBuffer, int outLength, va_list *args) +{ + switch (elemType) + { + case GT_INT: + GT_ENCODE_ELEM(GT_INT_TYPE,outBuffer, outLength, args); + //break; + case GT_UINT: + GT_ENCODE_ELEM(GT_UINT_TYPE,outBuffer, outLength, args); + //break; + case GT_SHORT: + GT_ENCODE_ELEM(GT_SHORT_TYPE,outBuffer, outLength, args); + //break; + case GT_USHORT: + GT_ENCODE_ELEM(GT_USHORT_TYPE,outBuffer, outLength, args); + //break; + case GT_CHAR: + GT_ENCODE_ELEM(GT_CHAR_TYPE,outBuffer, outLength, args); + //break; + case GT_UCHAR: + GT_ENCODE_ELEM(GT_UCHAR_TYPE,outBuffer, outLength, args); + //break; + case GT_FLOAT: //floats are promoted to double in varargs, need to demote + { + double temp; + float f; + double v = va_arg(*args,double); + memcpy(&temp,&v,sizeof(double)); + f = (float)temp; + if (outLength < sizeof(float)) + return -1; + memcpy(outBuffer, &f, sizeof(float)); + return sizeof(float); + } + //break; + case GT_DOUBLE: + { + double v; + if(outLength < sizeof(double)) + return -1; + v = va_arg(*args, double); + memcpy(outBuffer, &v, sizeof(double)); + return sizeof(double); + } + //break; + case GT_BIT: + GT_ENCODE_ELEM(GT_BIT_TYPE,outBuffer, outLength, args); + //break; + case GT_CSTR: + case GT_CSTR_PTR: + { + int len; + GT_CSTR_TYPE s = va_arg(*args, GT_CSTR_TYPE); + assert(s != NULL); + len = (int)strlen(s) + 1; + if (outLength < len ) + return -1; + strcpy(outBuffer, s); + return len; + } + //break; + case GT_DBSTR: + case GT_DBSTR_PTR: + { + int len; + GT_DBSTR_TYPE s = va_arg(*args, GT_DBSTR_TYPE); + assert(s != NULL); + len = dbstrlen(s) + 1; + if (outLength < len * 2) + return -1; + dbstrcpy((short *)outBuffer, s); + return len * 2; + } + //break; + case GT_CSTR_ARRAY: + case GT_CSTR_ARRAY_PTR: + { + int len = 0; + int strLen; + GT_CSTR_ARRAY_TYPE s = va_arg(*args, GT_CSTR_ARRAY_TYPE); + assert(s != NULL); + do + { + strLen = (int)strlen(s + len) + 1; + len += strLen; + if(outLength < len) + return -1; + } + while(strLen != 1); + memcpy(outBuffer, s, (size_t)len); + return len; + } + //break; + case GT_RAW: + case GT_RAW_PTR: + { + int len; + GT_RAW_TYPE data = va_arg(*args, GT_RAW_TYPE); + len = va_arg(*args, int); + if (outLength < len + (int)sizeof(len)) + return -1; + memcpy(outBuffer, &len, sizeof(len)); + memcpy(outBuffer + sizeof(len), data, (unsigned int)len); + return len + (int)sizeof(len); + } + + } + return -1; //bad type! +} + +static int gtInternalEncodeV(int usetype, GTMessageType msgType, const char *fmtString, char *outBuffer, int outLength, va_list *args) +{ + int elemSize; + int totSize = outLength; + const char *bitCounter; + + //set the message type + if (usetype) + { + elemSize = sizeof(msgType); + if (outLength < elemSize) + return -1; + + gt2MemCopy(outBuffer, (const char *)&msgType, elemSize); + outBuffer += elemSize; + outLength -= elemSize; + } + while (*fmtString) + { + if (*fmtString == GT_BIT) //see how many + { + for (bitCounter = fmtString; *bitCounter == GT_BIT && bitCounter - fmtString <= 8; bitCounter++) + {}; + elemSize = gtiEncodeBits((int)(bitCounter - fmtString), outBuffer, outLength, args); + fmtString = bitCounter - 1; + } else + elemSize = gtiEncodeSingle(*fmtString, outBuffer, outLength, args); + if (elemSize < 0) + return -1; //out of space + outBuffer += elemSize; + outLength -= elemSize; + fmtString++; + } + return totSize - outLength; +} + +int gtEncodeNoTypeV(const char *fmtString, char *outBuffer, int outLength, va_list *args) +{ + return gtInternalEncodeV(0,0,fmtString, outBuffer, outLength, args); +} + +int gtEncodeV(GTMessageType msgType, const char *fmtString, char *outBuffer, int outLength, va_list *args) +{ + return gtInternalEncodeV(1,msgType,fmtString, outBuffer, outLength, args); +} + +int gtEncode(GTMessageType msgType, const char *fmtString, char *outBuffer, int outLength, ...) +{ + int rcode; + va_list args; + + //set the values + va_start(args, outLength); + rcode = gtEncodeV(msgType, fmtString, outBuffer, outLength, &args); + va_end(args); + + return rcode; +} + +int gtEncodeNoType(const char *fmtString, char *outBuffer, int outLength, ...) +{ + int rcode; + va_list args; + + //set the values + va_start(args, outLength); + rcode = gtEncodeNoTypeV(fmtString, outBuffer, outLength, &args); + va_end(args); + + return rcode; +} + +static int gtDecodeInternalV(int usetype, const char *fmtString, char *inBuffer, int inLength, va_list *args) +{ + int elemSize; + int totSize = inLength; + const char *bitCounter; + + //skip the message type + if (usetype) + { + inBuffer += sizeof(GTMessageType); + inLength -= sizeof(GTMessageType); + } + + while (*fmtString) + { + if (*fmtString == GT_BIT) //see how many + { + for (bitCounter = fmtString; *bitCounter == GT_BIT && bitCounter - fmtString <= 8; bitCounter++) + {}; + elemSize = gtiDecodeBits((int)(bitCounter - fmtString), inBuffer, inLength, args); + fmtString = bitCounter - 1; + } else + elemSize = gtiDecodeSingle(*fmtString, inBuffer, inLength, args); + if (elemSize < 0) + return -1; //out of space + inBuffer += elemSize; + inLength -= elemSize; + fmtString++; + } + //NOTE: inLength should be 0 here if we "ate" the whole message + //If it's not 0, then the encoding and decoding strings probably did not match + //which would generally indicate a bug + //PANTS - commented out because we could be decoding the rest with a gtDecodeNoType +// assert(inLength == 0); + return totSize - inLength; +} + +int gtDecodeV(const char *fmtString, char *inBuffer, int inLength, va_list *args) +{ + return gtDecodeInternalV(1,fmtString, inBuffer, inLength, args); +} + +int gtDecodeNoTypeV(const char *fmtString, char *inBuffer, int inLength, va_list *args) +{ + return gtDecodeInternalV(0,fmtString, inBuffer, inLength, args); +} + +int gtDecode(const char *fmtString, char *inBuffer, int inLength, ...) +{ + int rcode; + va_list args; + + //set the values + va_start(args, inLength); + rcode = gtDecodeV(fmtString, inBuffer, inLength, &args); + va_end(args); + + return rcode; +} + +int gtDecodeNoType(const char *fmtString, char *inBuffer, int inLength, ...) +{ + int rcode; + va_list args; + + //set the values + va_start(args, inLength); + rcode = gtDecodeNoTypeV(fmtString, inBuffer, inLength, &args); + va_end(args); + + return rcode; +} + +GTMessageType gtEncodedMessageType(char *inBuffer) +{ + GTMessageType type; + //GS_ASSERT(sizeof(GTMessageType) ==2 ) + gt2MemCopy16((char *)&type, inBuffer); + return type; +} + +// change the message type for an encoded message +void gtEncodedMessageTypeSet (char *inBuffer, GTMessageType newtype) +{ + gt2MemCopy16(inBuffer, (char *)&newtype); +} + diff --git a/code/gamespy/gt2/gt2Encode.h b/code/gamespy/gt2/gt2Encode.h new file mode 100644 index 00000000..4e49fba5 --- /dev/null +++ b/code/gamespy/gt2/gt2Encode.h @@ -0,0 +1,180 @@ +//TODO: Address Byte order & Byte alignment issues +#ifndef _GT_ENCODE_H +#define _GT_ENCODE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(UNDER_CE) || defined(__mips64) || defined(_PSP) +#define ALIGNED_COPY +//the aligned copy code all needs to be optimized +#endif + + + + +// Used to identify the type of message so you can look up the correct format string +// and pass the correct parameters +// You should use 1 msgType for each unique format string/parameter combination +//////////////////////////////////////////////////////// +typedef unsigned short GTMessageType; + +// Encode a message into outBuffer +// Returns the length of the encoded message, or -1 to indicate insufficient space +// You must make sure the number of arguments match the fmtString list +//////////////////////////////////////////////////////// +int gtEncode(GTMessageType msgType, const char *fmtString, char *outBuffer, int outLength, ...); +int gtEncodeV(GTMessageType msgType, const char *fmtString, char *outBuffer, int outLength, va_list *args); +int gtEncodeNoType(const char *fmtString, char *outBuffer, int outLength, ...); +int gtEncodeNoTypeV(const char *fmtString, char *outBuffer, int outLength, va_list *args); + +// Decode the message from inBuffer into the vars provided +// Returns -1 if there was a problem with the buffer +// Vars should all be pointers (as if using scanf) +// You must make sure the number of arguments match the fmtString list +//////////////////////////////////////////////////////// +int gtDecode(const char *fmtString, char *inBuffer, int inLength, ...); +int gtDecodeV(const char *fmtString, char *inBuffer, int inLength, va_list *args); +int gtDecodeNoType(const char *fmtString, char *inBuffer, int inLength, ...); +int gtDecodeNoTypeV(const char *fmtString, char *inBuffer, int inLength, va_list *args); + +// Retrieve the message type for an encoded message +//////////////////////////////////////////////////////// +GTMessageType gtEncodedMessageType (char *inBuffer); +// change the message type for an encoded message +void gtEncodedMessageTypeSet (char *inBuffer, GTMessageType newtype); + +// This handles alignment issues and endianess +///////////////////////////////////////////////////////// +void gt2MemCopy16(char *out, char const *in); +void gt2MemCopy32(char *out, char const *in); +void gt2MemCopy64(char *out, char const *in); +void gt2MemCopy(char *out, char const *in, int size); + +/**************************** +Types that can be sent using the encode/decode functions +Most are self-explanatory, but the following require some clarification: + +GT_CSTR: This is a NUL terminated C-string. The length is determined automatically. +The NUL character is restored in decode. You simply pass the char * string as +the arguement for Encode. Note that in the pointer passed to Decode must have enough +memory allocated to it for the max string that will be encoded - otherwise +the destination may get trashed. If you cannot guarantee them max length of the +string, you should use GT_RAW (see below). + +GT_DBSTR: Same as a GT_CSTR, except with 2-byte characters instead of single byte. +String must be terminated with a double NUL character. + +GT_RAW: Use raw to send data blocks, structures, arrays, etc - although you must +make sure they're the same on all platforms! +Requires you to pass both a buffer and a length arguement to Encode and Decode. +You should pass the buffer first, then the length. +For Decode, you must initialize the length pointer to the max length of the buffer. +If the decoded data exceeds this length, the Decode function will return -1, and the +length pointer will be set to the actual length. You can then call Decode again with +a buffer that is at least the required length. +Example: +gtEncode(0, "r", buf, buflen, "somerawdata",10); +char *rawbuffer = malloc(5); +char rawlen = 5; +ret = gtDecode(0, "r", buf, buflen, rawbuffer, &rawlen); +//gtDecode will return -1, and rawlen will be set to 10 +if (ret == -1) +{ + rawbuffer = realloc(rawbuffer, rawlen); + gtDecode(0, "r", buf, buflen, rawbuffer, &rawlen); + //gtDecode will now succeed +} + +GT_CSTR_PTR, GT_DBSTR_PTR, GT_RAW_PTR: For Decode, instead of copying the data from +input buffer into the pointer provided, the pointer is simply set to the +offset of the data in the input buffer. This removes the need to allocate +memory for the pointers before hand, and elimantes an extra memory copy. +You will need to pass in double pointers (e.g. char **) so that the pointer +can be changed. +However, you must make sure you don't try to use the pointers after the +input buffer is freed/changed. +Note that the buffer passed in gtReceivedCallback must be copied off if +you want to continue using it after you return from the callback (or, you +can use the non-PTR versions that copy off into the buffers you provide +automatically) +You can pass the _PTR versions to Encode and they will behave exactly as +the regular versions. + +GT_CSTR_PTR, GT_CSTR_ARRAY_PTR: Same as GT_CSTR and GT_CSTR_PTR, but uses +an array of strings instead of a single string. The array of strings is +terminated by an empty string (a single NUL character). + +GT_BIT: If you pass all your bits together in the format string, they will be +packed together to save space. So, the format string "zzzzzzzz" will only take +1 byte for the data (+2 bytes for the message type). +Note that if you have other types between the bits, the packing will NOT occur, +e.g.: "ziz" will use 6 bytes (2 for the bits, 4 for the int), whereas "zzi" would use +only 5 bytes (1 for the bits, 4 for the int) +The argument type for bits is char for Encode and char * for Decode - +If the char is 0, the bit will not be set, if it's non-zero, the bit will be set. +Note that in Decode, the set bit will always be returned as 1 (not the non-zero value +you set) +*/ + +#define GT_INT 'i' +#define GT_INT_ "i" +#define GT_INT_TYPE int +#define GT_UINT 'u' +#define GT_UINT_ "u" +#define GT_UINT_TYPE unsigned int +#define GT_SHORT 'o' +#define GT_SHORT_ "o" +#define GT_SHORT_TYPE short +#define GT_USHORT 'p' +#define GT_USHORT_ "p" +#define GT_USHORT_TYPE unsigned short +#define GT_CHAR 'c' +#define GT_CHAR_ "c" +#define GT_CHAR_TYPE signed char +#define GT_UCHAR 'b' +#define GT_UCHAR_ "b" +#define GT_UCHAR_TYPE unsigned char +#define GT_FLOAT 'f' +#define GT_FLOAT_ "f" +#define GT_FLOAT_TYPE float +#define GT_DOUBLE 'd' +#define GT_DOUBLE_ "d" +#define GT_DOUBLE_TYPE double +#define GT_CSTR 's' +#define GT_CSTR_ "s" +#define GT_CSTR_TYPE char * +#define GT_CSTR_PTR 'S' +#define GT_CSTR_PTR_ "S" +#define GT_CSTR_PTR_TYPE char ** +#define GT_DBSTR 'w' +#define GT_DBSTR_ "w" +#define GT_DBSTR_TYPE short * +#define GT_DBSTR_PTR 'W' +#define GT_DBSTR_PTR_ "W" +#define GT_DBSTR_PTR_TYPE short ** +#define GT_CSTR_ARRAY 'a' +#define GT_CSTR_ARRAY_ "a" +#define GT_CSTR_ARRAY_TYPE char * +#define GT_CSTR_ARRAY_PTR 'A' +#define GT_CSTR_ARRAY_PTR_ "A" +#define GT_CSTR_ARRAY_PTR_TYPE char ** +#define GT_RAW 'r' //two parameters! (data, then length) +#define GT_RAW_ "r" //two parameters! +#define GT_RAW_TYPE char * +#define GT_RAW_PTR 'R' +#define GT_RAW_PTR_ "R" +#define GT_RAW_PTR_TYPE char ** +#define GT_BIT 'z' +#define GT_BIT_ "z" +#define GT_BIT_TYPE unsigned char + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/gt2/gt2Filter.c b/code/gamespy/gt2/gt2Filter.c new file mode 100644 index 00000000..27195af7 --- /dev/null +++ b/code/gamespy/gt2/gt2Filter.c @@ -0,0 +1,191 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Filter.h" +#include "gt2Callback.h" +#include "gt2Message.h" +#include "gt2Utility.h" + +static int GS_STATIC_CALLBACK gti2SendFiltersCompare +( + const void * elem1, + const void * elem2 +) +{ + gt2SendFilterCallback * callback1 = (gt2SendFilterCallback *)elem1; + gt2SendFilterCallback * callback2 = (gt2SendFilterCallback *)elem2; + + if(*callback1 == *callback2) + return 0; + + return 1; +} + +static int GS_STATIC_CALLBACK gti2ReceiveFiltersCompare +( + const void * elem1, + const void * elem2 +) +{ + gt2ReceiveFilterCallback * callback1 = (gt2ReceiveFilterCallback *)elem1; + gt2ReceiveFilterCallback * callback2 = (gt2ReceiveFilterCallback *)elem2; + + if(*callback1 == *callback2) + return 0; + + return 1; +} + +GT2Bool gti2AddSendFilter(GT2Connection connection, gt2SendFilterCallback callback) +{ + // Check if we have a send filters list. + if(!connection->sendFilters) + return GT2False; + + // Add this callback to the list. + ArrayAppend(connection->sendFilters, &callback); + + // Return GT2True if it was added. + return (ArraySearch(connection->sendFilters, &callback, gti2SendFiltersCompare, 0, 0) != NOT_FOUND); +} + +GT2Bool gti2AddReceiveFilter(GT2Connection connection, gt2ReceiveFilterCallback callback) +{ + // Check if we have a receive filters list. + if(!connection->receiveFilters) + return GT2False; + + // Add this callback to the list. + ArrayAppend(connection->receiveFilters, &callback); + + // Return GT2True if it was added. + return (ArraySearch(connection->receiveFilters, &callback, gti2ReceiveFiltersCompare, 0, 0) != NOT_FOUND); +} + +void gti2RemoveSendFilter(GT2Connection connection, gt2SendFilterCallback callback) +{ + int index; + + // Check for no filters. + if(!connection->sendFilters) + return; + + // check for removing all + if(!callback) + { + // Remove all the filters. + ArrayClear(connection->sendFilters); + return; + } + + // Find it. + index = ArraySearch(connection->sendFilters, &callback, gti2SendFiltersCompare, 0, 0); + if(index == NOT_FOUND) + return; + + // Remove it. + ArrayRemoveAt(connection->sendFilters, index); +} + +void gti2RemoveReceiveFilter(GT2Connection connection, gt2ReceiveFilterCallback callback) +{ + int index; + + // Check for no filters. + if(!connection->receiveFilters) + return; + + // check for removing all + if(!callback) + { + // Remove all the filters. + ArrayClear(connection->receiveFilters); + return; + } + + // Find it. + index = ArraySearch(connection->receiveFilters, &callback, gti2ReceiveFiltersCompare, 0, 0); + if(index == NOT_FOUND) + return; + + // Remove it. + ArrayRemoveAt(connection->receiveFilters, index); +} + +GT2Bool gti2FilteredSend(GT2Connection connection, int filterID, const GT2Byte * message, int len, GT2Bool reliable) +{ + int num; + + // Make sure we're connected. + if(connection->state != GTI2Connected) + return GT2True; + + // check the message and len + gti2MessageCheck(&message, &len); + + // Get the number of filters. + num = ArrayLength(connection->sendFilters); + + // Check if its a valid ID. + if(filterID < 0) + return GT2True; + if(filterID >= num) + return GT2True; + + // Is it the last one? + if(filterID == (num - 1)) + { + // Do the actual send. + if(!gti2Send(connection, message, len, reliable)) + return GT2False; + } + else + { + // Filter it. + if(!gti2SendFilterCallback(connection, ++filterID, message, len, reliable)) + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2FilteredReceive(GT2Connection connection, int filterID, GT2Byte * message, int len, GT2Bool reliable) +{ + int num; + + // Make sure we're connected. + if(connection->state != GTI2Connected) + return GT2True; + + // Get the number of filters. + num = ArrayLength(connection->receiveFilters); + + // Check if its a valid ID. + if(filterID < 0) + return GT2True; + if(filterID >= num) + return GT2True; + + // Is it the last one? + if(filterID == (num - 1)) + { + // call the callback + if(!gti2ReceivedCallback(connection, message, len, reliable)) + return GT2False; + } + else + { + // Filter it. + if(!gti2ReceiveFilterCallback(connection, ++filterID, message, len, reliable)) + return GT2False; + } + + return GT2True; +} diff --git a/code/gamespy/gt2/gt2Filter.h b/code/gamespy/gt2/gt2Filter.h new file mode 100644 index 00000000..a3595d97 --- /dev/null +++ b/code/gamespy/gt2/gt2Filter.h @@ -0,0 +1,24 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_FILTER_H_ +#define _GT2_FILTER_H_ + +#include "gt2Main.h" + +GT2Bool gti2AddSendFilter(GT2Connection connection, gt2SendFilterCallback callback); +void gti2RemoveSendFilter(GT2Connection connection, gt2SendFilterCallback callback); +GT2Bool gti2FilteredSend(GT2Connection connection, int filterID, const GT2Byte * message, int len, GT2Bool reliable); + +GT2Bool gti2AddReceiveFilter(GT2Connection connection, gt2ReceiveFilterCallback callback); +void gti2RemoveReceiveFilter(GT2Connection connection, gt2ReceiveFilterCallback callback); +GT2Bool gti2FilteredReceive(GT2Connection connection, int filterID, GT2Byte * message, int len, GT2Bool reliable); + +#endif diff --git a/code/gamespy/gt2/gt2Main.c b/code/gamespy/gt2/gt2Main.c new file mode 100644 index 00000000..9c688e2e --- /dev/null +++ b/code/gamespy/gt2/gt2Main.c @@ -0,0 +1,477 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Main.h" +#include "gt2Socket.h" +#include "gt2Connection.h" +#include "gt2Message.h" +#include "gt2Callback.h" +#include "gt2Filter.h" +#include "gt2Utility.h" + +#define GTI2_INVALID_IP_MASK 0xE0000000 + +/********************* +** SOCKET FUNCTIONS ** +*********************/ + +// Xbox VDP socket function(s). +#ifdef _XBOX +GT2Result gt2CreateVDPSocket +( + GT2Socket * socket, + const char * localAddress, + int outgoingBufferSize, + int incomingBufferSize, + gt2SocketErrorCallback callback +) +{ + return gti2CreateSocket(socket, localAddress, outgoingBufferSize, incomingBufferSize, callback, GTI2VdpProtocol); +} +#endif + + +GT2Result gt2CreateSocket +( + GT2Socket * socket, + const char * localAddress, + int outgoingBufferSize, + int incomingBufferSize, + gt2SocketErrorCallback callback +) +{ + return gti2CreateSocket(socket, localAddress, outgoingBufferSize, incomingBufferSize, callback, GTI2UdpProtocol); +} + +GT2Result gt2CreateAdHocSocket +( + GT2Socket * socket, + const char * localAddress, + int outgoingBufferSize, + int incomingBufferSize, + gt2SocketErrorCallback callback +) +{ + return gti2CreateSocket(socket, localAddress, outgoingBufferSize, incomingBufferSize, callback, GTI2AdHocProtocol); +} + + +void gt2CloseSocket(GT2Socket socket) +{ + // hard close the connections + gt2CloseAllConnectionsHard(socket); + + // close the socket + gti2CloseSocket(socket); +} + +void gt2Think(GT2Socket socket) +{ + // check for incoming messages + if(!gti2ReceiveMessages(socket)) + return; + + // let the connections think + if(!gti2SocketConnectionsThink(socket)) + return; + + // free closed connections + gti2FreeClosedConnections(socket); +} + +GT2Result gt2SendRawUDP +( + GT2Socket socket, + const char * remoteAddress, + const GT2Byte * message, + int len +) +{ + unsigned int ip; + unsigned short port; + + // get the ip and port + if(!gt2StringToAddress(remoteAddress, &ip, &port) || !port) + return GT2AddressError; + + // check for invalid IP ranges + // class D (224-239.*, multicast) and class E (240-255.*, experimental) + if((ntohl(ip) & GTI2_INVALID_IP_MASK) == GTI2_INVALID_IP_MASK) + return GT2AddressError; + + // check if this is for broadcast + if(!ip) + { + // check if broadcast is enable + if(!socket->broadcastEnabled) + { + if(!SetSockBroadcast(socket->socket)) + return GT2NetworkError; + socket->broadcastEnabled = GT2True; + } + + // set the broadcast ip + ip = gsiGetBroadcastIP(); + } + + // send the datagram + gti2SocketSend(socket, ip, port, message, len); + + return GT2Success; +} + +/********************* +** LISTEN FUNCTIONS ** +*********************/ + +void gt2Listen(GT2Socket socket, gt2ConnectAttemptCallback callback) +{ + gti2Listen(socket, callback); +} + +GT2Bool gt2Accept(GT2Connection connection, GT2ConnectionCallbacks * callbacks) +{ + return gti2AcceptConnection(connection, callbacks); +} + +void gt2Reject(GT2Connection connection, const GT2Byte * message, int len) +{ + gti2RejectConnection(connection, message, len); +} + +/************************* +** CONNECTION FUNCTIONS ** +*************************/ + +GT2Result gt2Connect +( + GT2Socket socket, + GT2Connection * connection, + const char * remoteAddress, + const GT2Byte * message, + int len, + int timeout, + GT2ConnectionCallbacks * callbacks, + GT2Bool blocking +) +{ + GT2Connection connectionTemp; + GT2Result result; + GT2Bool done; + unsigned int ip; + unsigned short port; + + { + // get the ip and port + if(!gt2StringToAddress(remoteAddress, &ip, &port) || !ip || !port) + return GT2AddressError; + } + + // check for invalid IP ranges + // class D (224-239.*, multicast) and class E (240-255.*, experimental) + if((ntohl(ip) & GTI2_INVALID_IP_MASK) == GTI2_INVALID_IP_MASK) + return GT2AddressError; + + // create the connection object + result = gti2NewOutgoingConnection(socket, &connectionTemp, ip, port); + if(result) + return result; + + // save the timeout value + connectionTemp->timeout = (unsigned int)timeout; + + // initiate the connection attempt + result = gti2StartConnectionAttempt(connectionTemp, message, len, callbacks); + if(result) + { + gti2FreeSocketConnection(connectionTemp); + return result; + } + + // if not blocking, return now + if(!blocking) + { + if(connection) + *connection = connectionTemp; + return GT2Success; + } + + // we're not really in a callback, but this will prevent the connection + // from being freed before the loop finishes. + connectionTemp->callbackLevel++; + + // if blocking, loop until the connect attempt is done + do + { + // think + gt2Think(socket); + + // check if we're done + done = (connectionTemp->state >= GTI2Connected); + + // if we're not done, take a rest + if(!done) + msleep(1); + } while(!done); + + // bring the callback level back down + connectionTemp->callbackLevel--; + + // is it success? + if(connectionTemp->state == GTI2Connected) + *connection = connectionTemp; + + return connectionTemp->connectionResult; +} + +GT2Result gt2Send +( + GT2Connection connection, + const GT2Byte * message, + int len, + GT2Bool reliable +) +{ + // used to check for voice data in reliable messages + unsigned short vdpDataLength; + + // can't send a message if not connected + if(connection->state != GTI2Connected) + return GT2InvalidConnection; + + // check the message and len + gti2MessageCheck(&message, &len); + + if (reliable && connection->socket->protocolType == GTI2VdpProtocol) + { + memcpy(&vdpDataLength, message, sizeof(unsigned short)); + assert(vdpDataLength + connection->socket->protocolOffset == len); + if (vdpDataLength + connection->socket->protocolOffset != len) + return GT2InvalidMessage; + } + + // do we need to filter it? + if(ArrayLength(connection->sendFilters)) + { + gti2SendFilterCallback(connection, 0, message, len, reliable); + return GT2Success; + } + + if (gti2Send(connection, message, len, reliable)) + return GT2Success; + + return GT2SendFailed; +} + +void gt2Ping(GT2Connection connection) +{ + gti2SendPing(connection); +} + +void gt2CloseConnection(GT2Connection connection) +{ + gti2CloseConnection(connection, GT2False); +} + +void gt2CloseConnectionHard(GT2Connection connection) +{ + gti2CloseConnection(connection, GT2True); +} + +static void gti2CloseAllConnectionsMap(void * elem, void * clientData) +{ + gt2CloseConnection(*(GT2Connection *)elem); + + GSI_UNUSED(clientData); +} + +void gt2CloseAllConnections(GT2Socket socket) +{ + TableMapSafe(socket->connections, gti2CloseAllConnectionsMap, NULL); +} + +static void gti2CloseAllConnectionsHardMap(void * elem, void * clientData) +{ + gt2CloseConnectionHard(*(GT2Connection *)elem); + + GSI_UNUSED(clientData); +} + +void gt2CloseAllConnectionsHard(GT2Socket socket) +{ + TableMapSafe(socket->connections, gti2CloseAllConnectionsHardMap, NULL); +} + +/************************* +** MESSAGE CONFIRMATION ** +*************************/ +GT2MessageID gt2GetLastSentMessageID(GT2Connection connection) +{ + return (GT2MessageID)(connection->serialNumber - 1); +} + +GT2Bool gt2WasMessageIDConfirmed(GT2Connection connection, GT2MessageID messageID) +{ + return gti2WasMessageIDConfirmed(connection, messageID); +} + +/********************* +** FILTER FUNCTIONS ** +*********************/ + +GT2Bool gt2AddSendFilter(GT2Connection connection, gt2SendFilterCallback callback) +{ + return gti2AddSendFilter(connection, callback); +} + +void gt2RemoveSendFilter(GT2Connection connection, gt2SendFilterCallback callback) +{ + gti2RemoveSendFilter(connection, callback); +} + +void gt2FilteredSend(GT2Connection connection, int filterID, const GT2Byte * message, int len, GT2Bool reliable) +{ + gti2FilteredSend(connection, filterID, message, len, reliable); +} + +GT2Bool gt2AddReceiveFilter(GT2Connection connection, gt2ReceiveFilterCallback callback) +{ + return gti2AddReceiveFilter(connection, callback); +} + +void gt2RemoveReceiveFilter(GT2Connection connection, gt2ReceiveFilterCallback callback) +{ + gti2RemoveReceiveFilter(connection, callback); +} + +void gt2FilteredReceive(GT2Connection connection, int filterID, GT2Byte * message, int len, GT2Bool reliable) +{ + gti2FilteredReceive(connection, filterID, message, len, reliable); +} + +/******************* +** INFO FUNCTIONS ** +*******************/ + +GT2Socket gt2GetConnectionSocket(GT2Connection connection) +{ + return connection->socket; +} + +GT2ConnectionState gt2GetConnectionState(GT2Connection connection) +{ + if(connection->state < GTI2Connected) + return GT2Connecting; + if(connection->state == GTI2Connected) + return GT2Connected; + if(connection->state == GTI2Closing) + return GT2Closing; + return GT2Closed; +} + +unsigned int gt2GetRemoteIP(GT2Connection connection) +{ + return connection->ip; +} + +unsigned short gt2GetRemotePort(GT2Connection connection) +{ + return connection->port; +} + +unsigned int gt2GetLocalIP(GT2Socket socket) +{ + return socket->ip; +} + +unsigned short gt2GetLocalPort(GT2Socket socket) +{ + return socket->port; +} + +int gt2GetIncomingBufferSize(GT2Connection connection) +{ + return connection->incomingBuffer.size; +} + +int gt2GetIncomingBufferFreeSpace(GT2Connection connection) +{ + return (connection->incomingBuffer.size - connection->incomingBuffer.len); +} + +int gt2GetOutgoingBufferSize(GT2Connection connection) +{ + return connection->outgoingBuffer.size; +} + +int gt2GetOutgoingBufferFreeSpace(GT2Connection connection) +{ + return (connection->outgoingBuffer.size - connection->outgoingBuffer.len); +} + +/***************************** +** SOCKET SHARING FUNCTIONS ** +*****************************/ + +SOCKET gt2GetSocketSOCKET(GT2Socket socket) +{ + return socket->socket; +} + +void gt2SetUnrecognizedMessageCallback(GT2Socket socket, gt2UnrecognizedMessageCallback callback) +{ + socket->unrecognizedMessageCallback = callback; +} + +/************************ +** USER DATA FUNCTIONS ** +************************/ + +void gt2SetSocketData(GT2Socket socket, void * data) +{ + assert(socket); + + socket->data = data; +} + +void * gt2GetSocketData(GT2Socket socket) +{ + assert(socket); + + return socket->data; +} + +void gt2SetConnectionData(GT2Connection connection, void * data) +{ + assert(connection); + + connection->data = data; +} + +void * gt2GetConnectionData(GT2Connection connection) +{ + assert(connection); + + return connection->data; +} + +/******************* +** DUMP FUNCTIONS ** +*******************/ + +void gt2SetSendDump(GT2Socket socket, gt2DumpCallback callback) +{ + socket->sendDumpCallback = callback; +} + +void gt2SetReceiveDump(GT2Socket socket, gt2DumpCallback callback) +{ + socket->receiveDumpCallback = callback; +} diff --git a/code/gamespy/gt2/gt2Main.h b/code/gamespy/gt2/gt2Main.h new file mode 100644 index 00000000..0915f45a --- /dev/null +++ b/code/gamespy/gt2/gt2Main.h @@ -0,0 +1,255 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_MAIN_H_ +#define _GT2_MAIN_H_ + +#include "gt2.h" +#include "../darray.h" +#include "../hashtable.h" +#include "gt2Auth.h" + +/************************* +** CONFIGURABLE DEFINES ** +*************************/ + +// these defines are internal to GT2 and are NOT guaranteed to persist from version to version. + + +// If set, this will convert all big endian vars to little endian before sending accross the net +// And on big endian machines, convert little endian to big endian on recv +//#define _GT2_ENDIAN_CONVERT_ENABLE // add this to your compiler pre-processor options + +#if defined GSI_BIG_ENDIAN && defined _GT2_ENDIAN_CONVERT_ENABLE + #define _GT2_ENDIAN_CONVERT +#endif + + + +// any unreliable application message that starts with this magic string will have extra overhead. +// the string can be changed to something that your application will not use, or not use frequently. +// the only impact of this change will be to make your application incomatible with other application's +// using either the original or another different magic string. +// the string can consist of any number of characters, as long as there's at least one character, and the +// length define matches the string's length. +#define GTI2_MAGIC_STRING "\xFE\xFE" +#define GTI2_MAGIC_STRING_LEN 2 + +// the size of the buffer into which GT2 directly receives messages. this buffer is declared on the stack, +// and so can be fairly large on most systems without having any impact. however, on some systems with small +// stacks, this size can overflow the stack, in which case it should be lowered. +// note, this buffer size only needs to be slighty larger than the largest message that will be sent ("slighty +// larger" due to overhead with reliable messages, and unreliable messages starting with the magic string). +#if defined(_PS2) && defined(INSOCK) + #define GTI2_STACK_RECV_BUFFER_SIZE NETBUFSIZE // Max for Insock. Otherwise SOCKET_ERROR +#elif defined(_NITRO) + #define GTI2_STACK_RECV_BUFFER_SIZE 1500 +#elif defined (_XBOX) // Xbox packets are 1304, + #define GTI2_STACK_RECV_BUFFER_SIZE 4096 // when using VDP sockets, 2 bytes are used for data length +#else + #define GTI2_STACK_RECV_BUFFER_SIZE 65535 +#endif + +// a server will disconnect a client that doesn't not successfully connect within this time (in milliseconds). +// if the connectAttemptCallback has been called, and GT2 is awaiting an accept/reject, the attempt will +// not be timed-out (although the client may abort the attempt at any time). +#define GTI2_SERVER_TIMEOUT (1 * 60 * 1000) +// the time (in milliseconds) GT2 waits between resending a message whose delivery has not yet been confirmed. +#define GTI2_RESEND_TIME 1000 +// the time (in milliseconds) GT2 waits after receiving a message it must acknowledge before it actually sends +// the ack. this allows it to combine acks, or include acks as part of other reliable messages it sends. +// if an ack is pending, a new incoming message does not reset this timer. +#define GTI2_PENDING_ACK_TIME 100 +// if GT2 does not send a message for this amount of time (in milliseconds), it sends a keep-alive message. +#define GTI2_KEEP_ALIVE_TIME (30 * 1000) +// if this is defined, it sets the percentage of sent datagrams to drop. this is good for simulating what will +// happen on a high packet loss connection. +//#define GTI2_DROP_SEND_RATE 30 +typedef enum +{ + GTI2UdpProtocol, // UDP socket type for standard sockets + GTI2VdpProtocol = 2, // VDP socket type only used for Xbox VDP sockets + GTI2AdHocProtocol = 3 // socket type only used for PSP Adhoc sockets +} GTI2ProtocolType; + +// The Maximum offset of eiter UDP or VDP +// measured in bytes +// used as a buffer offset +#define MAX_PROTOCOL_OFFSET 2 + +/********** +** TYPES ** +**********/ + +typedef enum +{ + // client-only states + GTI2AwaitingServerChallenge, // sent challenge, waiting for server's challenge + GTI2AwaitingAcceptance, // sent response, waiting for accept/reject from server + + // server-only states + GTI2AwaitingClientChallenge, // receiving challenge from a new client + GTI2AwaitingClientResponse, // sent challenge, waiting for client's response + GTI2AwaitingAcceptReject, // got client's response, waiting for app to accept/reject + + // post-negotiation states + GTI2Connected, // connected + GTI2Closing, // sent a close message (GTI2Close or GTI2Reject), waiting for confirmation + GTI2Closed // connection has been closed, free it as soon as possible +} GTI2ConnectionState; + +// message types +typedef enum +{ + // reliable messages + // all start with + // type is 1 bytes, SN and ESN are 2 bytes each + GTI2MsgAppReliable, // reliable application message + GTI2MsgClientChallenge, // client's challenge to the server (initial connection request) + GTI2MsgServerChallenge, // server's response to the client's challenge, and his challenge to the client + GTI2MsgClientResponse, // client's response to the server's challenge + GTI2MsgAccept, // server accepting client's connection attempt + GTI2MsgReject, // server rejecting client's connection attempt + GTI2MsgClose, // message indicating the connection is closing + GTI2MsgKeepAlive, // keep-alive used to help detect dropped connections + + GTI2NumReliableMessages, + + // unreliable messages + GTI2MsgAck = 100, // acknowledge receipt of reliable message(s) + GTI2MsgNack, // alert sender to missing reliable message(s) + GTI2MsgPing, // used to determine latency + GTI2MsgPong, // a reply to a ping + GTI2MsgClosed // confirmation of connection closure (GTI2MsgClose or GTI2MsgReject) - also sent in response to bad messages from unknown addresses + + // unreliable messages don't really have a message type, just the magic string repeated at the start +} GTI2MessageType; + +/*************** +** STRUCTURES ** +***************/ + +typedef struct GTI2Buffer +{ + GT2Byte * buffer; // The buffer's bytes. + int size; // Number of bytes in buffer. + int len; // Length of actual data in buffer. +} GTI2Buffer; + +typedef struct GTI2IncomingBufferMessage +{ + int start; // the start of the message + int len; // the length of the message + GTI2MessageType type; // the type + unsigned short serialNumber; // the serial number +} GTI2IncomingBufferMessage; + +typedef struct GTI2OutgoingBufferMessage +{ + int start; // the start of the message + int len; // the length of the message + unsigned short serialNumber; // the serial number + gsi_time lastSend; // last time this message was sent +} GTI2OutgoingBufferMessage; + +typedef struct GTI2Socket +{ + SOCKET socket; // the network socket used for all network communication + + unsigned int ip; // the ip this socket is bound to + unsigned short port; // the port this socket is bound to + + HashTable connections; // the connections that are using this socket + DArray closedConnections; // connections that are closed no longer get a spot in the hash table + + GT2Bool close; // if true, a close was attempted inside a callback, and it should be closed as soon as possible + GT2Bool error; // if true, there was a socket error using this socket + + int callbackLevel; // if >0, then we're inside a callback (or recursive callbacks) + gt2ConnectAttemptCallback connectAttemptCallback; // if set, callback used to handle incoming connection attempts + gt2SocketErrorCallback socketErrorCallback; // if set, call this in case of an error + gt2DumpCallback sendDumpCallback; // if set, gets called for every datagram sent + gt2DumpCallback receiveDumpCallback; // if set, gets called for every datagram and connection reset received + gt2UnrecognizedMessageCallback unrecognizedMessageCallback; // if set, gets called for all unrecognized messages + + void * data; // user data + + int outgoingBufferSize; // per-connection buffer sizes + int incomingBufferSize; + + GTI2ProtocolType protocolType; // set to UDP or VDP protocol depending on the call to create socket + // also used as an offset for VDP sockets + int protocolOffset; + GT2Bool broadcastEnabled; // set to true if the socket has already been broadcast enabled +} GTI2Socket; + +typedef struct GTI2Connection +{ + // ip and port uniquely identify this connection on this socket + unsigned int ip; // the ip on the other side of this connection (network byte order) + unsigned short port; // the port on the other side of this connection (host byte order) + + GTI2Socket * socket; // the parent socket + + GTI2ConnectionState state; // connection state + + GT2Bool initiated; // if true, the local side of the connection initiated the connection (client) + + GT2Bool freeAtAcceptReject; // if true, don't free the connection until accept/reject is called + + GT2Result connectionResult; // the result of the connect attempt + + gsi_time startTime; // the time the connection was created + gsi_time timeout; // the timeout value passed into gt2Connect + + int callbackLevel; // if >0, then we're inside a callback (or recursive callbacks) + GT2ConnectionCallbacks callbacks; // connection callbacks + + char * initialMessage; // this is the initial message for the client + int initialMessageLen; // the initial message length + + void * data; // user data + + GTI2Buffer incomingBuffer; // buffer for incoming data + GTI2Buffer outgoingBuffer; // buffer for outgoing data + DArray incomingBufferMessages; // identifies incoming messages stored in the buffer + DArray outgoingBufferMessages; // identifies outgoing messages stored in the buffer + + unsigned short serialNumber; // serial number of the next message to be sent out + unsigned short expectedSerialNumber; // the next serial number we're expecting from the remote side + + char response[GTI2_RESPONSE_LEN]; // after the challenge is sent during negotiation, this is the response we're expecting + + gsi_time lastSend; // the last time something was sent on this connection + gsi_time challengeTime; // the time the challenge was sent + + GT2Bool pendingAck; // if true, there is an ack waiting to go out, either on its own or as part of a reliable message + + gsi_time pendingAckTime; // the time at which the pending ack was first set + + DArray sendFilters; // filters that apply to outgoing data + DArray receiveFilters; // filters that apply to incoming data + +} GTI2Connection; + +// store last 32 ip's in a ring buffer +#define MAC_TABLE_SIZE 32 // must be power of 2 +typedef struct +{ + gsi_u32 ip; + char mac[6]; +} GTI2MacEntry; + +#ifdef GSI_ADHOC +static int lastmactableentry = 0; +static GTI2MacEntry MacTable[MAC_TABLE_SIZE]; +#endif // GSI_ADHOC + +#endif diff --git a/code/gamespy/gt2/gt2Message.c b/code/gamespy/gt2/gt2Message.c new file mode 100644 index 00000000..2ae44d8b --- /dev/null +++ b/code/gamespy/gt2/gt2Message.c @@ -0,0 +1,1802 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Message.h" +#include "gt2Buffer.h" +#include "gt2Connection.h" +#include "gt2Socket.h" +#include "gt2Callback.h" +#include "gt2Utility.h" +#include + +static unsigned short gti2UShortFromBuffer(const GT2Byte * buffer, int pos) +{ + unsigned short s; + s = (unsigned short)((buffer[pos] << 8) & 0xFF00); + pos++; + s |= buffer[pos]; + + return s; +} + + +static void gti2UShortToBuffer(GT2Byte * buffer, int pos, unsigned short s) +{ + buffer[pos++] = (GT2Byte)((s >> 8) & 0xFF); + buffer[pos] = (GT2Byte)(s & 0xFF); +} + +static int gti2SNDiff(unsigned short SN1, unsigned short SN2) +{ + return (short)(SN1 - SN2); +} + +static GT2Bool gti2ConnectionError(GT2Connection connection, GT2Result result, GT2CloseReason reason) +{ + // first check if we're still connecting + if(connection->state < GTI2Connected) + { + // check if the local side is the initiator + if(connection->initiated) + { + // mark it as closed + gti2ConnectionClosed(connection); + + // call the callback + if(!gti2ConnectedCallback(connection, result, NULL, 0)) + return GT2False; + } + else + { + // are we waiting for accept/reject? + if(connection->state == GTI2AwaitingAcceptReject) + connection->freeAtAcceptReject = GT2True; + + // mark it as closed + gti2ConnectionClosed(connection); + } + } + // report the close, as long as we're not already closed + else if(connection->state != GTI2Closed) + { + // mark it as closed + gti2ConnectionClosed(connection); + + // call the callback + if(!gti2ClosedCallback(connection, reason)) + return GT2False; + } + + return GT2True; +} + +static GT2Bool gti2ConnectionCommunicationError(GT2Connection connection) +{ + return gti2ConnectionError(connection, GT2NegotiationError, GT2CommunicationError); +} + +static GT2Bool gti2ConnectionMemoryError(GT2Connection connection) +{ + // let the other side know + if(!gti2SendClosed(connection)) + return GT2False; + + return gti2ConnectionError(connection, GT2OutOfMemory, GT2NotEnoughMemory); +} + + + + + +static GT2Bool gti2HandleESN(GT2Connection connection, unsigned short ESN) +{ + int len; + int i; + GTI2OutgoingBufferMessage * message; + int shortenBy; + + // get the number of messages in the outgoing queue + len = ArrayLength(connection->outgoingBufferMessages); + if(!len) + return GT2True; + + // loop through until we hit one we can't remove + for(i = 0 ; i < len ; i++) + { + // get the message + message = (GTI2OutgoingBufferMessage *)ArrayNth(connection->outgoingBufferMessages, i); + + // don't stop until we get to the ESN + if(gti2SNDiff(message->serialNumber, ESN) >= 0) + break; + } + + // check for not removing any + if(i == 0) + return GT2True; + + // remove the message info structs + while(i--) + ArrayDeleteAt(connection->outgoingBufferMessages, i); + + // check how many messages are left + len = ArrayLength(connection->outgoingBufferMessages); + if(!len) + { + // buffer is empty + connection->outgoingBuffer.len = 0; + return GT2True; + } + + // figure out how much to move everything forward + message = (GTI2OutgoingBufferMessage *)ArrayNth(connection->outgoingBufferMessages, 0); + shortenBy = message->start; + + // do the move on the info structs + for(i = 0 ; i < len ; i++) + { + message = (GTI2OutgoingBufferMessage *)ArrayNth(connection->outgoingBufferMessages, i); + message->start -= shortenBy; + } + + // move the actual data + gti2BufferShorten(&connection->outgoingBuffer, 0, shortenBy); + + return GT2True; +} + +static GT2Bool gti2HandleAppUnreliable(GT2Connection connection, GT2Byte * message, int len) +{ + // check the state + if((connection->state != GTI2Connected) && (connection->state != GTI2Closing)) + return GT2True; + + // do we need to filter it? + if(ArrayLength(connection->receiveFilters)) + { + if(!gti2ReceiveFilterCallback(connection, 0, message, len, GT2False)) + return GT2False; + return GT2True; + } + + // we received data, call the callback + if(!gti2ReceivedCallback(connection, message, len, GT2False)) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2HandleAppReliable(GT2Connection connection, GT2Byte * message, int len) +{ + // check the state + if((connection->state != GTI2Connected) && (connection->state != GTI2Closing)) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + } + else + { + // do we need to filter it? + if(ArrayLength(connection->receiveFilters)) + { + if(!gti2ReceiveFilterCallback(connection, 0, message, len, GT2True)) + return GT2False; + return GT2True; + } + + // we received data, call the callback + if(!gti2ReceivedCallback(connection, message, len, GT2True)) + return GT2False; + } + + return GT2True; +} + +static GT2Bool gti2HandleClientChallenge(GT2Connection connection, GT2Byte * message, int len) +{ + char response[GTI2_RESPONSE_LEN]; + char challenge[GTI2_CHALLENGE_LEN]; + + // check the state + if(connection->state != GTI2AwaitingClientChallenge) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // make sure the message is long enough + if(len < GTI2_CHALLENGE_LEN) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // generate a response to the challenge + gti2GetResponse((GT2Byte *)response, message); + + // generate our own challenge + gti2GetChallenge((GT2Byte *)challenge); + + // store what our response will be + gti2GetResponse((GT2Byte *)connection->response, (GT2Byte *)challenge); + + // send our own challenge + if(!gti2SendServerChallenge(connection, response, challenge)) + return GT2False; + + // new state + connection->state = GTI2AwaitingClientResponse; + + return GT2True; +} + + + +static GT2Bool gti2HandleServerChallenge(GT2Connection connection, GT2Byte * message, int len) +{ + char response[GTI2_RESPONSE_LEN]; + + // check the state + if(connection->state != GTI2AwaitingServerChallenge) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // make sure the message is long enough + if(len < (GTI2_RESPONSE_LEN + GTI2_CHALLENGE_LEN)) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // check the response + if(!gti2CheckResponse(message, (GT2Byte *)connection->response)) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // generate our response to the server's challenge + gti2GetResponse((GT2Byte *)response, message + GTI2_RESPONSE_LEN); + + // send the response, including our intial message + if(!gti2SendClientResponse(connection, response, connection->initialMessage, connection->initialMessageLen)) + return GT2False; + + // free the initial message + if(connection->initialMessage) + { + gsifree(connection->initialMessage); + connection->initialMessage = NULL; + } + + // new state + connection->state = GTI2AwaitingAcceptance; + + return GT2True; +} + +static GT2Bool gti2HandleClientResponse(GT2Connection connection, GT2Byte * message, int len) +{ + int latency; + + // check the state + if(connection->state != GTI2AwaitingClientResponse) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // make sure the message is long enough + if(len < (GTI2_RESPONSE_LEN)) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // check the response + if(!gti2CheckResponse(message, (GT2Byte *)connection->response)) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // need to make sure the connection didn't just stop listening + if(!connection->socket->connectAttemptCallback) + { + // send them a closed + if(!gti2SendClosed(connection)) + return GT2False; + + // mark it as closed + gti2ConnectionClosed(connection); + + return GT2True; + } + + // new state + connection->state = GTI2AwaitingAcceptReject; + + // calculate the approx. latency + latency = (int)(current_time() - connection->challengeTime); + + // it's up to the app now + if(!gti2ConnectAttemptCallback(connection->socket, connection, connection->ip, connection->port, latency, message + GTI2_RESPONSE_LEN, len - GTI2_RESPONSE_LEN)) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2HandleAccept(GT2Connection connection) +{ + // check the state + if(connection->state != GTI2AwaitingAcceptance) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // new state + connection->state = GTI2Connected; + + // call the callback + if(!gti2ConnectedCallback(connection, GT2Success, NULL, 0)) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2HandleReject(GT2Connection connection, GT2Byte * message, int len) +{ + // check the state + if(connection->state != GTI2AwaitingAcceptance) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // mark it as closed + gti2ConnectionClosed(connection); + + // send a closed reply + if(!gti2SendClosed(connection)) + return GT2False; + + // call the callback + if(!gti2ConnectedCallback(connection, GT2Rejected, message, len)) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2HandleClose(GT2Connection connection) +{ + GT2Bool localClose; + + // send a closed reply + if(!gti2SendClosed(connection)) + return GT2False; + + // were we attempting to close this connection? + localClose = (connection->state == GTI2Closing); + + // handle it as an error (so the right callbacks are called) + if(!gti2ConnectionError(connection, GT2Rejected, localClose?GT2LocalClose:GT2RemoteClose)) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2DeliverReliableMessage(GT2Connection connection, GTI2MessageType type, GT2Byte * message, int len) +{ + // bump our ESN + connection->expectedSerialNumber++; + + if(type == GTI2MsgAppReliable) + { + if(!gti2HandleAppReliable(connection, message, len)) + return GT2False; + } + else if(type == GTI2MsgClientChallenge) + { + if(!gti2HandleClientChallenge(connection, message, len)) + return GT2False; + } + else if(type == GTI2MsgServerChallenge) + { + if(!gti2HandleServerChallenge(connection, message, len)) + return GT2False; + } + else if(type == GTI2MsgClientResponse) + { + if(!gti2HandleClientResponse(connection, message, len)) + return GT2False; + } + else if(type == GTI2MsgAccept) + { + if(!gti2HandleAccept(connection)) + return GT2False; + } + else if(type == GTI2MsgReject) + { + if(!gti2HandleReject(connection, message, len)) + return GT2False; + } + else if(type == GTI2MsgClose) + { + if(!gti2HandleClose(connection)) + return GT2False; + } + else if(type == GTI2MsgKeepAlive) + { + // ignore + } + + return GT2True; +} +#ifdef WIN32 +static int __cdecl gti2IncomingBufferMessageCompare(const void * elem1, const void * elem2) +#else +static int gti2IncomingBufferMessageCompare(const void * elem1, const void * elem2) +#endif +{ + const GTI2IncomingBufferMessage * message1 = (GTI2IncomingBufferMessage *)elem1; + const GTI2IncomingBufferMessage * message2 = (GTI2IncomingBufferMessage *)elem2; + + return gti2SNDiff(message1->serialNumber, message2->serialNumber); +} + +static GT2Bool gti2BufferIncomingMessage(GT2Connection connection, GTI2MessageType type, unsigned short SN, GT2Byte * message, int len, GT2Bool * overflow) +{ + GTI2IncomingBufferMessage messageInfo; + GTI2IncomingBufferMessage * bufferedMessage; + int num; + int i; + + // check the number of messages being held + num = ArrayLength(connection->incomingBufferMessages); + + // check if this message is already buffered + for(i = 0 ; i < num ; i++) + { + // get the message + bufferedMessage = (GTI2IncomingBufferMessage *)ArrayNth(connection->incomingBufferMessages, i); + + // check if this is the same message + if(bufferedMessage->serialNumber == SN) + { + *overflow = GT2False; + return GT2True; + } + + // check if we've already past the target SN + if(gti2SNDiff(bufferedMessage->serialNumber, SN) > 0) + break; + } + + // check that there's enough space to store the message + if(gti2GetBufferFreeSpace(&connection->incomingBuffer) < len) + { + *overflow = GT2True; + return GT2True; + } + + // setup the message info + messageInfo.start = connection->incomingBuffer.len; + messageInfo.len = len; + messageInfo.type = type; + messageInfo.serialNumber = SN; + + // add it to the list + ArrayInsertSorted(connection->incomingBufferMessages, &messageInfo, gti2IncomingBufferMessageCompare); + + // make sure the length is one more + if(ArrayLength(connection->incomingBufferMessages) != (num + 1)) + { + *overflow = GT2True; + return GT2True; + } + + // copy the message into the buffer + gti2BufferWriteData(&connection->incomingBuffer, message, len); + + // check for sending a nack + // we want to send one when we think a message or messages were probably dropped + if(num == 0) + { + // if we're the only message in the hold, send a nack + if(!gti2SendNack(connection, connection->expectedSerialNumber, (unsigned short)(SN - 1))) + return GT2False; + } + else + { + GTI2IncomingBufferMessage * msg; + + // are we the highest message? + msg = (GTI2IncomingBufferMessage *)ArrayNth(connection->incomingBufferMessages, num); + if(msg->serialNumber == SN) + { + GTI2IncomingBufferMessage * prev; + unsigned short diff; + + // if we're not right after the second-highest SN, the ones in between were probably dropped + prev = (GTI2IncomingBufferMessage *)ArrayNth(connection->incomingBufferMessages, num - 1); + diff = (unsigned short)gti2SNDiff(SN, prev->serialNumber); + if(diff > 1) + { + if(!gti2SendNack(connection, (unsigned short)(prev->serialNumber + 1), (unsigned short)(SN - 1))) + return GT2False; + } + } + } + + *overflow = GT2False; + return GT2True; +} + +static void gti2RemoveHoldMessage(GT2Connection connection, GTI2IncomingBufferMessage * message, int index) +{ + int moveAfter; + int shortenBy; + int moveEnd = 0; + int num; + int i; + + // save off info about it + moveAfter = message->start; + shortenBy = message->len; + + // delete it + ArrayDeleteAt(connection->incomingBufferMessages, index); + + // loop through and fix up message's stored after this one in the buffer + // also figure out exactly how much data we'll need to move + num = ArrayLength(connection->incomingBufferMessages); + for(i = 0 ; i < num ; i++) + { + // check if this message needs to be moved forward + message = (GTI2IncomingBufferMessage *)ArrayNth(connection->incomingBufferMessages, i); + if(message->start > moveAfter) + { + message->start -= shortenBy; + moveEnd = max(moveEnd, message->start + message->len); + } + } + + // fix up the buffer itself + gti2BufferShorten(&connection->incomingBuffer, moveAfter, shortenBy); +} + +static GT2Bool gti2DeliverHoldMessages(GT2Connection connection) +{ + GTI2IncomingBufferMessage * message; + int num; + int i; + +restart: + + // loop through the buffered messages, checking if there are any that can now be delivered + // loop through backwards to ease removal + num = ArrayLength(connection->incomingBufferMessages); + for(i = (num - 1) ; i >= 0 ; i--) + { + message = (GTI2IncomingBufferMessage *)ArrayNth(connection->incomingBufferMessages, i); + + // we should deliver this if it's what we're expecting + if(message->serialNumber == connection->expectedSerialNumber) + { + // deliver it + if(!gti2DeliverReliableMessage(connection, message->type, connection->incomingBuffer.buffer + message->start, message->len)) + return GT2False; + + // remove it + gti2RemoveHoldMessage(connection, message, i); + + // we need to go through this loop again. + // goto's are evil, but a little evil is good here + goto restart; + } + } + + return GT2True; +} + +static void gti2SetPendingAck(GT2Connection connection) +{ + // if there's not a pending ack, set one + if(!connection->pendingAck) + { + connection->pendingAck = GT2True; + connection->pendingAckTime = current_time(); + } +} + +static GT2Bool gti2HandleReliableMessage(GT2Connection connection, GTI2MessageType type, GT2Byte * message, int len) +{ + unsigned short SN; + unsigned short ESN; + const int headerLength = connection->socket->protocolOffset + GTI2_MAGIC_STRING_LEN + 1 + 2 + 2; + GT2Bool overflow; + + // check if it's long enough + if(len < (connection->socket->protocolOffset + GTI2_MAGIC_STRING_LEN + 1 + 2 + 2)) // magic string + type + SN + ESN + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // get the SN + SN = gti2UShortFromBuffer(message + connection->socket->protocolOffset, GTI2_MAGIC_STRING_LEN + 1); + + // get the ESN + ESN = gti2UShortFromBuffer(message + connection->socket->protocolOffset, GTI2_MAGIC_STRING_LEN + 3); + + // update the message and length to point to the actual message + if (connection->socket->protocolType == GTI2VdpProtocol && type == GTI2MsgAppReliable) + { + message[connection->socket->protocolOffset + GTI2_MAGIC_STRING_LEN + 3] = message[0]; + message[connection->socket->protocolOffset + GTI2_MAGIC_STRING_LEN + 4] = message[1]; + message += headerLength - connection->socket->protocolOffset; + len -= headerLength - connection->socket->protocolOffset; + } + else + { + message += headerLength; + len -= headerLength; + } + + // handle the ESN + if(!gti2HandleESN(connection, ESN)) + return GT2False; + + // check if it's the SN we expected + if(SN == connection->expectedSerialNumber) + { + // make sure we ack this message + // do this before delivering, because we might send an ack as part of a reliable reply + gti2SetPendingAck(connection); + + // deliver the message + if(!gti2DeliverReliableMessage(connection, type, message, len)) + return GT2False; + + // check if there are any messages in the hold that can now be delivered + if(!gti2DeliverHoldMessages(connection)) + return GT2False; + + return GT2True; + } + + // check if the message is a duplicate + if(gti2SNDiff(SN, connection->expectedSerialNumber) < 0) + { + // it's a duplicate, ack it again + gti2SetPendingAck(connection); + + // ignore it + return GT2True; + } + + // we can't deliver this yet, so put it in the hold + if(!gti2BufferIncomingMessage(connection, type, SN, message, len, &overflow)) + return GT2False; + + // check for a buffer overflow + if(overflow) + { + if(!gti2ConnectionMemoryError(connection)) + return GT2False; + } + + return GT2True; +} + +static GT2Bool gti2HandleAck(GT2Connection connection, const GT2Byte * message, int len) +{ + unsigned short ESN; + + // make sure it has enough space for the ESN + if(len != 2) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // get the ESN + ESN = gti2UShortFromBuffer(message, 0); + + // handle it + if(!gti2HandleESN(connection, ESN)) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2HandleNack(GT2Connection connection, const GT2Byte * message, int len) +{ + unsigned short SNMin; + unsigned short SNMax; + int num; + int i; + GTI2OutgoingBufferMessage * messageInfo; + + // read based on length. + SNMin = gti2UShortFromBuffer(message, 0); + if(len == 2) + { + SNMax = SNMin; + } + else if(len == 4) + { + SNMax = gti2UShortFromBuffer(message, 2); + } + else + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + } + + // loop through the messages, resending any specified ones + num = ArrayLength(connection->outgoingBufferMessages); + for(i = 0 ; i < num ; i++) + { + messageInfo = (GTI2OutgoingBufferMessage *)ArrayNth(connection->outgoingBufferMessages, i); + if((gti2SNDiff(messageInfo->serialNumber, SNMin) >= 0) && (gti2SNDiff(messageInfo->serialNumber, SNMax) <= 0)) + { + if(!gti2ResendMessage(connection, messageInfo)) + return GT2False; + } + } + + return GT2True; +} + +static GT2Bool gti2HandlePing(GT2Connection connection, GT2Byte * message, int len) +{ + // send it right back + return gti2SendPong(connection, message, len); +} + +static GT2Bool gti2HandlePong(GT2Connection connection, const GT2Byte * message, int len) +{ + gsi_time startTime; + + // do we care about this? + if(!connection->callbacks.ping) + return GT2True; + + // is this a pong we're interested in? + // "time" + ping-sent-time + if(len != (4 + sizeof(gsi_time))) + return GT2True; + if(memcmp(message, "time", 4) != 0) + return GT2True; + + // get the start time + memcpy(&startTime, message + 4, sizeof(gsi_time)); + + // call the callback + if(!gti2PingCallback(connection, (int)(current_time() - startTime))) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2HandleClosed(GT2Connection connection) +{ + GT2Bool localClose; + + // are we already closed? + if(connection->state == GTI2Closed) + return GT2True; + + // were we attempting to close this connection? + localClose = (connection->state == GTI2Closing); + + // handle it as an error (so the right callbacks are called) + if(!gti2ConnectionError(connection, GT2Rejected, localClose?GT2LocalClose:GT2RemoteClose)) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2HandleUnreliableMessage(GT2Connection connection, GTI2MessageType type, GT2Byte * message, int len) +{ + int headerLength; + GT2Byte * dataStart; + int dataLen; + + // most unreliable messages don't need the header + headerLength = (connection->socket->protocolOffset + GTI2_MAGIC_STRING_LEN + 1); + dataStart = (message + headerLength); + dataLen = (len - headerLength); + + // handle unreliable messages based on type + if(type == GTI2MsgAck) + { + if(!gti2HandleAck(connection, dataStart, dataLen)) + return GT2False; + } + else if(type == GTI2MsgNack) + { + if(!gti2HandleNack(connection, dataStart, dataLen)) + return GT2False; + } + else if(type == GTI2MsgPing) + { + if(!gti2HandlePing(connection, message, len)) + return GT2False; + } + else if(type == GTI2MsgPong) + { + if(!gti2HandlePong(connection, dataStart, dataLen)) + return GT2False; + } + else if(type == GTI2MsgClosed) + { + if(!gti2HandleClosed(connection)) + return GT2False; + } + + return GT2True; +} + +// VDP sockets have data length which needs to be stripped off +static GT2Bool gti2HandleMessage(GT2Socket socket, GT2Byte * message, int len, unsigned int ip, unsigned short port) +{ + GT2Connection connection; + GT2Bool magicString; + GT2Result result; + GTI2MessageType type; + GT2Bool handled; + int actualLength = len - socket->protocolOffset; + + // VDP messages have 2 byte header which is removed based on protocol + GT2Byte *actualMessage = message + socket->protocolOffset; + + // find out if we have an existing connection for this address + connection = gti2SocketFindConnection(socket, ip, port); + + // let the dump handle this + if(socket->receiveDumpCallback) + { + if(!gti2DumpCallback(socket, connection, ip, port, GT2False, message, len, GT2False)) + return GT2False; + } + + // check if the message starts with the magic string + // use greater than for the len compare because it also must have a type + magicString = ((actualLength > GTI2_MAGIC_STRING_LEN) && (memcmp(actualMessage, GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN) == 0)); + + // check if we don't have a connection + if(!connection) + { + // if we don't know who this is from, let the unrecognized message callback have first crack at it + if(!gti2UnrecognizedMessageCallback(socket, ip, port, message, len, &handled)) + return GT2False; + + // if they handled it, we don't care about it. + if(handled) + return GT2True; + + // if this isn't a connection request, tell them the connection is closed + if(!magicString || (actualMessage[GTI2_MAGIC_STRING_LEN] != GTI2MsgClientChallenge)) + { + // if this is a closed message, don't send one back (to avoid recursion) + if(!magicString || (actualMessage[GTI2_MAGIC_STRING_LEN] != GTI2MsgClosed)) + { + if(!gti2SendClosedOnSocket(socket, ip, port)) + return GT2False; + } + return GT2True; + } + + // if we're not listening, we just ignore this + if(!socket->connectAttemptCallback) + return GT2True; + + // create a connection + result = gti2NewIncomingConnection(socket, &connection, ip, port); + if(result != GT2Success) + { + // as long as this wasn't a duplicate address error, tell them we're closed + // in the case of duplicates, we don't want to close the existing connection + if(result != GT2DuplicateAddress) + { + if(!gti2SendClosedOnSocket(socket, ip, port)) + return GT2False; + } + return GT2True; + } + } + + // is the connection already closed? + if(connection->state == GTI2Closed) + { + // if this is a closed message, don't send one back (to avoid recursion) + if(!magicString || (actualMessage[GTI2_MAGIC_STRING_LEN] != GTI2MsgClosed)) + { + if(!gti2SendClosed(connection)) + return GT2False; + } + + return GT2True; + } + + // check if this is an unreliable app message with a magic string header + if(magicString && ((actualLength >= (GTI2_MAGIC_STRING_LEN * 2)) && (memcmp(actualMessage + GTI2_MAGIC_STRING_LEN, GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN) == 0))) + { + message[3] = message[1]; + message[2] = message[0]; + message = actualMessage; + actualMessage += socket->protocolOffset; + actualLength -= socket->protocolOffset; + len -= GTI2_MAGIC_STRING_LEN; + magicString = GT2False; + } + + // if it doesn't have a magic string it's an app unreliable + if(!magicString) + { + // First determine if the connection found has gone throught the internal challenge response + if (connection->state < GTI2Connected) + { + // pass any message that doesn't have a magic string to + // the app so that the SDK doesn't drop them + if(!gti2UnrecognizedMessageCallback(socket, ip, port, message, len, &handled)) + return GT2False; + } + else + { + if(!gti2HandleAppUnreliable(connection, message, len)) + return GT2False; + } + + return GT2True; + } + + // get the message type + type = (GTI2MessageType)actualMessage[GTI2_MAGIC_STRING_LEN]; + // check for a bad type + /*if(type < 0) + { + if(!gti2ConnectionCommunicationError(connection)) + return GT2False; + + return GT2True; + }*/ + + + // check if it's reliable + if(type < GTI2NumReliableMessages) + { + // handle it + if(!gti2HandleReliableMessage(connection, type, message, len)) + return GT2False; + return GT2True; + } + + // handle unreliable messages + if(!gti2HandleUnreliableMessage(connection, type, message, len)) + return GT2False; + + return GT2True; +} + + +GT2Bool gti2HandleConnectionReset(GT2Socket socket, unsigned int ip, unsigned short port) +{ + GT2Connection connection; + + // find the connection for the reset + connection = gti2SocketFindConnection(socket, ip, port); + + // let the dump know about this + if(socket->receiveDumpCallback) + { + if(!gti2DumpCallback(socket, connection, ip, port, GT2True, NULL, 0, GT2False)) + return GT2False; + } + + // there's no connection, so ignore it + if(!connection) + return GT2True; + + // are we waiting for a response from the server? + if(connection->state == GTI2AwaitingServerChallenge) + { + // are we still within the timeout time? + if(!connection->timeout || ((current_time() - connection->startTime) < connection->timeout)) + return GT2True; + + // report this as a timeout + if(!gti2ConnectionError(connection, GT2TimedOut, GT2RemoteClose)) + return GT2False; + } + else + { + // report the error + if(!gti2ConnectionError(connection, GT2Rejected, GT2RemoteClose)) + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2HandleHostUnreachable(GT2Socket socket, unsigned int ip, unsigned short port, GT2Bool send) +{ + GT2Connection connection; + + // find the connection for the reset + connection = gti2SocketFindConnection(socket, ip, port); + + // let the dump know about this + if(socket->receiveDumpCallback) + { + if(!gti2DumpCallback(socket, connection, ip, port, GT2True, NULL, 0, send)) + return GT2False; + } + + // there's no connection, so ignore it + if(!connection) + return GT2True; + + + // report the error + if(!gti2ConnectionError(connection, GT2TimedOut, GT2RemoteClose)) + return GT2False; + + return GT2True; +} + + +#ifdef GSI_ADHOC + + + +// return length if successful +// <=0 on error +gsi_bool _NetworkAdHocSocketRecv(int socket_id, + char *buf, + int bufferlen, + int flags, + char *saddr, //struct SceNetEtherAddr = char[6]; + gsi_u16 *sport); + + + + + + + + +// return 0 if no data, -1 if error, >0 if data to read +int _NetworkAdHocCanReceiveOnSocket(int socket_id); + +GT2Bool gti2ReceiveAdHocMessages(GT2Socket socket,char *buffer, int buffersize) +{ + int rcode; + SOCKADDR_IN address; + int addressLen;//, datasize; + + // check for messages + while (1) + { + int datasize = _NetworkAdHocCanReceiveOnSocket(socket->socket); + if (datasize < 0) // error + { + gti2SocketError(socket); + return GT2False; + } + + if (datasize == 0) + break; // no data + { + // We have data to recv + // receive the message + char mac[6]; + gsi_u16 port; + //gsi_u32 ip; + + addressLen = sizeof(address); + + rcode = _NetworkAdHocSocketRecv(socket->socket, buffer,buffersize , 0, mac,&port); + if(rcode < 0) // fatal socket error + { + #if(0) // notes + if(0)//rcode == WSAECONNRESET) + { + // handle the reset + if(!gti2HandleConnectionReset(socket, address.sin_addr.s_addr, ntohs(address.sin_port))) + return GT2False; + } + else + if (rcode == WSAEHOSTUNREACH) + { + if (!gti2HandleHostUnreachable(socket, address.sin_addr.s_addr, ntohs(address.sin_port), GT2False)) + return GT2False; + } + else + #endif + { + gti2SocketError(socket); + return GT2False; + } + } + if(rcode == 0) // no data + { + return GT2False; + } + + // at this point we have valid data + + // change ethernet to IP address + address.sin_addr.s_addr = gt2MacToIp(mac); + address.sin_port = port; + + #ifdef RECV_LOG + // log it + gti2LogMessage(address.sin_addr.s_addr, ntohs(address.sin_port), + socket->ip, socket->port, + buffer, rcode); + #endif + // handle the message + if(!gti2HandleMessage(socket, (GT2Byte *)buffer, rcode, address.sin_addr.s_addr, address.sin_port)) + return GT2False; + } + } + + return GT2True; +} +#endif + +GT2Bool gti2ReceiveMessages(GT2Socket socket) +{ + int rcode; + SOCKADDR_IN address; + int addressLen; + + + // avoid overflowing stack + #if (GTI2_STACK_RECV_BUFFER_SIZE > 1600) + static char buffer[GTI2_STACK_RECV_BUFFER_SIZE]; + #else + char buffer[GTI2_STACK_RECV_BUFFER_SIZE]; + #endif + + + #ifdef GSI_ADHOC + if(socket->protocolType == GTI2AdHocProtocol) + { + return gti2ReceiveAdHocMessages(socket,buffer,GTI2_STACK_RECV_BUFFER_SIZE); + } + #endif + + // check for messages + while (CanReceiveOnSocket(socket->socket)) + { + // mj todo: get this plat specific stuff out of here. Belongs in play specific layer. + // abstract recvfrom + + // receive the message + addressLen = sizeof(address); + + rcode = recvfrom(socket->socket, buffer, sizeof(buffer), 0, (SOCKADDR *)&address, &addressLen); + + if (gsiSocketIsError(rcode)) + { + rcode = GOAGetLastError(socket->socket); + if(rcode == WSAECONNRESET) + { + // handle the reset + if(!gti2HandleConnectionReset(socket, address.sin_addr.s_addr, ntohs(address.sin_port))) + return GT2False; + } + #ifndef SN_SYSTEMS + else if (rcode == WSAEHOSTUNREACH) + { + if (!gti2HandleHostUnreachable(socket, address.sin_addr.s_addr, ntohs(address.sin_port), GT2False)) + return GT2False; + } + #endif + else if(rcode != WSAEMSGSIZE) + { + // fatal socket error + gti2SocketError(socket); + return GT2False; + } + } + else + { + #ifdef RECV_LOG + // log it + gti2LogMessage(address.sin_addr.s_addr, ntohs(address.sin_port), + socket->ip, socket->port, + buffer, rcode); + #endif + // handle the message + if(!gti2HandleMessage(socket, (GT2Byte *)buffer, rcode, address.sin_addr.s_addr, ntohs(address.sin_port))) + return GT2False; + } + } + + return GT2True; +} + +static GT2Bool gti2StoreOutgoingReliableMessageInfo(GT2Connection connection, unsigned short SN, int len) +{ + GTI2OutgoingBufferMessage messageInfo; + int num; + + // setup the message info + memset(&messageInfo, 0, sizeof(messageInfo)); + messageInfo.start = connection->outgoingBuffer.len; + messageInfo.len = len; + messageInfo.serialNumber = SN; + messageInfo.lastSend = current_time(); + + // check the number of messages before we do the add + num = ArrayLength(connection->outgoingBufferMessages); + + // add it to the list + ArrayAppend(connection->outgoingBufferMessages, &messageInfo); + + // make sure the length is one more + if(ArrayLength(connection->outgoingBufferMessages) != (num + 1)) + return GT2False; + + return GT2True; +} + +static GT2Bool gti2BeginReliableMessage(GT2Connection connection, GTI2MessageType type, int len, GT2Bool * overflow) +{ + int freeSpace; + + // VDP data length needed in the front of every packet + unsigned short vdpDataLength = (unsigned short)(len - connection->socket->protocolOffset); + + // check how much free space is in the outgoing buffer + freeSpace = gti2GetBufferFreeSpace(&connection->outgoingBuffer); + + // do we have the space to hold it? + if(freeSpace < len) + { + if(!gti2ConnectionMemoryError(connection)) + return GT2False; + + *overflow = GT2True; + return GT2True; + } + + // store the message's info + if(!gti2StoreOutgoingReliableMessageInfo(connection, connection->serialNumber, len)) + { + if(!gti2ConnectionMemoryError(connection)) + return GT2False; + + *overflow = GT2True; + return GT2True; + } + + // setup the header + if (connection->socket->protocolType == GTI2VdpProtocol) + gti2BufferWriteData(&connection->outgoingBuffer, (const GT2Byte *)&vdpDataLength, connection->socket->protocolOffset); + gti2BufferWriteData(&connection->outgoingBuffer, (const GT2Byte *)GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN); + gti2BufferWriteByte(&connection->outgoingBuffer, (GT2Byte)type); + gti2BufferWriteUShort(&connection->outgoingBuffer, connection->serialNumber++); + gti2BufferWriteUShort(&connection->outgoingBuffer, connection->expectedSerialNumber); + + *overflow = GT2False; + return GT2True; +} + +static GT2Bool gti2EndReliableMessage(GT2Connection connection) +{ + GTI2OutgoingBufferMessage * message; + int len; + + // the message we're sending is the last one + len = ArrayLength(connection->outgoingBufferMessages); + assert(len > 0); + message = (GTI2OutgoingBufferMessage *)ArrayNth(connection->outgoingBufferMessages, len - 1); + + // send it + if(!gti2ConnectionSendData(connection, connection->outgoingBuffer.buffer + message->start, message->len)) + return GT2False; + + // we just did an ack (as part of the message) + connection->pendingAck = GT2False; + + return GT2True; +} + +GT2Bool gti2SendAppReliable(GT2Connection connection, const GT2Byte * message, int len) +{ + int totalLen; + GT2Bool overflow; + + // magic string + type + SN + ESN + message + totalLen = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2 + len); + + // begin the message + if(!gti2BeginReliableMessage(connection, GTI2MsgAppReliable, totalLen, &overflow)) + return GT2False; + if(overflow) + return GT2True; + + // write the message + gti2BufferWriteData(&connection->outgoingBuffer, message, len); + + // end the message + if(!gti2EndReliableMessage(connection)) + return GT2False; + + return GT2True; +} + +GT2Bool gti2SendClientChallenge(GT2Connection connection, const char challenge[GTI2_CHALLENGE_LEN]) +{ + // magic string + type + SN + ESN + challenge + int totalLen = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2 + GTI2_CHALLENGE_LEN); + GT2Bool overflow; + + // begin the message + if(!gti2BeginReliableMessage(connection, GTI2MsgClientChallenge, totalLen + connection->socket->protocolOffset, &overflow)) + return GT2False; + + if(overflow) + return GT2True; + + // write the challenge + gti2BufferWriteData(&connection->outgoingBuffer, (const GT2Byte *)challenge, GTI2_CHALLENGE_LEN); + + // end the message + if(!gti2EndReliableMessage(connection)) + return GT2False; + + return GT2True; +} + +GT2Bool gti2SendServerChallenge(GT2Connection connection, const char response[GTI2_RESPONSE_LEN], const char challenge[GTI2_CHALLENGE_LEN]) +{ + // magic string + type + SN + ESN + response + challenge + int totalLen = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2 + GTI2_RESPONSE_LEN + GTI2_CHALLENGE_LEN); + GT2Bool overflow; + + // begin the message + if(!gti2BeginReliableMessage(connection, GTI2MsgServerChallenge, totalLen + connection->socket->protocolOffset, &overflow)) + return GT2False; + + if(overflow) + return GT2True; + + // write the response + gti2BufferWriteData(&connection->outgoingBuffer, (const GT2Byte *)response, GTI2_RESPONSE_LEN); + + // write the challenge + gti2BufferWriteData(&connection->outgoingBuffer, (const GT2Byte *)challenge, GTI2_CHALLENGE_LEN); + + // end the message + if(!gti2EndReliableMessage(connection)) + return GT2False; + + // save the time + connection->challengeTime = connection->lastSend; + + return GT2True; +} + +GT2Bool gti2SendClientResponse(GT2Connection connection, const char response[GTI2_RESPONSE_LEN], const char * message, int len) +{ + // magic string + type + SN + ESN + response + message + int totalLen = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2 + GTI2_RESPONSE_LEN + len); + GT2Bool overflow; + + // begin the message + if(!gti2BeginReliableMessage(connection, GTI2MsgClientResponse, totalLen + connection->socket->protocolOffset, &overflow)) + return GT2False; + + if(overflow) + return GT2True; + + // write the response + gti2BufferWriteData(&connection->outgoingBuffer, (const GT2Byte *)response, GTI2_RESPONSE_LEN); + + // write the message + gti2BufferWriteData(&connection->outgoingBuffer, (const GT2Byte *)message, len); + + // end the message + if(!gti2EndReliableMessage(connection)) + return GT2False; + + return GT2True; +} + +GT2Bool gti2SendAccept(GT2Connection connection) +{ + // magic string + type + SN + ESN + int totalLen = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2); + GT2Bool overflow; + + // begin the message + if(!gti2BeginReliableMessage(connection, GTI2MsgAccept, totalLen + connection->socket->protocolOffset, &overflow)) + return GT2False; + + if(overflow) + return GT2True; + + // end the message + if(!gti2EndReliableMessage(connection)) + return GT2False; + + return GT2True; +} + +GT2Bool gti2SendReject(GT2Connection connection, const GT2Byte * message, int len) +{ + // magic string + type + SN + ESN + message + int totalLen = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2 + len); + GT2Bool overflow; + + // begin the message + if(!gti2BeginReliableMessage(connection, GTI2MsgReject, totalLen + connection->socket->protocolOffset, &overflow)) + return GT2False; + + if(overflow) + return GT2True; + + // write the message + gti2BufferWriteData(&connection->outgoingBuffer, message, len); + + // end the message + if(!gti2EndReliableMessage(connection)) + return GT2False; + + return GT2True; +} + +GT2Bool gti2SendClose(GT2Connection connection) +{ + // magic string + type + SN + ESN + int totalLen = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2); + GT2Bool overflow; + + // begin the message + if(!gti2BeginReliableMessage(connection, GTI2MsgClose, totalLen + connection->socket->protocolOffset, &overflow)) + return GT2False; + + if(overflow) + return GT2True; + + // end the message + if(!gti2EndReliableMessage(connection)) + return GT2False; + + return GT2True; +} + +GT2Bool gti2SendKeepAlive(GT2Connection connection) +{ + // magic string + type + SN + ESN + int totalLen = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2); + GT2Bool overflow; + + // begin the message + if(!gti2BeginReliableMessage(connection, GTI2MsgKeepAlive, totalLen + connection->socket->protocolOffset, &overflow)) + return GT2False; + + if(overflow) + return GT2True; + + // end the message + if(!gti2EndReliableMessage(connection)) + return GT2False; + + return GT2True; +} + +GT2Bool gti2SendAppUnreliable(GT2Connection connection, const GT2Byte * message, int len) +{ + int freeSpace; + int totalLen; + GT2Byte * start; + + // check if we can send it right away (unreliable that doesn't start with the magic string) + if((len < GTI2_MAGIC_STRING_LEN) || + (memcmp(message + connection->socket->protocolOffset, GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN) != 0)) + { + if(!gti2ConnectionSendData(connection, message, len)) + return GT2False; + + return GT2True; + } + + // magic string + message + totalLen = (GTI2_MAGIC_STRING_LEN + len); + + // check how much free space is in the outgoing buffer + freeSpace = gti2GetBufferFreeSpace(&connection->outgoingBuffer); + + // do we have the space to hold it? + if(freeSpace < totalLen) + { + // just drop it + return GT2True; + } + + // store the start of the actual message in the buffer + start = (connection->outgoingBuffer.buffer + connection->outgoingBuffer.len); + + // Copy the VDP data length if necessary + if (connection->socket->protocolType == GTI2VdpProtocol) + gti2BufferWriteData(&connection->outgoingBuffer, message, 2); + + // copy it in, repeating the magic string at the beginning + gti2BufferWriteData(&connection->outgoingBuffer, (const GT2Byte *)GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN); + + // copy the data at the starting position + offset based on the protocol + gti2BufferWriteData(&connection->outgoingBuffer, message + connection->socket->protocolOffset, + (int)(len - connection->socket->protocolOffset)); + + // do the send + if(!gti2ConnectionSendData(connection, start, totalLen)) + return GT2False; + + // we don't need to save the message + gti2BufferShorten(&connection->outgoingBuffer, -1, totalLen); + + return GT2True; +} + +GT2Bool gti2SendAck(GT2Connection connection) +{ + // always allocate data length + magic string + type + ESN + // part of the buffer may not be used but more efficience to be on stack + char buffer[MAX_PROTOCOL_OFFSET + GTI2_MAGIC_STRING_LEN + 1 + 2]; + int pos = 0; + + // write the VDP data length + if (connection->socket->protocolType == GTI2VdpProtocol) + { + short dataLength = (GTI2_MAGIC_STRING_LEN + 1 + 2); + memcpy(buffer, &dataLength, 2); + pos += 2; + } + + // write the magic string + memcpy(buffer + pos, GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN); + pos += GTI2_MAGIC_STRING_LEN; + + // write the type + buffer[pos++] = GTI2MsgAck; + + // write the ESN + gti2UShortToBuffer((GT2Byte *)buffer, pos, connection->expectedSerialNumber); + pos += 2; + + // send it + if(!gti2ConnectionSendData(connection, (const GT2Byte *)buffer, pos)) + return GT2False; + + // we just did an ack + connection->pendingAck = GT2False; + + return GT2True; +} + + +GT2Bool gti2SendNack(GT2Connection connection, unsigned short SNMin, unsigned short SNMax) +{ + // data length + magic string + type + SNMin [+ SNMax] + // part of the buffer may not be used but more efficience to be on stack + char buffer[MAX_PROTOCOL_OFFSET + GTI2_MAGIC_STRING_LEN + 1 + 2 + 2]; + int pos = 0; + + // write the VDP data length + if (connection->socket->protocolType == GTI2VdpProtocol) + { + short dataLength = (GTI2_MAGIC_STRING_LEN + 1 + 2 + 2); + memcpy(buffer, &dataLength, 2); + pos += 2; + } + + // write the magic string + memcpy(buffer + pos, GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN); + pos += GTI2_MAGIC_STRING_LEN; + + // write the type + buffer[pos++] = GTI2MsgNack; + + // write the SNMin + gti2UShortToBuffer((GT2Byte *)buffer, pos, SNMin); + pos += 2; + + // write the SNMax if it's different + if(SNMin != SNMax) + { + gti2UShortToBuffer((GT2Byte *)buffer, pos, SNMax); + pos += 2; + } + + // send it + if(!gti2ConnectionSendData(connection, (const GT2Byte *)buffer, pos)) + return GT2False; + + return GT2True; +} + + +GT2Bool gti2SendPing(GT2Connection connection) +{ + // data length + magic string + type + "time" + current time + // part of the buffer may not be used but more efficience to be on stack + char buffer[MAX_PROTOCOL_OFFSET + GTI2_MAGIC_STRING_LEN + 1 + 4 + sizeof(gsi_time)]; + int pos = 0; + gsi_time now; + + // write the VDP data length + if (connection->socket->protocolType == GTI2VdpProtocol) + { + short dataLength = (GTI2_MAGIC_STRING_LEN + 1 + 4 + sizeof(gsi_time)); + memcpy(buffer, &dataLength, 2); + pos += 2; + } + // write the magic string + memcpy(buffer + pos, GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN); + pos += GTI2_MAGIC_STRING_LEN; + + // write the type + buffer[pos++] = GTI2MsgPing; + + // copy the time id + memcpy(buffer + pos, "time", 4); + pos += 4; + + // write the current time + now = current_time(); + memcpy(buffer + pos, &now, sizeof(gsi_time)); + + pos += (int)sizeof(gsi_time); + // send it + if(!gti2ConnectionSendData(connection, (const GT2Byte *)buffer, pos)) + return GT2False; + + return GT2True; +} + + +GT2Bool gti2SendPong(GT2Connection connection, GT2Byte * message, int len) +{ + // change the ping to a pong + message[GTI2_MAGIC_STRING_LEN] = GTI2MsgPong; + + // send it + return gti2ConnectionSendData(connection, message, len); +} + +GT2Bool gti2SendClosed(GT2Connection connection) +{ + // normal close + return gti2SendClosedOnSocket(connection->socket, connection->ip, connection->port); +} + + +GT2Bool gti2SendClosedOnSocket(GT2Socket socket, unsigned int ip, unsigned short port) +{ + // Vdp data length (not including voice) + magic string + type + // part of the buffer may not be used but more efficience to be on stack + char buffer[MAX_PROTOCOL_OFFSET + GTI2_MAGIC_STRING_LEN + 1]; + int pos = 0; + + // write the data length + if (socket->protocolType == GTI2VdpProtocol) + { + short dataLength = GTI2_MAGIC_STRING_LEN + 1; + memcpy(buffer, &dataLength, 2); + pos += 2; + } + + // write the magic string + memcpy(buffer + pos, GTI2_MAGIC_STRING, GTI2_MAGIC_STRING_LEN); + pos += GTI2_MAGIC_STRING_LEN; + + // write the type + buffer[pos++] = GTI2MsgClosed; + + // send it + if(!gti2SocketSend(socket, ip, port, (const GT2Byte *)buffer, pos)) + return GT2False; + + return GT2True; +} + + +GT2Bool gti2ResendMessage(GT2Connection connection, GTI2OutgoingBufferMessage * message) +{ + GTI2MessageType type; + int pos; + + // replace the ESN (it's after the magic string, the type, and the SN) + pos = (message->start + connection->socket->protocolOffset + GTI2_MAGIC_STRING_LEN + 1 + 2); + + gti2UShortToBuffer(connection->outgoingBuffer.buffer, pos, connection->expectedSerialNumber); + + // send the message + if(!gti2ConnectionSendData(connection, connection->outgoingBuffer.buffer + message->start, message->len)) + return GT2False; + + // update the last time sent + message->lastSend = connection->lastSend; + + // if it was a server challenge, update that time too + type = (GTI2MessageType)connection->outgoingBuffer.buffer[message->start + connection->socket->protocolOffset + GTI2_MAGIC_STRING_LEN]; + + if(type == GTI2MsgServerChallenge) + connection->challengeTime = connection->lastSend; + + return GT2True; +} + +GT2Bool gti2Send(GT2Connection connection, const GT2Byte * message, int len, GT2Bool reliable) +{ + if (reliable) + return gti2SendAppReliable(connection, message, len); + //send unreliable messages + return gti2SendAppUnreliable(connection, message, len); +} + +GT2Bool gti2WasMessageIDConfirmed(const GT2Connection connection, GT2MessageID messageID) +{ + GTI2OutgoingBufferMessage * message; + int len; + + // if there are no reliable messages waiting confirmation, then this has already been confirmed + len = ArrayLength(connection->outgoingBufferMessages); + if(!len) + return GT2True; + + // get the oldest message waiting confirmation + message = (GTI2OutgoingBufferMessage *)ArrayNth(connection->outgoingBufferMessages, 0); + + // if the message id we are looking for is older than the first one waiting confirmation, + // then it has already been confirmed + if(gti2SNDiff(messageID, message->serialNumber) < 0) + return GT2True; + + // the message hasn't been confirmed yet + return GT2False; +} diff --git a/code/gamespy/gt2/gt2Message.h b/code/gamespy/gt2/gt2Message.h new file mode 100644 index 00000000..96336bb1 --- /dev/null +++ b/code/gamespy/gt2/gt2Message.h @@ -0,0 +1,42 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_MESSAGE_H_ +#define _GT2_MESSAGE_H_ + +#include "gt2Main.h" + +GT2Bool gti2ReceiveMessages(GT2Socket socket); + +GT2Bool gti2Send(GT2Connection connection, const GT2Byte * message, int len, GT2Bool reliable); + +GT2Bool gti2SendAppReliable(GT2Connection connection, const GT2Byte * message, int len); +GT2Bool gti2SendAppUnreliable(GT2Connection connection, const GT2Byte * message, int len); +GT2Bool gti2SendClientChallenge(GT2Connection connection, const char challenge[GTI2_CHALLENGE_LEN]); +GT2Bool gti2SendServerChallenge(GT2Connection connection, const char response[GTI2_RESPONSE_LEN], const char challenge[GTI2_CHALLENGE_LEN]); +GT2Bool gti2SendClientResponse(GT2Connection connection, const char response[GTI2_RESPONSE_LEN], const char * message, int len); +GT2Bool gti2SendAccept(GT2Connection connection); +GT2Bool gti2SendReject(GT2Connection connection, const GT2Byte * message, int len); +GT2Bool gti2SendClose(GT2Connection connection); +GT2Bool gti2SendKeepAlive(GT2Connection connection); +GT2Bool gti2SendAck(GT2Connection connection); +GT2Bool gti2SendNack(GT2Connection connection, unsigned short SNMin, unsigned short SNMax); +GT2Bool gti2SendPing(GT2Connection connection); +GT2Bool gti2SendPong(GT2Connection connection, GT2Byte * message, int len); +GT2Bool gti2SendClosed(GT2Connection connection); +GT2Bool gti2SendClosedOnSocket(GT2Socket socket, unsigned int ip, unsigned short port); + +GT2Bool gti2ResendMessage(GT2Connection connection, GTI2OutgoingBufferMessage * message); + +GT2Bool gti2HandleConnectionReset(GT2Socket socket, unsigned int ip, unsigned short port); +GT2Bool gti2HandleHostUnreachable(GT2Socket socket, unsigned int ip, unsigned short port, GT2Bool send); +GT2Bool gti2WasMessageIDConfirmed(const GT2Connection connection, GT2MessageID messageID); + +#endif diff --git a/code/gamespy/gt2/gt2Socket.c b/code/gamespy/gt2/gt2Socket.c new file mode 100644 index 00000000..31ef7d58 --- /dev/null +++ b/code/gamespy/gt2/gt2Socket.c @@ -0,0 +1,528 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Socket.h" +#include "gt2Buffer.h" +#include "gt2Message.h" +#include "gt2Connection.h" +#include "gt2Utility.h" +#include "gt2Callback.h" +#include + +#ifdef GSI_ADHOC +// External functions defined at the platform specific level +// to be moved to nonport or socket.h +extern int _NetworkAdHocSocketCreate(gsi_u16 port); +extern void _NetworkAdHocSocketDestroy ( int adhoc_socket); +extern int _NetworkAdHocSocketSendTo ( int adhoc_socket, + const void *data, + int len, + int flags, + const char *dest_addr, + gsi_u16 dest_port + ); +extern void NetAdhocMacGet(char *mac); +#endif + +#define GTI2_DEFAULT_INCOMING_BUFFER_SIZE (64 * 1024) +#define GTI2_DEFAULT_OUTGOING_BUFFER_SIZE (64 * 1024) + +#define STARTING_SERIAL_NUMBER 0 + +static int gti2ConnectionHash(const void * elem, int numBuckets) +{ + GT2Connection connection = *(GT2Connection *)elem; + + return (int)(((connection->ip * connection->port)) % numBuckets); +} + +static int GS_STATIC_CALLBACK gti2ConnectionCompare(const void * elem1, const void * elem2) +{ + GT2Connection connection1 = *(GT2Connection *)elem1; + GT2Connection connection2 = *(GT2Connection *)elem2; + + if(connection1->ip != connection2->ip) + return (int)(connection1->ip - connection2->ip); + + return (int)(short)(connection1->port - connection2->port); +} + +static void gti2ConnectionFree(void * elem) +{ + gti2ConnectionCleanup(*(GT2Connection *)elem); +} + +GT2Connection gti2SocketFindConnection(GT2Socket socket, unsigned int ip, unsigned short port) +{ + GTI2Connection connection; + GT2Connection connectionPtr; + GT2Connection * connectionPtrPtr; + + connection.ip = ip; + connection.port = port; + + connectionPtr = &connection; + connectionPtrPtr = (GT2Connection *)TableLookup(socket->connections, &connectionPtr); + if(connectionPtrPtr) + return *connectionPtrPtr; + + return NULL; +} + +GT2Result gti2CreateSocket +( + GT2Socket * sock, + const char * localAddress, + int outgoingBufferSize, + int incomingBufferSize, + gt2SocketErrorCallback callback, + GTI2ProtocolType type +) +{ + SOCKADDR_IN address; + GT2Socket socketTemp; + int rcode; + unsigned int ip; + unsigned short port; + int len; + + // startup the sockets engine if needed + SocketStartUp(); + + // check for using defaults + if(!incomingBufferSize) + incomingBufferSize = GTI2_DEFAULT_INCOMING_BUFFER_SIZE; + if(!outgoingBufferSize) + outgoingBufferSize = GTI2_DEFAULT_OUTGOING_BUFFER_SIZE; + + // convert the address to an IP and port + if(!gt2StringToAddress(localAddress, &ip, &port)) + return GT2AddressError; + + // allocate the object + socketTemp = (GT2Socket)gsimalloc(sizeof(GTI2Socket)); + if(!socketTemp) + return GT2OutOfMemory; + + // set initial values + memset(socketTemp, 0, sizeof(GTI2Socket)); + socketTemp->socket = INVALID_SOCKET; + socketTemp->incomingBufferSize = incomingBufferSize; + socketTemp->outgoingBufferSize = outgoingBufferSize; + socketTemp->socketErrorCallback = callback; + + // create the connections table + socketTemp->connections = TableNew2(sizeof(GT2Connection), 32, 2, gti2ConnectionHash, gti2ConnectionCompare, NULL); + if(!socketTemp->connections) + { + gsifree(socketTemp); + return GT2OutOfMemory; + } + + // create the closed connections list + socketTemp->closedConnections = ArrayNew(sizeof(GT2Connection), 4, gti2ConnectionFree); + if(!socketTemp->closedConnections) + { + TableFree(socketTemp->connections); + gsifree(socketTemp); + return GT2OutOfMemory; + } + + // create the socket + +#ifdef _XBOX + if (type == GTI2VdpProtocol) + + socketTemp->socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_VDP); + else +#endif +#ifdef GSI_ADHOC + if (type == GTI2AdHocProtocol) + { + socketTemp->socket = _NetworkAdHocSocketCreate( port); + } + else +#endif + socketTemp->socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + socketTemp->protocolType = type; + + if (type == GTI2AdHocProtocol) + { + socketTemp->protocolOffset = 0; + } + else + socketTemp->protocolOffset = type; + +#ifdef _XBOX + if (type == GTI2UdpProtocol) + { + SetSockBroadcast(socketTemp->socket); + socketTemp->protocolOffset = type; + } +#endif + + if(socketTemp->socket == INVALID_SOCKET) + { + TableFree(socketTemp->connections); + ArrayFree(socketTemp->closedConnections); + gsifree(socketTemp); + return GT2NetworkError; + } + + // bind it + memset(&address, 0, sizeof(SOCKADDR_IN)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = ip; + address.sin_port = htons(port); + if (type != GTI2AdHocProtocol) + { + rcode = bind(socketTemp->socket, (SOCKADDR *)&address, sizeof(SOCKADDR_IN)); + if (gsiSocketIsError(rcode)) + { + closesocket(socketTemp->socket); + TableFree(socketTemp->connections); + ArrayFree(socketTemp->closedConnections); + gsifree(socketTemp); + return GT2NetworkError; + } + } + + // get the ip and port we're bound to + #ifdef GSI_ADHOC + if (type == GTI2AdHocProtocol) + { + char mac[6]; + NetAdhocMacGet(mac); + + socketTemp->ip = gt2MacToIp(mac); + socketTemp->port = ntohs(address.sin_port); + } + else + #endif + { + len = sizeof(SOCKADDR_IN); + getsockname(socketTemp->socket, (SOCKADDR *)&address, &len); + socketTemp->ip = address.sin_addr.s_addr; + socketTemp->port = ntohs(address.sin_port); + } + + *sock = socketTemp; + + return GT2Success; +} + +void gti2CloseSocket(GT2Socket socket) +{ + + if(socket->callbackLevel) + { + socket->close = GT2True; + return; + } + +#ifdef GSI_ADHOC + if (socket->protocolType == GTI2AdHocProtocol) + { + _NetworkAdHocSocketDestroy(socket->socket); + } + else +#endif + { + closesocket(socket->socket); + } + + TableFree(socket->connections); + ArrayFree(socket->closedConnections); + gsifree(socket); + + SocketShutDown(); +} + +void gti2Listen(GT2Socket socket, gt2ConnectAttemptCallback callback) +{ + socket->connectAttemptCallback = callback; +} + +static GT2Connection gti2CreateConnectionObject(void) +{ + // mj todo: give options of allocating this from a static pool for games with known number of connections. + return (GT2Connection)gsimalloc(sizeof(GTI2Connection)); +} + +GT2Result gti2NewSocketConnection(GT2Socket socket, GT2Connection * connection, unsigned int ip, unsigned short port) +{ + GT2Connection connectionPtr = NULL; + + // check if this ip and port already exists + if(gti2SocketFindConnection(socket, ip, port)) + return GT2DuplicateAddress; + + // allocate a connection object + connectionPtr = gti2CreateConnectionObject(); + if(!connectionPtr) + goto out_of_memory; + + // set some basics + memset(connectionPtr, 0, sizeof(GTI2Connection)); + connectionPtr->ip = ip; + connectionPtr->port = port; + connectionPtr->socket = socket; + connectionPtr->startTime = current_time(); + connectionPtr->lastSend = connectionPtr->startTime; + connectionPtr->serialNumber = STARTING_SERIAL_NUMBER; + connectionPtr->expectedSerialNumber = STARTING_SERIAL_NUMBER; + + // allocate the buffers + if(!gti2AllocateBuffer(&connectionPtr->incomingBuffer, socket->incomingBufferSize)) + goto out_of_memory; + if(!gti2AllocateBuffer(&connectionPtr->outgoingBuffer, socket->outgoingBufferSize)) + goto out_of_memory; + + // allocate the message arrays + connectionPtr->incomingBufferMessages = ArrayNew(sizeof(GTI2IncomingBufferMessage), 64, NULL); + if(!connectionPtr->incomingBufferMessages) + goto out_of_memory; + connectionPtr->outgoingBufferMessages = ArrayNew(sizeof(GTI2OutgoingBufferMessage), 64, NULL); + if(!connectionPtr->outgoingBufferMessages) + goto out_of_memory; + + // allocate the filter arrays + connectionPtr->sendFilters = ArrayNew(sizeof(gt2SendFilterCallback), 2, NULL); + if(!connectionPtr->sendFilters) + goto out_of_memory; + connectionPtr->receiveFilters = ArrayNew(sizeof(gt2ReceiveFilterCallback), 2, NULL); + if(!connectionPtr->receiveFilters) + goto out_of_memory; + + // add it to the table + TableEnter(socket->connections, &connectionPtr); + + // check that it's in the table (and get the address) + *connection = gti2SocketFindConnection(socket, ip, port); + if(!*connection) + goto out_of_memory; + + return GT2Success; + +out_of_memory: + + // there wasn't enough memory, free everything and return the error + if(connectionPtr) + { + gsifree(connectionPtr->incomingBuffer.buffer); + gsifree(connectionPtr->outgoingBuffer.buffer); + if(connectionPtr->incomingBufferMessages) + ArrayFree(connectionPtr->incomingBufferMessages); + if(connectionPtr->outgoingBufferMessages) + ArrayFree(connectionPtr->outgoingBufferMessages); + if(connectionPtr->sendFilters) + ArrayFree(connectionPtr->sendFilters); + if(connectionPtr->receiveFilters) + ArrayFree(connectionPtr->receiveFilters); + gsifree(connectionPtr); + } + + return GT2OutOfMemory; +} + +void gti2FreeSocketConnection(GT2Connection connection) +{ + // check if we can actually free it + if(connection->freeAtAcceptReject || connection->callbackLevel) + return; + + // remove it from the correct list depending on the connect state + if(connection->state == GTI2Closed) + { + int len; + int i; + + len = ArrayLength(connection->socket->closedConnections); + for(i = 0 ; i < len ; i++) + { + if(connection == *(GT2Connection *)ArrayNth(connection->socket->closedConnections, i)) + { + ArrayDeleteAt(connection->socket->closedConnections, i); + return; + } + } + } + else + { + TableRemove(connection->socket->connections, &connection); + } +} + +GT2Bool gti2SocketSend(GT2Socket socket, unsigned int ip, unsigned short port, const GT2Byte * message, int len) +{ + SOCKADDR_IN address; + int rcode; + +#ifdef GTI2_DROP_SEND_RATE + // drop some percentage of packets and see what happens + if((rand() % 100) < GTI2_DROP_SEND_RATE) + return GT2True; +#endif + + // check the message and len + gti2MessageCheck(&message, &len); + + if (socket->protocolType != GTI2AdHocProtocol) + { + #ifndef INSOCK // insock never sets write flag for UDP sockets + // check if we can't send + if(!CanSendOnSocket(socket->socket)) + return GT2True; + #endif + } + + // do the send + #ifdef GSI_ADHOC + if (socket->protocolType == GTI2AdHocProtocol) + { + char mac[6]; + // convert to mac + gt2IpToMac(ip,mac); + // change IP address to mac ethernet + rcode = _NetworkAdHocSocketSendTo(socket->socket, (const char *)message, len, 0, mac, port); + if(rcode <0) + { + gti2SocketError(socket); + return gsi_false; + } + + // let the dump handle this + if(socket->sendDumpCallback) + { + if(!gti2DumpCallback(socket, gti2SocketFindConnection(socket, ip, port), ip, port, GT2False, message, len, GT2True)) + return GT2False; + } + return gsi_true; + } + + #endif + + // fill the address structure + memset(&address, 0, sizeof(SOCKADDR_IN)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = ip; + address.sin_port = htons(port); + rcode = sendto(socket->socket, (const char *)message, len, 0, (SOCKADDR *)&address, sizeof(SOCKADDR_IN)); + if (gsiSocketIsError(rcode)) + { + rcode = GOAGetLastError(socket->socket); + if(rcode == WSAECONNRESET) + { + // handle the reset + if(!gti2HandleConnectionReset(socket, ip, port)) + return GT2False; + } +#ifndef SN_SYSTEMS + else if (rcode == WSAEHOSTUNREACH) + { + if (!gti2HandleHostUnreachable(socket, ip, port, GT2True)) + return GT2False; + } +#endif + // some systems might return these errors + else if((rcode == WSAENOBUFS) || (rcode == WSAEWOULDBLOCK)) + { + return GT2True; + } +#if defined(SN_SYSTEMS) || defined(_NITRO) || defined(_REVOLUTION) + // for systems that don't support WSAEHOSTDOWN (EHOSTDOWN) + else if((rcode != WSAEMSGSIZE)) +#else + else if((rcode != WSAEMSGSIZE) && (rcode != WSAEHOSTDOWN)) +#endif + { + // fatal socket error + gti2SocketError(socket); + return GT2False; + } + } + else + { + // let the dump handle this + if(socket->sendDumpCallback) + { + if(!gti2DumpCallback(socket, gti2SocketFindConnection(socket, ip, port), ip, port, GT2False, message, len, GT2True)) + return GT2False; + } + } + + return GT2True; +} + +static int gti2SocketConnectionsThinkMap(void * elem, void * clientData) +{ + GT2Connection connection = *(GT2Connection *)elem; + gsi_time now = *(gsi_time *)clientData; + + // only think if we're not closed + if(connection->state != GTI2Closed) + { + // think + if(!gti2ConnectionThink(connection, now)) + return 0; + } + + // check for a closed connection + if((connection->state == GTI2Closed) && !connection->freeAtAcceptReject && !connection->callbackLevel) + gti2FreeSocketConnection(connection); + + return 1; +} + +GT2Bool gti2SocketConnectionsThink(GT2Socket socket) +{ + gsi_time now; + + // get the current time + now = current_time(); + + // go through the list of connections and let them think + if(TableMapSafe2(socket->connections, gti2SocketConnectionsThinkMap, &now)) + return GT2False; + + return GT2True; +} + +void gti2FreeClosedConnections(GT2Socket socket) +{ + int i; + int len; + + // loop through the closed connections, attempting to free them all + len = ArrayLength(socket->closedConnections); + for(i = (len - 1) ; i >= 0 ; i--) + gti2FreeSocketConnection(*(GT2Connection *)ArrayNth(socket->closedConnections, i)); +} + +void gti2SocketError(GT2Socket socket) +{ + // if there was already an error, don't go through this again + if(socket->error) + return; + + // flag the error + socket->error = GT2True; + + // first close all the socket's connections + gt2CloseAllConnectionsHard(socket); + + // call the error callback + if(!gti2SocketErrorCallback(socket)) + return; + + // close the socket + gti2CloseSocket(socket); +} diff --git a/code/gamespy/gt2/gt2Socket.h b/code/gamespy/gt2/gt2Socket.h new file mode 100644 index 00000000..5975945e --- /dev/null +++ b/code/gamespy/gt2/gt2Socket.h @@ -0,0 +1,45 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_SOCKET_H_ +#define _GT2_SOCKET_H_ + +#include "gt2Main.h" + +GT2Result gti2CreateSocket +( + GT2Socket * socket, + const char * localAddress, + int outgoingBufferSize, + int incomingBufferSize, + gt2SocketErrorCallback callback, + GTI2ProtocolType type +); + +void gti2CloseSocket(GT2Socket socket); + +void gti2Listen(GT2Socket socket, gt2ConnectAttemptCallback callback); + +GT2Result gti2NewSocketConnection(GT2Socket socket, GT2Connection * connection, unsigned int ip, unsigned short port); +void gti2FreeSocketConnection(GT2Connection connection); + +GT2Connection gti2SocketFindConnection(GT2Socket socket, unsigned int ip, unsigned short port); + +// ip is network byte order, port is host byte order +// returns false if there was a fatal error +GT2Bool gti2SocketSend(GT2Socket socket, unsigned int ip, unsigned short port, const GT2Byte * message, int len); + +GT2Bool gti2SocketConnectionsThink(GT2Socket socket); + +void gti2FreeClosedConnections(GT2Socket socket); + +void gti2SocketError(GT2Socket socket); + +#endif diff --git a/code/gamespy/gt2/gt2Utility.c b/code/gamespy/gt2/gt2Utility.c new file mode 100644 index 00000000..5a8f3a10 --- /dev/null +++ b/code/gamespy/gt2/gt2Utility.c @@ -0,0 +1,421 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Main.h" +#include "gt2Utility.h" +#include +#include +#ifdef WIN32 +#include +#include +#endif + +#define GTI2_STACK_HOSTLEN_MAX 256 + + +/************************* +** BYTE ORDER FUNCTIONS ** +*************************/ + +unsigned int gt2NetworkToHostInt(unsigned int i) +{ + return (unsigned int)ntohl(i); +} + +unsigned int gt2HostToNetworkInt(unsigned int i) +{ + return (unsigned int)htonl(i); +} + +unsigned short gt2NetworkToHostShort(unsigned short s) +{ + return ntohs(s); +} + +unsigned short gt2HostToNetworkShort(unsigned short s) +{ + return htons(s); +} + +/******************************* +** INTERNET ADDRESS FUNCTIONS ** +*******************************/ + +const char * gt2AddressToString(unsigned int ip, unsigned short port, char string[22]) +{ + static char strAddressArray[2][22]; + static int nIndex; + char * strAddress; + + if(string) + strAddress = string; + else + { + nIndex ^= 1; + strAddress = strAddressArray[nIndex]; + } + + if(ip) + { + IN_ADDR inAddr; + + inAddr.s_addr = ip; + + if(port) + sprintf(strAddress, "%s:%d", inet_ntoa(inAddr), port); + else + sprintf(strAddress, "%s", inet_ntoa(inAddr)); + } + else if(port) + sprintf(strAddress, ":%d", port); + else + strAddress[0] = '\0'; + + return strAddress; +} + +GT2Bool gt2StringToAddress(const char * string, unsigned int * ip, unsigned short * port) +{ + unsigned int srcIP = 0; // avoid use uninit condition + unsigned short srcPort = 0; + + if(!string || !string[0]) + { + srcIP = 0; + srcPort = 0; + } + else + { + char stackHost[GTI2_STACK_HOSTLEN_MAX + 1]; + const char * colon; + const char * host; + + // Is there a port? + colon = strchr(string, ':'); + if(!colon) + { + // The string is the host. + host = string; + + // No port. + srcPort = 0; + } + else + { + int len; + const char * check; + int temp; + + // Is it just a port? + if(colon == string) + { + host = NULL; + srcIP = 0; + } + else + { + // Copy the host portion into the array on the stack. + len = (colon - string); + assert(len < GTI2_STACK_HOSTLEN_MAX); + memcpy(stackHost, string, (unsigned int)len); + stackHost[len] = '\0'; + host = stackHost; + } + + // Check the port. + for(check = (colon + 1) ; *check ; check++) + if(!isdigit(*check)) + return GT2False; + + // Get the port. + temp = atoi(colon + 1); + if((temp < 0) || (temp > 0xFFFF)) + return GT2False; + srcPort = (unsigned short)temp; + } + + // Is there a host? + if(host) + { + // Try dotted IP. + ///////////////// + srcIP = inet_addr(host); + if(srcIP == INADDR_NONE) + { +#if defined(_XBOX) + return GT2False; +#else + HOSTENT * hostent; + + hostent = gethostbyname(host); + if(hostent == NULL) + return GT2False; + + srcIP = *(unsigned int *)hostent->h_addr_list[0]; +#endif + } + } + } + + if(ip) + *ip = srcIP; + if(port) + *port = srcPort; + + return GT2True; +} + +#ifdef GSI_ADHOC +gsi_u32 gti2MacToIp(const char *mac) +// change mac ethernet to IP address +{ + // Mac (48 bit)<---> IP (32 bit) convertion. + // horrible hack. But the chances of a conflict are less then getting struck by lightning + // but in order to translate back, we will need to keep a table + // stick real value in table. + GTI2MacEntry *entry; + int i; + gsi_u32 ip; + memcpy(&ip,mac,4); // store rest in table + + + // find match in table + for (i=0;i< MAC_TABLE_SIZE;i++) + { + if(MacTable[i].ip == ip) + { + return ip; // already there, don't add to the table. + } + } + + + // if not found match, add it, overwriting oldest entry + entry = &MacTable[lastmactableentry]; + lastmactableentry = (lastmactableentry +1 ) & (MAC_TABLE_SIZE-1); + entry->ip = ip; + memcpy(entry->mac,mac,6); + + return ip; +} + +void gti2IpToMac(gsi_u32 ip,char *mac) +// change IP address to mac ethernet +{ + int i; + // find match in table + for (i=0;i< MAC_TABLE_SIZE;i++) + { + if(MacTable[i].ip == ip) + { + memcpy(mac,MacTable[i].mac,6); + return; + } + } + // error, can not find mac address + memset(mac,0,6); + GS_FAIL(); +} +#endif + +#if defined(_XBOX) + +// XBox doesn't support the address functions +static const char * gti2HandleHostInfo(HOSTENT * host, char *** aliases, unsigned int *** ips) { return NULL; } +const char * gt2IPToHostInfo(unsigned int ip, char *** aliases, unsigned int *** ips) { return NULL; } +const char * gt2StringToHostInfo(const char * string, char *** aliases, unsigned int *** ips) { return NULL; } + +unsigned int gt2XnAddrToIP(XNADDR theAddr, XNKID theKeyId) +{ + // String to fit ip address + : + port + IN_ADDR anAddr; + if (XNetXnAddrToInAddr(&theAddr, &theKeyId, &anAddr)== 0) + return anAddr.s_addr; + return 0; +} + +GT2Bool gt2IPToXnAddr(int ip, XNADDR *theAddr, XNKID *theKeyId) +{ + IN_ADDR anAddr; + anAddr.s_addr = ip; + if (XNetInAddrToXnAddr(anAddr, theAddr, theKeyId) == 0) + return GT2True; + return GT2False; +} + +#else + +static const char * gti2HandleHostInfo(HOSTENT * host, char *** aliases, unsigned int *** ips) +{ + if(!host || (host->h_addrtype != AF_INET) || (host->h_length != 4)) + return NULL; + + if(aliases) + *aliases = host->h_aliases; + if(ips) + *ips = (unsigned int **)host->h_addr_list; + + return host->h_name; +} + + +const char * gt2IPToHostInfo(unsigned int ip, char *** aliases, unsigned int *** ips) +{ +#ifdef _PSP + return NULL; +#else + HOSTENT * host; + + host = gethostbyaddr((const char *)&ip, 4, AF_INET); + + GSI_UNUSED(ip); + return gti2HandleHostInfo(host, aliases, ips); +#endif +} + +const char * gt2StringToHostInfo(const char * string, char *** aliases, unsigned int *** ips) +{ + HOSTENT * host; + unsigned int ip; + + if(!string || !string[0]) + return NULL; + + // Is the string actually a dotted IP? + ip = inet_addr(string); + if(ip != INADDR_NONE) + return gt2IPToHostInfo(ip, aliases, ips); + + host = gethostbyname(string); + + return gti2HandleHostInfo(host, aliases, ips); +} + +#endif + +const char * gt2IPToHostname(unsigned int ip) +{ + return gt2IPToHostInfo(ip, NULL, NULL); +} + +const char * gt2StringToHostname(const char * string) +{ + return gt2StringToHostInfo(string, NULL, NULL); +} + +char ** gt2IPToAliases(unsigned int ip) +{ + char ** aliases; + + if(!gt2IPToHostInfo(ip, &aliases, NULL)) + return NULL; + + return aliases; +} + +char ** gt2StringToAliases(const char * string) +{ + char ** aliases; + + if(!gt2StringToHostInfo(string, &aliases, NULL)) + return NULL; + + return aliases; +} + +unsigned int ** gt2IPToIPs(unsigned int ip) +{ + unsigned int ** ips; + + if(!gt2IPToHostInfo(ip, NULL, &ips)) + return NULL; + + return ips; +} + +unsigned int ** gt2StringToIPs(const char * string) +{ + unsigned int ** ips; + + if(!gt2StringToHostInfo(string, NULL, &ips)) + return NULL; + + return ips; +} + +/*********************** +** INTERNAL FUNCTIONS ** +***********************/ + +#ifdef __MWERKS__ // CodeWarrior will warn if not prototyped +void gti2MessageCheck(const GT2Byte ** message, int * len); +#endif + +// Used from gt2main.c +void gti2MessageCheck(const GT2Byte ** message, int * len) +{ + // check for an empty message + if(!*message) + { + *message = (const GT2Byte *)""; + *len = 0; + } + // check for calculating the message length + else if(*len == -1) + { + *len = (int)(strlen((const char *)*message) + 1); + } +} + +#ifdef RECV_LOG +void gti2LogMessage +( + unsigned int fromIP, unsigned short fromPort, + unsigned int toIP, unsigned short toPort, + const GT2Byte * message, int len +) +{ + FILE * file; + IN_ADDR ip; + int i; +#ifdef WIN32 + struct _timeb utcTime; + struct tm * now; +#endif + + file = fopen("recv.log", "at"); + if(!file) + return; + + // date-time +#ifdef WIN32 + _ftime(&utcTime); + now = localtime(&utcTime.time); + fprintf(file, "%02d.%02d.%02d %02d:%02d:%02d.%03d\n", + now->tm_year - 100, now->tm_mon + 1, now->tm_mday, + now->tm_hour, now->tm_min, now->tm_sec, utcTime.millitm); +#endif + + // from + ip.s_addr = fromIP; + fprintf(file, "%s:%d -> ", inet_ntoa(ip), fromPort); + + // to + ip.s_addr = toIP; + fprintf(file, "%s:%d\n", inet_ntoa(ip), toPort); + + // data + fprintf(file, "%d: ", len); + for(i = 0 ; i < len ; i++) + fprintf(file, "%02X ", message[i]); + fprintf(file, "\n\n"); + + fclose(file); +} +#endif diff --git a/code/gamespy/gt2/gt2Utility.h b/code/gamespy/gt2/gt2Utility.h new file mode 100644 index 00000000..34a5e439 --- /dev/null +++ b/code/gamespy/gt2/gt2Utility.h @@ -0,0 +1,28 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#ifndef _GT2_UTILITY_H_ +#define _GT2_UTILITY_H_ + +#include "gt2Main.h" + +void gti2MessageCheck(const GT2Byte ** message, int * len); + +#ifdef RECV_LOG +void gti2LogMessage +( + unsigned int fromIP, unsigned short fromPort, + unsigned int toIP, unsigned short toPort, + const GT2Byte * message, int len +); +#endif + + +#endif diff --git a/code/gamespy/gt2/gt2action/TGAFile.cpp b/code/gamespy/gt2/gt2action/TGAFile.cpp new file mode 100644 index 00000000..d65382e5 --- /dev/null +++ b/code/gamespy/gt2/gt2action/TGAFile.cpp @@ -0,0 +1,1574 @@ +/* + + Modified by Chris Losinger for Smaller Animals Software's ImgLib/ImgDLL. + Esp. RGB->BGR and row order switches. 9/98 +*/ +#include +#include + +/* + TGADefs.h - Tye and Constant declaration file + This File defines the Types, and the Constant Values used by the + TGAFile Class. + + Created By: Timothy A. Bish + Created On: 08/18/98 +*/ + +#ifndef TGADEFSH +#define TGADEFSH + +#include + +#pragma pack(1) // Align the structure on byte boundries... + +// Possible Image Types +#define TGA_NOIMAGETYPE 0 // No Image Data Included in Image +#define TGA_MAPRGBTYPE 1 // Colormapped Image Data - No Compression +#define TGA_RAWRGBTYPE 2 // Truecolor Image Data - No Compression +#define TGA_RAWMONOTYPE 3 // Monochrome Image Data - No Compression +#define TGA_MAPENCODETYPE 9 // Colormapped Image Data - Compressed RLE +#define TGA_RAWENCODETYPE 10 // Truecolor Image Data - Compressed RLE +#define TGA_MONOENCODETYPE 11 // Monochrome Image Data - Compressed RLE +// Version Macro +#define TGA_VERSIONONE 1 // Version 1 File Format +#define TGA_VERSIONTWO 2 // Version 2 File Format +// File Read Write Modes +const int GREYSC = 0; // Image is Greyscale +const int COLOUR = 1; // Image is Color +const int MAPPED = 2; // Image has a Color Map +const int RLENCO = 4; // Image is RLE Encoded + +// 18 Byte Sturcture representin the basic definitions of +// the image +typedef struct _aTGAHEADER +{ + BYTE IDLength; // 00h Size of ID Field + BYTE ColorMapType; // 01h Color Map Type + BYTE ImageType; // 02h Image Type Code + WORD CMapStart; // 03h Color Map Origin + WORD CMapLength; // 05h Color Map Length + BYTE CMapDepth; // 07h Color Map Depth + WORD XOffset; // 08h X origin of Image + WORD YOffset; // 0Ah Y origin of Image + WORD Width; // 0Ch Width of Image + WORD Height; // 0Eh Height of Image + BYTE PixelDepth; // 10h Image Pixel Size + BYTE ImageDescriptor; // 11h Image Description Byte +} TGAHEADER; + +// The footer is 26 Bytes in length and is always at the end of a +// TGA v2.0 file. +typedef struct _aTGAFOOTER +{ + DWORD ExtensionOffset; // Extension Area Offset + DWORD DeveloperOffset; // Developer Directory Offset + CHAR Signature[18]; // TGA Signature +} TGAFOOTER; + +typedef struct _aTGATAG +{ + WORD TagNumber; // ID Number of the Tag + DWORD DataOffset; // Offset Location of the Tag + DWORD DataSize; // Size of the Tag Data in Bytes +} TGATAG; + +// The extension area is basically the second header in the TGA v2.0 +// file format. +typedef struct _aTGAEXTENSION +{ + WORD Size; // Extension Size + CHAR AuthorName[41]; // Author Name + CHAR AuthorComment[324]; // Author Comment + WORD StampMonth; // Date/Time Stamp: Month + WORD StampDay; // Date/Time Stamp: Day + WORD StampYear; // Date/Time Stamp: Year + WORD StampHour; // Date/Time Stamp: Hour + WORD StampMinute; // Date/Time Stamp: Minute + WORD StampSecond; // Date/Time Stamp: Second + CHAR JobName[41]; // Job Name/ID + WORD JobHour; // Job Time: Hours + WORD JobMinute; // Job Time: Minutes + WORD JobSecond; // Job Time: Seconds + CHAR SoftwareId[41]; // Software ID + WORD VersionNumber; // Version Number of Software + BYTE VersionLetter; // Version Letter of Software + DWORD KeyColor; // Key Color + WORD PixelNumerator; // Pixel Aspect Ratio + WORD PixelDenominator; // Pixel Aspect Ratio + WORD GammaNumerator; // Gamma Value + WORD GammaDenominator; // Gamma Value + DWORD ColorOffset; // Color Correction Offset + DWORD StampOffset; // Postage Stamp Offset + DWORD ScanOffset; // Scanline Table Offset + BYTE AttributesType; // Attributes Type +} TGAEXTENSION; + +// The Color Correction Table is an array of 2048 Bytes in length, which +// contians 256 entries used to store the values used for color remapping. +typedef struct _aTGACOLORCORRECTIONTABLE +{ + SHORT Alpha; // Alpha Channel Seldom Used + SHORT Red; // Red Value of Correction + SHORT Green; // Green Value of Correction + SHORT Blue; // Green Value of Correction +} TGACOLORCORRECTIONTABLE; + +#define TRIALVERSION -1 // LIB was not initialized with a registered key + +#define IMGOK 0 // no err +#define MEMERR 1 // out of mem +#define FILEOPENERR 2 // error on file open +#define FILEREADERR 3 // error on file read +#define FILEWRITEERR 4 // error on file write +#define BADPARAMERR 5 // bad user param +#define INVALIDBMPERR 6 // bad BMP file +#define BMPRLEERR 7 // we don't do compressed (RLE) BMP files +#define INVALIDGIFERR 8 // bad GIF file +#define INVALIDJPGERR 9 // bad JPG file +#define IMGDCERR 10 // error with device context +#define IMGDIBERR 11 // problem with a GetDIBits call +#define NOGIFERR 12 // GIF support disabled +#define IMGNORESOURCE 13 // resource not found +#define CALLBACKCANCEL 14 // callback returned FALSE - operation aborted +#define INVALIDPNGERR 15 // bad PNG file +#define PNGCREATEERR 16 // internal PNG lib behavior - contact smaller animals s.w. +#define IMGDLLINTERNAL 17 // misc unexpected behavior error - contact smaller animals s.w. +#define IMGFONTERR 18 // trouble creating a font object +#define INTTIFFERR 19 // misc internal TIFF error +#define INVALIDTIFFERR 20 // invalid TIFF file +#define TIFFLZWNOTSUPPORTED 21 // this will not read TIFF-LZW iamges +#define INVALIDPCXERR 22 // invalid PCX image +#define CREATEBMPERR 23 // a call to the fn CreateCompatibleBitmap failed +#define IMGNOLINES 24 // end of an image while using single-line de/compression +#define GETDIBERR 25 // error during a call to GetDIBits +#define DEVOPNOSUPPORT 26 // device does not support an operation required by this function +#define INVALIDWMF 27 // invalid windows metafile +#define DEPTHMISMATCHERR 28 // the file was not of the requested bit-depth +#define INVALIDTGAERR 35 // Invalid TGA File +#define NOTGATHUMBNAIL 36 // No TGA Thumbnail in the file + +#pragma pack() + +#endif + +class TGAFile +{ + +public: + + // parameters + __int32 m_error; + +public: + + // operations + + TGAFile(); + + BOOL IsFileTGA(const char * fileName); + + LPVOID LoadTGA( const char *fileName, // Name of file + UINT32 *width, // Width in Pixel + UINT32 *height); // Height + + HGLOBAL LoadTGA8Bit(const char *fileName, // Name of File + UINT32 *width, // Width in pixels + UINT32 *height, // Height + RGBQUAD *pal); // Palette of RGBQUADS + + BOOL GetTGADims(const char *fileName, + UINT32 *width, + UINT32 *height); + + BOOL SaveTGA32(const char * fileName, // output path + BYTE *inBuf, // RGB buffer + UINT32 width, // size + UINT32 height); + + BOOL Save8BitTGA(const char * fileName, // output path + BYTE *colormappedbuffer, // one BYTE per pixel colomapped image + UINT32 width, // Width of image + UINT32 height, // Height of image + __int32 colors, // number of colors (number of RGBQUADs) + RGBQUAD *colormap); // array of RGBQUADs + + HGLOBAL LoadTGAThumbnail(const char *fileName, // Name of file + UINT32 *width, // Width in Pixel + UINT32 *height); // Height + +private: + + // Parameters + + char TGA_ImageIDFeild[256];// Text in file + BYTE TGA_Attribute; // Number of attribute bytes per pixel + // i.e. 1 for T16 and 8 for T32 + UINT32 mode; // Mode of current Read or Write + + // RLE Decompression Variables + BYTE Red, // Stores pixel value for + Grn, // RLE series of oixels + Blu, + Alpha; + UINT32 l; // Used when 8 bit files use RLE + __int32 RLECount, RLEFlag; // Indicates whether the RLE series + // is still going or is finished + +private: + + // Operations + + __int32 TGA_GetFileVersion(FILE *fp); // Determines whether this is a V1.0 + // or V2.0 TGA File + BOOL TGA_GetMapEntry(BYTE *Red, // Get the Color Values out of the + BYTE *Green, // Color map in the TGA File + BYTE *Blue, // Return TRUE on Success + BYTE *Alpha, + FILE *fp, + UINT32 Depth); + + // version that takes a file ptr + BOOL TGA_GetPixelValue(BYTE *Red, // Get and parse a single pixel value + BYTE *Grn, // from the TGA file. Handles Unencoding + BYTE *Blu, // of RLE encoded files. + BYTE *Alp, // plus Alpha (08jan00/bgw) + FILE *fp, + UINT32 PixelDepth, + RGBQUAD *CMap); + + // version that takes a buffer ptr + BOOL TGA_GetPixelValue(BYTE *Red, // Get and parse a single pixel value + BYTE *Grn, // from the TGA file. Handles Unencoding + BYTE *Blu, // of RLE encoded files. + BYTE *Alp, // plus Alpha (08jan00/bgw) + BYTE ** ppTGAData, + UINT32 PixelDepth, + RGBQUAD *CMap); +}; + +//#define MAX_IMAGEREAD_BUFFER 65535 +//BYTE * gpImageReadBuffer = NULL; +//long glImageReadBufferSize = 0; +//BYTE * gpImageReadBufPos = NULL; +// +// +//typedef struct tagTGAColorComponents +// { +// BYTE red; +// BYTE green; +// BYTE blue; +// BYTE alpha; +// } TGAColorComponents; + +/* TGA File REader Classs Implementation File + This Implementation Allows the reading of TGA (Targa) Files + into an RGB buffer. Also the class allows an RGB Buffer to be + written to a TGA File. There is also a function to determine + the dimensions of a TGA file. + + Created By: Timothy A. Bish + Created On: 08/17/98 + +*/ +//////////////////////////////////////////////////////////////////////////// +// No Much going on here +TGAFile::TGAFile() + { + m_error = IMGOK; + } + + +//////////////////////////////////////////////////////////////////////////// +// GetTGADimns +// Find dims of the image in a TGA file +// Returns - TRUE on success +BOOL +TGAFile::GetTGADims(const char * fileName, UINT32 * width, UINT32 * height) + { + // for safety + *width = 0; + *height = 0; + FILE * fp; + TGAHEADER tgahd; + + // Init the file Header to all zeros + ZeroMemory(&tgahd, sizeof(tgahd)); + + // init + m_error = IMGOK; + fp = fopen(fileName, "rb"); + + if(fp == NULL) + { + m_error = FILEOPENERR; + return FALSE; + } + + // Get the Header + if(fread(&tgahd, 1, sizeof(TGAHEADER), fp) != sizeof(TGAHEADER)) + { + m_error = FILEREADERR; + fclose(fp); + return FALSE; + } + + // Check fo valid data in structure + if(tgahd.PixelDepth > 32) + { + // I don't do Pixel Depths Bigger than 32 + m_error = INVALIDTGAERR; + fclose(fp); + return NULL; + } + + // Anything other than the standard TGA types + // and I quit + switch(tgahd.ImageType) + { + case TGA_MAPRGBTYPE: + case TGA_RAWRGBTYPE: + case TGA_RAWMONOTYPE: + case TGA_MAPENCODETYPE: + case TGA_RAWENCODETYPE: + case TGA_MONOENCODETYPE: + break; + + default: + m_error = INVALIDTGAERR; + fclose(fp); + return NULL; + } + + // Grab the Image dimensions + *width = tgahd.Width; + *height = tgahd.Height; + fclose(fp); + return TRUE; + } + +/***************************************************************************** +* NAME: +* TGAFile::IsFileTGA +* +* DESCRIPTION: +* Description goes here... +* +*******************************************************************************/ +BOOL +TGAFile::IsFileTGA(const char * fileName) + { + TGAHEADER tgahd; + FILE * fp; + + ZeroMemory(&tgahd, sizeof(tgahd)); + fp = fopen(fileName, "rb"); + long rc = fread(&tgahd, 1, sizeof(TGAHEADER), fp); + fclose(fp); + + if( (rc == sizeof(TGAHEADER)) && // must be big enough for a header... + (tgahd.PixelDepth == 32) && // 32-bit TGAs only + (tgahd.ImageType == TGA_RAWRGBTYPE) ) // Raw RGBA format only + return(TRUE); + + return(FALSE); + } + +//////////////////////////////////////////////////////////////////////////// +// LoadTGA +// load a .TGA file - 1,4,8,24,32 bit +// allocates and returns an RGB buffer containing the image. +// modifies width and height accordingly - NULL, 0, 0 on error +LPVOID +TGAFile::LoadTGA(const char * fileName, UINT32 * width, UINT32 * height) + { + LPVOID pNew = NULL; + BYTE * pRGB = NULL; + BYTE Alpha; + + TGAHEADER tgahd; + + BYTE TGA_Origin = 0; + + RGBQUAD CColMap[256]; + + // for safety + *width = 0; + *height = 0; + + // init + m_error = IMGOK; + + // Init the file Header to all zeros + ZeroMemory(&tgahd, sizeof(tgahd)); + FILE * fp; + fp = fopen(fileName, "rb"); + + if(fp == NULL) + { + m_error = FILEOPENERR; + return FALSE; + } + + // Read the TGA Header + long rc = fread(&tgahd, 1, sizeof(TGAHEADER), fp); + + if(rc != sizeof(TGAHEADER)) + { + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + + // Check fo valid data in structure + if((tgahd.PixelDepth> 32) || (tgahd.PixelDepth<8)) + { + // I don't do Pixel Depths Bigger than 32 + m_error = INVALIDTGAERR; + fclose(fp); + return NULL; + } + + // Anything other than the standard TGA types + // and I quit + switch(tgahd.ImageType) + { + case TGA_MAPRGBTYPE: + case TGA_RAWRGBTYPE: + case TGA_RAWMONOTYPE: + case TGA_MAPENCODETYPE: + case TGA_RAWENCODETYPE: + case TGA_MONOENCODETYPE: + break; + + default: + m_error = INVALIDTGAERR; + fclose(fp); + return NULL; + } + + // Set the number of Color Planes + if(tgahd.ImageType == TGA_RAWMONOTYPE) + { + mode = GREYSC; + } + else + { + mode = COLOUR; + } + + // Read the ID Descriptor if present + if(tgahd.IDLength != 0) + { + // Read the TGA Comments + long rc = fread(&TGA_ImageIDFeild, 1, tgahd.IDLength, fp); + + if(rc != tgahd.IDLength) + { + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + } + + // Parse the Image Descriptor + TGA_Attribute = (BYTE)(tgahd.ImageDescriptor & 0x0f); + TGA_Origin = (BYTE)((tgahd.ImageDescriptor & 0x20) / 32); + + // If present read the color map + if(tgahd.ColorMapType != 0) + { + // Get the color map + for(__int32 i = 0; i < (tgahd.CMapStart + tgahd.CMapLength); i++) + { + TGA_GetMapEntry(&CColMap[i].rgbRed, &CColMap[i].rgbGreen, &CColMap[i].rgbBlue, &Alpha, fp, tgahd.CMapDepth); + } + + // If the TGA file actually needs the color map + // Set the mode to show this + if((tgahd.ImageType != TGA_RAWRGBTYPE) && (tgahd.ImageType != TGA_RAWMONOTYPE) && (tgahd.ImageType != TGA_RAWENCODETYPE)) + mode = mode | MAPPED; + } + + // Check Run Length Encoding + if((tgahd.ImageType == TGA_MAPENCODETYPE) || (tgahd.ImageType == TGA_RAWENCODETYPE) || (tgahd.ImageType == TGA_MONOENCODETYPE)) + mode = mode | RLENCO; + + long lImgDataSize = (tgahd.Height * tgahd.Width) * (tgahd.PixelDepth / 8); + + // Allocate the memory buffers + pNew = malloc(lImgDataSize); +// pNew = (LPVOID) theApp.m_TGABuffer.GetBuffer((size_t)(tgahd.Width * tgahd.Height * 4)); + + if(pNew == NULL) + { + m_error = MEMERR; + fclose(fp); + return NULL; + } + else + pRGB = (BYTE *)pNew; + + // RGB from image Data + //DWORD destOffset = 0; + + RLECount = 0; + RLEFlag = 0; + +// // +// // (re-)alocate a local image buffer to read the TGA formatted +// // data into. this avoids the huge critical-section delays in +// // every call to getc(). +// // +// long lBufSize = lImgDataSize + 16; // slop +// if(lBufSize < MAX_IMAGEREAD_BUFFER) +// lBufSize = MAX_IMAGEREAD_BUFFER; +// if(glImageReadBufferSize < lBufSize) +// { +// if(gpImageReadBuffer) +// { +// free(gpImageReadBuffer); +// gpImageReadBuffer = NULL; // tidy +// gpImageReadBufPos = NULL; +// } +// +// glImageReadBufferSize = lBufSize; +// gpImageReadBuffer = (BYTE *) malloc(glImageReadBufferSize); +// +// if(!gpImageReadBuffer) +// { +// glImageReadBufferSize = 0; +// return NULL; // v. bad news. +// } +// TRACE("* (Re-)Allocated local TGA data buffer: %d bytes\n", glImageReadBufferSize); +// } + + // + // Read the TGA format data into the local buffer + // + long lTGABytesRead = fread(pRGB, 1, lImgDataSize, fp); + if(lTGABytesRead != lImgDataSize) + { +// ASSERT(0); + //free(pNew); + return NULL; + } + + // Grab the image dimensions + *width = tgahd.Width; + *height = tgahd.Height; + + // Clean Up + fclose(fp); + m_error = IMGOK; + return pNew; + +/////////////// +// all out...dorks didn't realize that TGA's memory format == DIBSections! +////////////// +#if 0 + // + // Read the TGA format data into the local buffer + // + long lTGABytesRead = fread(gpImageReadBuffer, 1, lImgDataSize, fp); + if(lTGABytesRead != lImgDataSize) + { + ASSERT(0); + return NULL; + } + + gpImageReadBufPos = gpImageReadBuffer; + + // copy DWORDs instead of bytes... + UINT32 * pPixel = (UINT32 *) pRGB; + UINT32 * pReadBufPixel = (UINT32 *) gpImageReadBufPos; + + for(UINT32 row = 0; row < tgahd.Height; row++) + { + for(UINT32 col = 0; col < tgahd.Width; col++) + { + + // Reset RLE Counters + BYTE Red, Grn, Blu, Alp; + TGA_GetPixelValue(&Red, &Grn, &Blu, &Alp, &gpImageReadBufPos, tgahd.PixelDepth, CColMap); + + // Invert if the image origin is in Bottom left + if(TGA_Origin != 0) + { // Bottom Left Origin + destOffset = ((tgahd.Height -1) -row) * tgahd.Width * 4 + col * 4; + *(pRGB + destOffset + 0) = Red; + *(pRGB + destOffset + 1) = Grn; + *(pRGB + destOffset + 2) = Blu; + *(pRGB + destOffset + 3) = Alp; + } + else + { // Top Left Origin + *(pRGB + destOffset + 0) = Red; + *(pRGB + destOffset + 1) = Grn; + *(pRGB + destOffset + 2) = Blu; + *(pRGB + destOffset + 3) = Alp; + destOffset += 4; + } + #if 0 // even faster! + // Red = *(gpImageReadBufPos++); + // Grn = *(gpImageReadBufPos++); + // Blu = *(gpImageReadBufPos++); + // Alp = *(gpImageReadBufPos++); + // + // *(pRGB + destOffset + 0) = Red; + // *(pRGB + destOffset + 1) = Grn; + // *(pRGB + destOffset + 2) = Blu; + // *(pRGB + destOffset + 3) = Alp; + // destOffset += 4; + + // TGAColorComponents rgbaPixel; + + *(pPixel++) = *(pReadBufPixel++); + + // BYTE * p = pRGB + destOffset; + // *(p++) = *(gpImageReadBufPos++); + // *(p++) = *(gpImageReadBufPos++); + // *(p++) = *(gpImageReadBufPos++); + // *(p++) = *(gpImageReadBufPos++); + // destOffset += 4; + #endif + } // loop col + } // loop row + + // Grab the image dimensions + *width = tgahd.Width; + *height = tgahd.Height; + + // Clean Up + fclose(fp); + m_error = IMGOK; + return pNew; +#endif + } + + +/////////////////////////////////////////////////////////////////////////////////// +// LoadTGA8Bit +// Loads in an 8 Bit buffer and the color palette that is +// associated with that buffer, if the image is 8 Bit, else +// it sets global error to DEPTHMISMATCHERR +HGLOBAL +TGAFile::LoadTGA8Bit(const char * fileName, // Name of File +UINT32 * width, // Width in pixels +UINT32 * height, // Height +RGBQUAD * pal) // Palette of RGBQUADS + { + HGLOBAL hNew = NULL; + BYTE * pRGB = NULL; + BYTE Alpha; + + TGAHEADER tgahd; + + BYTE TGA_Origin = 0; + + // for safety + *width = 0; + *height = 0; + + // init + m_error = IMGOK; + + // Init the file Header to all zeros + ZeroMemory(&tgahd, sizeof(tgahd)); + + // Init the Palette to all zeros + ZeroMemory(pal, sizeof(pal)); + FILE * fp; + fp = fopen(fileName, "rb"); + + if(fp == NULL) + { + m_error = FILEOPENERR; + return FALSE; + } + else + { + // Read the TGA Header + long rc = fread(&tgahd, 1, sizeof(TGAHEADER), fp); + + if(rc != sizeof(TGAHEADER)) + { + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + + // Check fo valid data in structure + if(tgahd.PixelDepth> 8) + { + // Not an 8bit Image + m_error = DEPTHMISMATCHERR; + fclose(fp); + return NULL; + } + + // Anything other than the standard TGA types + // and I quit + switch(tgahd.ImageType) + { + case TGA_MAPRGBTYPE: + case TGA_MAPENCODETYPE: + break; + + default: + m_error = DEPTHMISMATCHERR; + fclose(fp); + return NULL; + } + + // Set the Color Mode + if(tgahd.ImageType == TGA_RAWMONOTYPE) + { + mode = GREYSC; + } + else + { + mode = COLOUR; + } + + // Read the ID Descriptor if present + if(tgahd.IDLength != 0) + { + // Read the TGA Comments + long rc = fread(&TGA_ImageIDFeild, 1, tgahd.IDLength, fp); + + if(rc != tgahd.IDLength) + { + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + } + + // Parse the Image Descriptor + TGA_Attribute = (BYTE)(tgahd.ImageDescriptor & 0x0f); + TGA_Origin = (BYTE)((tgahd.ImageDescriptor & 0x20) / 32); + + // If present read the color map + if(tgahd.ColorMapType != 0) + { + // Get the color map + for(__int32 i = 0; i < (tgahd.CMapStart + tgahd.CMapLength); i++) + { + TGA_GetMapEntry(&pal[i].rgbRed, &pal[i].rgbGreen, &pal[i].rgbBlue, &Alpha, fp, tgahd.CMapDepth); + } + } + else + { + m_error = INVALIDTGAERR; + fclose(fp); + return NULL; + } + + // Check Run Length Encoding + if((tgahd.ImageType == TGA_MAPENCODETYPE) || (tgahd.ImageType == TGA_RAWENCODETYPE) || (tgahd.ImageType == TGA_MONOENCODETYPE)) + mode = mode | RLENCO; + + // Allocate the memory buffers + hNew = GlobalAlloc(GHND, tgahd.Width * tgahd.Height); + + if(hNew == NULL) + { + m_error = MEMERR; + fclose(fp); + return NULL; + } + else + { + pRGB = (BYTE *)GlobalLock(hNew); + + if(pRGB == NULL) + { + m_error = MEMERR; + fclose(fp); + return NULL; + } + } + + // RGB from image Data + DWORD destOffset = 0; + + RLECount = 0; + RLEFlag = 0; + + for(UINT32 row = 0; row < tgahd.Height; row++) + { + for(UINT32 col = 0; col < tgahd.Width; col++) + { + BYTE Red, Grn, Blu, Alp; + + // Reset RLE Counters + TGA_GetPixelValue(&Red, &Grn, &Blu, &Alp, fp, tgahd.PixelDepth, pal); + + // Invert if the image origin is in Bottom left + if(TGA_Origin == 0) + { // Bottom Left Origin + *(pRGB + destOffset) = Red; + destOffset = ((tgahd.Height -1) -row) * tgahd.Width + col; + } + else + { // Top Left Origin + *(pRGB + destOffset) = Red; + destOffset++; + } + } + } + } + + // Grab the image dimensions + *width = tgahd.Width; + *height = tgahd.Height; + + // Clean Up + GlobalUnlock(hNew); + fclose(fp); + m_error = IMGOK; + return hNew; + } + + +/////////////////////////////////////////////////////////////////////////////////// +// LoadTGAThumbNail +// Checks the TGA file for the existance of a thumbnail image +// Reads and returns it in a 24 Bit RGB Buffer if a thumbnail +// exists. Returns NULL if there isn't one sets TGANOTHUMBNAIL +HGLOBAL +TGAFile::LoadTGAThumbnail + ( + const char * fileName, // Name of file + UINT32 * width, // Width in Pixel + UINT32 * height // Height + ) + { + HGLOBAL hNew = NULL; + + TGAHEADER tgahd; + TGAFOOTER tgaft; + TGAEXTENSION tgaext; + + BYTE stampWidth = 0, stampHeight = 0; + long lResult; + + // for safety + *width = 0; + *height = 0; + + // init + m_error = IMGOK; + + // Init the file structs + ZeroMemory(&tgahd, sizeof(tgahd)); + ZeroMemory(&tgaft, sizeof(tgaft)); + ZeroMemory(&tgaext, sizeof(tgaext)); + FILE * fp; + fp = fopen(fileName, "rb"); + + if(fp == NULL) + { + m_error = FILEOPENERR; + return FALSE; + } + else + { + // Read the TGA Header + long rc = fread(&tgahd, 1, sizeof(TGAHEADER), fp); + + if(rc != sizeof(TGAHEADER)) + { + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + + // Check fo valid data in structure + if((tgahd.PixelDepth> 32) || (tgahd.PixelDepth<8)) + { + // I don't do Pixel Depths Bigger than 32 + m_error = INVALIDTGAERR; + fclose(fp); + return NULL; + } + + // Anything other than the standard TGA types + // and I quit + switch(tgahd.ImageType) + { + case TGA_MAPRGBTYPE: + case TGA_RAWRGBTYPE: + case TGA_RAWMONOTYPE: + case TGA_MAPENCODETYPE: + case TGA_RAWENCODETYPE: + case TGA_MONOENCODETYPE: + break; + + default: + m_error = INVALIDTGAERR; + fclose(fp); + return NULL; + } + + // Set the number of Color Planes + if(tgahd.ImageType == TGA_RAWMONOTYPE) + { + mode = GREYSC; + } + else + { + mode = COLOUR; + } + + // Check for file Version + // Seek the last 26 bytes of the file + if(fseek(fp, -26, SEEK_END)) + { + // Error Quit + fclose(fp); + return NULL; + } + + // Read in the last 26 Bytes of the File + lResult = fread(&tgaft, 1, sizeof(TGAFOOTER), fp); + + if(lResult != sizeof(TGAFOOTER)) + { + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + + // Check for the Marker at the end of the file + lResult = strcmp(tgaft.Signature, "TRUEVISION-XFILE."); + + if(lResult != 0) + { + // Not V2.0 File no Thumbnail + m_error = NOTGATHUMBNAIL; + fclose(fp); + return NULL; + } + + // Check for the existance of an extension area + if(tgaft.ExtensionOffset == 0) + { + // No Thumbnail in this file + m_error = NOTGATHUMBNAIL; + fclose(fp); + return NULL; + } + + // Seek the extension area + if(fseek(fp, tgaft.ExtensionOffset, SEEK_SET)) + { + // Error Quit + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + + // Read in the last 26 Bytes of the File + lResult = fread(&tgaext, 1, sizeof(TGAEXTENSION), fp); + + if(lResult != sizeof(TGAEXTENSION)) + { + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + + // Seek the thumbnail image + if(fseek(fp, tgaext.StampOffset, SEEK_SET)) + { + // Error Quit + m_error = FILEREADERR; + fclose(fp); + return NULL; + } + + // Read the Width and Height from the first two bytes + // of the postage stamp data. + fread(&stampWidth, 1, 1, fp); + fread(&stampHeight, 1, 1, fp); + + if((stampWidth <= 0) || (stampWidth> 64) || (stampHeight <= 0) || (stampHeight> 64)) + { + m_error = INVALIDTGAERR; + fclose(fp); + return NULL; + } + } + + // Clean Up + *width = stampWidth; + *height = stampHeight; + fclose(fp); + + // Return Okay Image + return hNew; + } + + +/////////////////////////////////////////////////////////////////////////////////// +// SaveTGA32 +// Saves the buffer as a 32 bit True Color Image in TGA format +BOOL +TGAFile::SaveTGA32 + ( + const char * fileName, // output path + BYTE * inBuf, // BGR buffer + UINT32 width, // size in pixels + UINT32 height + ) + { + long lResult = 0; + + TGAHEADER tgahd; + m_error = IMGOK; + + // Init the file Header to all zeros + ZeroMemory(&tgahd, sizeof(tgahd)); + + if(inBuf == NULL) + { + m_error = BADPARAMERR; + return FALSE; + } + + if((width == 0) || (height == 0)) + { + m_error = BADPARAMERR; + return FALSE; + } + + // Initialize the Header for the File + tgahd.IDLength = 0; + tgahd.ColorMapType = 0; + tgahd.ImageType = TGA_RAWRGBTYPE; + tgahd.CMapStart = 0; + tgahd.CMapLength = 0; + tgahd.CMapDepth = 0; + tgahd.XOffset = 0; + tgahd.YOffset = 0; + tgahd.Width = (WORD)width; + tgahd.Height = (WORD)height; + tgahd.PixelDepth = 32; + tgahd.ImageDescriptor = 0; + + // Open a file to write + FILE * fp = fopen(fileName, "wb"); + + if(fp == NULL) + { + m_error = FILEOPENERR; + return FALSE; + } + + // Write the Header to File. + if((lResult = fwrite(&tgahd, 1, sizeof(TGAHEADER), fp)) != 18) + { + fclose(fp); + m_error = FILEWRITEERR; + return FALSE; + } + + // Wrte the Bytes to file + DWORD destOffset = 0; + BYTE temp = 0; + DWORD rowStride = tgahd.Width * 4; + + for(UINT32 row = 0; row < tgahd.Height; row++) + { + //DWORD rowOffset = rowStride * row; + DWORD rowOffset = rowStride *((tgahd.Height -1) -row); + + for(UINT32 col = 0; col < tgahd.Width; col++) + { + destOffset = rowOffset + 4 * col; + temp = *(inBuf + destOffset + 2); + + if(fwrite(&temp, 1, 1, fp) != 1) + { + m_error = FILEWRITEERR; + fclose(fp); + return FALSE; + } + temp = *(inBuf + destOffset + 1); + + if(fwrite(&temp, 1, 1, fp) != 1) + { + m_error = FILEWRITEERR; + fclose(fp); + return FALSE; + } + temp = *(inBuf + destOffset + 0); + + if(fwrite(&temp, 1, 1, fp) != 1) + { + m_error = FILEWRITEERR; + fclose(fp); + return FALSE; + } + + if(fwrite(&temp, 1, 1, fp) != 1) + { + m_error = FILEWRITEERR; + fclose(fp); + return FALSE; + } + } + } + + // Cleanup + fclose(fp); + m_error = IMGOK; + return TRUE; + } + + +/////////////////////////////////////////////////////////////////////////////////// +// Save8BitTGA +// Save's to an 8 Bit Color mapped file using the Palette +// passed in to the function. +BOOL +TGAFile::Save8BitTGA(const char * fileName, // output path +BYTE * inBuf, // one BYTE per pixel colomapped image +UINT32 width, // Width of Image +UINT32 height, // Height of Image +__int32 colors, // number of colors (number of RGBQUADs) +RGBQUAD * colormap) // array of RGBQUADs + { + long lResult = 0; + + TGAHEADER tgahd; + + // Init + m_error = IMGOK; + + // Init the file Header to all zeros + ZeroMemory(&tgahd, sizeof(tgahd)); + + if(inBuf == NULL) + { + m_error = BADPARAMERR; + return FALSE; + } + + if((width == 0) || (height == 0)) + { + m_error = BADPARAMERR; + return FALSE; + } + + if(colormap == NULL) + { + m_error = BADPARAMERR; + return FALSE; + } + + // Initialize the Header for the File + tgahd.IDLength = 0; + tgahd.ColorMapType = 1; + tgahd.ImageType = TGA_MAPRGBTYPE; + tgahd.CMapStart = 0; + tgahd.CMapLength = (SHORT)colors; + tgahd.CMapDepth = 24; + tgahd.XOffset = 0; + tgahd.YOffset = 0; + tgahd.Width = (WORD)width; + tgahd.Height = (WORD)height; + tgahd.PixelDepth = 8; + tgahd.ImageDescriptor = 0x28; + + // Open a file to write + FILE * fp = fopen(fileName, "wb"); + + if(fp == NULL) + { + m_error = FILEOPENERR; + return FALSE; + } + + // Write the Header to File. + if((lResult = fwrite(&tgahd, 1, sizeof(TGAHEADER), fp)) != 18) + { + m_error = FILEWRITEERR; + fclose(fp); + return FALSE; + } + + // Write out the Colormap + for(__int32 i = 0; i < colors; i++) + { + putc(colormap[i].rgbBlue, fp); + putc(colormap[i].rgbGreen, fp); + putc(colormap[i].rgbRed, fp); + } + + // Wrte the Bytes to file + DWORD destOffset = 0; + BYTE temp = 0; + + for(UINT32 row = 0; row < tgahd.Height; row++) + { + for(UINT32 col = 0; col < tgahd.Width; col++) + { + temp = *(inBuf + destOffset + 0); + + if(fwrite(&temp, 1, 1, fp) != 1) + { + m_error = FILEWRITEERR; + fclose(fp); + return FALSE; + } + destOffset += 1; + } + } + + // Cleanup + fclose(fp); + m_error = IMGOK; + return TRUE; + } + + +//////////////////////////////////////////////////////////////////////////////////// +// TGA_GetMapEntry +// Get the Color Values out of the +// Color map in the TGA File +// Return 0 on Success +BOOL +TGAFile::TGA_GetMapEntry(BYTE * Red, BYTE * Green, BYTE * Blue, BYTE * Alpha, FILE * fp, UINT32 Depth) + { + UINT32 j, k, l; + BYTE i, r, g, b, a = 0; + long lResult; + + switch(Depth) + { + case 8: // Greyscale Read and Triplicate + lResult = fread(&i, 1, 1, fp); + + // Check for error + if(lResult != 1) + { + m_error = FILEREADERR; + return FALSE; + } + + // Set RGB Values + r = i; + g = i; + b = i; + break; + + case 16: // 5 bits each of Red, Green, and Blue + + case 15: // Watch for the Byte order + lResult = fread(&j, 1, 1, fp); + lResult = lResult + fread(&k, 1, 1, fp); + + // Check for error + if(lResult != 2) + { + m_error = FILEREADERR; + return FALSE; + } + l = j + k * 256; + b = (BYTE)(((l >> 10) & 31) << 3); + g = (BYTE)(((l >> 5) & 31) << 3); + r = (BYTE)((l & 31) << 3); + break; + + case 32: // Read the Alpha bit a Throw it away + + case 24: // Eight bits for each Red, Green and Blue + lResult = fread(&i, 1, 1, fp); + r = i; + lResult = lResult + fread(&i, 1, 1, fp); + g = i; + lResult = lResult + fread(&i, 1, 1, fp); + b = i; + + // Check for error + if(lResult != 3) + { + m_error = FILEREADERR; + return FALSE; + } + + if(Depth == 32) + { + lResult = fread(&i, 1, 1, fp); + + if(lResult != 1) + { + m_error = FILEREADERR; + return FALSE; + } + + // Stroe Alpha bit + a = i; + } + break; + + default: + // Some Other Pixel Depth Which I don't support + return FALSE; + } + *Red = r; + *Green = g; + *Blue = b; + *Alpha = a; + + // Reutrn No Error + return TRUE; + } + + +//////////////////////////////////////////////////////////////////////////////////// +// TGA_GetFileVersion +// Retrieves the Version of the TGA File +// BYTES 8-23 of a Version 2.0 Footer will be equal +// to "TRUEVISION-XFILE" as ASCII +// Returns - Version number 1 or 2 +__int32 +TGAFile::TGA_GetFileVersion(FILE * fp) + { + long result; + fpos_t pos; + + TGAFOOTER tgaft; + + // Save the Current position of the File Stream + if(fgetpos(fp, &pos)) + { + // Error Quit + return FILEREADERR; + } + + // Seek the last 26 bytes of the file + if(fseek(fp, -26, SEEK_END)) + { + // Error Quit + return FILEREADERR; + } + + // Read in the last 26 Bytes of the File + result = fread(&tgaft, 1, sizeof(TGAFOOTER), fp); + + if(result != sizeof(TGAFOOTER)) + { + m_error = FILEREADERR; + return FILEREADERR; + } + + // Return the File Stream to its initial position + if(fsetpos(fp, &pos)) + { + // Error Quit + return FILEREADERR; + } + + // Check for the Marker at the end of the file + if(!strcspn(tgaft.Signature, "TRUEVISION-XFILE")) + { + // Marker found its V2.0 TGA + return TGA_VERSIONTWO; + } + + // No Marker was found Assume V1.0 TGA + return TGA_VERSIONONE; + } + + +//////////////////////////////////////////////////////////////////////////////////// +// TGA_getPixelValue +// Retreve a pixel value from the buffer and parse +// the value if its RLE encoded. Returns the RGB +// value of the pixel. +// Retruns - TRUE on success +BOOL +TGAFile::TGA_GetPixelValue + ( + BYTE * rRed, + BYTE * rGrn, + BYTE * rBlu, + BYTE * rAlp, + BYTE ** ppTGAData, + UINT32 PixelDepth, + RGBQUAD * CColMap + ) + { + // + // Buffered TGAs are always 32-bit, + // so go direct from file to RGBA + // + *rRed = *((*ppTGAData)++); + *rGrn = *((*ppTGAData)++); + *rBlu = *((*ppTGAData)++); + *rAlp = *((*ppTGAData)++); + + return TRUE; + } + +//////////////////////////////////////////////////////////////////////////////////// +// TGA_getPixelValue +// Retreve a pixel value from the file and parse +// the value if its RLE encoded. Returns the RGB +// value of the pixel. +// Retruns - TRUE on success +BOOL +TGAFile::TGA_GetPixelValue(BYTE * rRed, BYTE * rGrn, BYTE * rBlu, BYTE * rAlp, FILE * fp, UINT32 PixelDepth, RGBQUAD * CColMap) + { + BYTE i, j, k; + long lResult; + + // Check for Run Length Encoding + if((mode & RLENCO) != 0) + { + if(RLECount == 0) + { // Restrat the rum + lResult = fread(&i, 1, 1, fp); + + if(lResult != 1) + { + m_error = FILEREADERR; + return FALSE; + } + RLEFlag = (i & 0x80) >> 7; + + if(RLEFlag == 0) + { // Stream of unencoded pixels + RLECount = i + 1; + } + else + { // Single Pixel Replicated + RLECount = i-127; + } + RLECount--; // ecrement count and get pixel + } + else + { + // I have already read the count and at least the first pixel + RLECount--; + + if(RLEFlag != 0) + { + // Replicated Pixels + goto PixelEncode; + } + } + } + + // Rea the appropiate number of BYTES and break into RGB + switch(PixelDepth) + { + case 8: // Greyscale Read 1 Byte and Triplicate + lResult = fread(&i, 1, 1, fp); + + if(lResult != 1) + { + m_error = FILEREADERR; + return FALSE; + } + Red = i; + Grn = i; + Blu = i; + l = i; + break; + + case 16: // 1 Bit alpha not used + + case 15: // Five bits each for RGB watch byte ordering + lResult = fread(&j, 1, 1, fp); + lResult = lResult + fread(&k, 1, 1, fp); + + // Check for error + if(lResult != 2) + { + m_error = FILEREADERR; + return FALSE; + } + l = j + k * 256; + Blu = (BYTE)(((l >> 10) & 31) << 3); + Grn = (BYTE)(((l >> 5) & 31) << 3); + Red = (BYTE)((l & 31) << 3); + break; + + case 24: // Eight bits each for RGB + lResult = fread(&i, 1, 1, fp); + Red = i; + lResult = lResult + fread(&i, 1, 1, fp); + Grn = i; + lResult = lResult + fread(&i, 1, 1, fp); + Blu = i; + + // opaque alpha (08jan00/bgw) + Alpha = 0xFF; + + // Check for error + if(lResult != 3) + { + m_error = FILEREADERR; + return FALSE; + } + break; + + case 32: // With alpha (08jan00/bgw) + lResult = fread(&i, 1, 1, fp); + Red = i; + lResult = lResult + fread(&i, 1, 1, fp); + Grn = i; + lResult = lResult + fread(&i, 1, 1, fp); + Blu = i; + lResult = lResult + fread(&i, 1, 1, fp); + Alpha = i; + + // Check for error + if(lResult != 4) + { + m_error = FILEREADERR; + return FALSE; + } + break; + + default: // Unknown number of bis per pixel + m_error = INVALIDTGAERR; + return NULL; + } + +PixelEncode: // Set the actual pixel values + + if((mode & MAPPED) == MAPPED) + { + // Remap Color Mapped Pixels + *rRed = CColMap[l].rgbRed; + *rGrn = CColMap[l].rgbGreen; + *rBlu = CColMap[l].rgbBlue; + *rAlp = 0xFF; // opaque always (08jan00/bgw) + } + else + { + // Set Unmapped Values + *rRed = Red; + *rGrn = Grn; + *rBlu = Blu; + *rAlp = Alpha; + } + return TRUE; + } + +extern "C" unsigned char * LoadTGAFile +( + const char * filename, + int * width, + int * height +) +{ + TGAFile tgaFile; + return (unsigned char *)tgaFile.LoadTGA(filename, (UINT32 *)width, (UINT32 *)height); +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/TGAFile.h b/code/gamespy/gt2/gt2action/TGAFile.h new file mode 100644 index 00000000..1dfd7369 --- /dev/null +++ b/code/gamespy/gt2/gt2action/TGAFile.h @@ -0,0 +1,22 @@ +/* TGA File REader Classs Implementation File + This Implementation Allows the reading of TGA (Targa) Files + into an RGB buffer. Also the class allows an RGB Buffer to be + written to a TGA File. There is also a function to determine + the dimensions of a TGA file. + + Created By: Timothy A. Bish + Created On: 08/17/98 + +*/ + +#ifndef TGAFILEH +#define TGAFILEH + +unsigned char * LoadTGAFile +( + const char * filename, + int * width, + int * height +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aClient.c b/code/gamespy/gt2/gt2action/gt2aClient.c new file mode 100644 index 00000000..9c8c94d7 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aClient.c @@ -0,0 +1,715 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include +#include +#include "gt2aMain.h" +#include "gt2aClient.h" +#include "gt2aParse.h" +#include "gt2aDisplay.h" +#include "gt2aSound.h" + +#define CLIENT_THINK_TIME 30 + +static GT2Socket Socket; // The socket used to connect to the server +static GT2Connection Connection; // The connection to the server. +float localRotation; // The local rotation, >=0, <360. +char serverAddress[128]; // Address of the server. +int localMotion; // STILL, FORWARD, BACKWARD. +int localTurning; // STILL, LEFT, RIGHT. +Player players[MAX_PLAYERS]; // The list of players. +GT2Bool connected; // True once we received the start message. +int localIndex = -1; // The local player's index into the players table. +unsigned long lastServerUpdate; // The last time we received an update from the server. +char localNick[MAX_NICK] = "Player"; // The local player's nick. +CObject cObjects[MAX_OBJECTS]; // The list of objects. +UpdateInfo updateHistory[UPDATE_HISTORY_LEN]; // Time diff for past updates +int updateHistoryStart; // The starting index of the history. +int ClientNumAsteroids; // The number of asteroids we're holding. +static unsigned short nextServerUpdateID; // The expected ID of the next update. +static unsigned short nextClientUpdateID; // The ID of our next update. + +// Stats. +///////// +int reliableBytesSentClient; +int reliableBytesReceivedClient; +int unreliableBytesSentClient; +int unreliableBytesReceivedClient; +int reliableMessagesSentClient; +int reliableMessagesReceivedClient; +int unreliableMessagesSentClient; +int unreliableMessagesReceivedClient; + +static void ClientSocketErrorCallback +( + GT2Socket socket +) +{ + printf("Server socket error\n"); + GSI_UNUSED(socket); +} + +static void ClientConnectedCallback +( + GT2Connection connection, + GT2Result result, + GT2Byte * message, + int len +) +{ + if(result != GT2Success) + { + printf("Connection failed (%d", result); + if(result == GT2Rejected) + printf(": %s)\n", message); + else + printf(")\n"); + exit(1); + } + GSI_UNUSED(len); + GSI_UNUSED(connection); +} + +static void ClientReceivedCallback +( + GT2Connection connection, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + char buffer[MAX_NICK + 16]; + GTMessageType type; + int rcode; + + // Check for no message. + //////////////////////// + if(!message) + return; + + // Get the message type. + //////////////////////// + type = gtEncodedMessageType((char *)message); + + // New client? + ////////////// + if(type == MSG_S_ADDCLIENT) + { + byte newPlayerIndex; + Player * newPlayer; + char nick[MAX_NICK]; + + // Decode it. + ///////////// + rcode = gtDecode(MSG_S_ADDCLIENT_STR, (char *)message, len, + &newPlayerIndex, + nick); + if(rcode == -1) + return; + + // Check the index. + /////////////////// + assert((newPlayerIndex >= 0) && (newPlayerIndex < MAX_PLAYERS)); + if((newPlayerIndex < 0) || (newPlayerIndex >= MAX_PLAYERS)) + return; + assert(!players[newPlayerIndex].used); + + // Add the client. + ////////////////// + newPlayer = &players[newPlayerIndex]; + memset(newPlayer, 0, sizeof(Player)); + newPlayer->used = GT2True; + + // Set the nick. + //////////////// + strncpy(newPlayer->nick, nick, MAX_NICK); + newPlayer->nick[MAX_NICK - 1] = '\0'; + + // Display a join message. + ////////////////////////// + sprintf(buffer, "%s joined", newPlayer->nick); + DisplayChat(buffer); + } + // Delete client? + ///////////////// + else if(type == MSG_S_DELCLIENT) + { + byte delPlayerIndex; + + // Decode it. + ///////////// + rcode = gtDecode(MSG_S_DELCLIENT_STR, (char *)message, len, + &delPlayerIndex); + if(rcode == -1) + return; + + // Check the index. + /////////////////// + assert((delPlayerIndex >= 0) && (delPlayerIndex < MAX_PLAYERS)); + if((delPlayerIndex < 0) || (delPlayerIndex >= MAX_PLAYERS)) + return; + assert(players[delPlayerIndex].used); + + // Display a part message. + ////////////////////////// + sprintf(buffer, "%s left", players[delPlayerIndex].nick); + DisplayChat(buffer); + + // Delete the client. + ///////////////////// + players[delPlayerIndex].used = GT2False; + } + // Connection attempt finished? + /////////////////////////////// + else if(type == MSG_S_START) + { + byte index; + + // Decode it. + ///////////// + rcode = gtDecode(MSG_S_START_STR, (char *)message, len, + &index); + if(rcode == -1) + return; + + // Check the index. + /////////////////// + assert((index >= 0) && (index < MAX_PLAYERS)); + if((index < 0) || (index >= MAX_PLAYERS)) + return; + + // Set the local index. + /////////////////////// + localIndex = index; + + // We finished connecting. + ////////////////////////// + connected = GT2True; + } + // Server update? + ///////////////// + else if(type == MSG_S_UPDATE) + { + Player * player; + CObject * object; + byte updatedClients; + byte updatedObjects; + byte index; + unsigned short packedPosition[2]; + int score; + byte forward; + byte backward; + byte right; + byte left; + byte dead; + byte type; + int time; + int i; + unsigned long now; + unsigned long diff; + unsigned short updateID; + unsigned short packedRotation; + + // We got an update. + //////////////////// + now = current_time(); + diff = (now - lastServerUpdate); + lastServerUpdate = now; + + // Decode the header. + ///////////////////// + rcode = gtDecode(MSG_S_UPDATE_STR, (char *)message, len, + &updateID, + &updatedClients, + &updatedObjects); + if(rcode == -1) + return; + message += rcode; + len -= rcode; + + // Check for first update. + ////////////////////////// + if(updateHistoryStart == -1) + { + nextServerUpdateID = (updateID + 1); + updateHistoryStart = 0; + } + else + { + int numDropped; + UpdateInfo * info; + + // Check for out of order. + ////////////////////////// + if(updateID < nextServerUpdateID) + return; + + // Fill in the dropped updates. + /////////////////////////////// + numDropped = (updateID - nextServerUpdateID); + nextServerUpdateID = (updateID + 1); + + // Fill in the dropped updates. + /////////////////////////////// + while(numDropped--) + { + info = &updateHistory[updateHistoryStart++]; + updateHistoryStart %= UPDATE_HISTORY_LEN; + + info->diff = -1; + info->len = 0; + } + + // Fill in this update. + /////////////////////// + info = &updateHistory[updateHistoryStart++]; + updateHistoryStart %= UPDATE_HISTORY_LEN; + + info->diff = diff; + info->len = (len + rcode); + } + + // Go through each updated client. + ////////////////////////////////// + for(i = 0 ; i < updatedClients ; i++) + { + // Decode the update. + ///////////////////// + rcode = gtDecodeNoType(MSG_S_UPDATE_CLIENT_STR, (char *)message, len, + &index, + &packedPosition[0], + &packedPosition[1], + &packedRotation, + &score, + &forward, + &backward, + &right, + &left, + &dead); + if(rcode == -1) + return; + message += rcode; + len -= rcode; + + // Check the index. + /////////////////// + assert(index >= 0); + assert(index < MAX_PLAYERS); + if((index < 0) || (index >= MAX_PLAYERS)) + return; + + // Cache the player. + //////////////////// + player = &players[index]; + + // Copy the values in. + ////////////////////// + SetV2f( + player->position, + UnsignedShortToPosition(packedPosition[0]), + UnsignedShortToPosition(packedPosition[1])); + player->rotation = UnsignedShortToRotation(packedRotation); + player->score = score; + if(forward) + player->motion = FORWARD; + else if(backward) + player->motion = BACKWARD; + else + player->motion = STILL; + if(right) + player->turning = RIGHT; + else if(left) + player->turning = LEFT; + else + player->turning = STILL; + player->dead = dead; + + // Update the roll. + /////////////////// + if(player->dead) + { + player->roll = 0; + } + else if(left) + { + if(player->roll > -1) + { + player->roll -= (diff / 1000.0); + if(player->roll < -1) + player->roll = -1; + } + } + else if(right) + { + if(player->roll < 1) + { + player->roll += (diff / 1000.0); + if(player->roll > 1) + player->roll = 1; + } + } + else if(player->roll > 0) + { + player->roll -= (diff / 1000.0); + if(player->roll < 0) + player->roll = 0; + } + else if(player->roll < 0) + { + player->roll += (diff / 1000.0); + if(player->roll > 0) + player->roll = 0; + } + } + + // Go through the objects. + ////////////////////////// + for(i = 0 ; i < updatedObjects ; i++) + { + // Decode the object. + ///////////////////// + rcode = gtDecodeNoType(MSG_S_UPDATE_OBJECT_STR, (char *)message, len, + &type, + &packedPosition[0], + &packedPosition[1], + &packedRotation, + &time); + if(rcode == -1) + return; + message += rcode; + len -= rcode; + + // Cache the object. + //////////////////// + object = &cObjects[i]; + object->used = GT2True; + + // Copy in the values. + ////////////////////// + object->type = type; + SetV2f( + object->position, + UnsignedShortToPosition(packedPosition[0]), + UnsignedShortToPosition(packedPosition[1])); + object->rotation = UnsignedShortToRotation(packedRotation); + object->totalTime = time; + } + + // Mark the rest of the objects as unused. + ////////////////////////////////////////// + for( ; i < MAX_OBJECTS ; i++) + cObjects[i].used = GT2False; + } + // Chat? + //////// + else if(type == MSG_S_CHAT) + { + char buffer[256]; + + // Decode it. + ///////////// + rcode = gtDecode(MSG_S_CHAT_STR, (char *)message, len, + &buffer); + if(rcode == -1) + return; + + // We got a chat message. + ///////////////////////// + DisplayChat(buffer); + } + // Sound? + ///////// + else if(type == MSG_S_SOUND) + { + byte sound; + + // Decode it. + ///////////// + rcode = gtDecode(MSG_S_SOUND_STR, (char *)message, len, + &sound); + if(rcode == -1) + return; + + // We got a sound event. + //////////////////////// + PlaySoundEffect(sound); + } + // NumAsteroids? + //////////////// + else if(type == MSG_S_NUMASTEROIDS) + { + byte total; + + // Decode it. + ///////////// + rcode = gtDecode(MSG_S_NUMASTEROIDS_STR, (char *)message, len, + &total); + if(rcode == -1) + return; + + // We got a new total. + ////////////////////// + ClientNumAsteroids = total; + } + GSI_UNUSED(reliable); + GSI_UNUSED(connection); +} + +static void ClientClosedCallback +( + GT2Connection connection, + GT2CloseReason reason +) +{ + printf("Connection closed (%d)\n", reason); + GSI_UNUSED(connection); +} + +void SendUpdate +( + void +) +{ + if(connected) + { + char buffer[64]; + int rcode; + + // Encode the messsage. + /////////////////////// + rcode = gtEncode(MSG_C_UPDATE, MSG_C_UPDATE_STR, buffer, sizeof(buffer), + nextClientUpdateID++, + RotationToUnsignedShort(localRotation), + localMotion == FORWARD ? 1 : 0, + localMotion == BACKWARD ? 1 : 0, + localTurning == RIGHT ? 1 : 0, + localTurning == LEFT ? 1 : 0); + if(rcode != -1) + { + // Send the message. + //////////////////// + gt2Send(Connection, (GT2Byte *)buffer, rcode, GT2False); + } + } +} + +void SendPress +( + const char * value +) +{ + if(connected) + { + char buffer[32]; + int rcode; + + // Encode the message. + ////////////////////// + rcode = gtEncode(MSG_C_PRESS, MSG_C_PRESS_STR, buffer, sizeof(buffer), + value); + if(rcode != -1) + { + // Send the message. + //////////////////// + gt2Send(Connection, (GT2Byte *)buffer, rcode, GT2True); + } + } +} + +void SendChat +( + const char * message +) +{ + if(connected) + { + char buffer[CHAT_MAX + 8]; + int rcode; + + // Encode the message. + ////////////////////// + rcode = gtEncode(MSG_C_CHAT, MSG_C_CHAT_STR, buffer, sizeof(buffer), + message); + + if(rcode != -1) + { + // Send the message. + //////////////////// + gt2Send(Connection, (GT2Byte *)buffer, rcode, GT2True); + } + } +} + +void ClientThink +( + unsigned long now +) +{ + static unsigned long lastUpdate; + static unsigned long lastTurn; + unsigned long diff; + + // Think. + ///////// + gt2Think(Socket); + + // Are we connected? + //////////////////// + if(!connected) + return; + + // For the first update, just set the time. + /////////////////////////////////////////// + if(!lastUpdate) + { + lastUpdate = now; + return; + } + + // How long since the last update? + ////////////////////////////////// + diff = (now - lastUpdate); + + // Check for an update. + /////////////////////// + if(diff >= CLIENT_THINK_TIME) + { + // Send an update. + ////////////////// + SendUpdate(); + + // Update the last send time. + ///////////////////////////// + lastUpdate = now; + } + + // How long since the last turn? + //////////////////////////////// + diff = (now - lastTurn); + + // Update our rotation. + /////////////////////// + if((localIndex != -1) && !players[localIndex].dead) + localRotation = ComputeNewRotation(localRotation, localTurning, diff, PLAYER_TURN_SPEED); + + // Update the last turn time. + ///////////////////////////// + lastTurn = now; +} + +static void ClientSendMonitor +( + GT2Connection connection, + int filterID, + const GT2Byte * message, + int len, + GT2Bool reliable +) +{ + // Update stats. + //////////////// + if(reliable) + { + reliableBytesSentClient += len; + reliableMessagesSentClient++; + } + else + { + unreliableBytesSentClient += len; + unreliableMessagesSentClient++; + } + + // We're done with the message. + /////////////////////////////// + gt2FilteredSend(connection, filterID, message, len, reliable); +} + +static void ClientReceiveMonitor +( + GT2Connection connection, + int filterID, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + // Update stats. + //////////////// + if(reliable) + { + reliableBytesReceivedClient += len; + reliableMessagesReceivedClient++; + } + else + { + unreliableBytesReceivedClient += len; + unreliableMessagesReceivedClient++; + } + + // We're done with the message. + /////////////////////////////// + gt2FilteredReceive(connection, filterID, message, len, reliable); +} + +GT2Bool InitializeClient +( + void +) +{ + GT2ConnectionCallbacks connectionCallbacks; + GT2Result result; + char buffer[256]; + int rcode; + + // Setup callback structs. + ////////////////////////// + memset(&connectionCallbacks, 0, sizeof(GT2ConnectionCallbacks)); + connectionCallbacks.connected = ClientConnectedCallback; + connectionCallbacks.received = ClientReceivedCallback; + connectionCallbacks.closed = ClientClosedCallback; + + // Init stuff. + ////////////// + lastServerUpdate = 0; + updateHistoryStart = -1; + nextServerUpdateID = 0; + nextClientUpdateID = 0; + + // If there was no server, we're connecting to ourselves. + ///////////////////////////////////////////////////////// + if(!serverAddress[0]) + strcpy(serverAddress, "127.0.0.1" PORT_STRING); + + // Create the socket. + ///////////////////// + result = gt2CreateSocket(&Socket, NULL, 0, 0, ClientSocketErrorCallback); + if(result != GT2Success) + return GT2False; + + // Setup the initial data. + ////////////////////////// + rcode = gtEncode(MSG_C_INITIAL, MSG_C_INITIAL_STR, buffer, sizeof(buffer), + localNick); + if(rcode == -1) + return GT2False; + + // Connect. + /////////// + result = gt2Connect(Socket, &Connection, serverAddress, (GT2Byte *)buffer, rcode, 0, &connectionCallbacks, GT2False); + if(result != GT2Success) + return GT2False; + + // Add our traffic monitors. + //////////////////////////// + gt2AddSendFilter(Connection, ClientSendMonitor); + gt2AddReceiveFilter(Connection, ClientReceiveMonitor); + + return (Connection != NULL); +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aClient.h b/code/gamespy/gt2/gt2action/gt2aClient.h new file mode 100644 index 00000000..f6ef701e --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aClient.h @@ -0,0 +1,92 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2ACLIENT_H_ +#define _GT2ACLIENT_H_ + +#include "gt2aMath.h" +#include "gt2aLogic.h" + +#define UPDATE_HISTORY_LEN 250 + +typedef struct Player +{ + GT2Bool used; // If this slot is in use or not. + V2f position; // The current position (0 <= x,y < MAP_MAX). + float rotation; // Client's view angle (0 <= rotation < 360). + int motion; // The client's current motion (STILL, FORWARD, BACKWARD). + int turning; // The client's current tunring direction (STILL, LEFT, RIGHT). + char nick[MAX_NICK]; // The client's nick. + int score; // The client's score. + GT2Bool dead; // True if this client is currently dead. + float roll; // How much to roll them (-1->0->1). +} Player; + +typedef struct CObject +{ + GT2Bool used; // If this slot is in use or not. + ObjectType type; // The type of object. + V2f position; // The object's position. + float rotation; // The object's rotation. + unsigned long totalTime; // The amount of time this object has existed. +} CObject; + +typedef struct UpdateInfo +{ + int diff; // Time since the last update was received. + int len; // Length of the update, in bytes. +} UpdateInfo; + +extern float localRotation; +extern char serverAddress[128]; +extern int localMotion; +extern int localTurning; +extern Player players[MAX_PLAYERS]; +extern GT2Bool connected; +extern int localIndex; +extern unsigned long lastServerUpdate; +extern char localNick[MAX_NICK]; +extern CObject cObjects[MAX_OBJECTS]; +extern UpdateInfo updateHistory[UPDATE_HISTORY_LEN]; +extern int updateHistoryStart; +extern int ClientNumAsteroids; + +// Stats. +///////// +extern int reliableBytesSentClient; +extern int reliableBytesReceivedClient; +extern int unreliableBytesSentClient; +extern int unreliableBytesReceivedClient; +extern int reliableMessagesSentClient; +extern int reliableMessagesReceivedClient; +extern int unreliableMessagesSentClient; +extern int unreliableMessagesReceivedClient; + +GT2Bool InitializeClient +( + void +); + +void ClientThink +( + unsigned long now +); + +void SendPress +( + const char * value +); + +void SendChat +( + const char * message +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aDisplay.c b/code/gamespy/gt2/gt2action/gt2aDisplay.c new file mode 100644 index 00000000..c8173f39 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aDisplay.c @@ -0,0 +1,1655 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include "gt2aMain.h" +#include "gt2aDisplay.h" +#include "gt2aClient.h" +#include "gt2aMath.h" +#include "gt2aInput.h" +#include "gt2aLogic.h" +#include "TGAFile.h" + +#define TITLE "GameSpy GT2Action by Dan 'Mr. Pants' Schoenblum" +#define FONT GLUT_STROKE_ROMAN +#define TEXT_SCALE 10 +#define CHAT_LINES 3 +#define CHAT_SCROLL_TIME (3.5 * 1000) +#define NUM_STARS 1000 +#define FRAME_HISTORY_LEN 250 +#define NUM_ROCKET_TEXTURES 4 +#define NUM_EXPLOSION_TEXTURES 2 +#define NUM_SHIP_TEXTURES 2 +#define NUM_ASTEROID_TEXTURES 3 +#define NUM_MINE_TEXTURES 3 +#define NUM_SPINNER_TEXTURES 3 +#define WINDOW_MIN 0 +#define WINDOW_MAX 10000 +#define POSITION_HISTORY_LEN 5 +#define MAX_FLICKER 50 +#define STARS_SCALE 4 +#define RADAR_SIZE 1500 +#define FULL_VIEW (20000.0 * Zoom) +#define HALF_VIEW (FULL_VIEW / 2.0) +#define ZOOM_IN_SPEED 0.4 +#define ZOOM_OUT_SPEED 0.4 +#define ZOOM_MIN 1.0 +#define ZOOM_MAX 1.5 + +typedef struct Star +{ + V2f position; + V3b color; + byte alpha; + byte flicker; +} Star; + +GT2Bool fullScreen; + +GT2Bool DrawGraphsOption; +GT2Bool DrawUpdateTimeOption = GT2True; +GT2Bool DrawUpdateLengthOption = GT2True; +GT2Bool DrawFrameTimeOption = GT2True; +GT2Bool DrawMarksOption = GT2True; +GT2Bool DrawFPSOption; +GT2Bool DrawWithPredictionOption = GT2True; +GT2Bool DrawBackgroundOption = GT2True; +GT2Bool DrawStarsOption = GT2True; +GT2Bool DrawBoundingCirclesOption = GT2False; +GT2Bool DrawRadarOption = GT2True; +float RadarScaleOption = 1; +float RadarPointScaleOption = 2; +GT2Bool ViewClippingOption = GT2True; + +int screenWidth = 500; +int screenHeight = 500; + +static char chatLines[CHAT_LINES][CHAT_MAX]; +static unsigned long lastChatScroll; + +static unsigned long Now; +static unsigned long Diff; + +static GLuint backgroundTexture; +static GLuint shipTextures[NUM_SHIP_TEXTURES]; +static GLuint explosionTextures[NUM_EXPLOSION_TEXTURES]; +static GLuint rocketTextures[NUM_ROCKET_TEXTURES]; +static GLuint asteroidTextures[NUM_ASTEROID_TEXTURES]; +static GLuint mineTextures[NUM_MINE_TEXTURES]; +static GLuint spinnerTextures[NUM_SPINNER_TEXTURES]; + +static Star stars[NUM_STARS]; + +static int FrameHistory[FRAME_HISTORY_LEN]; +static int FrameHistoryStart; + +static V2f localPosition; + +static float Zoom = 1.0; + +static void ScrollChat +( + void +) +{ + int i; + + // First find where to start the copying. + ///////////////////////////////////////// + for(i = 0 ; i < (CHAT_LINES - 1) ; i++) + if(!chatLines[i][0]) + break; + + // Move all lines 1 step towards the end of the array. + ////////////////////////////////////////////////////// + for( ; i > 0 ; i--) + strcpy(chatLines[i], chatLines[i - 1]); + + // Clear the first line. + //////////////////////// + chatLines[0][0] = '\0'; +} + +static void RemoveOldestChat +( + void +) +{ + int i; + + // No lines? + //////////// + if(!chatLines[0][0]) + return; + + // Find the oldest line. + //////////////////////// + for(i = (CHAT_LINES - 1) ; !chatLines[i][0] && (i > 0) ; i--); + + // Remove it. + ///////////// + chatLines[i][0] = '\0'; +} + +static void AddChatLine +( + const char * message +) +{ + // Do scrolling. + //////////////// + ScrollChat(); + + // Copy in the new line. + //////////////////////// + strncpy(chatLines[0], message, CHAT_MAX); + chatLines[0][CHAT_MAX - 1] = '\0'; + + // Update the scroll time. + ////////////////////////// + lastChatScroll = current_time(); +} + +static int GetStringWidth +( + const char * string +) +{ + int width = 0; + + if(!string) + return 0; + + while(*string) + width += glutStrokeWidth(FONT, *string++); + + return (width * TEXT_SCALE); +} + +static void DrawString +( + const char * string, + int x, + int y, + const V3b color, + float scale +) +{ + scale *= TEXT_SCALE; + glColor3ubv(color); + glPushMatrix(); + glTranslatef(x, y, 0); + glScalef(scale, scale, 1); + while(*string) + glutStrokeCharacter(FONT, *string++); + glPopMatrix(); +} + +static int GetCharacterWidth +( + char ch +) +{ + int width; + + width = glutStrokeWidth(FONT, ch); + width *= TEXT_SCALE; + + return width; +} + +static void DrawCharacter +( + char ch, + int x, + int y, + const V3b color +) +{ + glColor3ubv(color); + glPushMatrix(); + glTranslatef(x, y, 0); + glScalef(TEXT_SCALE, TEXT_SCALE, 1); + glutStrokeCharacter(FONT, ch); + glPopMatrix(); +} + +void DisplayChat +( + const char * message +) +{ + // Add this to the chat buffer. + /////////////////////////////// + AddChatLine(message); +} + +static void DrawChat +( + void +) +{ + const char * str; + int x; + int y; + int i; + float scale; + + // Settings. + //////////// + x = 100; + y = 200; + scale = 0.3; + + // Draw all the chat messages. + ////////////////////////////// + for(i = 0 ; (i < CHAT_LINES) && chatLines[i][0] ; i++) + { + DrawString(chatLines[i], x, y, White, scale); + y += 500; + } + + // Get the current chat buffer. + /////////////////////////////// + str = GetChatBuffer(); + if(str) + DrawString(str, x, WINDOW_MAX - 500, White, scale); +} + +static void DrawBoundingCircle +( + float radius +) +{ + // Check the bounding circles option. + ///////////////////////////////////// + if(DrawBoundingCirclesOption) + { + int i; + V2f point; + + // Draw in white. + ///////////////// + glColor3f(1, 1, 1); + + // Draw the circle (circle, sphere, whatever). + ////////////////////////////////////////////// +#if 1 + glBegin(GL_LINE_LOOP); + for(i = 0 ; i < 360 ; i += 30) + { + RotationToVector(point, i); + ScaleV2f(point, point, radius); + glVertex2fv(point); + } + glEnd(); +#else + glutWireSphere(radius, 16, 16); +#endif + + // Draw the forward line. + ///////////////////////// + glBegin(GL_LINES); + glVertex2f(0, 0); + glVertex2f(0, radius * 2); + glEnd(); + } +} + +static void DrawPlayer +( + Player * player, + const V2f position, + float rotation +) +{ + int width; + static const float textScale = 0.25; + V2f textPosition; + float diff; + static char text[MAX_NICK + 32]; + + // Get the text to show. + //////////////////////// + sprintf(text, "%s: %d", player->nick, player->score); + + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); + glTranslatef(position[0], position[1], 0); + + width = GetStringWidth(text); + width *= textScale; + textPosition[0] = -(width / 2); +#if 0 + textPosition[1] = 700; +#else + if((position[0] + textPosition[0]) < 20) + textPosition[0] = (20 - position[0]); + diff = (MAP_MAX - (position[0] + textPosition[0] + width)); + if(diff < 0) + textPosition[0] += diff; + if(position[1] < (MAP_MAX - 1000)) + textPosition[1] = 700; + else + textPosition[1] = -1000; +#endif + DrawString(text, textPosition[0], textPosition[1], White, textScale); + + if(!player->dead) + { + glRotatef(rotation, 0, 0, -1); + + glPushMatrix(); + if(player->roll) + { + glScalef(1, 1, .005); + glRotatef(35 * player->roll, 0, 1, 0); + } + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + + if(player->motion) + glBindTexture(GL_TEXTURE_2D, shipTextures[1]); + else + glBindTexture(GL_TEXTURE_2D, shipTextures[0]); + + glColor4f(1, 1, 1, 1); + + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-500, -300); + glTexCoord2f(1, 0); + glVertex2f(500, -300); + glTexCoord2f(1, 1); + glVertex2f(500, 700); + glTexCoord2f(0, 1); + glVertex2f(-500, 700); + glEnd(); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glPopMatrix(); + } + + DrawBoundingCircle(PLAYER_RADIUS); + glPopMatrix(); +} + +static void Predict +( + V2f newPosition, + float * rotation, + Player * player +) +{ + GT2Bool local; + + assert(newPosition); + assert(rotation); + assert(player); + + // Is this the local player? + //////////////////////////// + local = (player == &players[localIndex]); + + // Should we predict? + ///////////////////// + if(DrawWithPredictionOption && !player->dead && player->motion) + { + // Predict new position. + //////////////////////// + ComputeNewPosition( + newPosition, + player->position, + player->motion, + player->rotation, + Diff, + PLAYER_SPEED, + GT2True); + + // Predict rotation if not local. + ///////////////////////////////// + if(!local) + *rotation = ComputeNewRotation(player->rotation, player->turning, Diff, PLAYER_TURN_SPEED); + } + else + { + // Just use the actual position. + //////////////////////////////// + CopyV2f(newPosition, player->position); + + // Use the real rotation if not local. + ////////////////////////////////////// + if(!local) + *rotation = player->rotation; + } + + // If local, use our rotation. + ////////////////////////////// + if(local) + *rotation = localRotation; +} + +static void DrawPlayers +( + void +) +{ + int i; + Player * player; + V2f position; + float rotation; + float distance; + + // First draw the other players. + //////////////////////////////// + for(i = 0 ; i < MAX_PLAYERS ; i++) + { + player = &players[i]; + + // Check if we need to draw the player. + /////////////////////////////////////// + if(player->used && (i != localIndex)) + { + // Predict the new position. + //////////////////////////// + Predict(position, &rotation, player); + + // Check if the players are close enough. + ///////////////////////////////////////// + if(ViewClippingOption) + { + distance = DistanceV2f(localPosition, position); + if(distance >= FULL_VIEW) + continue; + } + + // Do the actual drawing. + ///////////////////////// + DrawPlayer(player, position, rotation); + } + } + + // Draw the local player. + ///////////////////////// + assert((localIndex >= 0) && (localIndex < MAX_PLAYERS)); + player = &players[localIndex]; + + // Do the actual drawing. + // The position was already set in SetMapView. + ////////////////////////////////////////////// + DrawPlayer(player, localPosition, localRotation); +} + +static void DrawRocket +( + CObject * rocket +) +{ + V2f position; + + // Are we using prediction? + /////////////////////////// + if(DrawWithPredictionOption) + { + // Predict new position. + //////////////////////// + ComputeNewPosition( + position, + rocket->position, + FORWARD, + rocket->rotation, + Diff, + ROCKET_SPEED, + GT2False); + } + else + { + // Just use the actual position. + //////////////////////////////// + CopyV2f(position, rocket->position); + } + + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); + glTranslatef(position[0], position[1], 0); + + glRotatef(rocket->rotation, 0, 0, -1); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + + glBindTexture(GL_TEXTURE_2D, rocketTextures[RandomInt(0, NUM_ROCKET_TEXTURES - 1)]); + + glColor4f(1, 1, 1, 1); + + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-350, -350); + glTexCoord2f(1, 0); + glVertex2f(350, -350); + glTexCoord2f(1, 1); + glVertex2f(350, 350); + glTexCoord2f(0, 1); + glVertex2f(-350, 350); + glEnd(); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + + DrawBoundingCircle(ROCKET_RADIUS); + glPopMatrix(); +} + +static void DrawMine +( + CObject * mine +) +{ + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); + glTranslatef(mine->position[0], mine->position[1], 0); + + glRotatef(mine->rotation, 0, 0, -1); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + + glBindTexture(GL_TEXTURE_2D, mineTextures[(int)mine->position[0] % NUM_MINE_TEXTURES]); + + glColor4f(1, 1, 1, 1); + + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-800, -800); + glTexCoord2f(1, 0); + glVertex2f(800, -800); + glTexCoord2f(1, 1); + glVertex2f(800, 800); + glTexCoord2f(0, 1); + glVertex2f(-800, 800); + glEnd(); + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + + DrawBoundingCircle(MINE_RADIUS); + glPopMatrix(); +} + +static void DrawAsteroid +( + CObject * asteroid +) +{ + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); + glTranslatef(asteroid->position[0], asteroid->position[1], 0); + + glRotatef(asteroid->rotation, 0, 0, -1); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + + //glBindTexture(GL_TEXTURE_2D, asteroidTextures[(int)asteroid->position[0] % NUM_ASTEROID_TEXTURES]); + glBindTexture(GL_TEXTURE_2D, asteroidTextures[1]); + + glColor4f(1, 1, 1, 1); + + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-800, -800); + glTexCoord2f(1, 0); + glVertex2f(800, -800); + glTexCoord2f(1, 1); + glVertex2f(800, 800); + glTexCoord2f(0, 1); + glVertex2f(-800, 800); + glEnd(); + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + + DrawBoundingCircle(ASTEROID_RADIUS); + glPopMatrix(); +} + +static void DrawExplosion +( + CObject * explosion +) +{ + unsigned long totalTime; + + totalTime = (explosion->totalTime + Diff); + + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); + glTranslatef(explosion->position[0], explosion->position[1], 0); + + glPushMatrix(); + glScalef(totalTime / 100.0, totalTime / 100.0, 1); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + + glColor4f(1, 1, 1, 1); + + glBindTexture(GL_TEXTURE_2D, explosionTextures[1]); + + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-700, -700); + glTexCoord2f(1, 0); + glVertex2f(700, -700); + glTexCoord2f(1, 1); + glVertex2f(700, 700); + glTexCoord2f(0, 1); + glVertex2f(-700, 700); + glEnd(); + + glRotatef(totalTime, 0, 0, -1); + + glBindTexture(GL_TEXTURE_2D, explosionTextures[0]); + + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-700, -700); + glTexCoord2f(1, 0); + glVertex2f(700, -700); + glTexCoord2f(1, 1); + glVertex2f(700, 700); + glTexCoord2f(0, 1); + glVertex2f(-700, 700); + glEnd(); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glPopMatrix(); + + DrawBoundingCircle(EXPLOSION_RADIUS); + glPopMatrix(); +} + +static void DrawObjects +( + void +) +{ + int i; + CObject * object; + float distance; + + // Draw all the objects. + //////////////////////// + for(i = 0 ; i < MAX_OBJECTS ; i++) + { + object = &cObjects[i]; + + // Is the object in use? + //////////////////////// + if(object->used) + { + if(ViewClippingOption) + { + // Is the player close enough to see it? + //////////////////////////////////////// + distance = DistanceV2f(localPosition, object->position); + if(distance >= FULL_VIEW) + continue; + } + + // Draw it based on type. + ///////////////////////// + if(object->type == ObjectRocket) + DrawRocket(object); + else if(object->type == ObjectMine) + DrawMine(object); + else if(object->type == ObjectAsteroid) + DrawAsteroid(object); + else if(object->type == ObjectExplosion) + DrawExplosion(object); + } + } +} + +static void DrawConnecting +( + void +) +{ + static const char * text = "Connecting"; + static int width = -1; + static const V3b textColor = { 0, 204, 0}; + static unsigned long lastSpin; + static int spinnerIndex = -1; + static const char * spinnerText = "|/-\\"; + static const V3b spinnerColor = { 0, 204, 0 }; + char spinnerChar; + int spinnerX; + + // Get the width of the string if we don't already have it. + /////////////////////////////////////////////////////////// + if(width == -1) + width = GetStringWidth(text); + + // Draw the string. + /////////////////// + DrawString(text, (WINDOW_MAX - width) / 2, (WINDOW_MAX / 2), textColor, 1); + + // Init if its the first time. + ////////////////////////////// + if(spinnerIndex == -1) + { + spinnerIndex = 0; + lastSpin = Now; + } + // Check if its time to spin the spinner. + ///////////////////////////////////////// + else if((Now - lastSpin) >= 250) + { + spinnerIndex++; + spinnerIndex %= 4; + lastSpin = Now; + } + + // Get the char to show. + //////////////////////// + spinnerChar = spinnerText[spinnerIndex]; + + // Get the x position of the char. + ////////////////////////////////// + spinnerX = ((WINDOW_MAX - GetCharacterWidth(spinnerChar)) / 2); + + // Draw the spinner. + //////////////////// + DrawCharacter(spinnerChar, spinnerX, 3000, spinnerColor); +} + +static int ReadLittleInt +( + FILE * file +) +{ + byte bytes[4]; + int i; + + if(fread(bytes, 1, 4, file) != 4) + return -1; + + i = bytes[0]; + i |= (bytes[1] << 8); + i |= (bytes[2] << 16); + i |= (bytes[3] << 24); + + return i; +} + +static GT2Bool LoadTexture +( + const char * filename, + GLuint * texture, + GT2Bool useAlpha +) +{ + int width; + int height; + GLubyte * bytes; + int len; + int i; + GLubyte temp; + int format; + + // Load the file. + ///////////////// + bytes = LoadTGAFile(filename, &width, &height); + if(!bytes) + return GT2False; + + // Convert from BGRA to RGBA. + ///////////////////////////// + len = (width * height * 4); + for(i = 0 ; i < len ; i += 4) + { + temp = bytes[i]; + bytes[i] = bytes[i + 2]; + bytes[i + 2] = temp; + } + + // Generate and bind the texture. + ///////////////////////////////// + glGenTextures(1, texture); + glBindTexture(GL_TEXTURE_2D, *texture); + + // Load it from memory. + /////////////////////// + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + if(useAlpha) + format = 4; + else + format = 3; + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bytes); + + // Set to decal mode. + ///////////////////// + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + // Setup the min/mag filters. + ///////////////////////////// + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + free(bytes); + + return GT2True; +} + +static void DrawBackground +( + void +) +{ + if(!DrawBackgroundOption) + return; + + glEnable(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, backgroundTexture); + + glColor4f(1, 1, 1, 1); + + glBegin(GL_QUADS); +#if 0 + glTexCoord2f(0, 0); + glVertex2f(MAP_MIN, MAP_MIN); + glTexCoord2f(1, 0); + glVertex2f(MAP_MAX, MAP_MIN); + glTexCoord2f(1, 1); + glVertex2f(MAP_MAX, MAP_MAX); + glTexCoord2f(0, 1); + glVertex2f(MAP_MIN, MAP_MAX); +#else + glTexCoord2f(0.01, 0.01); + glVertex2f(MAP_MIN, MAP_MIN); + glTexCoord2f(0.99, 0.01); + glVertex2f(MAP_MAX, MAP_MIN); + glTexCoord2f(0.99, 0.99); + glVertex2f(MAP_MAX, MAP_MAX); + glTexCoord2f(0.01, 0.99); + glVertex2f(MAP_MIN, MAP_MAX); +#endif + glEnd(); + + glDisable(GL_TEXTURE_2D); +} + +static void InitStar +( + unsigned long now, + Star * star +) +{ + int color; + + star->position[0] = RandomFloat(MAP_MIN, MAP_MAX, GT2True); + star->position[1] = RandomFloat(MAP_MIN, MAP_MAX, GT2True); + + color = RandomInt(0, 19); + if(color < 15) + CopyV3b(star->color, White); + else if(color < 17) + CopyV3b(star->color, Red); + else if(color < 19) + CopyV3b(star->color, Yellow); + else + CopyV3b(star->color, Blue); + star->alpha = RandomInt(0, 255); + star->flicker = RandomInt(1, MAX_FLICKER); +} + +static void DrawStars +( + void +) +{ + int i; + int alpha; + V2f position; + + if(!DrawStarsOption) + return; + + // Figure out the eye position. + /////////////////////////////// + CopyV2f(position, localPosition); + if(position[0] < HALF_VIEW) + position[0] = HALF_VIEW; + else if(position[0] > (MAP_MAX - HALF_VIEW)) + position[0] = (MAP_MAX - HALF_VIEW); + if(position[1] < HALF_VIEW) + position[1] = HALF_VIEW; + else if(position[1] > (MAP_MAX - HALF_VIEW)) + position[1] = (MAP_MAX - HALF_VIEW); + + // Draw the stars. + ////////////////// + glEnable(GL_BLEND); + glPushMatrix(); + glTranslatef(position[0] / STARS_SCALE, position[1] / STARS_SCALE, 0); + + glBegin(GL_POINTS); + for(i = 0 ; i < NUM_STARS ; i++) + { + alpha = stars[i].alpha; + alpha += RandomInt(-stars[i].flicker, stars[i].flicker); + alpha = ClampInt(alpha, 0, 255); + glColor4ub(stars[i].color[0], stars[i].color[1], stars[i].color[2], (byte)alpha); + glVertex2fv(stars[i].position); + } + glEnd(); + glPopMatrix(); + glDisable(GL_BLEND); +} + +static void DrawSpinner +( + void +) +{ + float rotation; + float scale; + + scale = (Now % 1001); + scale -= 50; + scale /= 50; + + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); + glTranslatef(MAP_HALF, MAP_HALF, 0); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + +#if 1 + glPushMatrix(); + rotation = (Now % 360); + glRotatef(rotation, 0, 0, 1); + glBindTexture(GL_TEXTURE_2D, spinnerTextures[0]); + glColor4f(1, 1, 1, 1); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-SPINNER_COORD, -SPINNER_COORD); + glTexCoord2f(1, 0); + glVertex2f(SPINNER_COORD, -SPINNER_COORD); + glTexCoord2f(1, 1); + glVertex2f(SPINNER_COORD, SPINNER_COORD); + glTexCoord2f(0, 1); + glVertex2f(-SPINNER_COORD, SPINNER_COORD); + glEnd(); + glPopMatrix(); +#endif + + glPushMatrix(); + rotation = (Now % (360 * 8)); + rotation /= 8; + glRotatef(rotation, 0, 0, 1); + glScalef(scale, -scale, 1); + glBindTexture(GL_TEXTURE_2D, spinnerTextures[2]); + glColor4f(1, 1, 1, 0.8); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-SPINNER_COORD, -SPINNER_COORD); + glTexCoord2f(1, 0); + glVertex2f(SPINNER_COORD, -SPINNER_COORD); + glTexCoord2f(1, 1); + glVertex2f(SPINNER_COORD, SPINNER_COORD); + glTexCoord2f(0, 1); + glVertex2f(-SPINNER_COORD, SPINNER_COORD); + glEnd(); + glPopMatrix(); + + glPushMatrix(); + rotation = (Now % (360 * 7)); + rotation /= 7; + glRotatef(rotation, 0, 0, 1); + glScalef(scale, -scale, 1); + glBindTexture(GL_TEXTURE_2D, spinnerTextures[1]); + glColor4f(1, 1, 1, 0.8); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-SPINNER_COORD, -SPINNER_COORD); + glTexCoord2f(1, 0); + glVertex2f(SPINNER_COORD, -SPINNER_COORD); + glTexCoord2f(1, 1); + glVertex2f(SPINNER_COORD, SPINNER_COORD); + glTexCoord2f(0, 1); + glVertex2f(-SPINNER_COORD, SPINNER_COORD); + glEnd(); + glPopMatrix(); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + + DrawBoundingCircle(SPINNER_RADIUS); + glPopMatrix(); +} + +static void DrawGraphs +( + void +) +{ + int i; + int diff; + int len; + int maxDiff = 0; + int maxLen = 0; + + if(updateHistoryStart == -1) + return; + + if(!DrawGraphsOption) + return; + + glPushMatrix(); + glScalef(20, 20, 1); + if(DrawUpdateTimeOption) + { + glColor3ubv(Green); + glBegin(GL_LINE_STRIP); + for(i = 0 ; i < UPDATE_HISTORY_LEN ; i++) + { + diff = updateHistory[(updateHistoryStart + i) % UPDATE_HISTORY_LEN].diff; + + if(diff > maxDiff) + maxDiff = diff; + + if(diff == -1) + { + glColor3ubv(Red); + glVertex2f(i * 2, 50); + glColor3ubv(Green); + } + else + { + if(diff == -1) + diff = 0; + glVertex2f(i * 2, diff / 2); + } + } + glEnd(); + } + + if(DrawUpdateLengthOption) + { + glColor3ubv(Purple); + glBegin(GL_LINE_STRIP); + for(i = 0 ; i < UPDATE_HISTORY_LEN ; i++) + { + len = updateHistory[(updateHistoryStart + i) % UPDATE_HISTORY_LEN].len; + + if(len > maxLen) + maxLen = len; + + glVertex2f(i * 2, len / 8); + } + glEnd(); + } + + if(DrawFrameTimeOption) + { + glColor3ubv(Yellow); + glBegin(GL_LINE_STRIP); + for(i = 0 ; i < FRAME_HISTORY_LEN ; i++) + { + diff = FrameHistory[(FrameHistoryStart + i) % FRAME_HISTORY_LEN]; + glVertex2f(i * 2, diff / 2); + } + glEnd(); + } + + if(DrawMarksOption) + { + glColor3ubv(White); + glBegin(GL_LINES); + for(i = 0 ; i < screenHeight; i += 25) + { + glVertex2f(screenWidth - 10, i); + glVertex2f(screenWidth, i); + } + glEnd(); + } + + glPopMatrix(); +} + +static void DrawFPS +( + unsigned long diff +) +{ + if(DrawFPSOption && diff) + { + static int counter; + static char buf[64]; + if(++counter == 10) + { + counter = 0; + sprintf(buf, "FPS: %d", 1000 / diff); + } + DrawString(buf, WINDOW_MAX - 1500, 600, Grey, 0.2); + } + + { + char buf[64]; + sprintf(buf, "%d asteroid%s", ClientNumAsteroids, + (ClientNumAsteroids==1) ? "" : "s"); + DrawString(buf, WINDOW_MAX - 3500, 600, Grey, 0.2); + } +} + +static void DrawRadar +( + void +) +{ + int i; + Player * player; + CObject * object; + GLdouble plane0[4] = { 0, 1, 1, 0 }; + GLdouble plane1[4] = { 1, 0, 1, 0 }; + + if(!DrawRadarOption) + return; + + glPushMatrix(); + // Move to the upper-right corner. + ////////////////////////////////// + glTranslatef( + WINDOW_MAX - (RADAR_SIZE * RadarScaleOption), + WINDOW_MAX - (RADAR_SIZE * RadarScaleOption), + 0); + + // Scale so we can draw in map space. + ///////////////////////////////////// + glScalef( + RADAR_SIZE / MAP_MAX * RadarScaleOption, + RADAR_SIZE / MAP_MAX * RadarScaleOption, + 1); + +#if 1 + // Enable textures. + /////////////////// + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glColor4f(1, 1, 1, 0.5); + + // Draw a background. + ///////////////////// + glBindTexture(GL_TEXTURE_2D, backgroundTexture); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(0, 0); + glTexCoord2f(1, 0); + glVertex2f(MAP_MAX, 0); + glTexCoord2f(1, 1); + glVertex2f(MAP_MAX, MAP_MAX); + glTexCoord2f(0, 1); + glVertex2f(0, MAP_MAX); + glEnd(); + + // Draw the spinner. + //////////////////// + glPushMatrix(); + glTranslatef(MAP_HALF, MAP_HALF, 0); + glRotatef(Now % 3600, 0, 0, 1); + + glBindTexture(GL_TEXTURE_2D, spinnerTextures[0]); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(-SPINNER_COORD, -SPINNER_COORD); + glTexCoord2f(1, 0); + glVertex2f(SPINNER_COORD, -SPINNER_COORD); + glTexCoord2f(1, 1); + glVertex2f(SPINNER_COORD, SPINNER_COORD); + glTexCoord2f(0, 1); + glVertex2f(-SPINNER_COORD, SPINNER_COORD); + glEnd(); + glPopMatrix(); + + // Disable textures. + //////////////////// + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); +#endif + + // Draw a box around the radar. + /////////////////////////////// + glColor3ubv(Yellow); + glBegin(GL_LINE_STRIP); + glVertex2f(0, MAP_MAX); + glVertex2f(0, 0); + glVertex2f(MAP_MAX, 0); + glEnd(); + + // Setup the clipping planes. + ///////////////////////////// + glEnable(GL_CLIP_PLANE0); + glEnable(GL_CLIP_PLANE1); + glClipPlane(GL_CLIP_PLANE0, plane0); + glClipPlane(GL_CLIP_PLANE1, plane1); + + // Scale points. + //////////////// + if(RadarPointScaleOption != 1) + glPointSize(RadarPointScaleOption); + + glBegin(GL_POINTS); + for(i = 0 ; i < MAX_OBJECTS ; i++) + { + object = &cObjects[i]; + + if(!object->used) + continue; + + if(object->type == ObjectRocket) + glColor3ubv(Red); + else if(object->type == ObjectMine) + glColor3ubv(Yellow); + else if(object->type == ObjectAsteroid) + glColor3ubv(White); + else + continue; + + glVertex2fv(object->position); + } + + glColor3ubv(Orange); + for(i = 0 ; i < MAX_PLAYERS ; i++) + { + if(i == localIndex) + continue; + + player = &players[i]; + + if(!player->used) + continue; + + glVertex2fv(player->position); + } + + glColor3ubv(Green); + glVertex2fv(players[localIndex].position); + glEnd(); + + // Unscale points. + ////////////////// + if(RadarPointScaleOption != 1) + glPointSize(1); + glPopMatrix(); + + glDisable(GL_CLIP_PLANE0); + glDisable(GL_CLIP_PLANE1); +} + +static void SetMapView +( + void +) +{ + V2f corner; + Player * player; + float rotation; + static V2f positionHistory[POSITION_HISTORY_LEN]; + static int positionHistoryStart = -1; + int i; + + // Get the local player. + //////////////////////// + player = &players[localIndex]; + + // Predict the local player's position. + /////////////////////////////////////// + Predict(localPosition, &rotation, player); + + // Put this position in the history. + //////////////////////////////////// + if(positionHistoryStart == -1) + { + positionHistoryStart = 0; + for(i = 0 ; i < POSITION_HISTORY_LEN ; i++) + CopyV2f(positionHistory[i], localPosition); + } + else + { + CopyV2f(positionHistory[positionHistoryStart++], localPosition); + positionHistoryStart %= POSITION_HISTORY_LEN; + } + + // Average out the position history to get the display position. + //////////////////////////////////////////////////////////////// + SetV2f(localPosition, 0, 0); + for(i = 0 ; i < POSITION_HISTORY_LEN ; i++) + AddV2f(localPosition, localPosition, positionHistory[i]); + ScaleV2f(localPosition, localPosition, (1.0 / POSITION_HISTORY_LEN)); + + // Set the left and bottom of the view. + /////////////////////////////////////// + SubScalarV2f(corner, localPosition, HALF_VIEW); + + // Don't show anything outside the map. + /////////////////////////////////////// + ClampV2f(corner, corner, 0, MAP_MAX - FULL_VIEW); + + glLoadIdentity(); + glViewport(0, 0, screenWidth, screenHeight); + gluOrtho2D(corner[0], corner[0] + FULL_VIEW, corner[1], corner[1] + FULL_VIEW); +} + +static void SetWindowView +( + void +) +{ + glLoadIdentity(); + glViewport(0, 0, screenWidth, screenHeight); + gluOrtho2D(0, WINDOW_MAX, 0, WINDOW_MAX); +} + +static void Display +( + void +) +{ + static unsigned long lastDisplay; + unsigned long now; + unsigned long diff; + + // Get the current time. + //////////////////////// + now = current_time(); + + // Get the time difference. + /////////////////////////// + diff = (now - lastDisplay); + + // Update the history. + ////////////////////// + if(FrameHistoryStart == -1) + FrameHistoryStart = 0; + else + { + FrameHistory[FrameHistoryStart++] = diff; + FrameHistoryStart %= FRAME_HISTORY_LEN; + } + + // New last display. + //////////////////// + lastDisplay = now; + + // Set the zoom. + //////////////// + if(localMotion == STILL) + { + if(Zoom > ZOOM_MIN) + { + Zoom -= (ZOOM_IN_SPEED * diff / 1000); + if(Zoom < ZOOM_MIN) + Zoom = ZOOM_MIN; + } + } + else + { + if(Zoom < ZOOM_MAX) + { + Zoom += (ZOOM_OUT_SPEED * diff / 1000); + if(Zoom > ZOOM_MAX) + Zoom = ZOOM_MAX; + } + } + + // Clear the color buffer. + ////////////////////////// + glClear(GL_COLOR_BUFFER_BIT); + + // We want to know how long since the last server update. + ///////////////////////////////////////////////////////// + Now = now; + Diff = (now - lastServerUpdate); + + // Are we connected to the server? + ////////////////////////////////// + if(connected) + { + // Set the view for drawing in map space. + ///////////////////////////////////////// + SetMapView(); + + // Draw the ground. + /////////////////// + DrawBackground(); + + // Draw some stars. + /////////////////// + DrawStars(); + + // Draw the spinner. + //////////////////// + DrawSpinner(); + + // Draw all the objects. + //////////////////////// + DrawObjects(); + + // Draw all the players. + //////////////////////// + DrawPlayers(); + + // Set the view for drawing in window space. + //////////////////////////////////////////// + SetWindowView(); + + // Draw the radar. + ////////////////// + DrawRadar(); + + // Draw the chat. + ///////////////// + DrawChat(); + + // Draw graphs. + /////////////// + DrawGraphs(); + + // Show the framerate. + ////////////////////// + DrawFPS(diff); + } + else + { + // Set the view for drawing in window space. + //////////////////////////////////////////// + SetWindowView(); + + // Draw the connecting screen. + ////////////////////////////// + DrawConnecting(); + } + + // Saw the front and back buffers. + ////////////////////////////////// + glutSwapBuffers(); +} + +static void Reshape +( + int width, + int height +) +{ + screenWidth = width; + screenHeight = height; + + // Setup a simple 2D orthographic view. + /////////////////////////////////////// + glLoadIdentity(); + glViewport(0, 0, width, height); + gluOrtho2D(0, MAP_MAX, 0, MAP_MAX); +} + +void InitializeDisplay +( + void +) +{ + int i; + unsigned long now; + char buffer[32]; + + // Init glut window. + //////////////////// + + glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_ALPHA); + + if(fullScreen) + { + glutGameModeString("640x480:32"); + glutEnterGameMode(); + //glutFullScreen(); + glutSetCursor(GLUT_CURSOR_NONE); + } + else + { + glutInitWindowSize(screenWidth, screenHeight); + glutInitWindowPosition(200, 200); + glutCreateWindow(TITLE); + } + // Set display callbacks. + ///////////////////////// + glutDisplayFunc(Display); + glutReshapeFunc(Reshape); + + // Set the background color. + //////////////////////////// + glClearColor(0, 0, 0, 0); + + // Smooth points. + ///////////////// + glEnable(GL_POINT_SMOOTH); + glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); + + // Don't show the backs of polygons. + //////////////////////////////////// + glEnable(GL_CULL_FACE); + + // We always use the same blend. + //////////////////////////////// + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Load textures. + ///////////////// + printf("Loading textures..."); + LoadTexture("images/space.tga", &backgroundTexture, GT2False); + printf("."); + for(i = 0 ; i < NUM_SHIP_TEXTURES ; i++) + { + sprintf(buffer, "images/ship%d.tga", i); + LoadTexture(buffer, &shipTextures[i], GT2True); + printf("."); + } + for(i = 0 ; i < NUM_EXPLOSION_TEXTURES ; i++) + { + sprintf(buffer, "images/explosion%d.tga", i); + LoadTexture(buffer, &explosionTextures[i], GT2True); + printf("."); + } + for(i = 0 ; i < NUM_ROCKET_TEXTURES ; i++) + { + sprintf(buffer, "images/rocket%d.tga", i); + LoadTexture(buffer, &rocketTextures[i], GT2True); + printf("."); + } + for(i = 0 ; i < NUM_ASTEROID_TEXTURES ; i++) + { + sprintf(buffer, "images/asteroid%d.tga", i); + LoadTexture(buffer, &asteroidTextures[i], GT2True); + printf("."); + } + for(i = 0 ; i < NUM_MINE_TEXTURES ; i++) + { + sprintf(buffer, "images/mine%d.tga", i); + LoadTexture(buffer, &mineTextures[i], GT2True); + printf("."); + } + for(i = 0 ; i < NUM_MINE_TEXTURES ; i++) + { + sprintf(buffer, "images/spinner%d.tga", i); + LoadTexture(buffer, &spinnerTextures[i], GT2True); + printf("."); + } + printf("\n"); + + // Get the time. + //////////////// + now = current_time(); + + // Setup stars. + /////////////// + for(i = 0 ; i < NUM_STARS ; i++) + InitStar(now, &stars[i]); + + // Setup frame history. + /////////////////////// + FrameHistoryStart = -1; +} + +void ShutdownDisplay +( + void +) +{ + if(fullScreen) + { + glutLeaveGameMode(); + } +} + +void DisplayThink +( + unsigned long now +) +{ + static unsigned long last; + unsigned long diff; + + // Get the frame time. + ////////////////////// + now = current_time(); + diff = (now - last); + if(diff < 5) + return; + last = now; + + // Is it time to scroll the chat? + ///////////////////////////////// + if((now - lastChatScroll) > CHAT_SCROLL_TIME) + { + RemoveOldestChat(); + lastChatScroll = now; + } + + // Update the frame. + //////////////////// + glutPostRedisplay(); +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aDisplay.h b/code/gamespy/gt2/gt2action/gt2aDisplay.h new file mode 100644 index 00000000..885a989b --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aDisplay.h @@ -0,0 +1,54 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2ADISPLAY_H_ +#define _GT2ADISPLAY_H_ + +extern GT2Bool fullScreen; + +extern GT2Bool DrawGraphsOption; +extern GT2Bool DrawUpdateTimeOption; +extern GT2Bool DrawUpdateLengthOption; +extern GT2Bool DrawFrameTimeOption; +extern GT2Bool DrawMarksOption; +extern GT2Bool DrawFPSOption; +extern GT2Bool DrawWithPredictionOption; +extern GT2Bool DrawBackgroundOption; +extern GT2Bool DrawStarsOption; +extern GT2Bool DrawBoundingCirclesOption; +extern GT2Bool DrawRadarOption; +extern float RadarScaleOption; +extern float RadarPointScaleOption; +extern GT2Bool ViewClippingOption; + +extern int screenWidth; +extern int screenHeight; + +void InitializeDisplay +( + void +); + +void ShutdownDisplay +( + void +); + +void DisplayThink +( + unsigned long now +); + +void DisplayChat +( + const char * message +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aInput.c b/code/gamespy/gt2/gt2action/gt2aInput.c new file mode 100644 index 00000000..b2a68bd5 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aInput.c @@ -0,0 +1,634 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include +#include "gt2aMain.h" +#include "gt2aInput.h" +#include "gt2aClient.h" +#include "gt2aDisplay.h" +#include "gt2aSound.h" + +#define TOGGLE(b) { ((b) = !(b)); printf( #b " = %s\n", (b)?"ON":"OFF"); } + +GT2Bool upPressed; +GT2Bool downPressed; +GT2Bool leftPressed; +GT2Bool rightPressed; + +GT2Bool UseJoystick = GT2False; + +void SetKeyboardNormal(void); +void SetKeyboardChat(void); + +static char chatBuffer[CHAT_MAX]; +static int nChatChars; +static GT2Bool chatting; + +static void KeyboardPress +( + unsigned char key, + int x, + int y +) +{ + // Lowercase it. + //////////////// + key = tolower(key); + + switch(key) + { + case 0x1B: // esc + ShutdownDisplay(); + exit(1); + + case 't': + SetKeyboardChat(); + break; + + case 'p': + TOGGLE(DrawWithPredictionOption); + break; + + case 'c': + TOGGLE(ViewClippingOption); + break; + + case 'w': + if(localIndex != -1) + { + char buffer[32]; + Player * player; + + player = &players[localIndex]; + sprintf(buffer, "%.0f %.0f %.0f", player->position[0], player->position[1], player->rotation); + DisplayChat(buffer); + } + break; + + case 'j': + TOGGLE(UseJoystick); + break; + + case 'g': + TOGGLE(DrawGraphsOption); + break; + + case 'r': + TOGGLE(DrawBoundingCirclesOption); + break; + } +} + +static void KeyboardRelease +( + unsigned char key, + int x, + int y +) +{ + // Lowercase it. + //////////////// + key = tolower(key); + + switch(key) + { + case 'b': + SendPress("mine"); + break; + + case ' ': + SendPress("rocket"); + break; + } +} + +static void SpecialKeyboardPress +( + int key, + int dummyx, + int dummyy +) +{ + switch(key) + { + case GLUT_KEY_UP: + upPressed = GT2True; + localMotion = FORWARD; + break; + + case GLUT_KEY_DOWN: + downPressed = GT2True; + localMotion = BACKWARD; + break; + + case GLUT_KEY_LEFT: + leftPressed = GT2True; + localTurning = LEFT; + break; + + case GLUT_KEY_RIGHT: + rightPressed = GT2True; + localTurning = RIGHT; + break; + } +} + +static void SpecialKeyboardRelease +( + int key, + int dummyx, + int dummyy +) +{ + switch(key) + { + case GLUT_KEY_UP: + upPressed = GT2False; + if(downPressed) + localMotion = BACKWARD; + else + localMotion = STILL; + break; + + case GLUT_KEY_DOWN: + downPressed = GT2False; + if(upPressed) + localMotion = FORWARD; + else + localMotion = STILL; + break; + + case GLUT_KEY_LEFT: + leftPressed = GT2False; + if(rightPressed) + localTurning = RIGHT; + else + localTurning = STILL; + break; + + case GLUT_KEY_RIGHT: + rightPressed = GT2False; + if(leftPressed) + localTurning = LEFT; + else + localTurning = STILL; + break; + } +} + +const char * GetChatBuffer +( + void +) +{ + if(chatting) + return (const char *)chatBuffer; + + return NULL; +} + +static void ChatAddChar +( + char c +) +{ + if(nChatChars < (CHAT_MAX - 1)) + { + chatBuffer[nChatChars++] = c; + chatBuffer[nChatChars] = '\0'; + } +} + +static void ChatBackspace +( + void +) +{ + if(nChatChars) + chatBuffer[--nChatChars] = '\0'; +} + +static void ChatClear +( + void +) +{ + chatBuffer[0] = '\0'; + nChatChars = 0; +} + +static void ChatSend +( + void +) +{ + assert(nChatChars < CHAT_MAX); + + // Send it. + /////////// + if(nChatChars > 0) + SendChat(chatBuffer); +} + +static void ChatKeyboardPress +( + unsigned char key, + int x, + int y +) +{ + switch(key) + { + case 0x1B: // esc + // Back to normal keyboard handling. + //////////////////////////////////// + SetKeyboardNormal(); + + return; + + case 0x0D: // enter + // Send the current chat message. + ///////////////////////////////// + ChatSend(); + + // Back to normal keyboard handling. + //////////////////////////////////// + SetKeyboardNormal(); + + return; + + case 0x08: // backspace + case 0x7F: // delete + ChatBackspace(); + break; + } + + // Is it printable? + /////////////////// + if(isprint(key)) + { + // Add it to the buffer. + //////////////////////// + ChatAddChar(key); + } +} + +static void ChatKeyboardRelease +( + unsigned char key, + int x, + int y +) +{ + switch(key) + { + } +} + +void SetKeyboardNormal +( + void +) +{ + // Ignore key repeats. + ////////////////////// + glutSetKeyRepeat(GLUT_KEY_REPEAT_OFF); + + // Set glut callbacks. + ////////////////////// + glutKeyboardFunc(KeyboardPress); + glutKeyboardUpFunc(KeyboardRelease); + + // We're not chatting. + ////////////////////// + chatting = GT2False; +} + +void SetKeyboardChat +( + void +) +{ + // We don't care about key repeats now. + /////////////////////////////////////// + glutSetKeyRepeat(GLUT_KEY_REPEAT_DEFAULT); + + // Set glut callbacks. + ////////////////////// + glutKeyboardFunc(ChatKeyboardPress); + glutKeyboardUpFunc(ChatKeyboardRelease); + + // Clear the chat buffer. + ///////////////////////// + ChatClear(); + + // We're chatting. + ////////////////// + chatting = GT2True; +} +static void MouseMotionPassive(int x, int y) +{ + static int x_hold = 0; + static int y_hold = 0; + + if(x_hold) + { + localRotation += -(x_hold - x); + + } + + if( (x <= 0) || (x >= 639) ) + glutWarpPointer(320,240); + + x_hold = x; + y_hold = y; +} + +static void MouseButton(int button, int state, int x, int y) +{ + switch(button) + { + case GLUT_LEFT_BUTTON: + if(state == GLUT_DOWN) + SendPress("rocket"); + break; + case GLUT_RIGHT_BUTTON: + switch(state) + { + case GLUT_DOWN: + upPressed = GT2True; + localMotion = FORWARD; + break; + case GLUT_UP: + upPressed = GT2False; + localMotion = STILL; + break; + } + break; + case GLUT_MIDDLE_BUTTON: + switch(state) + { + case GLUT_DOWN: + upPressed = GT2True; + localMotion = BACKWARD; + break; + case GLUT_UP: + upPressed = GT2False; + localMotion = STILL; + break; + } + break; + } +} + + +static void JoystickProcess(unsigned int buttonMask, int x, int y, int z) +{ + static BYTE buttonA = GT2False; + + + if(!UseJoystick) + return; + + if(buttonMask & GLUT_JOYSTICK_BUTTON_A) + { + if(buttonA == GT2False) + { + SendPress("rocket"); + buttonA = GT2True; + } + } + else + buttonA = GT2False; + + if (x < -50) + localTurning = LEFT; + else if(x > 50) + localTurning = RIGHT; + else + localTurning = STILL; + + if(z < -300) + localMotion = FORWARD; + else if(z > 300) + localMotion = BACKWARD; + else + localMotion = STILL; + +} + +static int MainMenu; +enum +{ + MainToggleSound, + MainToggleViewClipping +}; +static void MainMenuCallback +( + int item +) +{ + switch(item) + { + case MainToggleSound: + ToggleSound(); + break; + case MainToggleViewClipping: + TOGGLE(ViewClippingOption); + break; + } +} + +static int GraphMenu; +enum +{ + GraphToggleGraphs, + GraphToggleUpdateTime, + GraphToggleUpdateLength, + GraphToggleFrameTime, + GraphToggleMarks +}; +static void GraphMenuCallback +( + int item +) +{ + switch(item) + { + case GraphToggleGraphs: + TOGGLE(DrawGraphsOption); + break; + case GraphToggleUpdateTime: + TOGGLE(DrawUpdateTimeOption); + break; + case GraphToggleUpdateLength: + TOGGLE(DrawUpdateLengthOption); + break; + case GraphToggleFrameTime: + TOGGLE(DrawFrameTimeOption); + break; + case GraphToggleMarks: + TOGGLE(DrawMarksOption); + break; + } +} + +static int DrawMenu; +enum +{ + DrawToggleFPS, + DrawTogglePrediction, + DrawToggleBackground, + DrawToggleStars, + DrawToggleBoundingCircles +}; +static void DrawMenuCallback +( + int item +) +{ + switch(item) + { + case DrawToggleFPS: + TOGGLE(DrawFPSOption); + break; + case DrawTogglePrediction: + TOGGLE(DrawWithPredictionOption); + break; + case DrawToggleBackground: + TOGGLE(DrawBackgroundOption); + break; + case DrawToggleStars: + TOGGLE(DrawStarsOption); + break; + case DrawToggleBoundingCircles: + TOGGLE(DrawBoundingCirclesOption); + break; + } +} + +static int RadarMenu; +enum +{ + RadarDrawRadar, + RadarSmallRadar, + RadarMediumRadar, + RadarLargeRadar, + RadarSmallPoints, + RadarMediumPoints, + RadarLargePoints +}; +static void RadarMenuCallback +( + int item +) +{ + switch(item) + { + case RadarDrawRadar: + TOGGLE(DrawRadarOption); + break; + case RadarSmallRadar: + RadarScaleOption = 0.5; + break; + case RadarMediumRadar: + RadarScaleOption = 1; + break; + case RadarLargeRadar: + RadarScaleOption = 2; + break; + case RadarSmallPoints: + RadarPointScaleOption = 1; + break; + case RadarMediumPoints: + RadarPointScaleOption = 2; + break; + case RadarLargePoints: + RadarPointScaleOption = 3; + break; + } +} + +void InitializeInput +( + void +) +{ + // Set the keyboard to normal. + ////////////////////////////// + SetKeyboardNormal(); + + // The glut handlers for "special" keys don't change. + ///////////////////////////////////////////////////// + glutSpecialFunc(SpecialKeyboardPress); + glutSpecialUpFunc(SpecialKeyboardRelease); + + + // The glut handlers for mouse input + //////////////////////////////////// + glutPassiveMotionFunc(MouseMotionPassive); + glutMotionFunc(MouseMotionPassive); + glutMouseFunc (MouseButton); + + // The glut handler for joystick input + ////////////////////////////////////// + glutJoystickFunc(JoystickProcess, 1); + + // Setup the menu. + ////////////////// + MainMenu = glutCreateMenu(MainMenuCallback); + + // Graph sub-menu. + ////////////////// + GraphMenu = glutCreateMenu(GraphMenuCallback); + glutSetMenu(GraphMenu); + glutAddMenuEntry("Toggle Graphs", GraphToggleGraphs); + glutAddMenuEntry("Toggle Update Time", GraphToggleUpdateTime); + glutAddMenuEntry("Toggle Update Length", GraphToggleUpdateLength); + glutAddMenuEntry("Toggle Frame Time", GraphToggleFrameTime); + glutAddMenuEntry("Toggle Marks", GraphToggleMarks); + glutSetMenu(MainMenu); + glutAddSubMenu("Graph", GraphMenu); + + // Draw sub-menu. + ///////////////// + DrawMenu = glutCreateMenu(DrawMenuCallback); + glutSetMenu(DrawMenu); + glutAddMenuEntry("Toggle FPS", DrawToggleFPS); + glutAddMenuEntry("Toggle Prediction", DrawTogglePrediction); + glutAddMenuEntry("Toggle Background", DrawToggleBackground); + glutAddMenuEntry("Toggle Stars", DrawToggleStars); + glutAddMenuEntry("Toggle Bounding Circles", DrawToggleBoundingCircles); + glutSetMenu(MainMenu); + glutAddSubMenu("Draw", DrawMenu); + + // Radar sub-menu. + ////////////////// + RadarMenu = glutCreateMenu(RadarMenuCallback); + glutSetMenu(RadarMenu); + glutAddMenuEntry("Draw Radar", RadarDrawRadar); + glutAddMenuEntry("Small Radar", RadarSmallRadar); + glutAddMenuEntry("Medium Radar", RadarMediumRadar); + glutAddMenuEntry("Large Radar", RadarLargeRadar); + glutAddMenuEntry("Small Points", RadarSmallPoints); + glutAddMenuEntry("Medium Points", RadarMediumPoints); + glutAddMenuEntry("Large Points", RadarLargePoints); + glutSetMenu(MainMenu); + glutAddSubMenu("Radar", RadarMenu); + + // Main menu. + ///////////// + glutAddMenuEntry("Toggle Sound", MainToggleSound); + glutAddMenuEntry("Toggle View Clipping", MainToggleViewClipping); + + // Attach the menu. + /////////////////// + glutAttachMenu(GLUT_RIGHT_BUTTON); +} diff --git a/code/gamespy/gt2/gt2action/gt2aInput.h b/code/gamespy/gt2/gt2action/gt2aInput.h new file mode 100644 index 00000000..cadafa0c --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aInput.h @@ -0,0 +1,24 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2AINPUT_H_ +#define _GT2AINPUT_H_ + +void InitializeInput +( + void +); + +const char * GetChatBuffer +( + void +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aLogic.c b/code/gamespy/gt2/gt2action/gt2aLogic.c new file mode 100644 index 00000000..14603fa7 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aLogic.c @@ -0,0 +1,860 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include "gt2aMain.h" +#include "gt2aLogic.h" +#include "gt2aMath.h" +#include "gt2aSound.h" +#include "gt2aServer.h" + +#define DEATH_MESSAGES 0 + +SObject sObjects[MAX_OBJECTS]; +int numObjects; +static unsigned long Now; +static unsigned long Diff; +static int NumAsteroids; + +static void ExplodeObject(SObject * object); +static void InitAsteroid(SObject * asteroid); + +static SObject * AddObject +( + ObjectType type +) +{ + int i; + SObject * object; + + // Find an open slot. + ///////////////////// + for(i = 0 ; i < MAX_OBJECTS ; i++) + if(!sObjects[i].used) + break; + + // Nothing open? + //////////////// + if(i == MAX_OBJECTS) + return NULL; + + // Clear it. + //////////// + object = &sObjects[i]; + memset(object, 0, sizeof(SObject)); + object->used = GT2True; + object->type = type; + object->owner = -1; + + // Set the start time. + ////////////////////// + object->startTime = Now; + + // One more object. + /////////////////// + numObjects++; + assert(numObjects <= MAX_OBJECTS); + + return &sObjects[i]; +} + +static void RemoveObject +( + SObject * object +) +{ + assert(object); + if(!object) + return; + + // Mark this object for removal. + //////////////////////////////// + object->remove = GT2True; +} + +static void ClientKilled +( + Client * killed, + Client * killer +) +{ +#if DEATH_MESSAGES + char buffer[MAX_NICK + MAX_NICK + 32]; +#endif + + assert(killed); + + // Is there a killer? + ///////////////////// + if(killer) + { + // Self kill? + ///////////// + if(killer == killed) + { + // Subtract a point. + //////////////////// + killer->score--; + } + else + { + // Give the killer a point. + /////////////////////////// + killer->score++; + } + } + + // Kill the killed. + /////////////////// + killed->dead = GT2True; + killed->spawnTime = (Now + DEATH_TIME); + + // Play the death sound. + //////////////////////// + SendSound(SOUND_DIE, -1); + +#if DEATH_MESSAGES + // Let everyone know. + ///////////////////// + if(killer == killed) + sprintf(buffer, "%s killed himself", killed->nick); + else if(killer) + sprintf(buffer, "%s killed %s", killer->nick, killed->nick); + else + sprintf(buffer, "%s was killed", killed->nick); + BroadcastText(buffer, -1, GT2True); +#endif +} + +/////////// +//ROCKET // +/////////// + +static void RocketThink +( + SObject * rocket +) +{ + assert(rocket); + + // Move the rocket. + /////////////////// + ComputeNewPosition( + rocket->position, + rocket->position, + FORWARD, + rocket->rotation, + Diff, + ROCKET_SPEED, + GT2False); + + // Check if we should disappear. + //////////////////////////////// + if((rocket->position[0] < (MAP_MIN - MAP_EXTRA)) || + (rocket->position[0] > (MAP_MAX + MAP_EXTRA)) || + (rocket->position[1] < (MAP_MIN - MAP_EXTRA)) || + (rocket->position[1] > (MAP_MAX + MAP_EXTRA))) + { + RemoveObject(rocket); + } +} + +static void RocketTouchObject +( + SObject * rocket, + SObject * object +) +{ + // Don't explode if its another rocket with the same owner. + /////////////////////////////////////////////////////////// + if((object->type == ObjectRocket) && (object->owner == rocket->owner)) + return; + + // We pass through explosions. + ////////////////////////////// + if(object->type == ObjectExplosion) + return; + + // Explode the object. + ////////////////////// + ExplodeObject(object); + + // Remove the rocket. + ///////////////////// + RemoveObject(rocket); +} + +static void RocketTouchClient +( + SObject * rocket, + Client * client +) +{ + // Check for death. + /////////////////// + if(client->dead) + return; + + // Check for self. + ////////////////// + if(rocket->owner == client->index) + return; + + // Kill the client. + /////////////////// + if(rocket->owner == -1) + ClientKilled(client, NULL); + else + ClientKilled(client, &clients[rocket->owner]); + + // Explode the rocket. + ////////////////////// + ExplodeObject(rocket); +} + +////////// +// MINE // +////////// + +static void MineThink +( + SObject * mine +) +{ + // Check for detonate. + ////////////////////// + if((Now - mine->startTime) > MINE_TIME) + { + // Explode the mine. + //////////////////// + ExplodeObject(mine); + } + else + { + // Rotate the mine. + /////////////////// + mine->rotation = ComputeNewRotation(mine->rotation, RIGHT, Diff, MINE_TURN_SPEED); + + // Check if a second has passed. + //////////////////////////////// + if((int)((Now - mine->startTime) / 1000) > mine->count) + { + // One more full second. + //////////////////////// + mine->count++; + + // Make a sound during the last 3 seconds. + ////////////////////////////////////////// + if(mine->count >= ((MINE_TIME / 1000) - 3)) + SendSound(SOUND_MINE, -1); + } + } +} + +static void MineTouchObject +( + SObject * mine, + SObject * object +) +{ + // Ignore explosions. + ///////////////////// + if(object->type == ObjectExplosion) + return; + + // Blow up the object. + ////////////////////// + ExplodeObject(object); + + // Remove the mine. + /////////////////// + RemoveObject(mine); +} + +static void MineTouchClient +( + SObject * mine, + Client * client +) +{ + // Check if its armed itself. + ///////////////////////////// + if((Now - mine->startTime) < MINE_ARM_TIME) + return; + + // Check for death. + /////////////////// + if(client->dead) + return; + + // Kill the client. + /////////////////// + if(mine->owner == -1) + ClientKilled(client, NULL); + else + ClientKilled(client, &clients[mine->owner]); + + // Explode the mine. + //////////////////// + ExplodeObject(mine); +} + +////////////// +// ASTEROID // +////////////// + +static void AsteroidThink +( + SObject * asteroid +) +{ + // Rotate the asteroid. + /////////////////////// + asteroid->rotation = ComputeNewRotation(asteroid->rotation, LEFT, Diff, ASTEROID_TURN_SPEED); + + // Move the asteroid. + ///////////////////// + ComputeNewPosition(asteroid->position, asteroid->position, FORWARD, asteroid->heading, Diff, asteroid->speed, GT2False); + + // If it goes past the edge, loop it around. + //////////////////////////////////////////// + if(asteroid->position[0] > MAP_MAX + (2*asteroid->radius)) + asteroid->position[0] = MAP_MIN - (2*asteroid->radius); + if(asteroid->position[0] < MAP_MIN - (2*asteroid->radius)) + asteroid->position[0] = MAP_MAX + (2*asteroid->radius); + + if(asteroid->position[1] > MAP_MAX + (2*asteroid->radius)) + asteroid->position[1] = MAP_MIN - (2*asteroid->radius); + if(asteroid->position[1] < MAP_MIN - (2*asteroid->radius)) + asteroid->position[1] = MAP_MAX + (2*asteroid->radius); + +} + +static void AsteroidTouchObject +( + SObject * asteroid, + SObject * object +) +{ + // Explode on asteroid impact. + ////////////////////////////// + if(object->type == ObjectAsteroid) + { + ExplodeObject(asteroid); + ExplodeObject(object); + } +} + +static void AsteroidTouchClient +( + SObject * asteroid, + Client * client +) +{ + // Check for death. + /////////////////// + if(client->dead) + return; + + // Kill the client. + /////////////////// + ClientKilled(client, NULL); + + // Explode the asteroid. + //////////////////////// + ExplodeObject(asteroid); +} + +static void SpawnAsteroids +( + void +) +{ + SObject * asteroid; + int i; + + NumAsteroids = 0; + for(i = 0 ; i < NUM_ASTEROIDS ; i++) + { + asteroid = AddObject(ObjectAsteroid); + if(asteroid) + InitAsteroid(asteroid); + } +} + +static void AsteroidDie +( + SObject * asteroid +) +{ + // One less asteroid. + ///////////////////// + NumAsteroids--; + + // If they're all gone, make new ones. + ////////////////////////////////////// + if(!NumAsteroids) + SpawnAsteroids(); +} + +static void InitAsteroid +( + SObject * asteroid +) +{ + asteroid->position[0] = RandomFloat(MAP_MIN, MAP_MAX, GT2True); + asteroid->position[1] = RandomFloat(MAP_MIN, MAP_MAX, GT2True); + asteroid->rotation = RandomFloat(0, 360, GT2False); + asteroid->think = AsteroidThink; + asteroid->touchObject = AsteroidTouchObject; + asteroid->touchClient = AsteroidTouchClient; + asteroid->die = AsteroidDie; + asteroid->radius = ASTEROID_RADIUS; + asteroid->speed = RandomFloat(ASTEROID_SPEED_MIN, ASTEROID_SPEED_MAX, GT2False); + asteroid->heading = asteroid->rotation; + + NumAsteroids++; +} + +/////////////// +// EXPLOSION // +/////////////// + +static void ExplosionThink +( + SObject * explosion +) +{ + // Check for done. + ////////////////// + if((Now - explosion->startTime) > EXPLOSION_TIME) + { + // Delete the explosion. + //////////////////////// + RemoveObject(explosion); + } +} + +static void ExplosionTouchObject +( + SObject * explosion, + SObject * object +) +{ +} + +static void ExplosionTouchClient +( + SObject * explosion, + Client * client +) +{ + // Check if we're still dangerous. + ////////////////////////////////// + if((Now - explosion->startTime) > EXPLOSION_DANGER_TIME) + return; + + // Check for death. + /////////////////// + if(client->dead) + return; + + // Kill the client. + /////////////////// + if(explosion->owner == -1) + ClientKilled(client, NULL); + else + ClientKilled(client, &clients[explosion->owner]); +} + +static SObject * AddExplosion +( + V2f position, + int owner +) +{ + SObject * explosion; + + // Add an explosion. + //////////////////// + explosion = AddObject(ObjectExplosion); + if(!explosion) + return NULL; + + // Setup the explosion. + /////////////////////// + explosion->owner = owner; + CopyV2f(explosion->position, position); + explosion->rotation = RandomFloat(0, 360, GT2False); + explosion->think = ExplosionThink; + explosion->touchObject = ExplosionTouchObject; + explosion->touchClient = ExplosionTouchClient; + explosion->radius = EXPLOSION_RADIUS; + + // Send a sound effect. + /////////////////////// + SendSound(SOUND_EXPLOSION, -1); + + return explosion; +} + +static void ExplodeObject +( + SObject * object +) +{ + // Don't explode explosions. + //////////////////////////// + if(object->type == ObjectExplosion) + return; + + // Mark this object for explosion. + ////////////////////////////////// + object->explode = GT2True; + + if(object->type != ObjectAsteroid) + { + // Mark for removal. + //////////////////// + RemoveObject(object); + } +} + +static void ClientThink +( + Client * client +) +{ + // Dead? + //////// + if(client->dead) + { + // Time to spawn? + ///////////////// + if(Now > client->spawnTime) + ClientSpawn(client); + } + else if(client->numAsteroids) + { + static V2f spinnerPosition = { MAP_HALF, MAP_HALF }; + float distance; + + // Check for the spinner. + ///////////////////////// + distance = DistanceV2f(spinnerPosition, client->position); + if(distance < (PLAYER_RADIUS + SPINNER_RADIUS)) + { + // Drop off our asteroids. + ////////////////////////// + client->score += client->numAsteroids; + client->numAsteroids = 0; + SendNumAsteroids(client->numAsteroids, client->index); + SendSound(SOUND_PICKUP, client->index); + } + } +} + +void ClientPress +( + int clientIndex, + const char * button +) +{ + SObject * object; + Client * client; + V2f forward; + + assert(clientIndex >= 0); + assert(clientIndex < MAX_CLIENTS); + assert(clients[clientIndex].used); + + // Set the Now time. + //////////////////// + Now = current_time(); + + client = &clients[clientIndex]; + + // Check the button. + //////////////////// + if(strcasecmp(button, "mine") == 0) + { + // Add a mine object. + ///////////////////// + object = AddObject(ObjectMine); + if(object) + { + // Set its properties. + ////////////////////// + object->owner = clientIndex; + CopyV2f(object->position, client->position); + object->rotation = client->rotation; + object->think = MineThink; + object->touchObject = MineTouchObject; + object->touchClient = MineTouchClient; + object->radius = MINE_RADIUS; + + // Move it so its behind the client. + ////////////////////////////////////// + RotationToVector(forward, object->rotation); + ScaleV2f(forward, forward, -500); + AddV2f(object->position, object->position, forward); + + // Play the mine sound. + /////////////////////// + SendSound(SOUND_MINE, -1); + } + } + else if(strcasecmp(button, "rocket") == 0) + { + // Add a rocket object. + /////////////////////// + object = AddObject(ObjectRocket); + if(object) + { + // Set its properties. + ////////////////////// + object->owner = clientIndex; + CopyV2f(object->position, client->position); + object->rotation = client->rotation; + object->think = RocketThink; + object->touchObject = RocketTouchObject; + object->touchClient = RocketTouchClient; + object->radius = ROCKET_RADIUS; + + // Move it so its ahead of the client. + ////////////////////////////////////// + RotationToVector(forward, object->rotation); + ScaleV2f(forward, forward, 300); + AddV2f(object->position, object->position, forward); + + // Play the rocket sound. + ///////////////////////// + SendSound(SOUND_ROCKET, -1); + } + } +} + +void ObjectThink +( + SObject * object +) +{ + int i; + SObject * other; + Client * client; + + // Think. + ///////// + object->think(object); + + // Check for client touches. + //////////////////////////// + if(object->touchClient) + { + float range; + + range = (object->radius + PLAYER_RADIUS); + + for(i = 0 ; i < MAX_CLIENTS ; i++) + { + client = &clients[i]; + + // Check for in use. + //////////////////// + if(!client->used) + continue; + + // Check for a touch. + ///////////////////// + if(DistanceV2f(object->position, client->position) <= range) + { + // Touched. + /////////// + object->touchClient(object, client); + } + } + } + + // Check for object touches. + //////////////////////////// + if(object->touchObject) + { + for(i = 0 ; i < MAX_OBJECTS ; i++) + { + other = &sObjects[i]; + + // Check for in use. + //////////////////// + if(!other->used) + continue; + + // Check for self. + ////////////////// + if(other == object) + continue; + + // Check for a touch. + ///////////////////// + if(DistanceV2f(object->position, other->position) <= (object->radius + other->radius)) + { + // Touched. + /////////// + object->touchObject(object, other); + } + } + } +} + +void ObjectsThink +( + unsigned long now, + unsigned long diff +) +{ + Client * client; + SObject * object; + int i; + + Now = now; + Diff = diff; + + // Go through the list of clients. + ////////////////////////////////// + for(i = 0 ; i < MAX_CLIENTS ; i++) + { + client = &clients[i]; + + // Is it a real client? + /////////////////////// + if(client->used) + { + // Think. + ///////// + ClientThink(client); + } + } + + // Think for all the objects. + ///////////////////////////// + for(i = 0 ; i < MAX_OBJECTS ; i++) + { + object = &sObjects[i]; + + // Is the object in use? + //////////////////////// + if(object->used) + { + // Think. + ///////// + ObjectThink(object); + } + } + + // Check for exploding or removed objects. + ////////////////////////////////////////// + for(i = 0 ; i < MAX_OBJECTS ; i++) + { + object = &sObjects[i]; + + // Is the object in use? + //////////////////////// + if(object->used) + { + // Explode the object? + ////////////////////// + if(object->explode) + { + // Create an explosion. + /////////////////////// + AddExplosion(object->position, object->owner); + + // It exploded. + /////////////// + object->explode = GT2False; + + + // If it is an asteroid, then move it to the edge + ///////////////////////////////////////////////// + if(object->type == ObjectAsteroid) + { + object->position[0] = RandomFloat(MAP_MIN, MAP_MAX, GT2True); + object->position[1] = MAP_MIN - object->radius; + } + + + } + + // Remove the object? + ///////////////////// + if(object->remove) + { + // Call its die function. + ///////////////////////// + if(object->die) + object->die(object); + + if(object->used) + { + // We're not using this object anymore. + /////////////////////////////////////// + object->used = GT2False; + + // One less object. + /////////////////// + numObjects--; + assert(numObjects >= 0); + } + } + } + } +} + +void ClientSpawn +( + Client * client +) +{ + SObject * asteroid; + + // Throw away his asteroids. + //////////////////////////// + for( ; client->numAsteroids ; client->numAsteroids--) + { + asteroid = AddObject(ObjectAsteroid); + if(asteroid) + { + InitAsteroid(asteroid); + asteroid->position[0] = client->position[0]; + asteroid->position[0] += RandomFloat(-5000, 5000, GT2True); + asteroid->position[1] = client->position[1]; + asteroid->position[1] += RandomFloat(-5000, 5000, GT2True); + ClampV2f(asteroid->position, asteroid->position, MAP_MIN, MAP_MAX); + } + } + SendNumAsteroids(client->numAsteroids, client->index); + + // Pick a random starting point. + //////////////////////////////// + client->position[0] = RandomFloat(MAP_MIN, MAP_MAX, GT2True); + client->position[1] = RandomFloat(MAP_MIN, MAP_MAX, GT2True); + + // Not dead. + //////////// + client->dead = GT2False; +} + +void InitializeLogic +( + void +) +{ + // Add a bunch of asteroids. + //////////////////////////// + SpawnAsteroids(); +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aLogic.h b/code/gamespy/gt2/gt2action/gt2aLogic.h new file mode 100644 index 00000000..b389d682 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aLogic.h @@ -0,0 +1,119 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2ALOGIC_H_ +#define _GT2ALOGIC_H_ + +#include "gt2aServer.h" + +#define PLAYER_SPEED 7000 // units/second +#define PLAYER_TURN_SPEED 0.14 // degrees/millisecond +#define PLAYER_RADIUS 200 +#define PLAYER_MAX_ASTEROIDS 50 +#define DEATH_TIME 1000 +#define PRESS_TIME 100 // time between presses + +#define MAX_OBJECTS 256 +#define ROCKET_RADIUS 250 +#define ROCKET_SPEED 10500 +#define MINE_TIME 30000 +#define MINE_ARM_TIME 1000 +#define MINE_TURN_SPEED 0.36 // degrees/millisecond +#define MINE_RADIUS 200//141 +#define NUM_ASTEROIDS 50 +#define ASTEROID_TURN_SPEED 0.18 +#define ASTEROID_TURN_RANGE 0.24 +#define ASTEROID_RADIUS 350 +#define ASTEROID_SPEED_MIN 0 +#define ASTEROID_SPEED_MAX 5000 +#define EXPLOSION_RADIUS 900 +#define EXPLOSION_TIME 350 +#define EXPLOSION_DANGER_TIME 250 +#define SPINNER_RADIUS 5000 +#define SPINNER_COORD (SPINNER_RADIUS * 1.414) + +typedef enum +{ + ObjectRocket, + ObjectMine, + ObjectAsteroid, + ObjectExplosion, + NumObjects +} ObjectType; + +typedef void (* Think) +( + struct SObject * self +); + +typedef void (* TouchObject) +( + struct SObject * self, + struct SObject * object +); + +typedef void (* Die) +( + struct SObject * self +); + +typedef void (* TouchClient) +( + struct SObject * self, + struct Client * client +); + +typedef struct SObject +{ + GT2Bool used; // If this slot is in use or not. + ObjectType type; // The type of object. + int owner; // The client that owns this object, or -1. + V2f position; // The object's position. + float rotation; // The object's rotation. + unsigned long startTime; // The time this object was created. + Think think; // This object's think function. + TouchObject touchObject; // Called when touching another object. + TouchClient touchClient; // Called when touching a client. + Die die; // Called when the object dies. + int count; // Generic counter. + float radius; // This object's radius (for collision). + GT2Bool explode; // Explode this object when done thinking. + GT2Bool remove; // Remove this object when done thinking. + + float heading; // The direction the object is moving + float speed; // The speed of the object +} SObject; + +extern SObject sObjects[MAX_OBJECTS]; +extern int numObjects; + +void ObjectsThink +( + unsigned long now, + unsigned long diff +); + +void ClientPress +( + int clientIndex, + const char * button +); + +void ClientSpawn +( + Client * client +); + +void InitializeLogic +( + void +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aMain.c b/code/gamespy/gt2/gt2action/gt2aMain.c new file mode 100644 index 00000000..9584ee61 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aMain.c @@ -0,0 +1,276 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include +#include +#include +#include "gt2aMain.h" +#include "gt2aDisplay.h" +#include "gt2aClient.h" +#include "gt2aServer.h" +#include "gt2aInput.h" +#include "gt2aSound.h" +#include "../../ghttp/ghttp.h" + +const V3b Red = { 255, 0, 0 }; +const V3b Green = { 0, 255, 0 }; +const V3b Blue = { 0, 0, 255 }; +const V3b Yellow = { 255, 255, 0 }; +const V3b Orange = { 255, 128, 0 }; +const V3b Purple = { 255, 0, 255 }; +const V3b Black = { 0, 0, 0 }; +const V3b White = { 255, 255, 255 }; +const V3b Grey = { 128, 128, 128 }; + +static GT2Bool host = GT2True; +static GT2Bool dedicated; + +void Log +( + const char * format, + ... +) +{ +#if 1 + FILE * fp; + va_list args; + + va_start(args, format); + + fp = fopen("gt2a.log", "at"); + if(fp) + { + fprintf(fp, "%08d: ", (current_time() % 100000000)); + vfprintf(fp, format, args); + fclose(fp); + } + + va_end(args); +#endif +} + +static void Idle +( + void +) +{ + msleep(1); + glutPostRedisplay(); +} + +static void Timer +( + int value +) +{ + unsigned long now; + + // Reset the timer. + /////////////////// + glutTimerFunc(10, Timer, 0); + + // Get the current time. + //////////////////////// + now = current_time(); + + // Let the subsystems think. + //////////////////////////// + if(host) + ServerThink(now); + if(!dedicated) + ClientThink(now); + DisplayThink(now); +} + +static GHTTPBool NaminatorCallback +( + GHTTPRequest request, + GHTTPResult result, + char * buffer, + GHTTPByteCount bufferLen, + void * param +) +{ + if(result == GHTTPSuccess) + { + char * str; + + // Set the nick. + //////////////// + strncpy(localNick, buffer, MAX_NICK); + localNick[MAX_NICK - 1] = '\0'; + + // Cap off the newline. + /////////////////////// + str = strchr(localNick, '\n'); + if(str) + *str = '\0'; + + // Strip out stuff that'll mess up our + // key\value message passing. + ////////////////////////////////////// + while(str = strchr(localNick, '\\')) + *str = '/'; + + printf("%s\n", localNick); + } + else + { + printf("Error\n"); + } + + return GHTTPTrue; +} + +static void ParseArgs +( + int argc, + char ** argv +) +{ + GT2Bool gotNick = GT2False; + int i; + + for(i = 1 ; i < argc ; i++) + { + // Connect. + /////////// + if(strncasecmp(argv[i], "-c", 2) == 0) + { + if(++i < argc) + { + // Get the address to connect to. + ///////////////////////////////// + strcpy(serverAddress, argv[i]); + if(!strchr(serverAddress, ':')) + strcat(serverAddress, PORT_STRING); + host = GT2False; + } + } + // Fullscreen. + ////////////// + else if(strcasecmp(argv[i], "-full") == 0) + { + fullScreen = GT2True; + } + // Nick. + //////// + else if((strcasecmp(argv[i], "-nick") == 0) || + (strcasecmp(argv[i], "-name") == 0)) + { + if(++i < argc) + { + char * str; + + // Set the nick. + //////////////// + strncpy(localNick, argv[i], MAX_NICK); + localNick[MAX_NICK - 1] = '\0'; + + // Strip out stuff that'll mess up our + // key\value message passing. + ////////////////////////////////////// + while(str = strchr(localNick, '\\')) + *str = '/'; + + // We got a nick. + ///////////////// + gotNick = GT2True; + } + } + // Dedicated server. + //////////////////// + else if(strcasecmp(argv[i], "-dedicated") == 0) + { + dedicated = GT2True; + } + // Width. + ///////// + else if(strcasecmp(argv[i], "-width") == 0) + { + if(++i < argc) + screenWidth = atoi(argv[i]); + } + // Height. + ////////// + else if(strcasecmp(argv[i], "-height") == 0) + { + if(++i < argc) + screenHeight = atoi(argv[i]); + } + } + + // If we didn't get a nick, get a naminator nick. + ///////////////////////////////////////////////// + if(!gotNick) + { + printf("Getting naminator name..."); + ghttpGetFile("http://www.planetquake.com/excessive/name.asp", GHTTPTrue, NaminatorCallback, NULL); + } +} + +static GT2Bool Initialize +( + void +) +{ + // Init the display. + //////////////////// + InitializeDisplay(); + + // Init input handling. + /////////////////////// + InitializeInput(); + + // Init sound. + ////////////// + InitializeSound(); + + // Start the server if we're hosting. + ///////////////////////////////////// + if(host) + { + if(!InitializeServer()) + { + printf("Failed to initialize server!\n"); + return GT2False; + } + } + + // Start the client if not dedicated. + ///////////////////////////////////// + if(!dedicated) + { + if(!InitializeClient()) + { + printf("Failed to initialize client!\n"); + return GT2False; + } + } + + return GT2True; +} + +int main +( + int argc, + char ** argv +) +{ + srand(time(NULL)); + glutInit(&argc, argv); + ParseArgs(argc, argv); + glutTimerFunc(10, Timer, 0); + glutIdleFunc(Idle); + if(!Initialize()) + return 1; + glutMainLoop(); + return 0; +} diff --git a/code/gamespy/gt2/gt2action/gt2aMain.h b/code/gamespy/gt2/gt2action/gt2aMain.h new file mode 100644 index 00000000..555928a2 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aMain.h @@ -0,0 +1,85 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2AMAIN_H_ +#define _GT2AMAIN_H_ + +#include +#include +#include "../../nonport.h" +#include "../gt2.h" +#include "../gt2Encode.h" + +#define PORT_STRING ":12345" + +#define MAX_PLAYERS 64 + +#define BACKWARD -1 +#define STILL 0 +#define FORWARD 1 + +#define LEFT -1 +#define RIGHT 1 + +#define MAP_MIN 0 +#define MAP_MAX 100000.0 +#define MAP_HALF (MAP_MAX / 2) +#define MAP_EXTRA (MAP_MAX / 10) + +#define MAX_NICK 32 + +#define CHAT_MAX 64 + +#define MSG_C_INITIAL 1 +#define MSG_C_INITIAL_STR "s" +#define MSG_C_UPDATE 2 +#define MSG_C_UPDATE_STR "ppzzzz" +#define MSG_C_PRESS 3 +#define MSG_C_PRESS_STR "s" +#define MSG_C_CHAT 4 +#define MSG_C_CHAT_STR "s" +#define MSG_S_ADDCLIENT 1001 +#define MSG_S_ADDCLIENT_STR "bs" +#define MSG_S_DELCLIENT 1002 +#define MSG_S_DELCLIENT_STR "b" +#define MSG_S_START 1003 +#define MSG_S_START_STR "b" +#define MSG_S_UPDATE 1004 +#define MSG_S_UPDATE_STR "pbb" +#define MSG_S_UPDATE_CLIENT_STR "bpppizzzzz" +#define MSG_S_UPDATE_OBJECT_STR "bpppi" +#define MSG_S_CHAT 1005 +#define MSG_S_CHAT_STR "s" +#define MSG_S_SOUND 1006 +#define MSG_S_SOUND_STR "b" +#define MSG_S_NUMASTEROIDS 1007 +#define MSG_S_NUMASTEROIDS_STR "b" + +typedef unsigned char byte; +typedef float V2f[2]; +typedef byte V3b[3]; + +extern const V3b Red; +extern const V3b Green; +extern const V3b Blue; +extern const V3b Yellow; +extern const V3b Orange; +extern const V3b Purple; +extern const V3b Black; +extern const V3b White; +extern const V3b Grey; + +void Log +( + const char * format, + ... +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aMath.c b/code/gamespy/gt2/gt2action/gt2aMath.c new file mode 100644 index 00000000..0ea17969 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aMath.c @@ -0,0 +1,364 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include +#include +#include +#include "gt2aMain.h" +#include "gt2aMath.h" + +#define PI 3.1415926535897932384626433832795 +#define PI_DIV_180 (PI / 180) + +byte * CopyV3b +( + V3b dest, + const V3b src +) +{ + dest[0] = src[0]; + dest[1] = src[1]; + dest[2] = src[2]; + + return dest; +} + +byte * SetV3b +( + V3b dest, + byte src0, + byte src1, + byte src2 +) +{ + dest[0] = src0; + dest[1] = src1; + dest[2] = src2; + + return dest; +} + +float * CopyV2f +( + V2f dest, + const V2f src +) +{ + dest[0] = src[0]; + dest[1] = src[1]; + + return dest; +} + +float * SetV2f +( + V2f dest, + float src0, + float src1 +) +{ + dest[0] = src0; + dest[1] = src1; + + return dest; +} + +float * ScaleV2f +( + V2f dest, + const V2f src, + float scale +) +{ + dest[0] = (src[0] * scale); + dest[1] = (src[1] * scale); + + return dest; +} + +float * AddV2f +( + V2f dest, + const V2f src0, + const V2f src1 +) +{ + dest[0] = (src0[0] + src1[0]); + dest[1] = (src0[1] + src1[1]); + + return dest; +} + +float * SubV2f +( + V2f dest, + const V2f src0, + const V2f src1 +) +{ + dest[0] = (src0[0] - src1[0]); + dest[1] = (src0[1] - src1[1]); + + return dest; +} + +float * SubScalarV2f +( + V2f dest, + const V2f src, + float scalar +) +{ + dest[0] = (src[0] - scalar); + dest[1] = (src[1] - scalar); + + return dest; +} + +float LenV2f +( + const V2f src +) +{ + return (float)sqrt((src[0] * src[0]) + (src[1] * src[1])); +} + +float DistanceV2f +( + const V2f src0, + const V2f src1 +) +{ + V2f temp; + + SubV2f(temp, src0, src1); + + return LenV2f(temp); +} + +float * ClampV2f +( + V2f dest, + const V2f src, + float min, + float max +) +{ + float temp; + int i; + + for(i = 0 ; i < 2 ; i++) + { + temp = src[i]; + if(temp > max) + dest[i] = max; + else if(temp < min) + dest[i] = min; + else + dest[i] = temp; + } + + return dest; +} + +float ClampRotation +( + float rotation +) +{ + while(rotation >= 360) + rotation -= 360; + while(rotation < 0) + rotation += 360; + return rotation; +} + +int ClampInt +( + int num, + int min, + int max +) +{ + if(num < min) + num = min; + else if(num > max) + num = max; + return num; +} + +float * RotationToVector +( + V2f dest, + float rotation +) +{ + // Convert to radians. + ////////////////////// + rotation = (float)(rotation * PI_DIV_180); + + dest[0] = (float)sin(rotation); + dest[1] = (float)cos(rotation); + + return dest; +} + +float * ComputeNewPosition +( + V2f dest, + const V2f position, + int motion, + float rotation, + unsigned long diff, + int unitsPerSec, + GT2Bool clamp +) +{ + V2f forward; + float scale; + + // Check for no motion. + /////////////////////// + if(!motion) + { + CopyV2f(dest, position); + return dest; + } + + // Get the direction we're facing. + ////////////////////////////////// + RotationToVector(forward, rotation); + + // Scale it based on the amount of time. + //////////////////////////////////////// + scale = (float)(diff * unitsPerSec / 1000); + if(motion == BACKWARD) + scale = -scale; + ScaleV2f(forward, forward, scale); + + // Update the position. + /////////////////////// + AddV2f(dest, position, forward); + + // Clamp it. + //////////// + if(clamp) + ClampV2f(dest, dest, 0, MAP_MAX); + + return dest; +} + +float ComputeNewRotation +( + float rotation, + int turning, + unsigned long diff, + float degreesPerMilliSec +) +{ + // Turn. + //////// + rotation += (turning * (diff * degreesPerMilliSec)); + + // Clamp it to between 0 and 360. + ///////////////////////////////// + while(rotation >= 360) + rotation -= 360; + while(rotation < 0) + rotation += 360; + + return rotation; +} + +float RandomFloat +( + float minFloat, + float maxFloat, + GT2Bool maxInclusive +) +{ + float num; + + num = rand(); + num *= (maxFloat - minFloat); + num /= (RAND_MAX + (maxInclusive?0:1)); + num += minFloat; + + return num; +} + +int RandomInt +( + int minInt, + int maxInt +) +{ + return (minInt + ((rand() * (maxInt - minInt + 1)) / (RAND_MAX + 1))); +} + +unsigned short RotationToUnsignedShort +( + float rotation +) +{ + rotation *= 0xFFFF; + rotation /= 360; + if(rotation < 0) + rotation = 0; + if(rotation > 0xFFFF) + rotation = 0xFFFF; + + return (unsigned short)rotation; +} + +float UnsignedShortToRotation +( + unsigned short packedRotation +) +{ + float rotation; + + rotation = packedRotation; + rotation *= 360; + rotation /= 0xFFFF; + + return ClampRotation(rotation); +} + +unsigned short PositionToUnsignedShort +( + float position +) +{ + position += (MAP_EXTRA / 2); + position *= 0xFFFF; + position /= (MAP_MAX + MAP_EXTRA); + if(position < 0) + position = 0; + else if(position > USHRT_MAX) + position = USHRT_MAX; + + return (unsigned short)position; +} + +float UnsignedShortToPosition +( + unsigned short packedPosition +) +{ + float position; + + position = packedPosition; + position *= (MAP_MAX + MAP_EXTRA); + position /= 0xFFFF; + position -= (MAP_EXTRA / 2); + + return position; +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aMath.h b/code/gamespy/gt2/gt2action/gt2aMath.h new file mode 100644 index 00000000..04e0a3fb --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aMath.h @@ -0,0 +1,161 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2AMATH_H_ +#define _GT2AMATH_H_ + +#include +#include "gt2aMain.h" + +byte * CopyV3b +( + V3b dest, + const V3b src +); + +byte * SetV3b +( + V3b dest, + byte src0, + byte src1, + byte src2 +); + +float * CopyV2f +( + V2f dest, + const V2f src +); + +float * SetV2f +( + V2f dest, + float src0, + float src1 +); + +float * ScaleV2f +( + V2f dest, + const V2f src, + float scale +); + +float * AddV2f +( + V2f dest, + const V2f src0, + const V2f src1 +); + +float * SubV2f +( + V2f dest, + const V2f src0, + const V2f src1 +); + +float * SubScalarV2f +( + V2f dest, + const V2f src, + float scalar +); + +float LenV2f +( + const V2f src +); + +float DistanceV2f +( + const V2f src0, + const V2f src1 +); + +float * ClampV2f +( + V2f dest, + const V2f src, + float min, + float max +); + +float ClampRotation +( + float rotation +); + +int ClampInt +( + int num, + int min, + int max +); + +float * RotationToVector +( + V2f dest, + float rotation +); + +float * ComputeNewPosition +( + V2f dest, + const V2f position, + int motion, + float rotation, + unsigned long diff, + int unitsPerMilliSec, + GT2Bool clamp +); + +float ComputeNewRotation +( + float rotation, + int turning, + unsigned long diff, + float degreesPerMilliSec +); + +float RandomFloat +( + float minFloat, + float maxFloat, + GT2Bool maxInclusive +); + +int RandomInt +( + int minInt, + int maxInt +); + +unsigned short RotationToUnsignedShort +( + float rotation +); + +float UnsignedShortToRotation +( + unsigned short packedRotation +); + +unsigned short PositionToUnsignedShort +( + float position +); + +float UnsignedShortToPosition +( + unsigned short packedPosition +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aParse.c b/code/gamespy/gt2/gt2action/gt2aParse.c new file mode 100644 index 00000000..472ade22 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aParse.c @@ -0,0 +1,79 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include +#include "gt2aParse.h" + +static char buffer[256]; + +static char * StripKeyValue +( + char * input +) +{ + int i; + int c; + + // Get the key. + /////////////// + for(i = 0 ; i < (sizeof(buffer) - 1) ; i++) + { + c = *input++; + if((c == '\\') || (c == '\0')) + break; + buffer[i] = (char)c; + } + buffer[i] = '\0'; + + return buffer; +} + +char * ParseKeyValue +( + char * input, + const char * key +) +{ + char * str; + int len; + + if(!input) + return NULL; + + // Are we looking for the first key? + //////////////////////////////////// + if(!key) + { + // Does it start with a '\'? + //////////////////////////// + if(*input++ != '\\') + return NULL; + + // Get the key. + /////////////// + return StripKeyValue(input); + } + + // Find the key. + //////////////// + buffer[0] = '\\'; + len = strlen(key); + memcpy(buffer + 1, key, len); + buffer[++len] = '\\'; + buffer[++len] = '\0'; + str = strstr(input, buffer); + if(!str) + return NULL; + str += strlen(buffer); + + // Get the key. + /////////////// + return StripKeyValue(str); +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aParse.h b/code/gamespy/gt2/gt2action/gt2aParse.h new file mode 100644 index 00000000..9ae3b12a --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aParse.h @@ -0,0 +1,26 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2APARSE_H_ +#define _GT2APARSE_H_ + +// Search the input for "\key\". +// Returns the value following the key, +// or NULL if the key wasn't found. +// If key is NULL, returns the first key +// in input, or NULL if there's an error. +///////////////////////////////////////// +char * ParseKeyValue +( + char * input, + const char * key +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aServer.c b/code/gamespy/gt2/gt2action/gt2aServer.c new file mode 100644 index 00000000..e0b2eff8 --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aServer.c @@ -0,0 +1,746 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include "gt2aMain.h" +#include "gt2aServer.h" +#include "gt2aParse.h" +#include "gt2aMath.h" +#include "gt2aSound.h" +#include "gt2aLogic.h" + +#define SERVER_THINK_TIME 30 + +static GT2Socket Socket; +Client clients[MAX_CLIENTS]; +static int numClients; +static GT2ConnectionCallbacks connectionCallbacks; +static unsigned short nextServerUpdateID; + +static void Broadcast +( + const char * message, + int len, + int exceptIndex, + GT2Bool reliable +) +{ + int i; + + for(i = 0 ; i < MAX_CLIENTS ; i++) + if(clients[i].used && (i != exceptIndex)) + gt2Send(clients[i].connection, (const GT2Byte *)message, len, reliable); +} + +void BroadcastText +( + const char * message, + int exceptIndex, + GT2Bool reliable +) +{ + static char messageCopy[CHAT_MAX]; + static char buffer[256]; + char * slash; + int rcode; + + // Copy off the chat message. + ///////////////////////////// + strncpy(messageCopy, message, CHAT_MAX); + messageCopy[CHAT_MAX - 1] = '\0'; + + // Because of the way gt2a is sending messages + // we need to change \ to /. + ////////////////////////////////////////////// + while(slash = strchr(messageCopy, '\\')) + *slash = '/'; + + // Encode the message. + ////////////////////// + rcode = gtEncode(MSG_S_CHAT, MSG_S_CHAT_STR, buffer, sizeof(buffer), + messageCopy); + if(rcode != -1) + { + // Send this chat message out to everyone. + ////////////////////////////////////////// + Broadcast(buffer, rcode, -1, reliable); + } +} + +static int AddClient +( + void +) +{ + int i; + + // Find an open slot. + ///////////////////// + for(i = 0 ; i < MAX_CLIENTS ; i++) + if(!clients[i].used) + break; + + // Nothing open? + //////////////// + if(i == MAX_CLIENTS) + return -1; + + // Clear it. + //////////// + memset(&clients[i], 0, sizeof(Client)); + clients[i].index = i; + clients[i].used = GT2True; + + // One more client. + /////////////////// + numClients++; + assert(numClients <= MAX_CLIENTS); + + return i; +} + +static void RemoveClient +( + int clientIndex +) +{ + // Not in use anymore. + ////////////////////// + clients[clientIndex].used = GT2False; + + // One less client. + /////////////////// + numClients--; + assert(numClients >= 0); +} + +static void SendAddClient +( + int sendClientIndex, + int newClientIndex +) +{ + char buffer[256]; + Client * sendClient; + Client * newClient; + int rcode; + + assert((sendClientIndex >= 0) && (sendClientIndex < MAX_CLIENTS)); + sendClient = &clients[sendClientIndex]; + assert(sendClient->used); + assert(sendClient->connection); + + assert((newClientIndex >= 0) && (newClientIndex < MAX_CLIENTS)); + newClient = &clients[newClientIndex]; + assert(newClient->used); + assert(newClient->connection); + + // Encode the message. + ///////////////////// + rcode = gtEncode(MSG_S_ADDCLIENT, MSG_S_ADDCLIENT_STR, buffer, sizeof(buffer), + newClientIndex, + newClient->nick); + if(rcode != -1) + { + // Send the message. + //////////////////// + gt2Send(sendClient->connection, (const GT2Byte *)buffer, rcode, GT2True); + } +} + +static void SendDelClient +( + int sendClient, + int delClient +) +{ + char buffer[32]; + int rcode; + + assert((sendClient >= 0) && (sendClient < MAX_CLIENTS)); + assert(clients[sendClient].used); + assert(clients[sendClient].connection); + assert((delClient >= 0) && (delClient < MAX_CLIENTS)); + + // Encode the message. + ///////////////////// + rcode = gtEncode(MSG_S_DELCLIENT, MSG_S_DELCLIENT_STR, buffer, sizeof(buffer), + delClient); + if(rcode != -1) + { + // Send the message. + //////////////////// + gt2Send(clients[sendClient].connection, (const GT2Byte *)buffer, rcode, GT2True); + } +} + +static void SendStart +( + int sendClient +) +{ + char buffer[32]; + int rcode; + + assert((sendClient >= 0) && (sendClient < MAX_CLIENTS)); + assert(clients[sendClient].used); + assert(clients[sendClient].connection); + + // Encode the message. + ///////////////////// + rcode = gtEncode(MSG_S_START, MSG_S_START_STR, buffer, sizeof(buffer), + sendClient); + if(rcode != -1) + { + // Send the message. + //////////////////// + gt2Send(clients[sendClient].connection, (const GT2Byte *)buffer, rcode, GT2True); + } +} + +void SendSound +( + int sound, + int sendClient +) +{ + char buffer[32]; + int rcode; + + assert(sendClient >= -1); + assert(sendClient < MAX_CLIENTS); +#ifdef _DEBUG + if(sendClient != -1) + { + assert(clients[sendClient].used); + assert(clients[sendClient].connection); + } +#endif + + // Encode the message. + ///////////////////// + rcode = gtEncode(MSG_S_SOUND, MSG_S_SOUND_STR, buffer, sizeof(buffer), + sound); + if(rcode != -1) + { + // Send the message. + //////////////////// + if(sendClient == -1) + Broadcast(buffer, rcode, -1, GT2False); + else + gt2Send(clients[sendClient].connection, (const GT2Byte *)buffer, rcode, GT2False); + } +} + +void SendNumAsteroids +( + int total, + int sendClient +) +{ + char buffer[32]; + int rcode; + + assert(sendClient >= -1); + assert(sendClient < MAX_CLIENTS); + assert(clients[sendClient].used); + assert(clients[sendClient].connection); + assert(total >= 0); + assert(total <= PLAYER_MAX_ASTEROIDS); + + // Encode the message. + ///////////////////// + rcode = gtEncode(MSG_S_NUMASTEROIDS, MSG_S_NUMASTEROIDS_STR, buffer, sizeof(buffer), + (byte)total);; + if(rcode != -1) + { + // Send the message. + //////////////////// + gt2Send(clients[sendClient].connection, (const GT2Byte *)buffer, rcode, GT2True); + } +} + +static void SendUpdates +( + void +) +{ + int i; + char * buffer; + int len; + static char update[1024 * 8]; + Client * client; + SObject * object; + unsigned long now; + int rcode; + + // Get the current time. + //////////////////////// + now = current_time(); + + // Setup the encoding parameters. + ///////////////////////////////// + buffer = update; + len = sizeof(update); + + // First encode the header. + /////////////////////////// + rcode = gtEncode(MSG_S_UPDATE, MSG_S_UPDATE_STR, buffer, len, + nextServerUpdateID++, + numClients, + numObjects); + if(rcode == -1) + return; + buffer += rcode; + len -= rcode; + + // Encode each client. + ////////////////////// + for(i = 0 ; i < MAX_CLIENTS ; i++) + { + if(clients[i].used) + { + client = &clients[i]; + + rcode = gtEncodeNoType(MSG_S_UPDATE_CLIENT_STR, buffer, len, + i, + PositionToUnsignedShort(client->position[0]), + PositionToUnsignedShort(client->position[1]), + RotationToUnsignedShort(client->rotation), + client->score, + client->motion == FORWARD ? 1 : 0, + client->motion == BACKWARD ? 1 : 0, + client->turning == RIGHT ? 1 : 0, + client->turning == LEFT ? 1 : 0, + client->dead); + if(rcode == -1) + return; + buffer += rcode; + len -= rcode; + } + } + + // Encode each object. + ////////////////////// + for(i = 0 ; i < MAX_OBJECTS ; i++) + { + if(sObjects[i].used) + { + object = &sObjects[i]; + + rcode = gtEncodeNoType(MSG_S_UPDATE_OBJECT_STR, buffer, len, + object->type, + PositionToUnsignedShort(object->position[0]), + PositionToUnsignedShort(object->position[1]), + RotationToUnsignedShort(object->rotation), + (now - object->startTime)); + if(rcode == -1) + return; + buffer += rcode; + len -= rcode; + } + } + + // Send the update. + /////////////////// + Broadcast(update, sizeof(update) - len, -1, GT2False); +} + +static void ServerReceivedCallback +( + GT2Connection connection, + GT2Byte * message, + int len, + GT2Bool reliable +) +{ + int clientIndex; + Client * client; + GTMessageType type; + int rcode; + + // Check for no message. + //////////////////////// + if(!message) + return; + + // Which client was this? + ///////////////////////// + clientIndex = (int)gt2GetConnectionData(connection); + assert((clientIndex >= 0) && (clientIndex < MAX_CLIENTS)); + if((clientIndex < 0) || (clientIndex >= MAX_CLIENTS)) + return; + assert(clients[clientIndex].used); + if(!clients[clientIndex].used) + return; + client = &clients[clientIndex]; + + // Get the message type. + //////////////////////// + type = gtEncodedMessageType((char *)message); + + // Update? + ////////// + if(type == MSG_C_UPDATE) + { + unsigned short updateID; + unsigned long now; + byte forward; + byte backward; + byte right; + byte left; + unsigned short packedRotation; + + // Get the time we received it. + /////////////////////////////// + now = current_time(); + + // Decode it. + ///////////// + rcode = gtDecode(MSG_C_UPDATE_STR, (char *)message, len, + &updateID, + &packedRotation, + &forward, + &backward, + &right, + &left); + if(rcode == -1) + return; + + // Set the rotation. + //////////////////// + client->rotation = UnsignedShortToRotation(packedRotation); + + // Interpret the motion & turning bits. + /////////////////////////////////////// + if(forward) + client->motion = FORWARD; + else if(backward) + client->motion = BACKWARD; + else + client->motion = STILL; + if(right) + client->turning = RIGHT; + else if(left) + client->turning = LEFT; + else + client->turning = STILL; + + // Check if we should move the client. + ////////////////////////////////////// + if(client->lastUpdate && !client->dead && client->motion) + { + unsigned long diff; + + // Get how long since the last update. + ////////////////////////////////////// + diff = (now - client->lastUpdate); + + // Compute the new position. + //////////////////////////// + ComputeNewPosition( + client->position, + client->position, + client->motion, + client->rotation, + diff, + PLAYER_SPEED, + GT2True); + } + + // Set the last update time. + //////////////////////////// + client->lastUpdate = now; + } + // Press? + ///////// + else if(type == MSG_C_PRESS) + { + unsigned long now; + char press[64]; + + // A dead client can't fire. + //////////////////////////// + if(client->dead) + return; + + // Get the current time. + //////////////////////// + now = current_time(); + + // Is this too soon? + //////////////////// + if((now - client->lastPress) < PRESS_TIME) + return; + + // Decode it. + ///////////// + rcode = gtDecode(MSG_C_PRESS_STR, (char *)message, len, + press); + if(rcode == -1) + return; + + // The last press. + ////////////////// + client->lastPress = now; + + // Do the press. + //////////////// + ClientPress(clientIndex, press); + } + // Chat? + //////// + else if(type == MSG_C_CHAT) + { + char buffer[MAX_NICK + CHAT_MAX + 16]; + + // Start forming the outgoing chat message. + /////////////////////////////////////////// + sprintf(buffer, "%s: ", client->nick); + + // Decode the incoming message. + /////////////////////////////// + rcode = gtDecode(MSG_C_CHAT_STR, (char *)message, len, + buffer + strlen(buffer)); + if(rcode == -1) + return; + + // Send this chat message out to everyone. + ////////////////////////////////////////// + BroadcastText(buffer, -1, GT2True); + } +} + +static void ServerClosedCallback +( + GT2Connection connection, + GT2CloseReason reason +) +{ + int clientIndex; + int i; + SObject * object; + + // Which client was this? + ///////////////////////// + clientIndex = (int)gt2GetConnectionData(connection); + assert((clientIndex >= 0) && (clientIndex < MAX_CLIENTS)); + if((clientIndex < 0) || (clientIndex >= MAX_CLIENTS)) + return; + assert(clients[clientIndex].used); + if(!clients[clientIndex].used) + return; + + // Remove this client. + ////////////////////// + RemoveClient(clientIndex); + + // Send the remove client message. + ////////////////////////////////// + for(i = 0 ; i < MAX_CLIENTS ; i++) + if(clients[i].used) + SendDelClient(i, clientIndex); + + // Mark all owned objects as not owned. + /////////////////////////////////////// + for(i = 0 ; i < MAX_OBJECTS ; i++) + { + object = &sObjects[i]; + if(object->used && (object->owner == clientIndex)) + object->owner = -1; + } +} + +static void ServerConnectAttemptCallback +( + GT2Socket socket, + GT2Connection connection, + unsigned int ip, + unsigned short port, + int latency, + GT2Byte * message, + int len +) +{ + int newClientIndex; + Client * newClient; + int i; + char nick[MAX_NICK]; + int rcode; + + // Make sure we got a message. + ////////////////////////////// + if(!message) + { + gt2Reject(connection, (const GT2Byte *)"No initial data received", -1); + return; + } + + // Check the message type. + ////////////////////////// + if(gtEncodedMessageType((char *)message) != MSG_C_INITIAL) + { + gt2Reject(connection, (const GT2Byte *)"Bad message type", -1); + return; + } + + // Decode the data. + /////////////////// + rcode = gtDecode(MSG_C_INITIAL_STR, (char *)message, len, + nick); + if(rcode == -1) + { + gt2Reject(connection, (const GT2Byte *)"Bad message data", -1); + return; + } + + // Add the client to the list. + ////////////////////////////// + newClientIndex = AddClient(); + if(newClientIndex == -1) + { + gt2Reject(connection, (const GT2Byte *)"Server full", -1); + return; + } + newClient = &clients[newClientIndex]; + + // Accept the connection. + ///////////////////////// + if(!gt2Accept(connection, &connectionCallbacks)) + { + // Remove the client. + ///////////////////// + RemoveClient(newClientIndex); + return; + } + + // Set the connection's data to its index. + ////////////////////////////////////////// + gt2SetConnectionData(connection, (void *)newClientIndex); + + // Set some properties for the new client. + ////////////////////////////////////////// + newClient->connection = connection; + strncpy(newClient->nick, nick, MAX_NICK); + newClient->nick[MAX_NICK - 1] = '\0'; + + // Spawn the client. + //////////////////// + ClientSpawn(newClient); + + // Send new client message for the clients already connected. + // Also send new client messages for new client to other clients. + ///////////////////////////////////////////////////////////////// + for(i = 0 ; i < MAX_CLIENTS ; i++) + { + if(clients[i].used) + { + SendAddClient(i, newClientIndex); + if(i != newClientIndex) + SendAddClient(newClientIndex, i); + } + } + + // It has no asteroids. + /////////////////////// + SendNumAsteroids(0, newClientIndex); + + // Tell the new client it can start. + //////////////////////////////////// + SendStart(newClientIndex); +} + +static void ServerSocketErrorCallback +( + GT2Socket socket +) +{ + printf("Server socket error\n"); +} + +GT2Bool InitializeServer +( + void +) +{ + GT2Result result; + int i; + + // Setup callback structs. + ////////////////////////// + memset(&connectionCallbacks, 0, sizeof(GT2ConnectionCallbacks)); + connectionCallbacks.received = ServerReceivedCallback; + connectionCallbacks.closed = ServerClosedCallback; + + // Set client indices. + ////////////////////// + for(i = 0 ; i < MAX_CLIENTS ; i++) + clients[i].index = i; + + // Create the socket. + ///////////////////// + result = gt2CreateSocket(&Socket, PORT_STRING, 0, 0, ServerSocketErrorCallback); + if(result != GT2Success) + return GT2False; + + // Listen. + ////////// + gt2Listen(Socket, ServerConnectAttemptCallback); + + // Initialize the logic. + //////////////////////// + InitializeLogic(); + + return GT2True; +} + +void ServerThink +( + unsigned long now +) +{ + static GT2Bool firstTime = GT2True; + static unsigned long last; + unsigned long diff; + + // Think. + ///////// + gt2Think(Socket); + + // The first time through just store the time. + // This is so diff is accurate the second time through. + /////////////////////////////////////////////////////// + if(firstTime) + { + firstTime = GT2False; + last = now; + return; + } + + // How long since the last update? + ////////////////////////////////// + diff = (now - last); + + // Check for an update. + /////////////////////// + if(diff < SERVER_THINK_TIME) + return; + + // New last update. + /////////////////// + last = now; + + // Process objects. + /////////////////// + ObjectsThink(now, diff); + + // Send updates. + //////////////// + SendUpdates(); +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aServer.h b/code/gamespy/gt2/gt2action/gt2aServer.h new file mode 100644 index 00000000..d02b7a0c --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aServer.h @@ -0,0 +1,65 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2ASERVER_H_ +#define _GT2ASERVER_H_ + +#define MAX_CLIENTS MAX_PLAYERS + +typedef struct Client +{ + GT2Bool used; // If this slot is in use or not. + int index; // This client's clientIndex. + GT2Connection connection; // The GT2Connection for this client. + V2f position; // The current position (0 <= x,y < MAP_MAX). + float rotation; // Client's view angle (0 <= rotation < 360). + int motion; // Client's current motion (STILL, FORWARD, BACKWARD). + int turning; // Client's current turning direction (STILL, LEFT, RIGHT). + unsigned long lastUpdate; // Last update received from this client. + char nick[MAX_NICK]; // The client's nick. + int score; // The client's score. + GT2Bool dead; // True if this client was killed and not respawned. + unsigned long spawnTime; // If dead, spawn at this time. + unsigned long lastPress; // When the last press event was received. + int numAsteroids; // The number of asteroids being held by this player. +} Client; + +extern Client clients[MAX_CLIENTS]; + +GT2Bool InitializeServer +( + void +); + +void ServerThink +( + unsigned long now +); + +void SendSound +( + int sound, + int sendClient +); + +void SendNumAsteroids +( + int total, + int sendClient +); + +void BroadcastText +( + const char * message, + int exceptIndex, + GT2Bool reliable +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aSound.c b/code/gamespy/gt2/gt2action/gt2aSound.c new file mode 100644 index 00000000..8a68fbce --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aSound.c @@ -0,0 +1,543 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#include +#include +#include "gt2aMain.h" +#include "gt2aSound.h" +#include "gt2aMath.h" +#include "../../darray.h" + +#define MAX_DUPLICATES 8 + +static GT2Bool playSounds; +static LPDIRECTSOUND DirectSound; +static DArray SoundList; + +typedef struct SoundEffect +{ + LPDIRECTSOUNDBUFFER SoundBuffers[MAX_DUPLICATES]; + int numDuplicates; +} SoundEffect; + +// The order of this list must match up with the order +// of the SOUND_* enums in gt2aSound.h +////////////////////////////////////////////////////// +const char * soundFiles[] = +{ + "sounds\\explosion.wav", + "sounds\\mine.wav", + "sounds\\die.wav", + "sounds\\rocket.wav", + "sounds\\pickup.wav", + NULL +}; + +// Adapted from a Microsoft DirectSound sample. +/////////////////////////////////////////////// +static HRESULT ReadMMIO +( + HMMIO hmmioIn, + MMCKINFO * pckInRIFF, + WAVEFORMATEX ** ppwfxInfo +) +{ + MMCKINFO ckIn; // chunk info. for general use. + PCMWAVEFORMAT pcmWaveFormat; // Temp PCM structure to load in. + + *ppwfxInfo = NULL; + + if( ( 0 != mmioDescend( hmmioIn, pckInRIFF, NULL, 0 ) ) ) + return E_FAIL; + + if( (pckInRIFF->ckid != FOURCC_RIFF) || + (pckInRIFF->fccType != mmioFOURCC('W', 'A', 'V', 'E') ) ) + return E_FAIL; + + // Search the input file for for the 'fmt ' chunk. + ckIn.ckid = mmioFOURCC('f', 'm', 't', ' '); + if( 0 != mmioDescend(hmmioIn, &ckIn, pckInRIFF, MMIO_FINDCHUNK) ) + return E_FAIL; + + // Expect the 'fmt' chunk to be at least as large as ; + // if there are extra parameters at the end, we'll ignore them + if( ckIn.cksize < (LONG) sizeof(PCMWAVEFORMAT) ) + return E_FAIL; + + // Read the 'fmt ' chunk into . + if( mmioRead( hmmioIn, (HPSTR) &pcmWaveFormat, + sizeof(pcmWaveFormat)) != sizeof(pcmWaveFormat) ) + return E_FAIL; + + // Allocate the waveformatex, but if its not pcm format, read the next + // word, and thats how many extra bytes to allocate. + if( pcmWaveFormat.wf.wFormatTag == WAVE_FORMAT_PCM ) + { + if( NULL == ( *ppwfxInfo = (WAVEFORMATEX *)malloc(sizeof(WAVEFORMATEX)))) + return E_FAIL; + + // Copy the bytes from the pcm structure to the waveformatex structure + memcpy( *ppwfxInfo, &pcmWaveFormat, sizeof(pcmWaveFormat) ); + (*ppwfxInfo)->cbSize = 0; + } + else + { + // Read in length of extra bytes. + WORD cbExtraBytes = 0L; + if( mmioRead( hmmioIn, (CHAR*)&cbExtraBytes, sizeof(WORD)) != sizeof(WORD) ) + return E_FAIL; + + *ppwfxInfo = (WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX) + cbExtraBytes); + if( NULL == *ppwfxInfo ) + return E_FAIL; + + // Copy the bytes from the pcm structure to the waveformatex structure + memcpy( *ppwfxInfo, &pcmWaveFormat, sizeof(pcmWaveFormat) ); + (*ppwfxInfo)->cbSize = cbExtraBytes; + + // Now, read those extra bytes into the structure, if cbExtraAlloc != 0. + if( mmioRead( hmmioIn, (CHAR*)(((BYTE*)&((*ppwfxInfo)->cbSize))+sizeof(WORD)), + cbExtraBytes ) != cbExtraBytes ) + { + free(*ppwfxInfo); + *ppwfxInfo = NULL; + return E_FAIL; + } + } + + // Ascend the input file out of the 'fmt ' chunk. + if( 0 != mmioAscend( hmmioIn, &ckIn, 0 ) ) + { + free(*ppwfxInfo); + *ppwfxInfo = NULL; + return E_FAIL; + } + + return S_OK; +} + +// Adapted from a Microsoft DirectSound sample. +/////////////////////////////////////////////// +static HRESULT WaveOpenFile +( + CHAR * strFileName, + HMMIO * phmmioIn, + WAVEFORMATEX ** ppwfxInfo, + MMCKINFO * pckInRIFF +) +{ + HRESULT hr; + HMMIO hmmioIn = NULL; + + if( NULL == ( hmmioIn = mmioOpen( strFileName, NULL, MMIO_ALLOCBUF|MMIO_READ ) ) ) + return E_FAIL; + + if( FAILED( hr = ReadMMIO( hmmioIn, pckInRIFF, ppwfxInfo ) ) ) + { + mmioClose( hmmioIn, 0 ); + return hr; + } + + *phmmioIn = hmmioIn; + + return S_OK; +} + +// Adapted from a Microsoft DirectSound sample. +/////////////////////////////////////////////// +static HRESULT WaveStartDataRead +( + HMMIO * phmmioIn, + MMCKINFO * pckIn, + MMCKINFO * pckInRIFF +) +{ + // Seek to the data + if( -1 == mmioSeek( *phmmioIn, pckInRIFF->dwDataOffset + sizeof(FOURCC), + SEEK_SET ) ) + return E_FAIL; + + // Search the input file for for the 'data' chunk. + pckIn->ckid = mmioFOURCC('d', 'a', 't', 'a'); + if( 0 != mmioDescend( *phmmioIn, pckIn, pckInRIFF, MMIO_FINDCHUNK ) ) + return E_FAIL; + + return S_OK; +} + +// Adapted from a Microsoft DirectSound sample. +/////////////////////////////////////////////// +static HRESULT WaveReadFile +( + HMMIO hmmioIn, + UINT cbRead, + BYTE * pbDest, + MMCKINFO * pckIn, + UINT * cbActualRead +) +{ + MMIOINFO mmioinfoIn; // current status of + UINT cbDataIn; + DWORD cT; + + *cbActualRead = 0; + + if( 0 != mmioGetInfo( hmmioIn, &mmioinfoIn, 0 ) ) + return E_FAIL; + + cbDataIn = cbRead; + if( cbDataIn > pckIn->cksize ) + cbDataIn = pckIn->cksize; + + pckIn->cksize -= cbDataIn; + + for(cT = 0; cT < cbDataIn; cT++ ) + { + // Copy the bytes from the io to the buffer. + if( mmioinfoIn.pchNext == mmioinfoIn.pchEndRead ) + { + if( 0 != mmioAdvance( hmmioIn, &mmioinfoIn, MMIO_READ ) ) + return E_FAIL; + + if( mmioinfoIn.pchNext == mmioinfoIn.pchEndRead ) + return E_FAIL; + } + + // Actual copy. + *((BYTE*)pbDest+cT) = *((BYTE*)mmioinfoIn.pchNext); + mmioinfoIn.pchNext++; + } + + if( 0 != mmioSetInfo( hmmioIn, &mmioinfoIn, 0 ) ) + return E_FAIL; + + *cbActualRead = cbDataIn; + return S_OK; +} + +// If restoreBuffer is not NULL, restore it! +//////////////////////////////////////////// +static GT2Bool LoadSound +( + int sound, + const char * filename, + LPDIRECTSOUNDBUFFER restoreBuffer +) +{ + int rcode; + HRESULT result; + LPWAVEFORMATEX waveFormat; + HMMIO IOHandle; + MMCKINFO dataChunkInfo; + MMCKINFO parentChunkInfo; + DSBUFFERDESC bufferDescriptor; + SoundEffect soundEffect; + LPVOID lockedBuffer; + DWORD lockedBufferLen; + UINT numBytes; + LPDIRECTSOUNDBUFFER soundBuffer; + + // Open the file. + ///////////////// + rcode = WaveOpenFile((char *)filename, &IOHandle, &waveFormat, &parentChunkInfo); + if(rcode) + return GT2False; + + // Prepare to read the data chunk. + ////////////////////////////////// + rcode = WaveStartDataRead(&IOHandle, &dataChunkInfo, &parentChunkInfo); + if(rcode) + return GT2False; + + // If we're not restoring, create the buffer. + ///////////////////////////////////////////// + if(!restoreBuffer) + { + // Clear our sound object. + ////////////////////////// + memset(&soundEffect, 0, sizeof(SoundEffect)); + soundEffect.numDuplicates = 1; + + // Describe the desired buffer. + /////////////////////////////// + memset(&bufferDescriptor, 0, sizeof(DSBUFFERDESC)); + bufferDescriptor.dwSize = sizeof(DSBUFFERDESC); + bufferDescriptor.dwFlags = DSBCAPS_STATIC; + bufferDescriptor.dwBufferBytes = dataChunkInfo.cksize; + bufferDescriptor.lpwfxFormat = waveFormat; + + // Create the buffer. + ///////////////////// + result = IDirectSound_CreateSoundBuffer( + DirectSound, + &bufferDescriptor, + &soundEffect.SoundBuffers[0], + NULL); + if(FAILED(result)) + { + mmioClose(IOHandle, 0); + return GT2False; + } + + // Set the sound buffer pointer. + //////////////////////////////// + soundBuffer = soundEffect.SoundBuffers[0]; + } + else + { + // Set the sound buffer pointer. + //////////////////////////////// + soundBuffer = restoreBuffer; + } + + // Lock the buffer. + /////////////////// + result = IDirectSoundBuffer_Lock( + soundBuffer, + 0, + 0, + &lockedBuffer, + &lockedBufferLen, + NULL, + NULL, + DSBLOCK_ENTIREBUFFER); + if(FAILED(result)) + { + mmioClose(IOHandle, 0); + return GT2False; + } + + // Read the data into the buffer. + ///////////////////////////////// + rcode = WaveReadFile( + IOHandle, + lockedBufferLen, + (BYTE *)lockedBuffer, + &dataChunkInfo, + &numBytes); + if(rcode) + { + mmioClose(IOHandle, 0); + return GT2False; + } + + // Unlock the buffer. + ///////////////////// + IDirectSoundBuffer_Unlock( + soundBuffer, + lockedBuffer, + lockedBufferLen, + NULL, + 0); + + // Close the file. + ////////////////// + mmioClose(IOHandle, 0); + + // Add this sound to the list if its new. + ///////////////////////////////////////// + if(!restoreBuffer) + ArrayAppend(SoundList, &soundEffect); + + return GT2True; +} + +GT2Bool InitializeSound +( + void +) +{ + HRESULT result; + HWND hWnd; + int i; + + // Init the sound list. + /////////////////////// + SoundList = ArrayNew(sizeof(SoundEffect), 0, NULL); + if(!SoundList) + return GT2False; + + // Init direct sound. + ///////////////////// + result = DirectSoundCreate(NULL, &DirectSound, NULL); + if(FAILED(result)) + return GT2False; + + // Get the window handle. + ///////////////////////// + hWnd = GetForegroundWindow(); + if(!hWnd) + hWnd = GetDesktopWindow(); + + // Set the cooperative level. + ///////////////////////////// + result = IDirectSound_SetCooperativeLevel(DirectSound, hWnd, DSSCL_PRIORITY); + if(FAILED(result)) + return GT2False; + + // Load the sounds. + /////////////////// + for(i = 0 ; i < NUM_SOUNDS ; i++) + { + assert(soundFiles[i]); + if(!soundFiles[i]) + return GT2False; + LoadSound(i, soundFiles[i], NULL); + } + assert(!soundFiles[i]); + + // Turn on sound. + ///////////////// + playSounds = GT2True; + + return GT2True; +} + +void CleanupSound +( + void +) +{ + // Cleanup the sound list. + ////////////////////////// + ArrayFree(SoundList); + + // Cleanup DirectSound. + /////////////////////// + if(DirectSound) + { + IDirectSound_Release(DirectSound); + DirectSound = NULL; + } + + // COM crap. + //////////// + CoUninitialize(); +} + +void ToggleSound +( + void +) +{ + playSounds = !playSounds; +} + +void PlaySoundEffect +( + int sound +) +{ + SoundEffect * soundEffect; + HRESULT result; + DWORD status; + int i; + + // Make sure we're playing sounds. + ////////////////////////////////// + if(!playSounds) + return; + + // Get the correct sound. + ///////////////////////// + soundEffect = (SoundEffect *)ArrayNth(SoundList, sound); + assert(soundEffect); + if(!soundEffect) + return; + + // Find a free buffer. + ////////////////////// + for(i = 0 ; i < soundEffect->numDuplicates ; i++) + { + // Get the current status. + ////////////////////////// + result = IDirectSoundBuffer_GetStatus(soundEffect->SoundBuffers[i], &status); + if(FAILED(result)) + return; + + // Did it lose its buffer? + ////////////////////////// + if(status & DSBSTATUS_BUFFERLOST) + { + // Restore the buffer. + ////////////////////// + result = IDirectSoundBuffer_Restore(soundEffect->SoundBuffers[i]); + if(FAILED(result)) + continue; + + // Fill the sound data back in. + /////////////////////////////// + if(!LoadSound(sound, soundFiles[sound], soundEffect->SoundBuffers[i])) + continue; + } + + // Is it free? + ////////////// + if(!(status & DSBSTATUS_PLAYING)) + break; + } + + // Did we not find anything? + //////////////////////////// + if(i == soundEffect->numDuplicates) + { + // Are we at max duplicates? + //////////////////////////// + if(i == MAX_DUPLICATES) + { + // Just use a random buffer. + //////////////////////////// + i = RandomInt(0, MAX_DUPLICATES - 1); + } + else + { + // Make a new duplicate. + //////////////////////// + result = IDirectSound_DuplicateSoundBuffer( + DirectSound, + soundEffect->SoundBuffers[0], + &soundEffect->SoundBuffers[i]); + if(FAILED(result)) + return; + + // One more duplicate. + ////////////////////// + soundEffect->numDuplicates++; + } + } + + // Make sure the buffer is rewound. + /////////////////////////////////// + IDirectSoundBuffer_SetCurrentPosition(soundEffect->SoundBuffers[i], 0); + + // Play it. + /////////// + result = IDirectSoundBuffer_Play(soundEffect->SoundBuffers[i], 0, 0, 0); + + // Check for a lost buffer. + /////////////////////////// + if(result == DSERR_BUFFERLOST) + { + // Restore the buffer. + ////////////////////// + result = IDirectSoundBuffer_Restore(soundEffect->SoundBuffers[i]); + if(FAILED(result)) + return; + + // Fill the sound data back in. + /////////////////////////////// + if(!LoadSound(sound, soundFiles[sound], soundEffect->SoundBuffers[i])) + return; + + // Try playing again. + ///////////////////// + IDirectSoundBuffer_Play(soundEffect->SoundBuffers[i], 0, 0, 0); + } +} \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/gt2aSound.h b/code/gamespy/gt2/gt2action/gt2aSound.h new file mode 100644 index 00000000..f5d3ca6c --- /dev/null +++ b/code/gamespy/gt2/gt2action/gt2aSound.h @@ -0,0 +1,44 @@ +/* +GameSpy GT2 SDK +GT2Action - sample app +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2000 GameSpy Industries, Inc + +*/ + +#ifndef _GT2ASOUND_H_ +#define _GT2ASOUND_H_ + +enum +{ + SOUND_EXPLOSION, + SOUND_MINE, + SOUND_DIE, + SOUND_ROCKET, + SOUND_PICKUP, + NUM_SOUNDS +}; + +GT2Bool InitializeSound +( + void +); + +void CleanupSound +( + void +); + +void ToggleSound +( + void +); + +void PlaySoundEffect +( + int sound +); + +#endif \ No newline at end of file diff --git a/code/gamespy/gt2/gt2action/images/asteroid0.tga b/code/gamespy/gt2/gt2action/images/asteroid0.tga new file mode 100644 index 00000000..4bb42e95 Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/asteroid0.tga differ diff --git a/code/gamespy/gt2/gt2action/images/asteroid1.tga b/code/gamespy/gt2/gt2action/images/asteroid1.tga new file mode 100644 index 00000000..f9a3dee8 Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/asteroid1.tga differ diff --git a/code/gamespy/gt2/gt2action/images/asteroid2.tga b/code/gamespy/gt2/gt2action/images/asteroid2.tga new file mode 100644 index 00000000..a3164341 Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/asteroid2.tga differ diff --git a/code/gamespy/gt2/gt2action/images/explosion0.tga b/code/gamespy/gt2/gt2action/images/explosion0.tga new file mode 100644 index 00000000..a47afbb6 Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/explosion0.tga differ diff --git a/code/gamespy/gt2/gt2action/images/explosion1.tga b/code/gamespy/gt2/gt2action/images/explosion1.tga new file mode 100644 index 00000000..f857cd2e Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/explosion1.tga differ diff --git a/code/gamespy/gt2/gt2action/images/mine0.tga b/code/gamespy/gt2/gt2action/images/mine0.tga new file mode 100644 index 00000000..62f0fc07 Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/mine0.tga differ diff --git a/code/gamespy/gt2/gt2action/images/mine1.tga b/code/gamespy/gt2/gt2action/images/mine1.tga new file mode 100644 index 00000000..ecde8f6b Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/mine1.tga differ diff --git a/code/gamespy/gt2/gt2action/images/mine2.tga b/code/gamespy/gt2/gt2action/images/mine2.tga new file mode 100644 index 00000000..3469a023 Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/mine2.tga differ diff --git a/code/gamespy/gt2/gt2action/images/rocket0.tga b/code/gamespy/gt2/gt2action/images/rocket0.tga new file mode 100644 index 00000000..3f29b44e Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/rocket0.tga differ diff --git a/code/gamespy/gt2/gt2action/images/rocket1.tga b/code/gamespy/gt2/gt2action/images/rocket1.tga new file mode 100644 index 00000000..0c3841ac Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/rocket1.tga differ diff --git a/code/gamespy/gt2/gt2action/images/rocket2.tga b/code/gamespy/gt2/gt2action/images/rocket2.tga new file mode 100644 index 00000000..835dc2ca Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/rocket2.tga differ diff --git a/code/gamespy/gt2/gt2action/images/rocket3.tga b/code/gamespy/gt2/gt2action/images/rocket3.tga new file mode 100644 index 00000000..65350bbb Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/rocket3.tga differ diff --git a/code/gamespy/gt2/gt2action/images/ship0.tga b/code/gamespy/gt2/gt2action/images/ship0.tga new file mode 100644 index 00000000..f8948dcf Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/ship0.tga differ diff --git a/code/gamespy/gt2/gt2action/images/ship1.tga b/code/gamespy/gt2/gt2action/images/ship1.tga new file mode 100644 index 00000000..7581e356 Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/ship1.tga differ diff --git a/code/gamespy/gt2/gt2action/images/space.tga b/code/gamespy/gt2/gt2action/images/space.tga new file mode 100644 index 00000000..9ae59d86 Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/space.tga differ diff --git a/code/gamespy/gt2/gt2action/images/spinner0.tga b/code/gamespy/gt2/gt2action/images/spinner0.tga new file mode 100644 index 00000000..50018bed Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/spinner0.tga differ diff --git a/code/gamespy/gt2/gt2action/images/spinner1.tga b/code/gamespy/gt2/gt2action/images/spinner1.tga new file mode 100644 index 00000000..424da03a Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/spinner1.tga differ diff --git a/code/gamespy/gt2/gt2action/images/spinner2.tga b/code/gamespy/gt2/gt2action/images/spinner2.tga new file mode 100644 index 00000000..550b672b Binary files /dev/null and b/code/gamespy/gt2/gt2action/images/spinner2.tga differ 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 00000000..68fcdda1 Binary files /dev/null and b/code/gamespy/gt2/gt2action/sounds/die.wav differ diff --git a/code/gamespy/gt2/gt2action/sounds/explosion.wav b/code/gamespy/gt2/gt2action/sounds/explosion.wav new file mode 100644 index 00000000..d36b4a8d Binary files /dev/null and b/code/gamespy/gt2/gt2action/sounds/explosion.wav differ diff --git a/code/gamespy/gt2/gt2action/sounds/mine.wav b/code/gamespy/gt2/gt2action/sounds/mine.wav new file mode 100644 index 00000000..ea0f6645 Binary files /dev/null and b/code/gamespy/gt2/gt2action/sounds/mine.wav differ diff --git a/code/gamespy/gt2/gt2action/sounds/pickup.wav b/code/gamespy/gt2/gt2action/sounds/pickup.wav new file mode 100644 index 00000000..a00a969f Binary files /dev/null and b/code/gamespy/gt2/gt2action/sounds/pickup.wav differ diff --git a/code/gamespy/gt2/gt2action/sounds/rocket.wav b/code/gamespy/gt2/gt2action/sounds/rocket.wav new file mode 100644 index 00000000..dd3102fd Binary files /dev/null and b/code/gamespy/gt2/gt2action/sounds/rocket.wav differ 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 00000000..d0a3dfe3 Binary files /dev/null and b/code/gamespy/pt/fpupdate/fpupdate.exe differ 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 00000000..6d108625 Binary files /dev/null and b/code/gamespy/qr2/querytest.exe differ 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 00000000..7eef0bcb Binary files /dev/null and b/code/gamespy/sc/scRaceSample/res/ScRaceSample.ico differ diff --git a/code/gamespy/sc/scRaceSample/res/ScRaceSample.rc2 b/code/gamespy/sc/scRaceSample/res/ScRaceSample.rc2 new file mode 100644 index 00000000..746d0bdb --- /dev/null +++ b/code/gamespy/sc/scRaceSample/res/ScRaceSample.rc2 @@ -0,0 +1,13 @@ +// +// SCRACESAMPLE.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/sc/scRaceSample/resource.h b/code/gamespy/sc/scRaceSample/resource.h new file mode 100644 index 00000000..5c15d825 --- /dev/null +++ b/code/gamespy/sc/scRaceSample/resource.h @@ -0,0 +1,34 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by ScRaceSample.rc +// +#define IDD_SCRACESAMPLE_DIALOG 102 +#define IDC_EMAIL 103 +#define IDC_NICK 104 +#define IDC_PASSWORD 105 +#define IDR_MAINFRAME 128 +#define IDD_LOGIN 129 +#define IDD_HOST_OR_JOIN 130 +#define IDD_WAITING 132 +#define IDC_HOST 1000 +#define IDC_LOCAL_POSITION 1000 +#define IDC_JOIN 1001 +#define IDC_REMOTE_POSITION 1001 +#define IDC_JOIN_ADDRESS 1002 +#define IDC_LOGOUT 1004 +#define IDC_START_RACE 1005 +#define IDC_INFO 1006 +#define IDC_LOCAL_PROGRESS 1007 +#define IDC_REMOTE_PROGRESS 1008 +#define IDC_UPDATE_POSITIONS 1009 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/gamespy/sc/sci.h b/code/gamespy/sc/sci.h new file mode 100644 index 00000000..db81d9d0 --- /dev/null +++ b/code/gamespy/sc/sci.h @@ -0,0 +1,34 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __SCI_H__ +#define __SCI_H__ + +/* Internal interface for S&C SDK -- Developers should use sc.h */ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sc.h" + + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Data types + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __SCI_H__ diff --git a/code/gamespy/sc/sciInterface.c b/code/gamespy/sc/sciInterface.c new file mode 100644 index 00000000..1bf05252 --- /dev/null +++ b/code/gamespy/sc/sciInterface.c @@ -0,0 +1,132 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sciInterface.h" + + +/* PRIVATE INTERFACE FUNCTIONS -- SCMAIN.C CONTAINS PUBLIC INTERFACE */ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +// This is declared as an extern so it can be overriden when testing +#define SC_SERVICE_URL_FORMAT "http://%s.comp.pubsvs." GSI_DOMAIN_NAME "/CompetitionService/CompetitionService.asmx" +char scServiceURL[SC_SERVICE_MAX_URL_LEN] = ""; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciInterfaceCreate(SCInterface** theInterfaceOut) +{ +#ifdef GSI_SC_STATIC_MEM + static SCInterface gStaticInterface; +#endif + + GS_ASSERT(theInterfaceOut != NULL); + + // Check to see if the availability check has been performed and if it has + // set the service URL prepended with the gamename + if (__GSIACResult == GSIACAvailable) + { + if (scServiceURL[0] == '\0') + snprintf(scServiceURL, SC_SERVICE_MAX_URL_LEN, SC_SERVICE_URL_FORMAT, __GSIACGamename); + } + else + return SCResult_NO_AVAILABILITY_CHECK; + +#ifdef GSI_SC_STATIC_MEM + *theInterfaceOut = &gStaticInterface; +#else + *theInterfaceOut = (SCInterface*)gsimalloc(sizeof(SCInterface)); + if (*theInterfaceOut == NULL) + { + return SCResult_OUT_OF_MEMORY; + } +#endif + + GS_ASSERT(*theInterfaceOut != NULL); + memset(*theInterfaceOut, 0, sizeof(SCInterface)); + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciInterfaceInit(SCInterface* theInterface) +{ + SCResult anInitResult = SCResult_NO_ERROR; + + GS_ASSERT(theInterface != NULL); + + anInitResult = sciWsInit(&theInterface->mWebServices, theInterface); + if (anInitResult != SCResult_NO_ERROR) + { + return anInitResult; + } + + memset(theInterface->mSessionId, 0, sizeof(theInterface->mSessionId)); + memset(theInterface->mConnectionId, 0, sizeof(theInterface->mConnectionId)); + + theInterface->mInit = gsi_true; + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void sciInterfaceDestroy(SCInterface* theInterface) +{ + GS_ASSERT(theInterface != NULL); + GS_ASSERT(theInterface->mInit); + + sciWsDestroy(&theInterface->mWebServices); + + memset(theInterface->mSessionId, 0, sizeof(theInterface->mSessionId)); + memset(theInterface->mConnectionId, 0, sizeof(theInterface->mConnectionId)); + theInterface->mSessionId[0] = 0xde; + theInterface->mSessionId[1] = 0xad; + theInterface->mConnectionId[0] = 0xde; + theInterface->mConnectionId[1] = 0xad; + + theInterface->mInit = gsi_false; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void sciInterfaceSetSessionId(SCInterface * theInterface, const char * theSessionId) +{ + GS_ASSERT(theInterface != NULL); + + if (theSessionId == NULL) + theInterface->mSessionId[0] = '\0'; + else + { + GS_ASSERT(strlen(theSessionId) < sizeof(theInterface->mSessionId)); + strcpy((char *)theInterface->mSessionId, theSessionId); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void sciInterfaceSetConnectionId(SCInterface * theInterface, const char * theConnectionId) +{ + GS_ASSERT(theInterface != NULL); + + if (theConnectionId == NULL) + theInterface->mConnectionId[0] = '\0'; + else + { + GS_ASSERT(strlen(theConnectionId) < sizeof(theInterface->mConnectionId)); + strcpy((char *)theInterface->mConnectionId, theConnectionId); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/sc/sciInterface.h b/code/gamespy/sc/sciInterface.h new file mode 100644 index 00000000..eb2562e3 --- /dev/null +++ b/code/gamespy/sc/sciInterface.h @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __SCIINTERFACE_H__ +#define __SCIINTERFACE_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sci.h" +#include "sciWebServices.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// SDK instance +typedef struct +{ + SCWebServices mWebServices; + gsi_u32 mGameId; + //gsi_u32 mOptionsFlags; + gsi_bool mInit; + const char * mServiceURL; + GSCoreMgr * mSdkCore; + gsi_u8 mSessionId[SC_SESSION_GUID_SIZE]; + gsi_u8 mConnectionId[SC_CONNECTION_GUID_SIZE]; +} SCInterface; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciInterfaceCreate (SCInterface** theInterfaceOut); +SCResult sciInterfaceInit (SCInterface* theInterface); +void sciInterfaceDestroy(SCInterface* theInterface); + +void sciInterfaceSetSessionId(SCInterface* theInterface, const char * theSessionId); +void sciInterfaceSetConnectionId(SCInterface* theInterface, const char * theConnectionId); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __SCINTERFACE_H__ diff --git a/code/gamespy/sc/sciMain.c b/code/gamespy/sc/sciMain.c new file mode 100644 index 00000000..19ec53ed --- /dev/null +++ b/code/gamespy/sc/sciMain.c @@ -0,0 +1,706 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sci.h" +#include "sciInterface.h" +#include "sciReport.h" + +#include "../md5.h" +#include "../common/gsRC4.h" + +/* PUBLIC INTERFACE FUNCTIONS - SCINTERFACE.C CONTAINS PRIVATE INTERFACE */ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scInitialize(int theGameId, SCInterfacePtr* theInterfaceOut) +{ + SCInterface* anInterface = NULL; + SCResult anInitResult = SCResult_NO_ERROR; + + // Check parameters + if (theInterfaceOut == NULL) + { + return SCResult_INVALID_PARAMETERS; + } + + // Clear out parameter + *theInterfaceOut = NULL; + + // Obtain the internal interface + anInitResult = sciInterfaceCreate(&anInterface); + if (anInitResult != SCResult_NO_ERROR) + { + return anInitResult; + } + + // Init the internal interface + anInitResult = sciInterfaceInit(anInterface); + if (anInitResult != SCResult_NO_ERROR) + { + return anInitResult; + } + + anInterface->mGameId = (gsi_u32)theGameId; + //anInterface->mOptionsFlags = (gsi_u32)theOptionsFlags; + + // Set the out parameter and return + *theInterfaceOut = anInterface; + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scShutdown(SCInterfacePtr theInterface) +{ + SCInterface* anInterface = (SCInterface*)theInterface; + + // Check parameters + if (anInterface == NULL) + { + return SCResult_INVALID_PARAMETERS; + } + + // Destroy interface if necessary + if (anInterface->mInit) + { + sciInterfaceDestroy(anInterface); + gsifree(anInterface); + } + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scThink(SCInterfacePtr theInterface) +{ + SCInterface* anInterface = (SCInterface*)theInterface; + + // Check parameters + if (anInterface == NULL) + { + return SCResult_INVALID_PARAMETERS; + } + if (!anInterface->mInit) + { + return SCResult_NOT_INITIALIZED; + } + + sciWsThink(&anInterface->mWebServices); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +const char * scGetSessionId(const SCInterfacePtr theInterface) +{ + SCInterface * anInterface = (SCInterface*)theInterface; + GS_ASSERT(theInterface != NULL); + GS_ASSERT(gsi_is_true(anInterface->mInit)); + return (char *)anInterface->mSessionId; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +const char * scGetConnectionId(const SCInterfacePtr theInterface) +{ + SCInterface * anInterface = (SCInterface*)theInterface; + GS_ASSERT(theInterface != NULL); + GS_ASSERT(gsi_is_true(anInterface->mInit)); + return (char *)anInterface->mConnectionId; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scSetSessionId(const SCInterfacePtr theInterface, const gsi_u8 theSessionId[SC_SESSION_GUID_SIZE]) +{ + SCInterface * anInterface = (SCInterface*)theInterface; + GS_ASSERT(theInterface != NULL); + GS_ASSERT(gsi_is_true(anInterface->mInit)); + + sciInterfaceSetSessionId(anInterface, (char *)theSessionId); + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// The certificate and private data may be NULL if the local client +// is an unauthenticated dedicated server +SCResult SC_CALL scCreateSession(SCInterfacePtr theInterface, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCCreateSessionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData) +{ + SCInterface * anInterface = (SCInterface*)theInterface; + GS_ASSERT(theInterface != NULL); + GS_ASSERT(theCertificate != NULL || (theCertificate==NULL && thePrivateData==NULL)); + + if (!wsLoginCertIsValid(theCertificate)) + return SCResult_INVALID_PARAMETERS; + + return sciWsCreateSession(&anInterface->mWebServices, anInterface->mGameId, theCertificate, thePrivateData, theCallback, theTimeoutMs, theUserData); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scCreateMatchlessSession(SCInterfacePtr theInterface, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCCreateSessionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData) +{ + SCInterface * anInterface = (SCInterface*)theInterface; + GS_ASSERT(theInterface != NULL); + GS_ASSERT(theCertificate != NULL || (theCertificate==NULL && thePrivateData==NULL)); + + if (!wsLoginCertIsValid(theCertificate)) + return SCResult_INVALID_PARAMETERS; + + return sciWsCreateMatchlessSession(&anInterface->mWebServices, anInterface->mGameId, theCertificate, thePrivateData, theCallback, theTimeoutMs, theUserData); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/* +SCResult SC_CALL scJoinSession(SCInterfacePtr theInterface, + GSLoginCertificate * theCertificate, + GSLoginPrivateData * thePrivateData, + gsi_u8 theSessionId[SC_SESSION_GUID_SIZE], + SCSetReportIntentionCallback theCallback, + gsi_time theTimeoutMs) +{ + SCInterface * anInterface = (SCInterface*)theInterface; + GS_ASSERT(theInterface != NULL); + + memcpy(anInterface->mSessionId, theSessionId, SC_SESSION_GUID_SIZE); + return SCResult_NO_ERROR; +}*/ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scSetReportIntention(const SCInterfacePtr theInterface, + const gsi_u8 theConnectionId[SC_CONNECTION_GUID_SIZE], + gsi_bool isAuthoritative, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCSetReportIntentionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData) +{ + SCInterface* anInterface = (SCInterface*)theInterface; + + // Check parameters + if (anInterface == NULL) + { + return SCResult_INVALID_PARAMETERS; + } + if (!theCertificate) + { + return SCResult_INVALID_PARAMETERS; + } + if (!thePrivateData) + { + return SCResult_INVALID_PARAMETERS; + } + if (!anInterface->mInit) + { + return SCResult_NOT_INITIALIZED; + } + + sciInterfaceSetConnectionId(anInterface, (const char *)theConnectionId); + // Call web service + return sciWsSetReportIntention(&anInterface->mWebServices, anInterface->mGameId, + (char *)anInterface->mSessionId, (char *)anInterface->mConnectionId, isAuthoritative, + theCertificate, thePrivateData, theCallback, theTimeoutMs, theUserData); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scSubmitReport(const SCInterfacePtr theInterface, + const SCReportPtr theReport, + gsi_bool isAuthoritative, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCSubmitReportCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData) +{ + SCInterface* anInterface = (SCInterface*)theInterface; + SCIReport *aReport = (SCIReport *)theReport; + //SCResult aResult = SCResult_NO_ERROR; + + // Check parameters + if (anInterface == NULL) + { + return SCResult_INVALID_PARAMETERS; + } + if (!anInterface->mInit) + { + return SCResult_NOT_INITIALIZED; + } + + // Generate report from report data + // aResult = sciGenerateReport(theReportData, theProfileID, &aReport); + //if (aResult != SCResult_NO_ERROR) + // return aResult; + + // Prepare the report hash + { + SCIReportHeader * header = (SCIReportHeader*)aReport->mBuffer.mData; + MD5_CTX md5; + // Clear out the checksum portion of the header so that the + // MD5 hash is calculated on the entire report without a checksum + memset(header->mChecksum, 0, sizeof(header->mChecksum)); + + MD5Init(&md5); + MD5Update(&md5, (unsigned char *)aReport->mBuffer.mData, aReport->mBuffer.mPos); + MD5Final(header->mChecksum, &md5); + } + + // Call web service + return sciWsSubmitReport(&anInterface->mWebServices, anInterface->mGameId, + (char *)anInterface->mSessionId, (char *)anInterface->mConnectionId, aReport, isAuthoritative, + theCertificate, thePrivateData, theCallback, theTimeoutMs, theUserData); + +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scCreateReport(const SCInterfacePtr theInterface, + gsi_u32 theHeaderVersion, + gsi_u32 thePlayerCount, + gsi_u32 theTeamCount, + SCReportPtr * theReportDataOut) +{ + SCResult aResult = SCResult_NO_ERROR; + SCIReport * aReport = NULL; + SCInterface * aInterface = (SCInterface*)theInterface; + + GS_ASSERT(theInterface != NULL); + GS_ASSERT(theReportDataOut != NULL); + if (theInterface == NULL) + return SCResult_INVALID_PARAMETERS; + if (theReportDataOut == NULL) + return SCResult_INVALID_PARAMETERS; + + aResult = sciCreateReport(aInterface->mSessionId, theHeaderVersion, + thePlayerCount, theTeamCount, &aReport); + if (aResult == SCResult_NO_ERROR) + *theReportDataOut = (SCReportPtr)aReport; + + return aResult; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportBeginGlobalData(SCReportPtr theReport) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + + return sciReportBeginGlobalData(aReport); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportBeginPlayerData(SCReportPtr theReport) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + + return sciReportBeginPlayerData(aReport); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportBeginTeamData(SCReportPtr theReport) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + + return sciReportBeginTeamData(aReport); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportBeginNewTeam(SCReportPtr theReport) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + //return SCResult_NO_ERROR; + return sciReportBeginNewTeam(aReport); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportBeginNewPlayer(SCReportPtr theReport) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + //return SCResult_NO_ERROR; + return sciReportBeginNewPlayer(aReport); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportEnd(SCReportPtr theReport, gsi_bool isAuth, SCGameStatus theStatus) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + + return sciReportEnd(aReport, isAuth, theStatus); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportSetPlayerData(SCReportPtr theReport, + gsi_u32 thePlayerIndex, + const gsi_u8 thePlayerConnectionId[SC_CONNECTION_GUID_SIZE], + gsi_u32 thePlayerTeamId, + SCGameResult theResult, + gsi_u32 theProfileId, + const GSLoginCertificate * theCertificate, + const gsi_u8 theAuthHash[16]) +{ + SCIReport * aReport = NULL; + SCResult aResult = SCResult_NO_ERROR; + gsi_u32 i; + gsi_bool isNewTeam = gsi_true; + + GS_ASSERT(theReport != NULL); + GS_ASSERT(theCertificate != NULL); + GSI_UNUSED(theProfileId); + aReport = (SCIReport*)theReport; + + // store the team ID here and use this in order to index the team game results + for (i=0; imNumTeamsReported; i++) + { + // has the team already been reported by another player? + if (aReport->mTeamIds[i] == thePlayerTeamId) + { + isNewTeam = gsi_false; + break; + } + } + + if (isNewTeam) + { + aReport->mNumTeamsReported++; + + // have they gone over the limit of teams? If so, this is an error + GS_ASSERT(aReport->mNumTeamsReported < SC_MAX_NUM_TEAMS); + if (aReport->mNumTeamsReported > SC_MAX_NUM_TEAMS) + return SCResult_OUT_OF_MEMORY; + + // if no problems, store the teamId + aReport->mTeamIds[aReport->mNumTeamsReported-1] = thePlayerTeamId; + } + + + if (aResult == SCResult_NO_ERROR) aResult = sciReportSetPlayerConnectionId(aReport, thePlayerIndex, thePlayerConnectionId); + if (aResult == SCResult_NO_ERROR) aResult = sciReportSetPlayerTeamIndex (aReport, thePlayerIndex, thePlayerTeamId); + if (aResult == SCResult_NO_ERROR) aResult = sciReportSetPlayerGameResult (aReport, thePlayerIndex, theResult); + if (aResult == SCResult_NO_ERROR) aResult = sciReportSetPlayerAuthInfo (aReport, thePlayerIndex, theCertificate, theAuthHash); + + return aResult; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportSetTeamData(const SCReportPtr theReport, + gsi_u32 theTeamId, + SCGameResult theResult) +{ + SCIReport * aReport = NULL; + gsi_u32 i; + gsi_i32 aTeamIndex = -1; + GS_ASSERT(theReport != NULL); + GS_ASSERT(theResult < SCGameResultMax); + + aReport = (SCIReport*)theReport; + + // redundancy check - have they gone over the max limit of teams? + GS_ASSERT(aReport->mNumTeamsReported < SC_MAX_NUM_TEAMS); + + // find the proper team index based on the teamId + for (i=0; imNumTeamsReported; i++) + { + if (aReport->mTeamIds[i] == theTeamId) + { + aTeamIndex = (gsi_i32)i; + break; + } + } + + // if no such team exists in our list, an invalid ID was given + if (aTeamIndex == -1) + return SCResult_INVALID_PARAMETERS; + + // the teamindex reported here needs to be 0 based, which is why we subtract 1 + return sciReportSetTeamGameResult(aReport, (gsi_u32)aTeamIndex, theResult); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportSetAsMatchless(const SCReportPtr theReport) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + return(sciReportSetAsMatchless(aReport)); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportAddIntValue(const SCReportPtr theReport, + gsi_u16 theKeyId, + gsi_i32 theValue) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + return sciReportAddIntValue(aReport, theKeyId, theValue); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportAddInt64Value(SCReportPtr theReport, + gsi_u16 theKeyId, + gsi_i64 theValue) +{ + SCIReport *aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport *)theReport; + return sciReportAddInt64Value(aReport, theKeyId, theValue); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportAddShortValue(const SCReportPtr theReport, + gsi_u16 theKeyId, + gsi_i16 theValue) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + return sciReportAddShortValue(aReport, theKeyId, theValue); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportAddByteValue(const SCReportPtr theReport, + gsi_u16 theKeyId, + gsi_i8 theValue) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + return sciReportAddByteValue(aReport, theKeyId, theValue); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportAddFloatValue(const SCReportPtr theReport, + gsi_u16 theKeyId, + float theValue) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + return sciReportAddFloatValue(aReport, theKeyId, theValue); + +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scReportAddStringValue(const SCReportPtr theReport, + gsi_u16 theKeyId, + const gsi_char * theValue) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + GS_ASSERT(theValue != NULL); + aReport = (SCIReport*)theReport; + return sciReportAddStringValue(aReport, theKeyId, theValue); + +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scDestroyReport(SCReportPtr theReport) +{ + SCIReport * aReport = NULL; + GS_ASSERT(theReport != NULL); + aReport = (SCIReport*)theReport; + + return sciDestroyReport(aReport); +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Create a random key for this cipher +SCResult SC_CALL scPeerCipherInit(const GSLoginCertificate * theLocalCert, SCPeerCipher * theCipher) +{ + // hardcoded to a 256-bit key block, although some ciphers will not use the entire block + + // What to use for a random seed? + // 1) 16 bytes from our Util_Rand seeded with current time + // 2) Hashed with 16 bytes from the cert public key (for a little extra randomness) + // 3) Repeat 1-2 for every 16 bytes of key block needed + unsigned char randBytes[32]; + int i=0; + MD5_CTX md5; + + GS_ASSERT(theCipher != NULL); + //CANNOT assert on constant expressions, they are meaningless. + //GS_ASSERT(GS_CRYPT_RSA_BYTE_SIZE >= 32); // crypt lib must support 128-bit keys. + + #if defined (GS_CRYPT_NO_RANDOM) + Util_RandSeed(0x2d2d2d2d); + #else + Util_RandSeed(current_time()); + #endif + + for (i=0; i < 32; i++) + randBytes[i] = (unsigned char)Util_RandInt(0x00, 0xFF); + + // Calc the first half, bytes 0-15 + MD5Init(&md5); + MD5Update(&md5, randBytes, 16); + MD5Update(&md5, (unsigned char*)theLocalCert->mPeerPublicKey.modulus.mData, 16); // first 16 bytes of the key + MD5Final(&theCipher->mKey[0], &md5); + + // Calc the second half, bytes 16-31 + MD5Init(&md5); + MD5Update(&md5, &randBytes[16], 16); + MD5Update(&md5, (unsigned char*)&theLocalCert->mPeerPublicKey.modulus.mData[16], 16); // last 16 bytes of the key + MD5Final(&theCipher->mKey[16], &md5); + + theCipher->mKeyLen = 32; + + RC4Init(&theCipher->mRC4, theCipher->mKey, (int)theCipher->mKeyLen); + theCipher->mInitialized = gsi_true; + + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Misc, GSIDebugLevel_Notice, "Created PeerCipher key block\r\n"); + gsDebugBinary(GSIDebugCat_App, GSIDebugType_Misc, GSIDebugLevel_Notice, (char *)theCipher->mKey, (gsi_i32)theCipher->mKeyLen); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scPeerCipherCreateKeyExchangeMsg(const GSLoginCertificate * theRemoteCert, const SCPeerCipher * theCipher, SCPeerKeyExchangeMsg theMsgOut) +{ + // Encrypt the key with the recipients public key, this makes it safe to transmit + if (0 == gsCryptRSAEncryptBuffer(&theRemoteCert->mPeerPublicKey, theCipher->mKey, theCipher->mKeyLen, (unsigned char *)theMsgOut)) + return SCResult_NO_ERROR; + else + return SCResult_UNKNOWN_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scPeerCipherParseKeyExchangeMsg (const GSLoginCertificate * theLocalCert, const GSLoginPrivateData * theCertPrivateData, const SCPeerKeyExchangeMsg theMsg, SCPeerCipher * theCipherOut) +{ + GSI_UNUSED(theLocalCert); + // Decrypt the key with the local player's private key + if (0 == gsCryptRSADecryptBuffer(&theCertPrivateData->mPeerPrivateKey, (unsigned char *)theMsg, theCipherOut->mKey, &theCipherOut->mKeyLen)) + { + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Misc, GSIDebugLevel_Notice, "Decrypted PeerCipher key block\r\n"); + gsDebugBinary(GSIDebugCat_App, GSIDebugType_Misc, GSIDebugLevel_Notice, (char *)theCipherOut->mKey, (gsi_i32)theCipherOut->mKeyLen); + RC4Init(&theCipherOut->mRC4, theCipherOut->mKey, (int)theCipherOut->mKeyLen); + theCipherOut->mInitialized = gsi_true; + return SCResult_NO_ERROR; + } + else + return SCResult_UNKNOWN_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scPeerCipherEncryptBufferIV(SCPeerCipher * theCipher, gsi_u32 theMessageNum, gsi_u8 * theData, gsi_u32 theLen) +{ + RC4Context rc4; + MD5_CTX md5; + char tempHash[16]; + + GS_ASSERT(gsi_is_true(theCipher->mInitialized)); + + // Construct a new key for this message + // - Using the same key for all messages would be extremely unsafe + MD5Init(&md5); + MD5Update(&md5, theCipher->mKey, theCipher->mKeyLen); + MD5Update(&md5, (unsigned char*)&theMessageNum, sizeof(theMessageNum)); + MD5Final((unsigned char *)tempHash, &md5); + + RC4Init(&rc4, (unsigned char *)tempHash, GS_CRYPT_MD5_HASHSIZE); + RC4Encrypt(&rc4, (const unsigned char*)theData, theData, (int)theLen); + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scPeerCipherDecryptBufferIV(SCPeerCipher * theCipher, gsi_u32 theMessageNum, gsi_u8 * theData, gsi_u32 theLen) +{ + return scPeerCipherEncryptBufferIV(theCipher, theMessageNum, theData, theLen); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scPeerCipherEncryptBuffer(SCPeerCipher * theCipher, gsi_u8 * theData, gsi_u32 theLen) +{ + // Encrypt the data using the RC4 context + GS_ASSERT(gsi_is_true(theCipher->mInitialized)); + RC4Encrypt(&theCipher->mRC4, (const unsigned char*)theData, theData, (int)theLen); + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL scPeerCipherDecryptBuffer(SCPeerCipher * theCipher, gsi_u8 * theData, gsi_u32 theLen) +{ + return scPeerCipherEncryptBuffer(theCipher, theData, theLen); +} diff --git a/code/gamespy/sc/sciReport.c b/code/gamespy/sc/sciReport.c new file mode 100644 index 00000000..6c63fca9 --- /dev/null +++ b/code/gamespy/sc/sciReport.c @@ -0,0 +1,874 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sciReport.h" +#include "sciSerialize.h" +#include "../md5.h" + +#pragma warning(disable: 4267) + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciCreateReport(gsi_u8 theSessionGuid[SC_SESSION_GUID_SIZE], + gsi_u32 theHeaderVersion, + gsi_u32 thePlayerCount, + gsi_u32 theTeamCount, + SCIReport ** theReportOut) +{ + SCIReport * theNewReport; + SCIReportHeader * theReportHeader; + gsi_u8 * theReportData; + + // roster is [CCID (16) + TeamIndex (4)] * numplayers + const gsi_u32 theRosterSize = SC_GUID_BINARY_SIZE * thePlayerCount + + SC_REPORT_TEAMINDEX_LENGTH * thePlayerCount; + + GS_ASSERT(theReportOut != NULL); + + // allocate the report + //GS_ASSERT(0); // todo: memalignment + theNewReport = (SCIReport*)gsimalloc(sizeof(SCIReport)); + if (theNewReport == NULL) + return SCResult_OUT_OF_MEMORY; + memset(theNewReport, 0, sizeof(SCIReport)); + + // allocate the report buffer (holds submission data) + theReportData = (gsi_u8*)gsimalloc(SC_REPORT_BUFFER_BYTES); + if (theReportData == NULL) + { + gsifree(theNewReport); + return SCResult_OUT_OF_MEMORY; + } + memset(theReportData, 0, SC_REPORT_BUFFER_BYTES); + theNewReport->mBuffer.mIsStatic = gsi_false; + theNewReport->mBuffer.mCapacity = SC_REPORT_BUFFER_BYTES; + + // Fill in report header + theReportHeader = (SCIReportHeader*)theReportData; + memset(theReportHeader, 0, sizeof(SCIReportHeader)); + + theReportHeader->mProtocolVersion = htonl(SC_REPORT_PROTOCOL); + theReportHeader->mDeveloperVersion = htonl(theHeaderVersion); + + theReportHeader->mRosterSectionLength = theRosterSize; + theReportHeader->mAuthSectionLength = SC_REPORT_AUTHDATA_LENGTH * thePlayerCount; + theReportHeader->mResultsSectionLength = SC_REPORT_ENTITYRESULT_LENGTH * (thePlayerCount + theTeamCount); + theReportHeader->mPlayerCount = (gsi_u16)thePlayerCount; + theReportHeader->mTeamCount = (gsi_u16)theTeamCount; + //theReportHeader->mFlags = (gsi_u32)theOptionsFlags; + + // Finished, return new report + theNewReport->mReportState = SCIReportState_ROSTER; + theNewReport->mBuffer.mData = (char *)theReportData; + theNewReport->mCurEntityStartPos = -1; + theNewReport->mNumTeamsReported = 0; + + *theReportOut = theNewReport; + GSI_UNUSED(theSessionGuid); + return SCResult_NO_ERROR; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +SCResult sciDestroyReport(SCIReport *theReport) +{ + theReport->mReportState = SCIReportState_NONE; + theReport->mCurEntityKeyCount = 0; + theReport->mCurEntityStartPos = 0; +// theReport->mNumPlayersReported = 0; + theReport->mNumTeamsReported = 0; + theReport->mNumResultsReported = 0; + + gsifree(theReport->mBuffer.mData); + theReport->mBuffer.mData = NULL; + theReport->mBuffer.mCapacity = 0; + theReport->mBuffer.mLen = 0; + theReport->mBuffer.mPos = 0; + gsifree(theReport); + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportSetPlayerConnectionId(SCIReport * theReport, gsi_u32 thePlayerIndex, const gsi_u8 theConnectionId[SC_CONNECTION_GUID_SIZE]) +{ + gsi_u32 rosterDataOffset = 0; + + // Roster section is just after the header + rosterDataOffset = sizeof(SCIReportHeader); + rosterDataOffset += SC_REPORT_ROSTERDATA_LENGTH * thePlayerIndex; + + // copy the connection id + GS_ASSERT((rosterDataOffset + SC_GUID_BINARY_SIZE) < theReport->mBuffer.mCapacity); + + sciSerializeGUID((gsi_u8 *)&theReport->mBuffer.mData[rosterDataOffset], theConnectionId); + //memcpy(&theReport->mBuffer.mData[rosterDataOffset], theConnectionId, SC_CONNECTION_GUID_SIZE); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportSetPlayerTeamIndex(SCIReport * theReport, gsi_u32 thePlayerIndex, gsi_u32 theTeamIndex) +{ + //SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + gsi_u32 rosterDataOffset = 0; + + // Roster section is just after the header + rosterDataOffset = sizeof(SCIReportHeader); + rosterDataOffset += SC_REPORT_ROSTERDATA_LENGTH * thePlayerIndex; + + // copy the team index, which must appear just after the connection id + rosterDataOffset += SC_GUID_BINARY_SIZE; + GS_ASSERT((rosterDataOffset + sizeof(gsi_i32)) < theReport->mBuffer.mCapacity); + sciSerializeInt32((gsi_u8 *)&theReport->mBuffer.mData[rosterDataOffset], (gsi_i32)theTeamIndex); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportSetPlayerGameResult(SCIReport * theReport, gsi_u32 thePlayerIndex, SCGameResult theGameResult) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + gsi_u32 resultsDataOffset = 0; + + // Results section is just after the auth section + resultsDataOffset = sizeof(SCIReportHeader) + aHeader->mRosterSectionLength + aHeader->mAuthSectionLength; + resultsDataOffset += sizeof(gsi_u32) * thePlayerIndex; // 4 byte game result per player + + // copy the game result in network byte order + GS_ASSERT((resultsDataOffset + sizeof(gsi_u32)) < theReport->mBuffer.mCapacity); + sciSerializeInt32((gsi_u8 *)&theReport->mBuffer.mData[resultsDataOffset], theGameResult); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportSetPlayerAuthInfo(SCIReport * theReport, gsi_u32 thePlayerIndex, const GSLoginCertificate * theCertificate, const gsi_u8 theAuthHash[16]) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + gsi_u32 authDataOffset = 0; + + // Auth section is just after the roster + authDataOffset = sizeof(SCIReportHeader) + aHeader->mRosterSectionLength; + authDataOffset += SC_REPORT_AUTHDATA_LENGTH * thePlayerIndex; + + // copy auth data into the buffer + // &theReport->mBuffer.mData[authDataOffset] + GSI_UNUSED(theAuthHash); + GSI_UNUSED(theCertificate); + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportSetTeamGameResult(SCIReport * theReport, gsi_u32 theTeamIndex, SCGameResult theGameResult) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + gsi_u32 resultsDataOffset = 0; + + // Results section is just after the auth section + // team results are just after player results + resultsDataOffset = sizeof(SCIReportHeader) + aHeader->mRosterSectionLength + aHeader->mAuthSectionLength; + resultsDataOffset += sizeof(gsi_u32) * aHeader->mPlayerCount; // 4 byte game result per player + resultsDataOffset += sizeof(gsi_u32) * theTeamIndex; // 4 byte game result per player + + // copy the game result in network byte order + GS_ASSERT((resultsDataOffset + sizeof(gsi_i32)) < theReport->mBuffer.mCapacity); + sciSerializeInt32((gsi_u8 *)&theReport->mBuffer.mData[resultsDataOffset], theGameResult); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportSetAsMatchless(SCIReport * theReport) +{ + SCIReportHeader * aHeader; + + aHeader = (SCIReportHeader *)theReport->mBuffer.mData; + + aHeader->mFlags |= SC_REPORT_FLAG_MATCHLESS_SESSION; + + return SCResult_NO_ERROR; +} + +/* -----------UNUSED------------ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportPlayerAuth(SCIReport * theReport, + gsi_u32 thePlayerIndex, + gsi_u8 thePlayerConnectionId[SC_CONNECTION_GUID_SIZE], + gsi_u32 thePlayerTeamIndex, + gsi_u32 theProfileId, + GSLoginCertificate * theCertificate, + gsi_u8 theAuthHash[16]) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + //unsigned int len = 0; + + gsi_u32 rosterDataOffset = 0; + gsi_u32 authDataOffset = 0; + + //gsi_u8 * rosterWritePos = NULL; + + // Roster section is just after the header + rosterDataOffset = sizeof(SCIReportHeader); + + // Auth section is just after the roster + authDataOffset = sizeof(SCIReportHeader) + aHeader->mRosterSectionLength; + + // Check size and length + GS_ASSERT((authDataOffset + aHeader->mAuthSectionLength) < SC_REPORT_BUFFER_BYTES); + + // Record player index and team in roster section + // The report contains an array of players, so we offset the write position + // based on the number of players previously reported + rosterDataOffset += aHeader->mPlayerCount * (SC_GUID_BINARY_SIZE + SC_REPORT_ENTITYINDEX_LENGTH); + + // write the connection id GUID + GS_ASSERT(rosterDataOffset < authDataOffset); + sciSerializeGUID((gsi_u8 *)&theReport->mBuffer.mData[rosterDataOffset], thePlayerConnectionId); + rosterDataOffset += SC_GUID_BINARY_SIZE; + + +// GS_ASSERT(rosterDataOffset < authDataOffset); +// memcpy(&theReport->mBuffer.mData[rosterDataOffset], thePlayerConnectionId, SC_GUID_BINARY_SIZE); +// rosterDataOffset += SC_CONNECTION_GUID_SIZE; + + + GS_ASSERT(rosterDataOffset < authDataOffset); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[rosterDataOffset], (gsi_i16)thePlayerTeamIndex); + rosterDataOffset += 2; // we just wrote an Int16, so bump by two. + + GS_ASSERT(rosterDataOffset < authDataOffset); // check overrun + + // Write auth data for this player + // The report contains an array of auth data, so we offset the write position + // based on the number of players previously reported + authDataOffset += aHeader->mPlayerCount * (SC_GUID_BINARY_SIZE + SC_REPORT_ENTITYINDEX_LENGTH); + + GS_ASSERT((authDataOffset + SC_REPORT_AUTHDATA_LENGTH) < theReport->mBuffer.mCapacity); // check buffer space + + // Write auth data + //result = wsLoginCertWriteBinary(theCertificate, &theReport->mBuffer.mData[theReport->mBuffer.mPos], (SC_REPORT_BUFFER_BYTES - theReport->mBuffer.mPos), &len); + //if (gsi_is_false(result)) + // return SCResult_OUT_OF_MEMORY; + //memcpy(&theReport->mBuffer.mData[theReport->mBuffer.mPos], theAuthHash, 16); + //theReport->mBuffer.mPos += 16; + + //theReport->mNumPlayersReported++; // remember how many player's we've written data for + GSI_UNUSED(theAuthHash); + GSI_UNUSED(theCertificate); + GSI_UNUSED(theProfileId); + GSI_UNUSED(thePlayerIndex); + return SCResult_NO_ERROR; +} + +// -----------UNUSED------------ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportPlayerResult(SCIReport * theReport, + gsi_u32 thePlayerIndex, + SCGameResult theResult) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + //unsigned int len = 0; + gsi_u32 resultsOffset = 0; + + resultsOffset = sizeof(SCIReportHeader) + aHeader->mRosterSectionLength + aHeader->mAuthSectionLength; + GS_ASSERT(resultsOffset < SC_REPORT_BUFFER_BYTES); + + resultsOffset += SC_REPORT_ENTITYRESULT_LENGTH * theReport->mNumResultsReported; + sciSerializeInt32((gsi_u8 *)&theReport->mBuffer.mData[resultsOffset], theResult); + + theReport->mNumResultsReported++; + GSI_UNUSED(thePlayerIndex); + return SCResult_NO_ERROR; +} + +// -----------UNUSED------------ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportTeamResult(SCIReport * theReport, + gsi_u32 theTeamIndex, + SCGameResult theResult) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + //unsigned int len = 0; + gsi_u32 resultsOffset = 0; + + resultsOffset = sizeof(SCIReportHeader) + aHeader->mRosterSectionLength + aHeader->mAuthSectionLength; + resultsOffset += SC_REPORT_ENTITYRESULT_LENGTH * theReport->mNumResultsReported; + GS_ASSERT(resultsOffset < SC_REPORT_BUFFER_BYTES); + + resultsOffset += SC_REPORT_ENTITYRESULT_LENGTH * theReport->mNumResultsReported; + sciSerializeInt32((gsi_u8 *)&theReport->mBuffer.mData[resultsOffset], theResult); + + theReport->mNumResultsReported++; + GSI_UNUSED(theTeamIndex); + return SCResult_NO_ERROR; +}*/ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportBeginNewTeam(SCIReport * theReport) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + + //sciReportEndEntity(theReport); + + theReport->mCurEntityKeyCount = 0; + theReport->mCurEntityStartPos = (gsi_i32)theReport->mBuffer.mPos; + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + + aHeader->mTeamSectionLength += sizeof(gsi_u16); + + // Mark the start of the new entity + //theReport->mStatus.mCurEntityStart = &theReport->mBuffer.mData[theReport->mBuffer.mPos]; + //theReport->mStatus.mCurEntityType = SC_REPORT_ENTITY_TEAM; + //theReport->mBuffer.mPos += 4; // skip 4 byte for length, it will be filled in later + //aHeader->mTeamCount++; +// aHeader->mTeamDataLength += 4; // 4 bytes for the new team's length + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportBeginNewPlayer(SCIReport * theReport) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + GS_ASSERT(theReport != NULL); + + //if (theReport->mReportState == SCIReportState_ROSTER) + { + // this is the first player + + } + //GS_ASSERT(theReport->mReportState == SCIReportState_ROSTER); + + theReport->mCurEntityKeyCount = 0; + theReport->mCurEntityStartPos = (gsi_i32)theReport->mBuffer.mPos; + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + + aHeader->mPlayerSectionLength += sizeof(gsi_u16); + + + //sciReportEndEntity(theReport); + + // Mark the start of the new entity + //theReport->mStatus.mCurEntityStart = &theReport->mBuffer.mData[theReport->mBuffer.mPos]; + //theReport->mStatus.mCurEntityType = SC_REPORT_ENTITY_PLAYER; + //theReport->mBuffer.mPos += 4; // skip 4 byte for length, it will be filled in later + //sciSerializeInt32(&theReport->mBuffer.mData[theReport->mBuffer.mPos], thePlayerIndex); + //theReport->mBuffer.mPos += 4; // 4 byte index + //aHeader->mPlayerCount++; +// aHeader->mPlayerDataLength += 4; // 4 bytes for the new player's length + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportBeginGlobalData(SCIReport * theReport) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + + GS_ASSERT(theReport != NULL); + GS_ASSERT(theReport->mReportState == SCIReportState_ROSTER); + + theReport->mReportState = SCIReportState_GLOBALDATA; + //theReport->mCurEntityIndex = 0; + + // set buffer write position to start of global data + theReport->mBuffer.mPos = sizeof(SCIReportHeader) + aHeader->mRosterSectionLength + + aHeader->mAuthSectionLength + aHeader->mResultsSectionLength; + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportBeginPlayerData(SCIReport * theReport) +{ + //SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + + GS_ASSERT(theReport != NULL); + + // Must not have passed into player data state yet. + GS_ASSERT(theReport->mReportState == SCIReportState_ROSTER || + theReport->mReportState == SCIReportState_GLOBALDATA); + + // case where there is no global data + if (theReport->mReportState == SCIReportState_ROSTER) + { + } + + theReport->mReportState = SCIReportState_PLAYERDATA; + //theReport->mCurEntityIndex = 0; + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportBeginTeamData(SCIReport * theReport) +{ + GS_ASSERT(theReport != NULL); + + // Must not have passed into player data state yet. + GS_ASSERT( + theReport->mReportState == SCIReportState_ROSTER || + theReport->mReportState == SCIReportState_GLOBALDATA || + theReport->mReportState == SCIReportState_PLAYERDATA + ); + + theReport->mReportState = SCIReportState_TEAMDATA; + //theReport->mCurEntityIndex = 0; + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportEndEntity(SCIReport * theReport) +{ + //gsi_u8 * anEntityEnd = NULL; + //gsi_u32 anEntityLen = 0; + + // Calculate and update the last entities length + /*if (theReport->mStatus.mCurEntityStart != NULL) + { + anEntityEnd = &theReport->mBuffer.mData[theReport->mBuffer.mPos]; + anEntityLen = anEntityEnd - theReport->mStatus.mCurEntityStart; + sciSerializeDataLength(theReport->mStatus.mCurEntityStart, anEntityLen); + theReport->mStatus.mCurEntityStart = NULL; + }*/ + GSI_UNUSED(theReport); + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportEnd(SCIReport * theReport, gsi_bool isAuth, SCGameStatus theStatus) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + + sciReportEndEntity(theReport); + + if (gsi_is_true(isAuth)) + aHeader->mFlags |= SC_REPORT_FLAG_AUTHORITATIVE; + + // Send header in network byte order + aHeader->mGameStatus = htonl(theStatus); + + aHeader->mFlags = htonl(aHeader->mFlags); + aHeader->mPlayerCount = htons(aHeader->mPlayerCount); + aHeader->mTeamCount = htons(aHeader->mTeamCount); + aHeader->mReserved = 0; + + aHeader->mGameKeyCount = htons(aHeader->mGameKeyCount); + aHeader->mPlayerKeyCount = htons(aHeader->mPlayerKeyCount); + aHeader->mTeamKeyCount = htons(aHeader->mTeamKeyCount); + + aHeader->mRosterSectionLength = htonl(aHeader->mRosterSectionLength); + aHeader->mAuthSectionLength = htonl(aHeader->mAuthSectionLength); + aHeader->mResultsSectionLength = htonl(aHeader->mResultsSectionLength); + + aHeader->mGameSectionLength = htonl(aHeader->mGameSectionLength); + aHeader->mPlayerSectionLength = htonl(aHeader->mPlayerSectionLength); + aHeader->mTeamSectionLength = htonl(aHeader->mTeamSectionLength); + + +// aHeader->mSessionDataLength = htonl(aHeader->mSessionDataLength); +// aHeader->mPlayerDataLength = htonl(aHeader->mPlayerDataLength); +// aHeader->mTeamDataLength = htonl(aHeader->mTeamDataLength); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportAddIntValue(SCIReport * theReport, + gsi_u16 theKeyId, + gsi_i32 theValue) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + int writtenLen = 0; + gsi_i16 theKeyType = SCIKeyType_INT32; + + // calculate length of data to be written + writtenLen += sizeof(gsi_u16) + sizeof(gsi_u16); // 2 byte key ID, 2 byte key type; + writtenLen += sizeof(gsi_u32); // 4 bytes of data + + // Check room in buffer + GS_ASSERT((theReport->mBuffer.mPos + writtenLen) < theReport->mBuffer.mCapacity); + + // Update count and length markers + if (theReport->mReportState == SCIReportState_GLOBALDATA) + { + aHeader->mGameKeyCount++; + aHeader->mGameSectionLength += writtenLen; + } + else if (theReport->mReportState == SCIReportState_PLAYERDATA) + { + GS_ASSERT(theReport->mCurEntityStartPos != -1); + aHeader->mPlayerKeyCount++; + aHeader->mPlayerSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + else if (theReport->mReportState == SCIReportState_TEAMDATA) + { + aHeader->mTeamKeyCount++; + aHeader->mTeamSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + // Needs real error handling code or an assertion on a non-constant expression + //else + // GS_ASSERT(0); // invalid state for writing key/value pairs! + + // Write the data + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], (gsi_i16)theKeyId); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theKeyType); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt32((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theValue); + theReport->mBuffer.mPos += sizeof(gsi_u32); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportAddInt64Value(SCIReport * theReport, + gsi_u16 theKeyId, + gsi_i64 theValue) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + int writtenLen = 0; + gsi_i16 theKeyType = SCIKeyType_INT64; + + // calculate length of data to be written + writtenLen += sizeof(gsi_u16) + sizeof(gsi_u16); // 2 byte key ID, 2 byte key type; + writtenLen += sizeof(gsi_i64); // 4 bytes of data + + // Check room in buffer + GS_ASSERT((theReport->mBuffer.mPos + writtenLen) < theReport->mBuffer.mCapacity); + + // Update count and length markers + if (theReport->mReportState == SCIReportState_GLOBALDATA) + { + aHeader->mGameKeyCount++; + aHeader->mGameSectionLength += writtenLen; + } + else if (theReport->mReportState == SCIReportState_PLAYERDATA) + { + GS_ASSERT(theReport->mCurEntityStartPos != -1); + aHeader->mPlayerKeyCount++; + aHeader->mPlayerSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + else if (theReport->mReportState == SCIReportState_TEAMDATA) + { + aHeader->mTeamKeyCount++; + aHeader->mTeamSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + + // Write the data + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], (gsi_i16)theKeyId); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theKeyType); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt64((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theValue); + theReport->mBuffer.mPos += sizeof(gsi_i64); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportAddShortValue(SCIReport * theReport, + gsi_u16 theKeyId, + gsi_i16 theValue) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + int writtenLen = 0; + gsi_i16 theKeyType = SCIKeyType_INT16; + + // calculate length of data to be written + writtenLen += sizeof(gsi_u16) + sizeof(gsi_u16); // 2 byte key ID, 2 byte key type; + writtenLen += sizeof(gsi_u16); // 2 bytes of data + + // Check room in buffer + GS_ASSERT((theReport->mBuffer.mPos + writtenLen) < theReport->mBuffer.mCapacity); + + // Update count and length markers + if (theReport->mReportState == SCIReportState_GLOBALDATA) + { + aHeader->mGameKeyCount++; + aHeader->mGameSectionLength += writtenLen; + } + else if (theReport->mReportState == SCIReportState_PLAYERDATA) + { + GS_ASSERT(theReport->mCurEntityStartPos != -1); + aHeader->mPlayerKeyCount++; + aHeader->mPlayerSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + else if (theReport->mReportState == SCIReportState_TEAMDATA) + { + aHeader->mTeamKeyCount++; + aHeader->mTeamSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + // Needs real error handling code or an assertion on a non-constant expression + //else + // GS_ASSERT(0); // invalid state for writing key/value pairs! + + // Write the data + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], (gsi_i16)theKeyId); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theKeyType); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theValue); + theReport->mBuffer.mPos += sizeof(gsi_u16); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportAddByteValue(SCIReport * theReport, + gsi_u16 theKeyId, + gsi_i8 theValue) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + int writtenLen = 0; + gsi_i16 theKeyType = SCIKeyType_BYTE; + + // calculate length of data to be written + writtenLen += sizeof(gsi_u16) + sizeof(gsi_u16); // 2 byte key ID, 2 byte key type; + writtenLen += sizeof(gsi_u8); // 1 bytes of data + + // Check room in buffer + GS_ASSERT((theReport->mBuffer.mPos + writtenLen) < theReport->mBuffer.mCapacity); + + // Update count and length markers + if (theReport->mReportState == SCIReportState_GLOBALDATA) + { + aHeader->mGameKeyCount++; + aHeader->mGameSectionLength += writtenLen; + } + else if (theReport->mReportState == SCIReportState_PLAYERDATA) + { + GS_ASSERT(theReport->mCurEntityStartPos != -1); + aHeader->mPlayerKeyCount++; + aHeader->mPlayerSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + else if (theReport->mReportState == SCIReportState_TEAMDATA) + { + aHeader->mTeamKeyCount++; + aHeader->mTeamSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + // Needs real error handling code or an assertion on a non-constant expression + //else + // GS_ASSERT(0); // invalid state for writing key/value pairs! + + // Write the data + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], (gsi_i16)theKeyId); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theKeyType); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt8((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theValue); + theReport->mBuffer.mPos += sizeof(gsi_u8); + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportAddFloatValue(SCIReport * theReport, + gsi_u16 theKeyId, + float theValue) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + int writtenLen = 0; + gsi_i16 theKeyType = SCIKeyType_FLOAT; + + // calculate length of data to be written + writtenLen += sizeof(gsi_u16) + sizeof(gsi_u16); // 2 byte key ID, 2 byte key type; + writtenLen += sizeof(float); // stored in stream as a 4 byte char array + + // Check room in buffer + GS_ASSERT((theReport->mBuffer.mPos + writtenLen) < theReport->mBuffer.mCapacity); + + // Update count and length markers + if (theReport->mReportState == SCIReportState_GLOBALDATA) + { + aHeader->mGameKeyCount++; + aHeader->mGameSectionLength += writtenLen; + } + else if (theReport->mReportState == SCIReportState_PLAYERDATA) + { + GS_ASSERT(theReport->mCurEntityStartPos != -1); + aHeader->mPlayerKeyCount++; + aHeader->mPlayerSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + else if (theReport->mReportState == SCIReportState_TEAMDATA) + { + aHeader->mTeamKeyCount++; + aHeader->mTeamSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + // Needs real error handling code or an assertion on a non-constant expression + //else + // GS_ASSERT(0); // invalid state for writing key/value pairs! + + // Write the data + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], (gsi_i16)theKeyId); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theKeyType); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeFloat((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theValue); + theReport->mBuffer.mPos += sizeof(float); + + return SCResult_NO_ERROR; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult SC_CALL sciReportAddStringValue(SCIReport * theReport, + gsi_u16 theKeyId, + const gsi_char * theValue) +{ + SCIReportHeader * aHeader = (SCIReportHeader*)theReport->mBuffer.mData; + int writtenLen = 0, numLenBytes = 0, asciiLen = 0; + gsi_i16 theKeyType = SCIKeyType_STRING; + + // for unicode we need to store the ascii-converted string first in the buffer so as + // not to dynamically allocate space for it. Then once we have the length of it, we can + // write over the buffer + +#if defined(GSI_UNICODE) + // First check room in buffer + GS_ASSERT(((theReport->mBuffer.mPos + (_tcslen(theValue)) + (sizeof(gsi_u16)*2)) + < theReport->mBuffer.mCapacity)); + + // as long as we have enough room - add to the buffer and store length of the Ascii string + // need to remove 1 for the appended null char \0 which we want to remove + asciiLen = UCS2ToAsciiString(theValue, &theReport->mBuffer.mData[theReport->mBuffer.mPos])-1; + + // now determine how many bytes are necessary to represent len + numLenBytes = (asciiLen/127)+1; + +#else + + numLenBytes = (gsi_i32)(strlen(theValue)/127)+1; //finds out number of bytes necessary to represent len + asciiLen = (int)strlen(theValue); +#endif + + + // calculate length of data to be written + writtenLen += sizeof(gsi_u16) + sizeof(gsi_u16); // 2 byte key ID, 2 byte key type; + writtenLen += asciiLen; // 0 for empty string + writtenLen += numLenBytes; // the number of bytes necessary to represent len + + // Check room in buffer + GS_ASSERT((theReport->mBuffer.mPos + writtenLen) < theReport->mBuffer.mCapacity); + + // Update count and length markers + if (theReport->mReportState == SCIReportState_GLOBALDATA) + { + aHeader->mGameKeyCount++; + aHeader->mGameSectionLength += writtenLen; + } + else if (theReport->mReportState == SCIReportState_PLAYERDATA) + { + aHeader->mPlayerKeyCount++; + aHeader->mPlayerSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + else if (theReport->mReportState == SCIReportState_TEAMDATA) + { + aHeader->mTeamKeyCount++; + aHeader->mTeamSectionLength += writtenLen; + + theReport->mCurEntityKeyCount++; + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mCurEntityStartPos], (gsi_i16)theReport->mCurEntityKeyCount); + } + // Needs real error handling code or an assertion on a non-constant expression + //else + // GS_ASSERT(0); // invalid state for writing key/value pairs! + + // Write the data + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], (gsi_i16)theKeyId); + theReport->mBuffer.mPos += sizeof(gsi_u16); + sciSerializeInt16((gsi_u8 *)&theReport->mBuffer.mData[theReport->mBuffer.mPos], theKeyType); + theReport->mBuffer.mPos += sizeof(gsi_u16); + + // now prior to writing the string we need to prepend the length of the string to follow + // the .NET format of (string length)(String) + memset(&theReport->mBuffer.mData[theReport->mBuffer.mPos], asciiLen, (gsi_u32)numLenBytes); + theReport->mBuffer.mPos += numLenBytes; + +#if defined(GSI_UNICODE) + // Now strip to Ascii and write the length - subtract 1 to get rid of appended null character + theReport->mBuffer.mPos += UCS2ToAsciiString(theValue, &theReport->mBuffer.mData[theReport->mBuffer.mPos])-1; +#else + strncpy(&theReport->mBuffer.mData[theReport->mBuffer.mPos], theValue, strlen(theValue)); + theReport->mBuffer.mPos += strlen(theValue); +#endif + + return SCResult_NO_ERROR; +} + +#pragma warning(default: 4267) \ No newline at end of file diff --git a/code/gamespy/sc/sciReport.h b/code/gamespy/sc/sciReport.h new file mode 100644 index 00000000..6519a307 --- /dev/null +++ b/code/gamespy/sc/sciReport.h @@ -0,0 +1,185 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __SCIREPORT_H__ +#define __SCIREPORT_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sci.h" +#include "../hashtable.h" + + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Constants +#define SC_REPORT_PROTOCOL 2 + +// NOTE: broke up entity index in two - for later versions we will want to send the 2 byte +// team index followed by the 4 byte team ID later in the stream + + // Protocol length settings +#define SC_REPORT_ROSTERDATA_LENGTH 20 // V1.0 - 16 byte connection id + 4 byte team index +#define SC_REPORT_PLAYERINDEX_LENGTH 2 // V1.0 - 2 bytes for a player index +#define SC_REPORT_TEAMINDEX_LENGTH 4 // V1.0 - 4 bytes for a team index +#define SC_REPORT_AUTHDATA_LENGTH 16 // V1.0 - length of auth data per player +#define SC_REPORT_ENTITYRESULT_LENGTH 4 // V1.0 - 4 byte enum value + + +#define SC_REPORT_ENTITY_NONE 0 +#define SC_REPORT_ENTITY_PLAYER 1 +#define SC_REPORT_ENTITY_TEAM 2 + +// (must match server) +#define SC_REPORT_FLAG_AUTHORITATIVE (1<<0) +#define SC_REPORT_FLAG_NON_RELATIVE_RESULT (1<<1) +#define SC_REPORT_FLAG_MATCHLESS_SESSION (1<<2) +//#define SC_REPORT_FLAG_DEDICATED_HOST (1<<3) + +typedef enum +{ + SCIReportState_NONE, + SCIReportState_ROSTER, + //SCIReportState_AUTHDATA, + //SCIReportState_RESTULTS, + SCIReportState_GLOBALDATA, + SCIReportState_PLAYERDATA, + SCIReportState_TEAMDATA +} SCIReportState; + +typedef enum +{ + SCIKeyType_INT32, + SCIKeyType_INT16, + SCIKeyType_BYTE, + SCIKeyType_STRING, + SCIKeyType_FLOAT, + SCIKeyType_INT64 +} SCIKeyType; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Structs +typedef struct SCIReportHeader +{ + gsi_u32 mProtocolVersion; // To support future changes + gsi_u32 mDeveloperVersion; + gsi_u8 mChecksum[GS_CRYPT_MD5_HASHSIZE]; // Hash(session, player, team data) + gsi_u32 mGameStatus; + gsi_u32 mFlags; // Flags for authoritative, final, etc. + gsi_u16 mPlayerCount; // Players in session + gsi_u16 mTeamCount; // Teams in session + gsi_u16 mGameKeyCount; + gsi_u16 mPlayerKeyCount; + gsi_u16 mTeamKeyCount; + gsi_u16 mReserved; // pad, for 32-bit alignment + gsi_u32 mRosterSectionLength; // + gsi_u32 mAuthSectionLength; // + gsi_u32 mResultsSectionLength; // + gsi_u32 mGameSectionLength; // + gsi_u32 mPlayerSectionLength; // + gsi_u32 mTeamSectionLength; // +} SCIReportHeader; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct SCIReportBuffer +{ + gsi_bool mIsStatic; + + gsi_u32 mLen; + gsi_u32 mCapacity; + gsi_u32 mPos; + + char * mData; + +} SCIReportBuffer; + +typedef struct SCIReport +{ + SCIReportState mReportState; + + gsi_i32 mCurEntityStartPos; // where this entity's data begins + gsi_u16 mCurEntityKeyCount; // how many keys we've added for this entity + + //gsi_u32 mNumPlayersReported; // to check against expected count - not used + gsi_u32 mNumResultsReported; // to check against expected count + + gsi_u32 mNumTeamsReported; // keeps track of the number of team IDs that have been reported + gsi_u32 mTeamIds[SC_MAX_NUM_TEAMS]; // internal list of team IDs + + SCIReportBuffer mBuffer; +} SCIReport; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciCreateReport(gsi_u8 theSessionGuid[16], + gsi_u32 theHeaderVersion, + gsi_u32 thePlayerCount, + gsi_u32 theTeamCount, + SCIReport ** theReportOut); + +SCResult sciDestroyReport(SCIReport *theReport); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// ReportData functions +SCResult SC_CALL sciReportSetPlayerConnectionId(SCIReport * theReport, gsi_u32 thePlayerIndex, const gsi_u8 theConnectionId[SC_CONNECTION_GUID_SIZE]); +SCResult SC_CALL sciReportSetPlayerTeamIndex (SCIReport * theReport, gsi_u32 thePlayerIndex, gsi_u32 theTeamIndex); +SCResult SC_CALL sciReportSetPlayerGameResult (SCIReport * theReport, gsi_u32 thePlayerIndex, SCGameResult theGameResult); +SCResult SC_CALL sciReportSetPlayerAuthInfo (SCIReport * theReport, gsi_u32 thePlayerIndex, const GSLoginCertificate * theCertificate, const gsi_u8 theAuthHash[16]); +SCResult SC_CALL sciReportSetTeamGameResult (SCIReport * theReport, gsi_u32 theTeamIndex , SCGameResult theGameResult); +SCResult SC_CALL sciReportSetAsMatchless (SCIReport * theReport); + +// Key/Value data +SCResult SC_CALL sciReportBeginGlobalData(SCIReport * theReport); +SCResult SC_CALL sciReportBeginPlayerData(SCIReport * theReport); +SCResult SC_CALL sciReportBeginTeamData (SCIReport * theReport); + +SCResult SC_CALL sciReportAddIntValue(SCIReport * theReport, + gsi_u16 theKeyId, + gsi_i32 theValue); +SCResult SC_CALL sciReportAddInt64Value(SCIReport * theReport, + gsi_u16 theKeyId, + gsi_i64 theValue); +SCResult SC_CALL sciReportAddShortValue(SCIReport * theReport, + gsi_u16 theKeyId, + gsi_i16 theValue); +SCResult SC_CALL sciReportAddByteValue(SCIReport * theReport, + gsi_u16 theKeyId, + gsi_i8 theValue); +SCResult SC_CALL sciReportAddFloatValue(SCIReport * theReport, + gsi_u16 theKeyId, + float theValue); +SCResult SC_CALL sciReportAddStringValue(SCIReport * theReport, + gsi_u16 theKeyId, + const gsi_char * theValue); + +SCResult SC_CALL sciReportBeginNewTeam(SCIReport * theReport); +SCResult SC_CALL sciReportBeginNewPlayer(SCIReport * theReport); +SCResult SC_CALL sciReportEndEntity(SCIReport * theReport); +// Call when finished writing +SCResult SC_CALL sciReportEnd(SCIReport * theReport, gsi_bool isAuth, SCGameStatus theStatus); + + + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __SCREPORT_H__ diff --git a/code/gamespy/sc/sciSerialize.c b/code/gamespy/sc/sciSerialize.c new file mode 100644 index 00000000..9100ea99 --- /dev/null +++ b/code/gamespy/sc/sciSerialize.c @@ -0,0 +1,171 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sciSerialize.h" + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeInt8(gsi_u8* theCursor, gsi_i8 theValue) +{ + // Copy int8 value to possibly misaligned destination + gsi_u8* dst = (gsi_u8*)theCursor; + + *dst++ = (gsi_u8)theValue; + + return (gsi_u8*)dst; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeInt16(gsi_u8* theCursor, gsi_i16 theValue) +{ + // Convert to network byte order + gsi_i16 netValue = (gsi_i16)htons(theValue); + + // Copy int16 value to possibly misaligned destination + gsi_u8* dst = (gsi_u8*)theCursor; + gsi_u8* src = (gsi_u8*)&netValue; + + *dst++ = *src++; + *dst++ = *src++; + + return (gsi_u8*)dst; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeInt32(gsi_u8* theCursor, gsi_i32 theValue) +{ + // Convert to network byte order + gsi_i32 netValue = (gsi_i32)htonl(theValue); + + // Copy int32 value to possibly misaligned destination + gsi_u8* dst = (gsi_u8*)theCursor; + gsi_u8* src = (gsi_u8*)&netValue; + + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + + return (gsi_u8*)dst; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeInt64(gsi_u8* theCursor, gsi_i64 theValue) +{ + // Convert to network byte order + //gsi_i32 netValue = (gsi_i32)htonl(theValue); + + // Copy int32 value to possibly misaligned destination + gsi_u8* dst = (gsi_u8*)theCursor; + gsi_u8* src = (gsi_u8*)&theValue; + +#ifdef GSI_BIG_ENDIAN + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + + dst[4] = src[4]; + dst[5] = src[5]; + dst[6] = src[6]; + dst[7] = src[7]; +#else + dst[0] = src[7]; + dst[1] = src[6]; + dst[2] = src[5]; + dst[3] = src[4]; + + dst[4] = src[3]; + dst[5] = src[2]; + dst[6] = src[1]; + dst[7] = src[0]; +#endif + + return (gsi_u8*)dst; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeFloat(gsi_u8* theCursor, float theValue) +{ + unsigned char netValue[4]; + gsi_u8 *dst, *src; + +#if defined(GSI_BIG_ENDIAN) + //swap byte ordering for floats to little endian - htonl doesn't work + gsiFloatSwap(netValue, theValue); +#else + memcpy(netValue, &theValue, 4); +#endif + + dst = (gsi_u8*)theCursor; + src = (gsi_u8*)&netValue; + + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + + return (gsi_u8*)dst; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeKey(gsi_u8* theCursor, gsi_u16 theKey) +{ + return sciSerializeInt16(theCursor, (gsi_i16)theKey); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeDataLength(gsi_u8* theCursor, gsi_u32 theDataLength) +{ + return sciSerializeInt32(theCursor, (gsi_i32)theDataLength); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeGUID(gsi_u8* theCursor, const gsi_u8 theGUID[SC_CONNECTION_GUID_SIZE]) +{ + // a GUID is a string comprised of an int, two shorts and eight bytes + // 6B29FC40-CA47-1067-B31D-00DD010662DA + gsi_u32 anInt = 0; + gsi_u32 aShort1 = 0; + gsi_u32 aShort2 = 0; + gsi_u32 aIntArray[8]; + gsi_u8 aByteArray[8]; + int i=0; + + sscanf((char *)theGUID, "%8x", &anInt); + sscanf((char *)theGUID+9, "%4x", &aShort1); + sscanf((char *)theGUID+14, "%4x", &aShort2); + + sscanf((char *)theGUID+19, "%2x", &aIntArray[0]); + sscanf((char *)theGUID+21, "%2x", &aIntArray[1]); + + sscanf((char *)theGUID+24, "%2x%2x%2x%2x%2x%2x", &aIntArray[2], + &aIntArray[3], &aIntArray[4], &aIntArray[5], + &aIntArray[6], &aIntArray[7]); + + for (i=0; i < 8; i++) + aByteArray[i] = (gsi_u8)aIntArray[i]; + + theCursor = sciSerializeInt32(theCursor, (gsi_i32)anInt); + theCursor = sciSerializeInt16(theCursor, (gsi_i16)aShort1); + theCursor = sciSerializeInt16(theCursor, (gsi_i16)aShort2); + memcpy(theCursor, aByteArray, 8); + theCursor += 8; + + return theCursor; +} diff --git a/code/gamespy/sc/sciSerialize.h b/code/gamespy/sc/sciSerialize.h new file mode 100644 index 00000000..32e65d99 --- /dev/null +++ b/code/gamespy/sc/sciSerialize.h @@ -0,0 +1,45 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __SCISERIALIZE_H__ +#define __SCISERIALIZE_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sciReport.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u8* sciSerializeInt8 (gsi_u8* theCursor, gsi_i8 theValue); + +gsi_u8* sciSerializeInt16 (gsi_u8* theCursor, gsi_i16 theValue); + +gsi_u8* sciSerializeInt32 (gsi_u8* theCursor, gsi_i32 theValue); + +gsi_u8* sciSerializeInt64 (gsi_u8* theCursor, gsi_i64 theValue); + +gsi_u8* sciSerializeFloat (gsi_u8* theCursor, float theValue); + +gsi_u8* sciSerializeKey (gsi_u8* theCursor, gsi_u16 theKey); + +gsi_u8* sciSerializeDataLength(gsi_u8* theCursor, gsi_u32 theDataLength); + +gsi_u8* sciSerializeGUID (gsi_u8* theCursor, const gsi_u8 theGUID[SC_CONNECTION_GUID_SIZE]); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __SCSERIALIZE_H__ diff --git a/code/gamespy/sc/sciWebServices.c b/code/gamespy/sc/sciWebServices.c new file mode 100644 index 00000000..4620f94a --- /dev/null +++ b/code/gamespy/sc/sciWebServices.c @@ -0,0 +1,600 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../common/gsCore.h" + +#include "sci.h" +#include "sciInterface.h" +#include "sciWebServices.h" +#include "sciReport.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define SC_CREATEMATCHLESSSESSION_SOAPACTION "SOAPAction: \"http://gamespy.net/competition/CreateMatchlessSession\"" +#define SC_CREATESESSION_SOAPACTION "SOAPAction: \"http://gamespy.net/competition/CreateSession\"" +#define SC_SUBMITREPORT_SOAPACTION "SOAPAction: \"http://gamespy.net/competition/SubmitReport\"" +#define SC_SETINTENTION_SOAPACTION "SOAPAction: \"http://gamespy.net/competition/SetReportIntention\"" + +#define SC_SERVICE_NAMESPACE_COUNT 1 +const char * SC_SERVICE_NAMESPACES[SC_SERVICE_NAMESPACE_COUNT] = +{ + "gsc=\"http://gamespy.net/competition/\"" +}; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciWsInit(SCWebServices* theWebServices, + SCInterfacePtr theInterface) +{ + + + GS_ASSERT(theWebServices != NULL); + GS_ASSERT(theInterface != NULL); + GS_ASSERT(!theWebServices->mInit); + + // Check gsCore + if (gsCoreIsShutdown()) + { + return SCResult_CORE_NOT_INITIALIZED; + } + + // Initialize SCWebServices struct + theWebServices->mInterface = theInterface; + theWebServices->mCreateSessionCallback = NULL; + theWebServices->mSetReportIntentionCallback = NULL; + theWebServices->mSubmitReportDataCallback = NULL; + theWebServices->mCreateSessionUserData = NULL; + theWebServices->mSetReportIntentionUserData = NULL; + theWebServices->mSubmitReportUserData = NULL; + theWebServices->mCreateSessionPending = gsi_false; + theWebServices->mSetReportIntentionPending = gsi_false; + theWebServices->mSubmitReportPending = gsi_false; + + // Now initialized + theWebServices->mInit = gsi_true; + + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void sciWsDestroy(SCWebServices* theWebServices) +{ + GS_ASSERT(theWebServices != NULL); + GS_ASSERT(theWebServices->mInit); + + // No longer initialized + theWebServices->mInit = gsi_false; + + // Destroy SCWebServices struct + theWebServices->mCreateSessionCallback = NULL; + theWebServices->mSetReportIntentionCallback = NULL; + theWebServices->mSubmitReportDataCallback = NULL; + theWebServices->mCreateSessionUserData = NULL; + theWebServices->mSetReportIntentionUserData = NULL; + theWebServices->mSubmitReportUserData = NULL; + theWebServices->mCreateSessionPending = gsi_false; + theWebServices->mSetReportIntentionPending = gsi_false; + theWebServices->mSubmitReportPending = gsi_false; + theWebServices->mInterface = NULL; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void sciWsThink(SCWebServices* theWebServices) +{ + GS_ASSERT(theWebServices != NULL); + GS_ASSERT(theWebServices->mInit); + + gsCoreThink(0); + + GSI_UNUSED(theWebServices); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciWsCreateSession (SCWebServices * theWebServices, + gsi_u32 theGameId, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCCreateSessionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData) +{ + GSXmlStreamWriter aRequest = NULL; + + // Check parameters + GS_ASSERT(theWebServices != NULL); + GS_ASSERT(theWebServices->mInit); + + // Check for pending request + if (theWebServices->mCreateSessionPending) + return SCResult_CALLBACK_PENDING; + + // Create the XML message writer + aRequest = gsXmlCreateStreamWriter(SC_SERVICE_NAMESPACES, SC_SERVICE_NAMESPACE_COUNT); + if (aRequest == NULL) + return SCResult_OUT_OF_MEMORY; + + // Fill in the request data + if (gsi_is_false(gsXmlWriteOpenTag(aRequest, "gsc", "CreateSession")) || + gsi_is_false(gsXmlWriteOpenTag(aRequest, "gsc", "certificate")) || + gsi_is_false(wsLoginCertWriteXML(theCertificate, "gsc", aRequest)) || + gsi_is_false(gsXmlWriteCloseTag(aRequest, "gsc", "certificate")) || + gsi_is_false(gsXmlWriteHexBinaryElement(aRequest, "gsc", "proof", (const gsi_u8*)thePrivateData->mKeyHash, GS_CRYPT_MD5_HASHSIZE)) || + gsi_is_false(gsXmlWriteIntElement(aRequest, "gsc", "gameid", (gsi_u32)theGameId)) || + gsi_is_false(gsXmlWriteCloseTag(aRequest, "gsc", "CreateSession")) || + gsi_is_false(gsXmlCloseWriter(aRequest)) + ) + { + gsXmlFreeWriter(aRequest); + return SCResult_HTTP_ERROR; + } + + // Set callback + theWebServices->mCreateSessionCallback = theCallback; + theWebServices->mCreateSessionUserData = theUserData; + theWebServices->mCreateSessionPending = gsi_true; + + // Execute soap call + gsiExecuteSoap(scServiceURL, SC_CREATESESSION_SOAPACTION, + aRequest, sciWsCreateSessionCallback, theWebServices); + GSI_UNUSED(theTimeoutMs); + return SCResult_NO_ERROR; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciWsCreateMatchlessSession (SCWebServices * theWebServices, + gsi_u32 theGameId, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCCreateSessionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData) +{ + GSXmlStreamWriter aRequest = NULL; + + // Check parameters + GS_ASSERT(theWebServices != NULL); + GS_ASSERT(theWebServices->mInit); + + // Check for pending request + if (theWebServices->mCreateSessionPending) + return SCResult_CALLBACK_PENDING; + + // Create the XML message writer + aRequest = gsXmlCreateStreamWriter(SC_SERVICE_NAMESPACES, SC_SERVICE_NAMESPACE_COUNT); + if (aRequest == NULL) + return SCResult_OUT_OF_MEMORY; + + // Fill in the request data + if (gsi_is_false(gsXmlWriteOpenTag(aRequest, "gsc", "CreateMatchlessSession")) || + gsi_is_false(gsXmlWriteOpenTag(aRequest, "gsc", "certificate")) || + gsi_is_false(wsLoginCertWriteXML(theCertificate, "gsc", aRequest)) || + gsi_is_false(gsXmlWriteCloseTag(aRequest, "gsc", "certificate")) || + gsi_is_false(gsXmlWriteHexBinaryElement(aRequest, "gsc", "proof", (const gsi_u8*)thePrivateData->mKeyHash, GS_CRYPT_MD5_HASHSIZE)) || + gsi_is_false(gsXmlWriteIntElement(aRequest, "gsc", "gameid", (gsi_u32)theGameId)) || + gsi_is_false(gsXmlWriteCloseTag(aRequest, "gsc", "CreateMatchlessSession")) || + gsi_is_false(gsXmlCloseWriter(aRequest)) + ) + { + gsXmlFreeWriter(aRequest); + return SCResult_HTTP_ERROR; + } + + // Set callback + theWebServices->mCreateSessionCallback = theCallback; + theWebServices->mCreateSessionUserData = theUserData; + theWebServices->mCreateSessionPending = gsi_true; + + // Execute soap call + gsiExecuteSoap(scServiceURL, SC_CREATEMATCHLESSSESSION_SOAPACTION, + aRequest, sciWsCreateSessionCallback, theWebServices); + GSI_UNUSED(theTimeoutMs); + return SCResult_NO_ERROR; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void sciWsCreateSessionCallback(GHTTPResult theHttpResult, + GSXmlStreamWriter theRequestData, + GSXmlStreamReader theResponseData, + void* theUserData) +{ + SCResult aTranslatedResult = SCResult_HTTP_ERROR; + SCWebServices* aWebServices = (SCWebServices*)theUserData; + + char csid[255]; + char ccid[255]; + int csidLen = 255; + int ccidLen = 255; + + GS_ASSERT(aWebServices != NULL); + GS_ASSERT(aWebServices->mCreateSessionPending); + + // Check for shutdown + if (!aWebServices->mInit) + return; + + if (theHttpResult == GHTTPSuccess) + { + int createResult = 0; + + // Parse through in a way that will work for either type of CreateSession response. + if (gsi_is_false(gsXmlMoveToStart(theResponseData))) + { + aTranslatedResult = SCResult_RESPONSE_INVALID; + } + else if(gsi_is_false(gsXmlMoveToNext(theResponseData, "CreateSessionResponse"))) + { + if(gsi_is_false(gsXmlMoveToNext(theResponseData, "CreateMatchlessSessionResponse"))) + { + aTranslatedResult = SCResult_RESPONSE_INVALID; + } + } + + if(gsi_is_false(gsXmlMoveToNext(theResponseData, "CreateSessionResult"))) + { + if(gsi_is_false(gsXmlMoveToNext(theResponseData, "CreateMatchlessSessionResult"))) + { + aTranslatedResult = SCResult_RESPONSE_INVALID; + } + } + + if(gsi_is_false(gsXmlReadChildAsInt(theResponseData, "result", &createResult))) + { + aTranslatedResult = SCResult_RESPONSE_INVALID; + } + else if(aTranslatedResult != SCResult_RESPONSE_INVALID) + { + // Parse server reported result + if (createResult == SCWsResult_NO_ERROR) + { + // Read session and connection ID + if(gsi_is_false(gsXmlReadChildAsStringNT(theResponseData, "csid", csid, csidLen)) || + gsi_is_false(gsXmlReadChildAsStringNT(theResponseData, "ccid", ccid, ccidLen)) + ) + { + aTranslatedResult = SCResult_RESPONSE_INVALID; + } + else + { + sciInterfaceSetSessionId((SCInterface*)aWebServices->mInterface, csid); + sciInterfaceSetConnectionId((SCInterface*)aWebServices->mInterface, ccid); + aTranslatedResult = SCResult_NO_ERROR; + } + } + else + { + // Server reported an error, handle it? + + // TODO: + // translate result into developer useable form + // report result string as gsDebugFormat message for easier debugging + aTranslatedResult = SCResult_RESPONSE_INVALID; + } + } + } + else + { + aTranslatedResult = SCResult_HTTP_ERROR; + } + + // Client callback + aWebServices->mCreateSessionPending = gsi_false; + if (aWebServices->mCreateSessionCallback != NULL) + { + aWebServices->mCreateSessionCallback(aWebServices->mInterface, theHttpResult, aTranslatedResult, aWebServices->mCreateSessionUserData); + aWebServices->mCreateSessionUserData = NULL; + aWebServices->mCreateSessionCallback = NULL; + } + GSI_UNUSED(theRequestData); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciWsSetReportIntention(SCWebServices* theWebServices, + gsi_u32 theGameId, + const char * theSessionId, + const char * theConnectionId, + gsi_bool isAuthoritative, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCSetReportIntentionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData) +{ + GSXmlStreamWriter aRequest = NULL; + + // Check parameters + GS_ASSERT(theWebServices != NULL); + GS_ASSERT(theWebServices->mInit); + + // Check for pending request + if (theWebServices->mSetReportIntentionPending) + return SCResult_CALLBACK_PENDING; + + // Create the XML message writer + aRequest = gsXmlCreateStreamWriter(SC_SERVICE_NAMESPACES, SC_SERVICE_NAMESPACE_COUNT); + if (aRequest == NULL) + return SCResult_OUT_OF_MEMORY; + + // Fill in the request data + if (gsi_is_false(gsXmlWriteOpenTag(aRequest, "gsc", "SetReportIntention")) || + gsi_is_false(gsXmlWriteOpenTag(aRequest, "gsc", "certificate")) || + gsi_is_false(wsLoginCertWriteXML(theCertificate, "gsc", aRequest)) || + gsi_is_false(gsXmlWriteCloseTag(aRequest, "gsc", "certificate")) || + gsi_is_false(gsXmlWriteHexBinaryElement(aRequest, "gsc", "proof", (const gsi_u8*)thePrivateData->mKeyHash, GS_CRYPT_MD5_HASHSIZE)) || + gsi_is_false(gsXmlWriteStringElement(aRequest, "gsc", "csid", theSessionId)) || + gsi_is_false(gsXmlWriteStringElement(aRequest, "gsc", "ccid", theConnectionId)) || + gsi_is_false(gsXmlWriteIntElement(aRequest, "gsc", "gameid", (gsi_u32)theGameId)) || + gsi_is_false(gsXmlWriteIntElement(aRequest, "gsc", "authoritative", (gsi_u32)(gsi_is_true(isAuthoritative) ? 1:0))) || + gsi_is_false(gsXmlWriteCloseTag(aRequest, "gsc", "SetReportIntention")) || + gsi_is_false(gsXmlCloseWriter(aRequest)) + ) + { + gsXmlFreeWriter(aRequest); + return SCResult_HTTP_ERROR; + } + + // Set callback + theWebServices->mSetReportIntentionCallback = theCallback; + theWebServices->mSetReportIntentionUserData = theUserData; + theWebServices->mSetReportIntentionPending = gsi_true; + + // Execute soap call + gsiExecuteSoap(scServiceURL, SC_SETINTENTION_SOAPACTION, + aRequest, sciWsSetReportIntentionCallback, theWebServices); + + GSI_UNUSED(theTimeoutMs); + return SCResult_NO_ERROR; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void sciWsSetReportIntentionCallback(GHTTPResult theHttpResult, + GSXmlStreamWriter theRequestData, + GSXmlStreamReader theResponseData, + void* theUserData) +{ + SCResult aTranslatedResult = SCResult_HTTP_ERROR; + SCWebServices* aWebServices = (SCWebServices*)theUserData; + + char ccid[255]; + int ccidLen = 255; + + + GS_ASSERT(aWebServices != NULL); + GS_ASSERT(aWebServices->mSetReportIntentionPending); + + // Check for shutdown + if (!aWebServices->mInit) + return; + + if (theHttpResult == GHTTPSuccess) + { + int intentionResult = 0; + + if (gsi_is_false(gsXmlMoveToStart(theResponseData)) || + gsi_is_false(gsXmlMoveToNext(theResponseData, "SetReportIntentionResponse")) || + gsi_is_false(gsXmlMoveToNext(theResponseData, "SetReportIntentionResult")) || + gsi_is_false(gsXmlReadChildAsInt(theResponseData, "result", &intentionResult)) || + gsi_is_false(gsXmlReadChildAsStringNT(theResponseData, "ccid", ccid, ccidLen)) + ) + { + aTranslatedResult = SCResult_RESPONSE_INVALID; + } + else + { + if (intentionResult == SCWsResult_NO_ERROR) + { + aTranslatedResult = SCResult_NO_ERROR; + sciInterfaceSetConnectionId((SCInterface*)aWebServices->mInterface, ccid); + } + else + aTranslatedResult = SCResult_UNKNOWN_RESPONSE; + } + } + else + { + aTranslatedResult = SCResult_HTTP_ERROR; + } + + // Client callback + aWebServices->mSetReportIntentionPending = gsi_false; + if (aWebServices->mSetReportIntentionCallback != NULL) + { + aWebServices->mSetReportIntentionCallback(aWebServices->mInterface, + theHttpResult, + aTranslatedResult, + aWebServices->mSetReportIntentionUserData); + aWebServices->mSetReportIntentionUserData = NULL; + aWebServices->mSetReportIntentionCallback = NULL; + } + GSI_UNUSED(theRequestData); +} + + +/////////////////////////////////////////////////////////////////////////////// +// declared here to allow function to get around Unicode calls +extern GHTTPBool ghiPostAddFileFromMemory(GHTTPPost post,const char * name,const char * buffer, + int bufferLen,const char * reportFilename,const char * contentType); +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Private GSSoapCustomFunc used by sciWsSubmitReport +static void sciWsSubmitReportCustom(GHTTPPost thePost, void* theUserData) +{ + SCWebServices* aWebServices = (SCWebServices*)theUserData; + + //Use internal method to get around unicode calls + ghiPostAddFileFromMemory(thePost, "report", (char *)aWebServices->mSubmitReportData, + (gsi_i32)aWebServices->mSubmitReportLength, "report", "application/bin"); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciWsSubmitReport(SCWebServices* theWebServices, + gsi_u32 theGameId, + const char * theSessionId, + const char * theConnectionId, + const SCIReport * theReport, + gsi_bool isAuthoritative, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCSubmitReportCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData) +{ + GSXmlStreamWriter aRequest = NULL; + //SCIReportHeader * aReportHeader = NULL; + //gsi_u32 aTotalSize = 0; + + //SCReportStatus* aStatus = NULL; + + // Check parameters + GS_ASSERT(theWebServices != NULL); + //GS_ASSERT(theReportData != NULL); + + // Check for pending request + if (theWebServices->mSubmitReportPending) + { + return SCResult_CALLBACK_PENDING; + } + + // Get a pointer to the header + //aReportHeader = (SCIReportHeader*)theReport->mBuffer.mData; + + // Check for complete report + if (theReport->mBuffer.mPos < sizeof(SCIReportHeader)) + return SCResult_REPORT_INVALID; + + // Check size (early check for easier debugging) + //aTotalSize = sizeof(SCIReportHeader); + //aTotalSize += htonl(aReportHeader->mPlayerDataLength); + //aTotalSize += htonl(aReportHeader->mTeamDataLength); + //aTotalSize += htonl(aReportHeader->mSessionDataLength); + // aTotalSize += auth info... + //if (theReport->mBuffer.mPos != aTotalSize) + // return SCResult_REPORT_INVALID; + + // Create the XML message writer + aRequest = gsXmlCreateStreamWriter(SC_SERVICE_NAMESPACES, SC_SERVICE_NAMESPACE_COUNT); + if (aRequest == NULL) + return SCResult_OUT_OF_MEMORY; + + // Fill in the request data + if (gsi_is_false(gsXmlWriteOpenTag(aRequest, "gsc", "SubmitReport")) || + gsi_is_false(gsXmlWriteOpenTag(aRequest, "gsc", "certificate")) || + gsi_is_false(wsLoginCertWriteXML(theCertificate, "gsc", aRequest)) || + gsi_is_false(gsXmlWriteCloseTag(aRequest, "gsc", "certificate")) || + gsi_is_false(gsXmlWriteHexBinaryElement(aRequest, "gsc", "proof", (const gsi_u8*)thePrivateData->mKeyHash, GS_CRYPT_MD5_HASHSIZE)) || + gsi_is_false(gsXmlWriteStringElement(aRequest, "gsc", "csid", theSessionId)) || + gsi_is_false(gsXmlWriteStringElement(aRequest, "gsc", "ccid", theConnectionId)) || + gsi_is_false(gsXmlWriteIntElement(aRequest, "gsc", "gameid", (gsi_u32)theGameId)) || + gsi_is_false(gsXmlWriteIntElement(aRequest, "gsc", "authoritative", (gsi_u32)(gsi_is_true(isAuthoritative) ? 1:0))) || + gsi_is_false(gsXmlWriteCloseTag(aRequest, "gsc", "SubmitReport")) || + gsi_is_false(gsXmlCloseWriter(aRequest)) + ) + { + gsXmlFreeWriter(aRequest); + return SCResult_OUT_OF_MEMORY; + } + + // Get submission size + theWebServices->mSubmitReportData = (gsi_u8*)theReport->mBuffer.mData; + theWebServices->mSubmitReportLength = theReport->mBuffer.mPos; + + // Set callback + theWebServices->mSubmitReportDataCallback = theCallback; + theWebServices->mSubmitReportUserData = theUserData; + theWebServices->mSubmitReportPending = gsi_true; + + // Execute soap call + gsiExecuteSoapCustom(scServiceURL, SC_SUBMITREPORT_SOAPACTION, + aRequest, sciWsSubmitReportCallback,sciWsSubmitReportCustom, theWebServices); + + GSI_UNUSED(theTimeoutMs); + return SCResult_NO_ERROR; +} + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void sciWsSubmitReportCallback(GHTTPResult theHttpResult, + GSXmlStreamWriter theRequestData, + GSXmlStreamReader theResponseData, + void* theUserData) +{ + SCResult aTranslatedResult = SCResult_HTTP_ERROR; + SCWebServices* aWebServices = (SCWebServices*)theUserData; + + GS_ASSERT(aWebServices != NULL); + + // Check for shutdown + if (!aWebServices->mInit) + return; + + GS_ASSERT(aWebServices->mSubmitReportPending); + + if (theHttpResult == GHTTPSuccess) + { + int submitResult = 0; + + if (gsi_is_false(gsXmlMoveToStart(theResponseData)) || + gsi_is_false(gsXmlMoveToNext(theResponseData, "SubmitReportResponse")) || + gsi_is_false(gsXmlMoveToNext(theResponseData, "SubmitReportResult")) || + gsi_is_false(gsXmlReadChildAsInt(theResponseData, "result", &submitResult)) + ) + { + aTranslatedResult = SCResult_RESPONSE_INVALID; + } + else + { + switch (submitResult) + { + case SCWsResult_NO_ERROR: + aTranslatedResult = SCResult_NO_ERROR; + break; + case SCWsResult_REPORT_INVALID: + aTranslatedResult = SCResult_REPORT_INVALID; + break; + case SCWsResult_SINGLE_ATTACHMENT_EXPECTED: + aTranslatedResult = SCResult_SUBMISSION_FAILED; + break; + default: + aTranslatedResult = SCResult_UNKNOWN_RESPONSE; + break; + }; + } + } + else + { + aTranslatedResult = SCResult_HTTP_ERROR; + } + + // Client callback + aWebServices->mSubmitReportPending = gsi_false; + if (aWebServices->mSubmitReportDataCallback != NULL) + { + aWebServices->mSubmitReportDataCallback(aWebServices->mInterface, + theHttpResult, + aTranslatedResult, + aWebServices->mSubmitReportUserData); + aWebServices->mSubmitReportUserData = NULL; + aWebServices->mSubmitReportDataCallback = NULL; + } + GSI_UNUSED(theRequestData); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/sc/sciWebServices.h b/code/gamespy/sc/sciWebServices.h new file mode 100644 index 00000000..a6d0fb5c --- /dev/null +++ b/code/gamespy/sc/sciWebServices.h @@ -0,0 +1,126 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __SCWEBSERVICES_H__ +#define __SCWEBSERVICES_H__ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../common/gsSoap.h" +#include "../common/gsXML.h" +#include "../ghttp/ghttpPost.h" + +#include "sci.h" +#include "sciReport.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Web service result codes (must match definitions in StatsReceiver) +typedef enum +{ + SCWsResult_NO_ERROR = 0, + SCWsResult_REPORT_INVALID, + SCWsResult_SINGLE_ATTACHMENT_EXPECTED +} SCWsResult; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct +{ + SCInterfacePtr mInterface; + SCCreateSessionCallback mCreateSessionCallback; + SCSetReportIntentionCallback mSetReportIntentionCallback; + SCSubmitReportCallback mSubmitReportDataCallback; + gsi_bool mSetReportIntentionPending; + gsi_bool mCreateSessionPending; + gsi_bool mSubmitReportPending; + void * mSetReportIntentionUserData; + void * mCreateSessionUserData; + void * mSubmitReportUserData; + gsi_u8* mSubmitReportData; + gsi_u32 mSubmitReportLength; + gsi_bool mInit; +} SCWebServices; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciWsInit (SCWebServices* theWebServices, + SCInterfacePtr theInterface); +void sciWsDestroy(SCWebServices* theWebServices); +void sciWsThink (SCWebServices* theWebServices); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SCResult sciWsCreateSession (SCWebServices * theWebServices, + gsi_u32 theGameId, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCCreateSessionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData); + +SCResult sciWsCreateMatchlessSession(SCWebServices * theWebServices, + gsi_u32 theGameId, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCCreateSessionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData); + +void sciWsCreateSessionCallback(GHTTPResult theHttpResult, + GSXmlStreamWriter theRequestData, + GSXmlStreamReader theResponseData, + void* theUserData); + +SCResult sciWsSetReportIntention (SCWebServices* theWebServices, + gsi_u32 theGameId, + const char * theSessionId, + const char * theConnectionId, + gsi_bool isAuthoritative, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCSetReportIntentionCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData); + +void sciWsSetReportIntentionCallback(GHTTPResult theHttpResult, + GSXmlStreamWriter theRequestData, + GSXmlStreamReader theResponseData, + void* theUserData); + +SCResult sciWsSubmitReport (SCWebServices* theWebServices, + gsi_u32 theGameId, + const char * theSessionId, + const char * theConnectionId, + const SCIReport* theReport, + gsi_bool isAuthoritative, + const GSLoginCertificate * theCertificate, + const GSLoginPrivateData * thePrivateData, + SCSubmitReportCallback theCallback, + gsi_time theTimeoutMs, + void * theUserData); + +void sciWsSubmitReportCallback(GHTTPResult theHttpResult, + GSXmlStreamWriter theRequestData, + GSXmlStreamReader theResponseData, + void* theUserData); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // __SCWEBSERVICES_H__ diff --git a/code/gamespy/serverbrowsing/changelog.txt b/code/gamespy/serverbrowsing/changelog.txt new file mode 100644 index 00000000..9877cafb --- /dev/null +++ b/code/gamespy/serverbrowsing/changelog.txt @@ -0,0 +1,184 @@ +Changelog for: GameSpy Server Browsing SDK +-------------------------------------------------------- + +DATE VERSION BY TYPE DESCRIPTION +---------- ------- --- ------- --------------------------------------------------------- +12-12-2007 2.07.00 RMV RELEASE Released to Developer Site +12-11-2007 2.06.05 SAH OTHER Added NatNeg files to projects to remove compiler errors +11-27-2007 2.06.03 SAH CLEANUP Moved extern "c" block below includes to prevent linker errors +11-21-2007 2.06.02 SAH FIX Added NNFreeNegotiateList to ServerBrowserFree function +11-05-2007 2.06.01 DES FEATURE Added ServerBrowserConnectToServer (ACE functionality) +08-06-2007 2.06.00 RMV OTHER Modified sbctest, making it more thorough and developer friendly (and added ReadMe) +08-06-2007 2.06.00 RMV RELEASE Released to Developer Site +07-10-2007 2.05.04 RMV FIX Fixed test project files to get rid of Unicode warnings and fixed other compiler warnings +05-16-2007 2.05.03 DES FIX Changed use of u_char to gsi_u8 + DES FEATURE ICMP support is now included by default, define SB_NO_ICMP_SUPPORT to exclude it +01-25-2007 2.05.02 SAH FIX Fixed a bug where the country/region keys would be queried for by individual servers +01-16-2007 2.05.01 DES FEATURE Added X360 support +12-15-2006 2.05.00 MJW RELEASE Released to Developer Site +11-10-2006 2.04.36 JR RELEASE Limited release +10-23-2006 2.04.36 DES RELEASE Limited release +10-11-2006 2.04.36 SN FIX Fixed case where possible disconnection could corrupt input buffer when there + was still data on the buffer. +10-05-2006 2.04.35 SAH FIX Updated MacOSX Makefile +09-28-2006 2.04.34 SAH FIX Fixed PS3 project to work with PS3 095.00x SDK; changed included libaries in linker input. +08-18-2006 2.04.33 SN FIX Fixed ServerBrowserAuxUpdateIP where servers not pending queries were marked + with duplicate query error +08-02-2006 2.04.32 SAH RELEASE Releasing to developer site +07-31-2006 2.04.32 SAH FIX Fixed PS3 project file - added post-build step to create *.SELF for execution +07-25-2006 2.04.31 SAH FIX Fixed NITRO project, include for crt0.o now above others so it add correct file +07-24-2006 2.04.30 SAH FIX Added newline at EOF to sb_serverbrowsing.c to fix compilation warning + SAH FIX Removed #ifdef _PS3 for socket calls (changed platformSocket.h to typecast calls) +07-06-2006 2.04.29 SAH FIX Changed a break; to a return; for challenge received when calling auxUpdateIP synchronously + SAH FIX Made Querytest.c use test_main like our other SDKs, edited project appropriately +07-06-2006 2.04.28 SAH FIX Fixed PSP project file to not explicitly include the PSP linker file +07-06-2006 2.04.27 SAH FIX Fixed linux makefile to work with pthreads (for http asynch DNS lookup) +06-30-2006 2.04.26 SAH FIX Changed natneg = 1 to SBTrue to get rid of warnings + SAH FIX Fixed NITRO project && linker command file (for Codewarrior 2.0/NitroSDK 3.1) +06-30-2006 2.04.25 MJ FEATURE Added SBServerGetConnectionInfo +05-31-2006 2.04.24 SAH RELEASE Releasing to developer site + SAH FIX Fixed Linux makefile +05-30-2006 2.04.24 SAH FIX Fixed PS3 projects to work with PS3(084_001 SDK) +05-25-2006 2.04.24 SAH FIX Changed PSP project warning levels +05-24-2006 2.04.23 SAH FIX added sbe_duplicateupdateerror case to switch statement +05-23-2006 2.04.22 BED FIX AuxUpdate functions will no longer update a server while an update is already pending +05-23-2006 2.04.21 SAH FIX Added win32common.c and gsTestMain.c to sbctest project +05-23-2006 2.04.20 SAH FIX Added GS_STATIC_CALLBACK to compare functions for __fastcall support +05-19-2006 2.04.19 SAH FIX Added gsTestMain.c for nitro CodeWarrior project +05-15-2006 2.04.18 SAH FIX Added "PS3 Release" configuration to project +04-25-2006 2.04.17 SAH RELEASE Releasing to developer site +04-24-2006 2.04.17 SAH FIX Fixed UNICODE compile errors, mainly typecasts +04-24-2006 2.04.16 SAH FIX Fixed SortInfo creation call, fixed Nitro project files to work on build machine +04-20-2006 2.04.15 SAH FIX Removed unnecessary break; statements, added some (char*) typecasts + SAH FIX Moved GSI_UNUSED calls above return statements +04-20-2006 2.04.14 SAH FIX Added _PS3 wrapper typecast for recvfrom calls +04-19-2006 2.04.13 SAH FIX Added || defined(_PS3) to sbctest.c for PS3 Support +04-13-2006 2.04.12 SAH FIX Added parenthesis to remove compiler warnings before Release +04-10-2006 2.04.11 SAH FEATURE Multiple sorts will retain previous sorting arrangement when values are equal +03-28-2006 2.04.10 SN FIX Added extra checks to account for empty keys +03-28-2006 2.04.10 SN FIX Fixed SBServerGetIntValue to return negative numbers +03-20-2006 2.04.09 SN OTHER Added PSP to the defines before the test_main in sbctest + SN OTHER Moved the GSI_UNUSED calls to before the return statement +01-27-2006 2.04.09 SN RELEASE Releasing to developer site +01-27-2006 2.04.09 SN FIX Added psp prodg project and solution to sgv +12-22-2005 2.04.08 SN OTHER Cleaned up project files and added missing common code. +11-17-2005 2.04.07 DES FIX Compatibility fixes. + DES FIX Updated Nitro Makefile. +11-14-2005 2.04.06 DES FIX Updated OSX support. + DES FEATURE Added GSI_DOMAIN_NAME support. +10-12-2005 2.04.05 BED RELEASE Releasing to developer site. +10-11-2005 2.04.04 BED FEATURE Added query challege/response to prevent ip spoofing. +09-23-2005 2.04.03 DES FEATURE Updated DS support + DES FIX Fixed SBServerGetBoolValue() to return the default is the value is empty +07-28-2005 2.04.02 SN RELEASE Releasing to developer site. +06-28-2005 2.04.02 BED FEATURE Added ICMP support for remaining platforms. +06-03-2005 2.04.01 SN RELEASE Releasing to developer site. +05-05-2005 2.04.01 BED FIX Updated projects to use new common folder. +o5-04-2005 2.04.00 SN OTHER Created Visual Studio .NET projects +04-29-2005 2.04.00 BED FEATURE Added support for multi-packet qr2 responses. (Full keys direct only) +04-28-2005 2.03.03 SN RELEASE Releasing to developer site. +04-27-2005 2.03.03 DES RELEASE Limited release to Nintendo DS developers. +04-27-2005 2.03.03 DES FEATURE Modified sbctest to quit when the update is complete. +04-25-2005 2.03.02 DES FIX Fixed a function call in querytest. +04-21-2005 2.03.01 DES FIX Renamed IPHeader and ICMPHeader to avoid Nitro header conflict. +04-18-2005 2.03.00 DDW FEATURE Added initial Win32 support for ICMP pings of firewalled servers + FEATURE Added new SBServerHasValidPing public function +04-04-2005 2.02.21 SN RELEASE Releasing to developer site. +03-14-2005 2.02.21 DES FEATURE Nintendo DS support +02-17-2005 2.02.20 DDW OTHER Extended gamenames in SB structures to 36 bytes from 32 (dynamic gamename support) +01-27-2005 2.02.19 DES FIX Fixed custom SN sendto and moved it to nonport +11-03-2004 2.02.18 SN FIX Fixed and turned off availability check during LAN browsing, + SN FIX Added a check to a server object when obtaining key values + SN FIX Updated SBMFC sample to have new ServerBrowserNew function +09-27-2004 2.02.17 SN RELEASE Releasing qr2 fix to developer site +09-16-2004 2.02.16 SN RELEASE Releasing to developer site. +09-08.2004 2.02.16 BED FIX Fixed cases where same server could appear in FIFO multiple times +08-27-2004 2.02.15 DES CLEANUP Fixed warnings under OSX + DES CLEANUP Updated Win32 project configurations + DES CLEANUP Removed #pragma comment for linking with winsock (in nonport now) + DES CLEANUP General Unicode cleanup + DES CLEANUP Removed MacOS style includes + DES FEATURE Added OSX Makefile +08-25-2004 2.02.14 DES FEATURE Added OSX makefile +08-04-2004 2.02.13 SN RELEASE Releasing to developer site. +07-12-2004 2.02.13 SN FIX Cleared errors when warnings are treated as errors set, and warning level is highest. +06-18-2004 2.02.12 BED RELEASE Releasing to developer site. +06-16-2004 2.02.12 BED FEATURE PS2 Insock configurations added to samples. +05-24-2004 2.02.11 BED FIX ServerBrowserAuxUpdateIP no longer adds a duplicate SBServer to the list +05-22-2004 2.02.10 BED FEATURE Ps2 Insock support added. +05-21-2004 2.02.09 DDW FEATURE VEngine & Peer compatibility fix - now local to each serverlist +05-14-2004 2.02.08 DDW FEATURE VEngine & Peer compatibility fix +05-06-2004 2.02.07 DDW FEATURE Allow external management of the unique string hash +05-06-2004 2.02.06 BED FIX SBServerGetIntValue now returns instead of 0 for a key that is empty-string. +04-02-2004 2.02.05 BED FIX Capped max recvfrom size to 2048 for PS2 INSOCK compatibility. + BED FEATURE Added ability to specify network adapter when LAN browsing. + BED FIX Changed "unsigned long" to "gsi_time" in a couple places for PS2 compatability. +03-30-2004 2.02.04 BED FIX Fixed timing issue where server might appear twice in the FIFO, causing a crash. +03-19-2004 2.02.03 BED FEATURE Added ability to specify network adapter when LAN browsing. +03-09-2004 2.02.02 BED FIX Fixed case where browsing would not complete when all servers were behind a firewall. +01-12-2004 2.02.01 BED FEATURE Added ServerBrowserGetServerByIP for IP lookup into the list. +01-07-2004 2.02.00 DDW FEATURE Added internal support for player searching +01-07-2004 2.01.01 BED FIX Added prototypes for ascii functions in UNICODE mode. +12-16-2003 2.01.00 DDW FEATURE Added internal support for map loop lookup +11-10-2003 2.00.36 DES RELEASE Releasing to developer site. +11-10-2003 2.00.36 BED FIX Fixed case where LAN browsing would never complete. +11-07-2003 2.00.35 BED FIX Update CodeWarrior project file. +11-07-2003 2.00.35 DES FIX Updated linux and PS2 makefiles. +11-06-2003 2.00.34 BED FIX Fixed case where raw buffer was treated as string (unicode conversion doesn't apply) +11-04-2003 2.00.33 BED FIX Added some const-correctness to the SDK. + BED FIX Removed misc warnings for devstudio level 4. +11-04-2003 2.00.32 DES FEATURE Added availability check code. +10-22-2003 2.00.31 BED RELEASE Releasing to developer site. (UNIQUE NICK AND UNICODE SUPPORT) +10-22-2003 2.00.31 BED FIX Removed some compiler warnings on scrict setting. +10-21-2003 2.00.30 DES FIX Added stringutil files to VC++ sample projects. +10-09-2003 2.00.29 BED FIX Switched to gsi_time type instead of unsinged long for PS2 compatibility +09-09-2003 2.00.28 BED FEATURE Added UTF-8 wrapper -- define GSI_UNICODE + OTHER Added stringutil.c and stringutil.h to sample project files +08-06-2003 2.00.27 BED FIX Increased sb_serverlist.c incoming UDP buffer size from 500 to 1500 bytes. +07-29-2003 2.00.26 BED FIX Fixed calls in sb_server.c where integers (#2) were being returned instead of SBBool. +07-24-2003 2.00.25 DES RELEASE Releasing to developer site. +07-18-2003 2.00.25 BED FEATURE Added CodeWarrior (PS2) sample project file. + BED CLEANUP General cleanup to remove CodeWarrior warnings. +07-17-2003 2.00.24 DES CLEANUP Cleaned up the PS2 Makefile, it now uses Makefile.commmon. +07-16-2003 2.00.23 DES FIX Changed a couple of __mips64 checks to _PS2 checks. + BED FEATURE Added ProDG sample project files. +07-09-2003 2.00.22 BED CLEANUP Made MFC sample's server list not continually redraw while sorting. +06-11-2003 2.00.21 DES RELEASE Releasing to developer site. +05-11-2003 2.00.21 DDW CLEANUP Modularized SBServer type handling +05-11-2003 2.00.20 DDW CLEANUP Modularized server parsing and addition +05-09-2003 2.00.19 DES CLEANUP Removed Dreamcast support. +05-09-2003 2.00.18 DDW CLEANUP Modularized refstrings and serverlist functions +05-07-2003 2.00.17 DES RELEASE Releasing to developer site. +04-07-2003 2.00.17 DES FIX Fixed semicolon after if() in SBServerListFindServer. +03-24-2003 2.00.16 DDW FEATURE Added ServerBrowserLimitUpdate function to limit the number of results returned +03-03-2003 2.00.15 DES CLEANUP General cleanup to remove warnings. +02-28-2003 2.00.14 DES RELEASE Releasing to developer site with updated QR2. +02-05-2003 2.00.14 DES RELEASE Releasing to developer site. +02-05-2003 2.00.14 DES FIX Made sure servers in MFC sample show correct info after an sbc_serverupdated. + CLEANUP Switched select calls to CanReceiveOnSocket and CanSendOnSocket. +02-04-2003 2.00.13 DES RELEASE Relasing to developer site. +02-04-2003 2.00.13 DES CLEANUP Removed assert.h include from querytest.c + FIX Use GOA/QR2 checkbox in the MFC sample. + OTHER Show servers in the MFC sample as soon as they are added (sbc_serveradded). +01-23-2003 2.00.12 DES FIX Replaced a call to free with gsifree. +12-19-2002 2.00.11 DES RELEASE Releasing to developer site. +12-19-2002 2.00.11 DES CLEANUP Removed assert.h includes. +12-13-2002 2.00.10 DES FEATURE Added PS2 eenet stack support. +12-06-2002 2.00.09 DES RELEASE Releasing to developer site with updated QR2. +12-03-2002 2.00.09 DES RELEASE Releasing to developer site. +12-03-2002 2.00.09 DES FEATURE Added a Linux Makefile. +11-22-2002 2.00.08 DES RELEASE Releasing to developer site. +11-20-2002 2.00.08 DES FEATURE Added support for compiling on the PS2. +11-15-2002 2.00.07 DDW FEATURE Abstracted the server->next pointer into SBServer.c +11-07-2002 2.00.06 DDW FEATURE Added "deadlist" support so that servers are not freed on the same pass they are removed +11-07-2002 2.00.06 DDW FIX Fixed AV when disconnecting during mainlist with data pending +10-22-2002 2.00.05 DDW FIX Correctly removes server from query engine when removed message arrives +09-26-2002 2.00.04 DES RELEASE Limited release on developer site +09-26-2002 2.00.04 DDW FIX MFC sample checks whether server has full-rules before requerying +09-26-2002 2.00.03 DES FEATURE Added MFC sample (sbmfcsample) +09-26-2002 2.00.02 DDW OTHER Changelog started +09-26-2002 2.00.02 DES FIX Fixed memory leak when cleaning up server list object +09-24-2002 2.00.01 DDW FIX Fixed protocol parsing error on packet boundary +09-23-2002 2.00.00 DDW RELEASE Release to EAPAC for Generals (Peer) +09-06-2002 2.00.00 DDW RELEASE Release to EAPAC for Generals + diff --git a/code/gamespy/serverbrowsing/sb_ascii.h b/code/gamespy/serverbrowsing/sb_ascii.h new file mode 100644 index 00000000..06d74b7f --- /dev/null +++ b/code/gamespy/serverbrowsing/sb_ascii.h @@ -0,0 +1,167 @@ +/****** +GameSpy Server Browsing SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + + Please see the GameSpy Server Browsing SDK documentation for more + information + +******/ + +// PROTOTYPES FOR ASCII VERSIONS +// This is required to silence CodeWarrior warnings about functions not having a prototype + + +#ifndef _SB_ASCII_H +#define _SB_ASCII_H + +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* +ServerBrowserNew +---------------- +Creates and returns a new (empty) ServerBrowser object. +Returns NULL if an allocation error occurs. + +queryForGamename - The gamename you are querying for +queryFromGamename - The gamename you are querying from - generally the same as queryForGamename +queryFromKey - Secret key that corresponds to the queryFromGamename +queryFromVersion - A game-specific version identifier (pass 0 unless told otherwise) +maxConcUpdates - Max number of concurent updates (10-15 for modem users, 20-30 for high-bandwidth) +queryVersion - Query protocol to use. Use QVERSION_GOA for DeveloperSpec/Query&Reporting1 games, and QVERSION_QR2 for games that use Query & Reporting 2 +callback - The function that will be called with list updates +instance - User-defined instance data (e.g. structure or object pointer) */ +ServerBrowser ServerBrowserNewA(const char *queryForGamename, const char *queryFromGamename, const char *queryFromKey, int queryFromVersion, int maxConcUpdates, int queryVersion, SBBool lanBrowse, ServerBrowserCallback callback, void *instance); + + +/* ServerBrowserUpdate +------------------- +Starts an update by downloading a list of servers from the master server, then querying them. + +sb - The server browser object to update +async - If SBTrue, the update will be initiated, and ServerListThink must be called for processing and querying to occur + If SBFalse, the function will not return until the initial list of servers has been completely updated +disconnectOnComplete - If SBTrue, the connection to the master server will be disconnected immediately after the list is downloaded. + If SBFalse, the connection will be left open for additional data queries, and can be closed via ServerBrowserDisconnect +basicFields - This array of registered QR2 keys is used to determine the fields requested from servers during the initial "basic" update. + Only server keys listed in this array will be returned for servers. +numBasicFields - The number of fields in the basicFields array +serverFilter - SQL Filter string that will be applied on the master server to limit the list of servers returned. + All server keys are available for filtering on the master server, as well as the master-defined "country" and "region" keys. + Standard SQL syntax should be used. + +ServerBrowserLimitUpdate +------------------------ +Identical to ServerBrowserUpdate, except that the number of servers returned can be limited +maxServers - Maximum number of servers to be returned +*/ +SBError ServerBrowserUpdateA(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const char *serverFilter); +SBError ServerBrowserLimitUpdateA(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const char *serverFilter, int maxServers); + + +/* ServerBrowserAuxUpdateIP +------------------- +Manually updates a server given an IP address and query port. Use to manually add servers to the list when you just +have an IP and port for them. + +sb - The server browser object to add the server to +ip - The dotted IP address of the server e.g. "1.2.3.4" +port - The query port of the server +viaMaster - If SBTrue, information about the server will be retrieved from the master server instead of attempting to query the server directly. + If a connection to the master server does not exist, it will be made to kept open afterwards. + If SBFalse, the server will be contacted directly for information. +async - If SBTrue, the update will be initiated, and ServerListThink must be called for processing and querying to occur + If SBFalse, the function will not return until the server has been successfully or unsuccessfully updated +fullUpdate - If SBTrue, all server keys/rules/player/team information will be retrieved + If SBFalse, only the keys specified in the basicFields array of the ServerBrowserUpdate function will be retrieved */ +SBError ServerBrowserAuxUpdateIPA(ServerBrowser sb, const char *ip, unsigned short port, SBBool viaMaster, SBBool async, SBBool fullUpdate); + +/* ServerBrowserRemoveIP +------------------- +Removes a server from the list given an IP and query port */ +void ServerBrowserRemoveIPA(ServerBrowser sb, const char *ip, unsigned short port); + +/* ServerBrowserErrorDesc +------------------- +Returns a human-readable error string for the given error code. */ +const char *ServerBrowserErrorDescA(ServerBrowser sb, SBError error); + +/* ServerBrowserListQueryError +------------------- +When a list query error occurs, as indicated by the sbc_queryerror callback, this function allows you to +obtain the human-readable error string for the error (generally these errors are caused by errors in the +filter string) */ +const char *ServerBrowserListQueryErrorA(ServerBrowser sb); + + +/* ServerBrowserSendNatNegotiateCookieToServer +------------------ +Sends a cookie value to the server for use with NAT Negotiation */ +SBError ServerBrowserSendNatNegotiateCookieToServerA(ServerBrowser sb, const char *ip, unsigned short port, int cookie); + + +/* ServerBrowserSendMessageToServer +------------------ +Sends a game-specific message to a server */ +SBError ServerBrowserSendMessageToServerA(ServerBrowser sb, const char *ip, unsigned short port, const char *data, int len); + + +/* ServerBrowserSort +----------------- +Sort the server list in either ascending or descending order using the +specified comparemode. +sortkey can be a normal server key, or "ping" or "hostaddr" */ +void ServerBrowserSortA(ServerBrowser sb, SBBool ascending, const char *sortkey, SBCompareMode comparemode); + +/******************* +SBServer Object Functions +********************/ + + +/* SBServerGetPublicAddress/SBServerGetPrivateAddress +------------------- +Returns the string, dotted IP address for the specified server +The "private" version is only valid when the server has a private address available */ +char *SBServerGetPublicAddress(SBServer server); +char *SBServerGetPrivateAddress(SBServer server); + + +/* SBServerGet[]Value +------------------ +Returns the value for the specified key. If the key does not exist for the +given server, the default value is returned */ +const char *SBServerGetStringValueA(SBServer server, const char *keyname, const char *def); +int SBServerGetIntValueA(SBServer server, const char *key, int idefault); +double SBServerGetFloatValueA(SBServer server, const char *key, double fdefault); +SBBool SBServerGetBoolValueA(SBServer server, const char *key, SBBool bdefault); + + +/* SBServerGetPlayer[]Value / SBServerGetTeam[]Value +------------------ +Returns the value for the specified key on the specified player or team. If the key does not exist for the +given server, the default value is returned +Player keys take the form keyname_N where N is the player index, and team keys take the form +keyname_tN where N is the team index. You should only specify the keyname for the key in the below functions. +*/ +const char *SBServerGetPlayerStringValueA(SBServer server, int playernum, const char *key, const char *sdefault); +int SBServerGetPlayerIntValueA(SBServer server, int playernum, const char *key, int idefault); +double SBServerGetPlayerFloatValueA(SBServer server, int playernum, const char *key, double fdefault); + +const char *SBServerGetTeamStringValueA(SBServer server, int teamnum, const char *key, const char *sdefault); +int SBServerGetTeamIntValueA(SBServer server, int teamnum, const char *key, int idefault); +double SBServerGetTeamFloatValueA(SBServer server, int teamnum, const char *key, double fdefault); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/serverbrowsing/sb_crypt.c b/code/gamespy/serverbrowsing/sb_crypt.c new file mode 100644 index 00000000..85b5fd56 --- /dev/null +++ b/code/gamespy/serverbrowsing/sb_crypt.c @@ -0,0 +1,212 @@ +#include +#include +#include "sb_crypt.h" + + +static unsigned char keyrand(GOACryptState *state, int limit, + unsigned char *user_key, + unsigned char keysize, + unsigned char *rsum, + unsigned *keypos) +{ + unsigned int u, // Value from 0 to limit to return. + retry_limiter, // No infinite loops allowed. + mask; // Select just enough bits. + + if (!limit) return 0; // Avoid divide by zero error. + retry_limiter = 0; + mask = 1; // Fill mask with enough bits to cover + while (mask < (unsigned)limit) // the desired range. + mask = (mask << 1) + 1; + do + { + *rsum = (unsigned char)(state->cards[*rsum] + user_key[(*keypos)++]); + if (*keypos >= keysize) + { + *keypos = 0; // Recycle the user key. + *rsum = (unsigned char)(*rsum + keysize); // key "aaaa" != key "aaaaaaaa" + } + u = mask & *rsum; + if (++retry_limiter > 11) + u %= (unsigned int)limit; // Prevent very rare long loops. + } + while (u > (unsigned)limit); + return (unsigned char)(u); +} + + +void GOAHashInit(GOACryptState *state) +{ + // This function is used to initialize non-keyed hash + // computation. + + int i, j; + + // Initialize the indices and data dependencies. + + state->rotor = 1; + state->ratchet = 3; + state->avalanche = 5; + state->last_plain = 7; + state->last_cipher = 11; + + // Start with state->cards all in inverse order. + + for (i=0, j=255;i<256;i++,j--) + state->cards[i] = (unsigned char) j; +} + + +void GOACryptInit(GOACryptState *state, unsigned char *key, unsigned char keysize) +{ + // Key size may be up to 256 bytes. + // Pass phrases may be used directly, with longer length + // compensating for the low entropy expected in such keys. + // Alternatively, shorter keys hashed from a pass phrase or + // generated randomly may be used. For random keys, lengths + // of from 4 to 16 bytes are recommended, depending on how + // secure you want this to be. + + int i; + unsigned char toswap, swaptemp, rsum; + unsigned keypos; + + // If we have been given no key, assume the default hash setup. + + if (keysize < 1) + { + GOAHashInit(state); + return; + } + + // Start with state->cards all in order, one of each. + + for (i=0;i<256;i++) + state->cards[i] = (unsigned char)(i); + + // Swap the card at each position with some other card. + + toswap = 0; + keypos = 0; // Start with first byte of user key. + rsum = 0; + for (i=255;i>=0;i--) + { + toswap = keyrand(state, i, key, keysize, &rsum, &keypos); + swaptemp = state->cards[i]; + state->cards[i] = state->cards[toswap]; + state->cards[toswap] = swaptemp; + } + + // Initialize the indices and data dependencies. + // Indices are set to different values instead of all 0 + // to reduce what is known about the state of the state->cards + // when the first byte is emitted. + + state->rotor = state->cards[1]; + state->ratchet = state->cards[3]; + state->avalanche = state->cards[5]; + state->last_plain = state->cards[7]; + state->last_cipher = state->cards[rsum]; + + toswap = swaptemp = rsum = 0; + keypos = 0; +} + + +unsigned char GOAEncryptByte(GOACryptState *state, unsigned char b) +{ + // Picture a single enigma state->rotor with 256 positions, rewired + // on the fly by card-shuffling. + + // This cipher is a variant of one invented and written + // by Michael Paul Johnson in November, 1993. + + unsigned char swaptemp; + + // Shuffle the deck a little more. + + state->ratchet = (unsigned char)(state->ratchet + state->cards[state->rotor++]); + swaptemp = state->cards[state->last_cipher]; + state->cards[state->last_cipher] = state->cards[state->ratchet]; + state->cards[state->ratchet] = state->cards[state->last_plain]; + state->cards[state->last_plain] = state->cards[state->rotor]; + state->cards[state->rotor] = swaptemp; + state->avalanche = (unsigned char)(state->avalanche + state->cards[swaptemp]); + + // Output one byte from the state in such a way as to make it + // very hard to figure out which one you are looking at. + /* + state->last_cipher = b^state->cards[(state->cards[state->ratchet] + state->cards[state->rotor]) & 0xFF] ^ + state->cards[state->cards[(state->cards[state->last_plain] + + state->cards[state->last_cipher] + + state->cards[state->avalanche])&0xFF]]; + */ + state->last_cipher = (unsigned char)(b^state->cards[(state->cards[state->avalanche] + state->cards[state->rotor]) & 0xFF] ^ + state->cards[state->cards[(state->cards[state->last_plain] + + state->cards[state->last_cipher] + + state->cards[state->ratchet])&0xFF]]); + state->last_plain = b; + return state->last_cipher; +} + + +void GOAEncrypt(GOACryptState *state, unsigned char *bp, int len) +{ + int i; + for (i = 0 ; i < len ; i++) + { + bp[i] = GOAEncryptByte(state, bp[i]); + } +} + +unsigned char GOADecryptByte(GOACryptState *state, unsigned char b) +{ + unsigned char swaptemp; + + // Shuffle the deck a little more. + + state->ratchet = (unsigned char)(state->ratchet + state->cards[state->rotor++]); + swaptemp = state->cards[state->last_cipher]; + state->cards[state->last_cipher] = state->cards[state->ratchet]; + state->cards[state->ratchet] = state->cards[state->last_plain]; + state->cards[state->last_plain] = state->cards[state->rotor]; + state->cards[state->rotor] = swaptemp; + state->avalanche = (unsigned char)(state->avalanche + state->cards[swaptemp]); + + // Output one byte from the state in such a way as to make it + // very hard to figure out which one you are looking at. + /* + state->last_plain = b^state->cards[(state->cards[state->ratchet] + state->cards[state->rotor]) & 0xFF] ^ + state->cards[state->cards[(state->cards[state->last_plain] + + state->cards[state->last_cipher] + + state->cards[state->avalanche])&0xFF]]; + */ + //crt - change this around + state->last_plain = (unsigned char)(b^state->cards[(state->cards[state->avalanche] + state->cards[state->rotor]) & 0xFF] ^ + state->cards[state->cards[(state->cards[state->last_plain] + + state->cards[state->last_cipher] + + state->cards[state->ratchet])&0xFF]]); + state->last_cipher = b; + return state->last_plain; +} + +void GOADecrypt(GOACryptState *state, unsigned char *bp, int len) +{ + int i; + for (i = 0 ; i < len ; i++) + { + bp[i] = GOADecryptByte(state, bp[i]); + } +} + +void GOAHashFinal(GOACryptState *state, unsigned char *outputhash, // Destination + unsigned char hashlength) // Size of hash. +{ + int i; + + for (i=255;i>=0;i--) + GOAEncryptByte(state, (unsigned char) i); + for (i=0;istate +#define STATE_BASICKEYS (1 << 0) +#define STATE_FULLKEYS (1 << 1) +#define STATE_PENDINGBASICQUERY (1 << 2) +#define STATE_PENDINGFULLQUERY (1 << 3) +#define STATE_QUERYFAILED (1 << 4) +#define STATE_PENDINGICMPQUERY (1 << 5) +#define STATE_VALIDPING (1 << 6) +#define STATE_PENDINGQUERYCHALLENGE (1 << 7) + +//how long before a server query times out +#define MAX_QUERY_MSEC 2500 + +//game server flags +#define UNSOLICITED_UDP_FLAG 1 +#define PRIVATE_IP_FLAG 2 +#define CONNECT_NEGOTIATE_FLAG 4 +#define ICMP_IP_FLAG 8 +#define NONSTANDARD_PORT_FLAG 16 +#define NONSTANDARD_PRIVATE_PORT_FLAG 32 +#define HAS_KEYS_FLAG 64 +#define HAS_FULL_RULES_FLAG 128 + +//backend query flags (set in hbmaster, don't change) +#define QR2_USE_QUERY_CHALLENGE 128 + +//key types for the key type list +#define KEYTYPE_STRING 0 +#define KEYTYPE_BYTE 1 +#define KEYTYPE_SHORT 2 + +//how long to make the outgoing challenge +#define LIST_CHALLENGE_LEN 8 + +//protocol versions +#define LIST_PROTOCOL_VERSION 1 +#define LIST_ENCODING_VERSION 3 + +//message types for outgoing requests +#define SERVER_LIST_REQUEST 0 +#define SERVER_INFO_REQUEST 1 +#define SEND_MESSAGE_REQUEST 2 +#define KEEPALIVE_REPLY 3 +#define MAPLOOP_REQUEST 4 +#define PLAYERSEARCH_REQUEST 5 + +//message types for incoming requests +#define PUSH_KEYS_MESSAGE 1 +#define PUSH_SERVER_MESSAGE 2 +#define KEEPALIVE_MESSAGE 3 +#define DELETE_SERVER_MESSAGE 4 +#define MAPLOOP_MESSAGE 5 +#define PLAYERSEARCH_MESSAGE 6 + +//server list update options +#define SEND_FIELDS_FOR_ALL 1 +#define NO_SERVER_LIST 2 +#define PUSH_UPDATES 4 +#define SEND_GROUPS 32 +#define NO_LIST_CACHE 64 +#define LIMIT_RESULT_COUNT 128 + +//player search options +#define SEARCH_ALL_GAMES 1 +#define SEARCH_LEFT_SUBSTRING 2 +#define SEARCH_RIGHT_SUBSTRING 4 +#define SEARCH_ANY_SUBSTRING 8 + +//max number of keys for the basic key list +#define MAX_QUERY_KEYS 20 + +//how long to search on the LAN +#define SL_LAN_SEARCH_TIME 2000 + +//MAGIC bytes for the QR2 queries +#define QR2_MAGIC_1 0xFE +#define QR2_MAGIC_2 0xFD + +//magic bytes for nat negotiation message +#define NATNEG_MAGIC_LEN 6 +#define NN_MAGIC_0 0xFD +#define NN_MAGIC_1 0xFC +#define NN_MAGIC_2 0x1E +#define NN_MAGIC_3 0x66 +#define NN_MAGIC_4 0x6A +#define NN_MAGIC_5 0xB2 + + +//query types +#define QTYPE_BASIC 0 +#define QTYPE_FULL 1 +#define QTYPE_ICMP 2 + +//query strings for old-style servers +#define BASIC_GOA_QUERY "\\basic\\\\info\\" +#define BASIC_GOA_QUERY_LEN 13 +#define FULL_GOA_QUERY "\\status\\" +#define FULL_GOA_QUERY_LEN 8 + +//maximum length of a sortkey string +#define SORTKEY_LENGTH 255 + +//include ICMP support by default +#ifndef SB_NO_ICMP_SUPPORT + #undef SB_ICMP_SUPPORT + #define SB_ICMP_SUPPORT +#endif + +//a key/value pair +typedef struct _SBKeyValuePair +{ + const char *key; + const char *value; +} SBKeyValuePair; + + +//a ref-counted string +typedef struct _SBRefString +{ + const char *str; +#ifdef GSI_UNICODE + const unsigned short *str_W; +#endif + int refcount; +} SBRefString; + + +typedef struct _SBServerList *SBServerListPtr; +typedef struct _SBQueryEngine *SBQueryEnginePtr; + +//callback types for server lists +typedef enum {slc_serveradded, slc_serverupdated, slc_serverdeleted, slc_initiallistcomplete, slc_disconnected, slc_queryerror, slc_publicipdetermined, slc_serverchallengereceived} SBListCallbackReason; +//callback types for query engine +typedef enum {qe_updatesuccess, qe_updatefailed, qe_engineidle, qe_challengereceived} SBQueryEngineCallbackReason; + +//callback function prototypes +typedef void (*SBListCallBackFn)(SBServerListPtr serverlist, SBListCallbackReason reason, SBServer server, void *instance); +typedef void (*SBEngineCallbackFn)(SBQueryEnginePtr engine, SBQueryEngineCallbackReason reason, SBServer server, void *instance); +typedef void (*SBMaploopCallbackFn)(SBServerListPtr serverlist, SBServer server, time_t mapChangeTime, int numMaps, char *mapList[], void *instance); +typedef void (*SBPlayerSearchCallbackFn)(SBServerListPtr serverlist, char *nick, goa_uint32 serverIP, unsigned short serverPort, time_t lastSeenTime, char *gamename, void *instance); + +//key information structure +typedef struct _KeyInfo +{ + const char *keyName; + int keyType; +} KeyInfo; + + +typedef struct _SBServerList SBServerList; + +#ifdef VENGINE_SUPPORT + #define FTABLE_TYPES + #include "../../VEngine/ve_gm3ftable.h" +#endif + + +//keeps track of previous and current sorting information +typedef struct _SortInfo +{ + gsi_char sortkey[SORTKEY_LENGTH]; + SBCompareMode comparemode; +} SortInfo; + +struct _SBServerList +{ + SBServerListState state; + DArray servers; + DArray keylist; + char queryforgamename[36]; + char queryfromgamename[36]; + char queryfromkey[32]; + char mychallenge[LIST_CHALLENGE_LEN]; + char *inbuffer; + int inbufferlen; + const char *popularvalues[MAX_POPULAR_VALUES]; + int numpopularvalues; + int expectedelements; + + SBListCallBackFn ListCallback; + SBMaploopCallbackFn MaploopCallback; + SBPlayerSearchCallbackFn PlayerSearchCallback; + void *instance; + + SortInfo currsortinfo; + SortInfo prevsortinfo; + + SBBool sortascending; + goa_uint32 mypublicip; + goa_uint32 srcip; + unsigned short defaultport; + + char *lasterror; +#ifdef GSI_UNICODE + unsigned short *lasterror_W; +#endif + + SOCKET slsocket; + gsi_time lanstarttime; + int fromgamever; + GOACryptState cryptkey; + int queryoptions; + SBListParseState pstate; + gsi_u16 backendgameflags; + + const char* mLanAdapterOverride; + + SBServer deadlist; + +#ifdef VENGINE_SUPPORT + #define FTABLE_IMPLEMENT + #include "../../VEngine/ve_gm3ftable.h" +#endif + +}; + + +//server object + +#ifndef SB_SERVER_DECLARED +#define SB_SERVER_DECLARED + +struct _SBServer +{ + goa_uint32 publicip; + unsigned short publicport; + goa_uint32 privateip; + unsigned short privateport; + goa_uint32 icmpip; + unsigned char state; + unsigned char flags; + HashTable keyvals; + gsi_time updatetime; + gsi_u32 querychallenge; + struct _SBServer *next; + gsi_u8 splitResponseBitmap; +}; + +#endif + +typedef struct _SBServerFIFO +{ + SBServer first; + SBServer last; + int count; +} SBServerFIFO; + +typedef struct _SBQueryEngine +{ + int queryversion; + int maxupdates; + SBServerFIFO querylist; + SBServerFIFO pendinglist; + SOCKET querysock; + #if !defined(SN_SYSTEMS) + SOCKET icmpsock; + #endif + goa_uint32 mypublicip; + unsigned char serverkeys[MAX_QUERY_KEYS]; + int numserverkeys; + SBEngineCallbackFn ListCallback; + void *instance; +} SBQueryEngine; + + +struct _ServerBrowser +{ + SBQueryEngine engine; + SBServerList list; + SBBool disconnectFlag; + SBBool dontUpdate; + goa_uint32 triggerIP; + unsigned short triggerPort; + ServerBrowserCallback BrowserCallback; + SBConnectToServerCallback ConnectCallback; + void *instance; +}; + +#define SB_ICMP_ECHO 8 +#define SB_ICMP_ECHO_REPLY 0 + +typedef struct _IPHeader { + gsi_u8 ip_hl_ver; + gsi_u8 ip_tos; + gsi_i16 ip_len; + gsi_u16 ip_id; + gsi_i16 ip_off; + gsi_u8 ip_ttl; + gsi_u8 ip_p; + gsi_u16 ip_sum; + struct in_addr ip_src,ip_dst; +} SBIPHeader; + + + +typedef struct _ICMPHeader +{ + gsi_u8 type; + gsi_u8 code; + gsi_u16 cksum; + union { + struct { + gsi_u16 id; + gsi_u16 sequence; + } echo; + gsi_u32 idseq; + gsi_u16 gateway; + struct { + gsi_u16 __notused; + gsi_u16 mtu; + } frag; + } un; +} SBICMPHeader; + + +//server list functions +void SBServerListInit(SBServerList *slist, const char *queryForGamename, const char *queryFromGamename, const char *queryFromKey, int queryFromVersion, SBBool lanBrowse, SBListCallBackFn callback, void *instance); +SBError SBServerListConnectAndQuery(SBServerList *slist, const char *fieldList, const char *serverFilter, int options, int maxServers); +SBError SBServerListGetLANList(SBServerList *slist, unsigned short startport, unsigned short endport, int queryversion); +void SBServerListDisconnect(SBServerList *slist); +void SBServerListCleanup(SBServerList *slist); +void SBServerListClear(SBServerList *slist); +SBError SBGetServerRulesFromMaster(SBServerList *slist, goa_uint32 ip, unsigned short port); +SBError SBSendMessageToServer(SBServerList *slist, goa_uint32 ip, unsigned short port, const char *data, int len); +SBError SBSendNatNegotiateCookieToServer(SBServerList *slist, goa_uint32 ip, unsigned short port, int cookie); +SBError SBSendMaploopRequest(SBServerList *slist, SBServer server, SBMaploopCallbackFn callback); +SBError SBSendPlayerSearchRequest(SBServerList *slist, char *searchName, int searchOptions, int maxResults, SBPlayerSearchCallbackFn callback); +int SBServerListFindServerByIP(SBServerList *slist, goa_uint32 ip, unsigned short port); +int SBServerListFindServer(SBServerList *slist, SBServer findserver); +void SBServerListRemoveAt(SBServerList *slist, int index); +void SBServerListAppendServer(SBServerList *slist, SBServer server); +void SBServerListSort(SBServerList *slist, SBBool ascending, SortInfo sortinfo); +int SBServerListCount(SBServerList *slist); +SBServer SBServerListNth(SBServerList *slist, int i); +SBError SBListThink(SBServerList *slist); +const char *SBLastListErrorA(SBServerList *slist); +const unsigned short *SBLastListErrorW(SBServerList *slist); +void SBSetLastListErrorPtr(SBServerList *slist, char* theError); +void SBFreeDeadList(SBServerList *slist); +void SBAllocateServerList(SBServerList *slist); + +//sbserver functions +SBServer SBAllocServer(SBServerList *slist, goa_uint32 publicip, unsigned short publicport); +void SBServerFree(void *elem); +void SBServerAddKeyValue(SBServer server, const char *keyname, const char *value); +void SBServerAddIntKeyValue(SBServer server, const char *keyname, int value); +void SBServerParseKeyVals(SBServer server, char *keyvals); +void SBServerParseQR2FullKeysSingle(SBServer server, char *data, int len); +void SBServerParseQR2FullKeysSplit(SBServer server, char *data, int len); +void SBServerSetFlags(SBServer server, unsigned char flags); +void SBServerSetPublicAddr(SBServer server, goa_uint32 ip, unsigned short port); +void SBServerSetPrivateAddr(SBServer server, goa_uint32 ip, unsigned short port); +void SBServerSetICMPIP(SBServer server, goa_uint32 icmpip); +void SBServerSetState(SBServer server, unsigned char state); +void SBServerSetNext(SBServer server, SBServer next); +SBServer SBServerGetNext(SBServer server); +unsigned char SBServerGetState(SBServer server); +unsigned char SBServerGetFlags(SBServer server); +unsigned short SBServerGetPublicQueryPortNBO(SBServer server); +int SBIsNullServer(SBServer server); +extern SBServer SBNullServer; + + +//ref-str functions +const char *SBRefStr(SBServerList *slist,const char *str); +void SBReleaseStr(SBServerList *slist,const char *str); +HashTable SBRefStrHash(SBServerList *slist); +void SBRefStrHashCleanup(SBServerList *slist); + +extern char *SBOverrideMasterServer; + + +//query engine functions +void SBQueryEngineInit(SBQueryEngine *engine, int maxupdates, int queryversion, SBBool lanBrowse, SBEngineCallbackFn callback, void *instance); +void SBQueryEngineUpdateServer(SBQueryEngine *engine, SBServer server, int addfront, int querytype, SBBool usequerychallenge); +void SBQueryEngineSetPublicIP(SBQueryEngine *engine, goa_uint32 mypublicip); +SBServer SBQueryEngineUpdateServerByIP(SBQueryEngine *engine, const char *ip, unsigned short queryport, int addfront, int querytype, SBBool usequerychallenge); +void SBQueryEngineThink(SBQueryEngine *engine); +void SBQueryEngineAddQueryKey(SBQueryEngine *engine, unsigned char keyid); +void SBEngineCleanup(SBQueryEngine *engine); +void SBQueryEngineRemoveServerFromFIFOs(SBQueryEngine *engine, SBServer server); +int NTSLengthSB(char *buf, int len); +void SBEngineHaltUpdates(SBQueryEngine *engine); + + +//server browser internal function +SBError ServerBrowserBeginUpdate2(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const char *serverFilter, int updateOptions, int maxServers); + + +// Ascii versions of functions that should still be available in GSI_UNICODE mode +#ifdef GSI_UNICODE +const char *SBServerGetStringValueA(SBServer server, const char *keyname, const char *def); // for peer SDK +int SBServerGetIntValueA(SBServer server, const char *key, int idefault); +#endif + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/serverbrowsing/sb_queryengine.c b/code/gamespy/serverbrowsing/sb_queryengine.c new file mode 100644 index 00000000..ae6015c6 --- /dev/null +++ b/code/gamespy/serverbrowsing/sb_queryengine.c @@ -0,0 +1,700 @@ +#include "sb_serverbrowsing.h" +#include "sb_internal.h" + +#ifdef GSI_MANIC_DEBUG +// Make sure the server isn't already in the fifo +void FIFODebugCheckAdd(SBServerFIFO *fifo, SBServer server) +{ + SBServer aServer = fifo->first; + while(aServer != NULL) + { + assert(aServer != server); + aServer = aServer->next; + } +} + + +// Verify the contents of the fifo +void FIFODebugCheck(SBServerFIFO *fifo) +{ + int i=0; + SBServer aServer; + + assert(fifo != NULL); + aServer = fifo->first; + for (i=0; i < fifo->count; i++) + { + assert(aServer != NULL); + aServer = aServer->next; + } +} +#else +#define FIFODebugCheckAdd(a,b) +#define FIFODebugCheck(a) +#endif + +//FIFO Queue management functions +static void FIFOAddRear(SBServerFIFO *fifo, SBServer server) +{ + FIFODebugCheckAdd(fifo, server); + + if (fifo->last != NULL) + fifo->last->next = server; + fifo->last = server; + server->next = NULL; + if (fifo->first == NULL) + fifo->first = server; + fifo->count++; + + FIFODebugCheck(fifo); +} + +static void FIFOAddFront(SBServerFIFO *fifo, SBServer server) +{ + FIFODebugCheckAdd(fifo, server); + + server->next = fifo->first; + fifo->first = server; + if (fifo->last == NULL) + fifo->last = server; + fifo->count++; + + FIFODebugCheck(fifo); +} + +static SBServer FIFOGetFirst(SBServerFIFO *fifo) +{ + SBServer hold; + hold = fifo->first; + if (hold != NULL) + { + fifo->first = hold->next; + if (fifo->first == NULL) + fifo->last = NULL; + fifo->count--; + } + + FIFODebugCheck(fifo); + return hold; +} + +static SBBool FIFORemove(SBServerFIFO *fifo, SBServer server) +{ + SBServer hold, prev; + prev = NULL; + hold = fifo->first; + while (hold != NULL) + { + if (hold == server) //found + { + if (prev != NULL) //there is a previous.. + prev->next = hold->next; + if (fifo->first == hold) + fifo->first = hold->next; + if (fifo->last == hold) + fifo->last = prev; + fifo->count--; + // assert((fifo->count == 0 && fifo->first == NULL && fifo->last == NULL) || fifo->count > 0); + return SBTrue; + } + prev = hold; + hold = hold->next; + } + + FIFODebugCheck(fifo); + return SBFalse; +} + +static void FIFOClear(SBServerFIFO *fifo) +{ + fifo->first = fifo->last = NULL; + fifo->count = 0; + + FIFODebugCheck(fifo); +} + +#ifdef SB_ICMP_SUPPORT +static unsigned short IPChecksum(const unsigned short *buf, int len) +{ + unsigned long cksum = 0; + + //Calculate the checksum + while (len > 1) + { + cksum += *buf++; + len -= sizeof(unsigned short); + } + + //If we have one char left + if (len) { + cksum += *(unsigned char*)buf; + } + + //Complete the calculations + cksum = (cksum >> 16) + (cksum & 0xffff); + cksum += (cksum >> 16); + + //Return the value (inversed) + return (unsigned short)(~cksum); +} +#endif + +static void QEStartQuery(SBQueryEngine *engine, SBServer server) +{ + unsigned char queryBuffer[256]; + int queryLen; + gsi_bool querySuccess = gsi_false; + struct sockaddr_in saddr; + + saddr.sin_family = AF_INET; + server->updatetime = current_time(); + + if (server->state & STATE_PENDINGICMPQUERY) //send an ICMP ping request + { +#ifdef SB_ICMP_SUPPORT + #if !defined(SN_SYSTEMS) + SBICMPHeader *_icmp = (SBICMPHeader *)(queryBuffer); + //todo: alignment issues on PS2 + _icmp->type = SB_ICMP_ECHO; + _icmp->code = 0; + _icmp->un.idseq = server->updatetime; //no need for network byte order since only we read the reply + _icmp->cksum = 0; + queryLen = sizeof(SBICMPHeader) + 6; + memcpy(queryBuffer + sizeof(SBICMPHeader), &server->publicip, 4); //put some data in the echo packet that we can use to verify the reply + memcpy(queryBuffer + sizeof(SBICMPHeader) + 4, &server->publicport, 2); + _icmp->cksum = IPChecksum((unsigned short *)queryBuffer, queryLen); + if (SBServerGetFlags(server) & ICMP_IP_FLAG) //there is a special ICMP address + { + saddr.sin_addr.s_addr = server->icmpip; + } else + { + saddr.sin_addr.s_addr = server->publicip; + } + + sendto(engine->icmpsock, (char *)queryBuffer, queryLen, 0, (struct sockaddr *)&saddr, sizeof(saddr)); + querySuccess = gsi_true; + #else + int result; + sndev_set_ping_ip_type optval; + optval.ip_addr = server->icmpip; + + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Attempting to send ICMP ping to %s\r\n", inet_ntoa(*((struct in_addr*)&server->icmpip))); + + result = sndev_set_options(0, SN_DEV_SET_PING_IP, (void*)&optval, sizeof(optval)); // tell SN to ping this addr + if (result==SN_PING_FAIL) + { + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError, + "ICMP ping attempt failed on SNSystems (unknown error)\r\n"); + querySuccess = gsi_true; // let the SDK process failures as a timed out ping + } + else if (result==SN_PING_FULL) + { + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError, + "ICMP ping attempt failed on SNSystems (SN_PING_FULL)\r\n"); + querySuccess = gsi_false; + } + else + querySuccess = gsi_true; + #endif +#endif + } else //send a UDP query + { + if (engine->queryversion == QVERSION_QR2) + { + if (server->state & STATE_PENDINGQUERYCHALLENGE) + { + // send IP verify request now, but send qr2 query later when IP verify returns + int pos = 0; + queryBuffer[pos++] = QR2_MAGIC_1; + queryBuffer[pos++] = QR2_MAGIC_2; + queryBuffer[pos++] = 0x09; // ip verify prequery + //set the request key + memcpy(&queryBuffer[pos], &server->updatetime, 4); + pos += 4; + queryLen = pos; + } + else + { + gsi_u32 challengeNBO = htonl(server->querychallenge); + int pos = 0; + + //set the header + queryBuffer[pos++] = QR2_MAGIC_1; + queryBuffer[pos++] = QR2_MAGIC_2; + queryBuffer[pos++] = 0; + memcpy(&queryBuffer[pos], &server->updatetime, 4); //set the request key + pos += 4; + if (challengeNBO != 0) + { + memcpy(&queryBuffer[pos], &challengeNBO, 4); // set the challenge + pos += 4; + } + if (server->state & STATE_PENDINGBASICQUERY) + { + int i; + queryBuffer[pos++] = (unsigned char)engine->numserverkeys; + for (i = 0 ; i < engine->numserverkeys ; i++) + queryBuffer[pos++] = engine->serverkeys[i]; + + //don't request any player or team keys + queryBuffer[pos++] = 0x00; + queryBuffer[pos++] = 0x00; + queryLen = pos; + + } else //request all keys for everyone + { + queryBuffer[pos++] = 0xFF; + queryBuffer[pos++] = 0xFF; + queryBuffer[pos++] = 0xFF; + + // Tell the server we support split packets + queryBuffer[pos++] = 0x1; + queryLen = pos; // 11 + } + } + } else //GOA + { + if (server->state & STATE_PENDINGBASICQUERY) //original - do a \basic\info\ query + { + memcpy(queryBuffer, BASIC_GOA_QUERY, BASIC_GOA_QUERY_LEN); + queryLen = BASIC_GOA_QUERY_LEN; + } else //original - do a \status\ query + { + memcpy(queryBuffer, FULL_GOA_QUERY, FULL_GOA_QUERY_LEN); + queryLen = FULL_GOA_QUERY_LEN; + } + } + if (server->publicip == engine->mypublicip && (server->flags & PRIVATE_IP_FLAG)) //try querying the private IP + { + saddr.sin_addr.s_addr = server->privateip; + saddr.sin_port = server->privateport; + } else + { + saddr.sin_addr.s_addr = server->publicip; + saddr.sin_port = server->publicport; + } + sendto(engine->querysock, (char *)queryBuffer, queryLen, 0, (struct sockaddr *)&saddr, sizeof(saddr)); + querySuccess = gsi_true; + } + + //add it to the query list + if (gsi_is_true(querySuccess)) + FIFOAddRear(&engine->querylist, server); + else + server->updatetime = 0; +} + + +void SBQueryEngineInit(SBQueryEngine *engine, int maxupdates, int queryversion, SBBool lanBrowse, SBEngineCallbackFn callback, void *instance) +{ + // 11-03-2004 : Added by Saad Nader + // fix for LANs and unnecessary availability check + /////////////////////////////////////////////////// + if(lanBrowse == SBFalse) + { + if(__GSIACResult != GSIACAvailable) + return; + } + SocketStartUp(); + engine->queryversion = queryversion; + engine->maxupdates = maxupdates; + engine->numserverkeys = 0; + engine->ListCallback = callback; + engine->instance = instance; + engine->mypublicip = 0; + engine->querysock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); +#if defined(SB_ICMP_SUPPORT) + #if defined(SN_SYSTEMS) + { + // reset SNSystems internal ICMP ping structures + sndev_set_ping_reset_type optval; + optval.timeout_ms = MAX_QUERY_MSEC; // this gets rounded up to 3 sec + optval.reserved = 0; + sndev_set_options(0, SN_DEV_SET_PING_RESET, &optval, sizeof(optval)); + } + #else + engine->icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + #endif +#endif + FIFOClear(&engine->pendinglist); + FIFOClear(&engine->querylist); +} + +void SBQueryEngineSetPublicIP(SBQueryEngine *engine, goa_uint32 mypublicip) +{ + engine->mypublicip = mypublicip; +} + +void SBEngineHaltUpdates(SBQueryEngine *engine) +{ + FIFOClear(&engine->pendinglist); + FIFOClear(&engine->querylist); +} + + +void SBEngineCleanup(SBQueryEngine *engine) +{ + closesocket(engine->querysock); +#ifdef SB_ICMP_SUPPORT + #if !defined(SN_SYSTEMS) + closesocket(engine->icmpsock); + #endif +#endif + engine->querysock = INVALID_SOCKET; + FIFOClear(&engine->pendinglist); + FIFOClear(&engine->querylist); +} + + +//NOTE: the server must not be in the pending or update list currently! +void SBQueryEngineUpdateServer(SBQueryEngine *engine, SBServer server, int addfront, int querytype, SBBool usequerychallenge) +{ + // Assert state of FIFOs + FIFODebugCheckAdd(&engine->pendinglist, server); + FIFODebugCheckAdd(&engine->querylist, server); + + server->state &= (unsigned char)~(STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY|STATE_PENDINGICMPQUERY|STATE_PENDINGQUERYCHALLENGE|STATE_QUERYFAILED); //clear out these flags + server->splitResponseBitmap = 0; + server->querychallenge = 0; + +#ifndef SB_ICMP_SUPPORT + if (querytype == QTYPE_ICMP) + return; // ICMP not supported +#endif + + if (querytype == QTYPE_BASIC) + server->state |= STATE_PENDINGBASICQUERY; + else if (querytype == QTYPE_FULL) + server->state |= STATE_PENDINGFULLQUERY; + else if (querytype == QTYPE_ICMP) + server->state |= STATE_PENDINGICMPQUERY; + else + return; // hoterror: unsupported querytype! + + if (usequerychallenge && (querytype == QTYPE_FULL || querytype == QTYPE_BASIC)) + server->state |= STATE_PENDINGQUERYCHALLENGE; + + if (engine->querylist.count < engine->maxupdates) //add it now.. + { + QEStartQuery(engine, server); + return; + } + //else need to queue it + + if (addfront) + FIFOAddFront(&engine->pendinglist, server); + else + FIFOAddRear(&engine->pendinglist, server); +} + +SBServer SBQueryEngineUpdateServerByIP(SBQueryEngine *engine, const char *ip, unsigned short queryport, int addfront, int querytype, SBBool usequerychallenge) +{ + //need to create a new server + SBServer server; + goa_uint32 ipaddr; + ipaddr = inet_addr(ip); + server = SBAllocServer(NULL, ipaddr, htons(queryport)); + server->flags = UNSOLICITED_UDP_FLAG; //we assume we can talk directly to it + SBQueryEngineUpdateServer(engine, server, addfront, querytype, usequerychallenge); + return server; +} + + +static void ParseSingleQR2Reply(SBQueryEngine *engine, SBServer server, char *data, int len) +{ + int i; + int dlen; + + // 0x00 == qr2 query response, 0x09 == qr2 challenge response + if (data[0] != 0x00 && data[0] != 0x09) + return; + + //we could test the request key here for added security, or skip + data += 5; + len -= 5; + if (server->state & STATE_PENDINGQUERYCHALLENGE) + { + server->state &= (unsigned char)~(STATE_PENDINGQUERYCHALLENGE); + + if (len > 0) + { + server->querychallenge = (gsi_u32)atoi(data); + FIFORemove(&engine->querylist, server); // remove it + QEStartQuery(engine, server); // readd it with a keys query + engine->ListCallback(engine, qe_challengereceived, server, engine->instance); + return; + } + } + else if (server->state & STATE_PENDINGBASICQUERY) + { + //need to pick out the keys they selected + for (i = 0 ; i < engine->numserverkeys ; i++) + { + dlen = NTSLengthSB(data, len); + if (dlen < 0) + break; + //add the value if its not a Query-From-Master-Only key + if (!qr2_internal_is_master_only_key(qr2_registered_key_list[engine->serverkeys[i]])) + { + SBServerAddKeyValue(server, qr2_registered_key_list[engine->serverkeys[i]], data); + } + data += dlen; + len -= dlen; + } + server->state |= STATE_BASICKEYS|STATE_VALIDPING; + } + else //need to parse out all the keys + { + // Is this a split packet format? + if (*data && strncmp("splitnum", data, 8)==0) + { + SBServerParseQR2FullKeysSplit(server, data, len); + if (server->splitResponseBitmap != 0xFF) + return; + server->state |= STATE_FULLKEYS|STATE_BASICKEYS|STATE_VALIDPING; + } + else + { + // single packet + SBServerParseQR2FullKeysSingle(server, data, len); + server->state |= STATE_FULLKEYS|STATE_BASICKEYS|STATE_VALIDPING; + } + } + server->state &= (unsigned char)~(STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY); + server->updatetime = current_time() - server->updatetime; + FIFORemove(&engine->querylist, server); + engine->ListCallback(engine, qe_updatesuccess, server, engine->instance); +} + +static void ParseSingleGOAReply(SBQueryEngine *engine, SBServer server, char *data, int len) +{ + int isfinal; + //need to check before parse as it will modify the string + isfinal = (strstr(data,"\\final\\") != NULL); + SBServerParseKeyVals(server, data); + if (isfinal) + { + if (server->state & STATE_PENDINGBASICQUERY) + server->state |= STATE_BASICKEYS|STATE_VALIDPING; + else + server->state |= STATE_FULLKEYS|STATE_VALIDPING; + server->state &= (unsigned char)~(STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY); + server->updatetime = current_time() - server->updatetime; + FIFORemove(&engine->querylist, server); + engine->ListCallback(engine, qe_updatesuccess, server, engine->instance); + } + + GSI_UNUSED(len); +} + +static SBBool ParseSingleICMPReply(SBQueryEngine *engine, SBServer server, char *data, int len) +{ +#ifdef SB_ICMP_SUPPORT + SBIPHeader *ipheader = (SBIPHeader *)data; + SBICMPHeader *icmpheader; + int ipheaderlen; + goa_uint32 packetpublicip; + unsigned short packetpublicport; + //todo: byte alignment on PS2 + ipheaderlen = (gsi_u8)(ipheader->ip_hl_ver & 15); + ipheaderlen *= 4; + icmpheader = (SBICMPHeader *)(data + ipheaderlen); + if (icmpheader->type != SB_ICMP_ECHO_REPLY) + return SBFalse; + if (icmpheader->un.idseq != server->updatetime) + return SBFalse; + if (len < ipheaderlen + (int)sizeof(SBICMPHeader) + 6) + return SBFalse; //not enough data + //check the server IP and port + memcpy(&packetpublicip, data + ipheaderlen + sizeof(SBICMPHeader), 4); + memcpy(&packetpublicport, data + ipheaderlen + sizeof(SBICMPHeader) + 4, 2); + if (packetpublicport != server->publicport || packetpublicip != server->publicip) + return SBFalse; + //else its a valid echo + server->updatetime = current_time() - server->updatetime; + server->state |= STATE_VALIDPING; + server->state &= (unsigned char)~(STATE_PENDINGICMPQUERY); + FIFORemove(&engine->querylist, server); + engine->ListCallback(engine, qe_updatesuccess, server, engine->instance); +#else + GSI_UNUSED(engine); + GSI_UNUSED(server); + GSI_UNUSED(data); + GSI_UNUSED(len); +#endif + return SBTrue; + +} + +#if defined(SN_SYSTEMS) && defined(SB_ICMP_SUPPORT) +static void ProcessIncomingICMPReplies(SBQueryEngine *engine) +{ + SBServer server; + int result = 0; + int found = 0; + int i = 0; + sndev_stat_ping_times_type optval; + gsi_i32 optsize = sizeof(optval); + + // Get the ICMP replies from the SNSystems stack + result = sndev_get_status(0, SN_DEV_STAT_PING_TIMES, (void*)&optval, &optsize); + if (result != 0) + { + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Failed on sndev_get_status (checking ICMP pings): %d\r\n", result); + return; + } + if (optval.num_entries == 0) + return; // no outstanding pings (according to sn_systems) + + // match servers to ping responses + for (server = engine->querylist.first; server != NULL; server = server->next) + { + if ((server->state & STATE_PENDINGICMPQUERY) == 0 || + (server->flags & ICMP_IP_FLAG) == 0) + continue; // server not flagged for ICMP + + // find this server + for (i=0; iicmpip == optval.times[i].ip_addr) + { + if (optval.times[i].status == SN_PING_TIMES_CODE_GOTREPLY) + { + server->updatetime = optval.times[i].time_ms; + server->state |= STATE_VALIDPING; + server->state &= (unsigned char)~(STATE_PENDINGICMPQUERY); + FIFORemove(&engine->querylist, server); + engine->ListCallback(engine, qe_updatesuccess, server, engine->instance); + } + //else + // let query engine timeout queries on its own (for simplicity) + + found++; + if (found == optval.num_entries) + return; // found them all + } + } + } +} +#endif // SN_SYSTEMS && SB_ICMP_SUPPORT + +static void ProcessIncomingReplies(SBQueryEngine *engine, SBBool icmpSocket) +{ + int i; + char indata[MAX_RECVFROM_SIZE]; + struct sockaddr_in saddr; + int saddrlen = sizeof(saddr); + SBServer server; + SOCKET recvSock = 0; + + if (icmpSocket) + { + #ifdef SB_ICMP_SUPPORT + #if defined(SN_SYSTEMS) + ProcessIncomingICMPReplies(engine); + return; + #else + recvSock = engine->icmpsock; + #endif + #endif + } + else + { + recvSock = engine->querysock; + } + + // Process all information in the socket buffer + while(CanReceiveOnSocket(recvSock)) + { + i = (int)recvfrom(recvSock, indata, sizeof(indata) - 1, 0, (struct sockaddr *)&saddr, &saddrlen); + + if (gsiSocketIsError(i)) + break; + indata[i] = 0; + //find the server in our query list + for (server = engine->querylist.first ; server != NULL ; server = server->next) + { + if ((icmpSocket && (server->flags & ICMP_IP_FLAG) && server->icmpip == saddr.sin_addr.s_addr) || //if it's an ICMP query and it matches the ICMP address + (server->publicip == saddr.sin_addr.s_addr && (server->publicport == saddr.sin_port || icmpSocket)) || //if it matches public - port doesnt need to match for ICMP + (server->publicip == engine->mypublicip && (server->flags & PRIVATE_IP_FLAG) && server->privateip == saddr.sin_addr.s_addr && server->privateport == saddr.sin_port)) //or has a private, and matches + { + if (icmpSocket) + { + if (ParseSingleICMPReply(engine, server, indata, i)) + break; //only break if it matches exactly, since we may have multiple outstanding pings to the same ICMPIP for different servers! + } else + { + if (engine->queryversion == QVERSION_QR2) + ParseSingleQR2Reply(engine, server, indata, i); + else + ParseSingleGOAReply(engine, server, indata, i); + break; + } + } + } + } + + +} + +static void TimeoutOldQueries(SBQueryEngine *engine) +{ + gsi_time ctime = current_time(); + while (engine->querylist.first != NULL) + { + if (ctime > engine->querylist.first->updatetime + MAX_QUERY_MSEC) + { + engine->querylist.first->flags |= STATE_QUERYFAILED; + engine->querylist.first->updatetime = MAX_QUERY_MSEC; + engine->querylist.first->flags &= (unsigned char)~(STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY|STATE_PENDINGICMPQUERY); + engine->ListCallback(engine, qe_updatefailed, engine->querylist.first, engine->instance); + FIFOGetFirst(&engine->querylist); + } else + break; //since servers are added in FIFO order, nothing later can have already expired + } +} + +static void QueueNextQueries(SBQueryEngine *engine) +{ + while (engine->querylist.count < engine->maxupdates && engine->pendinglist.count > 0) + { + SBServer server = FIFOGetFirst(&engine->pendinglist); + QEStartQuery(engine, server); + } +} + +void SBQueryEngineThink(SBQueryEngine *engine) +{ + if (engine->querylist.count == 0) //not querying anything - we can go away + return; + ProcessIncomingReplies(engine, SBFalse); +#ifdef SB_ICMP_SUPPORT + ProcessIncomingReplies(engine, SBTrue); +#endif + TimeoutOldQueries(engine); + if (engine->pendinglist.count > 0) + QueueNextQueries(engine); + if (engine->querylist.count == 0) //we are now idle.. + engine->ListCallback(engine, qe_engineidle, NULL, engine->instance); +} + +void SBQueryEngineAddQueryKey(SBQueryEngine *engine, unsigned char keyid) +{ + if (engine->numserverkeys < MAX_QUERY_KEYS) + engine->serverkeys[engine->numserverkeys++] = keyid; +} + + +//remove a server from our update FIFOs +void SBQueryEngineRemoveServerFromFIFOs(SBQueryEngine *engine, SBServer server) +{ + SBBool ret; + + // remove the server from the current query list + ret = FIFORemove(&engine->querylist, server); + if(ret) + return; // -- Caution: assumes that server will not be in pendinglist + FIFORemove(&engine->pendinglist, server); +} diff --git a/code/gamespy/serverbrowsing/sb_server.c b/code/gamespy/serverbrowsing/sb_server.c new file mode 100644 index 00000000..477e850d --- /dev/null +++ b/code/gamespy/serverbrowsing/sb_server.c @@ -0,0 +1,892 @@ +#include "sb_internal.h" +#include "sb_ascii.h" + +//for the unique value list +#if defined(_NITRO) + #define LIST_NUMKEYBUCKETS 100 + #define LIST_NUMKEYCHAINS 2 +#else + #define LIST_NUMKEYBUCKETS 500 + #define LIST_NUMKEYCHAINS 4 +#endif + +// for unicode version of key/value pairs +#define UKEY_LENGTH_MAX 255 +#define UVALUE_LENGTH_MAX 255 + +#ifndef EXTERN_REFSTR_HASH +//global, shared unique value list +#if defined(_WIN32) && !defined(_DLL) && !defined (_USRDLL) && !defined(_MANAGED) && defined(GM_2B) +//for gmaster2b +__declspec( thread ) +#endif +HashTable g_SBRefStrList = NULL; +#endif + + +/*********** + * REF COUNTING STRING HASHTABLE FUNCTIONS + **********/ +static int StringHash(const char *s, int numbuckets); +static int RefStringHash(const void *elem, int numbuckets) +{ + return StringHash(((SBRefString *)elem)->str, numbuckets); +} + + + +/* keyval +* Compares two gkeyvaluepair +*/ + +static int GS_STATIC_CALLBACK RefStringCompare(const void *entry1, const void *entry2) +{ + return strcasecmp(((SBRefString *)entry1)->str,((SBRefString *)entry2)->str); +} + + +static void RefStringFree(void *elem) +{ + gsifree((char *)((SBRefString *)elem)->str); +#ifdef GSI_UNICODE + gsifree((unsigned short *)((SBRefString *)elem)->str_W); +#endif +} + + +#ifndef EXTERN_REFSTR_HASH + +HashTable SBRefStrHash(SBServerList *slist) +{ + if (g_SBRefStrList == NULL) + g_SBRefStrList = TableNew2(sizeof(SBRefString),LIST_NUMKEYBUCKETS,LIST_NUMKEYCHAINS,RefStringHash, RefStringCompare, RefStringFree); + + GSI_UNUSED(slist); + return g_SBRefStrList; +} + +void SBRefStrHashCleanup(SBServerList *slist) +{ + if (g_SBRefStrList != NULL && TableCount(g_SBRefStrList) == 0) + { + TableFree(g_SBRefStrList); + g_SBRefStrList = NULL; + } + + GSI_UNUSED(slist); +} + +#endif + + +void SBServerFree(void *elem) +{ + SBServer server = *(SBServer *)elem; + //free all the keys.. + TableFree(server->keyvals); + server->keyvals = NULL; + gsifree(server); +} + +void SBServerAddKeyValue(SBServer server, const char *keyname, const char *value) +{ + SBKeyValuePair kv; + kv.key = SBRefStr(NULL, keyname); + kv.value = SBRefStr(NULL, value); + TableEnter(server->keyvals, &kv); + + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Misc, GSIDebugLevel_Comment, + "SBServerAddKeyValue added %s\\%s\r\n", keyname, value); +} + +void SBServerAddIntKeyValue(SBServer server, const char *keyname, int value) +{ + char stemp[20]; + sprintf(stemp, "%d", value); + SBServerAddKeyValue(server, keyname, stemp); +} + +typedef struct +{ + SBServerKeyEnumFn EnumFn; + void *instance; +} SBServerEnumData; + +/* ServerEnumKeys +----------------- +Enumerates the keys/values for a given server by calling KeyEnumFn with each +key/value. The user-defined instance data will be passed to the KeyFn callback */ + +static void KeyMapF(void *elem, void *clientData) +{ + SBKeyValuePair *kv = (SBKeyValuePair *)elem; + SBServerEnumData *ped = (SBServerEnumData *)clientData; + +#ifndef GSI_UNICODE + ped->EnumFn((char *)kv->key, (char *)kv->value, ped->instance); +#else + unsigned short key_W[UKEY_LENGTH_MAX]; + unsigned short value_W[UVALUE_LENGTH_MAX]; + UTF8ToUCS2String(kv->key, key_W); + UTF8ToUCS2String(kv->value, value_W); + ped->EnumFn(key_W, value_W, ped->instance); +#endif +} +void SBServerEnumKeys(SBServer server, SBServerKeyEnumFn KeyFn, void *instance) +{ + SBServerEnumData ed; + + ed.EnumFn = KeyFn; + ed.instance = instance; + TableMap(server->keyvals, KeyMapF, &ed); +} + + +const char *SBServerGetStringValueA(SBServer server, const char *keyname, const char *def) +{ + SBKeyValuePair kv, *ptr; + + // 11-03-2004 : Saad Nader + // Check if we are getting a valid server + // before doing anything! + /////////////////////////////////////////// + assert(server); + if (!server) + return NULL; + kv.key = keyname; + ptr = (SBKeyValuePair *)TableLookup(server->keyvals, &kv); + if (ptr == NULL) + return def; + return ptr->value; +} +#ifdef GSI_UNICODE +const unsigned short *SBServerGetStringValueW(SBServer server, const unsigned short *keyname, const unsigned short *def) +{ + char* keyname_A = UCS2ToUTF8StringAlloc(keyname); + const char* value = SBServerGetStringValueA(server, keyname_A, NULL); + if (value == NULL) + return def; + else + { + // Since we need the unicode version, we have to dig down to the + // reference counted string structure + SBRefString ref, *val; + ref.str = value; + val = (SBRefString *)TableLookup(SBRefStrHash(NULL), &ref); + if (val == NULL) + return def; // this shouldn't happen + + return val->str_W; + } +} +#endif + +int SBServerGetIntValueA(SBServer server, const char *key, int idefault) +{ + const char *s, *s2; + // check assumtions during development + GS_ASSERT(key != NULL); + GS_ASSERT(server != NULL); + if (server == NULL) + return idefault; + if (strcmp(key,"ping") == 0) //ooh! they want the ping! + return SBServerGetPing(server); + s = SBServerGetStringValueA(server, key, NULL); + if (s == NULL) + return idefault; + s2 = (*s != '-') ? s : s+1; // check for signed values + if (!isdigit((unsigned char)*s2)) // empty-string/non-numeric should return idefault + return idefault; + else + return atoi(s); +} +#ifdef GSI_UNICODE +int SBServerGetIntValueW(SBServer server, const unsigned short *key, int idefault) +{ + char keyname_A[255]; + UCS2ToUTF8String(key, keyname_A); + return SBServerGetIntValueA(server, keyname_A, idefault); +} +#endif + +double SBServerGetFloatValueA(SBServer server, const char *key, double fdefault) +{ + const char *s; + s = SBServerGetStringValueA(server, key, NULL); + if (s == NULL) + return fdefault; + else + return atof(s); +} +#ifdef GSI_UNICODE +double SBServerGetFloatValueW(SBServer server, const unsigned short *key, double fdefault) +{ + char keyname_A[255]; + UCS2ToUTF8String(key, keyname_A); + return SBServerGetFloatValueA(server, keyname_A, fdefault); +} +#endif + +SBBool SBServerGetBoolValueA(SBServer server, const char *key, SBBool bdefault) +{ + const char *s; + s = SBServerGetStringValueA(server, key, NULL); + if (!s || !s[0]) + return bdefault; + + // check the first char for known "false" values + if('0' == s[0]|| 'F' == s[0] || 'f' == s[0] || 'N' == s[0] || 'n' == s[0]) + return SBFalse; + + // presume that all other non-zero values are "true" + return SBTrue; +} +#ifdef GSI_UNICODE +SBBool SBServerGetBoolValueW(SBServer server, const unsigned short *key, SBBool bdefault) +{ + char keyname_A[255]; + UCS2ToUTF8String(key, keyname_A); + return SBServerGetBoolValueA(server, keyname_A, bdefault); +} +#endif + +const char *SBServerGetPlayerStringValueA(SBServer server, int playernum, const char *key, const char *sdefault) +{ + char keyname[128]; + sprintf(keyname, "%s_%d", key, playernum); + return SBServerGetStringValueA(server, keyname, sdefault); +} +#ifdef GSI_UNICODE +const unsigned short *SBServerGetPlayerStringValueW(SBServer server, int playernum, const unsigned short *key, const unsigned short *sdefault) +{ + char keyname_A[UKEY_LENGTH_MAX]; + char default_A[UKEY_LENGTH_MAX]; + const char* value_A = NULL; + UCS2ToUTF8String(key, keyname_A); + UCS2ToUTF8String(sdefault, default_A); + + value_A = SBServerGetPlayerStringValueA(server, playernum, keyname_A, default_A); + if (value_A == NULL) + return sdefault; + else + { + // Since we need the unicode version, we have to dig down to the SBRefString structure + SBRefString ref, *val; + ref.str = value_A; + val = (SBRefString *)TableLookup(SBRefStrHash(NULL), &ref); + if (val == NULL) + return sdefault; + return val->str_W; + } +} +#endif + +int SBServerGetPlayerIntValueA(SBServer server, int playernum, const char *key, int idefault) +{ + char keyname[128]; + sprintf(keyname, "%s_%d", key, playernum); + return SBServerGetIntValueA(server, keyname, idefault); +} +#ifdef GSI_UNICODE +int SBServerGetPlayerIntValueW(SBServer server, int playernum, const unsigned short *key, int idefault) +{ + char keyname_A[UKEY_LENGTH_MAX]; + UCS2ToUTF8String(key, keyname_A); + return SBServerGetPlayerIntValueA(server, playernum, keyname_A, idefault); +} +#endif + +double SBServerGetPlayerFloatValueA(SBServer server, int playernum, const char *key, double fdefault) +{ + char keyname[128]; + sprintf(keyname, "%s_%d", key, playernum); + return SBServerGetFloatValueA(server, keyname, fdefault); +} +#ifdef GSI_UNICODE +double SBServerGetPlayerFloatValueW(SBServer server, int playernum, const unsigned short *key, double fdefault) +{ + char keyname_A[UKEY_LENGTH_MAX]; + UCS2ToUTF8String(key, keyname_A); + return SBServerGetPlayerFloatValueA(server, playernum, keyname_A, fdefault); +} +#endif + +const char *SBServerGetTeamStringValueA(SBServer server, int teamnum, const char *key, const char *sdefault) +{ + char keyname[128]; + sprintf(keyname, "%s_t%d", key, teamnum); + return SBServerGetStringValueA(server, keyname, sdefault); +} +#ifdef GSI_UNICODE +const unsigned short *SBServerGetTeamStringValueW(SBServer server, int teamnum, const unsigned short *key, const unsigned short *sdefault) +{ + char keyname_A[UKEY_LENGTH_MAX]; + char default_A[UKEY_LENGTH_MAX]; + const char* value_A = NULL; + UCS2ToUTF8String(key, keyname_A); + UCS2ToUTF8String(sdefault, default_A); + value_A = SBServerGetTeamStringValueA(server, teamnum, keyname_A, default_A); + + if (value_A == NULL) + return sdefault; + else + { + // Since we need the unicode version, we have to dig down to the SBRefString structure + SBRefString ref, *val; + ref.str = value_A; + val = (SBRefString *)TableLookup(SBRefStrHash(NULL), &ref); + if (val == NULL) + return sdefault; + return val->str_W; + } +} +#endif + +int SBServerGetTeamIntValueA(SBServer server, int teamnum, const char *key, int idefault) +{ + char keyname[128]; + sprintf(keyname, "%s_t%d", key, teamnum); + return SBServerGetIntValueA(server, keyname, idefault); +} +#ifdef GSI_UNICODE +int SBServerGetTeamIntValueW(SBServer server, int teamnum, const unsigned short *key, int idefault) +{ + char keyname_A[UKEY_LENGTH_MAX]; + UCS2ToUTF8String(key, keyname_A); + return SBServerGetTeamIntValueA(server, teamnum, keyname_A, idefault); +} +#endif + +double SBServerGetTeamFloatValueA(SBServer server, int teamnum, const char *key, double fdefault) +{ + char keyname[128]; + sprintf(keyname, "%s_t%d", key, teamnum); + return SBServerGetFloatValueA(server, keyname, fdefault); +} +#ifdef GSI_UNICODE +double SBServerGetTeamFloatValueW(SBServer server, int teamnum, const unsigned short *key, double fdefault) +{ + char keyname_A[UKEY_LENGTH_MAX]; + UCS2ToUTF8String(key, keyname_A); + return SBServerGetTeamFloatValueA(server, teamnum, keyname_A, fdefault); +} +#endif + +SBBool SBServerHasBasicKeys(SBServer server) +{ + return (((server->state & STATE_BASICKEYS) == STATE_BASICKEYS) ? SBTrue : SBFalse); +} + +SBBool SBServerHasFullKeys(SBServer server) +{ + return (((server->state & STATE_FULLKEYS) == STATE_FULLKEYS) ? SBTrue : SBFalse); +} + +SBBool SBServerHasValidPing(SBServer server) +{ + return (((server->state & STATE_VALIDPING) == STATE_VALIDPING) ? SBTrue : SBFalse); +} + + +char *SBServerGetPublicAddress(SBServer server) +{ + return (char *)inet_ntoa(*(struct in_addr *)&server->publicip); +} + +unsigned int SBServerGetPublicInetAddress(SBServer server) +{ + return server->publicip; +} + + +unsigned short SBServerGetPublicQueryPort(SBServer server) +{ + return ntohs(server->publicport); +} + +unsigned short SBServerGetPublicQueryPortNBO(SBServer server) +{ + return server->publicport; +} + +SBBool SBServerHasPrivateAddress(SBServer server) +{ + return (((server->flags & PRIVATE_IP_FLAG) == PRIVATE_IP_FLAG) ? SBTrue : SBFalse); +} + + + +SBBool SBServerDirectConnect(SBServer server) +{ + return (((server->flags & UNSOLICITED_UDP_FLAG) == UNSOLICITED_UDP_FLAG) ? SBTrue : SBFalse); +} + +char *SBServerGetPrivateAddress(SBServer server) +{ + return (char *)inet_ntoa(*(struct in_addr *)&server->privateip); +} + +unsigned int SBServerGetPrivateInetAddress(SBServer server) +{ + return server->privateip; +} + + +unsigned short SBServerGetPrivateQueryPort(SBServer server) +{ + return ntohs(server->privateport); +} + +void SBServerSetNext(SBServer server, SBServer next) +{ + server->next = next; +} + +SBServer SBServerGetNext(SBServer server) +{ + return server->next; +} + +static int CheckValidKey(char *key) +{ + const char *InvalidKeys[]={"queryid","final"}; + int i; + for (i = 0; i < sizeof(InvalidKeys)/sizeof(InvalidKeys[0]); i++) + { + if (strcmp(key,InvalidKeys[i]) == 0) + return 0; + } + return 1; +} + +static char *mytok(char *instr, char delim) +{ + char *result; + static char *thestr; + + if (instr) + thestr = instr; + result=thestr; + while (*thestr && *thestr != delim) + { + thestr++; + } + if (thestr == result) + result = NULL; + if (*thestr) //not the null term + *thestr++ = '\0'; + return result; +} + + +void SBServerParseKeyVals(SBServer server, char *keyvals) +{ + char *k, *v; + + k = mytok(++keyvals,'\\'); //skip over starting backslash + while (k != NULL) + { + v = mytok(NULL,'\\'); + if (v == NULL) + v = ""; + if (CheckValidKey(k)) + { + //add the value if its not a Query-From-Master-Only key + if (!qr2_internal_is_master_only_key(k)) + { + SBServerAddKeyValue(server, k, v); + } + } + k = mytok(NULL,'\\'); + } +} + + +/* +Query Response Format +Packet Type: 1 Byte +Request Key: 4 Bytes (copied from request packet) + +Server Keys (if number of keys requested > 0) +If server key list specified: +Server Values: NTS, one per key specified +If server key list not specified: +Server Keys / Server Values: NTS Pairs, terminated with NUL + +Player Keys (if number of keys requested > 0) +Number of Players: 2 Bytes +If player key list NOT specified + Player Keys: NTS, one per key, terminated with NUL +Player Values: NTS, one per key specified, per player + +Team Keys (if number of keys requested > 0) +Number of Teams: 2 Bytes +If team key list NOT specified + Team Keys: NTS, one per key, terminated with NUL +Team Values: NTS, one per key specified, per team + +*/ +void SBServerParseQR2FullKeysSingle(SBServer server, char *data, int len) +{ + int dlen; + char *k; + char *v; + char *keys; + int nkeys; + unsigned short nunits; + int pflag; + int i,j; + char tempkey[128]; + //first pull out all the server keys/values + while (*data) + { + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; //not a full NTS + k = data; + data += dlen; + len -= dlen; + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; //not a full NTS + v = data; + data += dlen; + len -= dlen; + + //add the value if its not a Query-From-Master-Only key + if (!qr2_internal_is_master_only_key(k)) + { + SBServerAddKeyValue(server, k, v); + } + } + //skip the NUL + data++; + len--; + //now get out the # of players (or teams) .. we do this whole thing 2X, once for players once for teams + for (pflag = 0 ; pflag < 2 ; pflag++) + { + if (len < 2) + return; + memcpy(&nunits, data, 2); + nunits = ntohs(nunits); + data += 2; + len -= 2; + keys = data; + nkeys = 0; + //count up the number of keys.. + while (*data) + { + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; //not all there + if (dlen > 100) //key is too long, may cause buffer overrun + return; + nkeys++; + data += dlen; + len -= dlen; + } + //skip the NUL + data++; + len--; + //now for each player/team + for (i = 0 ; i < nunits ; i++) + { + k = keys; + //for each key.. + for (j = 0 ; j < nkeys ; j++) + { + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; //not all there + sprintf(tempkey, "%s%d", k, i); + SBServerAddKeyValue(server, tempkey, data); + data += dlen; + len -= dlen; + k += strlen(k) + 1; //skip to the next key + } + } + } + +} + + +// FullKeys with split response support +// Goes something like: +// [qr2header]["splitnum"][num (byte)][keytype] +// if keytype is server, read KV's until a NULL +// otherwise read a keyname, then a number of values until NULL +#define QR2_SPLITPACKET_NUMSTRING "splitnum" +#define QR2_SPLITPACKET_MAX 7 // -xxxxxxx +#define QR2_SPLITPACKET_FINAL (1<<7) // x------- +void SBServerParseQR2FullKeysSplit(SBServer server, char *data, int len) +{ + int dlen; + char *k; + char *v; + unsigned int packetNum = 0; + gsi_bool isFinal = gsi_false; + + // make sure it's valid + if (*data == '\0') + return; + + // data should have "splitnum" followed by BINARY key + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; + k = data; + data += dlen; + len -= dlen; + + if (strncasecmp(QR2_SPLITPACKET_NUMSTRING,k,strlen(QR2_SPLITPACKET_NUMSTRING))!=0) + return; + if (len < 1) + return; + + packetNum = (unsigned int)((unsigned char)*data); + data++; + len--; + + // check final flag + if ((packetNum & QR2_SPLITPACKET_FINAL) == QR2_SPLITPACKET_FINAL) + { + isFinal = gsi_true; + packetNum ^= QR2_SPLITPACKET_FINAL; + } + + // sanity check the packet num + if (packetNum > QR2_SPLITPACKET_MAX) + return; + + // update the received flags + if (isFinal == SBTrue) + // mark all bits higher than the final packet + server->splitResponseBitmap |= (char)(0xFF<splitResponseBitmap |= (1< 0) + { + int keyType = 0; + int nindex = 0; + + // Read the key type + keyType = *data; + data++; + len--; + + if (keyType < 0 || keyType > 2) + { + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Split packet parse error, invalid key type! (%d)\r\n", keyType); + return; // invalid key type! + } + + // read keys until section terminator + while(*data) + { + // Read key name + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; + k = data; + data += dlen; + len -= dlen; + + if (keyType == 0) + { + // read the server key value + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; + v = data; + data += dlen; + len -= dlen; + + //add the value if its not a Query-From-Master-Only key + if (!qr2_internal_is_master_only_key(k)) + { + SBServerAddKeyValue(server, k, v); + } + } + else + { + char tempkey[128]; + + // Read first player/team number + if (len < 1) + return; + + nindex = *data; + data++; + len--; + + // read values until + while(*data) + { + // read the value + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; + v = data; + data += dlen; + len -= dlen; + // append team or player index before adding + sprintf(tempkey, "%s%d", k, nindex); + SBServerAddKeyValue(server, tempkey, v); + nindex++; // index increments from start + } + + // skip the null (key terminator) + if (len > 0) + { + data++; + len--; + } + } + } + + // skip the null (section terminator) + if (len > 0) + { + if (*data != '\0') + { + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Split packet parse error, NULL byte expected!\r\n"); + return; // ERROR! + } + data++; + len--; + } + } +} + +/*********** + * UTILITY FUNCTIONS + **********/ +#define MULTIPLIER -1664117991 +static int StringHash(const char *s, int numbuckets) +{ + goa_uint32 hashcode = 0; + while (*s != 0) + { + hashcode = (goa_uint32)((int)hashcode * MULTIPLIER + tolower(*s)); + s++; + } + return (int)(hashcode % numbuckets); + +} + +static void KeyValFree(void *elem) +{ + SBKeyValuePair *kv = (SBKeyValuePair *)elem; + SBReleaseStr(NULL, kv->key); + SBReleaseStr(NULL, kv->value); +} + +static int KeyValHashKey(const void *elem, int numbuckets) +{ + return StringHash(((SBKeyValuePair *)elem)->key, numbuckets); +} + + +static int GS_STATIC_CALLBACK KeyValCompareKey(const void *entry1, const void *entry2) +{ + GS_ASSERT(entry1) + GS_ASSERT(entry2) + if ( + (((SBKeyValuePair *)entry1)->key == NULL) || + (((SBKeyValuePair *)entry2)->key == NULL) + ) + return 1;// treat NULL as not the same + + return strcasecmp(((SBKeyValuePair *)entry1)->key, ((SBKeyValuePair *)entry2)->key); +} + + +int SBServerGetPing(SBServer server) +{ + return (int) server->updatetime; +} + +#define NUM_BUCKETS 8 +#define NUM_CHAINS 4 + +//todo: benchmark sorted darray vs. hashtable - memory + speed +SBServer SBAllocServer(SBServerList *slist, goa_uint32 publicip, unsigned short publicport) +{ + SBServer server; + server = (SBServer)gsimalloc(sizeof(struct _SBServer)); + if (server == NULL) + return NULL; + server->keyvals = TableNew2(sizeof(SBKeyValuePair),NUM_BUCKETS,NUM_CHAINS, KeyValHashKey, KeyValCompareKey, KeyValFree); + if (server->keyvals == NULL) + { + gsifree(server); + return NULL; + } + server->state = 0; + server->flags = 0; + server->next = NULL; + server->updatetime = 0; + server->icmpip = 0; + server->publicip = publicip; + server->publicport = publicport; + server->privateip = 0; + server->privateport = 0; + + GSI_UNUSED(slist); + return server; +} + + + +void SBServerSetFlags(SBServer server, unsigned char flags) +{ + server->flags = flags; +} +void SBServerSetPublicAddr(SBServer server, goa_uint32 ip, unsigned short port) +{ + server->publicip = ip; + server->publicport = port; + +} +void SBServerSetPrivateAddr(SBServer server, goa_uint32 ip, unsigned short port) +{ + server->privateip = ip; + server->privateport = port; + +} +void SBServerSetICMPIP(SBServer server, goa_uint32 icmpip) +{ + server->icmpip = icmpip; + +} +void SBServerSetState(SBServer server, unsigned char state) +{ + server->state = state; + +} +unsigned char SBServerGetState(SBServer server) +{ + return server->state; + +} +unsigned char SBServerGetFlags(SBServer server) +{ + return server->flags; + +} + +int SBIsNullServer(SBServer server) +{ + return (server == SBNullServer) ? 1 : 0; +} + +SBServer SBNullServer = NULL; diff --git a/code/gamespy/serverbrowsing/sb_serverbrowsing.c b/code/gamespy/serverbrowsing/sb_serverbrowsing.c new file mode 100644 index 00000000..a8a64f03 --- /dev/null +++ b/code/gamespy/serverbrowsing/sb_serverbrowsing.c @@ -0,0 +1,681 @@ +#include "sb_internal.h" +#include "sb_ascii.h" +#include "../natneg/natneg.h" + +//Future Versions: +//ICMP Ping support (icmp engine) + + +//internal callback proxy for server list +static void ListCallback(SBServerList *serverlist, SBListCallbackReason reason, SBServer server, void *instance) +{ + ServerBrowser sb = (ServerBrowser)instance; + switch (reason) + { + case slc_serveradded: + sb->BrowserCallback(sb, sbc_serveradded, server, sb->instance); + if ((server->state & (STATE_BASICKEYS|STATE_FULLKEYS)) == 0 || (server->state & STATE_VALIDPING) == 0) //we need to do an update + { + // Don't update if an update is already pending + if (server->state & (STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY|STATE_PENDINGICMPQUERY)) + break; + + if (!sb->dontUpdate) //if this flag is set, we don't want to trigger updates + { + int qtype; + if (server->flags & UNSOLICITED_UDP_FLAG) + { + if (sb->list.state == sl_lanbrowse || sb->engine.numserverkeys == 0) + qtype = QTYPE_FULL; + else + qtype = QTYPE_BASIC; + } else + qtype = QTYPE_ICMP; //we can only do an ICMP query + + if (serverlist->backendgameflags & QR2_USE_QUERY_CHALLENGE) + SBQueryEngineUpdateServer(&sb->engine, server, 0, qtype, SBTrue); + else + SBQueryEngineUpdateServer(&sb->engine, server, 0, qtype, SBFalse); + + } + } + break; + case slc_serverupdated: + if ((server->state & (STATE_BASICKEYS|STATE_FULLKEYS|STATE_VALIDPING)) == 0) //if it was updated, but with no data, then the update failed! + sb->BrowserCallback(sb, sbc_serverupdatefailed, server, sb->instance); + else + sb->BrowserCallback(sb, sbc_serverupdated, server, sb->instance); + break; + case slc_serverdeleted: + if ((server->state & (STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY|STATE_PENDINGICMPQUERY)) != 0) + SBQueryEngineRemoveServerFromFIFOs(&sb->engine, server); + sb->BrowserCallback(sb, sbc_serverdeleted, server, sb->instance); + break; + case slc_initiallistcomplete: + if (sb->disconnectFlag) + SBServerListDisconnect(serverlist); + // If there aren't any servers to query, call the completed callback + if (ArrayLength(serverlist->servers)==0 || sb->engine.querylist.count==0) + sb->BrowserCallback(sb, sbc_updatecomplete, NULL, sb->instance); + break; + case slc_disconnected: + break; + case slc_queryerror: + sb->BrowserCallback(sb, sbc_queryerror, NULL, sb->instance); + break; + case slc_publicipdetermined: + SBQueryEngineSetPublicIP(&sb->engine, sb->list.mypublicip); + break; + case slc_serverchallengereceived: + break; + } + if (server != NULL && server->publicip == sb->triggerIP && server->publicport == sb->triggerPort) + sb->triggerIP = 0; //clear the trigger +} + +//internal callback proxy for query engine +static void EngineCallback(SBQueryEnginePtr engine, SBQueryEngineCallbackReason reason, SBServer server, void *instance) +{ + ServerBrowser sb = (ServerBrowser)instance; + switch (reason) + { + case qe_updatefailed: + sb->BrowserCallback(sb, sbc_serverupdatefailed, server, sb->instance); + break; + case qe_updatesuccess: + sb->BrowserCallback(sb, sbc_serverupdated, server, sb->instance); + break; + case qe_engineidle: + sb->BrowserCallback(sb, sbc_updatecomplete, server, sb->instance); + break; + case qe_challengereceived: + sb->BrowserCallback(sb, sbc_serverchallengereceived, server, sb->instance); + //challenge received will return instead of break - since the game has not yet been updated + return; + } + if (server != NULL && server->publicip == sb->triggerIP && server->publicport == sb->triggerPort) + sb->triggerIP = 0; //clear the trigger + + GSI_UNUSED(engine); +} + + + +ServerBrowser ServerBrowserNewA(const char *queryForGamename, const char *queryFromGamename, const char *queryFromKey, int queryFromVersion, int maxConcUpdates, int queryVersion, SBBool lanBrowse, ServerBrowserCallback callback, void *instance) +{ + ServerBrowser sb; + if(lanBrowse == SBFalse) + { + if(__GSIACResult != GSIACAvailable) + return NULL; + } + + sb = (ServerBrowser)gsimalloc(sizeof(struct _ServerBrowser)); + if (sb == NULL) + return NULL; + sb->BrowserCallback = callback; + sb->ConnectCallback = NULL; + sb->instance = instance; + sb->dontUpdate = SBFalse; + SBServerListInit(&sb->list, queryForGamename, queryFromGamename, queryFromKey, queryFromVersion, lanBrowse, ListCallback, sb); + SBQueryEngineInit(&sb->engine, maxConcUpdates, queryVersion, lanBrowse, EngineCallback, sb); + return sb; +} +#ifdef GSI_UNICODE +ServerBrowser ServerBrowserNewW(const unsigned short *queryForGamename, const unsigned short *queryFromGamename, const unsigned short *queryFromKey, int queryFromVersion, int maxConcUpdates, int queryVersion, SBBool lanBrowse, ServerBrowserCallback callback, void *instance) +{ + char forGameName_A[255]; + char fromGameName_A[255]; + char fromGameKey_A[255]; + + assert(queryForGamename != NULL); + assert(queryFromGamename != NULL); + assert(queryFromKey != NULL); + + UCS2ToAsciiString(queryForGamename, forGameName_A); + UCS2ToAsciiString(queryFromGamename, fromGameName_A); + UCS2ToAsciiString(queryFromKey, fromGameKey_A); + return ServerBrowserNewA(forGameName_A, fromGameName_A, fromGameKey_A, queryFromVersion, maxConcUpdates, queryVersion, lanBrowse, callback, instance); +} +#endif + +void ServerBrowserFree(ServerBrowser sb) +{ + SBServerListCleanup(&sb->list); + SBEngineCleanup(&sb->engine); + NNFreeNegotiateList(); + gsifree(sb); +} + +//internal version that allows passing of additional options +SBError ServerBrowserBeginUpdate2(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const char *serverFilter, int updateOptions, int maxServers) +{ + char keyList[MAX_FIELD_LIST_LEN] = ""; + int listLen = 0; + int i; + int keylen; + SBError err; + + sb->disconnectFlag = disconnectOnComplete; + //clear this out in case it was already set + sb->engine.numserverkeys = 0; + //build the key list... + for (i = 0 ; i < numBasicFields ; i++) + { + keylen = (int)strlen(qr2_registered_key_list[basicFields[i]]); + if (listLen + keylen + 1 >= MAX_FIELD_LIST_LEN) + break; //can't add this field, too long + listLen += sprintf(keyList + listLen, "\\%s", qr2_registered_key_list[basicFields[i]]); + //add to the engine query list + SBQueryEngineAddQueryKey(&sb->engine, basicFields[i]); + } + +#if defined(SN_SYSTEMS) && defined(SB_ICMP_SUPPORT) + { + // reset SNSystems internal ICMP ping structures + sndev_set_ping_reset_type optval; + optval.timeout_ms = MAX_QUERY_MSEC; // this gets rounded up to 3 sec + optval.reserved = 0; + sndev_set_options(0, SN_DEV_SET_PING_RESET, &optval, sizeof(optval)); + } +#endif + + err = SBServerListConnectAndQuery(&sb->list, keyList, serverFilter, updateOptions, maxServers); + if (err != sbe_noerror) + return err; + + if (!async) //loop while we are still getting the main list and the engine is updating... + { + while ((sb->list.state == sl_mainlist) || ((sb->engine.querylist.count > 0) && (err == sbe_noerror))) + { + msleep(10); + err = ServerBrowserThink(sb); + } + } + return err; +} + + +SBError ServerBrowserUpdateA(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const char *serverFilter) +{ + return ServerBrowserBeginUpdate2(sb, async, disconnectOnComplete, basicFields, numBasicFields, serverFilter, 0, 0); +} +#ifdef GSI_UNICODE +SBError ServerBrowserUpdateW(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const unsigned short *serverFilter) +{ + char serverFilter_A[1024]; + if (serverFilter != NULL) + UCS2ToUTF8String(serverFilter, serverFilter_A); + return ServerBrowserUpdateA(sb, async, disconnectOnComplete, basicFields, numBasicFields, (serverFilter != NULL) ? serverFilter_A:NULL); +} +#endif + +SBError ServerBrowserLimitUpdateA(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const char *serverFilter, int maxServers) +{ + return ServerBrowserBeginUpdate2(sb, async, disconnectOnComplete, basicFields, numBasicFields, serverFilter, LIMIT_RESULT_COUNT, maxServers); +} +#ifdef GSI_UNICODE +SBError ServerBrowserLimitUpdateW(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const unsigned short *serverFilter, int maxServers) +{ + char serverFilter_A[1024]; + if (serverFilter != NULL) + UCS2ToUTF8String(serverFilter, serverFilter_A); + return ServerBrowserLimitUpdateA(sb, async, disconnectOnComplete, basicFields, numBasicFields, (serverFilter != NULL) ? serverFilter_A:NULL, maxServers); +} +#endif + +SBError ServerBrowserLANUpdate(ServerBrowser sb, SBBool async, unsigned short startSearchPort, unsigned short endSearchPort) +{ + SBError err = sbe_noerror; + ServerBrowserHalt(sb); + SBServerListGetLANList(&sb->list, startSearchPort, endSearchPort, sb->engine.queryversion); + if (!async) + { + while ((sb->list.state == sl_lanbrowse) || ((sb->engine.querylist.count > 0) && (err == sbe_noerror))) + { + msleep(10); + err = ServerBrowserThink(sb); + } + } + return err; +} + +static SBError WaitForTriggerUpdate(ServerBrowser sb, SBBool viaMaster) +{ + SBError err = sbe_noerror; + //wait until info is received for the triggerIP + while (sb->triggerIP != 0 && err == sbe_noerror) + { + msleep(10); + err = ServerBrowserThink(sb); + if (viaMaster && sb->list.state == sb_disconnected) //we were supposed to get from master, and it's disconnected + break; + } + return err; + +} + + +SBError ServerBrowserSendMessageToServerA(ServerBrowser sb, const char *ip, unsigned short port, const char *data, int len) +{ + return SBSendMessageToServer(&sb->list, inet_addr(ip), htons(port), data, len); +} +#ifdef GSI_UNICODE +SBError ServerBrowserSendMessageToServerW(ServerBrowser sb, const unsigned short *ip, unsigned short port, const char *data, int len) +{ + char ip_A[128]; + UCS2ToAsciiString(ip, ip_A); + return ServerBrowserSendMessageToServerA(sb, ip_A, port, data, len); +} +#endif + +SBError ServerBrowserSendNatNegotiateCookieToServerA(ServerBrowser sb, const char *ip, unsigned short port, int cookie) +{ + return SBSendNatNegotiateCookieToServer(&sb->list, inet_addr(ip), htons(port), cookie); +} +#ifdef GSI_UNICODE +SBError ServerBrowserSendNatNegotiateCookieToServerW(ServerBrowser sb, const unsigned short *ip, unsigned short port, int cookie) +{ + char ip_A[128]; + UCS2ToAsciiString(ip, ip_A); + return ServerBrowserSendNatNegotiateCookieToServerA(sb, ip_A, port, cookie); +} +#endif + +static void NatNegProgressCallback(NegotiateState state, void *userdata) +{ + // we don't do anything here + GSI_UNUSED(state); + GSI_UNUSED(userdata); +} + +static void NatNegCompletedCallback(NegotiateResult result, SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *userdata) +{ + ServerBrowser sb = (ServerBrowser)userdata; + + if(result == nr_success) + { + sb->ConnectCallback(sb, sbcs_succeeded, gamesocket, remoteaddr, sb->instance); + } + else + { + sb->ConnectCallback(sb, sbcs_failed, INVALID_SOCKET, NULL, sb->instance); + } + + sb->ConnectCallback = NULL; + + GSI_UNUSED(remoteaddr); +} + +SBError ServerBrowserConnectToServer(ServerBrowser sb, SBServer server, SBConnectToServerCallback callback) +{ + SBError sbError; + NegotiateError nnError; + int cookie; + + if((sb == NULL) || (server == NULL) || (callback == NULL)) + return sbe_paramerror; + + if(sb->ConnectCallback != NULL) + return sbe_connecterror; + + // for now, always do natneg + + // send a cookie to the server + Util_RandSeed((unsigned long)current_time()); + cookie = Util_RandInt(GSI_MIN_I32, GSI_MAX_I32); + sbError = ServerBrowserSendNatNegotiateCookieToServerA(sb, SBServerGetPublicAddress(server), SBServerGetPublicQueryPort(server), cookie); + if(sbError != sbe_noerror) + return sbError; + + // start the nn + nnError = NNBeginNegotiation(cookie, 0, NatNegProgressCallback, NatNegCompletedCallback, sb); + if(nnError != ne_noerror) + return sbe_connecterror; + + sb->ConnectCallback = callback; + + return sbe_noerror; +} + +SBError ServerBrowserAuxUpdateIPA(ServerBrowser sb, const char *ip, unsigned short port, SBBool viaMaster, SBBool async, SBBool fullUpdate) +{ + + SBError err = sbe_noerror; + sb->dontUpdate = SBTrue; + + if (!viaMaster) //do an engine query + { + SBServer server; + int i; + SBBool usequerychallenge = (sb->list.backendgameflags & QR2_USE_QUERY_CHALLENGE) > 0 ? SBTrue:SBFalse; + + //need to see if the server exists... + i = SBServerListFindServerByIP(&sb->list, inet_addr(ip), htons(port)); + if (i == -1) + { + server = SBQueryEngineUpdateServerByIP(&sb->engine, ip, port, 1, (fullUpdate) ? QTYPE_FULL : QTYPE_BASIC, usequerychallenge); + SBServerListAppendServer(&sb->list, server); + } + else + server = SBServerListNth(&sb->list, i); + + // Don't overwrite the existing update, otherwise the response for the first update + // will be mistaken as the response for the second update. (Which is bad if they have different update parameters.) + if (server->state & (STATE_PENDINGBASICQUERY|STATE_PENDINGFULLQUERY|STATE_PENDINGICMPQUERY)) + return sbe_duplicateupdateerror; + else + SBQueryEngineUpdateServer(&sb->engine, server, 1, (fullUpdate) ? QTYPE_FULL : QTYPE_BASIC, usequerychallenge); + + } else //do a master update + { + err = SBGetServerRulesFromMaster(&sb->list, inet_addr(ip), htons(port)); + //this will add the server itself.. + } + if (!async && err == sbe_noerror) + { + sb->triggerIP = inet_addr(ip); + sb->triggerPort = htons(port); + err = WaitForTriggerUpdate(sb, viaMaster); + } + sb->dontUpdate = SBFalse; + return err; +} +#ifdef GSI_UNICODE +SBError ServerBrowserAuxUpdateIPW(ServerBrowser sb, const unsigned short *ip, unsigned short port, SBBool viaMaster, SBBool async, SBBool fullUpdate) +{ + char ip_A[128]; + UCS2ToAsciiString(ip, ip_A); + return ServerBrowserAuxUpdateIPA(sb, ip_A, port, viaMaster, async, fullUpdate); +} +#endif + +SBError ServerBrowserAuxUpdateServer(ServerBrowser sb, SBServer server, SBBool async, SBBool fullUpdate) +{ + SBBool viaMaster; + SBError err = sbe_noerror; + SBBool usequerychallenge = (sb->list.backendgameflags & QR2_USE_QUERY_CHALLENGE) > 0 ? SBTrue:SBFalse; + + sb->dontUpdate = SBTrue; + + if (server->flags & UNSOLICITED_UDP_FLAG) //do an engine query + { + //remove from the existing update lists if present + SBQueryEngineRemoveServerFromFIFOs(&sb->engine, server); + SBQueryEngineUpdateServer(&sb->engine, server, 1, (fullUpdate) ? QTYPE_FULL : QTYPE_BASIC, usequerychallenge); + viaMaster = SBFalse; + } else //do a master update + { + err = SBGetServerRulesFromMaster(&sb->list, server->publicip, server->publicport); + viaMaster = SBTrue; + } + if (!async && err == sbe_noerror) + { + sb->triggerIP = server->publicip; + sb->triggerPort = server->publicport; + err = WaitForTriggerUpdate(sb, viaMaster); + } + sb->dontUpdate = SBFalse; + return err; +} + +void ServerBrowserRemoveIPA(ServerBrowser sb, const char *ip, unsigned short port) +{ + int i = SBServerListFindServerByIP(&sb->list, inet_addr(ip), htons(port)); + if (i != -1) + SBServerListRemoveAt(&sb->list, i); +} +#ifdef GSI_UNICODE +void ServerBrowserRemoveIPW(ServerBrowser sb, const unsigned short *ip, unsigned short port) +{ + char ip_A[128]; + UCS2ToAsciiString(ip, ip_A); + ServerBrowserRemoveIPA(sb, ip_A, port); +} +#endif + +void ServerBrowserRemoveServer(ServerBrowser sb, SBServer server) +{ + int i = SBServerListFindServer(&sb->list, server); + if (i != -1) + SBServerListRemoveAt(&sb->list, i); +} + +SBError ServerBrowserThink(ServerBrowser sb) +{ + NNThink(); + SBQueryEngineThink(&sb->engine); + return SBListThink(&sb->list); +} + +void ServerBrowserHalt(ServerBrowser sb) +{ + //stop the list + SBServerListDisconnect(&sb->list); + //stop the query engine... + SBEngineHaltUpdates(&sb->engine); + +} + +void ServerBrowserClear(ServerBrowser sb) +{ + ServerBrowserHalt(sb); + SBServerListClear(&sb->list); +} + +const char *ServerBrowserErrorDescA(ServerBrowser sb, SBError error) +{ + switch (error) + { + case sbe_noerror: + return "None"; + //break; + case sbe_socketerror: + return "Socket creation error"; + //break; + case sbe_dnserror: + return "DNS lookup error"; + //break; + case sbe_connecterror: + return "Connection failed"; + //break; + case sbe_dataerror: + return "Data stream error"; + //break; + case sbe_allocerror: + return "Memory allocation error"; + //break; + case sbe_paramerror: + return "Function parameter error"; + //break; + case sbe_duplicateupdateerror: + return "Duplicate update request error"; + //break; + } + + GSI_UNUSED(sb); + return ""; +} +#ifdef GSI_UNICODE +const unsigned short *ServerBrowserErrorDescW(ServerBrowser sb, SBError error) +{ + switch (error) + { + case sbe_noerror: + return L"None"; + break; + case sbe_socketerror: + return L"Socket creation error"; + break; + case sbe_dnserror: + return L"DNS lookup error"; + break; + case sbe_connecterror: + return L"Connection failed"; + break; + case sbe_dataerror: + return L"Data stream error"; + break; + case sbe_allocerror: + return L"Memory allocation error"; + break; + case sbe_paramerror: + return L"Function parameter error"; + break; + case sbe_duplicateupdateerror: + return L"Duplicate update request error"; + break; + } + return L""; + + GSI_UNUSED(sb); +} +#endif + +const char *ServerBrowserListQueryErrorA(ServerBrowser sb) +{ + return SBLastListErrorA(&sb->list); +} +#ifdef GSI_UNICODE +const unsigned short *ServerBrowserListQueryErrorW(ServerBrowser sb) +{ + return SBLastListErrorW(&sb->list); +} +#endif + +SBState ServerBrowserState(ServerBrowser sb) +{ + if (sb->engine.querylist.count > 0) + return sb_querying; + if (sb->list.state == sl_mainlist || sb->list.state == sl_lanbrowse) + return sb_listxfer; + if (sb->list.state == sl_disconnected) + return sb_disconnected; + return sb_connected; +} + +int ServerBrowserPendingQueryCount(ServerBrowser sb) +{ + return sb->engine.querylist.count + sb->engine.pendinglist.count; +} + +SBServer ServerBrowserGetServer(ServerBrowser sb, int index) +{ + return SBServerListNth(&sb->list, index); +} + +SBServer ServerBrowserGetServerByIPA(ServerBrowser sb, const char* ip, unsigned short port) +{ + int anIndex = -1; + goa_uint32 anIP = 0; + unsigned short aPortNBO = htons(port); + + anIP = inet_addr(ip); + anIndex = SBServerListFindServerByIP(&sb->list, anIP, aPortNBO); + if (anIndex != -1) + return SBServerListNth(&sb->list, anIndex); + return NULL; +} +#ifdef GSI_UNICODE +SBServer ServerBrowserGetServerByIPW(ServerBrowser sb, const unsigned short* ip, unsigned short port) +{ + char ip_A[20]; + + if(ip == NULL || wcslen(ip) > 16) + return NULL; + + UCS2ToAsciiString(ip, ip_A); + return ServerBrowserGetServerByIPA(sb, ip_A, port); +} +#endif + +int ServerBrowserCount(ServerBrowser sb) +{ + return SBServerListCount(&sb->list); +} + +void ServerBrowserSortA(ServerBrowser sb, SBBool ascending, const char *sortkey, SBCompareMode comparemode) +{ + SortInfo info; + info.comparemode = comparemode; +#ifdef GSI_UNICODE + GS_ASSERT(sortkey != NULL && _tcslen((const unsigned short *)sortkey) <= SORTKEY_LENGTH); + _tcscpy(info.sortkey, (const unsigned short *)sortkey); +#else + GS_ASSERT(sortkey != NULL && _tcslen(sortkey) <= SORTKEY_LENGTH); + _tcscpy(info.sortkey, sortkey); +#endif + SBServerListSort(&sb->list, ascending, info); +} +#ifdef GSI_UNICODE +void ServerBrowserSortW(ServerBrowser sb, SBBool ascending, const unsigned short *sortkey, SBCompareMode comparemode) +{ + char sortkey_A[255]; + UCS2ToUTF8String(sortkey, sortkey_A); + //struct SortInfo info; + //info.comparemode = comparemode; + //GS_ASSERT(sortkey != NULL && _tcslen((const unsigned short *)sortkey) <= SORTKEY_LENGTH); + //_tcscpy(info.sortkey, (const unsigned short *)sortkey_A); + //SBServerListSort(&sb->list, ascending, info); + ServerBrowserSortA(sb, ascending, sortkey_A, comparemode); +} +#endif + +char *ServerBrowserGetMyPublicIP(ServerBrowser sb) +{ + return (char *)inet_ntoa(*(struct in_addr *)&sb->list.mypublicip); +} + +unsigned int ServerBrowserGetMyPublicIPAddr(ServerBrowser sb) +{ + return sb->list.mypublicip; +} + +void ServerBrowserDisconnect(ServerBrowser sb) +{ + SBServerListDisconnect(&sb->list); +} + +// Allows the user to specify a broadcast address for LAN hosting +void ServerBrowserLANSetLocalAddr(ServerBrowser sb, const char* theAddr) +{ + sb->list.mLanAdapterOverride = theAddr; +} + + +/* SBServerGetConnectionInfo +---------------- +Check if Nat Negotiation is requires, based off whether it is a lan game, public ip present and several other facts. +Returns an IP string to use for NatNeg, or direct connect if possible +Work for subsequent connection to this server, One of three results will occur +i) Lan game, connect using ipstring +2) Internet game, connect using ipstring +3) nat traversal required, perform nat negotiation using Nat SDK and this ipstring before connecting. + +return sb_true if further processing is required... i.e. NAT. sb_false if not. +fills an IP string +*/ +SBBool SBServerGetConnectionInfo(ServerBrowser gSB, SBServer server, gsi_u16 PortToConnectTo, char *ipstring) +{ + SBBool natneg = SBFalse; + if (SBServerHasPrivateAddress(server) == SBTrue && (SBServerGetPublicInetAddress(server) == ServerBrowserGetMyPublicIPAddr(gSB))) + { + + //directly connect to private IP (LAN) + sprintf(ipstring,"%s:%d", SBServerGetPrivateAddress(server),PortToConnectTo ); + + } + else + if ((SBServerDirectConnect(server) == SBTrue )&& (SBServerHasPrivateAddress(server) == SBFalse)) + { + //can directly connect to public IP, no negotiation required + sprintf(ipstring,"%s:%d", SBServerGetPrivateAddress(server), PortToConnectTo ); + } + else + { + //Nat Negotiation required + natneg = SBTrue; + sprintf(ipstring,"%s:%d", SBServerGetPublicAddress(server), SBServerGetPublicQueryPort (server) ); + } + return natneg; +} diff --git a/code/gamespy/serverbrowsing/sb_serverbrowsing.h b/code/gamespy/serverbrowsing/sb_serverbrowsing.h new file mode 100644 index 00000000..81a65202 --- /dev/null +++ b/code/gamespy/serverbrowsing/sb_serverbrowsing.h @@ -0,0 +1,480 @@ +/****** +GameSpy Server Browsing SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + + Please see the GameSpy Server Browsing SDK documentation for more + information + +******/ + + +#ifndef _SB_SERVERBROWSING_H +#define _SB_SERVERBROWSING_H + +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + + + +/******************* +ServerBrowser Typedefs +********************/ +//ServerBrowser is an abstract data type used to represent the server list and query engine objects. +typedef struct _ServerBrowser *ServerBrowser; + +//SBServer is an abstract data type representing a single server. +#ifndef SBServer +typedef struct _SBServer *SBServer; +#endif +//Simple boolean type used for some functions +typedef enum {SBFalse, SBTrue} SBBool; + +//Error codes that can be returned from functions +typedef enum +{ + sbe_noerror, //no error has occured + sbe_socketerror, //a socket function has returned an unexpected error + sbe_dnserror, //DNS lookup of master address failed + sbe_connecterror, //connection to master server failed + sbe_dataerror, //invalid data was returned from master server + sbe_allocerror, //memory allocation failed + sbe_paramerror, //an invalid parameter was passed to a function + sbe_duplicateupdateerror //server update requested on a server that was already being updated +} SBError; + +//States the ServerBrowser object can be in +typedef enum +{ + sb_disconnected, //idle and not connected to the master server + sb_listxfer, //downloading list of servers from the master server + sb_querying, //querying servers + sb_connected //idle but still connected to the master server +} SBState; + +//Callbacks that can occur during server browsing operations +typedef enum +{ + sbc_serveradded, //a server was added to the list, may just have an IP & port at this point + sbc_serverupdated, //server information has been updated - either basic or full information is now available about this server + sbc_serverupdatefailed, //an attempt to retrieve information about this server, either directly or from the master, failed + sbc_serverdeleted, //a server was removed from the list + sbc_updatecomplete, //the server query engine is now idle + sbc_queryerror, //the master returned an error string for the provided query + sbc_serverchallengereceived // received ip verification challenge from server +} SBCallbackReason; + +//Passed to callback to indicate state of attempt to connect to server +typedef enum +{ + sbcs_succeeded, + sbcs_failed +} SBConnectToServerState; + +//Prototype for the callback function you need to provide +typedef void (*ServerBrowserCallback)(ServerBrowser sb, SBCallbackReason reason, SBServer server, void *instance); + +//Prototype for the callback function used when connecting to a server +typedef void (*SBConnectToServerCallback)(ServerBrowser sb, SBConnectToServerState state, SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *instance); + +//Maximum length for the SQL filter string +#define MAX_FILTER_LEN 256 + +//Version defines for query protocol +#define QVERSION_GOA 0 +#define QVERSION_QR2 1 + +/******************* +ServerBrowser Object Functions +********************/ + +#ifndef GSI_UNICODE +#define ServerBrowserNew ServerBrowserNewA +#define ServerBrowserUpdate ServerBrowserUpdateA +#define ServerBrowserLimitUpdate ServerBrowserLimitUpdateA +#define ServerBrowserAuxUpdateIP ServerBrowserAuxUpdateIPA +#define ServerBrowserRemoveIP ServerBrowserRemoveIPA +#define ServerBrowserSendNatNegotiateCookieToServer ServerBrowserSendNatNegotiateCookieToServerA +#define ServerBrowserSendMessageToServer ServerBrowserSendMessageToServerA +#define ServerBrowserSort ServerBrowserSortA +#define SBServerGetStringValue SBServerGetStringValueA +#define SBServerGetIntValue SBServerGetIntValueA +#define SBServerGetFloatValue SBServerGetFloatValueA +#define SBServerGetBoolValue SBServerGetBoolValueA +#define SBServerGetPlayerStringValue SBServerGetPlayerStringValueA +#define SBServerGetPlayerIntValue SBServerGetPlayerIntValueA +#define SBServerGetPlayerFloatValue SBServerGetPlayerFloatValueA +#define SBServerGetTeamStringValue SBServerGetTeamStringValueA +#define SBServerGetTeamIntValue SBServerGetTeamIntValueA +#define SBServerGetTeamFloatValue SBServerGetTeamFloatValueA +#define ServerBrowserListQueryError ServerBrowserListQueryErrorA +#define ServerBrowserErrorDesc ServerBrowserErrorDescA +#define ServerBrowserGetServerByIP ServerBrowserGetServerByIPA +#else +#define ServerBrowserNew ServerBrowserNewW +#define ServerBrowserUpdate ServerBrowserUpdateW +#define ServerBrowserLimitUpdate ServerBrowserLimitUpdateW +#define ServerBrowserAuxUpdateIP ServerBrowserAuxUpdateIPW +#define ServerBrowserRemoveIP ServerBrowserRemoveIPW +#define ServerBrowserSendNatNegotiateCookieToServer ServerBrowserSendNatNegotiateCookieToServerW +#define ServerBrowserSendMessageToServer ServerBrowserSendMessageToServerW +#define ServerBrowserSort ServerBrowserSortW +#define SBServerGetStringValue SBServerGetStringValueW +#define SBServerGetIntValue SBServerGetIntValueW +#define SBServerGetFloatValue SBServerGetFloatValueW +#define SBServerGetBoolValue SBServerGetBoolValueW +#define SBServerGetPlayerStringValue SBServerGetPlayerStringValueW +#define SBServerGetPlayerIntValue SBServerGetPlayerIntValueW +#define SBServerGetPlayerFloatValue SBServerGetPlayerFloatValueW +#define SBServerGetTeamStringValue SBServerGetTeamStringValueW +#define SBServerGetTeamIntValue SBServerGetTeamIntValueW +#define SBServerGetTeamFloatValue SBServerGetTeamFloatValueW +#define ServerBrowserListQueryError ServerBrowserListQueryErrorW +#define ServerBrowserErrorDesc ServerBrowserErrorDescW +#define ServerBrowserGetServerByIP ServerBrowserGetServerByIPW +#endif +/* +ServerBrowserNew +---------------- +Creates and returns a new (empty) ServerBrowser object. +Returns NULL if an allocation error occurs. + +queryForGamename - The gamename you are querying for +queryFromGamename - The gamename you are querying from - generally the same as queryForGamename +queryFromKey - Secret key that corresponds to the queryFromGamename +queryFromVersion - A game-specific version identifier (pass 0 unless told otherwise) +maxConcUpdates - Max number of concurent updates (10-15 for modem users, 20-30 for high-bandwidth) +queryVersion - Query protocol to use. Use QVERSION_GOA for DeveloperSpec/Query&Reporting1 games, and QVERSION_QR2 for games that use Query & Reporting 2 +callback - The function that will be called with list updates +instance - User-defined instance data (e.g. structure or object pointer) */ +ServerBrowser ServerBrowserNew(const gsi_char *queryForGamename, const gsi_char *queryFromGamename, const gsi_char *queryFromKey, int queryFromVersion, int maxConcUpdates, int queryVersion, SBBool lanBrowse, ServerBrowserCallback callback, void *instance); + +/* +ServerBrowserFree +----------------- +Free a ServerBrowser and all internal sturctures and servers */ +void ServerBrowserFree(ServerBrowser sb); + + +/* ServerBrowserUpdate +------------------- +Starts an update by downloading a list of servers from the master server, then querying them. + +sb - The server browser object to update +async - If SBTrue, the update will be initiated, and ServerListThink must be called for processing and querying to occur + If SBFalse, the function will not return until the initial list of servers has been completely updated +disconnectOnComplete - If SBTrue, the connection to the master server will be disconnected immediately after the list is downloaded. + If SBFalse, the connection will be left open for additional data queries, and can be closed via ServerBrowserDisconnect +basicFields - This array of registered QR2 keys is used to determine the fields requested from servers during the initial "basic" update. + Only server keys listed in this array will be returned for servers. +numBasicFields - The number of fields in the basicFields array +serverFilter - SQL Filter string that will be applied on the master server to limit the list of servers returned. + All server keys are available for filtering on the master server, as well as the master-defined "country" and "region" keys. + Standard SQL syntax should be used. + +ServerBrowserLimitUpdate +------------------------ +Identical to ServerBrowserUpdate, except that the number of servers returned can be limited +maxServers - Maximum number of servers to be returned +*/ +SBError ServerBrowserUpdate(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const gsi_char *serverFilter); +SBError ServerBrowserLimitUpdate(ServerBrowser sb, SBBool async, SBBool disconnectOnComplete, const unsigned char *basicFields, int numBasicFields, const gsi_char *serverFilter, int maxServers); + + +/* ServerBrowserThink +------------------- +Processes incoming data from the master server and game servers that are being queried. Should be called +as often as possible while a server list update is in progress (~10ms is ideal). */ +SBError ServerBrowserThink(ServerBrowser sb); + +/* ServerBrowserLANUpdate +------------------- +Starts an update by searching for servers on the LAN, then querying them. You can specifiy a range of ports to search +for servers. Generally this should start with your standard query port, and range above it, since the QR and QR2 SDKs will +automatically allocate higher port numbers when running multiple servers on the same machine. You should limit your search +to 100 ports or less in most cases to limit flooding of the LAN with broadcast packets. + +sb - The server browser object to update +async - If SBTrue, the update will be initiated, and ServerListThink must be called for processing and querying to occur + If SBFalse, the function will not return until the initial list of servers has been completely updated +startSearchPort - The initial port to begin searching for servers on +endSearchPort - The final port to search. All ports between start and end will be queried. */ +SBError ServerBrowserLANUpdate(ServerBrowser sb, SBBool async, unsigned short startSearchPort, unsigned short endSearchPort); + +/* ServerBrowserAuxUpdateIP +------------------- +Manually updates a server given an IP address and query port. Use to manually add servers to the list when you just +have an IP and port for them. + +sb - The server browser object to add the server to +ip - The dotted IP address of the server e.g. "1.2.3.4" +port - The query port of the server +viaMaster - If SBTrue, information about the server will be retrieved from the master server instead of attempting to query the server directly. + If a connection to the master server does not exist, it will be made to kept open afterwards. + If SBFalse, the server will be contacted directly for information. +async - If SBTrue, the update will be initiated, and ServerListThink must be called for processing and querying to occur + If SBFalse, the function will not return until the server has been successfully or unsuccessfully updated +fullUpdate - If SBTrue, all server keys/rules/player/team information will be retrieved + If SBFalse, only the keys specified in the basicFields array of the ServerBrowserUpdate function will be retrieved */ +SBError ServerBrowserAuxUpdateIP(ServerBrowser sb, const gsi_char *ip, unsigned short port, SBBool viaMaster, SBBool async, SBBool fullUpdate); + +/* ServerBrowserAuxUpdateServer +------------------- +Manually updates a server object. Generally used to get additional information about a server (for example, to get full rules and +player information from a server that only has basic information so far), but can also be used to "refresh" the information +about a given server. Data will automatically be retrieved from the master server directly or from the game server as appropriate. +When called asynchronously, multiple server update requests can be queued and will be executed by the query engine in turn. + +sb - The server browser object to add the server to +server - Server object to update +async - If SBTrue, the update will be initiated, and ServerListThink must be called for processing and querying to occur + If SBFalse, the function will not return until the server has been successfully or unsuccessfully updated +fullUpdate - If SBTrue, all server keys/rules/player/team information will be retrieved + If SBFalse, only the keys specified in the basicFields array of the ServerBrowserUpdate function will be retrieved */ +SBError ServerBrowserAuxUpdateServer(ServerBrowser sb, SBServer server, SBBool async, SBBool fullUpdate); + + +/* ServerBrowserDisconnects +------------------- +Disconnects an idle connection to the master server when it is no longer needed. Note that if you disconnect +and then request an operation that requires a connection to the master, such as an AuxServerUpdate via the master, +the connection will be automatically re-established. */ +void ServerBrowserDisconnect(ServerBrowser sb); + + +/* ServerBrowserState +------------------- +Returns the current state of the Server Browser object */ +SBState ServerBrowserState(ServerBrowser sb); + +/* ServerBrowserRemoveIP +------------------- +Removes a server from the list given an IP and query port */ +void ServerBrowserRemoveIP(ServerBrowser sb, const gsi_char *ip, unsigned short port); + +/* ServerBrowserRemoveServer +------------------- +Removes a server from the list and releases all resources associated with it */ +void ServerBrowserRemoveServer(ServerBrowser sb, SBServer server); + +/* ServerBrowserHalt +------------------- +Stops a server list update in progress, clears any servers queued to be queried, and disconneects +from the master server. */ +void ServerBrowserHalt(ServerBrowser sb); + +/* ServerBrowserClear +------------------- +Removes all the servers from the list and frees all resources associated with them. */ +void ServerBrowserClear(ServerBrowser sb); + +/* ServerBrowserErrorDesc +------------------- +Returns a human-readable error string for the given error code. */ +const gsi_char *ServerBrowserErrorDesc(ServerBrowser sb, SBError error); + +/* ServerBrowserListQueryError +------------------- +When a list query error occurs, as indicated by the sbc_queryerror callback, this function allows you to +obtain the human-readable error string for the error (generally these errors are caused by errors in the +filter string) */ +const gsi_char *ServerBrowserListQueryError(ServerBrowser sb); + +/* ServerBrowserGetServer +---------------------- +Returns the server at the specified index, or NULL if the index is out of bounds */ +SBServer ServerBrowserGetServer(ServerBrowser sb, int index); + +/* ServerBrowserGetServerByIP +---------------------- +Returns the SBServer with the specified IP, or NULL if the server is not in the list */ +SBServer ServerBrowserGetServerByIP(ServerBrowser sb, const gsi_char* ip, unsigned short port); + +/* ServerBrowserCount +------------------ +Returns the number of servers on the specified list. Indexing is 0 based, so +the actual server indexes are 0 <= valid index < Count */ +int ServerBrowserCount(ServerBrowser sb); + +/* ServerBrowserPendingQueryCount +------------------ +Returns the number of servers currently being queried or queued to be queried. When this number is 0, the query +engine is idle */ +int ServerBrowserPendingQueryCount(ServerBrowser sb); + +/* ServerBrowserGetMyPublicIP +------------------ +Returns the public IP address for this computer, as seen by an outside machine (the master server). Use to determine +if a server you want to connect to is on the same private network or not. Only valid after a call to ServerListUpdate has +connected to the master server */ +char *ServerBrowserGetMyPublicIP(ServerBrowser sb); + +/* ServerBrowserGetMyPublicIPAddr +------------------ +Same as ServerBrowserGetMyPublicIP except that the address is returned in standard network-byte-order form */ +unsigned int ServerBrowserGetMyPublicIPAddr(ServerBrowser sb); + + +/* ServerBrowserSendNatNegotiateCookieToServer +------------------ +Sends a cookie value to the server for use with NAT Negotiation */ +SBError ServerBrowserSendNatNegotiateCookieToServer(ServerBrowser sb, const gsi_char *ip, unsigned short port, int cookie); + + +/* ServerBrowserSendMessageToServer +------------------ +Sends a game-specific message to a server */ +SBError ServerBrowserSendMessageToServer(ServerBrowser sb, const gsi_char *ip, unsigned short port, const char *data, int len); + +/* ServerBrowserConnectToServer +------------------ +Attempts to connect to the server, using natneg if necessary */ +SBError ServerBrowserConnectToServer(ServerBrowser sb, SBServer server, SBConnectToServerCallback callback); + + +/* Comparision types for the ServerBrowserSort function +int - assume the values are int and do an integer compare +float - assume the values are float and do a flot compare +strcase - assume the values are strings and do a case sensitive compare +stricase - assume the values are strings and do a case insensitive compare */ +typedef enum {sbcm_int, sbcm_float, sbcm_strcase, sbcm_stricase} SBCompareMode; + + +/* ServerBrowserSort +----------------- +Sort the server list in either ascending or descending order using the +specified comparemode. +sortkey can be a normal server key, or "ping" or "hostaddr" */ +void ServerBrowserSort(ServerBrowser sb, SBBool ascending, const gsi_char *sortkey, SBCompareMode comparemode); + +/* ServerBrowserLANSetLocalAddr +------------------- +Sets the network adapter to use for LAN broadcasts (optional) */ +void ServerBrowserLANSetLocalAddr(ServerBrowser sb, const char* theAddr); + + +/******************* +SBServer Object Functions +********************/ + + +// Callback function used for enumerating the keys/values for a server +typedef void (*SBServerKeyEnumFn)(gsi_char *key, gsi_char *value, void *instance); + + +/* SBServerGetConnectionInfo +---------------- +Check if Nat Negotiation is requires, based off whether it is a lan game, public ip present and several other facts. +Returns an IP string to use for NatNeg, or direct connect if possible +Work for subsequent connection to this server, One of three results will occur +i) Lan game, connect using ipstring +2) Internet game, connect using ipstring +3) nat traversal required, perform nat negotiation using Nat SDK and this ipstring before connecting. + +return sb_true if further processing is required... i.e. NAT. sb_false if not. +fills an IP string +*/ +SBBool SBServerGetConnectionInfo(ServerBrowser gSB, SBServer server, gsi_u16 PortToConnectTo, char *ipstring_out); + + +/* SBServerHasPrivateAddress +---------------- +Indicates whether the master server has provided a private address for this server */ +SBBool SBServerHasPrivateAddress(SBServer server); + +/* SBServerDirectConnect +---------------- +Indicates whether the server supports direct UDP connections (if false, NAT Negotiation is required) */ +SBBool SBServerDirectConnect(SBServer server); + +/* SBServerGetPing +---------------- +Returns the ping for the specified server. Ping is measured as the latency from the time a +query is sent to the server until the data is returned for that query */ +int SBServerGetPing(SBServer server); + + +/* SBServerGetPublicAddress/SBServerGetPrivateAddress +------------------- +Returns the string, dotted IP address for the specified server +The "private" version is only valid when the server has a private address available */ +char *SBServerGetPublicAddress(SBServer server); +char *SBServerGetPrivateAddress(SBServer server); + +/* SBServerGetPublicInetAddress/SBServerGetPrivateInetAddress +------------------- +Returns the network-ordered IP address for the specified server */ +unsigned int SBServerGetPublicInetAddress(SBServer server); +unsigned int SBServerGetPrivateInetAddress(SBServer server); + +/* SBServerGetPublicQueryPort/SBServerGetPrivateQueryPort +---------------- +Returns the "query" port for the specified server. If the game uses a seperate +"game" port, it can be retrieved via: SBServerGetIntValue(server,"hostport",0) */ +unsigned short SBServerGetPublicQueryPort(SBServer server); +unsigned short SBServerGetPrivateQueryPort(SBServer server); + + +/* SBServerHasBasicKeys +---------------- +Returns SBTrue if a server has at least basic keys available for it. "Basic" keys +are those indicated in the ServerBrowserUpdate function. */ +SBBool SBServerHasBasicKeys(SBServer server); + +/* SBServerHasFullKeys +---------------- +Returns SBTrue if a server has full keys available for it. This includes all server +rules and player/team keys. */ +SBBool SBServerHasFullKeys(SBServer server); + +/* SBServerHasValidPing +---------------- +Returns SBTrue if a server has a valid ping value for it (otherwise the ping will be 0) */ +SBBool SBServerHasValidPing(SBServer server); + + +/* SBServerGet[]Value +------------------ +Returns the value for the specified key. If the key does not exist for the +given server, the default value is returned */ +const gsi_char *SBServerGetStringValue(SBServer server, const gsi_char *keyname, const gsi_char *def); +int SBServerGetIntValue(SBServer server, const gsi_char *key, int idefault); +double SBServerGetFloatValue(SBServer server, const gsi_char *key, double fdefault); +SBBool SBServerGetBoolValue(SBServer server, const gsi_char *key, SBBool bdefault); + + +/* SBServerGetPlayer[]Value / SBServerGetTeam[]Value +------------------ +Returns the value for the specified key on the specified player or team. If the key does not exist for the +given server, the default value is returned +Player keys take the form keyname_N where N is the player index, and team keys take the form +keyname_tN where N is the team index. You should only specify the keyname for the key in the below functions. +*/ +const gsi_char *SBServerGetPlayerStringValue(SBServer server, int playernum, const gsi_char *key, const gsi_char *sdefault); +int SBServerGetPlayerIntValue(SBServer server, int playernum, const gsi_char *key, int idefault); +double SBServerGetPlayerFloatValue(SBServer server, int playernum, const gsi_char *key, double fdefault); + +const gsi_char *SBServerGetTeamStringValue(SBServer server, int teamnum, const gsi_char *key, const gsi_char *sdefault); +int SBServerGetTeamIntValue(SBServer server, int teamnum, const gsi_char *key, int idefault); +double SBServerGetTeamFloatValue(SBServer server, int teamnum, const gsi_char *key, double fdefault); + + +/* SBServerEnumKeys +----------------- +Enumerates the keys/values for a given server by calling KeyEnumFn with each +key/value. The user-defined instance data will be passed to the KeyFn callback */ +void SBServerEnumKeys(SBServer server, SBServerKeyEnumFn KeyFn, void *instance); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/gamespy/serverbrowsing/sb_serverlist.c b/code/gamespy/serverbrowsing/sb_serverlist.c new file mode 100644 index 00000000..40a1465f --- /dev/null +++ b/code/gamespy/serverbrowsing/sb_serverlist.c @@ -0,0 +1,1816 @@ +#include "sb_serverbrowsing.h" +#include "sb_internal.h" +#include "sb_ascii.h" + + +#define SERVER_GROWBY 100 + +//for the master server info +#define INCOMING_BUFFER_SIZE 4096 + +#define MAX_OUTGOING_REQUEST_SIZE (MAX_FIELD_LIST_LEN + MAX_FILTER_LEN + 255) + + + + + + +static SBServerList *g_sortserverlist; //global serverlist for sorting info!! + + +//private function used to compare the key values based on a previously defined sortkey +static int prevKeyCompare(SBServer server1, SBServer server2) +{ + const char *prevsortkey = (const char *)g_sortserverlist->prevsortinfo.sortkey; + int diff; + double f; + //test which type of sort + switch(g_sortserverlist->prevsortinfo.comparemode) + { + case sbcm_int: + diff = SBServerGetIntValueA(server1, prevsortkey, 0) - + SBServerGetIntValueA(server2, prevsortkey, 0); + break; + case sbcm_float: + f = SBServerGetFloatValueA(server1, prevsortkey, 0) - + SBServerGetFloatValueA(server2, prevsortkey, 0); + if (!g_sortserverlist->sortascending) + f = -f; + if ((float)f > (float)0.0) + return 1; + else if ((float)f < (float)0.0) + return -1; + else + return 0; + //break; + case sbcm_strcase: + diff = strcmp(SBServerGetStringValueA(server1, prevsortkey, ""), + SBServerGetStringValueA(server2, prevsortkey, "")); + break; + case sbcm_stricase: + diff = strcasecmp(SBServerGetStringValueA(server1, prevsortkey, ""), + SBServerGetStringValueA(server2, prevsortkey, "")); + break; + default: + return 0; + } + if (!g_sortserverlist->sortascending) + diff = -diff; + return diff; +} + + +/**** +Comparision Functions +***/ +static int GS_STATIC_CALLBACK IntKeyCompare(const void *entry1, const void *entry2) +{ + SBServer server1 = *(SBServer *)entry1, server2 = *(SBServer *)entry2; + int diff; + const char *currsortkey = (const char *)g_sortserverlist->currsortinfo.sortkey; + + diff = SBServerGetIntValueA(server1, currsortkey, 0) - + SBServerGetIntValueA(server2, currsortkey, 0); + + if (diff == 0) //if equal, sort by previous sort value to retain earlier sort + return prevKeyCompare(server1, server2); + + if (!g_sortserverlist->sortascending) + diff = -diff; + return diff; + +} + +static int GS_STATIC_CALLBACK FloatKeyCompare(const void *entry1, const void *entry2) +{ + SBServer server1 = *(SBServer *)entry1, server2 = *(SBServer *)entry2; + double f; + const char *currsortkey = (const char *)g_sortserverlist->currsortinfo.sortkey; + + f = SBServerGetFloatValueA(server1, currsortkey, 0) - + SBServerGetFloatValueA(server2, currsortkey, 0); + + //if equal, sort by previous sort value to retain earlier sort + if ( !((float)f > (float)0.0) && !((float)f < (float)0.0) ) + return prevKeyCompare(server1, server2); + + if (!g_sortserverlist->sortascending) + f = -f; + if ((float)f > (float)0.0) + return 1; + else if ((float)f < (float)0.0) + return -1; + else + return 0; +} + +static int GS_STATIC_CALLBACK StrCaseKeyCompare(const void *entry1, const void *entry2) +{ + SBServer server1 = *(SBServer *)entry1, server2 = *(SBServer *)entry2; + int diff; + const char *currsortkey = (const char *)g_sortserverlist->currsortinfo.sortkey; + + diff = strcmp(SBServerGetStringValueA(server1, currsortkey, ""), + SBServerGetStringValueA(server2, currsortkey, "")); + + if (diff == 0) //if equal, sort by previous sort value to retain earlier sort + return prevKeyCompare(server1, server2); + + if (!g_sortserverlist->sortascending) + diff = -diff; + return diff; +} + +static int GS_STATIC_CALLBACK StrNoCaseKeyCompare(const void *entry1, const void *entry2) +{ + SBServer server1 = *(SBServer *)entry1, server2 = *(SBServer *)entry2; + int diff; + const char *currsortkey = (const char *)g_sortserverlist->currsortinfo.sortkey; + + diff = strcasecmp(SBServerGetStringValueA(server1, currsortkey, ""), + SBServerGetStringValueA(server2, currsortkey, "")); + + if (diff == 0) //if equal, sort by previous sort value to retain earlier sort + return prevKeyCompare(server1, server2); + + if (!g_sortserverlist->sortascending) + diff = -diff; + return diff; +} + + +/* ServerListSort +----------------- +Sort the server list in either ascending or descending order using the +specified comparemode. +sortkey can be a normal server key, or "ping" or "hostaddr" */ +void SBServerListSort(SBServerList *slist, SBBool ascending, SortInfo sortinfo) +{ + ArrayCompareFn comparator; + switch (sortinfo.comparemode) + { + case sbcm_int: comparator = IntKeyCompare; + break; + case sbcm_float: comparator = FloatKeyCompare; + break; + case sbcm_strcase: comparator = StrCaseKeyCompare; + break; + case sbcm_stricase: comparator = StrNoCaseKeyCompare; + break; + default: + comparator = StrNoCaseKeyCompare; + } + + //set last used sortkey + if (_tcslen(slist->prevsortinfo.sortkey) == 0) //set prev to current if not initialized + slist->prevsortinfo = sortinfo; + else if (strcmp((const char *)sortinfo.sortkey, (const char *)slist->currsortinfo.sortkey) != 0) + slist->prevsortinfo = slist->currsortinfo; //only set new sort if different than prev + + slist->currsortinfo = sortinfo; + slist->sortascending = ascending; + g_sortserverlist = slist; + ArraySort(slist->servers,comparator); +} + + + + +void SBServerListAppendServer(SBServerList *slist, SBServer server) +{ + ArrayAppend(slist->servers, &server); + slist->ListCallback(slist, slc_serveradded, server, slist->instance); +} + + + +int SBServerListFindServer(SBServerList *slist, SBServer findserver) +{ + int numservers; + int i; + numservers = ArrayLength(slist->servers); + for (i = 0 ; i < numservers ; i++) + { + if (findserver == *(SBServer *)ArrayNth(slist->servers, i)) + { + return i; + } + } + return -1; +} + +int SBServerListFindServerByIP(SBServerList *slist, goa_uint32 ip, unsigned short port) +{ + int numservers; + SBServer server; + int i; + numservers = ArrayLength(slist->servers); + for (i = 0 ; i < numservers ; i++) + { + server = *(SBServer *)ArrayNth(slist->servers, i); + if (SBServerGetPublicInetAddress(server) == ip && SBServerGetPublicQueryPortNBO(server) == port) + { + return i; + } + } + return -1; +} + +//add to the singly linked list of dead servers +static void AddServerToDeadlist(SBServerList *slist, SBServer server) +{ + if (slist->deadlist == NULL) + { + SBServerSetNext(server, NULL); + } else + SBServerSetNext(server, slist->deadlist); + slist->deadlist = server; +} + + +void SBServerListRemoveAt(SBServerList *slist, int index) +{ + SBServer server = *(SBServer *)ArrayNth(slist->servers, index); + slist->ListCallback(slist, slc_serverdeleted, server, slist->instance); + //need to remove it... + ArrayDeleteAt(slist->servers, index); + //now add it to the dead list + AddServerToDeadlist(slist, server); +} + +int SBServerListCount(SBServerList *slist) +{ + return ArrayLength(slist->servers); +} + +SBServer SBServerListNth(SBServerList *slist, int i) +{ + return *(SBServer *)ArrayNth(slist->servers, i); +} + + +void SBFreeDeadList(SBServerList *slist) +{ + SBServer server, next; + if (slist->deadlist == NULL) + return; + server = slist->deadlist; + while (server != NULL) + { + next = SBServerGetNext(server); + SBServerFree(&server); + server = next; + } + slist->deadlist = NULL; +} + + +void SBServerListClear(SBServerList *slist) +{ + //we need to add each server to the dead list so it can be freed after the clear + int i; + int nservers = ArrayLength(slist->servers); + for (i = 0 ; i < nservers ; i++) + { + AddServerToDeadlist(slist, *(SBServer *)ArrayNth(slist->servers, i)); + } + ArrayClear(slist->servers); + //now free the dead list + SBFreeDeadList(slist); +} + +void SBAllocateServerList(SBServerList *slist) +{ + slist->servers = ArrayNew(sizeof(SBServer ), SERVER_GROWBY, NULL); //don't free the server automatically - it goes into the dead list + slist->deadlist = NULL; +} + + + +const char *SBRefStr(SBServerList *slist, const char *str) +{ + SBRefString ref, *val; + ref.str = str; + val = (SBRefString *)TableLookup(SBRefStrHash(slist), &ref); + if (val != NULL) + { + val->refcount++; + return val->str; + } + + //else we need to add. + ref.str = goastrdup(str); + ref.refcount = 1; +#ifdef GSI_UNICODE + ref.str_W = UTF8ToUCS2StringAlloc(str); +#endif + TableEnter(SBRefStrHash(slist), &ref); + return ref.str; + + +} + +void SBReleaseStr(SBServerList *slist, const char *str) +{ + SBRefString ref, *val; + ref.str = str; + val = (SBRefString *)TableLookup(SBRefStrHash(slist), &ref); + assert(val != NULL); + if (val == NULL) + return; //not found! + val->refcount--; + if (val->refcount == 0) + TableRemove(SBRefStrHash(slist), &ref); +} + +#ifdef VENGINE_SUPPORT + #define FTABLE_ASSIGN + #include "../../VEngine/ve_gm3ftable.h" +#endif + + +#ifdef VENGINE_SUPPORT +#define FTABLE_DEFINES +#include "../../VEngine/ve_gm3ftable.h" +#endif + + + +//global pointer to alternate master server name/IP +char *SBOverrideMasterServer = NULL; + + +int NTSLengthSB(char *buf, int len) +{ + int i; + for (i = 0 ; i < len ; i++) + { + if (buf[i] == '\0') //found a full NTS + return i + 1; //return the length including the null + } + return -1; +} + + + +void SBServerListInit(SBServerList *slist, const char *queryForGamename, const char *queryFromGamename, const char *queryFromKey, int queryFromVersion, SBBool lanBrowse, SBListCallBackFn callback, void *instance) +{ + assert(slist != NULL); + // 11-03-2004 : Added by Saad Nader + // fix for LANs and unnecessary availability check + /////////////////////////////////////////////////// + if (lanBrowse == SBFalse) + { + if(__GSIACResult != GSIACAvailable) + return; + } + +#ifdef VENGINE_SUPPORT + SBServerListSetPointers(slist); +#endif + + slist->state = sl_disconnected; + SBAllocateServerList(slist); + SBRefStrHash(slist); //make sure it's initialized + strcpy(slist->queryforgamename, queryForGamename); + strcpy(slist->queryfromgamename, queryFromGamename); + strcpy(slist->queryfromkey, queryFromKey); + slist->ListCallback = callback; + slist->MaploopCallback = NULL; //populate when requested + assert(callback != NULL); + slist->instance = instance; + slist->mypublicip = 0; + slist->slsocket = INVALID_SOCKET; + slist->inbuffer = NULL; + slist->inbufferlen = 0; + slist->keylist = NULL; + slist->expectedelements = -1; + slist->numpopularvalues = 0; + slist->srcip = 0; + slist->fromgamever = queryFromVersion; +#ifdef GSI_UNICODE + slist->lasterror = NULL; + slist->lasterror_W = NULL; + _tcscpy(slist->currsortinfo.sortkey, (const unsigned short *)""); + _tcscpy(slist->prevsortinfo.sortkey, (const unsigned short *)""); +#else + _tcscpy(slist->currsortinfo.sortkey, ""); + _tcscpy(slist->prevsortinfo.sortkey, ""); +#endif + SBSetLastListErrorPtr(slist, ""); + slist->mLanAdapterOverride = NULL; + slist->backendgameflags = QR2_USE_QUERY_CHALLENGE; + + srand((unsigned int)current_time()); + SocketStartUp(); + +} + + +static void ErrorDisconnect(SBServerList *slist) +{ + static char* QUERY_ERROR = "Query Error: "; + + // check to see if there is a Query Error string in the inbuffer + if ((slist->inbufferlen > 0) && ((unsigned int)slist->inbufferlen > strlen(QUERY_ERROR)) && + (0 == strncmp(slist->inbuffer, QUERY_ERROR, strlen(QUERY_ERROR)))) + { + // call the callback with queryerror first + SBSetLastListErrorPtr(slist, slist->inbuffer + strlen(QUERY_ERROR)); + slist->ListCallback(slist, slc_queryerror, SBNullServer, slist->instance); + } + + //call the callback.. + slist->ListCallback(slist, slc_disconnected, SBNullServer, slist->instance); + SBServerListDisconnect(slist); +} + +#define MULTIPLIER -1664117991 +static int StringHash(const char *s, int numbuckets) +{ + goa_uint32 hashcode = 0; + while (*s != 0) + { + hashcode = (goa_uint32)((int)hashcode * MULTIPLIER + tolower(*s)); + s++; + } + return (int)(hashcode % numbuckets); + +} + + + +static SBError ServerListConnect(SBServerList *slist) +{ + struct sockaddr_in saddr; + struct hostent *hent; + char masterHostname[128]; + int masterIndex; + + + masterIndex = StringHash(slist->queryforgamename, NUM_MASTER_SERVERS); + if (SBOverrideMasterServer != NULL) + strcpy(masterHostname, SBOverrideMasterServer); + else //use the default format... + sprintf(masterHostname,"%s.ms%d." GSI_DOMAIN_NAME, slist->queryforgamename, masterIndex); + saddr.sin_family = AF_INET; + saddr.sin_port = htons(MSPORT2); + saddr.sin_addr.s_addr = inet_addr(masterHostname); + if (saddr.sin_addr.s_addr == INADDR_NONE) + { + hent = gethostbyname(masterHostname); + if (!hent) + return sbe_dnserror; + memcpy(&saddr.sin_addr.s_addr,hent->h_addr_list[0], sizeof(saddr.sin_addr.s_addr)); + } + + if (slist->slsocket == INVALID_SOCKET) + { + slist->slsocket = socket ( AF_INET, SOCK_STREAM, IPPROTO_TCP ); + if (slist->slsocket == INVALID_SOCKET) + return sbe_socketerror; + } + if (connect ( slist->slsocket, (struct sockaddr *) &saddr, sizeof saddr ) != 0) + { + closesocket(slist->slsocket); + slist->slsocket = INVALID_SOCKET; + return sbe_connecterror; + } + + //else we are connected + return sbe_noerror; + +} + +static void BufferAddNTS(char **buffer, const char *str, int *len) +{ + int slen; + if (str == NULL) + str = ""; + slen = (int)strlen(str) + 1; + memcpy(*buffer, str, (size_t)slen); + (*len) += slen; + (*buffer) += slen; +} + +static void BufferAddByte(char **buffer, unsigned char bval, int *len) +{ + *(*buffer) = (char)bval; + (*len) += 1; + (*buffer) += 1; +} + +static void BufferAddInt(char **buffer, int ival, int *len) +{ + memcpy(*buffer, &ival, 4); + (*len) += 4; + (*buffer) += 4; +} + +// This a utility to write ival as a little-endian (PC) byte order number +static void BufferAddIntLE(char **buffer, int ival, int *len) +{ + unsigned int ivalNBO = htonl(ival); + unsigned int ivalBE = 0; + ivalBE |= ((0x000000FF&ivalNBO)<<24); + ivalBE |= ((0x0000FF00&ivalNBO)<< 8); + ivalBE |= ((0x00FF0000&ivalNBO)>> 8); + ivalBE |= ((0xFF000000&ivalNBO)>>24); + BufferAddInt(buffer, (int)ivalBE, len); +} + + +static void BufferAddData(char **buffer, char *data, int dlen, int *len) +{ + memcpy(*buffer, data, (size_t)dlen); + (*len) += dlen; + (*buffer) += dlen; +} + + + +#define CALCULATEODDMODE(buffer, i, oddmode) ((buffer[i-1] & 1) ^ (i & 1) ^ oddmode ^ (buffer[0] & 1) ^ ((buffer[0] < 79) ? 1 : 0) ^ ((buffer[i-1] < buffer[0]) ? 1 : 0)); +static void SetupListChallenge(SBServerList *slist) +{ + int i; + int oddmode; + + slist->mychallenge[0] = (char)(33 + rand() % 93); //use chars in the range 33 - 125 + oddmode = 0; + for (i = 1; i < LIST_CHALLENGE_LEN ; i++) + { + oddmode = CALCULATEODDMODE(slist->mychallenge,i, oddmode); + slist->mychallenge[i] = (char)(33 + rand() % 93); //use chars in the range 33 - 125 + //if oddmode make sure the char is odd, otherwise make sure it's even + if ((oddmode && (slist->mychallenge[i] & 1) == 0) || (!oddmode && ((slist->mychallenge[i] & 1) == 1))) + slist->mychallenge[i]++; + + } +} + +static SBError SendWithRetry(SBServerList *slist, char *data, int len) +{ + SBError err; + int ret = 0; + + int retryCount = 1; + + while (retryCount >= 0) + { + retryCount--; + ret = send(slist->slsocket, data, len, 0); + if (ret <= 0 && retryCount >= 0) //error! try to reconnect + { + if (slist->inbufferlen > 0) + break; + else + SBServerListDisconnect(slist); + err = SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0); + if (err != sbe_noerror) + { + ErrorDisconnect(slist); + return err; //couldn't reconnect + } + } else + break; //send ok + } + +#ifdef INSOCK + GSI_UNUSED(data); + GSI_UNUSED(len); +#endif + + if (ret <= 0) + return sbe_connecterror; + else + return sbe_noerror; +} + + + +/* +1 bytes - message type (0) +2 bytes - message length +Protocol Version - Byte +Requested Encoding - Byte +Query For Game - NTS +Query From Game - NTS +Server Challenge - 8 bytes +Filter - NTS +Field List - NTS +Options - 4 bytes +Send Fields for non-NAT servers too (TurboQuery!) +No-server-slist-requested (just initiate connection and/or receive push updates) +Push Live-Updates +Log Alternate Source IP +*/ +#define ALTERNATE_SOURCE_IP 8 + +SBError SBServerListConnectAndQuery(SBServerList *slist, const char *fieldList, const char *serverFilter, int options, int maxServers) +{ + SBError err; + int ret; + int requestLen; + unsigned short netLen; + char *requestBuf; + char outgoingRequest[MAX_OUTGOING_REQUEST_SIZE + 1]; + assert(slist->state == sl_disconnected); + + + if (fieldList == NULL) + fieldList = ""; + if (serverFilter == NULL) + serverFilter = ""; + + if (strlen(fieldList) > MAX_FIELD_LIST_LEN) + return sbe_paramerror; + + if (strlen(serverFilter) > MAX_FILTER_LEN) + return sbe_paramerror; + + + err = ServerListConnect(slist); + if (err != sbe_noerror) + return err; + + slist->queryoptions = options; + + //setup our challenge value + SetupListChallenge(slist); + + //skip the length field for now + requestLen = 2; + requestBuf = outgoingRequest + 2; + + BufferAddByte(&requestBuf, SERVER_LIST_REQUEST, &requestLen); + BufferAddByte(&requestBuf, LIST_PROTOCOL_VERSION, &requestLen); + BufferAddByte(&requestBuf, LIST_ENCODING_VERSION, &requestLen); + BufferAddIntLE(&requestBuf, slist->fromgamever, &requestLen); + BufferAddNTS(&requestBuf, slist->queryforgamename, &requestLen); + BufferAddNTS(&requestBuf, slist->queryfromgamename, &requestLen); + BufferAddData(&requestBuf, slist->mychallenge, LIST_CHALLENGE_LEN, &requestLen); + BufferAddNTS(&requestBuf, serverFilter, &requestLen); + BufferAddNTS(&requestBuf, fieldList, &requestLen); + options = (int)htonl((unsigned int)options); + BufferAddInt(&requestBuf, options, &requestLen); + if (slist->queryoptions & ALTERNATE_SOURCE_IP) + { + BufferAddInt(&requestBuf, (int)slist->srcip, &requestLen); + } + if (slist->queryoptions & LIMIT_RESULT_COUNT) + { + BufferAddIntLE(&requestBuf, maxServers, &requestLen); + } + netLen = htons((unsigned short)requestLen); + memcpy(outgoingRequest, &netLen, 2); //set the length... + + //now send! + ret = send(slist->slsocket, outgoingRequest, requestLen, 0); + if (ret <= 0) + { + SBServerListDisconnect(slist); + return sbe_connecterror; + } + slist->state = sl_mainlist; + slist->pstate = pi_cryptheader; + //allocate an incoming buffer + if (slist->inbuffer == NULL) + { + slist->inbuffer = (char *)gsimalloc(INCOMING_BUFFER_SIZE); + if (slist->inbuffer == NULL) + return sbe_allocerror; + slist->inbufferlen = 0; + } + + + return sbe_noerror; +} + +SBError SBServerListGetLANList(SBServerList *slist, unsigned short startport, unsigned short endport, int queryversion) +{ + struct sockaddr_in saddr; + unsigned short i; + unsigned char qr2_echo_request[] = {QR2_MAGIC_1, QR2_MAGIC_2, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}; + int qr2requestlen = sizeof(qr2_echo_request) / sizeof(qr2_echo_request[0]); + if (slist->state != sl_disconnected) + SBServerListDisconnect(slist); + + slist->slsocket = socket ( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); + if (slist->slsocket == INVALID_SOCKET) + return sbe_socketerror; + +// enable broadcasting where needed +#if !defined(INSOCK) && !defined(_NITRO) && !defined(_REVOLUTION) + { + int optval = 1; + if (setsockopt(slist->slsocket, SOL_SOCKET, SO_BROADCAST, (char *)&optval, sizeof(optval)) != 0) + return sbe_socketerror; + } +#endif + + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = gsiGetBroadcastIP(); //broadcast + + // Added for multihomed machines. Allows the user to specify which adapter to use + if (slist->mLanAdapterOverride != NULL) + { + struct sockaddr_in aClassCAddr; + aClassCAddr.sin_family = AF_INET; + aClassCAddr.sin_addr.s_addr = inet_addr(slist->mLanAdapterOverride); + aClassCAddr.sin_port = 0; // bind to any port + if (0 != bind(slist->slsocket, (struct sockaddr*)&aClassCAddr, sizeof(aClassCAddr))) + return sbe_socketerror; + } + + if (endport - startport > 500) //the max we will search + endport = (unsigned short)(startport + 500); + for (i = startport ; i <= endport ; i += 1) + { + saddr.sin_port = htons(i); + if (queryversion == QVERSION_QR2) //send a QR2 echo request + sendto(slist->slsocket, (char*)qr2_echo_request,qr2requestlen,0,(struct sockaddr *)&saddr,sizeof(saddr)); + else //send a GOA echo request + sendto(slist->slsocket, "\\echo\\test",10,0,(struct sockaddr *)&saddr,sizeof(saddr)); + } + slist->state = sl_lanbrowse; + slist->lanstarttime = current_time(); + return sbe_noerror; +} + +static void FreePopularValues(SBServerList *slist) +{ + int i; + for (i = 0 ; i < slist->numpopularvalues ; i++) + SBReleaseStr(slist, slist->popularvalues[i]); + slist->numpopularvalues = 0; +} + +static void FreeKeyList(SBServerList *slist) +{ + int i; + if (slist->keylist == NULL) + return; + for (i = 0 ; i < ArrayLength(slist->keylist) ; i++) + SBReleaseStr(slist, ((KeyInfo *)ArrayNth(slist->keylist,i))->keyName); + + ArrayFree(slist->keylist); + slist->keylist = NULL; + +} + +void SBServerListDisconnect(SBServerList *slist) +{ + if (slist->inbuffer != NULL) + gsifree(slist->inbuffer); + slist->inbuffer = NULL; + slist->inbufferlen = 0; + if (slist->slsocket != INVALID_SOCKET) + closesocket(slist->slsocket); + slist->slsocket = INVALID_SOCKET; + slist->state = sl_disconnected; + + FreeKeyList(slist); + slist->expectedelements = -1; + FreePopularValues(slist); +} + +void SBServerListCleanup(SBServerList *slist) +{ + SBServerListDisconnect(slist); + SBServerListClear(slist); + SBRefStrHashCleanup(slist); + if(slist->servers) + ArrayFree(slist->servers); + slist->servers = NULL; + +#ifdef GSI_UNICODE + if (slist->lasterror_W != NULL) + { + gsifree(slist->lasterror_W); + slist->lasterror_W = NULL; + } +#endif +} + + + +static void InitCryptKey(SBServerList *slist, char *key, int keylen) +{ + //combine our secret key, our challenge, and the server's challenge into a crypt key + int i; + int seckeylen = (int)strlen(slist->queryfromkey); + char *seckey = slist->queryfromkey; + for (i = 0 ; i < keylen ; i++) + { + slist->mychallenge[(i * seckey[i % seckeylen]) % LIST_CHALLENGE_LEN] ^= (char)((slist->mychallenge[i % LIST_CHALLENGE_LEN] ^ key[i]) & 0xFF); + } + GOACryptInit(&(slist->cryptkey), (unsigned char *)(slist->mychallenge), LIST_CHALLENGE_LEN); + +} + + +static int ServerSizeForFlags(int flags) +{ + int size = 5; //all servers are at least 5 .. + if (flags & PRIVATE_IP_FLAG) + size += 4; + if (flags & ICMP_IP_FLAG) + size += 4; + if (flags & NONSTANDARD_PORT_FLAG) + size += 2; + if (flags & NONSTANDARD_PRIVATE_PORT_FLAG) + size += 2; + return size; + +} + + +static int FullRulesPresent(char *buf, int len) +{ + int i; + while (len > 0 && *buf) + { + i = NTSLengthSB(buf, len); + if (i < 0) + return 0; //no full key + buf += i; + len -= i; + i = NTSLengthSB(buf, len); + if (i < 0) + return 0; //no full value + buf += i; + len -= i; + } + if (len == 0) + return 0; //not even enough space for the null term + if (*buf == 0) + return 1; //all there + return 0; +} + +//checks to see if all the keys for the given servers are there +static int AllKeysPresent(SBServerList *slist, char *buf, int len) +{ + int numkeys; + int i; + int strindex; + numkeys = ArrayLength(slist->keylist); + for (i = 0 ; i < numkeys ; i++) + { + switch (((KeyInfo *)ArrayNth(slist->keylist, i))->keyType) + { + case KEYTYPE_BYTE: + buf++; + len--; + break; + case KEYTYPE_SHORT: + buf += 2; + len -= 2; + break; + case KEYTYPE_STRING: + if (len < 1) + return 0; //not enough + strindex = (unsigned char)(buf[0]); + buf++; + len--; + if (strindex == 0xFF) //a NTS string + { + strindex = NTSLengthSB(buf, len); + if (strindex == -1) //not all there.. + return 0; + buf += strindex; + len -= strindex; + } //else it's a popular string - just the index is present + break; + default: + assert(0); + return 0; //error - unknown key type + } + if (len < 0) + return 0; //not enough.. + } + return 1; +} + +#define LAST_SERVER_MARKER "\xFF\xFF\xFF\xFF" +#define SERVER_MARKER_LEN 4 + +//parse only the IP/port from the server buffer +static void ParseServerIPPort(SBServerList *slist, char *buf, int len, goa_uint32 *ip, unsigned short *port) +{ + unsigned char flags; + if (len < 5) + return; //invalid buffer + flags = (unsigned char)buf[0]; + buf++; + len--; + memcpy(ip, buf, 4); + + buf += 4; + len -= 4; + + if (flags & NONSTANDARD_PORT_FLAG) + { + if (len < 2) + return; //invalid buffer + memcpy(port, buf, 2); + } else + *port = slist->defaultport; +} + + + + +//parse a server buffer +static int ParseServer(SBServerList *slist, SBServer server, char *buf, int len, int usepopularlist) +{ + int numkeys; + int i; + short sval; + int strindex; + int holdlen = len; + goa_uint32 ip; + unsigned short port; + unsigned char flags; + + + flags = (unsigned char)buf[0]; + SBServerSetFlags(server, flags); + buf++; + len--; + + //skip the IP, it's already set + buf += 4; + len -= 4; + + //skip the port, if needed + if (flags & NONSTANDARD_PORT_FLAG) + { + buf += 2; + len -= 2; + } + + if (flags & PRIVATE_IP_FLAG) + { + memcpy(&ip, buf, 4); + buf += 4; + len -= 4; + } else + ip = 0; + if (flags & NONSTANDARD_PRIVATE_PORT_FLAG) + { + memcpy(&port, buf, 2); + buf += 2; + len -= 2; + } else + port = slist->defaultport; + SBServerSetPrivateAddr(server, ip, port); + if (flags & ICMP_IP_FLAG) + { + memcpy(&ip, buf, 4); + buf += 4; + len -= 4; + SBServerSetICMPIP(server, ip); + } + + if (flags & HAS_KEYS_FLAG) //parse the keys + { + numkeys = ArrayLength(slist->keylist); + for (i = 0 ; i < numkeys ; i++) + { + KeyInfo *ki = (KeyInfo *)ArrayNth(slist->keylist, i); + switch (ki->keyType) + { + case KEYTYPE_BYTE: + SBServerAddIntKeyValue(server, ki->keyName, (unsigned char)buf[0]); + buf++; + len--; + break; + case KEYTYPE_SHORT: + memcpy(&sval, buf, 2); + SBServerAddIntKeyValue(server, ki->keyName, ntohs((unsigned short)sval)); + buf += 2; + len -= 2; + break; + case KEYTYPE_STRING: + if (usepopularlist) + { + strindex = (unsigned char)(buf[0]); + buf++; + len--; + } else + strindex = 0xFF; + if (strindex == 0xFF) //a NTS string + { + SBServerAddKeyValue(server, ki->keyName, buf); + strindex = (int)strlen(buf) + 1; + buf += strindex; + len -= strindex; + } else //else it's a popular string - just the index is present + { + SBServerAddKeyValue(server, ki->keyName, slist->popularvalues[strindex]); + } + break; + } + + } + SBServerSetState(server, (unsigned char)(SBServerGetState(server) | STATE_BASICKEYS)); + } + if (flags & HAS_FULL_RULES_FLAG) //got the full rules in there + { + while (*buf && len > 0) + { + char *k = buf; + i = (int)strlen(k) + 1; + buf += i; + len -= i; + SBServerAddKeyValue(server, k, buf); + i = (int)strlen(buf) + 1; + buf += i; + len -= i; + } + buf++; + len--; //get the last null out + SBServerSetState(server, (unsigned char)(SBServerGetState(server) | STATE_FULLKEYS)); + } + + // the server was an empty server message, thus it should be cleared of + // basic key and full key states + { + unsigned char state = SBServerGetState(server); + if ((flags & (HAS_FULL_RULES_FLAG|HAS_KEYS_FLAG)) == 0 && ((state & STATE_BASICKEYS) | (state & STATE_FULLKEYS))) + { + state &= (unsigned char)~(STATE_BASICKEYS|STATE_FULLKEYS); + SBServerSetState(server, state); + } + } + return holdlen - len; +} + + +static int IncomingListParseServer(SBServerList *slist, char *buf, int len) +{ + int i; + goa_uint32 ip; + unsigned short port; + SBServer server; + unsigned char flags; + //fields depends on the flags.. + if (len < 1) + return 0; + flags = (unsigned char)(buf[0]); + i = ServerSizeForFlags(flags); + if (len < i) + return 0; + if (flags & HAS_KEYS_FLAG) + { + if (!AllKeysPresent(slist, buf + i, len - i)) + return 0; + } + if (flags & HAS_FULL_RULES_FLAG) + { + if (!FullRulesPresent(buf + i, len - i)) + return 0; + } + //else we have a whole server! + //see if it's the "last" server (0xffffffff) + if (memcmp(buf + 1, LAST_SERVER_MARKER, SERVER_MARKER_LEN) == 0) + return -1; + ParseServerIPPort(slist, buf, len, &ip, &port); + server = SBAllocServer(slist, ip, port); + if (SBIsNullServer(server)) + return -2; + i = ParseServer(slist, server, buf, len, 1); + SBServerListAppendServer(slist, server); + return i; +} + +const char *SBLastListErrorA(SBServerList *slist) +{ + return slist->lasterror; +} +#ifdef GSI_UNICODE +const unsigned short *SBLastListErrorW(SBServerList *slist) +{ + return slist->lasterror_W; +} +#endif +void SBSetLastListErrorPtr(SBServerList *slist, char* theError) +{ +#ifdef GSI_UNICODE + if (slist->lasterror != theError) + { + // set the ascii error + slist->lasterror = theError; + + // free the current unicode error and allocate a new one + if (slist->lasterror_W != NULL) + gsifree(slist->lasterror_W); + slist->lasterror_W = UTF8ToUCS2StringAlloc(slist->lasterror); + } +#else + slist->lasterror = theError; +#endif +} + +#define FIXED_HEADER_LEN 6 + +/* +--challenge header +1 byte - number of random bytes to follow xor 0xEC +random bytes +1 byte - length xor 0xEA +keylen bytes - server challenge +--fixed header +YOUR public IP - 4 bytes +Standard query port - 2 bytes - or 0xFFFF if the master does not know about this gamename yet +[rest only follows if they requested a server slist] +--key slist +Number of Keys - 1 byte +List of Keys - with key type +Key Type - 1 Byte (0 = string, 1 = byte, 2 = short) +KeyName - NTS +--unique value slist +number of unique string values - 1 byte +Table of Unique String Values (255 most popular string keys) +Key NTS +--servers +Server Table (for each server - terminated with IP of 0xFFFFFFFF, flags 0) +1 byte flags +-Connect negotiate support? +-Has private IP +-Unsolicited UDP support? +-Has ICMP IP +-Nonstandard Public Port +-Nonstandard Private Port +-Has Keys +4 bytes public ip +[optional] +4 bytes port +[optional] +4 bytes private IP +[optional] +4 bytes private port +[optional] +4 bytes ICMP IP +[optional - keys] +String values +1-bytes - value ID +or +FF + valuestring + 00 +Byte Values + 1 byte - value + Short values? + 2 bytes - value +*/ + +static SBError ProcessMainListData(SBServerList *slist) +{ + int reqlen; + int keyoffset; + int keylen; + char *inbuf = slist->inbuffer; + int inlen = slist->inbufferlen; + switch (slist->pstate) + { + case pi_cryptheader: + if (inlen < 1) + break; + reqlen = (((unsigned char)(inbuf[0])) ^ 0xEC) + 2; + if (inlen < reqlen) + break; //not there yet + keyoffset = reqlen; + keylen = ((unsigned char)(inbuf[reqlen - 1])) ^ 0xEA; + reqlen += keylen; + if (inlen < reqlen) + break; //not there yet + //otherwise we have the whole crypt header and can init our crypt key + InitCryptKey(slist, inbuf + keyoffset, keylen); + slist->pstate = pi_fixedheader; + + // the first byte of the "random" data is actually + // the game options flag + memcpy(&slist->backendgameflags, &inbuf[1], 2); + slist->backendgameflags = ntohs(slist->backendgameflags); + + inbuf += reqlen; + inlen -= reqlen; + //decrypt any remaining data! + GOADecrypt(&(slist->cryptkey), (unsigned char *)inbuf, inlen); + //and fall through + case pi_fixedheader: + if (inlen < FIXED_HEADER_LEN) + break; + memcpy(&slist->mypublicip, inbuf, 4); + slist->ListCallback(slist, slc_publicipdetermined, SBNullServer, slist->instance); + memcpy(&slist->defaultport, inbuf + 4, 2); + if (slist->defaultport == 0xFFFF) //there was an error- grab the error string if present.. + { + if (NTSLengthSB(inbuf + FIXED_HEADER_LEN, inlen - FIXED_HEADER_LEN) == -1) //not all there + break; + SBSetLastListErrorPtr(slist, inbuf + FIXED_HEADER_LEN); + //make the error callback + slist->ListCallback(slist, slc_queryerror, SBNullServer, slist->instance); + if (slist->inbuffer == NULL) + break; + } + inbuf += FIXED_HEADER_LEN; + inlen -= FIXED_HEADER_LEN; + if ((slist->queryoptions & NO_SERVER_LIST) || slist->defaultport == 0xFFFF) + { + slist->pstate = pi_finished; + slist->state = sl_connected; + break; //no more data expected (if we didn't request any more, or the server doesn't know about this game) + } + slist->pstate = pi_keylist; + slist->expectedelements = -1; + + //and fall through + case pi_keylist: + if (slist->expectedelements == -1) //we haven't read the initial count of keys yet.. + { + if (inlen < 1) + break; + slist->expectedelements = (unsigned char)(inbuf[0]); + slist->keylist = ArrayNew(sizeof(KeyInfo), slist->expectedelements, NULL); + if (slist->keylist == NULL) //error + return sbe_allocerror; + inbuf++; + inlen--; + } + //try to read elements, up to the expected amount... + while (slist->expectedelements > ArrayLength(slist->keylist)) + { + KeyInfo ki; + int keylen; + if (inlen < 2) + break; //can't possibly be a full key (keytype + string) + keylen = NTSLengthSB(inbuf + 1, inlen - 1); + if (keylen == -1) + break; //no full NTS string there + ki.keyType = (unsigned char)(inbuf[0]); + ki.keyName = SBRefStr(slist, inbuf + 1); + ArrayAppend(slist->keylist, &ki); + inbuf += keylen + 1; + inlen -= keylen + 1; + } + if (slist->expectedelements > ArrayLength(slist->keylist)) + break; //didn't read them all... + //else we have read all the keys, fall through.. + slist->pstate = pi_uniquevaluelist; + slist->expectedelements = -1; + case pi_uniquevaluelist: + if (slist->expectedelements == -1) //we haven't read the # of unique values yet.. + { + if (inlen < 1) + break; + slist->expectedelements = (unsigned char)(inbuf[0]); + slist->numpopularvalues = 0; + inbuf++; + inlen--; + } + while (slist->expectedelements > slist->numpopularvalues) + { + int keylen = NTSLengthSB(inbuf, inlen); + if (keylen == -1) + break; //no full NTS string + slist->popularvalues[slist->numpopularvalues++] = SBRefStr(slist, inbuf); + inbuf += keylen; + inlen -= keylen; + } + if (slist->expectedelements > slist->numpopularvalues) + break; //didn't read all the popular values + //else we've got them all - move on to servers! + slist->pstate = pi_servers; + case pi_servers : + if (inlen < 5) //not enough for a full servers + break; + do + { + reqlen = IncomingListParseServer(slist, inbuf, inlen); + if (reqlen == -2) + return sbe_allocerror; + else if (reqlen == -1) //that was the last server! + { + inlen -= 5; + inbuf += 5; + slist->pstate = pi_finished; + slist->state = sl_connected; + slist->ListCallback(slist, slc_initiallistcomplete, SBNullServer, slist->instance); + break; + } + inbuf += reqlen; + inlen -= reqlen; + if (slist->inbuffer == NULL) + reqlen = 0; //break out - they disconnected + } while (reqlen != 0); + break; + default: + break; + } + assert(inlen >= 0); + if (slist->inbuffer == NULL) + return sbe_noerror; //don't keep processing - they disconnected + if (inlen != 0) //need to shift it over.. + { + memmove(slist->inbuffer, inbuf, (size_t)inlen); + } + slist->inbufferlen = inlen; + //we could clear the key list here if we wanted + + return sbe_noerror; +} + +/* + Number of Keys - Byte + List of Keys - with key type + Key Type - 1 Byte (0 = string, 1 = byte, 2 = short) + KeyName - NTS +*/ +static SBError ProcessPushKeyList(SBServerList *slist, char *buf, int len) +{ + int i; + int numkeys; + //first byte is the # of keys + numkeys = (unsigned char)buf[0]; + buf++; + len--; + //set up our key list... + if (slist->keylist != NULL) //free the existing key list + { + FreeKeyList(slist); + } + slist->keylist = ArrayNew(sizeof(KeyInfo), numkeys, NULL); + if (slist->keylist == NULL) + return sbe_allocerror; + for (i = 0 ; i < numkeys ; i++) + { + int keylen; + KeyInfo ki; + if (len < 2) + return sbe_dataerror; //can't possibly be a full key (keytype + string) + keylen = NTSLengthSB(buf + 1, len - 1); + if (keylen == -1) + return sbe_dataerror; //no full NTS string there + ki.keyType = (unsigned char)(buf[0]); + ki.keyName = SBRefStr(slist, buf + 1); + ArrayAppend(slist->keylist, &ki); + buf += keylen + 1; + len -= keylen + 1; + } + return sbe_noerror; +} + + +/* +Is Final Packet - Byte +Result Count - 1 bytes (up to 255 per packet) +Results + Player Name - NTS + 6 bytes - server addr + 4 bytes - last seen time (UTC Unix time) + gamename - NTS (empty if they are searching for only a single game) +*/ + +static SBError ProcessPlayerSearch(SBServerList *slist, char *buf, int len) +{ + unsigned char isFinal; + unsigned char resultCount; + unsigned int ip; + unsigned short port; + char *nick; + time_t lastSeen; + int i; + + if (len < 2) + return sbe_dataerror; //not a full packet + isFinal = (unsigned char)buf[0]; + resultCount = (unsigned char)buf[1]; + buf += 2; + len -= 2; + for (i = 0 ; i < resultCount ; i++) + { + int slen; + nick = buf; + slen = NTSLengthSB(buf, len); + if (slen == -1) + return sbe_dataerror; + buf += slen; + len -= slen; + if (len < 11) + return sbe_dataerror; //not a full entry + memcpy(&ip, buf, 4); + memcpy(&port, buf + 4, 2); + memcpy(&lastSeen, buf + 6, 4); + lastSeen = (time_t)ntohl((unsigned long)lastSeen); + buf += 10; + len -= 10; + slen = NTSLengthSB(buf, len); + if (slen == -1) + return sbe_dataerror; //not a valid gamename + slist->PlayerSearchCallback(slist, nick, ip, port, lastSeen, buf, slist->instance); + //now skip the gamename + buf += slen; + len -= slen; + } + if (isFinal) //send a final callback + slist->PlayerSearchCallback(slist, NULL, 0, 0, 0, NULL, slist->instance); + + + return sbe_noerror; + +} +/* + Server IP - 4 bytes + Server Port - 2 bytes + Map change time - 4 bytes + Number of maps - 1 byte + Maps + Mapname - NTS +*/ + + +static SBError ProcessMaploop(SBServerList *slist, char *buf, int len) +{ + time_t changeTime; + unsigned int ip; + unsigned short port; + SBServer server; + unsigned char mapCount; + int i; + char *mapList[MAX_MAPLOOP_LENGTH]; + if (len < 11) + return sbe_dataerror; //not a valid packet + memcpy(&ip, buf, 4); + memcpy(&port, buf + 4, 2); + i = SBServerListFindServerByIP(slist, ip, port); + if (i == -1) + return sbe_noerror; //not present any more + server = SBServerListNth(slist, i); + memcpy(&changeTime, buf + 6, 4); + changeTime = (time_t)ntohl((unsigned long)changeTime); + mapCount = (unsigned char)buf[10]; + buf += 11; + len -= 11; + for (i = 0 ; i < mapCount && i < MAX_MAPLOOP_LENGTH; i++) + { + int maplen; + if (len < 1) + break; + maplen = NTSLengthSB(buf, len); + if (maplen == -1) + return sbe_dataerror; //not a full NTS string + mapList[i] = buf; + buf += maplen; + len -= maplen; + } + if (slist->MaploopCallback == NULL) + return sbe_noerror; + slist->MaploopCallback(slist, server, changeTime, i, mapList, slist->instance); + return sbe_noerror; +} + +static SBError ProcessDeleteServer(SBServerList *slist, char *buf, int len) +{ + goa_uint32 ip; + unsigned short port; + int i; + + if (len < 6) + return sbe_dataerror; + //need to grab out the ip & port to find it in our list + memcpy(&ip, buf, 4); + memcpy(&port, buf + 4, 2); + //see if we can find the server in the list - if not, then add it + i = SBServerListFindServerByIP(slist, ip, port); + if (i == -1) + return sbe_noerror; + SBServerListRemoveAt(slist, i); + return sbe_noerror; +} + +/* +same as server list server format, except without using popular value lists +*/ +static SBError ProcessPushServer(SBServerList *slist, char *buf, int len) +{ + + SBServer server; + goa_uint32 ip; + unsigned short port; + int foundexisting; + int i; + + if (len < 5) + return sbe_dataerror; + //need to grab out the ip & port to find it in our list + ParseServerIPPort(slist, buf, len, &ip, &port); + + foundexisting = SBServerListFindServerByIP(slist, ip, port); + if (foundexisting == -1) //need to add it + { + server = SBAllocServer(slist, ip, port); + if (SBIsNullServer(server)) + return sbe_allocerror; + } else + server = SBServerListNth(slist, foundexisting); + i = ParseServer(slist, server, buf, len, 0); + if (i < 0) + return sbe_dataerror; //whole thing wasn't there + if (foundexisting == -1) + SBServerListAppendServer(slist, server); + slist->ListCallback(slist, slc_serverupdated, server, slist->instance); + return sbe_noerror; + +} + + + +static SBError ProcessAdHocData(SBServerList *slist) +{ + unsigned short msglen; + int i; + SBError ret = sbe_noerror; + while (slist->inbufferlen >= 3) //while there is at least 1 message in there.. (2 bytes for the size + 1 for the type) + { + //first two bytes are the length - see if we have the whole message.. + memcpy(&msglen, slist->inbuffer, 2); + msglen = ntohs(msglen); + if (msglen > INCOMING_BUFFER_SIZE) //ack! won't fit.. + { + ret = sbe_dataerror; + break; + } + if (slist->inbufferlen < msglen) + return sbe_noerror; //whole message not yet here + //else process the message + switch (slist->inbuffer[2]) + { + case PUSH_KEYS_MESSAGE: + ret = ProcessPushKeyList(slist, slist->inbuffer + 3, msglen - 3); + break; + case PUSH_SERVER_MESSAGE: + ret = ProcessPushServer(slist, slist->inbuffer + 3, msglen - 3); + break; + case KEEPALIVE_MESSAGE: //just need to send it back.. + i = (int)send(slist->slsocket, slist->inbuffer, msglen, 0); + if (i <= 0) + return sbe_connecterror; + break; + case DELETE_SERVER_MESSAGE: + ret = ProcessDeleteServer(slist, slist->inbuffer + 3, msglen - 3); + break; + case MAPLOOP_MESSAGE: + ret = ProcessMaploop(slist, slist->inbuffer + 3, msglen - 3); + break; + case PLAYERSEARCH_MESSAGE: + ret = ProcessPlayerSearch(slist, slist->inbuffer + 3, msglen - 3); + break; + } + + slist->inbufferlen -= msglen; + assert(slist->inbufferlen >= 0); + if (slist->inbufferlen != 0 && slist->inbuffer != NULL) //if anything is left, shift it over.. + { + memmove(slist->inbuffer, slist->inbuffer + msglen, (size_t)slist->inbufferlen); + } + if (ret != sbe_noerror) + break; + } + if (ret != sbe_noerror) + ErrorDisconnect(slist); + return ret; +} + + +static SBError ProcessIncomingData(SBServerList *slist) +{ + SBError err; + int len; + int oldlen; + + if(!CanReceiveOnSocket(slist->slsocket)) + return sbe_noerror; + + //append to data + oldlen = slist->inbufferlen; + len = recv(slist->slsocket, slist->inbuffer + slist->inbufferlen, INCOMING_BUFFER_SIZE - slist->inbufferlen, 0); + if (gsiSocketIsError(len)|| len == 0) + { + ErrorDisconnect(slist); + return sbe_connecterror; + } + slist->inbufferlen += len; + err = sbe_noerror; + if (slist->state == sl_connected || slist->pstate > pi_cryptheader) //decrypt any new data.. + { + GOADecrypt(&(slist->cryptkey), (unsigned char *)(slist->inbuffer + oldlen), slist->inbufferlen - oldlen); + } + + if (slist->state == sl_mainlist) + err = ProcessMainListData(slist); + if (err != sbe_noerror) + return err; + //always need to check this after mainlistdata, in case some extra data has some in (e.g. key list for push) + if (slist->state == sl_connected && slist->inbufferlen > 0) + return ProcessAdHocData(slist); + return sbe_noerror; + +} + +SBError SBGetServerRulesFromMaster(SBServerList *slist, goa_uint32 ip, unsigned short port) +{ + char requestBuffer[16]; + unsigned short tmp; + unsigned short msgLen = 9; + + if (slist->state == sl_disconnected) //try to connect + SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0); + + if (slist->state == sl_disconnected) + return sbe_connecterror; + + //the length + tmp = htons(msgLen); + memcpy(requestBuffer, &tmp, 2); + //the message type + requestBuffer[2] = SERVER_INFO_REQUEST; + memcpy(requestBuffer + 3, &ip, sizeof(ip)); + memcpy(requestBuffer + 7, &port, sizeof(port)); + return SendWithRetry(slist, requestBuffer, msgLen); +} + +SBError SBSendMessageToServer(SBServerList *slist, goa_uint32 ip, unsigned short port, const char *data, int len) +{ + char requestBuffer[16]; + unsigned short tmp; + unsigned short msgLen = 9; + SBError ret; + int i; + + if (slist->state == sl_disconnected) //try to connect + SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0); + + if (slist->state == sl_disconnected) + return sbe_connecterror; + + //the length + tmp = htons((unsigned short)(msgLen + len)); + memcpy(requestBuffer, &tmp, 2); + //the message type + requestBuffer[2] = SEND_MESSAGE_REQUEST; + memcpy(requestBuffer + 3, &ip, sizeof(ip)); + memcpy(requestBuffer + 7, &port, sizeof(port)); + ret = SendWithRetry(slist, requestBuffer, msgLen); + if (ret != sbe_noerror) + return ret; + i = send(slist->slsocket, data, len, 0); //send the actual push data + if (i < 0) + return sbe_connecterror; + return sbe_noerror; + +} + +SBError SBSendPlayerSearchRequest(SBServerList *slist, char *searchName, int searchOptions, int maxResults, SBPlayerSearchCallbackFn callback) +{ + char requestBuffer[256]; + unsigned short msgLen = 11; + int namelen; + SBError ret; + + if (slist->state == sl_disconnected) //try to connect + SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0); + if (slist->state == sl_disconnected) + return sbe_connecterror; + + slist->PlayerSearchCallback = callback; + + + requestBuffer[2] = PLAYERSEARCH_REQUEST; + searchOptions = (int)htonl((unsigned int)searchOptions); + memcpy(requestBuffer + 3, &searchOptions, 4); + maxResults = (int)htonl((unsigned int)maxResults); + memcpy(requestBuffer + 7, &maxResults, 4); + namelen = (int)strlen(searchName); + msgLen = (unsigned short)(msgLen + (namelen + 1)); + if (msgLen > sizeof(requestBuffer)) + return sbe_paramerror; //search string too long + memcpy(requestBuffer + 11, searchName, (size_t)namelen + 1); + msgLen = htons((unsigned short)(msgLen)); + memcpy(requestBuffer, &msgLen, 2); + ret = SendWithRetry(slist, requestBuffer, ntohs(msgLen)); + if (ret != sbe_noerror) + return ret; + return sbe_noerror; +} + + +SBError SBSendMaploopRequest(SBServerList *slist, SBServer server, SBMaploopCallbackFn callback) +{ + char requestBuffer[16]; + unsigned short tmp; + unsigned short msgLen = 9; + unsigned short port; + unsigned int ip; + SBError ret; + + + if (slist->state == sl_disconnected) //try to connect + SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0); + if (slist->state == sl_disconnected) + return sbe_connecterror; + + slist->MaploopCallback = callback; + //the length + tmp = htons((unsigned short)(msgLen)); + memcpy(requestBuffer, &tmp, 2); + //the message type + requestBuffer[2] = MAPLOOP_REQUEST; + ip = SBServerGetPublicInetAddress(server); + port = SBServerGetPublicQueryPortNBO(server); + memcpy(requestBuffer + 3, &ip, sizeof(ip)); + memcpy(requestBuffer + 7, &port, sizeof(port)); + ret = SendWithRetry(slist, requestBuffer, msgLen); + if (ret != sbe_noerror) + return ret; + return sbe_noerror; + +} + +SBError SBSendNatNegotiateCookieToServer(SBServerList *slist, goa_uint32 ip, unsigned short port, int cookie) +{ + unsigned char negotiateBuffer[NATNEG_MAGIC_LEN + 4]; + negotiateBuffer[0] = NN_MAGIC_0; + negotiateBuffer[1] = NN_MAGIC_1; + negotiateBuffer[2] = NN_MAGIC_2; + negotiateBuffer[3] = NN_MAGIC_3; + negotiateBuffer[4] = NN_MAGIC_4; + negotiateBuffer[5] = NN_MAGIC_5; + cookie = (int)htonl((unsigned int)cookie); + memcpy(negotiateBuffer + NATNEG_MAGIC_LEN, &cookie, 4); + return SBSendMessageToServer(slist, ip, port, (char*)negotiateBuffer, NATNEG_MAGIC_LEN + 4); +} + +static SBError ProcessLanData(SBServerList *slist) +{ + char indata[1500]; // make sure we have enough room for a large UDP packet + struct sockaddr_in saddr; + int saddrlen = sizeof(saddr); + int error; + int foundexisting; + SBServer server; + + while (CanReceiveOnSocket(slist->slsocket)) //we break if the select fails + { +#if defined(_PS3) + error = (int)recvfrom(slist->slsocket, indata, sizeof(indata) - 1, 0, (struct sockaddr *)&saddr, (socklen_t*)&saddrlen ); +#else + error = (int)recvfrom(slist->slsocket, indata, sizeof(indata) - 1, 0, (struct sockaddr *)&saddr, &saddrlen ); +#endif + + if (gsiSocketIsError(error)) + continue; + //if we got data, then add it to the list... + foundexisting = SBServerListFindServerByIP(slist, saddr.sin_addr.s_addr, saddr.sin_port); + if (foundexisting != -1) //already exists + continue; + server = SBAllocServer(slist, saddr.sin_addr.s_addr, saddr.sin_port); + if (SBIsNullServer(server)) + return sbe_allocerror; + SBServerSetFlags(server, UNSOLICITED_UDP_FLAG|NONSTANDARD_PORT_FLAG); + SBServerListAppendServer(slist, server); + } + if (current_time() - slist->lanstarttime > SL_LAN_SEARCH_TIME) //done waiting for replies + { + closesocket(slist->slsocket); + slist->slsocket = INVALID_SOCKET; + slist->state = sl_disconnected; + slist->ListCallback(slist, slc_initiallistcomplete, SBNullServer, slist->instance); + } + return sbe_noerror; +} + +SBError SBListThink(SBServerList *slist) +{ + SBFreeDeadList(slist); //free any pending deleted servers + switch (slist->state) + { + case sl_disconnected: + break; + case sl_connected: + case sl_mainlist: + return ProcessIncomingData(slist); + //break; + case sl_lanbrowse: + return ProcessLanData(slist); + //break; + } + + return sbe_noerror; + //see if any data is available... + +} + + + + diff --git a/code/gamespy/serverbrowsing/sbmfcsample/StdAfx.cpp b/code/gamespy/serverbrowsing/sbmfcsample/StdAfx.cpp new file mode 100644 index 00000000..749a2aaa --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// sbmfcsample.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/code/gamespy/serverbrowsing/sbmfcsample/StdAfx.h b/code/gamespy/serverbrowsing/sbmfcsample/StdAfx.h new file mode 100644 index 00000000..3f7783ea --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/StdAfx.h @@ -0,0 +1,27 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__BFD40F2B_3C12_4712_8A50_65B3BCFA0A5F__INCLUDED_) +#define AFX_STDAFX_H__BFD40F2B_3C12_4712_8A50_65B3BCFA0A5F__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__BFD40F2B_3C12_4712_8A50_65B3BCFA0A5F__INCLUDED_) diff --git a/code/gamespy/serverbrowsing/sbmfcsample/res/sbmfcsample.ico b/code/gamespy/serverbrowsing/sbmfcsample/res/sbmfcsample.ico new file mode 100644 index 00000000..7eef0bcb Binary files /dev/null and b/code/gamespy/serverbrowsing/sbmfcsample/res/sbmfcsample.ico differ diff --git a/code/gamespy/serverbrowsing/sbmfcsample/res/sbmfcsample.rc2 b/code/gamespy/serverbrowsing/sbmfcsample/res/sbmfcsample.rc2 new file mode 100644 index 00000000..8289015e --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/res/sbmfcsample.rc2 @@ -0,0 +1,13 @@ +// +// SBMFCSAMPLE.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/code/gamespy/serverbrowsing/sbmfcsample/resource.h b/code/gamespy/serverbrowsing/sbmfcsample/resource.h new file mode 100644 index 00000000..10c3d57c --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/resource.h @@ -0,0 +1,31 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by sbmfcsample.rc +// +#define IDD_SBMFCSAMPLE_DIALOG 102 +#define IDR_MAINFRAME 128 +#define IDC_SERVERLIST 1001 +#define IDC_REFRESH 1002 +#define IDC_PLAYERLIST 1003 +#define IDC_GAMENAME 1004 +#define IDC_FILTER 1005 +#define IDC_INTERNET 1006 +#define IDC_LAN 1007 +#define IDC_STARTP 1008 +#define IDC_ENDP 1009 +#define IDC_GOA 1010 +#define IDC_QR2 1011 +#define IDC_PROGRESS 1012 +#define IDC_SERVERS 1013 +#define IDC_PLAYERS 1014 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 130 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1016 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.cpp b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.cpp new file mode 100644 index 00000000..4277c7f8 --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.cpp @@ -0,0 +1,63 @@ +// sbmfcsample.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "sbmfcsample.h" +#include "sbmfcsampleDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CSbmfcsampleApp + +BEGIN_MESSAGE_MAP(CSbmfcsampleApp, CWinApp) + //{{AFX_MSG_MAP(CSbmfcsampleApp) + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CSbmfcsampleApp construction + +CSbmfcsampleApp::CSbmfcsampleApp() +{ +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CSbmfcsampleApp object + +CSbmfcsampleApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CSbmfcsampleApp initialization + +BOOL CSbmfcsampleApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization +/* +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif +*/ + CSbmfcsampleDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + } + else if (nResponse == IDCANCEL) + { + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.h b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.h new file mode 100644 index 00000000..60e34cbe --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.h @@ -0,0 +1,47 @@ +// sbmfcsample.h : main header file for the SBMFCSAMPLE application +// + +#if !defined(AFX_SBMFCSAMPLE_H__F2EE1BD5_6089_4F00_9E83_2D0A7C9AA592__INCLUDED_) +#define AFX_SBMFCSAMPLE_H__F2EE1BD5_6089_4F00_9E83_2D0A7C9AA592__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CSbmfcsampleApp: +// See sbmfcsample.cpp for the implementation of this class +// + +class CSbmfcsampleApp : public CWinApp +{ +public: + CSbmfcsampleApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CSbmfcsampleApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CSbmfcsampleApp) + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_SBMFCSAMPLE_H__F2EE1BD5_6089_4F00_9E83_2D0A7C9AA592__INCLUDED_) diff --git a/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.rc b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.rc new file mode 100644 index 00000000..f05e98b1 --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsample.rc @@ -0,0 +1,197 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\sbmfcsample.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\sbmfcsample.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_SBMFCSAMPLE_DIALOG DIALOGEX 0, 0, 320, 251 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "sbmfcsample" +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "Refresh",IDC_REFRESH,7,127,50,14 + PUSHBUTTON "Close",IDOK,60,127,50,14 + EDITTEXT IDC_GAMENAME,49,145,56,12,ES_AUTOHSCROLL + EDITTEXT IDC_FILTER,49,160,56,12,ES_AUTOHSCROLL + CONTROL "Internet",IDC_INTERNET,"Button",BS_AUTORADIOBUTTON | + WS_GROUP,7,176,40,10 + CONTROL "Lan",IDC_LAN,"Button",BS_AUTORADIOBUTTON,60,176,28,10 + CONTROL "GOA",IDC_GOA,"Button",BS_AUTORADIOBUTTON | WS_GROUP,7, + 188,31,10 + CONTROL "QR2",IDC_QR2,"Button",BS_AUTORADIOBUTTON,60,188,31,10 + CONTROL "List2",IDC_SERVERLIST,"SysListView32",LVS_REPORT | + WS_BORDER | WS_GROUP | WS_TABSTOP,7,7,306,117 + CONTROL "List3",IDC_PLAYERLIST,"SysListView32",LVS_REPORT | + WS_BORDER | WS_TABSTOP,112,146,201,98 + LTEXT "Gamename:",IDC_STATIC,7,147,39,8 + LTEXT "Filter:",IDC_STATIC,7,162,18,8 + CONTROL "Progress2",IDC_PROGRESS,"msctls_progress32",PBS_SMOOTH | + WS_BORDER,112,126,144,10 + LTEXT "Servers:",IDC_STATIC,260,126,27,8 + LTEXT "222222",IDC_SERVERS,288,126,25,8 + EDITTEXT IDC_STARTP,49,214,56,12,ES_AUTOHSCROLL | ES_NUMBER + EDITTEXT IDC_ENDP,49,229,56,12,ES_AUTOHSCROLL | ES_NUMBER + CTEXT "For LAN Only",IDC_STATIC,16,201,72,11 + LTEXT "Start Port",IDC_STATIC,7,215,35,11 + LTEXT "End Port",IDC_STATIC,8,230,28,11 +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "sbmfcsample MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "sbmfcsample\0" + VALUE "LegalCopyright", "Copyright (C) 2002\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "sbmfcsample.EXE\0" + VALUE "ProductName", "sbmfcsample Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_SBMFCSAMPLE_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 313 + TOPMARGIN, 7 + BOTTOMMARGIN, 244 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\sbmfcsample.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsampleDlg.cpp b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsampleDlg.cpp new file mode 100644 index 00000000..8d033aeb --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsampleDlg.cpp @@ -0,0 +1,497 @@ +// sbmfcsampleDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "sbmfcsample.h" +#include "sbmfcsampleDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +//SB - timer ID and frequency +#define TIMER_ID 100 +#define TIMER_FREQUENCY 10 + +//SB - server list columns +#define COL_SERVERNAME 0 +#define COL_PING 1 +#define COL_PLAYERS 2 +#define COL_MAPNAME 3 +#define COL_GAMETYPE 4 + +//SB - player list columns +#define COL_PNAME 0 +#define COL_PPING 1 +#define COL_PSCORE 2 + +// 11-02-2004 : Saad Nader +// replaced with a GUI-based way of doing it. +//SB - starting and ending port for LAN game searches +//#define START_PORT 7778 +//#define END_PORT (START_PORT + 100) + +//SB - maximum number of concurrent updates +#define MAX_UPDATES 30 + +///////////////////////////////////////////////////////////////////////////// +// CSbmfcsampleDlg dialog + +CSbmfcsampleDlg::CSbmfcsampleDlg(CWnd* pParent /*=NULL*/) + : CDialog(CSbmfcsampleDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CSbmfcsampleDlg) + m_filter = _T(""); + m_gamename = _T(""); + m_startPort = 0; + m_endPort = 0; + //}}AFX_DATA_INIT + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); +} + +void CSbmfcsampleDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CSbmfcsampleDlg) + DDX_Control(pDX, IDC_SERVERS, m_servers); + DDX_Control(pDX, IDC_PROGRESS, m_progress); + DDX_Control(pDX, IDC_SERVERLIST, m_serverList); + DDX_Control(pDX, IDC_PLAYERLIST, m_playerList); + DDX_Text(pDX, IDC_FILTER, m_filter); + DDX_Text(pDX, IDC_GAMENAME, m_gamename); + DDX_Text(pDX, IDC_STARTP, m_startPort); + DDX_Text(pDX, IDC_ENDP, m_endPort); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CSbmfcsampleDlg, CDialog) + //{{AFX_MSG_MAP(CSbmfcsampleDlg) + ON_BN_CLICKED(IDC_REFRESH, OnRefresh) + ON_NOTIFY(NM_CLICK, IDC_SERVERLIST, OnClickServerlist) + ON_NOTIFY(NM_DBLCLK, IDC_SERVERLIST, OnDblclkServerlist) + ON_WM_TIMER() + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_NOTIFY(LVN_COLUMNCLICK, IDC_SERVERLIST, OnColumnclickServerlist) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CSbmfcsampleDlg message handlers + +BOOL CSbmfcsampleDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + SetIcon(m_hIcon, TRUE); + SetIcon(m_hIcon, FALSE); + + //SB - setup list columns + m_serverList.InsertColumn(COL_SERVERNAME, "Server Name", LVCFMT_LEFT, 150, -1); + m_serverList.InsertColumn(COL_PING, "Ping", LVCFMT_LEFT, 50, 0); + m_serverList.InsertColumn(COL_PLAYERS, "Players", LVCFMT_LEFT, 75, 1); + m_serverList.InsertColumn(COL_MAPNAME, "Map", LVCFMT_LEFT, 75, 2); + m_serverList.InsertColumn(COL_GAMETYPE, "GameType", LVCFMT_LEFT, 100, 3); + m_playerList.InsertColumn(COL_PNAME, "Player Name", LVCFMT_LEFT, 150, -1); + m_playerList.InsertColumn(COL_PPING, "Ping", LVCFMT_LEFT, 50, 0); + m_playerList.InsertColumn(COL_PSCORE, "Score", LVCFMT_LEFT, 50, 1); + ListView_SetExtendedListViewStyle(m_serverList.m_hWnd, LVS_EX_FULLROWSELECT); + ListView_SetExtendedListViewStyle(m_playerList.m_hWnd, LVS_EX_FULLROWSELECT); + + //SB - default to Internet + CheckRadioButton(IDC_INTERNET, IDC_LAN, IDC_INTERNET); + + //SB - default to QR2 + CheckRadioButton(IDC_GOA, IDC_QR2, IDC_QR2); + + //SB - no server browser yet + m_serverBrowser = NULL; + + //SB - no timer yet + m_timerID = 0; + + //SB - no servers yet + m_servers.SetWindowText(""); + + //SB - check that the game's backend is available + GSIACResult result; + GSIStartAvailableCheck("gmtest"); + while((result = GSIAvailableCheckThink()) == GSIACWaiting) + msleep(5); + if(result != GSIACAvailable) + { + MessageBox("The backend is not available\n"); + return TRUE; + } + + return TRUE; +} + +BOOL CSbmfcsampleDlg::DestroyWindow() +{ + // free the browser + if(m_serverBrowser) + { + ServerBrowserFree(m_serverBrowser); + m_serverBrowser = NULL; + } + + return CDialog::DestroyWindow(); +} + +void CSbmfcsampleDlg::OnTimer(UINT nIDEvent) +{ + // think if our timer was called + if(nIDEvent == m_timerID) + { + ServerBrowserThink(m_serverBrowser); + } + + CDialog::OnTimer(nIDEvent); +} + +void CSbmfcsampleDlg::OnRefresh() +{ + UpdateData(); + + // create the server list if we need to + if(!CreateServerList()) + return; + + // if we're doing an update, cancel it + SBState state = ServerBrowserState(m_serverBrowser); + if((state != sb_connected) && (state != sb_disconnected)) + { + ServerBrowserHalt(m_serverBrowser); + return; + } + + // clear the server browser and list + ServerBrowserClear(m_serverBrowser); + m_serverList.DeleteAllItems(); + + // clear the progress bar + m_progress.SetPos(0); + + // clear the server count + m_serverCount = 0; + m_servers.SetWindowText("0"); + + // set a timer + if(!m_timerID) + m_timerID = SetTimer(TIMER_ID, TIMER_FREQUENCY, NULL); + + // fields we're interested in + unsigned char fields[] = { HOSTNAME_KEY, NUMPLAYERS_KEY, MAXPLAYERS_KEY, MAPNAME_KEY, GAMETYPE_KEY }; + int numFields = sizeof(fields) / sizeof(fields[0]); + + // check for internet/lan + bool internet = (IsDlgButtonChecked(IDC_INTERNET) == BST_CHECKED); + + // do an update + SBError error; + if(internet) + error = ServerBrowserUpdate(m_serverBrowser, SBTrue, SBFalse, fields, numFields, (char *)(const char *)m_filter); + else + error = ServerBrowserLANUpdate(m_serverBrowser, SBTrue, (unsigned short)m_startPort, (unsigned short)m_endPort); +} + +void CSbmfcsampleDlg::OnClickServerlist(NMHDR* pNMHDR, LRESULT* pResult) +{ + // clear the player box + m_playerList.DeleteAllItems(); + + // find the selected server + POSITION pos = m_serverList.GetFirstSelectedItemPosition(); + if(pos == NULL) + return; + int index = m_serverList.GetNextSelectedItem(pos); + + // get the server + LVITEM item; + item.mask = LVIF_PARAM; + item.iItem = index; + item.iSubItem = 0; + m_serverList.GetItem(&item); + SBServer server = (SBServer)item.lParam; + + if (!SBServerHasFullKeys(server)) //we need to query for the full rules + { + // turn on the hour glass + HCURSOR cursor = SetCursor(LoadCursor(NULL, IDC_WAIT)); + + // do a server update (this is blocking!) + ServerBrowserAuxUpdateServer(m_serverBrowser, server, SBFalse, SBTrue); + + // turn off the hour glass + SetCursor(cursor); + } + + // get the player count + int count = SBServerGetIntValue(server, "numplayers", 0); + + // add the players to the list + for(int i = 0 ; i < count ; i++) + { + m_playerList.InsertItem(0, SBServerGetPlayerStringValue(server, i, "player", "(NO NAME)")); + m_playerList.SetItem(0, COL_PPING, LVIF_TEXT, SBServerGetPlayerStringValue(server, i, "ping", "0"), -1, 0, 0, 0); + m_playerList.SetItem(0, COL_PSCORE, LVIF_TEXT, SBServerGetPlayerStringValue(server, i, "score", "0"), -1, 0, 0, 0); + } + + // MFC + *pResult = 0; + + GSI_UNUSED(pNMHDR); +} + +void CSbmfcsampleDlg::OnDblclkServerlist(NMHDR* pNMHDR, LRESULT* pResult) +{ + // launch the game here + //MessageBox("If this were a real server browser, you would be launched now!","Go!"); + + // find the selected server + POSITION pos = m_serverList.GetFirstSelectedItemPosition(); + if(pos == NULL) + return; + int index = m_serverList.GetNextSelectedItem(pos); + + // get the server + LVITEM item; + item.mask = LVIF_PARAM; + item.iItem = index; + item.iSubItem = 0; + m_serverList.GetItem(&item); + SBServer server = (SBServer)item.lParam; + SBError error = ServerBrowserConnectToServer(m_serverBrowser, server, SBConnectCallback); + if(error != sbe_noerror) + MessageBox("Error starting connect to server"); + + // MFC + *pResult = 0; + + GSI_UNUSED(pNMHDR); +} + +void CSbmfcsampleDlg::OnColumnclickServerlist(NMHDR* pNMHDR, LRESULT* pResult) +{ + NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; + + // track ascending/descending + static bool ascending = false; + ascending = !ascending; + + //figure out which column they clicked + switch(pNMListView->iSubItem) + { + case COL_SERVERNAME: + ServerBrowserSort(m_serverBrowser, (SBBool)ascending, "hostname", sbcm_stricase); + break; + case COL_PING: + ServerBrowserSort(m_serverBrowser, (SBBool)ascending, "ping", sbcm_int); + break; + case COL_PLAYERS: + ServerBrowserSort(m_serverBrowser, (SBBool)ascending, "numplayers", sbcm_int); + break; + case COL_MAPNAME: + ServerBrowserSort(m_serverBrowser, (SBBool)ascending, "mapname", sbcm_stricase); + break; + case COL_GAMETYPE: + ServerBrowserSort(m_serverBrowser, (SBBool)ascending, "gametype", sbcm_stricase); + break; + } + + // we don't want the server list to redraw every time we insert a server! + m_serverList.SetRedraw(false); + + // clear the server list + m_serverList.DeleteAllItems(); + + // clear the server count + m_serverCount = 0; + m_servers.SetWindowText("0"); + + // go through the list of servers + for(int i = 0; i < ServerBrowserCount(m_serverBrowser) ; i++) + { + // if we got basic info for it, put it back in the list + SBServer server = ServerBrowserGetServer(m_serverBrowser, i); + if(SBServerHasBasicKeys(server)) + AddServer(server, FALSE); + } + + // let the server list redraw itself now that we're done updating + m_serverList.SetRedraw(true); + + *pResult = 0; +} + +void CSbmfcsampleDlg::SBCallback(ServerBrowser serverBrowser, SBCallbackReason reason, SBServer server, void *instance) +{ + CString str; + CSbmfcsampleDlg * dlg = (CSbmfcsampleDlg *)instance; + + CString address; + if(server) + address.Format("%s:%d", SBServerGetPublicAddress(server), SBServerGetPublicQueryPort(server)); + + switch(reason) + { + case sbc_serveradded: + dlg->AddServer(server, TRUE); + break; + case sbc_serverupdated: + dlg->AddServer(server, TRUE); + break; + case sbc_serverupdatefailed: + break; + case sbc_serverdeleted: + dlg->RemoveServer(server); + break; + case sbc_updatecomplete: + dlg->m_progress.SetPos(100); + break; + case sbc_queryerror: + str.Format("Query Error: %s\n", ServerBrowserListQueryError(dlg->m_serverBrowser)); + dlg->MessageBox(str); + break; + } + + GSI_UNUSED(serverBrowser); +} + +void CSbmfcsampleDlg::SBConnectCallback(ServerBrowser serverBrowser, SBConnectToServerState state, SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *instance) +{ + CSbmfcsampleDlg * dlg = (CSbmfcsampleDlg *)instance; + + switch(state) + { + case sbcs_succeeded: + dlg->MessageBox("Connected to server"); + closesocket(gamesocket); + break; + case sbcs_failed: + dlg->MessageBox("Failed to connect to server"); + break; + } + + GSI_UNUSED(serverBrowser); + GSI_UNUSED(remoteaddr); +} + +BOOL CSbmfcsampleDlg::CreateServerList() +{ + // only create the object once + if(!m_serverBrowser) + { + // check for an empty gamename + if(m_gamename.IsEmpty()) + { + MessageBox("No game specified"); + GetDlgItem(IDC_GAMENAME)->SetFocus(); + return FALSE; + } + SBBool lanBrowsing = (SBBool)(IsDlgButtonChecked(IDC_LAN) == BST_CHECKED); + if (m_startPort == 0 && lanBrowsing == SBTrue) + { + AfxMessageBox("Invalid start port!"); + GetDlgItem(IDC_STARTP)->SetFocus(); + return FALSE; + } + if (m_endPort == 0 && lanBrowsing == SBTrue) + { + AfxMessageBox("Invalid end port!"); + GetDlgItem(IDC_ENDP)->SetFocus(); + return FALSE; + } + + // create it + m_serverBrowser = ServerBrowserNew(m_gamename, "gmtest", "HA6zkS", 0, MAX_UPDATES, (IsDlgButtonChecked(IDC_GOA) == BST_CHECKED)?QVERSION_GOA:QVERSION_QR2, lanBrowsing, SBCallback, this); + if(!m_serverBrowser) + { + MessageBox("Unable to create the server browser object"); + return FALSE; + } + + // don't let them change the gamename + GetDlgItem(IDC_GAMENAME)->EnableWindow(FALSE); + } + + return TRUE; +} + +void CSbmfcsampleDlg::AddServer(SBServer server, BOOL checkForReplace) +{ + // set the progress + if(ServerBrowserCount(m_serverBrowser) > 0) + m_progress.SetPos((ServerBrowserCount(m_serverBrowser) - ServerBrowserPendingQueryCount(m_serverBrowser)) * 100 / ServerBrowserCount(m_serverBrowser)); + + // check for the server in the list + int index = FindServer(server); + bool replace = (index != -1); + + // if we didn't find a server to replace, append + if(!replace) + index = m_serverList.GetItemCount(); + + // set or insert the hostname + const char * hostname = SBServerGetStringValue(server, "hostname","(NO NAME)"); + if(replace) + { + m_serverList.SetItem(index, COL_SERVERNAME, LVIF_TEXT, hostname, -1, 0, 0, NULL); + } + else + { + m_serverList.InsertItem(index, hostname); + m_serverList.SetItem(index, COL_SERVERNAME, LVIF_PARAM, NULL, -1, 0, 0, (LPARAM)server); + } + + // set the rest of the columns + int numplayers = SBServerGetIntValue(server, "numplayers", 0); + CString ping, players; + if (SBServerHasValidPing(server)) + ping.Format("%d%s", SBServerGetPing(server), SBServerDirectConnect(server) ? "" : "i"); + else + ping = "Unknown"; + players.Format("%d/%d", numplayers, SBServerGetIntValue(server, "maxplayers", 0)); + m_serverList.SetItem(index, COL_PING, LVIF_TEXT, ping, -1, 0, 0, 0); + m_serverList.SetItem(index, COL_PLAYERS, LVIF_TEXT, players, -1, 0, 0, 0); + m_serverList.SetItem(index, COL_MAPNAME, LVIF_TEXT, SBServerGetStringValue(server, "mapname", "(NO MAP)"), -1, 0, 0, 0); + m_serverList.SetItem(index, COL_GAMETYPE, LVIF_TEXT, SBServerGetStringValue(server, "gametype", ""), -1, 0, 0, 0); + + // update server count + if(!replace) + { + CString str; + str.Format("%d", ++m_serverCount); + m_servers.SetWindowText(str); + } + + GSI_UNUSED(checkForReplace); +} + +void CSbmfcsampleDlg::RemoveServer(SBServer server) +{ + // find the server + int index = FindServer(server); + ASSERT(index != -1); + if(index == -1) + return; + + // remove it + m_serverList.DeleteItem(index); + + // update server count + CString str; + str.Format("%d", --m_serverCount); + m_servers.SetWindowText(str); +} + +int CSbmfcsampleDlg::FindServer(SBServer server) +{ + LVFINDINFO info; + info.flags = LVFI_PARAM; + info.lParam = (LPARAM)server; + return m_serverList.FindItem(&info); +} diff --git a/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsampleDlg.h b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsampleDlg.h new file mode 100644 index 00000000..10498e0c --- /dev/null +++ b/code/gamespy/serverbrowsing/sbmfcsample/sbmfcsampleDlg.h @@ -0,0 +1,74 @@ +// sbmfcsampleDlg.h : header file +// + +#if !defined(AFX_SBMFCSAMPLEDLG_H__8B726D03_AF99_4938_A63C_18525912E55D__INCLUDED_) +#define AFX_SBMFCSAMPLEDLG_H__8B726D03_AF99_4938_A63C_18525912E55D__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +///////////////////////////////////////////////////////////////////////////// +// CSbmfcsampleDlg dialog + +//SB - header +#include "..\sb_serverbrowsing.h" +#include "..\..\qr2\qr2regkeys.h" +#include "..\..\common\gsAvailable.h" + +class CSbmfcsampleDlg : public CDialog +{ +// Construction +public: + CSbmfcsampleDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CSbmfcsampleDlg) + enum { IDD = IDD_SBMFCSAMPLE_DIALOG }; + CStatic m_servers; + CProgressCtrl m_progress; + CListCtrl m_serverList; + CListCtrl m_playerList; + CString m_filter; + CString m_gamename; + int m_startPort; + int m_endPort; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CSbmfcsampleDlg) + public: + virtual BOOL DestroyWindow(); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + static void SBCallback(ServerBrowser serverBrowser, SBCallbackReason reason, SBServer server, void * instance); + static void SBConnectCallback(ServerBrowser serverBrowser, SBConnectToServerState state, SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *instance); + void AddServer(SBServer server, BOOL checkForReplace); + void RemoveServer(SBServer server); + int FindServer(SBServer server); + BOOL CreateServerList(); + HICON m_hIcon; + ServerBrowser m_serverBrowser; + UINT m_timerID; + int m_serverCount; + + // Generated message map functions + //{{AFX_MSG(CSbmfcsampleDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnRefresh(); + afx_msg void OnClickServerlist(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnDblclkServerlist(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnTimer(UINT nIDEvent); + afx_msg void OnColumnclickServerlist(NMHDR* pNMHDR, LRESULT* pResult); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_SBMFCSAMPLEDLG_H__8B726D03_AF99_4938_A63C_18525912E55D__INCLUDED_) diff --git a/code/gamespy/webservices/AuthService.c b/code/gamespy/webservices/AuthService.c new file mode 100644 index 00000000..0ad0ac51 --- /dev/null +++ b/code/gamespy/webservices/AuthService.c @@ -0,0 +1,1093 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "AuthService.h" +#include "../common/gsXML.h" +#include "../common/gsAvailable.h" +#include "../md5.h" + +#pragma warning(disable: 4267) + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define WS_AUTHSERVICE_LOGINPROFILE "LoginProfile" +#define WS_AUTHSERVICE_LOGINUNIQUE "LoginUniqueNick" +#define WS_AUTHSERVICE_LOGINREMOTEAUTH "LoginRemoteAuth" +#define WS_AUTHSERVICE_LOGINPS3CERT "LoginPs3Cert" +#define WS_AUTHSERVICE_PROTOVERSION 1 + +#define WS_AUTHSERVICE_NAMESPACE "ns1" +#define WS_AUTHSERVICE_LOGINPROFILE_SOAP "SOAPAction: \"http://gamespy.net/AuthService/LoginProfile\"" +#define WS_AUTHSERVICE_LOGINUNIQUE_SOAP "SOAPAction: \"http://gamespy.net/AuthService/LoginUniqueNick\"" +#define WS_AUTHSERVICE_LOGINREMOTEAUTH_SOAP "SOAPAction: \"http://gamespy.net/AuthService/LoginRemoteAuth\"" +#define WS_AUTHSERVICE_LOGINPS3CERT_SOAP "SOAPAction: \"http://gamespy.net/AuthService/LoginPs3Cert\"" + +#define WS_AUTHSERVICE_NAMESPACE_COUNT 1 +const char * WS_AUTHSERVICE_NAMESPACES[WS_AUTHSERVICE_NAMESPACE_COUNT] = +{ + WS_AUTHSERVICE_NAMESPACE "=\"http://gamespy.net/AuthService/\"" +}; + +const char WS_AUTHSERVICE_SIGNATURE_KEY[] = + "BF05D63E93751AD4A59A4A7389CF0BE8" + "A22CCDEEA1E7F12C062D6E194472EFDA" + "5184CCECEB4FBADF5EB1D7ABFE911814" + "53972AA971F624AF9BA8F0F82E2869FB" + "7D44BDE8D56EE50977898F3FEE758696" + "22C4981F07506248BD3D092E8EA05C12" + "B2FA37881176084C8F8B8756C4722CDC" + "57D2AD28ACD3AD85934FB48D6B2D2027"; + +/* "D589F4433FAB2855F85A4EB40E6311F0" + "284C7882A72380938FD0C55CC1D65F7C" + "6EE79EDEF06C1AE5EDE139BDBBAB4219" + "B7D2A3F0AC3D3B23F59F580E0E89B9EC" + "787F2DD5A49788C633D5D3CE79438934" + "861EA68AE545D5BBCAAAD917CE9F5C7C" + "7D1452D9214F989861A7511097C35E60" + "A7273DECEA71CB5F8251653B26ACE781";*/ + + + +const char WS_AUTHSERVICE_SIGNATURE_EXP[] = + "010001"; + +// This is declared as an extern so it can be overriden when testing +#define WS_LOGIN_SERVICE_URL_FORMAT "https://%s.auth.pubsvs." GSI_DOMAIN_NAME "/AuthService/AuthService.asmx" +char wsAuthServiceURL[WS_LOGIN_MAX_URL_LEN] = ""; + +typedef struct WSIRequestData +{ + union + { + WSLoginCallback mLoginCallback; + WSLoginPs3CertCallback mLoginPs3CertCallback; + } mUserCallback; + //void * mUserCallback; + void * mUserData; +} WSIRequestData; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void wsiLoginEncryptPassword(const gsi_char * password, gsi_u8 ciphertext[GS_CRYPT_RSA_BYTE_SIZE]); +static gsi_bool wsiServiceAvailable(); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Checks to make sure the availability check has been performed prior +// to using any AuthService Login, and sets the service URL if it has +static gsi_bool wsiServiceAvailable() +{ + if (__GSIACResult == GSIACAvailable) + { + if (wsAuthServiceURL[0] == '\0') + snprintf(wsAuthServiceURL, WS_LOGIN_MAX_URL_LEN, WS_LOGIN_SERVICE_URL_FORMAT, __GSIACGamename); + return gsi_true; + } + else + return gsi_false; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void wsiLoginProfileCallback(GHTTPResult theResult, + GSXmlStreamWriter theRequestXml, + GSXmlStreamReader theResponseXml, + void * theRequestData) +{ + GHTTPResult translatedResult = GHTTPSuccess; + WSIRequestData * requestData = (WSIRequestData*)theRequestData; + + WSLoginResponse response; + GSLoginCertificate * cert = &response.mCertificate; // for convenience + + // initialize local variables + cert->mIsValid = gsi_false; + memset(&response, 0, sizeof(response)); + + if (theResult == GHTTPSuccess) + { + // try to parse the soap + if (gsi_is_false(gsXmlMoveToStart(theResponseXml)) || + gsi_is_false(gsXmlMoveToNext(theResponseXml, "LoginProfileResult"))) + { + response.mLoginResult = WSLogin_ParseError; + } + else + { + // prepare response structure + if (gsi_is_false(gsXmlReadChildAsInt(theResponseXml, "responseCode", (int*)&response.mResponseCode))) + { + // could not parse login response code + response.mLoginResult = WSLogin_ParseError; + } + else if (response.mResponseCode != WSLogin_Success) + { + // server reported an error into reponseCode + response.mLoginResult = WSLogin_ServerError; + } + else if (gsi_is_false(gsXmlMoveToChild(theResponseXml, "certificate")) || + gsi_is_false(wsLoginCertReadXML(cert, theResponseXml)) || + gsi_is_false(gsXmlMoveToParent(theResponseXml)) || + gsi_is_false(gsXmlReadChildAsLargeInt(theResponseXml, "peerkeyprivate", + &response.mPrivateData.mPeerPrivateKey.exponent)) + ) + { + response.mLoginResult = WSLogin_ParseError; + } + else + { + MD5_CTX md5; + + // peer privatekey modulus is same as peer public key modulus + memcpy(&response.mPrivateData.mPeerPrivateKey.modulus, &cert->mPeerPublicKey.modulus, sizeof(cert->mPeerPublicKey.modulus)); + + // hash the private key + MD5Init(&md5); + //gsLargeIntAddToMD5(&response.mPrivateData.mPeerPrivateKey.modulus, &md5); + gsLargeIntAddToMD5(&response.mPrivateData.mPeerPrivateKey.exponent, &md5); + MD5Final((unsigned char*)response.mPrivateData.mKeyHash, &md5); + + // verify certificate + cert->mIsValid = wsLoginCertIsValid(cert); + if (gsi_is_false(cert->mIsValid)) + { + response.mLoginResult = WSLogin_InvalidCertificate; + } + } + } + } + else + { + response.mLoginResult = WSLogin_HttpError; + } + + // trigger the user callback + if (requestData->mUserCallback.mLoginCallback != NULL) + { + WSLoginCallback userCallback = (WSLoginCallback)(requestData->mUserCallback.mLoginCallback); + (userCallback)(translatedResult, &response, requestData->mUserData); + } + gsifree(requestData); + GSI_UNUSED(theRequestXml); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u32 wsLoginProfile(int partnerCode, + int namespaceId, + const gsi_char * profileNick, + const gsi_char * email, + const gsi_char * password, + const gsi_char * cdkey, + WSLoginCallback callback, + void * userData) +{ + GSXmlStreamWriter writer; + WSIRequestData * requestData = NULL; + gsi_u8 encryptedPassword[GS_CRYPT_RSA_BYTE_SIZE]; + + if (!wsiServiceAvailable()) + return WSLogin_NoAvailabilityCheck; + + GS_ASSERT(partnerCode >= 0); + GS_ASSERT(profileNick != NULL); + GS_ASSERT(email != NULL); + GS_ASSERT(password != NULL); + + if (_tcslen(profileNick) >= WS_LOGIN_NICK_LEN) + return WSLogin_InvalidParameters; + if (_tcslen(email) >= WS_LOGIN_EMAIL_LEN) + return WSLogin_InvalidParameters; + if (_tcslen(password) >= WS_LOGIN_PASSWORD_LEN) + return WSLogin_InvalidParameters; + if (cdkey != NULL && (_tcslen(cdkey) > WS_LOGIN_CDKEY_LEN)) + return WSLogin_InvalidParameters; + + // make a copy of the request callback and user param + requestData = (WSIRequestData*)gsimalloc(sizeof(WSIRequestData)); + if (requestData == NULL) + return WSLogin_OutOfMemory; + requestData->mUserCallback.mLoginCallback = callback; + requestData->mUserData = userData; + + // encrypt the password (includes safety padding and hash) + wsiLoginEncryptPassword(password, encryptedPassword); + + writer = gsXmlCreateStreamWriter(WS_AUTHSERVICE_NAMESPACES, WS_AUTHSERVICE_NAMESPACE_COUNT); + if (writer != NULL) + { + GSSoapTask * aTask = NULL; + + if (gsi_is_false(gsXmlWriteOpenTag (writer, WS_AUTHSERVICE_NAMESPACE, WS_AUTHSERVICE_LOGINPROFILE)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "version", WS_AUTHSERVICE_PROTOVERSION)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "partnercode", (gsi_u32)partnerCode)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "namespaceid", (gsi_u32)namespaceId)) || + gsi_is_false(gsXmlWriteAsciiStringElement(writer, WS_AUTHSERVICE_NAMESPACE, "email", email)) || + gsi_is_false(gsXmlWriteAsciiStringElement(writer, WS_AUTHSERVICE_NAMESPACE, "profilenick", profileNick)) || + gsi_is_false(gsXmlWriteOpenTag (writer, WS_AUTHSERVICE_NAMESPACE, "password")) || + gsi_is_false(gsXmlWriteHexBinaryElement(writer, WS_AUTHSERVICE_NAMESPACE, "Value", encryptedPassword, GS_CRYPT_RSA_BYTE_SIZE)) || + gsi_is_false(gsXmlWriteCloseTag (writer, WS_AUTHSERVICE_NAMESPACE, "password")) || + gsi_is_false(gsXmlWriteCloseTag (writer, WS_AUTHSERVICE_NAMESPACE, WS_AUTHSERVICE_LOGINPROFILE)) || + gsi_is_false(gsXmlCloseWriter (writer)) + ) + { + gsXmlFreeWriter(writer); + return WSLogin_OutOfMemory; + } + + aTask = gsiExecuteSoap(wsAuthServiceURL, WS_AUTHSERVICE_LOGINPROFILE_SOAP, + writer, wsiLoginProfileCallback, (void*)requestData); + if (aTask == NULL) + { + gsXmlFreeWriter(writer); + gsifree(requestData); + return WSLogin_OutOfMemory; + } + } + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void wsLoginUniqueCallback(GHTTPResult theResult, + GSXmlStreamWriter theRequestXml, + GSXmlStreamReader theResponseXml, + void * theRequestData) +{ + GHTTPResult translatedResult = theResult; + WSIRequestData * requestData = (WSIRequestData*)theRequestData; + + WSLoginResponse response; + GSLoginCertificate * cert = &response.mCertificate; // for convenience + cert->mIsValid = gsi_false; + + memset(&response, 0, sizeof(response)); + + if (theResult == GHTTPSuccess) + { + // try to parse the soap + if (gsi_is_false(gsXmlMoveToStart(theResponseXml)) || + gsi_is_false(gsXmlMoveToNext(theResponseXml, "LoginUniqueNickResult"))) + { + response.mLoginResult = WSLogin_ParseError; + } + else + { + // prepare response structure + if (gsi_is_false(gsXmlReadChildAsInt(theResponseXml, "responseCode", (int*)&response.mResponseCode))) + { + // could not parse login response code + response.mLoginResult = WSLogin_ParseError; + } + else if (response.mResponseCode != WSLogin_Success) + { + // server reported an error into reponseCode + response.mLoginResult = WSLogin_ServerError; + } + else if (gsi_is_false(gsXmlMoveToChild(theResponseXml, "certificate")) || + gsi_is_false(wsLoginCertReadXML(cert, theResponseXml)) || + gsi_is_false(gsXmlMoveToParent(theResponseXml)) || + gsi_is_false(gsXmlReadChildAsLargeInt(theResponseXml, "peerkeyprivate", + &response.mPrivateData.mPeerPrivateKey.exponent)) + ) + { + response.mLoginResult = WSLogin_ParseError; + } + else + { + MD5_CTX md5; + + // peer privatekey modulus is same as peer public key modulus + memcpy(&response.mPrivateData.mPeerPrivateKey.modulus, &cert->mPeerPublicKey.modulus, sizeof(cert->mPeerPublicKey.modulus)); + + // hash the private key + // -- we use the has like a password for simple authentication + MD5Init(&md5); + //gsLargeIntAddToMD5(&response.mPrivateData.mPeerPrivateKey.modulus, &md5); + gsLargeIntAddToMD5(&response.mPrivateData.mPeerPrivateKey.exponent, &md5); + MD5Final((unsigned char*)response.mPrivateData.mKeyHash, &md5); + + // verify certificate + cert->mIsValid = wsLoginCertIsValid(cert); + if (gsi_is_false(cert->mIsValid)) + { + response.mLoginResult = WSLogin_InvalidCertificate; + } + } + } + } + else if (theResult == GHTTPRequestCancelled) + { + response.mLoginResult = WSLogin_Cancelled; + } + else + { + response.mLoginResult = WSLogin_HttpError; + } + + // trigger the user callback + if (requestData->mUserCallback.mLoginCallback != NULL) + { + WSLoginCallback userCallback = (WSLoginCallback)(requestData->mUserCallback.mLoginCallback); + (userCallback)(translatedResult, &response, requestData->mUserData); + } + gsifree(requestData); + GSI_UNUSED(theRequestXml); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//gsi_u32 wsLoginUnique(WSLoginUniqueRequest * request, WSLoginCallback callback) +gsi_u32 wsLoginUnique(int partnerCode, + int namespaceId, + const gsi_char * uniqueNick, + const gsi_char * password, + const gsi_char * cdkey, + WSLoginCallback userCallback, + void * userData) +{ + GSXmlStreamWriter writer; + WSIRequestData * requestData = NULL; + gsi_u8 encryptedPassword[GS_CRYPT_RSA_BYTE_SIZE]; + + if (!wsiServiceAvailable()) + return WSLogin_NoAvailabilityCheck; + + GS_ASSERT(partnerCode >= 0); + GS_ASSERT(uniqueNick != NULL); + GS_ASSERT(password != NULL); + + if (_tcslen(uniqueNick) >= WS_LOGIN_UNIQUENICK_LEN) + return WSLogin_InvalidParameters; + if (_tcslen(password) >= WS_LOGIN_PASSWORD_LEN) + return WSLogin_InvalidParameters; + if (cdkey != NULL && (_tcslen(cdkey) >= WS_LOGIN_CDKEY_LEN)) + return WSLogin_InvalidParameters; + + // allocate the request values + requestData = (WSIRequestData*)gsimalloc(sizeof(WSIRequestData)); + if (requestData == NULL) + return WSLogin_OutOfMemory; + requestData->mUserCallback.mLoginCallback = userCallback; + requestData->mUserData = userData; + + // encrypt the password (includes safety padding and hash) + wsiLoginEncryptPassword(password, encryptedPassword); + + // create the xml request + writer = gsXmlCreateStreamWriter(WS_AUTHSERVICE_NAMESPACES, WS_AUTHSERVICE_NAMESPACE_COUNT); + if (writer != NULL) + { + GSSoapTask * aTask = NULL; + + if (gsi_is_false(gsXmlWriteOpenTag (writer, WS_AUTHSERVICE_NAMESPACE, WS_AUTHSERVICE_LOGINUNIQUE)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "version", WS_AUTHSERVICE_PROTOVERSION)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "partnercode", (gsi_u32)partnerCode)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "namespaceid", (gsi_u32)namespaceId)) || + gsi_is_false(gsXmlWriteAsciiStringElement(writer, WS_AUTHSERVICE_NAMESPACE, "uniquenick", uniqueNick)) || + gsi_is_false(gsXmlWriteOpenTag (writer, WS_AUTHSERVICE_NAMESPACE, "password")) || + gsi_is_false(gsXmlWriteHexBinaryElement(writer, WS_AUTHSERVICE_NAMESPACE, "Value", encryptedPassword, GS_CRYPT_RSA_BYTE_SIZE)) || + gsi_is_false(gsXmlWriteCloseTag (writer, WS_AUTHSERVICE_NAMESPACE, "password")) || + gsi_is_false(gsXmlWriteCloseTag (writer, WS_AUTHSERVICE_NAMESPACE, WS_AUTHSERVICE_LOGINUNIQUE)) || + gsi_is_false(gsXmlCloseWriter (writer)) + ) + { + gsXmlFreeWriter(writer); + return WSLogin_OutOfMemory; + } + + aTask = gsiExecuteSoap(wsAuthServiceURL, WS_AUTHSERVICE_LOGINUNIQUE_SOAP, + writer, wsLoginUniqueCallback, (void*)requestData); + if (aTask == NULL) + { + gsXmlFreeWriter(writer); + gsifree(requestData); + return WSLogin_OutOfMemory; + } + } + return 0; +} + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void wsLoginRemoteAuthCallback(GHTTPResult theResult, + GSXmlStreamWriter theRequestXml, + GSXmlStreamReader theResponseXml, + void * theRequestData) +{ + GHTTPResult translatedResult = theResult; + WSIRequestData * requestData = (WSIRequestData*)theRequestData; + + WSLoginResponse response; + GSLoginCertificate * cert = &response.mCertificate; // for convenience + cert->mIsValid = gsi_false; + + memset(&response, 0, sizeof(response)); + + if (theResult == GHTTPSuccess) + { + // try to parse the soap + if (gsi_is_false(gsXmlMoveToStart(theResponseXml)) || + gsi_is_false(gsXmlMoveToNext(theResponseXml, "LoginRemoteAuthResult"))) + { + response.mLoginResult = WSLogin_ParseError; + } + else + { + // prepare response structure + if (gsi_is_false(gsXmlReadChildAsInt(theResponseXml, "responseCode", (int*)&response.mResponseCode))) + { + // could not parse login response code + response.mLoginResult = WSLogin_ParseError; + } + else if (response.mResponseCode != WSLogin_Success) + { + // server reported an error into reponseCode + response.mLoginResult = WSLogin_ServerError; + } + else if (gsi_is_false(gsXmlMoveToChild(theResponseXml, "certificate")) || + gsi_is_false(wsLoginCertReadXML(cert, theResponseXml)) || + gsi_is_false(gsXmlMoveToParent(theResponseXml)) || + gsi_is_false(gsXmlReadChildAsLargeInt(theResponseXml, "peerkeyprivate", + &response.mPrivateData.mPeerPrivateKey.exponent)) + ) + { + response.mLoginResult = WSLogin_ParseError; + } + else + { + MD5_CTX md5; + + // peer privatekey modulus is same as peer public key modulus + memcpy(&response.mPrivateData.mPeerPrivateKey.modulus, &cert->mPeerPublicKey.modulus, sizeof(cert->mPeerPublicKey.modulus)); + + // hash the private key + // -- we use the has like a password for simple authentication + MD5Init(&md5); + //gsLargeIntAddToMD5(&response.mPrivateData.mPeerPrivateKey.modulus, &md5); + gsLargeIntAddToMD5(&response.mPrivateData.mPeerPrivateKey.exponent, &md5); + MD5Final((unsigned char*)response.mPrivateData.mKeyHash, &md5); + + // verify certificate + cert->mIsValid = wsLoginCertIsValid(cert); + if (gsi_is_false(cert->mIsValid)) + { + response.mLoginResult = WSLogin_InvalidCertificate; + } + } + } + } + else if (theResult == GHTTPRequestCancelled) + { + response.mLoginResult = WSLogin_Cancelled; + } + else + { + response.mLoginResult = WSLogin_HttpError; + } + + // trigger the user callback + if (requestData->mUserCallback.mLoginCallback != NULL) + { + WSLoginCallback userCallback = (WSLoginCallback)(requestData->mUserCallback.mLoginCallback); + (userCallback)(translatedResult, &response, requestData->mUserData); + } + gsifree(requestData); + GSI_UNUSED(theRequestXml); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u32 wsLoginRemoteAuth(int partnerCode, + int namespaceId, + const gsi_char authtoken[WS_LOGIN_AUTHTOKEN_LEN], + const gsi_char partnerChallenge[WS_LOGIN_PARTNERCHALLENGE_LEN], + WSLoginCallback userCallback, + void * userData) +{ + GSXmlStreamWriter writer; + WSIRequestData * requestData = NULL; + //gsi_u8 encryptedChallenge[GS_CRYPT_RSA_BYTE_SIZE]; + + if (!wsiServiceAvailable()) + return WSLogin_NoAvailabilityCheck; + + GS_ASSERT(partnerCode >= 0); + + // allocate the request values + requestData = (WSIRequestData*)gsimalloc(sizeof(WSIRequestData)); + if (requestData == NULL) + return WSLogin_OutOfMemory; + requestData->mUserCallback.mLoginCallback = userCallback; + requestData->mUserData = userData; + + // encrypt the password (includes safety padding and hash) + //wsiLoginEncryptPassword(partnerChallenge, encryptedChallenge); + + // create the xml request + writer = gsXmlCreateStreamWriter(WS_AUTHSERVICE_NAMESPACES, WS_AUTHSERVICE_NAMESPACE_COUNT); + if (writer != NULL) + { + GSSoapTask * aTask = NULL; + + if (gsi_is_false(gsXmlWriteOpenTag (writer, WS_AUTHSERVICE_NAMESPACE, WS_AUTHSERVICE_LOGINREMOTEAUTH)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "version", WS_AUTHSERVICE_PROTOVERSION)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "partnercode", (gsi_u32)partnerCode)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "namespaceid", (gsi_u32)namespaceId)) || + gsi_is_false(gsXmlWriteTStringElement(writer, WS_AUTHSERVICE_NAMESPACE, "authtoken", authtoken)) || + gsi_is_false(gsXmlWriteTStringElement(writer, WS_AUTHSERVICE_NAMESPACE, "challenge", partnerChallenge)) || + //gsi_is_false(gsXmlWriteOpenTag (writer, WS_AUTHSERVICE_NAMESPACE, "challenge")) || + //gsi_is_false(gsXmlWriteHexBinaryElement(writer, WS_AUTHSERVICE_NAMESPACE, "Value", encryptedChallenge, GS_CRYPT_RSA_BYTE_SIZE)) || + //gsi_is_false(gsXmlWriteCloseTag (writer, WS_AUTHSERVICE_NAMESPACE, "challenge")) || + gsi_is_false(gsXmlWriteCloseTag (writer, WS_AUTHSERVICE_NAMESPACE, WS_AUTHSERVICE_LOGINREMOTEAUTH)) || + gsi_is_false(gsXmlCloseWriter (writer)) + ) + { + gsXmlFreeWriter(writer); + return WSLogin_OutOfMemory; + } + + aTask = gsiExecuteSoap(wsAuthServiceURL, WS_AUTHSERVICE_LOGINREMOTEAUTH_SOAP, + writer, wsLoginRemoteAuthCallback, (void*)requestData); + if (aTask == NULL) + { + gsXmlFreeWriter(writer); + gsifree(requestData); + return WSLogin_OutOfMemory; + } + } + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void wsLoginPs3CertCallback(GHTTPResult theResult, + GSXmlStreamWriter theRequestXml, + GSXmlStreamReader theResponseXml, + void * theRequestData) +{ + + GHTTPResult translatedResult = theResult; + WSIRequestData * requestData = (WSIRequestData*)theRequestData; + + WSLoginPs3CertResponse response; + memset(&response, 0, sizeof(response)); + + if (theResult == GHTTPSuccess) + { + // try to parse the soap + if (gsi_is_false(gsXmlMoveToStart(theResponseXml)) || + gsi_is_false(gsXmlMoveToNext(theResponseXml, "LoginPs3CertResult"))) + { + response.mLoginResult = WSLogin_ParseError; + } + else + { + // prepare response structure + if (gsi_is_false(gsXmlReadChildAsInt(theResponseXml, "responseCode", (int*)&response.mResponseCode))) + { + // could not parse login response code + response.mLoginResult = WSLogin_ParseError; + } + else if (response.mResponseCode != WSLogin_Success) + { + // server reported an error into reponseCode + response.mLoginResult = WSLogin_ServerError; + } + else + { + const char * tokenStr = NULL; + const char * challengeStr = NULL; + int tokenLen = 0; + int challengeLen = 0; + + // Check length of token+challenge, then read into memory + if ( //gsi_is_false(gsXmlReadChildAsBase64Binary(theResponseXml, "authToken", NULL, &tokenLen)) || + //gsi_is_false(gsXmlReadChildAsBase64Binary(theResponseXml, "partnerChallenge", NULL, &challengeLen)) || + //(tokenLen > WS_LOGIN_AUTHTOKEN_LEN || challengeLen > WS_LOGIN_PARTNERCHALLENGE_LEN) || + gsi_is_false(gsXmlReadChildAsString(theResponseXml, "authToken", &tokenStr, &tokenLen)) || + gsi_is_false(gsXmlReadChildAsString(theResponseXml, "partnerChallenge", &challengeStr, &challengeLen)) || + (tokenLen >= WS_LOGIN_AUTHTOKEN_LEN || challengeLen >= WS_LOGIN_PARTNERCHALLENGE_LEN) ) + { + response.mLoginResult = WSLogin_ParseError; + } + else + { + memcpy(response.mRemoteAuthToken, tokenStr, (gsi_u32)tokenLen); + memcpy(response.mPartnerChallenge, challengeStr, (gsi_u32)challengeLen); + response.mRemoteAuthToken[tokenLen] = '\0'; + response.mPartnerChallenge[challengeLen] = '\0'; + } + } + } + } + else if (theResult == GHTTPRequestCancelled) + { + response.mLoginResult = WSLogin_Cancelled; + } + else + { + response.mLoginResult = WSLogin_HttpError; + } + + // trigger the user callback + if (requestData->mUserCallback.mLoginPs3CertCallback != NULL) + { + WSLoginPs3CertCallback userCallback = (WSLoginPs3CertCallback)(requestData->mUserCallback.mLoginPs3CertCallback); + (userCallback)(translatedResult, &response, requestData->mUserData); + } + gsifree(requestData); + GSI_UNUSED(theRequestXml); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//gsi_u32 wsLoginUnique(WSLoginUniqueRequest * request, WSLoginCallback callback) +gsi_u32 wsLoginPs3Cert(int gameId, + int partnerCode, + int namespaceId, + const gsi_u8 * ps3cert, + int certLen, + WSLoginPs3CertCallback userCallback, + void * userData) +{ + GSXmlStreamWriter writer; + WSIRequestData * requestData = NULL; + + if (!wsiServiceAvailable()) + return WSLogin_NoAvailabilityCheck; + + GS_ASSERT(partnerCode >= 0); + GS_ASSERT(ps3cert != NULL); + + + // Version check, todo: use something better than length + //if (certLen != 248) + // return WSLogin_InvalidParameters; + + // allocate the request values + requestData = (WSIRequestData*)gsimalloc(sizeof(WSIRequestData)); + if (requestData == NULL) + return WSLogin_OutOfMemory; + + requestData->mUserCallback.mLoginPs3CertCallback = userCallback; + requestData->mUserData = userData; + + // create the xml request + writer = gsXmlCreateStreamWriter(WS_AUTHSERVICE_NAMESPACES, WS_AUTHSERVICE_NAMESPACE_COUNT); + if (writer != NULL) + { + GSSoapTask * aTask = NULL; + + if (gsi_is_false(gsXmlWriteOpenTag (writer, WS_AUTHSERVICE_NAMESPACE, WS_AUTHSERVICE_LOGINPS3CERT)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "version", WS_AUTHSERVICE_PROTOVERSION)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "gameid", (gsi_u32)gameId)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "partnercode", (gsi_u32)partnerCode)) || + gsi_is_false(gsXmlWriteIntElement (writer, WS_AUTHSERVICE_NAMESPACE, "namespaceid", (gsi_u32)namespaceId)) || + gsi_is_false(gsXmlWriteOpenTag (writer, WS_AUTHSERVICE_NAMESPACE, "npticket")) || + gsi_is_false(gsXmlWriteBase64BinaryElement(writer, WS_AUTHSERVICE_NAMESPACE, "Value", ps3cert, certLen)) || + gsi_is_false(gsXmlWriteCloseTag (writer, WS_AUTHSERVICE_NAMESPACE, "npticket")) || + gsi_is_false(gsXmlWriteCloseTag (writer, WS_AUTHSERVICE_NAMESPACE, WS_AUTHSERVICE_LOGINPS3CERT)) || + gsi_is_false(gsXmlCloseWriter (writer)) + ) + { + gsXmlFreeWriter(writer); + return WSLogin_OutOfMemory; + } + + aTask = gsiExecuteSoap(wsAuthServiceURL, WS_AUTHSERVICE_LOGINPS3CERT_SOAP, + writer, wsLoginPs3CertCallback, (void*)requestData); + if (aTask == NULL) + { + gsXmlFreeWriter(writer); + gsifree(requestData); + return WSLogin_OutOfMemory; + } + } + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void wsiLoginEncryptPassword(const gsi_char * password, gsi_u8 ciphertext[GS_CRYPT_RSA_BYTE_SIZE]) +{ + gsCryptRSAKey sigkeypub; + + +#ifdef GSI_UNICODE + char password_A[WS_LOGIN_PASSWORD_LEN]; + // strip password into ascii to encrypt + UCS2ToAsciiString(password, password_A); +#endif + + gsLargeIntSetFromHexString(&sigkeypub.modulus, WS_AUTHSERVICE_SIGNATURE_KEY); + gsLargeIntSetFromHexString(&sigkeypub.exponent, WS_AUTHSERVICE_SIGNATURE_EXP); + +#ifdef GSI_UNICODE + gsCryptRSAEncryptBuffer(&sigkeypub, (const gsi_u8*)password_A, _tcslen(password), ciphertext); +#else + gsCryptRSAEncryptBuffer(&sigkeypub, (const gsi_u8*)password, strlen(password), ciphertext); +#endif +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_u32 wsiMakeLittleEndian32(gsi_u32 num) +{ +#if defined(GSI_BIG_ENDIAN) + num = gsiByteOrderSwap32(num); +#endif + return num; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool wsLoginCertIsValid(const GSLoginCertificate * cert) +{ + // Verify the signature + gsCryptRSAKey sigkeypub; + MD5_CTX md5; + gsi_u8 hash[16]; + gsi_i32 cryptResult = 0; + gsi_u32 temp; + + // hash certificate data + MD5Init(&md5); + temp = wsiMakeLittleEndian32(cert->mLength); + MD5Update(&md5, (unsigned char*)&temp, 4); + temp = wsiMakeLittleEndian32(cert->mVersion); + MD5Update(&md5, (unsigned char*)&temp, 4); + temp = wsiMakeLittleEndian32(cert->mPartnerCode); + MD5Update(&md5, (unsigned char*)&temp, 4); + temp = wsiMakeLittleEndian32(cert->mNamespaceId); + MD5Update(&md5, (unsigned char*)&temp, 4); + temp = wsiMakeLittleEndian32(cert->mUserId); + MD5Update(&md5, (unsigned char*)&temp, 4); + temp = wsiMakeLittleEndian32(cert->mProfileId); + MD5Update(&md5, (unsigned char*)&temp, 4); + temp = wsiMakeLittleEndian32(cert->mExpireTime); + MD5Update(&md5, (unsigned char*)&temp, 4); + +#if defined(GSI_UNICODE) + { + char profile_A[WS_LOGIN_NICK_LEN]; + char uniquenick_A[WS_LOGIN_UNIQUENICK_LEN]; + char keyhash_A[WS_LOGIN_KEYHASH_LEN]; + + UCS2ToAsciiString(cert->mProfileNick, profile_A); + UCS2ToAsciiString(cert->mUniqueNick, uniquenick_A); + UCS2ToAsciiString(cert->mCdKeyHash, keyhash_A); + + MD5Update(&md5, (unsigned char*)profile_A, strlen(profile_A)); //FIX for unicode + MD5Update(&md5, (unsigned char*)uniquenick_A, strlen(uniquenick_A)); //FIX for unicode + MD5Update(&md5, (unsigned char*)keyhash_A, strlen(keyhash_A)); //FIX for unicode + } +#else + MD5Update(&md5, (unsigned char*)&cert->mProfileNick, strlen(cert->mProfileNick)); + MD5Update(&md5, (unsigned char*)&cert->mUniqueNick, strlen(cert->mUniqueNick)); + MD5Update(&md5, (unsigned char*)&cert->mCdKeyHash, strlen(cert->mCdKeyHash)); +#endif + + // must be hashed in big endian byte order + // skip leading zeroes + gsLargeIntAddToMD5(&cert->mPeerPublicKey.modulus, &md5); + gsLargeIntAddToMD5(&cert->mPeerPublicKey.exponent, &md5); + + MD5Update(&md5, (unsigned char*)&cert->mServerData, WS_LOGIN_SERVERDATA_LEN); + MD5Final(hash, &md5); + + gsLargeIntSetFromHexString(&sigkeypub.modulus, WS_AUTHSERVICE_SIGNATURE_KEY); + gsLargeIntSetFromHexString(&sigkeypub.exponent, WS_AUTHSERVICE_SIGNATURE_EXP); + cryptResult = gsCryptRSAVerifySignedHash(&sigkeypub, hash, 16, cert->mSignature, WS_LOGIN_SIGNATURE_LEN); + if (cryptResult == 0) + return gsi_true; + else + return gsi_false; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Utility to write a certificate in binary form, does it really belong in this file? +#define WRITE_NETWORK_INT(a) { \ + intNB = (gsi_i32)htonl(a); \ + if(lenoutSoFar + sizeof(intNB) > maxlen) \ + return gsi_false; \ + memcpy(bufout+lenoutSoFar, &intNB, sizeof(intNB)); \ + lenoutSoFar += sizeof(intNB); } +#define WRITE_NTS(a) { \ + if(lenoutSoFar + _tcslen(a) > maxlen) \ + return gsi_false; \ + strcpy(bufout+lenoutSoFar, a); \ + lenoutSoFar += _tcslen(a) + 1; } + +#define WRITE_BINARY(a,l) { \ + if(lenoutSoFar + l > maxlen) \ + return gsi_false; \ + memcpy(bufout+lenoutSoFar, a, l); \ + lenoutSoFar += l; } + +#define WRITE_REV_BINARY(a,l) { \ + int i=(gsi_i32)l; \ + const char * readPos = ((char*)a)+i-1; \ + if (lenoutSoFar + l > maxlen) \ + return gsi_false; \ + while(i > 0) \ + { \ + *(bufout+lenoutSoFar) = *readPos; \ + readPos--; lenoutSoFar++; i--; \ + }; \ + } + +gsi_bool wsLoginCertWriteBinary(const GSLoginCertificate * cert, char * bufout, unsigned int maxlen, unsigned int * lenout) +{ + gsi_i32 intNB; // network byte order int + gsi_i32 lenoutSoFar = 0; // tracks bytes written to bufout + gsi_i32 lenTemp = 0; // for temp length of large ints + + + WRITE_NETWORK_INT(cert->mLength); + WRITE_NETWORK_INT(cert->mVersion); + WRITE_NETWORK_INT(cert->mPartnerCode); + WRITE_NETWORK_INT(cert->mNamespaceId); + WRITE_NETWORK_INT(cert->mUserId); + WRITE_NETWORK_INT(cert->mProfileId); + WRITE_NETWORK_INT(cert->mExpireTime); + +#ifndef GSI_UNICODE + WRITE_NTS(cert->mProfileNick); + WRITE_NTS(cert->mUniqueNick); + WRITE_NTS(cert->mCdKeyHash); +#else + if((lenoutSoFar + _tcslen(cert->mProfileNick) + _tcslen(cert->mUniqueNick) + _tcslen(cert->mCdKeyHash)) > maxlen) + return gsi_false; + // strip unicode to Ascii before writing this to the buffer + lenoutSoFar += UCS2ToAsciiString(cert->mProfileNick, bufout+lenoutSoFar)+1; + lenoutSoFar += UCS2ToAsciiString(cert->mUniqueNick, bufout+lenoutSoFar)+1; + lenoutSoFar += UCS2ToAsciiString(cert->mCdKeyHash, bufout+lenoutSoFar)+1; +#endif + + lenTemp = (gsi_i32)gsLargeIntGetByteLength(&cert->mPeerPublicKey.modulus); // size in bytes, no leading zeroes + WRITE_NETWORK_INT(lenTemp); + WRITE_REV_BINARY(cert->mPeerPublicKey.modulus.mData, (unsigned int)lenTemp); + + lenTemp = (gsi_i32)gsLargeIntGetByteLength(&cert->mPeerPublicKey.exponent); // size in bytes, no leading zeroes + WRITE_NETWORK_INT(lenTemp); + WRITE_REV_BINARY(cert->mPeerPublicKey.exponent.mData, (unsigned int)lenTemp); + + WRITE_NETWORK_INT(WS_LOGIN_SERVERDATA_LEN); + WRITE_BINARY(cert->mServerData, (unsigned int)WS_LOGIN_SERVERDATA_LEN); + + WRITE_NETWORK_INT(GS_CRYPT_RSA_BYTE_SIZE); + WRITE_BINARY(cert->mSignature, (unsigned int)WS_LOGIN_SERVERDATA_LEN); + + *lenout = (gsi_u32)lenoutSoFar; + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Utility to read the binary certificate + +// each read macro first clears the buffer (a) before writing +#define READ_NTOH_INT(a) { \ + gsi_i32 temp; \ + memset(&a, 0, sizeof(a)); \ + memcpy(&temp, bufin, sizeof(gsi_i32)); \ + intHB = (gsi_u32)ntohl(temp); \ + memcpy(&a, &intHB, sizeof(intHB)); \ + if(lenoutSoFar + sizeof(intHB) > maxlen) \ + return gsi_false; \ + bufin += sizeof(gsi_u32); \ + lenoutSoFar += sizeof(intHB); } +#define READ_NTS(a,l) { \ + memset(&a, 0, sizeof(a)); \ + _tcsncpy(a, bufin, l); \ + if(lenoutSoFar + _tcslen(a)+1 > maxlen) \ + return gsi_false; \ + bufin += _tcslen(a)+1; \ + lenoutSoFar += _tcslen(a)+1; } + +#define READ_ASCII(a,l) { \ + char temp[l]; \ + memset(&a, 0, sizeof(a)); \ + strncpy(temp, bufin, l); \ + AsciiToUCS2String(temp, a); \ + if(lenoutSoFar + _tcslen(a)+1 > maxlen) \ + return gsi_false; \ + bufin += _tcslen(a)+1; \ + lenoutSoFar += _tcslen(a)+1; } + + +#define READ_BINARY(a,l) { \ + int index = 0; \ + memset(&a, 0, sizeof(a)); \ + if(lenoutSoFar + l > maxlen) \ + return gsi_false; \ + memcpy(a+index, bufin, l); \ + lenoutSoFar += l; \ + bufin += l; } + +#define READ_REV_BINARY_TO_INT(a,l) { \ + int i=(gsi_i32)l; \ + int index = 0; \ + char temp[4]; \ + const char * readPos = bufin+i-1; \ + memset(&a, 0, sizeof(a)); \ + if (lenoutSoFar + l > maxlen) \ + return gsi_false; \ + while(i > 0) \ + { \ + temp[index%4] = *readPos; \ + if (index%4 == 3) \ + memcpy(a+(index/4), temp, 4); \ + else if (index == (gsi_i32)l-1) \ + memcpy(a+(index/4), temp, l); \ + readPos--; index++; lenoutSoFar++; i--; \ + }; \ + bufin += l; } + + + + +gsi_bool wsLoginCertReadBinary(GSLoginCertificate * certOut, char * bufin, unsigned int maxlen) +{ + gsi_u32 intHB; // host byte order int + gsi_u32 lenoutSoFar = 0; // tracks bufin index to make sure we dont exceed the maxlen + gsi_u32 lenTemp = 0; // for temp length of large ints + + + READ_NTOH_INT(certOut->mLength); + READ_NTOH_INT(certOut->mVersion); + READ_NTOH_INT(certOut->mPartnerCode); + READ_NTOH_INT(certOut->mNamespaceId); + READ_NTOH_INT(certOut->mUserId); + READ_NTOH_INT(certOut->mProfileId); + READ_NTOH_INT(certOut->mExpireTime); + +#ifndef GSI_UNICODE + + READ_NTS(certOut->mProfileNick, WS_LOGIN_NICK_LEN); + READ_NTS(certOut->mUniqueNick, WS_LOGIN_UNIQUENICK_LEN); + READ_NTS(certOut->mCdKeyHash, WS_LOGIN_CDKEY_LEN); +#else + + // parses ascii to unicode before writing into the buffer + READ_ASCII(certOut->mProfileNick, WS_LOGIN_NICK_LEN); + READ_ASCII(certOut->mUniqueNick, WS_LOGIN_UNIQUENICK_LEN); + READ_ASCII(certOut->mCdKeyHash, WS_LOGIN_CDKEY_LEN); +#endif + + READ_NTOH_INT(lenTemp); //size of the modulus data in bytes + // now calculate the length in int's + certOut->mPeerPublicKey.modulus.mLength = (lenTemp / GS_LARGEINT_DIGIT_SIZE_BYTES); + if (lenTemp % GS_LARGEINT_DIGIT_SIZE_BYTES != 0) + certOut->mPeerPublicKey.modulus.mLength++; + + READ_REV_BINARY_TO_INT(certOut->mPeerPublicKey.modulus.mData, lenTemp); + + READ_NTOH_INT(lenTemp); //size of the exponent data in bytes + // now calculate the length in int's + certOut->mPeerPublicKey.exponent.mLength = (lenTemp / GS_LARGEINT_DIGIT_SIZE_BYTES); + if (lenTemp % GS_LARGEINT_DIGIT_SIZE_BYTES != 0) + certOut->mPeerPublicKey.exponent.mLength++; + + READ_REV_BINARY_TO_INT(certOut->mPeerPublicKey.exponent.mData, lenTemp); + + bufin += sizeof(gsi_u32); //skip the prepended length + READ_BINARY(certOut->mServerData, (unsigned int)WS_LOGIN_SERVERDATA_LEN); + + bufin += sizeof(gsi_u32); //skip the prepended length + READ_BINARY(certOut->mSignature, (unsigned int)WS_LOGIN_SERVERDATA_LEN); + + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool wsLoginCertWriteXML(const GSLoginCertificate * cert, const char * aNamespace, GSXmlStreamWriter writer) +{ + GS_ASSERT(cert != NULL); + GS_ASSERT(writer != NULL); + + if (gsi_is_false(gsXmlWriteIntElement(writer, aNamespace, "length", cert->mLength)) || + gsi_is_false(gsXmlWriteIntElement(writer, aNamespace, "version", cert->mVersion)) || + gsi_is_false(gsXmlWriteIntElement(writer, aNamespace, "partnercode", cert->mPartnerCode)) || + gsi_is_false(gsXmlWriteIntElement(writer, aNamespace, "namespaceid", cert->mNamespaceId)) || + gsi_is_false(gsXmlWriteIntElement(writer, aNamespace, "userid", cert->mUserId)) || + gsi_is_false(gsXmlWriteIntElement(writer, aNamespace, "profileid", cert->mProfileId)) || + gsi_is_false(gsXmlWriteIntElement(writer, aNamespace, "expiretime", cert->mExpireTime)) || + gsi_is_false(gsXmlWriteAsciiStringElement(writer, aNamespace, "profilenick", cert->mProfileNick))|| + gsi_is_false(gsXmlWriteAsciiStringElement(writer, aNamespace, "uniquenick", cert->mUniqueNick)) || + gsi_is_false(gsXmlWriteAsciiStringElement(writer, aNamespace, "cdkeyhash", cert->mCdKeyHash)) || + gsi_is_false(gsXmlWriteLargeIntElement(writer, aNamespace, "peerkeymodulus", &cert->mPeerPublicKey.modulus)) || + gsi_is_false(gsXmlWriteLargeIntElement(writer, aNamespace, "peerkeyexponent", &cert->mPeerPublicKey.exponent)) || + gsi_is_false(gsXmlWriteHexBinaryElement(writer, aNamespace, "serverdata", cert->mServerData, WS_LOGIN_SERVERDATA_LEN)) || + gsi_is_false(gsXmlWriteHexBinaryElement(writer, aNamespace, "signature", cert->mSignature, WS_LOGIN_SIGNATURE_LEN)) + ) + { + //gsLargeIntReverseBytes(&cert->mPeerPublicKey.modulus); + //gsLargeIntReverseBytes(&cert->mPeerPublicKey.exponent); + return gsi_false; + } + + //gsLargeIntReverseBytes(&cert->mPeerPublicKey.modulus); + //gsLargeIntReverseBytes(&cert->mPeerPublicKey.exponent); + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool wsLoginCertReadXML(GSLoginCertificate * cert, GSXmlStreamReader reader) +{ + char hexstr[GS_CRYPT_RSA_BYTE_SIZE*2 +1]; // temp storage for key hex strings + int hexlen; + + GS_ASSERT(cert != NULL); + GS_ASSERT(reader != NULL); + + if (gsi_is_false(gsXmlReadChildAsInt (reader, "length", (int*)&cert->mLength)) || + gsi_is_false(gsXmlReadChildAsInt (reader, "version", (int*)&cert->mVersion)) || + gsi_is_false(gsXmlReadChildAsInt (reader, "partnercode",(int*)&cert->mPartnerCode)) || + gsi_is_false(gsXmlReadChildAsInt (reader, "namespaceid",(int*)&cert->mNamespaceId)) || + gsi_is_false(gsXmlReadChildAsInt (reader, "userid", (int*)&cert->mUserId)) || + gsi_is_false(gsXmlReadChildAsInt (reader, "profileid", (int*)&cert->mProfileId)) || + gsi_is_false(gsXmlReadChildAsInt (reader, "expiretime", (int*)&cert->mExpireTime)) || + gsi_is_false(gsXmlReadChildAsTStringNT (reader, "profilenick", cert->mProfileNick, WS_LOGIN_NICK_LEN)) || + gsi_is_false(gsXmlReadChildAsTStringNT (reader, "uniquenick", cert->mUniqueNick, WS_LOGIN_UNIQUENICK_LEN)) || + gsi_is_false(gsXmlReadChildAsTStringNT (reader, "cdkeyhash", cert->mCdKeyHash, WS_LOGIN_KEYHASH_LEN)) || + + gsi_is_false(gsXmlReadChildAsStringNT (reader, "peerkeymodulus", hexstr, GS_CRYPT_RSA_BYTE_SIZE*2 +1)) || + gsi_is_false(gsLargeIntSetFromHexString(&cert->mPeerPublicKey.modulus, hexstr)) || + + gsi_is_false(gsXmlReadChildAsStringNT (reader, "peerkeyexponent", hexstr, GS_CRYPT_RSA_BYTE_SIZE*2 +1)) || + gsi_is_false(gsLargeIntSetFromHexString(&cert->mPeerPublicKey.exponent, hexstr)) || + + gsi_is_false(gsXmlReadChildAsHexBinary(reader, "serverdata", cert->mServerData, WS_LOGIN_SERVERDATA_LEN, &hexlen)) || + + gsi_is_false(gsXmlReadChildAsHexBinary(reader, "signature", cert->mSignature, WS_LOGIN_SIGNATURE_LEN, &hexlen)) + ) + { + return gsi_false; + } + return gsi_true; +} + +#pragma warning(default: 4267) \ No newline at end of file diff --git a/code/gamespy/webservices/AuthService.h b/code/gamespy/webservices/AuthService.h new file mode 100644 index 00000000..dc99d619 --- /dev/null +++ b/code/gamespy/webservices/AuthService.h @@ -0,0 +1,199 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __AUTHSERVICE_H__ +#define __AUTHSERVICE_H__ + + +// ***** Authentication web services. +// +// ***** PUBLIC INTERFACE AT THE BOTTOM OF THE FILE + +#include "../common/gsSoap.h" +#include "../common/gsCrypt.h" +#include "../common/gsLargeInt.h" + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +// URL for sc services. +#define WS_LOGIN_MAX_URL_LEN (128) +extern char wsAuthServiceURL[WS_LOGIN_MAX_URL_LEN]; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define WSLogin_PARTNERCODE_GAMESPY 0 +#define WSLogin_NAMESPACE_SHARED_NONUNIQUE 0 +#define WSLogin_NAMESPACE_SHARED_UNIQUE 1 + +typedef enum WSLoginValue +{ + // Login response code (mResponseCode) + // -- GameSpy Devs: Must match server + WSLogin_Success = 0, + WSLogin_ServerInitFailed, + + WSLogin_UserNotFound, + WSLogin_InvalidPassword, + WSLogin_InvalidProfile, + WSLogin_UniqueNickExpired, + + WSLogin_DBError, + WSLogin_ServerError, + WSLogin_FailureMax, // must be the last failure + + // Login result (mLoginResult) + WSLogin_HttpError = 100, // ghttp reported an error, response ignored + WSLogin_ParseError, // couldn't parse http response + WSLogin_InvalidCertificate, // login success but certificate was invalid! + WSLogin_LoginFailed, // failed login or other error condition + WSLogin_OutOfMemory, // could not process due to insufficient memory + WSLogin_InvalidParameters, // check the function arguments + WSLogin_NoAvailabilityCheck,// No availability check was performed + WSLogin_Cancelled, // login request was cancelled + WSLogin_UnknownError // error occured, but detailed information not available + +} WSLoginValue; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define WS_LOGIN_SIGKEY_LEN_BITS (GS_CRYPT_RSA_BINARY_SIZE) +#define WS_LOGIN_PEERKEY_LEN_BITS (GS_CRYPT_RSA_BINARY_SIZE) + +#define WS_LOGIN_NICK_LEN (30+1) +#define WS_LOGIN_EMAIL_LEN (50+1) +#define WS_LOGIN_PASSWORD_LEN (30+1) +#define WS_LOGIN_UNIQUENICK_LEN (20+1) +#define WS_LOGIN_CDKEY_LEN (64+1) +#define WS_LOGIN_PEERKEYMOD_LEN (WS_LOGIN_PEERKEY_LEN_BITS/8) +#define WS_LOGIN_PEERKEYEXP_LEN (WS_LOGIN_PEERKEY_LEN_BITS/8) +#define WS_LOGIN_PEERKEYPRV_LEN (WS_LOGIN_PEERKEY_LEN_BITS/8) +#define WS_LOGIN_KEYHASH_LEN (33) // 16 byte hash in hexstr +1 for NULL +#define WS_LOGIN_SIGNATURE_LEN (WS_LOGIN_SIGKEY_LEN_BITS/8) +#define WS_LOGIN_SERVERDATA_LEN (WS_LOGIN_PEERKEY_LEN_BITS/8) +#define WS_LOGIN_AUTHTOKEN_LEN (256) +#define WS_LOGIN_PARTNERCHALLENGE_LEN (256) + +// A user's login certificate, signed by the GameSpy AuthService +// The certificate is public and may be freely passed around +// Avoid use of pointer members so that structure may be easily copied +typedef struct GSLoginCertificate +{ + gsi_bool mIsValid; + + gsi_u32 mLength; + gsi_u32 mVersion; + gsi_u32 mPartnerCode; // aka Account space + gsi_u32 mNamespaceId; + gsi_u32 mUserId; + gsi_u32 mProfileId; + gsi_u32 mExpireTime; + gsi_char mProfileNick[WS_LOGIN_NICK_LEN]; + gsi_char mUniqueNick[WS_LOGIN_UNIQUENICK_LEN]; + gsi_char mCdKeyHash[WS_LOGIN_KEYHASH_LEN]; // hexstr - bigendian + gsCryptRSAKey mPeerPublicKey; + gsi_u8 mSignature[GS_CRYPT_RSA_BYTE_SIZE]; // binary - bigendian + gsi_u8 mServerData[WS_LOGIN_SERVERDATA_LEN]; // binary - bigendian +} GSLoginCertificate; + +// Private information for the owner of the certificate only +// -- careful! private key information must be kept secret -- +typedef struct GSLoginCertificatePrivate +{ + gsCryptRSAKey mPeerPrivateKey; + char mKeyHash[GS_CRYPT_MD5_HASHSIZE]; +} GSLoginPrivateData; + +//typedef char GSLoginCertificateKeyHash[GS_CRYPT_MD5_HASHSIZE]; // Hash of private key, for simple auth + +typedef enum +{ + wsLoginType_INVALID, + wsLoginType_PROFILE, + wsLoginType_UNIQUENICK, + wsLoginType_GPTICKET, + wsLoginType_REMOTEAUTH +} WSLoginType; + +/* +typedef struct WSLoginProfileRequest +{ + int mPartnerCode; + char mProfileName[WS_LOGIN_NICK_LEN]; + char mEmailAddress[WS_LOGIN_EMAIL_LEN]; + char mPassword[WS_LOGIN_PASSWORD_LEN]; + char mCdKeyHash[WS_LOGIN_KEYHASH_LEN]; + void * mUserData; +} WSLoginProfileRequest; + +typedef struct WSLoginUniqueRequest +{ + int mPartnerCode; + char mUniqueNick[WS_LOGIN_NICK_LEN]; + char mPassword[WS_LOGIN_PASSWORD_LEN]; + char mCdKeyHash[WS_LOGIN_KEYHASH_LEN]; + void * mUserData; +} WSLoginUniqueRequest;*/ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// CERTIFICATE login callback format +typedef struct WSLoginResponse +{ + WSLoginValue mLoginResult; // SDK high level result, e.g. LoginFailed + WSLoginValue mResponseCode; // server's result code, e.g. BadPassword + GSLoginCertificate mCertificate; // Show this to others (prooves: "Bill is a valid user") + GSLoginPrivateData mPrivateData; // Keep this secret! (prooves: "I am Bill") + void * mUserData; +} WSLoginResponse; + +typedef void (*WSLoginCallback)(GHTTPResult httpResult, WSLoginResponse * response, void * userData); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// PS3 login callback format +typedef struct WSLoginPs3CertResponse +{ + WSLoginValue mLoginResult; // SDK high level result, e.g. LoginFailed + WSLoginValue mResponseCode; // server's result code, e.g. BadPassword + char mRemoteAuthToken[WS_LOGIN_AUTHTOKEN_LEN]; // Show this to others + char mPartnerChallenge[WS_LOGIN_PARTNERCHALLENGE_LEN]; // keep this secret! (It's a "password" for the token.) + void * mUserData; +} WSLoginPs3CertResponse; + +typedef void (*WSLoginPs3CertCallback)(GHTTPResult httpResult, WSLoginPs3CertResponse * response, void * userData); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Services to obtain a certificate +gsi_u32 wsLoginProfile(int partnerCode, int namespaceId, const gsi_char * profileNick, const gsi_char * email, const gsi_char * password, const gsi_char * cdkeyhash, WSLoginCallback callback, void * userData); +gsi_u32 wsLoginUnique (int partnerCode, int namespaceId, const gsi_char * uniqueNick, const gsi_char * password, const gsi_char * cdkeyhash, WSLoginCallback callback, void * userData); +gsi_u32 wsLoginRemoteAuth(int partnerCode, int namespaceId, const gsi_char authtoken[WS_LOGIN_AUTHTOKEN_LEN], const gsi_char partnerChallenge[WS_LOGIN_PARTNERCHALLENGE_LEN], WSLoginCallback callback, void * userData); + +// Services to obtain a remote auth token +gsi_u32 wsLoginPs3Cert(int gameId, int partnerCode, int namespaceId, const gsi_u8 * ps3cert, int certLen, WSLoginPs3CertCallback callback, void * userData); + +// Certificate Utilities, for use after obtaining a certificate +gsi_bool wsLoginCertIsValid (const GSLoginCertificate * cert); +gsi_bool wsLoginCertWriteXML (const GSLoginCertificate * cert, const char * anamespace, GSXmlStreamWriter writer); +gsi_bool wsLoginCertWriteBinary(const GSLoginCertificate * cert, char * bufout, unsigned int maxlen, unsigned int * lenout); +gsi_bool wsLoginCertReadBinary (GSLoginCertificate * certOut, char * bufin, unsigned int maxlen); +gsi_bool wsLoginCertReadXML (GSLoginCertificate * cert, GSXmlStreamReader reader); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif //__AUTHSERVICE_H__